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 = '''
53 tsk.last_cmd = cmd = \'\'\' %s \'\'\' % s
54 return tsk.exec_command(cmd, cwd=cwdx, env=env.env or None)
57 COMPILE_TEMPLATE_NOSHELL = '''
64 if isinstance(xx, str): return [xx]
66 def merge(lst1, lst2):
68 return lst1[:-1] + [lst1[-1] + lst2[0]] + lst2[1:]
73 lst = [x for x in lst if x]
75 return tsk.exec_command(lst, cwd=cwdx, env=env.env or None)
80 The metaclass :py:class:`waflib.Task.store_task_type` stores all class tasks
81 created by user scripts or Waf tools to this dict. It maps class names to class objects.
84 class store_task_type(type):
86 Metaclass: store the task classes into the dict pointed by the
87 class attribute 'register' which defaults to :py:const:`waflib.Task.classes`,
89 The attribute 'run_str' is compiled into a method 'run' bound to the task class.
91 def __init__(cls, name, bases, dict):
92 super(store_task_type, cls).__init__(name, bases, dict)
95 if name != 'evil' and name != 'Task':
96 if getattr(cls, 'run_str', None):
97 # if a string is provided, convert it to a method
98 (f, dvars) = compile_fun(cls.run_str, cls.shell)
99 cls.hcode = Utils.h_cmd(cls.run_str)
100 cls.orig_run_str = cls.run_str
101 # change the name of run_str or it is impossible to subclass with a function
104 cls.vars = list(set(cls.vars + dvars))
106 elif getattr(cls, 'run', None) and not 'hcode' in cls.__dict__:
107 # getattr(cls, 'hcode') would look in the upper classes
108 cls.hcode = Utils.h_cmd(cls.run)
111 getattr(cls, 'register', classes)[name] = cls
113 evil = store_task_type('evil', (object,), {})
114 "Base class provided to avoid writing a metaclass, so the code can run in python 2.6 and 3.x unmodified"
118 This class deals with the filesystem (:py:class:`waflib.Node.Node`). The method :py:class:`waflib.Task.Task.runnable_status`
119 uses a hash value (from :py:class:`waflib.Task.Task.signature`) which is persistent from build to build. When the value changes,
120 the task has to be executed. The method :py:class:`waflib.Task.Task.post_run` will assign the task signature to the output
124 """ConfigSet variables that should trigger a rebuild (class attribute used for :py:meth:`waflib.Task.Task.sig_vars`)"""
127 """Specify whether task instances must always be executed or not (class attribute)"""
130 """Execute the command with the shell (class attribute)"""
133 """Color for the console display, see :py:const:`waflib.Logs.colors_lst`"""
136 """File extensions that objects of this task class may use"""
139 """File extensions that objects of this task class may create"""
142 """List of task class names to execute before instances of this class"""
145 """List of task class names to execute after instances of this class"""
147 hcode = Utils.SIG_NIL
148 """String representing an additional hash for the class representation"""
150 keep_last_cmd = False
151 """Whether to keep the last command executed on the instance after execution.
152 This may be useful for certain extensions but it can a lot of memory.
156 """Optional weight to tune the priority for task instances.
157 The higher, the earlier. The weight only applies to single task objects."""
160 """Optional weight to tune the priority of task instances and whole subtrees.
161 The higher, the earlier."""
164 """Priority order set by the scheduler on instances during the build phase.
165 You most likely do not need to set it.
168 __slots__ = ('hasrun', 'generator', 'env', 'inputs', 'outputs', 'dep_nodes', 'run_after')
170 def __init__(self, *k, **kw):
171 self.hasrun = NOT_RUN
173 self.generator = kw['generator']
175 self.generator = self
178 """:py:class:`waflib.ConfigSet.ConfigSet` object (make sure to provide one)"""
181 """List of input nodes, which represent the files used by the task instance"""
184 """List of output nodes, which represent the files created by the task instance"""
187 """List of additional nodes to depend on"""
189 self.run_after = set()
190 """Set of tasks that must be executed before this one"""
192 def __lt__(self, other):
193 return self.priority() > other.priority()
194 def __le__(self, other):
195 return self.priority() >= other.priority()
196 def __gt__(self, other):
197 return self.priority() < other.priority()
198 def __ge__(self, other):
199 return self.priority() <= other.priority()
203 :return: current working directory
204 :rtype: :py:class:`waflib.Node.Node`
206 bld = self.generator.bld
207 ret = getattr(self, 'cwd', None) or getattr(bld, 'cwd', bld.bldnode)
208 if isinstance(ret, str):
209 if os.path.isabs(ret):
210 ret = bld.root.make_node(ret)
212 ret = self.generator.path.make_node(ret)
215 def quote_flag(self, x):
217 Surround a process argument by quotes so that a list of arguments can be written to a file
226 x = x.replace('\\', '\\\\')
228 x = x.replace('"', '\\"')
229 if old != x or ' ' in x or '\t' in x or "'" in x:
235 Priority of execution; the higher, the earlier
237 :return: the priority value
238 :rtype: a tuple of numeric values
240 return (self.weight + self.prio_order, - getattr(self.generator, 'tg_idx_count', 0))
242 def split_argfile(self, cmd):
244 Splits a list of process commands into the executable part and its list of arguments
246 :return: a tuple containing the executable first and then the rest of arguments
249 return ([cmd[0]], [self.quote_flag(x) for x in cmd[1:]])
251 def exec_command(self, cmd, **kw):
253 Wrapper for :py:meth:`waflib.Context.Context.exec_command`.
254 This version set the current working directory (``build.variant_dir``),
255 applies PATH settings (if self.env.PATH is provided), and can run long
256 commands through a temporary ``@argfile``.
258 :param cmd: process command to execute
259 :type cmd: list of string (best) or string (process will use a shell)
260 :return: the return code
265 #. cwd: current working directory (Node or string)
266 #. stdout: set to None to prevent waf from capturing the process standard output
267 #. stderr: set to None to prevent waf from capturing the process standard error
268 #. timeout: timeout value (Python 3)
271 kw['cwd'] = self.get_cwd()
273 if hasattr(self, 'timeout'):
274 kw['timeout'] = self.timeout
277 env = kw['env'] = dict(kw.get('env') or self.env.env or os.environ)
278 env['PATH'] = self.env.PATH if isinstance(self.env.PATH, str) else os.pathsep.join(self.env.PATH)
280 if hasattr(self, 'stdout'):
281 kw['stdout'] = self.stdout
282 if hasattr(self, 'stderr'):
283 kw['stderr'] = self.stderr
285 # workaround for command line length limit:
286 # http://support.microsoft.com/kb/830473
287 if not isinstance(cmd, str) and (len(repr(cmd)) >= 8192 if Utils.is_win32 else len(cmd) > 200000):
288 cmd, args = self.split_argfile(cmd)
290 (fd, tmp) = tempfile.mkstemp()
291 os.write(fd, '\r\n'.join(args).encode())
294 Logs.debug('argfile: @%r -> %r', tmp, args)
295 return self.generator.bld.exec_command(cmd + ['@' + tmp], **kw)
300 # anti-virus and indexers can keep files open -_-
303 return self.generator.bld.exec_command(cmd, **kw)
307 Runs the task and handles errors
309 :return: 0 or None if everything is fine
312 # remove the task signature immediately before it is executed
313 # so that the task will be executed again in case of failure
315 del self.generator.bld.task_sigs[self.uid()]
322 self.err_msg = traceback.format_exc()
323 self.hasrun = EXCEPTION
327 self.hasrun = CRASHED
331 except Errors.WafError:
334 self.err_msg = traceback.format_exc()
335 self.hasrun = EXCEPTION
337 self.hasrun = SUCCESS
339 if self.hasrun != SUCCESS and self.scan:
340 # rescan dependencies on next run
342 del self.generator.bld.imp_sigs[self.uid()]
346 def log_display(self, bld):
347 "Writes the execution status on the context logger"
348 if self.generator.bld.progress_bar == 3:
358 if self.generator.bld.progress_bar == 1:
359 c1 = Logs.colors.cursor_off
360 c2 = Logs.colors.cursor_on
361 logger.info(s, extra={'stream': sys.stderr, 'terminator':'', 'c1': c1, 'c2' : c2})
363 logger.info(s, extra={'terminator':'', 'c1': '', 'c2' : ''})
367 Returns an execution status for the console, the progress bar, or the IDE output.
371 col1 = Logs.colors(self.color)
372 col2 = Logs.colors.NORMAL
373 master = self.generator.bld.producer
376 # the current task position, computed as late as possible
377 return master.processed - master.ready.qsize()
379 if self.generator.bld.progress_bar == 1:
380 return self.generator.bld.progress_line(cur(), master.total, col1, col2)
382 if self.generator.bld.progress_bar == 2:
383 ela = str(self.generator.bld.timer)
385 ins = ','.join([n.name for n in self.inputs])
386 except AttributeError:
389 outs = ','.join([n.name for n in self.outputs])
390 except AttributeError:
392 return '|Total %s|Current %s|Inputs %s|Outputs %s|Time %s|\n' % (master.total, cur(), ins, outs, ela)
400 fs = '[%%%dd/%%%dd] %%s%%s%%s%%s\n' % (n, n)
404 return fs % (cur(), total, kw, col1, s, col2)
406 def hash_constraints(self):
408 Identifies a task type for all the constraints relevant for the scheduler: precedence, file production
410 :return: a hash value
413 return (tuple(self.before), tuple(self.after), tuple(self.ext_in), tuple(self.ext_out), self.__class__.__name__, self.hcode)
415 def format_error(self):
417 Returns an error message to display the build failure reasons
422 msg = ': %r\n%r' % (self, getattr(self, 'last_cmd', ''))
424 msg = ' (run with -v to display more information)'
425 name = getattr(self.generator, 'name', '')
426 if getattr(self, "err_msg", None):
428 elif not self.hasrun:
429 return 'task in %r was not executed for some reason: %r' % (name, self)
430 elif self.hasrun == CRASHED:
432 return ' -> task in %r failed with exit status %r%s' % (name, self.err_code, msg)
433 except AttributeError:
434 return ' -> task in %r failed%s' % (name, msg)
435 elif self.hasrun == MISSING:
436 return ' -> missing files in %r%s' % (name, msg)
437 elif self.hasrun == CANCELED:
438 return ' -> %r canceled because of missing dependencies' % name
440 return 'invalid status for task in %r: %r' % (name, self.hasrun)
442 def colon(self, var1, var2):
444 Enable scriptlet expressions of the form ${FOO_ST:FOO}
445 If the first variable (FOO_ST) is empty, then an empty list is returned
447 The results will be slightly different if FOO_ST is a list, for example::
449 env.FOO = ['p1', 'p2']
451 # ${FOO_ST:FOO} returns
454 env.FOO_ST = ['-a', '-b']
455 # ${FOO_ST:FOO} returns
456 ['-a', '-b', 'p1', '-a', '-b', 'p2']
462 if isinstance(var2, str):
466 if isinstance(tmp, str):
467 return [tmp % x for x in it]
476 "string to display to the user"
477 name = self.__class__.__name__
479 if name.endswith(('lib', 'program')) or not self.inputs:
480 node = self.outputs[0]
481 return node.path_from(node.ctx.launch_node())
482 if not (self.inputs or self.outputs):
483 return self.__class__.__name__
484 if len(self.inputs) == 1:
485 node = self.inputs[0]
486 return node.path_from(node.ctx.launch_node())
488 src_str = ' '.join([a.path_from(a.ctx.launch_node()) for a in self.inputs])
489 tgt_str = ' '.join([a.path_from(a.ctx.launch_node()) for a in self.outputs])
494 return '%s: %s%s%s' % (self.__class__.__name__, src_str, sep, tgt_str)
497 "Display keyword used to prettify the console outputs"
498 name = self.__class__.__name__
499 if name.endswith(('lib', 'program')):
501 if len(self.inputs) == 1 and len(self.outputs) == 1:
511 "for debugging purposes"
513 ins = ",".join([x.name for x in self.inputs])
514 outs = ",".join([x.name for x in self.outputs])
515 except AttributeError:
516 ins = ",".join([str(x) for x in self.inputs])
517 outs = ",".join([str(x) for x in self.outputs])
518 return "".join(['\n\t{task %r: ' % id(self), self.__class__.__name__, " ", ins, " -> ", outs, '}'])
522 Returns an identifier used to determine if tasks are up-to-date. Since the
523 identifier will be stored between executions, it must be:
525 - unique for a task: no two tasks return the same value (for a given build context)
526 - the same for a given task instance
528 By default, the node paths, the class name, and the function are used
529 as inputs to compute a hash.
531 The pointer to the object (python built-in 'id') will change between build executions,
532 and must be avoided in such hashes.
539 except AttributeError:
540 m = Utils.md5(self.__class__.__name__)
542 for x in self.inputs + self.outputs:
544 self.uid_ = m.digest()
547 def set_inputs(self, inp):
549 Appends the nodes to the *inputs* list
551 :param inp: input nodes
552 :type inp: node or list of nodes
554 if isinstance(inp, list):
557 self.inputs.append(inp)
559 def set_outputs(self, out):
561 Appends the nodes to the *outputs* list
563 :param out: output nodes
564 :type out: node or list of nodes
566 if isinstance(out, list):
569 self.outputs.append(out)
571 def set_run_after(self, task):
573 Run this task only after the given *task*.
576 :type task: :py:class:`waflib.Task.Task`
578 assert isinstance(task, Task)
579 self.run_after.add(task)
583 Task signatures are stored between build executions, they are use to track the changes
584 made to the input nodes (not to the outputs!). The signature hashes data from various sources:
586 * explicit dependencies: files listed in the inputs (list of node objects) :py:meth:`waflib.Task.Task.sig_explicit_deps`
587 * implicit dependencies: list of nodes returned by scanner methods (when present) :py:meth:`waflib.Task.Task.sig_implicit_deps`
588 * hashed data: variables/values read from task.vars/task.env :py:meth:`waflib.Task.Task.sig_vars`
590 If the signature is expected to give a different result, clear the cache kept in ``self.cache_sig``::
592 from waflib import Task
593 class cls(Task.Task):
595 sig = super(Task.Task, self).signature()
596 delattr(self, 'cache_sig')
597 return super(Task.Task, self).signature()
599 :return: the signature value
600 :rtype: string or bytes
603 return self.cache_sig
604 except AttributeError:
607 self.m = Utils.md5(self.hcode)
610 self.sig_explicit_deps()
615 # implicit deps / scanner results
618 self.sig_implicit_deps()
619 except Errors.TaskRescan:
620 return self.signature()
622 ret = self.cache_sig = self.m.digest()
625 def runnable_status(self):
627 Returns the Task status
629 :return: a task state in :py:const:`waflib.Task.RUN_ME`,
630 :py:const:`waflib.Task.SKIP_ME`, :py:const:`waflib.Task.CANCEL_ME` or :py:const:`waflib.Task.ASK_LATER`.
633 bld = self.generator.bld
634 if bld.is_install < 0:
637 for t in self.run_after:
640 elif t.hasrun < SKIPPED:
641 # a dependency has an error
644 # first compute the signature
646 new_sig = self.signature()
647 except Errors.TaskNotReady:
650 # compare the signature to a signature computed previously
653 prev_sig = bld.task_sigs[key]
655 Logs.debug('task: task %r must run: it was never run before or the task code changed', self)
658 if new_sig != prev_sig:
659 Logs.debug('task: task %r must run: the task signature changed', self)
662 # compare the signatures of the outputs
663 for node in self.outputs:
664 sig = bld.node_sigs.get(node)
666 Logs.debug('task: task %r must run: an output node has no signature', self)
669 Logs.debug('task: task %r must run: an output node was produced by another task', self)
671 if not node.exists():
672 Logs.debug('task: task %r must run: an output node does not exist', self)
675 return (self.always_run and RUN_ME) or SKIP_ME
679 Called after successful execution to record that the task has run by
680 updating the entry in :py:attr:`waflib.Build.BuildContext.task_sigs`.
682 bld = self.generator.bld
683 for node in self.outputs:
684 if not node.exists():
685 self.hasrun = MISSING
686 self.err_msg = '-> missing file: %r' % node.abspath()
687 raise Errors.WafError(self.err_msg)
688 bld.node_sigs[node] = self.uid() # make sure this task produced the files in question
689 bld.task_sigs[self.uid()] = self.signature()
690 if not self.keep_last_cmd:
693 except AttributeError:
696 def sig_explicit_deps(self):
698 Used by :py:meth:`waflib.Task.Task.signature`; it hashes :py:attr:`waflib.Task.Task.inputs`
699 and :py:attr:`waflib.Task.Task.dep_nodes` signatures.
701 bld = self.generator.bld
705 for x in self.inputs + self.dep_nodes:
708 # manual dependencies, they can slow down the builds
710 additional_deps = bld.deps_man
711 for x in self.inputs + self.outputs:
713 d = additional_deps[x]
720 except AttributeError:
721 if hasattr(v, '__call__'):
722 v = v() # dependency is a function, call it
725 def sig_deep_inputs(self):
727 Enable rebuilds on input files task signatures. Not used by default.
729 Example: hashes of output programs can be unchanged after being re-linked,
730 despite the libraries being different. This method can thus prevent stale unit test
731 results (waf_unit_test.py).
733 Hashing input file timestamps is another possibility for the implementation.
734 This may cause unnecessary rebuilds when input tasks are frequently executed.
735 Here is an implementation example::
738 for node in self.inputs + self.dep_nodes:
739 st = os.stat(node.abspath())
740 lst.append(st.st_mtime)
741 lst.append(st.st_size)
742 self.m.update(Utils.h_list(lst))
744 The downside of the implementation is that it absolutely requires all build directory
745 files to be declared within the current build.
747 bld = self.generator.bld
748 lst = [bld.task_sigs[bld.node_sigs[node]] for node in (self.inputs + self.dep_nodes) if node.is_bld()]
749 self.m.update(Utils.h_list(lst))
753 Used by :py:meth:`waflib.Task.Task.signature`; it hashes :py:attr:`waflib.Task.Task.env` variables/values
755 sig = self.generator.bld.hash_env_vars(self.env, self.vars)
760 This method, when provided, returns a tuple containing:
762 * a list of nodes corresponding to real files
763 * a list of names for files not found in path_lst
767 from waflib.Task import Task
769 def scan(self, node):
772 The first and second lists in the tuple are stored in :py:attr:`waflib.Build.BuildContext.node_deps` and
773 :py:attr:`waflib.Build.BuildContext.raw_deps` respectively.
776 def sig_implicit_deps(self):
778 Used by :py:meth:`waflib.Task.Task.signature`; it hashes node signatures
779 obtained by scanning for dependencies (:py:meth:`waflib.Task.Task.scan`).
781 The exception :py:class:`waflib.Errors.TaskRescan` is thrown
782 when a file has changed. In this case, the method :py:meth:`waflib.Task.Task.signature` is called
783 once again, and return here to call :py:meth:`waflib.Task.Task.scan` and searching for dependencies.
785 bld = self.generator.bld
787 # get the task signatures from previous runs
789 prev = bld.imp_sigs.get(key, [])
794 if prev == self.compute_sig_implicit_deps():
796 except Errors.TaskNotReady:
798 except EnvironmentError:
799 # when a file was renamed, remove the stale nodes (headers in folders without source files)
800 # this will break the order calculation for headers created during the build in the source directory (should be uncommon)
801 # the behaviour will differ when top != out
802 for x in bld.node_deps.get(self.uid(), []):
803 if not x.is_bld() and not x.exists():
805 del x.parent.children[x.name]
808 del bld.imp_sigs[key]
809 raise Errors.TaskRescan('rescan')
811 # no previous run or the signature of the dependencies has changed, rescan the dependencies
812 (bld.node_deps[key], bld.raw_deps[key]) = self.scan()
814 Logs.debug('deps: scanner for %s: %r; unresolved: %r', self, bld.node_deps[key], bld.raw_deps[key])
816 # recompute the signature and return it
818 bld.imp_sigs[key] = self.compute_sig_implicit_deps()
819 except EnvironmentError:
820 for k in bld.node_deps.get(self.uid(), []):
822 Logs.warn('Dependency %r for %r is missing: check the task declaration and the build order!', k, self)
825 def compute_sig_implicit_deps(self):
827 Used by :py:meth:`waflib.Task.Task.sig_implicit_deps` for computing the actual hash of the
828 :py:class:`waflib.Node.Node` returned by the scanner.
830 :return: a hash value for the implicit dependencies
831 :rtype: string or bytes
834 self.are_implicit_nodes_ready()
836 # scanner returns a node that does not have a signature
837 # just *ignore* the error and let them figure out from the compiler output
839 for k in self.generator.bld.node_deps.get(self.uid(), []):
841 return self.m.digest()
843 def are_implicit_nodes_ready(self):
845 For each node returned by the scanner, see if there is a task that creates it,
846 and infer the build order
848 This has a low performance impact on null builds (1.86s->1.66s) thanks to caching (28s->1.86s)
850 bld = self.generator.bld
852 cache = bld.dct_implicit_nodes
853 except AttributeError:
854 bld.dct_implicit_nodes = cache = {}
856 # one cache per build group
858 dct = cache[bld.current_group]
860 dct = cache[bld.current_group] = {}
861 for tsk in bld.cur_tasks:
862 for x in tsk.outputs:
866 for x in bld.node_deps.get(self.uid(), []):
868 self.run_after.add(dct[x])
872 for tsk in self.run_after:
874 #print "task is not ready..."
875 raise Errors.TaskNotReady('not ready')
876 if sys.hexversion > 0x3000000:
880 except AttributeError:
881 m = Utils.md5(self.__class__.__name__.encode('latin-1', 'xmlcharrefreplace'))
883 for x in self.inputs + self.outputs:
884 up(x.abspath().encode('latin-1', 'xmlcharrefreplace'))
885 self.uid_ = m.digest()
887 uid.__doc__ = Task.uid.__doc__
890 def is_before(t1, t2):
892 Returns a non-zero value if task t1 is to be executed before task t2::
898 waflib.Task.is_before(t1, t2) # True
900 :param t1: Task object
901 :type t1: :py:class:`waflib.Task.Task`
902 :param t2: Task object
903 :type t2: :py:class:`waflib.Task.Task`
905 to_list = Utils.to_list
906 for k in to_list(t2.ext_in):
907 if k in to_list(t1.ext_out):
910 if t1.__class__.__name__ in to_list(t2.after):
913 if t2.__class__.__name__ in to_list(t1.before):
918 def set_file_constraints(tasks):
920 Updates the ``run_after`` attribute of all tasks based on the task inputs and outputs
923 :type tasks: list of :py:class:`waflib.Task.Task`
925 ins = Utils.defaultdict(set)
926 outs = Utils.defaultdict(set)
930 for a in x.dep_nodes:
935 links = set(ins.keys()).intersection(outs.keys())
938 a.run_after.update(outs[k])
941 class TaskGroup(object):
943 Wrap nxm task order constraints into a single object
944 to prevent the creation of large list/set objects
946 This is an optimization
948 def __init__(self, prev, next):
953 def get_hasrun(self):
959 hasrun = property(get_hasrun, None)
961 def set_precedence_constraints(tasks):
963 Updates the ``run_after`` attribute of all tasks based on the after/before/ext_out/ext_in attributes
966 :type tasks: list of :py:class:`waflib.Task.Task`
968 cstr_groups = Utils.defaultdict(list)
970 h = x.hash_constraints()
971 cstr_groups[h].append(x)
973 keys = list(cstr_groups.keys())
976 # this list should be short
977 for i in range(maxi):
978 t1 = cstr_groups[keys[i]][0]
979 for j in range(i + 1, maxi):
980 t2 = cstr_groups[keys[j]][0]
982 # add the constraints based on the comparisons
983 if is_before(t1, t2):
986 elif is_before(t2, t1):
992 a = cstr_groups[keys[a]]
993 b = cstr_groups[keys[b]]
995 if len(a) < 2 or len(b) < 2:
997 x.run_after.update(a)
999 group = TaskGroup(set(a), set(b))
1001 x.run_after.add(group)
1005 Compiles a scriptlet expression into a Python function
1007 :param c: function to compile
1009 :return: the function 'f' declared in the input string
1016 re_cond = re.compile('(?P<var>\w+)|(?P<or>\|)|(?P<and>&)')
1017 re_novar = re.compile(r'^(SRC|TGT)\W+.*?$')
1018 reg_act = re.compile(r'(?P<backslash>\\)|(?P<dollar>\$\$)|(?P<subst>\$\{(?P<var>\w+)(?P<code>.*?)\})', re.M)
1019 def compile_fun_shell(line):
1021 Creates a compiled function to execute a process through a sub-shell
1028 elif g('backslash'):
1031 extr.append((g('var'), g('code')))
1034 line = reg_act.sub(repl, line) or line
1038 # performs substitutions and populates dvars
1047 return 'env[%r]' % x
1051 for (var, meth) in extr:
1054 app('tsk.inputs%s' % meth)
1056 app('" ".join([a.path_from(cwdx) for a in tsk.inputs])')
1059 app('tsk.outputs%s' % meth)
1061 app('" ".join([a.path_from(cwdx) for a in tsk.outputs])')
1063 if meth.startswith(':'):
1064 if var not in dvars:
1068 m = '[a.path_from(cwdx) for a in tsk.inputs]'
1070 m = '[a.path_from(cwdx) for a in tsk.outputs]'
1071 elif re_novar.match(m):
1072 m = '[tsk.inputs%s]' % m[3:]
1073 elif re_novar.match(m):
1074 m = '[tsk.outputs%s]' % m[3:]
1075 elif m[:3] not in ('tsk', 'gen', 'bld'):
1076 dvars.append(meth[1:])
1078 app('" ".join(tsk.colon(%r, %s))' % (var, m))
1079 elif meth.startswith('?'):
1080 # In A?B|C output env.A if one of env.B or env.C is non-empty
1081 expr = re_cond.sub(replc, meth[1:])
1082 app('p(%r) if (%s) else ""' % (var, expr))
1084 app('%s%s' % (var, meth))
1086 if var not in dvars:
1088 app("p('%s')" % var)
1090 parm = "%% (%s) " % (',\n\t\t'.join(parm))
1094 c = COMPILE_TEMPLATE_SHELL % (line, parm)
1095 Logs.debug('action: %s', c.strip().splitlines())
1096 return (funex(c), dvars)
1098 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)
1099 def compile_fun_noshell(line):
1101 Creates a compiled function to execute a process without a sub-shell
1109 # performs substitutions and populates dvars
1118 return 'env[%r]' % x
1120 for m in reg_act_noshell.finditer(line):
1121 if m.group('space'):
1124 elif m.group('text'):
1125 app('[%r]' % m.group('text').replace('$$', '$'))
1126 elif m.group('subst'):
1127 var = m.group('var')
1128 code = m.group('code')
1131 app('[tsk.inputs%s]' % code)
1133 app('[a.path_from(cwdx) for a in tsk.inputs]')
1136 app('[tsk.outputs%s]' % code)
1138 app('[a.path_from(cwdx) for a in tsk.outputs]')
1140 if code.startswith(':'):
1141 # a composed variable ${FOO:OUT}
1142 if not var in dvars:
1146 m = '[a.path_from(cwdx) for a in tsk.inputs]'
1148 m = '[a.path_from(cwdx) for a in tsk.outputs]'
1149 elif re_novar.match(m):
1150 m = '[tsk.inputs%s]' % m[3:]
1151 elif re_novar.match(m):
1152 m = '[tsk.outputs%s]' % m[3:]
1153 elif m[:3] not in ('tsk', 'gen', 'bld'):
1156 app('tsk.colon(%r, %s)' % (var, m))
1157 elif code.startswith('?'):
1158 # In A?B|C output env.A if one of env.B or env.C is non-empty
1159 expr = re_cond.sub(replc, code[1:])
1160 app('to_list(env[%r] if (%s) else [])' % (var, expr))
1162 # plain code such as ${tsk.inputs[0].abspath()}
1163 app('gen.to_list(%s%s)' % (var, code))
1165 # a plain variable such as # a plain variable like ${AR}
1166 app('to_list(env[%r])' % var)
1167 if not var in dvars:
1170 tmp = 'merge(%s, %s)' % (buf[-2], buf[-1])
1173 merge = True # next turn
1175 buf = ['lst.extend(%s)' % x for x in buf]
1176 fun = COMPILE_TEMPLATE_NOSHELL % "\n\t".join(buf)
1177 Logs.debug('action: %s', fun.strip().splitlines())
1178 return (funex(fun), dvars)
1180 def compile_fun(line, shell=False):
1182 Parses a string expression such as '${CC} ${SRC} -o ${TGT}' and returns a pair containing:
1184 * The function created (compiled) for use as :py:meth:`waflib.Task.Task.run`
1185 * The list of variables that must cause rebuilds when *env* data is modified
1189 from waflib.Task import compile_fun
1190 compile_fun('cxx', '${CXX} -o ${TGT[0]} ${SRC} -I ${SRC[0].parent.bldpath()}')
1193 bld(source='wscript', rule='echo "foo\\${SRC[0].name}\\bar"')
1195 The env variables (CXX, ..) on the task must not hold dicts so as to preserve a consistent order.
1196 The reserved keywords ``TGT`` and ``SRC`` represent the task input and output nodes
1199 if isinstance(line, str):
1200 if line.find('<') > 0 or line.find('>') > 0 or line.find('&&') > 0:
1206 if isinstance(x, str):
1207 fun, dvars = compile_fun(x, shell)
1209 funs_lst.append(fun)
1211 # assume a function to let through
1213 def composed_fun(task):
1219 return composed_fun, dvars_lst
1221 return compile_fun_shell(line)
1223 return compile_fun_noshell(line)
1225 def task_factory(name, func=None, vars=None, color='GREEN', ext_in=[], ext_out=[], before=[], after=[], shell=False, scan=None):
1227 Returns a new task subclass with the function ``run`` compiled from the line given.
1229 :param func: method run
1230 :type func: string or function
1231 :param vars: list of variables to hash
1232 :type vars: list of string
1233 :param color: color to use
1235 :param shell: when *func* is a string, enable/disable the use of the shell
1237 :param scan: method scan
1238 :type scan: function
1239 :rtype: :py:class:`waflib.Task.Task`
1243 'vars': vars or [], # function arguments are static, and this one may be modified by the class
1250 if isinstance(func, str) or isinstance(func, tuple):
1251 params['run_str'] = func
1253 params['run'] = func
1255 cls = type(Task)(name, (Task,), params)
1259 cls.ext_in = Utils.to_list(ext_in)
1261 cls.ext_out = Utils.to_list(ext_out)
1263 cls.before = Utils.to_list(before)
1265 cls.after = Utils.to_list(after)
1269 def deep_inputs(cls):
1271 Task class decorator to enable rebuilds on input files task signatures
1273 def sig_explicit_deps(self):
1274 Task.sig_explicit_deps(self)
1275 Task.sig_deep_inputs(self)
1276 cls.sig_explicit_deps = sig_explicit_deps
1280 "Provided for compatibility reasons, TaskBase should not be used"