3 # WARNING! Do not edit! https://waf.io/book/index.html#_obtaining_the_waf_file
7 # Thomas Nagy, 2005-2018 (ita)
10 Tasks represent atomic operations such as processes.
13 import os, re, sys, tempfile, traceback
14 from waflib import Utils, Logs, Errors
18 """The task was not executed yet"""
21 """The task has been executed but the files have not been created"""
24 """The task execution returned a non-zero exit status"""
27 """An exception occurred in the task execution"""
30 """A dependency for the task is missing so it was cancelled"""
33 """The task did not have to be executed"""
36 """The task was successfully executed"""
39 """The task is not ready to be executed"""
42 """The task does not need to be executed"""
45 """The task must be executed"""
48 """The task cannot be executed because of a dependency problem"""
50 COMPILE_TEMPLATE_SHELL = '''
57 tsk.last_cmd = cmd = \'\'\' %s \'\'\' % s
58 return tsk.exec_command(cmd, cwd=cwdx, env=env.env or None)
61 COMPILE_TEMPLATE_NOSHELL = '''
68 if isinstance(xx, str): return [xx]
70 def merge(lst1, lst2):
72 return lst1[:-1] + [lst1[-1] + lst2[0]] + lst2[1:]
77 lst = [x for x in lst if x]
79 return tsk.exec_command(lst, cwd=cwdx, env=env.env or None)
84 The metaclass :py:class:`waflib.Task.store_task_type` stores all class tasks
85 created by user scripts or Waf tools to this dict. It maps class names to class objects.
88 class store_task_type(type):
90 Metaclass: store the task classes into the dict pointed by the
91 class attribute 'register' which defaults to :py:const:`waflib.Task.classes`,
93 The attribute 'run_str' is compiled into a method 'run' bound to the task class.
95 def __init__(cls, name, bases, dict):
96 super(store_task_type, cls).__init__(name, bases, dict)
99 if name != 'evil' and name != 'Task':
100 if getattr(cls, 'run_str', None):
101 # if a string is provided, convert it to a method
102 (f, dvars) = compile_fun(cls.run_str, cls.shell)
103 cls.hcode = Utils.h_cmd(cls.run_str)
104 cls.orig_run_str = cls.run_str
105 # change the name of run_str or it is impossible to subclass with a function
108 cls.vars = list(set(cls.vars + dvars))
110 elif getattr(cls, 'run', None) and not 'hcode' in cls.__dict__:
111 # getattr(cls, 'hcode') would look in the upper classes
112 cls.hcode = Utils.h_cmd(cls.run)
115 getattr(cls, 'register', classes)[name] = cls
117 evil = store_task_type('evil', (object,), {})
118 "Base class provided to avoid writing a metaclass, so the code can run in python 2.6 and 3.x unmodified"
122 This class deals with the filesystem (:py:class:`waflib.Node.Node`). The method :py:class:`waflib.Task.Task.runnable_status`
123 uses a hash value (from :py:class:`waflib.Task.Task.signature`) which is persistent from build to build. When the value changes,
124 the task has to be executed. The method :py:class:`waflib.Task.Task.post_run` will assign the task signature to the output
128 """ConfigSet variables that should trigger a rebuild (class attribute used for :py:meth:`waflib.Task.Task.sig_vars`)"""
131 """Specify whether task instances must always be executed or not (class attribute)"""
134 """Execute the command with the shell (class attribute)"""
137 """Color for the console display, see :py:const:`waflib.Logs.colors_lst`"""
140 """File extensions that objects of this task class may use"""
143 """File extensions that objects of this task class may create"""
146 """List of task class names to execute before instances of this class"""
149 """List of task class names to execute after instances of this class"""
151 hcode = Utils.SIG_NIL
152 """String representing an additional hash for the class representation"""
154 keep_last_cmd = False
155 """Whether to keep the last command executed on the instance after execution.
156 This may be useful for certain extensions but it can a lot of memory.
160 """Optional weight to tune the priority for task instances.
161 The higher, the earlier. The weight only applies to single task objects."""
164 """Optional weight to tune the priority of task instances and whole subtrees.
165 The higher, the earlier."""
168 """Priority order set by the scheduler on instances during the build phase.
169 You most likely do not need to set it.
172 __slots__ = ('hasrun', 'generator', 'env', 'inputs', 'outputs', 'dep_nodes', 'run_after')
174 def __init__(self, *k, **kw):
175 self.hasrun = NOT_RUN
177 self.generator = kw['generator']
179 self.generator = self
182 """:py:class:`waflib.ConfigSet.ConfigSet` object (make sure to provide one)"""
185 """List of input nodes, which represent the files used by the task instance"""
188 """List of output nodes, which represent the files created by the task instance"""
191 """List of additional nodes to depend on"""
193 self.run_after = set()
194 """Set of tasks that must be executed before this one"""
196 def __lt__(self, other):
197 return self.priority() > other.priority()
198 def __le__(self, other):
199 return self.priority() >= other.priority()
200 def __gt__(self, other):
201 return self.priority() < other.priority()
202 def __ge__(self, other):
203 return self.priority() <= other.priority()
207 :return: current working directory
208 :rtype: :py:class:`waflib.Node.Node`
210 bld = self.generator.bld
211 ret = getattr(self, 'cwd', None) or getattr(bld, 'cwd', bld.bldnode)
212 if isinstance(ret, str):
213 if os.path.isabs(ret):
214 ret = bld.root.make_node(ret)
216 ret = self.generator.path.make_node(ret)
219 def quote_flag(self, x):
221 Surround a process argument by quotes so that a list of arguments can be written to a file
230 x = x.replace('\\', '\\\\')
232 x = x.replace('"', '\\"')
233 if old != x or ' ' in x or '\t' in x or "'" in x:
239 Priority of execution; the higher, the earlier
241 :return: the priority value
242 :rtype: a tuple of numeric values
244 return (self.weight + self.prio_order, - getattr(self.generator, 'tg_idx_count', 0))
246 def split_argfile(self, cmd):
248 Splits a list of process commands into the executable part and its list of arguments
250 :return: a tuple containing the executable first and then the rest of arguments
253 return ([cmd[0]], [self.quote_flag(x) for x in cmd[1:]])
255 def exec_command(self, cmd, **kw):
257 Wrapper for :py:meth:`waflib.Context.Context.exec_command`.
258 This version set the current working directory (``build.variant_dir``),
259 applies PATH settings (if self.env.PATH is provided), and can run long
260 commands through a temporary ``@argfile``.
262 :param cmd: process command to execute
263 :type cmd: list of string (best) or string (process will use a shell)
264 :return: the return code
269 #. cwd: current working directory (Node or string)
270 #. stdout: set to None to prevent waf from capturing the process standard output
271 #. stderr: set to None to prevent waf from capturing the process standard error
272 #. timeout: timeout value (Python 3)
275 kw['cwd'] = self.get_cwd()
277 if hasattr(self, 'timeout'):
278 kw['timeout'] = self.timeout
281 env = kw['env'] = dict(kw.get('env') or self.env.env or os.environ)
282 env['PATH'] = self.env.PATH if isinstance(self.env.PATH, str) else os.pathsep.join(self.env.PATH)
284 if hasattr(self, 'stdout'):
285 kw['stdout'] = self.stdout
286 if hasattr(self, 'stderr'):
287 kw['stderr'] = self.stderr
289 # workaround for command line length limit:
290 # http://support.microsoft.com/kb/830473
291 if not isinstance(cmd, str) and (len(repr(cmd)) >= 8192 if Utils.is_win32 else len(cmd) > 200000):
292 cmd, args = self.split_argfile(cmd)
294 (fd, tmp) = tempfile.mkstemp()
295 os.write(fd, '\r\n'.join(args).encode())
298 Logs.debug('argfile: @%r -> %r', tmp, args)
299 return self.generator.bld.exec_command(cmd + ['@' + tmp], **kw)
304 # anti-virus and indexers can keep files open -_-
307 return self.generator.bld.exec_command(cmd, **kw)
311 Runs the task and handles errors
313 :return: 0 or None if everything is fine
316 # remove the task signature immediately before it is executed
317 # so that the task will be executed again in case of failure
319 del self.generator.bld.task_sigs[self.uid()]
326 self.err_msg = traceback.format_exc()
327 self.hasrun = EXCEPTION
331 self.hasrun = CRASHED
335 except Errors.WafError:
338 self.err_msg = traceback.format_exc()
339 self.hasrun = EXCEPTION
341 self.hasrun = SUCCESS
343 if self.hasrun != SUCCESS and self.scan:
344 # rescan dependencies on next run
346 del self.generator.bld.imp_sigs[self.uid()]
350 def log_display(self, bld):
351 "Writes the execution status on the context logger"
352 if self.generator.bld.progress_bar == 3:
362 if self.generator.bld.progress_bar == 1:
363 c1 = Logs.colors.cursor_off
364 c2 = Logs.colors.cursor_on
365 logger.info(s, extra={'stream': sys.stderr, 'terminator':'', 'c1': c1, 'c2' : c2})
367 logger.info(s, extra={'terminator':'', 'c1': '', 'c2' : ''})
371 Returns an execution status for the console, the progress bar, or the IDE output.
375 col1 = Logs.colors(self.color)
376 col2 = Logs.colors.NORMAL
377 master = self.generator.bld.producer
380 # the current task position, computed as late as possible
381 return master.processed - master.ready.qsize()
383 if self.generator.bld.progress_bar == 1:
384 return self.generator.bld.progress_line(cur(), master.total, col1, col2)
386 if self.generator.bld.progress_bar == 2:
387 ela = str(self.generator.bld.timer)
389 ins = ','.join([n.name for n in self.inputs])
390 except AttributeError:
393 outs = ','.join([n.name for n in self.outputs])
394 except AttributeError:
396 return '|Total %s|Current %s|Inputs %s|Outputs %s|Time %s|\n' % (master.total, cur(), ins, outs, ela)
404 fs = '[%%%dd/%%%dd] %%s%%s%%s%%s\n' % (n, n)
408 return fs % (cur(), total, kw, col1, s, col2)
410 def hash_constraints(self):
412 Identifies a task type for all the constraints relevant for the scheduler: precedence, file production
414 :return: a hash value
417 return (tuple(self.before), tuple(self.after), tuple(self.ext_in), tuple(self.ext_out), self.__class__.__name__, self.hcode)
419 def format_error(self):
421 Returns an error message to display the build failure reasons
426 msg = ': %r\n%r' % (self, getattr(self, 'last_cmd', ''))
428 msg = ' (run with -v to display more information)'
429 name = getattr(self.generator, 'name', '')
430 if getattr(self, "err_msg", None):
432 elif not self.hasrun:
433 return 'task in %r was not executed for some reason: %r' % (name, self)
434 elif self.hasrun == CRASHED:
436 return ' -> task in %r failed with exit status %r%s' % (name, self.err_code, msg)
437 except AttributeError:
438 return ' -> task in %r failed%s' % (name, msg)
439 elif self.hasrun == MISSING:
440 return ' -> missing files in %r%s' % (name, msg)
441 elif self.hasrun == CANCELED:
442 return ' -> %r canceled because of missing dependencies' % name
444 return 'invalid status for task in %r: %r' % (name, self.hasrun)
446 def colon(self, var1, var2):
448 Enable scriptlet expressions of the form ${FOO_ST:FOO}
449 If the first variable (FOO_ST) is empty, then an empty list is returned
451 The results will be slightly different if FOO_ST is a list, for example::
453 env.FOO = ['p1', 'p2']
455 # ${FOO_ST:FOO} returns
458 env.FOO_ST = ['-a', '-b']
459 # ${FOO_ST:FOO} returns
460 ['-a', '-b', 'p1', '-a', '-b', 'p2']
466 if isinstance(var2, str):
470 if isinstance(tmp, str):
471 return [tmp % x for x in it]
480 "string to display to the user"
481 name = self.__class__.__name__
483 if name.endswith(('lib', 'program')) or not self.inputs:
484 node = self.outputs[0]
485 return node.path_from(node.ctx.launch_node())
486 if not (self.inputs or self.outputs):
487 return self.__class__.__name__
488 if len(self.inputs) == 1:
489 node = self.inputs[0]
490 return node.path_from(node.ctx.launch_node())
492 src_str = ' '.join([a.path_from(a.ctx.launch_node()) for a in self.inputs])
493 tgt_str = ' '.join([a.path_from(a.ctx.launch_node()) for a in self.outputs])
498 return '%s: %s%s%s' % (self.__class__.__name__, src_str, sep, tgt_str)
501 "Display keyword used to prettify the console outputs"
502 name = self.__class__.__name__
503 if name.endswith(('lib', 'program')):
505 if len(self.inputs) == 1 and len(self.outputs) == 1:
515 "for debugging purposes"
517 ins = ",".join([x.name for x in self.inputs])
518 outs = ",".join([x.name for x in self.outputs])
519 except AttributeError:
520 ins = ",".join([str(x) for x in self.inputs])
521 outs = ",".join([str(x) for x in self.outputs])
522 return "".join(['\n\t{task %r: ' % id(self), self.__class__.__name__, " ", ins, " -> ", outs, '}'])
526 Returns an identifier used to determine if tasks are up-to-date. Since the
527 identifier will be stored between executions, it must be:
529 - unique for a task: no two tasks return the same value (for a given build context)
530 - the same for a given task instance
532 By default, the node paths, the class name, and the function are used
533 as inputs to compute a hash.
535 The pointer to the object (python built-in 'id') will change between build executions,
536 and must be avoided in such hashes.
543 except AttributeError:
544 m = Utils.md5(self.__class__.__name__)
546 for x in self.inputs + self.outputs:
548 self.uid_ = m.digest()
551 def set_inputs(self, inp):
553 Appends the nodes to the *inputs* list
555 :param inp: input nodes
556 :type inp: node or list of nodes
558 if isinstance(inp, list):
561 self.inputs.append(inp)
563 def set_outputs(self, out):
565 Appends the nodes to the *outputs* list
567 :param out: output nodes
568 :type out: node or list of nodes
570 if isinstance(out, list):
573 self.outputs.append(out)
575 def set_run_after(self, task):
577 Run this task only after the given *task*.
580 :type task: :py:class:`waflib.Task.Task`
582 assert isinstance(task, Task)
583 self.run_after.add(task)
587 Task signatures are stored between build executions, they are use to track the changes
588 made to the input nodes (not to the outputs!). The signature hashes data from various sources:
590 * explicit dependencies: files listed in the inputs (list of node objects) :py:meth:`waflib.Task.Task.sig_explicit_deps`
591 * implicit dependencies: list of nodes returned by scanner methods (when present) :py:meth:`waflib.Task.Task.sig_implicit_deps`
592 * hashed data: variables/values read from task.vars/task.env :py:meth:`waflib.Task.Task.sig_vars`
594 If the signature is expected to give a different result, clear the cache kept in ``self.cache_sig``::
596 from waflib import Task
597 class cls(Task.Task):
599 sig = super(Task.Task, self).signature()
600 delattr(self, 'cache_sig')
601 return super(Task.Task, self).signature()
603 :return: the signature value
604 :rtype: string or bytes
607 return self.cache_sig
608 except AttributeError:
611 self.m = Utils.md5(self.hcode)
614 self.sig_explicit_deps()
619 # implicit deps / scanner results
622 self.sig_implicit_deps()
623 except Errors.TaskRescan:
624 return self.signature()
626 ret = self.cache_sig = self.m.digest()
629 def runnable_status(self):
631 Returns the Task status
633 :return: a task state in :py:const:`waflib.Task.RUN_ME`,
634 :py:const:`waflib.Task.SKIP_ME`, :py:const:`waflib.Task.CANCEL_ME` or :py:const:`waflib.Task.ASK_LATER`.
637 bld = self.generator.bld
638 if bld.is_install < 0:
641 for t in self.run_after:
644 elif t.hasrun < SKIPPED:
645 # a dependency has an error
648 # first compute the signature
650 new_sig = self.signature()
651 except Errors.TaskNotReady:
654 # compare the signature to a signature computed previously
657 prev_sig = bld.task_sigs[key]
659 Logs.debug('task: task %r must run: it was never run before or the task code changed', self)
662 if new_sig != prev_sig:
663 Logs.debug('task: task %r must run: the task signature changed', self)
666 # compare the signatures of the outputs
667 for node in self.outputs:
668 sig = bld.node_sigs.get(node)
670 Logs.debug('task: task %r must run: an output node has no signature', self)
673 Logs.debug('task: task %r must run: an output node was produced by another task', self)
675 if not node.exists():
676 Logs.debug('task: task %r must run: an output node does not exist', self)
679 return (self.always_run and RUN_ME) or SKIP_ME
683 Called after successful execution to record that the task has run by
684 updating the entry in :py:attr:`waflib.Build.BuildContext.task_sigs`.
686 bld = self.generator.bld
687 for node in self.outputs:
688 if not node.exists():
689 self.hasrun = MISSING
690 self.err_msg = '-> missing file: %r' % node.abspath()
691 raise Errors.WafError(self.err_msg)
692 bld.node_sigs[node] = self.uid() # make sure this task produced the files in question
693 bld.task_sigs[self.uid()] = self.signature()
694 if not self.keep_last_cmd:
697 except AttributeError:
700 def sig_explicit_deps(self):
702 Used by :py:meth:`waflib.Task.Task.signature`; it hashes :py:attr:`waflib.Task.Task.inputs`
703 and :py:attr:`waflib.Task.Task.dep_nodes` signatures.
705 bld = self.generator.bld
709 for x in self.inputs + self.dep_nodes:
712 # manual dependencies, they can slow down the builds
714 additional_deps = bld.deps_man
715 for x in self.inputs + self.outputs:
717 d = additional_deps[x]
724 except AttributeError:
725 if hasattr(v, '__call__'):
726 v = v() # dependency is a function, call it
731 Used by :py:meth:`waflib.Task.Task.signature`; it hashes :py:attr:`waflib.Task.Task.env` variables/values
733 sig = self.generator.bld.hash_env_vars(self.env, self.vars)
738 This method, when provided, returns a tuple containing:
740 * a list of nodes corresponding to real files
741 * a list of names for files not found in path_lst
745 from waflib.Task import Task
747 def scan(self, node):
750 The first and second lists in the tuple are stored in :py:attr:`waflib.Build.BuildContext.node_deps` and
751 :py:attr:`waflib.Build.BuildContext.raw_deps` respectively.
754 def sig_implicit_deps(self):
756 Used by :py:meth:`waflib.Task.Task.signature`; it hashes node signatures
757 obtained by scanning for dependencies (:py:meth:`waflib.Task.Task.scan`).
759 The exception :py:class:`waflib.Errors.TaskRescan` is thrown
760 when a file has changed. In this case, the method :py:meth:`waflib.Task.Task.signature` is called
761 once again, and return here to call :py:meth:`waflib.Task.Task.scan` and searching for dependencies.
763 bld = self.generator.bld
765 # get the task signatures from previous runs
767 prev = bld.imp_sigs.get(key, [])
772 if prev == self.compute_sig_implicit_deps():
774 except Errors.TaskNotReady:
776 except EnvironmentError:
777 # when a file was renamed, remove the stale nodes (headers in folders without source files)
778 # this will break the order calculation for headers created during the build in the source directory (should be uncommon)
779 # the behaviour will differ when top != out
780 for x in bld.node_deps.get(self.uid(), []):
781 if not x.is_bld() and not x.exists():
783 del x.parent.children[x.name]
786 del bld.imp_sigs[key]
787 raise Errors.TaskRescan('rescan')
789 # no previous run or the signature of the dependencies has changed, rescan the dependencies
790 (bld.node_deps[key], bld.raw_deps[key]) = self.scan()
792 Logs.debug('deps: scanner for %s: %r; unresolved: %r', self, bld.node_deps[key], bld.raw_deps[key])
794 # recompute the signature and return it
796 bld.imp_sigs[key] = self.compute_sig_implicit_deps()
797 except EnvironmentError:
798 for k in bld.node_deps.get(self.uid(), []):
800 Logs.warn('Dependency %r for %r is missing: check the task declaration and the build order!', k, self)
803 def compute_sig_implicit_deps(self):
805 Used by :py:meth:`waflib.Task.Task.sig_implicit_deps` for computing the actual hash of the
806 :py:class:`waflib.Node.Node` returned by the scanner.
808 :return: a hash value for the implicit dependencies
809 :rtype: string or bytes
812 self.are_implicit_nodes_ready()
814 # scanner returns a node that does not have a signature
815 # just *ignore* the error and let them figure out from the compiler output
817 for k in self.generator.bld.node_deps.get(self.uid(), []):
819 return self.m.digest()
821 def are_implicit_nodes_ready(self):
823 For each node returned by the scanner, see if there is a task that creates it,
824 and infer the build order
826 This has a low performance impact on null builds (1.86s->1.66s) thanks to caching (28s->1.86s)
828 bld = self.generator.bld
830 cache = bld.dct_implicit_nodes
831 except AttributeError:
832 bld.dct_implicit_nodes = cache = {}
834 # one cache per build group
836 dct = cache[bld.current_group]
838 dct = cache[bld.current_group] = {}
839 for tsk in bld.cur_tasks:
840 for x in tsk.outputs:
844 for x in bld.node_deps.get(self.uid(), []):
846 self.run_after.add(dct[x])
850 for tsk in self.run_after:
852 #print "task is not ready..."
853 raise Errors.TaskNotReady('not ready')
854 if sys.hexversion > 0x3000000:
858 except AttributeError:
859 m = Utils.md5(self.__class__.__name__.encode('latin-1', 'xmlcharrefreplace'))
861 for x in self.inputs + self.outputs:
862 up(x.abspath().encode('latin-1', 'xmlcharrefreplace'))
863 self.uid_ = m.digest()
865 uid.__doc__ = Task.uid.__doc__
868 def is_before(t1, t2):
870 Returns a non-zero value if task t1 is to be executed before task t2::
876 waflib.Task.is_before(t1, t2) # True
878 :param t1: Task object
879 :type t1: :py:class:`waflib.Task.Task`
880 :param t2: Task object
881 :type t2: :py:class:`waflib.Task.Task`
883 to_list = Utils.to_list
884 for k in to_list(t2.ext_in):
885 if k in to_list(t1.ext_out):
888 if t1.__class__.__name__ in to_list(t2.after):
891 if t2.__class__.__name__ in to_list(t1.before):
896 def set_file_constraints(tasks):
898 Updates the ``run_after`` attribute of all tasks based on the task inputs and outputs
901 :type tasks: list of :py:class:`waflib.Task.Task`
903 ins = Utils.defaultdict(set)
904 outs = Utils.defaultdict(set)
908 for a in x.dep_nodes:
913 links = set(ins.keys()).intersection(outs.keys())
916 a.run_after.update(outs[k])
919 class TaskGroup(object):
921 Wrap nxm task order constraints into a single object
922 to prevent the creation of large list/set objects
924 This is an optimization
926 def __init__(self, prev, next):
931 def get_hasrun(self):
937 hasrun = property(get_hasrun, None)
939 def set_precedence_constraints(tasks):
941 Updates the ``run_after`` attribute of all tasks based on the after/before/ext_out/ext_in attributes
944 :type tasks: list of :py:class:`waflib.Task.Task`
946 cstr_groups = Utils.defaultdict(list)
948 h = x.hash_constraints()
949 cstr_groups[h].append(x)
951 keys = list(cstr_groups.keys())
954 # this list should be short
955 for i in range(maxi):
956 t1 = cstr_groups[keys[i]][0]
957 for j in range(i + 1, maxi):
958 t2 = cstr_groups[keys[j]][0]
960 # add the constraints based on the comparisons
961 if is_before(t1, t2):
964 elif is_before(t2, t1):
970 a = cstr_groups[keys[a]]
971 b = cstr_groups[keys[b]]
973 if len(a) < 2 or len(b) < 2:
975 x.run_after.update(a)
977 group = TaskGroup(set(a), set(b))
979 x.run_after.add(group)
983 Compiles a scriptlet expression into a Python function
985 :param c: function to compile
987 :return: the function 'f' declared in the input string
994 re_cond = re.compile('(?P<var>\w+)|(?P<or>\|)|(?P<and>&)')
995 re_novar = re.compile(r'^(SRC|TGT)\W+.*?$')
996 reg_act = re.compile(r'(?P<backslash>\\)|(?P<dollar>\$\$)|(?P<subst>\$\{(?P<var>\w+)(?P<code>.*?)\})', re.M)
997 def compile_fun_shell(line):
999 Creates a compiled function to execute a process through a sub-shell
1006 elif g('backslash'):
1009 extr.append((g('var'), g('code')))
1012 line = reg_act.sub(repl, line) or line
1016 # performs substitutions and populates dvars
1025 return 'env[%r]' % x
1029 for (var, meth) in extr:
1032 app('tsk.inputs%s' % meth)
1034 app('" ".join([a.path_from(cwdx) for a in tsk.inputs])')
1037 app('tsk.outputs%s' % meth)
1039 app('" ".join([a.path_from(cwdx) for a in tsk.outputs])')
1041 if meth.startswith(':'):
1042 if var not in dvars:
1046 m = '[a.path_from(cwdx) for a in tsk.inputs]'
1048 m = '[a.path_from(cwdx) for a in tsk.outputs]'
1049 elif re_novar.match(m):
1050 m = '[tsk.inputs%s]' % m[3:]
1051 elif re_novar.match(m):
1052 m = '[tsk.outputs%s]' % m[3:]
1053 elif m[:3] not in ('tsk', 'gen', 'bld'):
1054 dvars.append(meth[1:])
1056 app('" ".join(tsk.colon(%r, %s))' % (var, m))
1057 elif meth.startswith('?'):
1058 # In A?B|C output env.A if one of env.B or env.C is non-empty
1059 expr = re_cond.sub(replc, meth[1:])
1060 app('p(%r) if (%s) else ""' % (var, expr))
1062 app('%s%s' % (var, meth))
1064 if var not in dvars:
1066 app("p('%s')" % var)
1068 parm = "%% (%s) " % (',\n\t\t'.join(parm))
1072 c = COMPILE_TEMPLATE_SHELL % (line, parm)
1073 Logs.debug('action: %s', c.strip().splitlines())
1074 return (funex(c), dvars)
1076 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)
1077 def compile_fun_noshell(line):
1079 Creates a compiled function to execute a process without a sub-shell
1087 # performs substitutions and populates dvars
1096 return 'env[%r]' % x
1098 for m in reg_act_noshell.finditer(line):
1099 if m.group('space'):
1102 elif m.group('text'):
1103 app('[%r]' % m.group('text').replace('$$', '$'))
1104 elif m.group('subst'):
1105 var = m.group('var')
1106 code = m.group('code')
1109 app('[tsk.inputs%s]' % code)
1111 app('[a.path_from(cwdx) for a in tsk.inputs]')
1114 app('[tsk.outputs%s]' % code)
1116 app('[a.path_from(cwdx) for a in tsk.outputs]')
1118 if code.startswith(':'):
1119 # a composed variable ${FOO:OUT}
1120 if not var in dvars:
1124 m = '[a.path_from(cwdx) for a in tsk.inputs]'
1126 m = '[a.path_from(cwdx) for a in tsk.outputs]'
1127 elif re_novar.match(m):
1128 m = '[tsk.inputs%s]' % m[3:]
1129 elif re_novar.match(m):
1130 m = '[tsk.outputs%s]' % m[3:]
1131 elif m[:3] not in ('tsk', 'gen', 'bld'):
1134 app('tsk.colon(%r, %s)' % (var, m))
1135 elif code.startswith('?'):
1136 # In A?B|C output env.A if one of env.B or env.C is non-empty
1137 expr = re_cond.sub(replc, code[1:])
1138 app('to_list(env[%r] if (%s) else [])' % (var, expr))
1140 # plain code such as ${tsk.inputs[0].abspath()}
1141 app('gen.to_list(%s%s)' % (var, code))
1143 # a plain variable such as # a plain variable like ${AR}
1144 app('to_list(env[%r])' % var)
1145 if not var in dvars:
1148 tmp = 'merge(%s, %s)' % (buf[-2], buf[-1])
1151 merge = True # next turn
1153 buf = ['lst.extend(%s)' % x for x in buf]
1154 fun = COMPILE_TEMPLATE_NOSHELL % "\n\t".join(buf)
1155 Logs.debug('action: %s', fun.strip().splitlines())
1156 return (funex(fun), dvars)
1158 def compile_fun(line, shell=False):
1160 Parses a string expression such as '${CC} ${SRC} -o ${TGT}' and returns a pair containing:
1162 * The function created (compiled) for use as :py:meth:`waflib.Task.Task.run`
1163 * The list of variables that must cause rebuilds when *env* data is modified
1167 from waflib.Task import compile_fun
1168 compile_fun('cxx', '${CXX} -o ${TGT[0]} ${SRC} -I ${SRC[0].parent.bldpath()}')
1171 bld(source='wscript', rule='echo "foo\\${SRC[0].name}\\bar"')
1173 The env variables (CXX, ..) on the task must not hold dicts so as to preserve a consistent order.
1174 The reserved keywords ``TGT`` and ``SRC`` represent the task input and output nodes
1177 if isinstance(line, str):
1178 if line.find('<') > 0 or line.find('>') > 0 or line.find('&&') > 0:
1184 if isinstance(x, str):
1185 fun, dvars = compile_fun(x, shell)
1187 funs_lst.append(fun)
1189 # assume a function to let through
1191 def composed_fun(task):
1197 return composed_fun, dvars_lst
1199 return compile_fun_shell(line)
1201 return compile_fun_noshell(line)
1203 def task_factory(name, func=None, vars=None, color='GREEN', ext_in=[], ext_out=[], before=[], after=[], shell=False, scan=None):
1205 Returns a new task subclass with the function ``run`` compiled from the line given.
1207 :param func: method run
1208 :type func: string or function
1209 :param vars: list of variables to hash
1210 :type vars: list of string
1211 :param color: color to use
1213 :param shell: when *func* is a string, enable/disable the use of the shell
1215 :param scan: method scan
1216 :type scan: function
1217 :rtype: :py:class:`waflib.Task.Task`
1221 'vars': vars or [], # function arguments are static, and this one may be modified by the class
1228 if isinstance(func, str) or isinstance(func, tuple):
1229 params['run_str'] = func
1231 params['run'] = func
1233 cls = type(Task)(name, (Task,), params)
1237 cls.ext_in = Utils.to_list(ext_in)
1239 cls.ext_out = Utils.to_list(ext_out)
1241 cls.before = Utils.to_list(before)
1243 cls.after = Utils.to_list(after)
1248 "Provided for compatibility reasons, TaskBase should not be used"