summaryrefslogtreecommitdiffstats
path: root/extras/waf_xattr.py
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2019-03-17 17:31:05 +0100
committerDavid Robillard <d@drobilla.net>2019-03-17 17:31:05 +0100
commit406f89271452fdb573c7e28113b1ed08ff2b4eda (patch)
treed2dcbaf61f3749f73dc7a5e10d3fc6cd5e6e129a /extras/waf_xattr.py
parent7983a5aae615290d04fd43cbc2752f8cf4a46d10 (diff)
downloadsuil-406f89271452fdb573c7e28113b1ed08ff2b4eda.tar.gz
suil-406f89271452fdb573c7e28113b1ed08ff2b4eda.tar.bz2
suil-406f89271452fdb573c7e28113b1ed08ff2b4eda.zip
Squashed 'waflib/' changes from 915dcb1..e7a29b6
e7a29b6 Upgrade to waf 2.0.15 8280f9d Add command for running executables from the build directory 8073c1a Make make_simple_dox() safe in case of exception 70d03b8 Avoid use of global counter hacks for configuration display b7d689a Rewrite test framework 94deadf Automatically add options and move add_flags() to options context f4259ee Reduce system include path noise 927b608 Automatically display configuration header c44b8f3 Set line justification from a constant in the wscript a48e26f Automatically detect if wscript has a test hook ef66724 Save runtime variables in the environment 63bcbcd Clean up TestContext b1d9505 Add ExecutionContext for setting runtime environment 387c1df Add show_diff() and test_file_equals() utilities 29d4d29 Fix in-tree library paths 9fde01f Add custom configuration context 6d3612f Add lib_path_name constant git-subtree-dir: waflib git-subtree-split: e7a29b6b9b2f842314244c23c14d8f8f560904e1
Diffstat (limited to 'extras/waf_xattr.py')
-rw-r--r--extras/waf_xattr.py150
1 files changed, 150 insertions, 0 deletions
diff --git a/extras/waf_xattr.py b/extras/waf_xattr.py
new file mode 100644
index 0000000..351dd63
--- /dev/null
+++ b/extras/waf_xattr.py
@@ -0,0 +1,150 @@
+#! /usr/bin/env python
+# encoding: utf-8
+
+"""
+Use extended attributes instead of database files
+
+1. Input files will be made writable
+2. This is only for systems providing extended filesystem attributes
+3. By default, hashes are calculated only if timestamp/size change (HASH_CACHE below)
+4. The module enables "deep_inputs" on all tasks by propagating task signatures
+5. This module also skips task signature comparisons for task code changes due to point 4.
+6. This module is for Python3/Linux only, but it could be extended to Python2/other systems
+ using the xattr library
+7. For projects in which tasks always declare output files, it should be possible to
+ store the rest of build context attributes on output files (imp_sigs, raw_deps and node_deps)
+ but this is not done here
+
+On a simple C++ project benchmark, the variations before and after adding waf_xattr.py were observed:
+total build time: 20s -> 22s
+no-op build time: 2.4s -> 1.8s
+pickle file size: 2.9MB -> 2.6MB
+"""
+
+import os
+from waflib import Logs, Node, Task, Utils, Errors
+from waflib.Task import SKIP_ME, RUN_ME, CANCEL_ME, ASK_LATER, SKIPPED, MISSING
+
+HASH_CACHE = True
+SIG_VAR = 'user.waf.sig'
+SEP = ','.encode()
+TEMPLATE = '%b%d,%d'.encode()
+
+try:
+ PermissionError
+except NameError:
+ PermissionError = IOError
+
+def getxattr(self):
+ return os.getxattr(self.abspath(), SIG_VAR)
+
+def setxattr(self, val):
+ os.setxattr(self.abspath(), SIG_VAR, val)
+
+def h_file(self):
+ try:
+ ret = getxattr(self)
+ except OSError:
+ if HASH_CACHE:
+ st = os.stat(self.abspath())
+ mtime = st.st_mtime
+ size = st.st_size
+ else:
+ if len(ret) == 16:
+ # for build directory files
+ return ret
+
+ if HASH_CACHE:
+ # check if timestamp and mtime match to avoid re-hashing
+ st = os.stat(self.abspath())
+ mtime, size = ret[16:].split(SEP)
+ if int(1000 * st.st_mtime) == int(mtime) and st.st_size == int(size):
+ return ret[:16]
+
+ ret = Utils.h_file(self.abspath())
+ if HASH_CACHE:
+ val = TEMPLATE % (ret, int(1000 * st.st_mtime), int(st.st_size))
+ try:
+ setxattr(self, val)
+ except PermissionError:
+ os.chmod(self.abspath(), st.st_mode | 128)
+ setxattr(self, val)
+ return ret
+
+def runnable_status(self):
+ bld = self.generator.bld
+ if bld.is_install < 0:
+ return SKIP_ME
+
+ for t in self.run_after:
+ if not t.hasrun:
+ return ASK_LATER
+ elif t.hasrun < SKIPPED:
+ # a dependency has an error
+ return CANCEL_ME
+
+ # first compute the signature
+ try:
+ new_sig = self.signature()
+ except Errors.TaskNotReady:
+ return ASK_LATER
+
+ if not self.outputs:
+ # compare the signature to a signature computed previously
+ # this part is only for tasks with no output files
+ key = self.uid()
+ try:
+ prev_sig = bld.task_sigs[key]
+ except KeyError:
+ Logs.debug('task: task %r must run: it was never run before or the task code changed', self)
+ return RUN_ME
+ if new_sig != prev_sig:
+ Logs.debug('task: task %r must run: the task signature changed', self)
+ return RUN_ME
+
+ # compare the signatures of the outputs to make a decision
+ for node in self.outputs:
+ try:
+ sig = node.h_file()
+ except EnvironmentError:
+ Logs.debug('task: task %r must run: an output node does not exist', self)
+ return RUN_ME
+ if sig != new_sig:
+ Logs.debug('task: task %r must run: an output node is stale', self)
+ return RUN_ME
+
+ return (self.always_run and RUN_ME) or SKIP_ME
+
+def post_run(self):
+ bld = self.generator.bld
+ sig = self.signature()
+ for node in self.outputs:
+ if not node.exists():
+ self.hasrun = MISSING
+ self.err_msg = '-> missing file: %r' % node.abspath()
+ raise Errors.WafError(self.err_msg)
+ os.setxattr(node.abspath(), 'user.waf.sig', sig)
+ if not self.outputs:
+ # only for task with no outputs
+ bld.task_sigs[self.uid()] = sig
+ if not self.keep_last_cmd:
+ try:
+ del self.last_cmd
+ except AttributeError:
+ pass
+
+try:
+ os.getxattr
+except AttributeError:
+ pass
+else:
+ h_file.__doc__ = Node.Node.h_file.__doc__
+
+ # keep file hashes as file attributes
+ Node.Node.h_file = h_file
+
+ # enable "deep_inputs" on all tasks
+ Task.Task.runnable_status = runnable_status
+ Task.Task.post_run = post_run
+ Task.Task.sig_deep_inputs = Utils.nada
+