3 # Thomas Nagy, 2005-2018 (ita)
6 Tasks represent atomic operations such as processes.
9 import os
, re
, sys
, tempfile
, traceback
10 from waflib
import Utils
, Logs
, Errors
14 """The task was not executed yet"""
17 """The task has been executed but the files have not been created"""
20 """The task execution returned a non-zero exit status"""
23 """An exception occurred in the task execution"""
26 """A dependency for the task is missing so it was cancelled"""
29 """The task did not have to be executed"""
32 """The task was successfully executed"""
35 """The task is not ready to be executed"""
38 """The task does not need to be executed"""
41 """The task must be executed"""
44 """The task cannot be executed because of a dependency problem"""
46 COMPILE_TEMPLATE_SHELL
= '''
54 if isinstance(xx, str): return [xx]
56 tsk.last_cmd = cmd = \'\'\' %s \'\'\' % s
57 return tsk.exec_command(cmd, cwd=cwdx, env=env.env or None)
60 COMPILE_TEMPLATE_NOSHELL
= '''
67 if isinstance(xx, str): return [xx]
69 def merge(lst1, lst2):
71 return lst1[:-1] + [lst1[-1] + lst2[0]] + lst2[1:]
76 lst = [x for x in lst if x]
78 return tsk.exec_command(lst, cwd=cwdx, env=env.env or None)
81 COMPILE_TEMPLATE_SIG_VARS
= '''
83 sig = tsk.generator.bld.hash_env_vars(tsk.env, tsk.vars)
92 tsk.m.update(repr(buf).encode())
97 The metaclass :py:class:`waflib.Task.store_task_type` stores all class tasks
98 created by user scripts or Waf tools to this dict. It maps class names to class objects.
101 class store_task_type(type):
103 Metaclass: store the task classes into the dict pointed by the
104 class attribute 'register' which defaults to :py:const:`waflib.Task.classes`,
106 The attribute 'run_str' is compiled into a method 'run' bound to the task class.
108 def __init__(cls
, name
, bases
, dict):
109 super(store_task_type
, cls
).__init
__(name
, bases
, dict)
112 if name
!= 'evil' and name
!= 'Task':
113 if getattr(cls
, 'run_str', None):
114 # if a string is provided, convert it to a method
115 (f
, dvars
) = compile_fun(cls
.run_str
, cls
.shell
)
116 cls
.hcode
= Utils
.h_cmd(cls
.run_str
)
117 cls
.orig_run_str
= cls
.run_str
118 # change the name of run_str or it is impossible to subclass with a function
122 cls
.vars = list(set(cls
.vars + dvars
))
125 fun
= compile_sig_vars(cls
.vars)
128 elif getattr(cls
, 'run', None) and not 'hcode' in cls
.__dict
__:
129 # getattr(cls, 'hcode') would look in the upper classes
130 cls
.hcode
= Utils
.h_cmd(cls
.run
)
133 getattr(cls
, 'register', classes
)[name
] = cls
135 evil
= store_task_type('evil', (object,), {})
136 "Base class provided to avoid writing a metaclass, so the code can run in python 2.6 and 3.x unmodified"
140 Task objects represents actions to perform such as commands to execute by calling the `run` method.
142 Detecting when to execute a task occurs in the method :py:meth:`waflib.Task.Task.runnable_status`.
144 Detecting which tasks to execute is performed through a hash value returned by
145 :py:meth:`waflib.Task.Task.signature`. The task signature is persistent from build to build.
148 """ConfigSet variables that should trigger a rebuild (class attribute used for :py:meth:`waflib.Task.Task.sig_vars`)"""
151 """Specify whether task instances must always be executed or not (class attribute)"""
154 """Execute the command with the shell (class attribute)"""
157 """Color for the console display, see :py:const:`waflib.Logs.colors_lst`"""
160 """File extensions that objects of this task class may use"""
163 """File extensions that objects of this task class may create"""
166 """The instances of this class are executed before the instances of classes whose names are in this list"""
169 """The instances of this class are executed after the instances of classes whose names are in this list"""
171 hcode
= Utils
.SIG_NIL
172 """String representing an additional hash for the class representation"""
174 keep_last_cmd
= False
175 """Whether to keep the last command executed on the instance after execution.
176 This may be useful for certain extensions but it can a lot of memory.
180 """Optional weight to tune the priority for task instances.
181 The higher, the earlier. The weight only applies to single task objects."""
184 """Optional weight to tune the priority of task instances and whole subtrees.
185 The higher, the earlier."""
188 """Priority order set by the scheduler on instances during the build phase.
189 You most likely do not need to set it.
192 __slots__
= ('hasrun', 'generator', 'env', 'inputs', 'outputs', 'dep_nodes', 'run_after')
194 def __init__(self
, *k
, **kw
):
195 self
.hasrun
= NOT_RUN
197 self
.generator
= kw
['generator']
199 self
.generator
= self
202 """:py:class:`waflib.ConfigSet.ConfigSet` object (make sure to provide one)"""
205 """List of input nodes, which represent the files used by the task instance"""
208 """List of output nodes, which represent the files created by the task instance"""
211 """List of additional nodes to depend on"""
213 self
.run_after
= set()
214 """Set of tasks that must be executed before this one"""
216 def __lt__(self
, other
):
217 return self
.priority() > other
.priority()
218 def __le__(self
, other
):
219 return self
.priority() >= other
.priority()
220 def __gt__(self
, other
):
221 return self
.priority() < other
.priority()
222 def __ge__(self
, other
):
223 return self
.priority() <= other
.priority()
227 :return: current working directory
228 :rtype: :py:class:`waflib.Node.Node`
230 bld
= self
.generator
.bld
231 ret
= getattr(self
, 'cwd', None) or getattr(bld
, 'cwd', bld
.bldnode
)
232 if isinstance(ret
, str):
233 if os
.path
.isabs(ret
):
234 ret
= bld
.root
.make_node(ret
)
236 ret
= self
.generator
.path
.make_node(ret
)
239 def quote_flag(self
, x
):
241 Surround a process argument by quotes so that a list of arguments can be written to a file
250 x
= x
.replace('\\', '\\\\')
252 x
= x
.replace('"', '\\"')
253 if old
!= x
or ' ' in x
or '\t' in x
or "'" in x
:
259 Priority of execution; the higher, the earlier
261 :return: the priority value
262 :rtype: a tuple of numeric values
264 return (self
.weight
+ self
.prio_order
, - getattr(self
.generator
, 'tg_idx_count', 0))
266 def split_argfile(self
, cmd
):
268 Splits a list of process commands into the executable part and its list of arguments
270 :return: a tuple containing the executable first and then the rest of arguments
273 return ([cmd
[0]], [self
.quote_flag(x
) for x
in cmd
[1:]])
275 def exec_command(self
, cmd
, **kw
):
277 Wrapper for :py:meth:`waflib.Context.Context.exec_command`.
278 This version set the current working directory (``build.variant_dir``),
279 applies PATH settings (if self.env.PATH is provided), and can run long
280 commands through a temporary ``@argfile``.
282 :param cmd: process command to execute
283 :type cmd: list of string (best) or string (process will use a shell)
284 :return: the return code
289 #. cwd: current working directory (Node or string)
290 #. stdout: set to None to prevent waf from capturing the process standard output
291 #. stderr: set to None to prevent waf from capturing the process standard error
292 #. timeout: timeout value (Python 3)
295 kw
['cwd'] = self
.get_cwd()
297 if hasattr(self
, 'timeout'):
298 kw
['timeout'] = self
.timeout
301 env
= kw
['env'] = dict(kw
.get('env') or self
.env
.env
or os
.environ
)
302 env
['PATH'] = self
.env
.PATH
if isinstance(self
.env
.PATH
, str) else os
.pathsep
.join(self
.env
.PATH
)
304 if hasattr(self
, 'stdout'):
305 kw
['stdout'] = self
.stdout
306 if hasattr(self
, 'stderr'):
307 kw
['stderr'] = self
.stderr
309 if not isinstance(cmd
, str):
311 # win32 compares the resulting length http://support.microsoft.com/kb/830473
312 too_long
= sum([len(arg
) for arg
in cmd
]) + len(cmd
) > 8192
314 # non-win32 counts the amount of arguments (200k)
315 too_long
= len(cmd
) > 200000
317 if too_long
and getattr(self
, 'allow_argsfile', True):
318 # Shunt arguments to a temporary file if the command is too long.
319 cmd
, args
= self
.split_argfile(cmd
)
321 (fd
, tmp
) = tempfile
.mkstemp()
322 os
.write(fd
, '\r\n'.join(args
).encode())
325 Logs
.debug('argfile: @%r -> %r', tmp
, args
)
326 return self
.generator
.bld
.exec_command(cmd
+ ['@' + tmp
], **kw
)
331 # anti-virus and indexers can keep files open -_-
333 return self
.generator
.bld
.exec_command(cmd
, **kw
)
337 Runs the task and handles errors
339 :return: 0 or None if everything is fine
342 # remove the task signature immediately before it is executed
343 # so that the task will be executed again in case of failure
345 del self
.generator
.bld
.task_sigs
[self
.uid()]
352 self
.err_msg
= traceback
.format_exc()
353 self
.hasrun
= EXCEPTION
357 self
.hasrun
= CRASHED
361 except Errors
.WafError
:
364 self
.err_msg
= traceback
.format_exc()
365 self
.hasrun
= EXCEPTION
367 self
.hasrun
= SUCCESS
369 if self
.hasrun
!= SUCCESS
and self
.scan
:
370 # rescan dependencies on next run
372 del self
.generator
.bld
.imp_sigs
[self
.uid()]
376 def log_display(self
, bld
):
377 "Writes the execution status on the context logger"
378 if self
.generator
.bld
.progress_bar
== 3:
388 if self
.generator
.bld
.progress_bar
== 1:
389 c1
= Logs
.colors
.cursor_off
390 c2
= Logs
.colors
.cursor_on
391 logger
.info(s
, extra
={'stream': sys
.stderr
, 'terminator':'', 'c1': c1
, 'c2' : c2
})
393 logger
.info(s
, extra
={'terminator':'', 'c1': '', 'c2' : ''})
397 Returns an execution status for the console, the progress bar, or the IDE output.
401 col1
= Logs
.colors(self
.color
)
402 col2
= Logs
.colors
.NORMAL
403 master
= self
.generator
.bld
.producer
406 # the current task position, computed as late as possible
407 return master
.processed
- master
.ready
.qsize()
409 if self
.generator
.bld
.progress_bar
== 1:
410 return self
.generator
.bld
.progress_line(cur(), master
.total
, col1
, col2
)
412 if self
.generator
.bld
.progress_bar
== 2:
413 ela
= str(self
.generator
.bld
.timer
)
415 ins
= ','.join([n
.name
for n
in self
.inputs
])
416 except AttributeError:
419 outs
= ','.join([n
.name
for n
in self
.outputs
])
420 except AttributeError:
422 return '|Total %s|Current %s|Inputs %s|Outputs %s|Time %s|\n' % (master
.total
, cur(), ins
, outs
, ela
)
430 fs
= '[%%%dd/%%%dd] %%s%%s%%s%%s\n' % (n
, n
)
434 return fs
% (cur(), total
, kw
, col1
, s
, col2
)
436 def hash_constraints(self
):
438 Identifies a task type for all the constraints relevant for the scheduler: precedence, file production
440 :return: a hash value
443 return (tuple(self
.before
), tuple(self
.after
), tuple(self
.ext_in
), tuple(self
.ext_out
), self
.__class
__.__name
__, self
.hcode
)
445 def format_error(self
):
447 Returns an error message to display the build failure reasons
452 msg
= ': %r\n%r' % (self
, getattr(self
, 'last_cmd', ''))
454 msg
= ' (run with -v to display more information)'
455 name
= getattr(self
.generator
, 'name', '')
456 if getattr(self
, "err_msg", None):
458 elif not self
.hasrun
:
459 return 'task in %r was not executed for some reason: %r' % (name
, self
)
460 elif self
.hasrun
== CRASHED
:
462 return ' -> task in %r failed with exit status %r%s' % (name
, self
.err_code
, msg
)
463 except AttributeError:
464 return ' -> task in %r failed%s' % (name
, msg
)
465 elif self
.hasrun
== MISSING
:
466 return ' -> missing files in %r%s' % (name
, msg
)
467 elif self
.hasrun
== CANCELED
:
468 return ' -> %r canceled because of missing dependencies' % name
470 return 'invalid status for task in %r: %r' % (name
, self
.hasrun
)
472 def colon(self
, var1
, var2
):
474 Enable scriptlet expressions of the form ${FOO_ST:FOO}
475 If the first variable (FOO_ST) is empty, then an empty list is returned
477 The results will be slightly different if FOO_ST is a list, for example::
479 env.FOO = ['p1', 'p2']
481 # ${FOO_ST:FOO} returns
484 env.FOO_ST = ['-a', '-b']
485 # ${FOO_ST:FOO} returns
486 ['-a', '-b', 'p1', '-a', '-b', 'p2']
492 if isinstance(var2
, str):
496 if isinstance(tmp
, str):
497 return [tmp
% x
for x
in it
]
506 "string to display to the user"
507 name
= self
.__class
__.__name
__
509 if name
.endswith(('lib', 'program')) or not self
.inputs
:
510 node
= self
.outputs
[0]
511 return node
.path_from(node
.ctx
.launch_node())
512 if not (self
.inputs
or self
.outputs
):
513 return self
.__class
__.__name
__
514 if len(self
.inputs
) == 1:
515 node
= self
.inputs
[0]
516 return node
.path_from(node
.ctx
.launch_node())
518 src_str
= ' '.join([a
.path_from(a
.ctx
.launch_node()) for a
in self
.inputs
])
519 tgt_str
= ' '.join([a
.path_from(a
.ctx
.launch_node()) for a
in self
.outputs
])
524 return '%s: %s%s%s' % (self
.__class
__.__name
__, src_str
, sep
, tgt_str
)
527 "Display keyword used to prettify the console outputs"
528 name
= self
.__class
__.__name
__
529 if name
.endswith(('lib', 'program')):
531 if len(self
.inputs
) == 1 and len(self
.outputs
) == 1:
541 "for debugging purposes"
543 ins
= ",".join([x
.name
for x
in self
.inputs
])
544 outs
= ",".join([x
.name
for x
in self
.outputs
])
545 except AttributeError:
546 ins
= ",".join([str(x
) for x
in self
.inputs
])
547 outs
= ",".join([str(x
) for x
in self
.outputs
])
548 return "".join(['\n\t{task %r: ' % id(self
), self
.__class
__.__name
__, " ", ins
, " -> ", outs
, '}'])
552 Returns an identifier used to determine if tasks are up-to-date. Since the
553 identifier will be stored between executions, it must be:
555 - unique for a task: no two tasks return the same value (for a given build context)
556 - the same for a given task instance
558 By default, the node paths, the class name, and the function are used
559 as inputs to compute a hash.
561 The pointer to the object (python built-in 'id') will change between build executions,
562 and must be avoided in such hashes.
569 except AttributeError:
570 m
= Utils
.md5(self
.__class
__.__name
__)
572 for x
in self
.inputs
+ self
.outputs
:
574 self
.uid_
= m
.digest()
577 def set_inputs(self
, inp
):
579 Appends the nodes to the *inputs* list
581 :param inp: input nodes
582 :type inp: node or list of nodes
584 if isinstance(inp
, list):
587 self
.inputs
.append(inp
)
589 def set_outputs(self
, out
):
591 Appends the nodes to the *outputs* list
593 :param out: output nodes
594 :type out: node or list of nodes
596 if isinstance(out
, list):
599 self
.outputs
.append(out
)
601 def set_run_after(self
, task
):
603 Run this task only after the given *task*.
605 Calling this method from :py:meth:`waflib.Task.Task.runnable_status` may cause
606 build deadlocks; see :py:meth:`waflib.Tools.fc.fc.runnable_status` for details.
609 :type task: :py:class:`waflib.Task.Task`
611 assert isinstance(task
, Task
)
612 self
.run_after
.add(task
)
616 Task signatures are stored between build executions, they are use to track the changes
617 made to the input nodes (not to the outputs!). The signature hashes data from various sources:
619 * explicit dependencies: files listed in the inputs (list of node objects) :py:meth:`waflib.Task.Task.sig_explicit_deps`
620 * implicit dependencies: list of nodes returned by scanner methods (when present) :py:meth:`waflib.Task.Task.sig_implicit_deps`
621 * hashed data: variables/values read from task.vars/task.env :py:meth:`waflib.Task.Task.sig_vars`
623 If the signature is expected to give a different result, clear the cache kept in ``self.cache_sig``::
625 from waflib import Task
626 class cls(Task.Task):
628 sig = super(Task.Task, self).signature()
629 delattr(self, 'cache_sig')
630 return super(Task.Task, self).signature()
632 :return: the signature value
633 :rtype: string or bytes
636 return self
.cache_sig
637 except AttributeError:
640 self
.m
= Utils
.md5(self
.hcode
)
643 self
.sig_explicit_deps()
648 # implicit deps / scanner results
651 self
.sig_implicit_deps()
652 except Errors
.TaskRescan
:
653 return self
.signature()
655 ret
= self
.cache_sig
= self
.m
.digest()
658 def runnable_status(self
):
660 Returns the Task status
662 :return: a task state in :py:const:`waflib.Task.RUN_ME`,
663 :py:const:`waflib.Task.SKIP_ME`, :py:const:`waflib.Task.CANCEL_ME` or :py:const:`waflib.Task.ASK_LATER`.
666 bld
= self
.generator
.bld
667 if bld
.is_install
< 0:
670 for t
in self
.run_after
:
673 elif t
.hasrun
< SKIPPED
:
674 # a dependency has an error
677 # first compute the signature
679 new_sig
= self
.signature()
680 except Errors
.TaskNotReady
:
683 # compare the signature to a signature computed previously
686 prev_sig
= bld
.task_sigs
[key
]
688 Logs
.debug('task: task %r must run: it was never run before or the task code changed', self
)
691 if new_sig
!= prev_sig
:
692 Logs
.debug('task: task %r must run: the task signature changed', self
)
695 # compare the signatures of the outputs
696 for node
in self
.outputs
:
697 sig
= bld
.node_sigs
.get(node
)
699 Logs
.debug('task: task %r must run: an output node has no signature', self
)
702 Logs
.debug('task: task %r must run: an output node was produced by another task', self
)
704 if not node
.exists():
705 Logs
.debug('task: task %r must run: an output node does not exist', self
)
708 return (self
.always_run
and RUN_ME
) or SKIP_ME
712 Called after successful execution to record that the task has run by
713 updating the entry in :py:attr:`waflib.Build.BuildContext.task_sigs`.
715 bld
= self
.generator
.bld
716 for node
in self
.outputs
:
717 if not node
.exists():
718 self
.hasrun
= MISSING
719 self
.err_msg
= '-> missing file: %r' % node
.abspath()
720 raise Errors
.WafError(self
.err_msg
)
721 bld
.node_sigs
[node
] = self
.uid() # make sure this task produced the files in question
722 bld
.task_sigs
[self
.uid()] = self
.signature()
723 if not self
.keep_last_cmd
:
726 except AttributeError:
729 def sig_explicit_deps(self
):
731 Used by :py:meth:`waflib.Task.Task.signature`; it hashes :py:attr:`waflib.Task.Task.inputs`
732 and :py:attr:`waflib.Task.Task.dep_nodes` signatures.
734 bld
= self
.generator
.bld
738 for x
in self
.inputs
+ self
.dep_nodes
:
741 # manual dependencies, they can slow down the builds
743 additional_deps
= bld
.deps_man
744 for x
in self
.inputs
+ self
.outputs
:
746 d
= additional_deps
[x
]
753 except AttributeError:
754 if hasattr(v
, '__call__'):
755 v
= v() # dependency is a function, call it
758 def sig_deep_inputs(self
):
760 Enable rebuilds on input files task signatures. Not used by default.
762 Example: hashes of output programs can be unchanged after being re-linked,
763 despite the libraries being different. This method can thus prevent stale unit test
764 results (waf_unit_test.py).
766 Hashing input file timestamps is another possibility for the implementation.
767 This may cause unnecessary rebuilds when input tasks are frequently executed.
768 Here is an implementation example::
771 for node in self.inputs + self.dep_nodes:
772 st = os.stat(node.abspath())
773 lst.append(st.st_mtime)
774 lst.append(st.st_size)
775 self.m.update(Utils.h_list(lst))
777 The downside of the implementation is that it absolutely requires all build directory
778 files to be declared within the current build.
780 bld
= self
.generator
.bld
781 lst
= [bld
.task_sigs
[bld
.node_sigs
[node
]] for node
in (self
.inputs
+ self
.dep_nodes
) if node
.is_bld()]
782 self
.m
.update(Utils
.h_list(lst
))
786 Used by :py:meth:`waflib.Task.Task.signature`; it hashes :py:attr:`waflib.Task.Task.env` variables/values
787 When overriding this method, and if scriptlet expressions are used, make sure to follow
788 the code in :py:meth:`waflib.Task.Task.compile_sig_vars` to enable dependencies on scriptlet results.
790 This method may be replaced on subclasses by the metaclass to force dependencies on scriptlet code.
792 sig
= self
.generator
.bld
.hash_env_vars(self
.env
, self
.vars)
797 This method, when provided, returns a tuple containing:
799 * a list of nodes corresponding to real files
800 * a list of names for files not found in path_lst
804 from waflib.Task import Task
806 def scan(self, node):
809 The first and second lists in the tuple are stored in :py:attr:`waflib.Build.BuildContext.node_deps` and
810 :py:attr:`waflib.Build.BuildContext.raw_deps` respectively.
813 def sig_implicit_deps(self
):
815 Used by :py:meth:`waflib.Task.Task.signature`; it hashes node signatures
816 obtained by scanning for dependencies (:py:meth:`waflib.Task.Task.scan`).
818 The exception :py:class:`waflib.Errors.TaskRescan` is thrown
819 when a file has changed. In this case, the method :py:meth:`waflib.Task.Task.signature` is called
820 once again, and return here to call :py:meth:`waflib.Task.Task.scan` and searching for dependencies.
822 bld
= self
.generator
.bld
824 # get the task signatures from previous runs
826 prev
= bld
.imp_sigs
.get(key
, [])
831 if prev
== self
.compute_sig_implicit_deps():
833 except Errors
.TaskNotReady
:
835 except EnvironmentError:
836 # when a file was renamed, remove the stale nodes (headers in folders without source files)
837 # this will break the order calculation for headers created during the build in the source directory (should be uncommon)
838 # the behaviour will differ when top != out
839 for x
in bld
.node_deps
.get(self
.uid(), []):
840 if not x
.is_bld() and not x
.exists():
842 del x
.parent
.children
[x
.name
]
845 del bld
.imp_sigs
[key
]
846 raise Errors
.TaskRescan('rescan')
848 # no previous run or the signature of the dependencies has changed, rescan the dependencies
849 (bld
.node_deps
[key
], bld
.raw_deps
[key
]) = self
.scan()
851 Logs
.debug('deps: scanner for %s: %r; unresolved: %r', self
, bld
.node_deps
[key
], bld
.raw_deps
[key
])
853 # recompute the signature and return it
855 bld
.imp_sigs
[key
] = self
.compute_sig_implicit_deps()
856 except EnvironmentError:
857 for k
in bld
.node_deps
.get(self
.uid(), []):
859 Logs
.warn('Dependency %r for %r is missing: check the task declaration and the build order!', k
, self
)
862 def compute_sig_implicit_deps(self
):
864 Used by :py:meth:`waflib.Task.Task.sig_implicit_deps` for computing the actual hash of the
865 :py:class:`waflib.Node.Node` returned by the scanner.
867 :return: a hash value for the implicit dependencies
868 :rtype: string or bytes
871 self
.are_implicit_nodes_ready()
873 # scanner returns a node that does not have a signature
874 # just *ignore* the error and let them figure out from the compiler output
876 for k
in self
.generator
.bld
.node_deps
.get(self
.uid(), []):
878 return self
.m
.digest()
880 def are_implicit_nodes_ready(self
):
882 For each node returned by the scanner, see if there is a task that creates it,
883 and infer the build order
885 This has a low performance impact on null builds (1.86s->1.66s) thanks to caching (28s->1.86s)
887 bld
= self
.generator
.bld
889 cache
= bld
.dct_implicit_nodes
890 except AttributeError:
891 bld
.dct_implicit_nodes
= cache
= {}
893 # one cache per build group
895 dct
= cache
[bld
.current_group
]
897 dct
= cache
[bld
.current_group
] = {}
898 for tsk
in bld
.cur_tasks
:
899 for x
in tsk
.outputs
:
903 for x
in bld
.node_deps
.get(self
.uid(), []):
905 self
.run_after
.add(dct
[x
])
909 for tsk
in self
.run_after
:
911 #print "task is not ready..."
912 raise Errors
.TaskNotReady('not ready')
913 if sys
.hexversion
> 0x3000000:
917 except AttributeError:
918 m
= Utils
.md5(self
.__class
__.__name
__.encode('latin-1', 'xmlcharrefreplace'))
920 for x
in self
.inputs
+ self
.outputs
:
921 up(x
.abspath().encode('latin-1', 'xmlcharrefreplace'))
922 self
.uid_
= m
.digest()
924 uid
.__doc
__ = Task
.uid
.__doc
__
927 def is_before(t1
, t2
):
929 Returns a non-zero value if task t1 is to be executed before task t2::
935 waflib.Task.is_before(t1, t2) # True
937 :param t1: Task object
938 :type t1: :py:class:`waflib.Task.Task`
939 :param t2: Task object
940 :type t2: :py:class:`waflib.Task.Task`
942 to_list
= Utils
.to_list
943 for k
in to_list(t2
.ext_in
):
944 if k
in to_list(t1
.ext_out
):
947 if t1
.__class__
.__name
__ in to_list(t2
.after
):
950 if t2
.__class__
.__name
__ in to_list(t1
.before
):
955 def set_file_constraints(tasks
):
957 Updates the ``run_after`` attribute of all tasks based on the task inputs and outputs
960 :type tasks: list of :py:class:`waflib.Task.Task`
962 ins
= Utils
.defaultdict(set)
963 outs
= Utils
.defaultdict(set)
967 for a
in x
.dep_nodes
:
972 links
= set(ins
.keys()).intersection(outs
.keys())
975 a
.run_after
.update(outs
[k
])
978 class TaskGroup(object):
980 Wrap nxm task order constraints into a single object
981 to prevent the creation of large list/set objects
983 This is an optimization
985 def __init__(self
, prev
, next
):
990 def get_hasrun(self
):
996 hasrun
= property(get_hasrun
, None)
998 def set_precedence_constraints(tasks
):
1000 Updates the ``run_after`` attribute of all tasks based on the after/before/ext_out/ext_in attributes
1003 :type tasks: list of :py:class:`waflib.Task.Task`
1005 cstr_groups
= Utils
.defaultdict(list)
1007 h
= x
.hash_constraints()
1008 cstr_groups
[h
].append(x
)
1010 keys
= list(cstr_groups
.keys())
1013 # this list should be short
1014 for i
in range(maxi
):
1015 t1
= cstr_groups
[keys
[i
]][0]
1016 for j
in range(i
+ 1, maxi
):
1017 t2
= cstr_groups
[keys
[j
]][0]
1019 # add the constraints based on the comparisons
1020 if is_before(t1
, t2
):
1023 elif is_before(t2
, t1
):
1029 a
= cstr_groups
[keys
[a
]]
1030 b
= cstr_groups
[keys
[b
]]
1032 if len(a
) < 2 or len(b
) < 2:
1034 x
.run_after
.update(a
)
1036 group
= TaskGroup(set(a
), set(b
))
1038 x
.run_after
.add(group
)
1042 Compiles a scriptlet expression into a Python function
1044 :param c: function to compile
1046 :return: the function 'f' declared in the input string
1053 re_cond
= re
.compile(r
'(?P<var>\w+)|(?P<or>\|)|(?P<and>&)')
1054 re_novar
= re
.compile(r
'^(SRC|TGT)\W+.*?$')
1055 reg_act
= re
.compile(r
'(?P<backslash>\\)|(?P<dollar>\$\$)|(?P<subst>\$\{(?P<var>\w+)(?P<code>.*?)\})', re
.M
)
1056 def compile_fun_shell(line
):
1058 Creates a compiled function to execute a process through a sub-shell
1065 elif g('backslash'):
1068 extr
.append((g('var'), g('code')))
1071 line
= reg_act
.sub(repl
, line
) or line
1078 # performs substitutions and populates dvars
1086 return 'env[%r]' % x
1090 for (var
, meth
) in extr
:
1093 app('tsk.inputs%s' % meth
)
1095 app('" ".join([a.path_from(cwdx) for a in tsk.inputs])')
1098 app('tsk.outputs%s' % meth
)
1100 app('" ".join([a.path_from(cwdx) for a in tsk.outputs])')
1102 if meth
.startswith(':'):
1106 m
= '[a.path_from(cwdx) for a in tsk.inputs]'
1108 m
= '[a.path_from(cwdx) for a in tsk.outputs]'
1109 elif re_novar
.match(m
):
1110 m
= '[tsk.inputs%s]' % m
[3:]
1111 elif re_novar
.match(m
):
1112 m
= '[tsk.outputs%s]' % m
[3:]
1115 if m
[:3] not in ('tsk', 'gen', 'bld'):
1117 app('" ".join(tsk.colon(%r, %s))' % (var
, m
))
1118 elif meth
.startswith('?'):
1119 # In A?B|C output env.A if one of env.B or env.C is non-empty
1120 expr
= re_cond
.sub(replc
, meth
[1:])
1121 app('p(%r) if (%s) else ""' % (var
, expr
))
1123 call
= '%s%s' % (var
, meth
)
1128 app("p('%s')" % var
)
1130 parm
= "%% (%s) " % (',\n\t\t'.join(parm
))
1134 c
= COMPILE_TEMPLATE_SHELL
% (line
, parm
)
1135 Logs
.debug('action: %s', c
.strip().splitlines())
1136 return (funex(c
), dvars
)
1138 reg_act_noshell
= re
.compile(r
"(?P<space>\s+)|(?P<subst>\$\{(?P<var>\w+)(?P<code>.*?)\})|(?P<text>([^$ \t\n\r\f\v]|\$\$)+)", re
.M
)
1139 def compile_fun_noshell(line
):
1141 Creates a compiled function to execute a process without a sub-shell
1153 # performs substitutions and populates dvars
1161 return 'env[%r]' % x
1163 for m
in reg_act_noshell
.finditer(line
):
1164 if m
.group('space'):
1167 elif m
.group('text'):
1168 app('[%r]' % m
.group('text').replace('$$', '$'))
1169 elif m
.group('subst'):
1170 var
= m
.group('var')
1171 code
= m
.group('code')
1174 app('[tsk.inputs%s]' % code
)
1176 app('[a.path_from(cwdx) for a in tsk.inputs]')
1179 app('[tsk.outputs%s]' % code
)
1181 app('[a.path_from(cwdx) for a in tsk.outputs]')
1183 if code
.startswith(':'):
1184 # a composed variable ${FOO:OUT}
1188 m
= '[a.path_from(cwdx) for a in tsk.inputs]'
1190 m
= '[a.path_from(cwdx) for a in tsk.outputs]'
1191 elif re_novar
.match(m
):
1192 m
= '[tsk.inputs%s]' % m
[3:]
1193 elif re_novar
.match(m
):
1194 m
= '[tsk.outputs%s]' % m
[3:]
1197 if m
[:3] not in ('tsk', 'gen', 'bld'):
1199 app('tsk.colon(%r, %s)' % (var
, m
))
1200 elif code
.startswith('?'):
1201 # In A?B|C output env.A if one of env.B or env.C is non-empty
1202 expr
= re_cond
.sub(replc
, code
[1:])
1203 app('to_list(env[%r] if (%s) else [])' % (var
, expr
))
1205 # plain code such as ${tsk.inputs[0].abspath()}
1206 call
= '%s%s' % (var
, code
)
1208 app('to_list(%s)' % call
)
1210 # a plain variable such as # a plain variable like ${AR}
1211 app('to_list(env[%r])' % var
)
1214 tmp
= 'merge(%s, %s)' % (buf
[-2], buf
[-1])
1217 merge
= True # next turn
1219 buf
= ['lst.extend(%s)' % x
for x
in buf
]
1220 fun
= COMPILE_TEMPLATE_NOSHELL
% "\n\t".join(buf
)
1221 Logs
.debug('action: %s', fun
.strip().splitlines())
1222 return (funex(fun
), dvars
)
1224 def compile_fun(line
, shell
=False):
1226 Parses a string expression such as '${CC} ${SRC} -o ${TGT}' and returns a pair containing:
1228 * The function created (compiled) for use as :py:meth:`waflib.Task.Task.run`
1229 * The list of variables that must cause rebuilds when *env* data is modified
1233 from waflib.Task import compile_fun
1234 compile_fun('cxx', '${CXX} -o ${TGT[0]} ${SRC} -I ${SRC[0].parent.bldpath()}')
1237 bld(source='wscript', rule='echo "foo\\${SRC[0].name}\\bar"')
1239 The env variables (CXX, ..) on the task must not hold dicts so as to preserve a consistent order.
1240 The reserved keywords ``TGT`` and ``SRC`` represent the task input and output nodes
1243 if isinstance(line
, str):
1244 if line
.find('<') > 0 or line
.find('>') > 0 or line
.find('&&') > 0:
1250 if isinstance(x
, str):
1251 fun
, dvars
= compile_fun(x
, shell
)
1253 funs_lst
.append(fun
)
1255 # assume a function to let through
1257 def composed_fun(task
):
1263 return composed_fun
, dvars_lst
1265 return compile_fun_shell(line
)
1267 return compile_fun_noshell(line
)
1269 def compile_sig_vars(vars):
1271 This method produces a sig_vars method suitable for subclasses that provide
1272 scriptlet code in their run_str code.
1273 If no such method can be created, this method returns None.
1275 The purpose of the sig_vars method returned is to ensures
1276 that rebuilds occur whenever the contents of the expression changes.
1277 This is the case B below::
1280 # case A: regular variables
1281 tg = bld(rule='echo ${FOO}')
1282 tg.env.FOO = '%s' % time.time()
1284 bld(rule='echo ${gen.foo}', foo='%s' % time.time())
1286 :param vars: env variables such as CXXFLAGS or gen.foo
1287 :type vars: list of string
1288 :return: A sig_vars method relevant for dependencies if adequate, else None
1289 :rtype: A function, or None in most cases
1292 for x
in sorted(vars):
1293 if x
[:3] in ('tsk', 'gen', 'bld'):
1294 buf
.append('buf.append(%s)' % x
)
1296 return funex(COMPILE_TEMPLATE_SIG_VARS
% '\n\t'.join(buf
))
1299 def task_factory(name
, func
=None, vars=None, color
='GREEN', ext_in
=[], ext_out
=[], before
=[], after
=[], shell
=False, scan
=None):
1301 Returns a new task subclass with the function ``run`` compiled from the line given.
1303 :param func: method run
1304 :type func: string or function
1305 :param vars: list of variables to hash
1306 :type vars: list of string
1307 :param color: color to use
1309 :param shell: when *func* is a string, enable/disable the use of the shell
1311 :param scan: method scan
1312 :type scan: function
1313 :rtype: :py:class:`waflib.Task.Task`
1317 'vars': vars or [], # function arguments are static, and this one may be modified by the class
1324 if isinstance(func
, str) or isinstance(func
, tuple):
1325 params
['run_str'] = func
1327 params
['run'] = func
1329 cls
= type(Task
)(name
, (Task
,), params
)
1333 cls
.ext_in
= Utils
.to_list(ext_in
)
1335 cls
.ext_out
= Utils
.to_list(ext_out
)
1337 cls
.before
= Utils
.to_list(before
)
1339 cls
.after
= Utils
.to_list(after
)
1343 def deep_inputs(cls
):
1345 Task class decorator to enable rebuilds on input files task signatures
1347 def sig_explicit_deps(self
):
1348 Task
.sig_explicit_deps(self
)
1349 Task
.sig_deep_inputs(self
)
1350 cls
.sig_explicit_deps
= sig_explicit_deps
1354 "Provided for compatibility reasons, TaskBase should not be used"
1356 class TaskSemaphore(object):
1358 Task semaphores provide a simple and efficient way of throttling the amount of
1359 a particular task to run concurrently. The throttling value is capped
1360 by the amount of maximum jobs, so for example, a `TaskSemaphore(10)`
1361 has no effect in a `-j2` build.
1363 Task semaphores are typically specified on the task class level::
1365 class compile(waflib.Task.Task):
1366 semaphore = waflib.Task.TaskSemaphore(2)
1367 run_str = 'touch ${TGT}'
1369 Task semaphores are meant to be used by the build scheduler in the main
1370 thread, so there are no guarantees of thread safety.
1372 def __init__(self
, num
):
1374 :param num: maximum value of concurrent tasks
1378 self
.locking
= set()
1379 self
.waiting
= set()
1381 def is_locked(self
):
1382 """Returns True if this semaphore cannot be acquired by more tasks"""
1383 return len(self
.locking
) >= self
.num
1385 def acquire(self
, tsk
):
1387 Mark the semaphore as used by the given task (not re-entrant).
1389 :param tsk: task object
1390 :type tsk: :py:class:`waflib.Task.Task`
1391 :raises: :py:class:`IndexError` in case the resource is already acquired
1393 if self
.is_locked():
1394 raise IndexError('Cannot lock more %r' % self
.locking
)
1395 self
.locking
.add(tsk
)
1397 def release(self
, tsk
):
1399 Mark the semaphore as unused by the given task.
1401 :param tsk: task object
1402 :type tsk: :py:class:`waflib.Task.Task`
1403 :raises: :py:class:`KeyError` in case the resource is not acquired by the task
1405 self
.locking
.remove(tsk
)