3 # Thomas Nagy, 2005-2010 (ita)
6 Tasks represent atomic operations such as processes.
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 occured in the task execution"""
26 """The task did not have to be executed"""
29 """The task was successfully executed"""
32 """The task is not ready to be executed"""
35 """The task does not need to be executed"""
38 """The task must be executed"""
40 # To save some memory during the build, consider discarding tsk.last_cmd in the two templates below
42 COMPILE_TEMPLATE_SHELL
= '''
47 cwdx = getattr(bld, 'cwdx', bld.bldnode) # TODO single cwd value in waf 1.9
48 wd = getattr(tsk, 'cwd', None)
50 tsk.last_cmd = cmd = \'\'\' %s \'\'\' % s
51 return tsk.exec_command(cmd, cwd=wd, env=env.env or None)
54 COMPILE_TEMPLATE_NOSHELL
= '''
59 cwdx = getattr(bld, 'cwdx', bld.bldnode) # TODO single cwd value in waf 1.9
60 wd = getattr(tsk, 'cwd', None)
62 if isinstance(xx, str): return [xx]
64 tsk.last_cmd = lst = []
66 lst = [x for x in lst if x]
67 return tsk.exec_command(lst, cwd=wd, env=env.env or None)
71 "Class tasks created by user scripts or Waf tools (maps names to class objects). Task classes defined in Waf tools are registered here through the metaclass :py:class:`waflib.Task.store_task_type`."
73 class store_task_type(type):
75 Metaclass: store the task classes into :py:const:`waflib.Task.classes`, or to the dict pointed
76 by the class attribute 'register'.
77 The attribute 'run_str' will be processed to compute a method 'run' on the task class
78 The decorator :py:func:`waflib.Task.cache_outputs` is also applied to the class
80 def __init__(cls
, name
, bases
, dict):
81 super(store_task_type
, cls
).__init
__(name
, bases
, dict)
84 if name
.endswith('_task'):
85 name
= name
.replace('_task', '')
86 if name
!= 'evil' and name
!= 'TaskBase':
88 if getattr(cls
, 'run_str', None):
89 # if a string is provided, convert it to a method
90 (f
, dvars
) = compile_fun(cls
.run_str
, cls
.shell
)
91 cls
.hcode
= Utils
.h_cmd(cls
.run_str
)
92 cls
.orig_run_str
= cls
.run_str
93 # change the name of run_str or it is impossible to subclass with a function
96 cls
.vars = list(set(cls
.vars + dvars
))
98 elif getattr(cls
, 'run', None) and not 'hcode' in cls
.__dict
__:
99 # getattr(cls, 'hcode') would look in the upper classes
100 cls
.hcode
= Utils
.h_cmd(cls
.run
)
103 getattr(cls
, 'register', classes
)[name
] = cls
105 evil
= store_task_type('evil', (object,), {})
106 "Base class provided to avoid writing a metaclass, so the code can run in python 2.6 and 3.x unmodified"
108 class TaskBase(evil
):
110 Base class for all Waf tasks, which should be seen as an interface.
111 For illustration purposes, instances of this class will execute the attribute
112 'fun' in :py:meth:`waflib.Task.TaskBase.run`. When in doubt, create
113 subclasses of :py:class:`waflib.Task.Task` instead.
115 Subclasses should override these methods:
117 #. __str__: string to display to the user
118 #. runnable_status: ask the task if it should be run, skipped, or if we have to ask later
119 #. run: let threads execute the task
120 #. post_run: let threads update the data regarding the task (cache)
122 .. warning:: For backward compatibility reasons, the suffix "_task" is truncated in derived class names. This limitation will be removed in Waf 1.9.
126 """Color for the console display, see :py:const:`waflib.Logs.colors_lst`"""
129 """File extensions that objects of this task class might use"""
132 """File extensions that objects of this task class might create"""
135 """List of task class names to execute before instances of this class"""
138 """List of task class names to execute after instances of this class"""
141 """String representing an additional hash for the class representation"""
143 def __init__(self
, *k
, **kw
):
145 The base task class requires a task generator, which will be itself if missing
147 self
.hasrun
= NOT_RUN
149 self
.generator
= kw
['generator']
151 self
.generator
= self
154 "for debugging purposes"
155 return '\n\t{task %r: %s %s}' % (self
.__class
__.__name
__, id(self
), str(getattr(self
, 'fun', '')))
158 "string to display to the user"
159 if hasattr(self
, 'fun'):
160 return self
.fun
.__name
__
161 return self
.__class
__.__name
__
164 "Very fast hashing scheme but not persistent (replace/implement in subclasses and see :py:meth:`waflib.Task.Task.uid`)"
168 if hasattr(self
, 'fun'):
172 def exec_command(self
, cmd
, **kw
):
174 Wrapper for :py:meth:`waflib.Context.Context.exec_command` which sets a current working directory to ``build.variant_dir``
176 :return: the return code
179 bld
= self
.generator
.bld
181 if not kw
.get('cwd', None):
183 except AttributeError:
184 bld
.cwd
= kw
['cwd'] = bld
.variant_dir
185 return bld
.exec_command(cmd
, **kw
)
187 def runnable_status(self
):
191 :return: a task state in :py:const:`waflib.Task.RUN_ME`, :py:const:`waflib.Task.SKIP_ME` or :py:const:`waflib.Task.ASK_LATER`.
198 Assume that the task has had a new attribute ``master`` which is an instance of :py:class:`waflib.Runner.Parallel`.
199 Execute the task and then put it back in the queue :py:attr:`waflib.Runner.Parallel.out` (may be replaced by subclassing).
206 # remove the task signature immediately before it is executed
207 # in case of failure the task will be executed again
209 # TODO waf 1.9 - this breaks encapsulation
210 del self
.generator
.bld
.task_sigs
[self
.uid()]
215 self
.generator
.bld
.returned_tasks
.append(self
)
216 self
.log_display(self
.generator
.bld
)
219 self
.err_msg
= Utils
.ex_stack()
220 self
.hasrun
= EXCEPTION
223 m
.error_handler(self
)
229 self
.hasrun
= CRASHED
233 except Errors
.WafError
:
236 self
.err_msg
= Utils
.ex_stack()
237 self
.hasrun
= EXCEPTION
239 self
.hasrun
= SUCCESS
240 if self
.hasrun
!= SUCCESS
:
241 m
.error_handler(self
)
247 Called by threads to execute the tasks. The default is empty and meant to be overridden in subclasses.
248 It is a bad idea to create nodes in this method (so, no node.ant_glob)
252 if hasattr(self
, 'fun'):
253 return self
.fun(self
)
257 "Update the cache files (executed by threads). Override in subclasses."
260 def log_display(self
, bld
):
261 "Write the execution status on the context logger"
262 if self
.generator
.bld
.progress_bar
== 3:
272 if self
.generator
.bld
.progress_bar
== 1:
273 c1
= Logs
.colors
.cursor_off
274 c2
= Logs
.colors
.cursor_on
275 logger
.info(s
, extra
={'stream': sys
.stderr
, 'terminator':'', 'c1': c1
, 'c2' : c2
})
277 logger
.info(s
, extra
={'terminator':'', 'c1': '', 'c2' : ''})
281 Return an execution status for the console, the progress bar, or the IDE output.
285 col1
= Logs
.colors(self
.color
)
286 col2
= Logs
.colors
.NORMAL
290 # the current task position, computed as late as possible
292 if hasattr(master
, 'ready'):
293 tmp
-= master
.ready
.qsize()
294 return master
.processed
+ tmp
296 if self
.generator
.bld
.progress_bar
== 1:
297 return self
.generator
.bld
.progress_line(cur(), master
.total
, col1
, col2
)
299 if self
.generator
.bld
.progress_bar
== 2:
300 ela
= str(self
.generator
.bld
.timer
)
302 ins
= ','.join([n
.name
for n
in self
.inputs
])
303 except AttributeError:
306 outs
= ','.join([n
.name
for n
in self
.outputs
])
307 except AttributeError:
309 return '|Total %s|Current %s|Inputs %s|Outputs %s|Time %s|\n' % (master
.total
, cur(), ins
, outs
, ela
)
317 fs
= '[%%%dd/%%%dd] %%s%%s%%s%%s\n' % (n
, n
)
321 return fs
% (cur(), total
, kw
, col1
, s
, col2
)
323 def attr(self
, att
, default
=None):
325 Retrieve an attribute from the instance or from the class.
327 :param att: variable name
329 :param default: default value
331 ret
= getattr(self
, att
, self
)
332 if ret
is self
: return getattr(self
.__class
__, att
, default
)
335 def hash_constraints(self
):
337 Identify a task type for all the constraints relevant for the scheduler: precedence, file production
339 :return: a hash value
343 tup
= (str(cls
.before
), str(cls
.after
), str(cls
.ext_in
), str(cls
.ext_out
), cls
.__name
__, cls
.hcode
)
347 def format_error(self
):
349 Error message to display to the user when a build fails
353 msg
= getattr(self
, 'last_cmd', '')
354 name
= getattr(self
.generator
, 'name', '')
355 if getattr(self
, "err_msg", None):
357 elif not self
.hasrun
:
358 return 'task in %r was not executed for some reason: %r' % (name
, self
)
359 elif self
.hasrun
== CRASHED
:
361 return ' -> task in %r failed (exit status %r): %r\n%r' % (name
, self
.err_code
, self
, msg
)
362 except AttributeError:
363 return ' -> task in %r failed: %r\n%r' % (name
, self
, msg
)
364 elif self
.hasrun
== MISSING
:
365 return ' -> missing files in %r: %r\n%r' % (name
, self
, msg
)
367 return 'invalid status for task in %r: %r' % (name
, self
.hasrun
)
369 def colon(self
, var1
, var2
):
371 Support code for scriptlet expressions such as ${FOO_ST:FOO}
372 If the first variable (FOO_ST) is empty, then an empty list is returned
374 The results will be slightly different if FOO_ST is a list, for example::
376 env.FOO_ST = ['-a', '-b']
378 # ${FOO_ST:FOO} returns
381 env.FOO = ['p1', 'p2']
382 # ${FOO_ST:FOO} returns
383 ['-a', '-b', 'p1', '-a', '-b', 'p2']
389 if isinstance(var2
, str):
393 if isinstance(tmp
, str):
394 return [tmp
% x
for x
in it
]
402 class Task(TaskBase
):
404 This class deals with the filesystem (:py:class:`waflib.Node.Node`). The method :py:class:`waflib.Task.Task.runnable_status`
405 uses a hash value (from :py:class:`waflib.Task.Task.signature`) which is persistent from build to build. When the value changes,
406 the task has to be executed. The method :py:class:`waflib.Task.Task.post_run` will assign the task signature to the output
409 .. warning:: For backward compatibility reasons, the suffix "_task" is truncated in derived class names. This limitation will be removed in Waf 1.9.
412 """Variables to depend on (class attribute used for :py:meth:`waflib.Task.Task.sig_vars`)"""
415 """Execute the command with the shell (class attribute)"""
417 def __init__(self
, *k
, **kw
):
418 TaskBase
.__init
__(self
, *k
, **kw
)
421 """ConfigSet object (make sure to provide one)"""
424 """List of input nodes, which represent the files used by the task instance"""
427 """List of output nodes, which represent the files created by the task instance"""
430 """List of additional nodes to depend on"""
432 self
.run_after
= set([])
433 """Set of tasks that must be executed before this one"""
435 # Additionally, you may define the following
436 #self.dep_vars = 'PREFIX DATADIR'
439 "string to display to the user"
440 name
= self
.__class
__.__name
__
442 if (name
.endswith('lib') or name
.endswith('program')) or not self
.inputs
:
443 node
= self
.outputs
[0]
444 return node
.path_from(node
.ctx
.launch_node())
445 if not (self
.inputs
or self
.outputs
):
446 return self
.__class
__.__name
__
447 if len(self
.inputs
) == 1:
448 node
= self
.inputs
[0]
449 return node
.path_from(node
.ctx
.launch_node())
451 src_str
= ' '.join([a
.path_from(a
.ctx
.launch_node()) for a
in self
.inputs
])
452 tgt_str
= ' '.join([a
.path_from(a
.ctx
.launch_node()) for a
in self
.outputs
])
453 if self
.outputs
: sep
= ' -> '
455 return '%s: %s%s%s' % (self
.__class
__.__name
__.replace('_task', ''), src_str
, sep
, tgt_str
)
458 name
= self
.__class
__.__name
__
459 if name
.endswith('lib') or name
.endswith('program'):
461 if len(self
.inputs
) == 1 and len(self
.outputs
) == 1:
471 "for debugging purposes"
473 ins
= ",".join([x
.name
for x
in self
.inputs
])
474 outs
= ",".join([x
.name
for x
in self
.outputs
])
475 except AttributeError:
476 ins
= ",".join([str(x
) for x
in self
.inputs
])
477 outs
= ",".join([str(x
) for x
in self
.outputs
])
478 return "".join(['\n\t{task %r: ' % id(self
), self
.__class
__.__name
__, " ", ins
, " -> ", outs
, '}'])
482 Return an identifier used to determine if tasks are up-to-date. Since the
483 identifier will be stored between executions, it must be:
485 - unique: no two tasks return the same value (for a given build context)
486 - the same for a given task instance
488 By default, the node paths, the class name, and the function are used
489 as inputs to compute a hash.
491 The pointer to the object (python built-in 'id') will change between build executions,
492 and must be avoided in such hashes.
499 except AttributeError:
502 up(self
.__class
__.__name
__)
503 for x
in self
.inputs
+ self
.outputs
:
505 self
.uid_
= m
.digest()
509 def set_inputs(self
, inp
):
511 Append the nodes to the *inputs*
513 :param inp: input nodes
514 :type inp: node or list of nodes
516 if isinstance(inp
, list): self
.inputs
+= inp
517 else: self
.inputs
.append(inp
)
519 def set_outputs(self
, out
):
521 Append the nodes to the *outputs*
523 :param out: output nodes
524 :type out: node or list of nodes
526 if isinstance(out
, list): self
.outputs
+= out
527 else: self
.outputs
.append(out
)
529 def set_run_after(self
, task
):
531 Run this task only after *task*. Affect :py:meth:`waflib.Task.runnable_status`
532 You probably want to use tsk.run_after.add(task) directly
535 :type task: :py:class:`waflib.Task.Task`
537 assert isinstance(task
, TaskBase
)
538 self
.run_after
.add(task
)
542 Task signatures are stored between build executions, they are use to track the changes
543 made to the input nodes (not to the outputs!). The signature hashes data from various sources:
545 * explicit dependencies: files listed in the inputs (list of node objects) :py:meth:`waflib.Task.Task.sig_explicit_deps`
546 * implicit dependencies: list of nodes returned by scanner methods (when present) :py:meth:`waflib.Task.Task.sig_implicit_deps`
547 * hashed data: variables/values read from task.__class__.vars/task.env :py:meth:`waflib.Task.Task.sig_vars`
549 If the signature is expected to give a different result, clear the cache kept in ``self.cache_sig``::
551 from waflib import Task
552 class cls(Task.Task):
554 sig = super(Task.Task, self).signature()
555 delattr(self, 'cache_sig')
556 return super(Task.Task, self).signature()
558 try: return self
.cache_sig
559 except AttributeError: pass
562 self
.m
.update(self
.hcode
)
565 self
.sig_explicit_deps()
570 # implicit deps / scanner results
573 self
.sig_implicit_deps()
574 except Errors
.TaskRescan
:
575 return self
.signature()
577 ret
= self
.cache_sig
= self
.m
.digest()
580 def runnable_status(self
):
582 Override :py:meth:`waflib.Task.TaskBase.runnable_status` to determine if the task is ready
583 to be run (:py:attr:`waflib.Task.Task.run_after`)
585 #return 0 # benchmarking
587 for t
in self
.run_after
:
591 bld
= self
.generator
.bld
593 # first compute the signature
595 new_sig
= self
.signature()
596 except Errors
.TaskNotReady
:
599 # compare the signature to a signature computed previously
602 prev_sig
= bld
.task_sigs
[key
]
604 Logs
.debug("task: task %r must run as it was never run before or the task code changed" % self
)
607 # compare the signatures of the outputs
608 for node
in self
.outputs
:
610 if node
.sig
!= new_sig
:
612 except AttributeError:
613 Logs
.debug("task: task %r must run as the output nodes do not exist" % self
)
616 if new_sig
!= prev_sig
:
622 Called after successful execution to update the cache data :py:class:`waflib.Node.Node` sigs
623 and :py:attr:`waflib.Build.BuildContext.task_sigs`.
625 The node signature is obtained from the task signature, but the output nodes may also get the signature
626 of their contents. See the class decorator :py:func:`waflib.Task.update_outputs` if you need this behaviour.
628 bld
= self
.generator
.bld
629 sig
= self
.signature()
631 for node
in self
.outputs
:
632 # check if the node exists ..
634 os
.stat(node
.abspath())
636 self
.hasrun
= MISSING
637 self
.err_msg
= '-> missing file: %r' % node
.abspath()
638 raise Errors
.WafError(self
.err_msg
)
640 # important, store the signature for the next run
641 node
.sig
= node
.cache_sig
= sig
643 bld
.task_sigs
[self
.uid()] = self
.cache_sig
645 def sig_explicit_deps(self
):
647 Used by :py:meth:`waflib.Task.Task.signature`, hash :py:attr:`waflib.Task.Task.inputs`
648 and :py:attr:`waflib.Task.Task.dep_nodes` signatures.
652 bld
= self
.generator
.bld
656 for x
in self
.inputs
+ self
.dep_nodes
:
659 except (AttributeError, TypeError):
660 raise Errors
.WafError('Missing node signature for %r (required by %r)' % (x
, self
))
662 # manual dependencies, they can slow down the builds
664 additional_deps
= bld
.deps_man
665 for x
in self
.inputs
+ self
.outputs
:
667 d
= additional_deps
[id(x
)]
672 if isinstance(v
, bld
.root
.__class
__):
675 except AttributeError:
676 raise Errors
.WafError('Missing node signature for %r (required by %r)' % (v
, self
))
677 elif hasattr(v
, '__call__'):
678 v
= v() # dependency is a function, call it
681 return self
.m
.digest()
685 Used by :py:meth:`waflib.Task.Task.signature`, hash :py:attr:`waflib.Task.Task.env` variables/values
689 bld
= self
.generator
.bld
693 # dependencies on the environment vars
694 act_sig
= bld
.hash_env_vars(env
, self
.__class
__.vars)
697 # additional variable dependencies, if provided
698 dep_vars
= getattr(self
, 'dep_vars', None)
700 upd(bld
.hash_env_vars(env
, dep_vars
))
702 return self
.m
.digest()
706 This method, when provided, returns a tuple containing:
708 * a list of nodes corresponding to real files
709 * a list of names for files not found in path_lst
713 from waflib.Task import Task
715 def scan(self, node):
718 The first and second lists are stored in :py:attr:`waflib.Build.BuildContext.node_deps` and
719 :py:attr:`waflib.Build.BuildContext.raw_deps` respectively.
722 def sig_implicit_deps(self
):
724 Used by :py:meth:`waflib.Task.Task.signature` hashes node signatures obtained by scanning for dependencies (:py:meth:`waflib.Task.Task.scan`).
726 The exception :py:class:`waflib.Errors.TaskRescan` is thrown
727 when a file has changed. When this occurs, :py:meth:`waflib.Task.Task.signature` is called
728 once again, and this method will be executed once again, this time calling :py:meth:`waflib.Task.Task.scan`
729 for searching the dependencies.
734 bld
= self
.generator
.bld
736 # get the task signatures from previous runs
738 prev
= bld
.task_sigs
.get((key
, 'imp'), [])
743 if prev
== self
.compute_sig_implicit_deps():
745 except Errors
.TaskNotReady
:
747 except EnvironmentError:
748 # when a file was renamed (IOError usually), remove the stale nodes (headers in folders without source files)
749 # this will break the order calculation for headers created during the build in the source directory (should be uncommon)
750 # the behaviour will differ when top != out
751 for x
in bld
.node_deps
.get(self
.uid(), []):
757 del x
.parent
.children
[x
.name
]
760 del bld
.task_sigs
[(key
, 'imp')]
761 raise Errors
.TaskRescan('rescan')
763 # no previous run or the signature of the dependencies has changed, rescan the dependencies
764 (nodes
, names
) = self
.scan()
766 Logs
.debug('deps: scanner for %s returned %s %s' % (str(self
), str(nodes
), str(names
)))
768 # store the dependencies in the cache
769 bld
.node_deps
[key
] = nodes
770 bld
.raw_deps
[key
] = names
773 self
.are_implicit_nodes_ready()
775 # recompute the signature and return it
777 bld
.task_sigs
[(key
, 'imp')] = sig
= self
.compute_sig_implicit_deps()
780 for k
in bld
.node_deps
.get(self
.uid(), []):
784 Logs
.warn('Missing signature for node %r (may cause rebuilds)' % k
)
788 def compute_sig_implicit_deps(self
):
790 Used by :py:meth:`waflib.Task.Task.sig_implicit_deps` for computing the actual hash of the
791 :py:class:`waflib.Node.Node` returned by the scanner.
799 bld
= self
.generator
.bld
801 self
.are_implicit_nodes_ready()
803 # scanner returns a node that does not have a signature
804 # just *ignore* the error and let them figure out from the compiler output
806 for k
in bld
.node_deps
.get(self
.uid(), []):
808 return self
.m
.digest()
810 def are_implicit_nodes_ready(self
):
812 For each node returned by the scanner, see if there is a task behind it, and force the build order
814 The performance impact on null builds is nearly invisible (1.66s->1.86s), but this is due to
815 agressive caching (1.86s->28s)
817 bld
= self
.generator
.bld
819 cache
= bld
.dct_implicit_nodes
820 except AttributeError:
821 bld
.dct_implicit_nodes
= cache
= {}
826 dct
= cache
[bld
.cur
] = {}
827 for tsk
in bld
.cur_tasks
:
828 for x
in tsk
.outputs
:
832 for x
in bld
.node_deps
.get(self
.uid(), []):
834 self
.run_after
.add(dct
[x
])
838 for tsk
in self
.run_after
:
840 #print "task is not ready..."
841 raise Errors
.TaskNotReady('not ready')
842 if sys
.hexversion
> 0x3000000:
846 except AttributeError:
849 up(self
.__class
__.__name
__.encode('iso8859-1', 'xmlcharrefreplace'))
850 for x
in self
.inputs
+ self
.outputs
:
851 up(x
.abspath().encode('iso8859-1', 'xmlcharrefreplace'))
852 self
.uid_
= m
.digest()
854 uid
.__doc
__ = Task
.uid
.__doc
__
857 def is_before(t1
, t2
):
859 Return a non-zero value if task t1 is to be executed before task t2::
865 waflib.Task.is_before(t1, t2) # True
868 :type t1: :py:class:`waflib.Task.TaskBase`
870 :type t2: :py:class:`waflib.Task.TaskBase`
872 to_list
= Utils
.to_list
873 for k
in to_list(t2
.ext_in
):
874 if k
in to_list(t1
.ext_out
):
877 if t1
.__class__
.__name
__ in to_list(t2
.after
):
880 if t2
.__class__
.__name
__ in to_list(t1
.before
):
885 def set_file_constraints(tasks
):
887 Adds tasks to the task 'run_after' attribute based on the task inputs and outputs
890 :type tasks: list of :py:class:`waflib.Task.TaskBase`
892 ins
= Utils
.defaultdict(set)
893 outs
= Utils
.defaultdict(set)
895 for a
in getattr(x
, 'inputs', []) + getattr(x
, 'dep_nodes', []):
897 for a
in getattr(x
, 'outputs', []):
900 links
= set(ins
.keys()).intersection(outs
.keys())
903 a
.run_after
.update(outs
[k
])
905 def set_precedence_constraints(tasks
):
907 Add tasks to the task 'run_after' attribute based on the after/before/ext_out/ext_in attributes
910 :type tasks: list of :py:class:`waflib.Task.TaskBase`
912 cstr_groups
= Utils
.defaultdict(list)
914 h
= x
.hash_constraints()
915 cstr_groups
[h
].append(x
)
917 keys
= list(cstr_groups
.keys())
920 # this list should be short
921 for i
in range(maxi
):
922 t1
= cstr_groups
[keys
[i
]][0]
923 for j
in range(i
+ 1, maxi
):
924 t2
= cstr_groups
[keys
[j
]][0]
926 # add the constraints based on the comparisons
927 if is_before(t1
, t2
):
930 elif is_before(t2
, t1
):
936 aval
= set(cstr_groups
[keys
[a
]])
937 for x
in cstr_groups
[keys
[b
]]:
938 x
.run_after
.update(aval
)
942 Compile a function by 'exec'
944 :param c: function to compile
946 :return: the function 'f' declared in the input string
953 reg_act
= re
.compile(r
"(?P<backslash>\\)|(?P<dollar>\$\$)|(?P<subst>\$\{(?P<var>\w+)(?P<code>.*?)\})", re
.M
)
954 def compile_fun_shell(line
):
956 Create a compiled function to execute a process with the shell
957 WARNING: this method may disappear anytime, so use compile_fun instead
963 if g('dollar'): return "$"
964 elif g('backslash'): return '\\\\'
965 elif g('subst'): extr
.append((g('var'), g('code'))); return "%s"
968 line
= reg_act
.sub(repl
, line
) or line
973 for (var
, meth
) in extr
:
975 if meth
: app('tsk.inputs%s' % meth
)
976 else: app('" ".join([a.path_from(cwdx) for a in tsk.inputs])')
978 if meth
: app('tsk.outputs%s' % meth
)
979 else: app('" ".join([a.path_from(cwdx) for a in tsk.outputs])')
981 if meth
.startswith(':'):
984 m
= '[a.path_from(cwdx) for a in tsk.inputs]'
986 m
= '[a.path_from(cwdx) for a in tsk.outputs]'
987 elif m
[:3] not in ('tsk', 'gen', 'bld'):
988 dvars
.extend([var
, meth
[1:]])
990 app('" ".join(tsk.colon(%r, %s))' % (var
, m
))
992 app('%s%s' % (var
, meth
))
994 if not var
in dvars
: dvars
.append(var
)
996 if parm
: parm
= "%% (%s) " % (',\n\t\t'.join(parm
))
999 c
= COMPILE_TEMPLATE_SHELL
% (line
, parm
)
1001 Logs
.debug('action: %s' % c
.strip().splitlines())
1002 return (funex(c
), dvars
)
1004 def compile_fun_noshell(line
):
1006 Create a compiled function to execute a process without the shell
1007 WARNING: this method may disappear anytime, so use compile_fun instead
1012 if g('dollar'): return "$"
1013 elif g('backslash'): return '\\'
1014 elif g('subst'): extr
.append((g('var'), g('code'))); return "<<|@|>>"
1017 line2
= reg_act
.sub(repl
, line
)
1018 params
= line2
.split('<<|@|>>')
1024 for x
in range(len(extr
)):
1025 params
[x
] = params
[x
].strip()
1027 app("lst.extend(%r)" % params
[x
].split())
1028 (var
, meth
) = extr
[x
]
1030 if meth
: app('lst.append(tsk.inputs%s)' % meth
)
1031 else: app("lst.extend([a.path_from(cwdx) for a in tsk.inputs])")
1033 if meth
: app('lst.append(tsk.outputs%s)' % meth
)
1034 else: app("lst.extend([a.path_from(cwdx) for a in tsk.outputs])")
1036 if meth
.startswith(':'):
1039 m
= '[a.path_from(cwdx) for a in tsk.inputs]'
1041 m
= '[a.path_from(cwdx) for a in tsk.outputs]'
1042 elif m
[:3] not in ('tsk', 'gen', 'bld'):
1043 dvars
.extend([var
, m
])
1045 app('lst.extend(tsk.colon(%r, %s))' % (var
, m
))
1047 app('lst.extend(gen.to_list(%s%s))' % (var
, meth
))
1049 app('lst.extend(to_list(env[%r]))' % var
)
1050 if not var
in dvars
: dvars
.append(var
)
1054 app("lst.extend(%r)" % params
[-1].split())
1055 fun
= COMPILE_TEMPLATE_NOSHELL
% "\n\t".join(buf
)
1056 Logs
.debug('action: %s' % fun
.strip().splitlines())
1057 return (funex(fun
), dvars
)
1059 def compile_fun(line
, shell
=False):
1061 Parse a string expression such as "${CC} ${SRC} -o ${TGT}" and return a pair containing:
1063 * the function created (compiled) for use as :py:meth:`waflib.Task.TaskBase.run`
1064 * the list of variables that imply a dependency from self.env
1068 from waflib.Task import compile_fun
1069 compile_fun('cxx', '${CXX} -o ${TGT[0]} ${SRC} -I ${SRC[0].parent.bldpath()}')
1072 bld(source='wscript', rule='echo "foo\\${SRC[0].name}\\bar"')
1074 The env variables (CXX, ..) on the task must not hold dicts (order)
1075 The reserved keywords *TGT* and *SRC* represent the task input and output nodes
1078 if isinstance(line
, str):
1079 if line
.find('<') > 0 or line
.find('>') > 0 or line
.find('&&') > 0:
1085 if isinstance(x
, str):
1086 fun
, dvars
= compile_fun(x
, shell
)
1088 funs_lst
.append(fun
)
1090 # assume a function to let through
1092 def composed_fun(task
):
1098 return composed_fun
, dvars
1100 return compile_fun_shell(line
)
1102 return compile_fun_noshell(line
)
1104 def task_factory(name
, func
=None, vars=None, color
='GREEN', ext_in
=[], ext_out
=[], before
=[], after
=[], shell
=False, scan
=None):
1106 Returns a new task subclass with the function ``run`` compiled from the line given.
1108 :param func: method run
1109 :type func: string or function
1110 :param vars: list of variables to hash
1111 :type vars: list of string
1112 :param color: color to use
1114 :param shell: when *func* is a string, enable/disable the use of the shell
1116 :param scan: method scan
1117 :type scan: function
1118 :rtype: :py:class:`waflib.Task.Task`
1122 'vars': vars or [], # function arguments are static, and this one may be modified by the class
1125 'ext_in': Utils
.to_list(ext_in
),
1126 'ext_out': Utils
.to_list(ext_out
),
1127 'before': Utils
.to_list(before
),
1128 'after': Utils
.to_list(after
),
1133 if isinstance(func
, str) or isinstance(func
, tuple):
1134 params
['run_str'] = func
1136 params
['run'] = func
1138 cls
= type(Task
)(name
, (Task
,), params
)
1144 def always_run(cls
):
1146 Task class decorator
1148 Set all task instances of this class to be executed whenever a build is started
1149 The task signature is calculated, but the result of the comparation between
1150 task signatures is bypassed
1152 old
= cls
.runnable_status
1158 cls
.runnable_status
= always
1161 def update_outputs(cls
):
1163 Task class decorator
1165 If you want to create files in the source directory. For example, to keep *foo.txt* in the source
1166 directory, create it first and declare::
1169 bld(rule='cp ${SRC} ${TGT}', source='wscript', target='foo.txt', update_outputs=True)
1171 old_post_run
= cls
.post_run
1174 for node
in self
.outputs
:
1175 node
.sig
= node
.cache_sig
= Utils
.h_file(node
.abspath())
1176 self
.generator
.bld
.task_sigs
[node
.abspath()] = self
.uid() # issue #1017
1177 cls
.post_run
= post_run
1180 old_runnable_status
= cls
.runnable_status
1181 def runnable_status(self
):
1182 status
= old_runnable_status(self
)
1183 if status
!= RUN_ME
:
1187 # by default, we check that the output nodes have the signature of the task
1188 # perform a second check, returning 'SKIP_ME' as we are expecting that
1189 # the signatures do not match
1190 bld
= self
.generator
.bld
1191 prev_sig
= bld
.task_sigs
[self
.uid()]
1192 if prev_sig
== self
.signature():
1193 for x
in self
.outputs
:
1194 if not x
.is_child_of(bld
.bldnode
):
1195 # special case of files created in the source directory
1196 # hash them here for convenience -_-
1197 x
.sig
= Utils
.h_file(x
.abspath())
1198 if not x
.sig
or bld
.task_sigs
[x
.abspath()] != self
.uid():
1209 except AttributeError:
1212 cls
.runnable_status
= runnable_status