third_party:waf: update to upstream 2.0.4 release
[vlendec/samba-autobuild/.git] / third_party / waf / waflib / Build.py
1 #! /usr/bin/env python
2 # encoding: utf-8
3 # WARNING! Do not edit! https://waf.io/book/index.html#_obtaining_the_waf_file
4
5 #!/usr/bin/env python
6 # encoding: utf-8
7 # Thomas Nagy, 2005-2018 (ita)
8
9 """
10 Classes related to the build phase (build, clean, install, step, etc)
11
12 The inheritance tree is the following:
13
14 """
15
16 import os, sys, errno, re, shutil, stat
17 try:
18         import cPickle
19 except ImportError:
20         import pickle as cPickle
21 from waflib import Node, Runner, TaskGen, Utils, ConfigSet, Task, Logs, Options, Context, Errors
22
23 CACHE_DIR = 'c4che'
24 """Name of the cache directory"""
25
26 CACHE_SUFFIX = '_cache.py'
27 """ConfigSet cache files for variants are written under :py:attr:´waflib.Build.CACHE_DIR´ in the form ´variant_name´_cache.py"""
28
29 INSTALL = 1337
30 """Positive value '->' install, see :py:attr:`waflib.Build.BuildContext.is_install`"""
31
32 UNINSTALL = -1337
33 """Negative value '<-' uninstall, see :py:attr:`waflib.Build.BuildContext.is_install`"""
34
35 SAVED_ATTRS = 'root node_sigs task_sigs imp_sigs raw_deps node_deps'.split()
36 """Build class members to save between the runs; these should be all dicts
37 except for `root` which represents a :py:class:`waflib.Node.Node` instance
38 """
39
40 CFG_FILES = 'cfg_files'
41 """Files from the build directory to hash before starting the build (``config.h`` written during the configuration)"""
42
43 POST_AT_ONCE = 0
44 """Post mode: all task generators are posted before any task executed"""
45
46 POST_LAZY = 1
47 """Post mode: post the task generators group after group, the tasks in the next group are created when the tasks in the previous groups are done"""
48
49 PROTOCOL = -1
50 if sys.platform == 'cli':
51         PROTOCOL = 0
52
53 class BuildContext(Context.Context):
54         '''executes the build'''
55
56         cmd = 'build'
57         variant = ''
58
59         def __init__(self, **kw):
60                 super(BuildContext, self).__init__(**kw)
61
62                 self.is_install = 0
63                 """Non-zero value when installing or uninstalling file"""
64
65                 self.top_dir = kw.get('top_dir', Context.top_dir)
66                 """See :py:attr:`waflib.Context.top_dir`; prefer :py:attr:`waflib.Build.BuildContext.srcnode`"""
67
68                 self.out_dir = kw.get('out_dir', Context.out_dir)
69                 """See :py:attr:`waflib.Context.out_dir`; prefer :py:attr:`waflib.Build.BuildContext.bldnode`"""
70
71                 self.run_dir = kw.get('run_dir', Context.run_dir)
72                 """See :py:attr:`waflib.Context.run_dir`"""
73
74                 self.launch_dir = Context.launch_dir
75                 """See :py:attr:`waflib.Context.out_dir`; prefer :py:meth:`waflib.Build.BuildContext.launch_node`"""
76
77                 self.post_mode = POST_LAZY
78                 """Whether to post the task generators at once or group-by-group (default is group-by-group)"""
79
80                 self.cache_dir = kw.get('cache_dir')
81                 if not self.cache_dir:
82                         self.cache_dir = os.path.join(self.out_dir, CACHE_DIR)
83
84                 self.all_envs = {}
85                 """Map names to :py:class:`waflib.ConfigSet.ConfigSet`, the empty string must map to the default environment"""
86
87                 # ======================================= #
88                 # cache variables
89
90                 self.node_sigs = {}
91                 """Dict mapping build nodes to task identifier (uid), it indicates whether a task created a particular file (persists across builds)"""
92
93                 self.task_sigs = {}
94                 """Dict mapping task identifiers (uid) to task signatures (persists across builds)"""
95
96                 self.imp_sigs = {}
97                 """Dict mapping task identifiers (uid) to implicit task dependencies used for scanning targets (persists across builds)"""
98
99                 self.node_deps = {}
100                 """Dict mapping task identifiers (uid) to node dependencies found by :py:meth:`waflib.Task.Task.scan` (persists across builds)"""
101
102                 self.raw_deps = {}
103                 """Dict mapping task identifiers (uid) to custom data returned by :py:meth:`waflib.Task.Task.scan` (persists across builds)"""
104
105                 self.task_gen_cache_names = {}
106
107                 self.jobs = Options.options.jobs
108                 """Amount of jobs to run in parallel"""
109
110                 self.targets = Options.options.targets
111                 """List of targets to build (default: \*)"""
112
113                 self.keep = Options.options.keep
114                 """Whether the build should continue past errors"""
115
116                 self.progress_bar = Options.options.progress_bar
117                 """
118                 Level of progress status:
119
120                 0. normal output
121                 1. progress bar
122                 2. IDE output
123                 3. No output at all
124                 """
125
126                 # Manual dependencies.
127                 self.deps_man = Utils.defaultdict(list)
128                 """Manual dependencies set by :py:meth:`waflib.Build.BuildContext.add_manual_dependency`"""
129
130                 # just the structure here
131                 self.current_group = 0
132                 """
133                 Current build group
134                 """
135
136                 self.groups = []
137                 """
138                 List containing lists of task generators
139                 """
140
141                 self.group_names = {}
142                 """
143                 Map group names to the group lists. See :py:meth:`waflib.Build.BuildContext.add_group`
144                 """
145
146                 for v in SAVED_ATTRS:
147                         if not hasattr(self, v):
148                                 setattr(self, v, {})
149
150         def get_variant_dir(self):
151                 """Getter for the variant_dir attribute"""
152                 if not self.variant:
153                         return self.out_dir
154                 return os.path.join(self.out_dir, os.path.normpath(self.variant))
155         variant_dir = property(get_variant_dir, None)
156
157         def __call__(self, *k, **kw):
158                 """
159                 Create a task generator and add it to the current build group. The following forms are equivalent::
160
161                         def build(bld):
162                                 tg = bld(a=1, b=2)
163
164                         def build(bld):
165                                 tg = bld()
166                                 tg.a = 1
167                                 tg.b = 2
168
169                         def build(bld):
170                                 tg = TaskGen.task_gen(a=1, b=2)
171                                 bld.add_to_group(tg, None)
172
173                 :param group: group name to add the task generator to
174                 :type group: string
175                 """
176                 kw['bld'] = self
177                 ret = TaskGen.task_gen(*k, **kw)
178                 self.task_gen_cache_names = {} # reset the cache, each time
179                 self.add_to_group(ret, group=kw.get('group'))
180                 return ret
181
182         def __copy__(self):
183                 """
184                 Build contexts cannot be copied
185
186                 :raises: :py:class:`waflib.Errors.WafError`
187                 """
188                 raise Errors.WafError('build contexts cannot be copied')
189
190         def load_envs(self):
191                 """
192                 The configuration command creates files of the form ``build/c4che/NAMEcache.py``. This method
193                 creates a :py:class:`waflib.ConfigSet.ConfigSet` instance for each ``NAME`` by reading those
194                 files and stores them in :py:attr:`waflib.Build.BuildContext.allenvs`.
195                 """
196                 node = self.root.find_node(self.cache_dir)
197                 if not node:
198                         raise Errors.WafError('The project was not configured: run "waf configure" first!')
199                 lst = node.ant_glob('**/*%s' % CACHE_SUFFIX, quiet=True)
200
201                 if not lst:
202                         raise Errors.WafError('The cache directory is empty: reconfigure the project')
203
204                 for x in lst:
205                         name = x.path_from(node).replace(CACHE_SUFFIX, '').replace('\\', '/')
206                         env = ConfigSet.ConfigSet(x.abspath())
207                         self.all_envs[name] = env
208                         for f in env[CFG_FILES]:
209                                 newnode = self.root.find_resource(f)
210                                 if not newnode or not newnode.exists():
211                                         raise Errors.WafError('Missing configuration file %r, reconfigure the project!' % f)
212
213         def init_dirs(self):
214                 """
215                 Initialize the project directory and the build directory by creating the nodes
216                 :py:attr:`waflib.Build.BuildContext.srcnode` and :py:attr:`waflib.Build.BuildContext.bldnode`
217                 corresponding to ``top_dir`` and ``variant_dir`` respectively. The ``bldnode`` directory is
218                 created if necessary.
219                 """
220                 if not (os.path.isabs(self.top_dir) and os.path.isabs(self.out_dir)):
221                         raise Errors.WafError('The project was not configured: run "waf configure" first!')
222
223                 self.path = self.srcnode = self.root.find_dir(self.top_dir)
224                 self.bldnode = self.root.make_node(self.variant_dir)
225                 self.bldnode.mkdir()
226
227         def execute(self):
228                 """
229                 Restore data from previous builds and call :py:meth:`waflib.Build.BuildContext.execute_build`.
230                 Overrides from :py:func:`waflib.Context.Context.execute`
231                 """
232                 self.restore()
233                 if not self.all_envs:
234                         self.load_envs()
235                 self.execute_build()
236
237         def execute_build(self):
238                 """
239                 Execute the build by:
240
241                 * reading the scripts (see :py:meth:`waflib.Context.Context.recurse`)
242                 * calling :py:meth:`waflib.Build.BuildContext.pre_build` to call user build functions
243                 * calling :py:meth:`waflib.Build.BuildContext.compile` to process the tasks
244                 * calling :py:meth:`waflib.Build.BuildContext.post_build` to call user build functions
245                 """
246
247                 Logs.info("Waf: Entering directory `%s'", self.variant_dir)
248                 self.recurse([self.run_dir])
249                 self.pre_build()
250
251                 # display the time elapsed in the progress bar
252                 self.timer = Utils.Timer()
253
254                 try:
255                         self.compile()
256                 finally:
257                         if self.progress_bar == 1 and sys.stderr.isatty():
258                                 c = self.producer.processed or 1
259                                 m = self.progress_line(c, c, Logs.colors.BLUE, Logs.colors.NORMAL)
260                                 Logs.info(m, extra={'stream': sys.stderr, 'c1': Logs.colors.cursor_off, 'c2' : Logs.colors.cursor_on})
261                         Logs.info("Waf: Leaving directory `%s'", self.variant_dir)
262                 try:
263                         self.producer.bld = None
264                         del self.producer
265                 except AttributeError:
266                         pass
267                 self.post_build()
268
269         def restore(self):
270                 """
271                 Load data from a previous run, sets the attributes listed in :py:const:`waflib.Build.SAVED_ATTRS`
272                 """
273                 try:
274                         env = ConfigSet.ConfigSet(os.path.join(self.cache_dir, 'build.config.py'))
275                 except EnvironmentError:
276                         pass
277                 else:
278                         if env.version < Context.HEXVERSION:
279                                 raise Errors.WafError('Project was configured with a different version of Waf, please reconfigure it')
280
281                         for t in env.tools:
282                                 self.setup(**t)
283
284                 dbfn = os.path.join(self.variant_dir, Context.DBFILE)
285                 try:
286                         data = Utils.readf(dbfn, 'rb')
287                 except (EnvironmentError, EOFError):
288                         # handle missing file/empty file
289                         Logs.debug('build: Could not load the build cache %s (missing)', dbfn)
290                 else:
291                         try:
292                                 Node.pickle_lock.acquire()
293                                 Node.Nod3 = self.node_class
294                                 try:
295                                         data = cPickle.loads(data)
296                                 except Exception as e:
297                                         Logs.debug('build: Could not pickle the build cache %s: %r', dbfn, e)
298                                 else:
299                                         for x in SAVED_ATTRS:
300                                                 setattr(self, x, data.get(x, {}))
301                         finally:
302                                 Node.pickle_lock.release()
303
304                 self.init_dirs()
305
306         def store(self):
307                 """
308                 Store data for next runs, set the attributes listed in :py:const:`waflib.Build.SAVED_ATTRS`. Uses a temporary
309                 file to avoid problems on ctrl+c.
310                 """
311                 data = {}
312                 for x in SAVED_ATTRS:
313                         data[x] = getattr(self, x)
314                 db = os.path.join(self.variant_dir, Context.DBFILE)
315
316                 try:
317                         Node.pickle_lock.acquire()
318                         Node.Nod3 = self.node_class
319                         x = cPickle.dumps(data, PROTOCOL)
320                 finally:
321                         Node.pickle_lock.release()
322
323                 Utils.writef(db + '.tmp', x, m='wb')
324
325                 try:
326                         st = os.stat(db)
327                         os.remove(db)
328                         if not Utils.is_win32: # win32 has no chown but we're paranoid
329                                 os.chown(db + '.tmp', st.st_uid, st.st_gid)
330                 except (AttributeError, OSError):
331                         pass
332
333                 # do not use shutil.move (copy is not thread-safe)
334                 os.rename(db + '.tmp', db)
335
336         def compile(self):
337                 """
338                 Run the build by creating an instance of :py:class:`waflib.Runner.Parallel`
339                 The cache file is written when at least a task was executed.
340
341                 :raises: :py:class:`waflib.Errors.BuildError` in case the build fails
342                 """
343                 Logs.debug('build: compile()')
344
345                 # delegate the producer-consumer logic to another object to reduce the complexity
346                 self.producer = Runner.Parallel(self, self.jobs)
347                 self.producer.biter = self.get_build_iterator()
348                 try:
349                         self.producer.start()
350                 except KeyboardInterrupt:
351                         if self.is_dirty():
352                                 self.store()
353                         raise
354                 else:
355                         if self.is_dirty():
356                                 self.store()
357
358                 if self.producer.error:
359                         raise Errors.BuildError(self.producer.error)
360
361         def is_dirty(self):
362                 return self.producer.dirty
363
364         def setup(self, tool, tooldir=None, funs=None):
365                 """
366                 Import waf tools defined during the configuration::
367
368                         def configure(conf):
369                                 conf.load('glib2')
370
371                         def build(bld):
372                                 pass # glib2 is imported implicitly
373
374                 :param tool: tool list
375                 :type tool: list
376                 :param tooldir: optional tool directory (sys.path)
377                 :type tooldir: list of string
378                 :param funs: unused variable
379                 """
380                 if isinstance(tool, list):
381                         for i in tool:
382                                 self.setup(i, tooldir)
383                         return
384
385                 module = Context.load_tool(tool, tooldir)
386                 if hasattr(module, "setup"):
387                         module.setup(self)
388
389         def get_env(self):
390                 """Getter for the env property"""
391                 try:
392                         return self.all_envs[self.variant]
393                 except KeyError:
394                         return self.all_envs['']
395         def set_env(self, val):
396                 """Setter for the env property"""
397                 self.all_envs[self.variant] = val
398
399         env = property(get_env, set_env)
400
401         def add_manual_dependency(self, path, value):
402                 """
403                 Adds a dependency from a node object to a value::
404
405                         def build(bld):
406                                 bld.add_manual_dependency(
407                                         bld.path.find_resource('wscript'),
408                                         bld.root.find_resource('/etc/fstab'))
409
410                 :param path: file path
411                 :type path: string or :py:class:`waflib.Node.Node`
412                 :param value: value to depend
413                 :type value: :py:class:`waflib.Node.Node`, byte object, or function returning a byte object
414                 """
415                 if not path:
416                         raise ValueError('Invalid input path %r' % path)
417
418                 if isinstance(path, Node.Node):
419                         node = path
420                 elif os.path.isabs(path):
421                         node = self.root.find_resource(path)
422                 else:
423                         node = self.path.find_resource(path)
424                 if not node:
425                         raise ValueError('Could not find the path %r' % path)
426
427                 if isinstance(value, list):
428                         self.deps_man[node].extend(value)
429                 else:
430                         self.deps_man[node].append(value)
431
432         def launch_node(self):
433                 """Returns the launch directory as a :py:class:`waflib.Node.Node` object (cached)"""
434                 try:
435                         # private cache
436                         return self.p_ln
437                 except AttributeError:
438                         self.p_ln = self.root.find_dir(self.launch_dir)
439                         return self.p_ln
440
441         def hash_env_vars(self, env, vars_lst):
442                 """
443                 Hashes configuration set variables::
444
445                         def build(bld):
446                                 bld.hash_env_vars(bld.env, ['CXX', 'CC'])
447
448                 This method uses an internal cache.
449
450                 :param env: Configuration Set
451                 :type env: :py:class:`waflib.ConfigSet.ConfigSet`
452                 :param vars_lst: list of variables
453                 :type vars_list: list of string
454                 """
455
456                 if not env.table:
457                         env = env.parent
458                         if not env:
459                                 return Utils.SIG_NIL
460
461                 idx = str(id(env)) + str(vars_lst)
462                 try:
463                         cache = self.cache_env
464                 except AttributeError:
465                         cache = self.cache_env = {}
466                 else:
467                         try:
468                                 return self.cache_env[idx]
469                         except KeyError:
470                                 pass
471
472                 lst = [env[a] for a in vars_lst]
473                 cache[idx] = ret = Utils.h_list(lst)
474                 Logs.debug('envhash: %s %r', Utils.to_hex(ret), lst)
475                 return ret
476
477         def get_tgen_by_name(self, name):
478                 """
479                 Fetches a task generator by its name or its target attribute;
480                 the name must be unique in a build::
481
482                         def build(bld):
483                                 tg = bld(name='foo')
484                                 tg == bld.get_tgen_by_name('foo')
485
486                 This method use a private internal cache.
487
488                 :param name: Task generator name
489                 :raises: :py:class:`waflib.Errors.WafError` in case there is no task genenerator by that name
490                 """
491                 cache = self.task_gen_cache_names
492                 if not cache:
493                         # create the index lazily
494                         for g in self.groups:
495                                 for tg in g:
496                                         try:
497                                                 cache[tg.name] = tg
498                                         except AttributeError:
499                                                 # raised if not a task generator, which should be uncommon
500                                                 pass
501                 try:
502                         return cache[name]
503                 except KeyError:
504                         raise Errors.WafError('Could not find a task generator for the name %r' % name)
505
506         def progress_line(self, idx, total, col1, col2):
507                 """
508                 Computes a progress bar line displayed when running ``waf -p``
509
510                 :returns: progress bar line
511                 :rtype: string
512                 """
513                 if not sys.stderr.isatty():
514                         return ''
515
516                 n = len(str(total))
517
518                 Utils.rot_idx += 1
519                 ind = Utils.rot_chr[Utils.rot_idx % 4]
520
521                 pc = (100. * idx)/total
522                 fs = "[%%%dd/%%d][%%s%%2d%%%%%%s][%s][" % (n, ind)
523                 left = fs % (idx, total, col1, pc, col2)
524                 right = '][%s%s%s]' % (col1, self.timer, col2)
525
526                 cols = Logs.get_term_cols() - len(left) - len(right) + 2*len(col1) + 2*len(col2)
527                 if cols < 7:
528                         cols = 7
529
530                 ratio = ((cols * idx)//total) - 1
531
532                 bar = ('='*ratio+'>').ljust(cols)
533                 msg = Logs.indicator % (left, bar, right)
534
535                 return msg
536
537         def declare_chain(self, *k, **kw):
538                 """
539                 Wraps :py:func:`waflib.TaskGen.declare_chain` for convenience
540                 """
541                 return TaskGen.declare_chain(*k, **kw)
542
543         def pre_build(self):
544                 """Executes user-defined methods before the build starts, see :py:meth:`waflib.Build.BuildContext.add_pre_fun`"""
545                 for m in getattr(self, 'pre_funs', []):
546                         m(self)
547
548         def post_build(self):
549                 """Executes user-defined methods after the build is successful, see :py:meth:`waflib.Build.BuildContext.add_post_fun`"""
550                 for m in getattr(self, 'post_funs', []):
551                         m(self)
552
553         def add_pre_fun(self, meth):
554                 """
555                 Binds a callback method to execute after the scripts are read and before the build starts::
556
557                         def mycallback(bld):
558                                 print("Hello, world!")
559
560                         def build(bld):
561                                 bld.add_pre_fun(mycallback)
562                 """
563                 try:
564                         self.pre_funs.append(meth)
565                 except AttributeError:
566                         self.pre_funs = [meth]
567
568         def add_post_fun(self, meth):
569                 """
570                 Binds a callback method to execute immediately after the build is successful::
571
572                         def call_ldconfig(bld):
573                                 bld.exec_command('/sbin/ldconfig')
574
575                         def build(bld):
576                                 if bld.cmd == 'install':
577                                         bld.add_pre_fun(call_ldconfig)
578                 """
579                 try:
580                         self.post_funs.append(meth)
581                 except AttributeError:
582                         self.post_funs = [meth]
583
584         def get_group(self, x):
585                 """
586                 Returns the build group named `x`, or the current group if `x` is None
587
588                 :param x: name or number or None
589                 :type x: string, int or None
590                 """
591                 if not self.groups:
592                         self.add_group()
593                 if x is None:
594                         return self.groups[self.current_group]
595                 if x in self.group_names:
596                         return self.group_names[x]
597                 return self.groups[x]
598
599         def add_to_group(self, tgen, group=None):
600                 """Adds a task or a task generator to the build; there is no attempt to remove it if it was already added."""
601                 assert(isinstance(tgen, TaskGen.task_gen) or isinstance(tgen, Task.Task))
602                 tgen.bld = self
603                 self.get_group(group).append(tgen)
604
605         def get_group_name(self, g):
606                 """
607                 Returns the name of the input build group
608
609                 :param g: build group object or build group index
610                 :type g: integer or list
611                 :return: name
612                 :rtype: string
613                 """
614                 if not isinstance(g, list):
615                         g = self.groups[g]
616                 for x in self.group_names:
617                         if id(self.group_names[x]) == id(g):
618                                 return x
619                 return ''
620
621         def get_group_idx(self, tg):
622                 """
623                 Returns the index of the group containing the task generator given as argument::
624
625                         def build(bld):
626                                 tg = bld(name='nada')
627                                 0 == bld.get_group_idx(tg)
628
629                 :param tg: Task generator object
630                 :type tg: :py:class:`waflib.TaskGen.task_gen`
631                 :rtype: int
632                 """
633                 se = id(tg)
634                 for i, tmp in enumerate(self.groups):
635                         for t in tmp:
636                                 if id(t) == se:
637                                         return i
638                 return None
639
640         def add_group(self, name=None, move=True):
641                 """
642                 Adds a new group of tasks/task generators. By default the new group becomes
643                 the default group for new task generators (make sure to create build groups in order).
644
645                 :param name: name for this group
646                 :type name: string
647                 :param move: set this new group as default group (True by default)
648                 :type move: bool
649                 :raises: :py:class:`waflib.Errors.WafError` if a group by the name given already exists
650                 """
651                 if name and name in self.group_names:
652                         raise Errors.WafError('add_group: name %s already present', name)
653                 g = []
654                 self.group_names[name] = g
655                 self.groups.append(g)
656                 if move:
657                         self.current_group = len(self.groups) - 1
658
659         def set_group(self, idx):
660                 """
661                 Sets the build group at position idx as current so that newly added
662                 task generators are added to this one by default::
663
664                         def build(bld):
665                                 bld(rule='touch ${TGT}', target='foo.txt')
666                                 bld.add_group() # now the current group is 1
667                                 bld(rule='touch ${TGT}', target='bar.txt')
668                                 bld.set_group(0) # now the current group is 0
669                                 bld(rule='touch ${TGT}', target='truc.txt') # build truc.txt before bar.txt
670
671                 :param idx: group name or group index
672                 :type idx: string or int
673                 """
674                 if isinstance(idx, str):
675                         g = self.group_names[idx]
676                         for i, tmp in enumerate(self.groups):
677                                 if id(g) == id(tmp):
678                                         self.current_group = i
679                                         break
680                 else:
681                         self.current_group = idx
682
683         def total(self):
684                 """
685                 Approximate task count: this value may be inaccurate if task generators
686                 are posted lazily (see :py:attr:`waflib.Build.BuildContext.post_mode`).
687                 The value :py:attr:`waflib.Runner.Parallel.total` is updated during the task execution.
688
689                 :rtype: int
690                 """
691                 total = 0
692                 for group in self.groups:
693                         for tg in group:
694                                 try:
695                                         total += len(tg.tasks)
696                                 except AttributeError:
697                                         total += 1
698                 return total
699
700         def get_targets(self):
701                 """
702                 This method returns a pair containing the index of the last build group to post,
703                 and the list of task generator objects corresponding to the target names.
704
705                 This is used internally by :py:meth:`waflib.Build.BuildContext.get_build_iterator`
706                 to perform partial builds::
707
708                         $ waf --targets=myprogram,myshlib
709
710                 :return: the minimum build group index, and list of task generators
711                 :rtype: tuple
712                 """
713                 to_post = []
714                 min_grp = 0
715                 for name in self.targets.split(','):
716                         tg = self.get_tgen_by_name(name)
717                         m = self.get_group_idx(tg)
718                         if m > min_grp:
719                                 min_grp = m
720                                 to_post = [tg]
721                         elif m == min_grp:
722                                 to_post.append(tg)
723                 return (min_grp, to_post)
724
725         def get_all_task_gen(self):
726                 """
727                 Returns a list of all task generators for troubleshooting purposes.
728                 """
729                 lst = []
730                 for g in self.groups:
731                         lst.extend(g)
732                 return lst
733
734         def post_group(self):
735                 """
736                 Post task generators from the group indexed by self.current_group; used internally
737                 by :py:meth:`waflib.Build.BuildContext.get_build_iterator`
738                 """
739                 def tgpost(tg):
740                         try:
741                                 f = tg.post
742                         except AttributeError:
743                                 pass
744                         else:
745                                 f()
746
747                 if self.targets == '*':
748                         for tg in self.groups[self.current_group]:
749                                 tgpost(tg)
750                 elif self.targets:
751                         if self.current_group < self._min_grp:
752                                 for tg in self.groups[self.current_group]:
753                                         tgpost(tg)
754                         else:
755                                 for tg in self._exact_tg:
756                                         tg.post()
757                 else:
758                         ln = self.launch_node()
759                         if ln.is_child_of(self.bldnode):
760                                 Logs.warn('Building from the build directory, forcing --targets=*')
761                                 ln = self.srcnode
762                         elif not ln.is_child_of(self.srcnode):
763                                 Logs.warn('CWD %s is not under %s, forcing --targets=* (run distclean?)', ln.abspath(), self.srcnode.abspath())
764                                 ln = self.srcnode
765                         for tg in self.groups[self.current_group]:
766                                 try:
767                                         p = tg.path
768                                 except AttributeError:
769                                         pass
770                                 else:
771                                         if p.is_child_of(ln):
772                                                 tgpost(tg)
773
774         def get_tasks_group(self, idx):
775                 """
776                 Returns all task instances for the build group at position idx,
777                 used internally by :py:meth:`waflib.Build.BuildContext.get_build_iterator`
778
779                 :rtype: list of :py:class:`waflib.Task.Task`
780                 """
781                 tasks = []
782                 for tg in self.groups[idx]:
783                         try:
784                                 tasks.extend(tg.tasks)
785                         except AttributeError: # not a task generator
786                                 tasks.append(tg)
787                 return tasks
788
789         def get_build_iterator(self):
790                 """
791                 Creates a Python generator object that returns lists of tasks that may be processed in parallel.
792
793                 :return: tasks which can be executed immediately
794                 :rtype: generator returning lists of :py:class:`waflib.Task.Task`
795                 """
796                 if self.targets and self.targets != '*':
797                         (self._min_grp, self._exact_tg) = self.get_targets()
798
799                 if self.post_mode != POST_LAZY:
800                         for self.current_group, _ in enumerate(self.groups):
801                                 self.post_group()
802
803                 for self.current_group, _ in enumerate(self.groups):
804                         # first post the task generators for the group
805                         if self.post_mode != POST_AT_ONCE:
806                                 self.post_group()
807
808                         # then extract the tasks
809                         tasks = self.get_tasks_group(self.current_group)
810
811                         # if the constraints are set properly (ext_in/ext_out, before/after)
812                         # the call to set_file_constraints may be removed (can be a 15% penalty on no-op rebuilds)
813                         # (but leave set_file_constraints for the installation step)
814                         #
815                         # if the tasks have only files, set_file_constraints is required but set_precedence_constraints is not necessary
816                         #
817                         Task.set_file_constraints(tasks)
818                         Task.set_precedence_constraints(tasks)
819
820                         self.cur_tasks = tasks
821                         if tasks:
822                                 yield tasks
823
824                 while 1:
825                         # the build stops once there are no tasks to process
826                         yield []
827
828         def install_files(self, dest, files, **kw):
829                 """
830                 Creates a task generator to install files on the system::
831
832                         def build(bld):
833                                 bld.install_files('${DATADIR}', self.path.find_resource('wscript'))
834
835                 :param dest: path representing the destination directory
836                 :type dest: :py:class:`waflib.Node.Node` or string (absolute path)
837                 :param files: input files
838                 :type files: list of strings or list of :py:class:`waflib.Node.Node`
839                 :param env: configuration set to expand *dest*
840                 :type env: :py:class:`waflib.ConfigSet.ConfigSet`
841                 :param relative_trick: preserve the folder hierarchy when installing whole folders
842                 :type relative_trick: bool
843                 :param cwd: parent node for searching srcfile, when srcfile is not an instance of :py:class:`waflib.Node.Node`
844                 :type cwd: :py:class:`waflib.Node.Node`
845                 :param postpone: execute the task immediately to perform the installation (False by default)
846                 :type postpone: bool
847                 """
848                 assert(dest)
849                 tg = self(features='install_task', install_to=dest, install_from=files, **kw)
850                 tg.dest = tg.install_to
851                 tg.type = 'install_files'
852                 if not kw.get('postpone', True):
853                         tg.post()
854                 return tg
855
856         def install_as(self, dest, srcfile, **kw):
857                 """
858                 Creates a task generator to install a file on the system with a different name::
859
860                         def build(bld):
861                                 bld.install_as('${PREFIX}/bin', 'myapp', chmod=Utils.O755)
862
863                 :param dest: destination file
864                 :type dest: :py:class:`waflib.Node.Node` or string (absolute path)
865                 :param srcfile: input file
866                 :type srcfile: string or :py:class:`waflib.Node.Node`
867                 :param cwd: parent node for searching srcfile, when srcfile is not an instance of :py:class:`waflib.Node.Node`
868                 :type cwd: :py:class:`waflib.Node.Node`
869                 :param env: configuration set for performing substitutions in dest
870                 :type env: :py:class:`waflib.ConfigSet.ConfigSet`
871                 :param postpone: execute the task immediately to perform the installation (False by default)
872                 :type postpone: bool
873                 """
874                 assert(dest)
875                 tg = self(features='install_task', install_to=dest, install_from=srcfile, **kw)
876                 tg.dest = tg.install_to
877                 tg.type = 'install_as'
878                 if not kw.get('postpone', True):
879                         tg.post()
880                 return tg
881
882         def symlink_as(self, dest, src, **kw):
883                 """
884                 Creates a task generator to install a symlink::
885
886                         def build(bld):
887                                 bld.symlink_as('${PREFIX}/lib/libfoo.so', 'libfoo.so.1.2.3')
888
889                 :param dest: absolute path of the symlink
890                 :type dest: :py:class:`waflib.Node.Node` or string (absolute path)
891                 :param src: link contents, which is a relative or abolute path which may exist or not
892                 :type src: string
893                 :param env: configuration set for performing substitutions in dest
894                 :type env: :py:class:`waflib.ConfigSet.ConfigSet`
895                 :param add: add the task created to a build group - set ``False`` only if the installation task is created after the build has started
896                 :type add: bool
897                 :param postpone: execute the task immediately to perform the installation
898                 :type postpone: bool
899                 :param relative_trick: make the symlink relative (default: ``False``)
900                 :type relative_trick: bool
901                 """
902                 assert(dest)
903                 tg = self(features='install_task', install_to=dest, install_from=src, **kw)
904                 tg.dest = tg.install_to
905                 tg.type = 'symlink_as'
906                 tg.link = src
907                 # TODO if add: self.add_to_group(tsk)
908                 if not kw.get('postpone', True):
909                         tg.post()
910                 return tg
911
912 @TaskGen.feature('install_task')
913 @TaskGen.before_method('process_rule', 'process_source')
914 def process_install_task(self):
915         """Creates the installation task for the current task generator; uses :py:func:`waflib.Build.add_install_task` internally."""
916         self.add_install_task(**self.__dict__)
917
918 @TaskGen.taskgen_method
919 def add_install_task(self, **kw):
920         """
921         Creates the installation task for the current task generator, and executes it immediately if necessary
922
923         :returns: An installation task
924         :rtype: :py:class:`waflib.Build.inst`
925         """
926         if not self.bld.is_install:
927                 return
928         if not kw['install_to']:
929                 return
930
931         if kw['type'] == 'symlink_as' and Utils.is_win32:
932                 if kw.get('win32_install'):
933                         kw['type'] = 'install_as'
934                 else:
935                         # just exit
936                         return
937
938         tsk = self.install_task = self.create_task('inst')
939         tsk.chmod = kw.get('chmod', Utils.O644)
940         tsk.link = kw.get('link', '') or kw.get('install_from', '')
941         tsk.relative_trick = kw.get('relative_trick', False)
942         tsk.type = kw['type']
943         tsk.install_to = tsk.dest = kw['install_to']
944         tsk.install_from = kw['install_from']
945         tsk.relative_base = kw.get('cwd') or kw.get('relative_base', self.path)
946         tsk.install_user = kw.get('install_user')
947         tsk.install_group = kw.get('install_group')
948         tsk.init_files()
949         if not kw.get('postpone', True):
950                 tsk.run_now()
951         return tsk
952
953 @TaskGen.taskgen_method
954 def add_install_files(self, **kw):
955         """
956         Creates an installation task for files
957
958         :returns: An installation task
959         :rtype: :py:class:`waflib.Build.inst`
960         """
961         kw['type'] = 'install_files'
962         return self.add_install_task(**kw)
963
964 @TaskGen.taskgen_method
965 def add_install_as(self, **kw):
966         """
967         Creates an installation task for a single file
968
969         :returns: An installation task
970         :rtype: :py:class:`waflib.Build.inst`
971         """
972         kw['type'] = 'install_as'
973         return self.add_install_task(**kw)
974
975 @TaskGen.taskgen_method
976 def add_symlink_as(self, **kw):
977         """
978         Creates an installation task for a symbolic link
979
980         :returns: An installation task
981         :rtype: :py:class:`waflib.Build.inst`
982         """
983         kw['type'] = 'symlink_as'
984         return self.add_install_task(**kw)
985
986 class inst(Task.Task):
987         """Task that installs files or symlinks; it is typically executed by :py:class:`waflib.Build.InstallContext` and :py:class:`waflib.Build.UnInstallContext`"""
988         def __str__(self):
989                 """Returns an empty string to disable the standard task display"""
990                 return ''
991
992         def uid(self):
993                 """Returns a unique identifier for the task"""
994                 lst = self.inputs + self.outputs + [self.link, self.generator.path.abspath()]
995                 return Utils.h_list(lst)
996
997         def init_files(self):
998                 """
999                 Initializes the task input and output nodes
1000                 """
1001                 if self.type == 'symlink_as':
1002                         inputs = []
1003                 else:
1004                         inputs = self.generator.to_nodes(self.install_from)
1005                         if self.type == 'install_as':
1006                                 assert len(inputs) == 1
1007                 self.set_inputs(inputs)
1008
1009                 dest = self.get_install_path()
1010                 outputs = []
1011                 if self.type == 'symlink_as':
1012                         if self.relative_trick:
1013                                 self.link = os.path.relpath(self.link, os.path.dirname(dest))
1014                         outputs.append(self.generator.bld.root.make_node(dest))
1015                 elif self.type == 'install_as':
1016                         outputs.append(self.generator.bld.root.make_node(dest))
1017                 else:
1018                         for y in inputs:
1019                                 if self.relative_trick:
1020                                         destfile = os.path.join(dest, y.path_from(self.relative_base))
1021                                 else:
1022                                         destfile = os.path.join(dest, y.name)
1023                                 outputs.append(self.generator.bld.root.make_node(destfile))
1024                 self.set_outputs(outputs)
1025
1026         def runnable_status(self):
1027                 """
1028                 Installation tasks are always executed, so this method returns either :py:const:`waflib.Task.ASK_LATER` or :py:const:`waflib.Task.RUN_ME`.
1029                 """
1030                 ret = super(inst, self).runnable_status()
1031                 if ret == Task.SKIP_ME and self.generator.bld.is_install:
1032                         return Task.RUN_ME
1033                 return ret
1034
1035         def post_run(self):
1036                 """
1037                 Disables any post-run operations
1038                 """
1039                 pass
1040
1041         def get_install_path(self, destdir=True):
1042                 """
1043                 Returns the destination path where files will be installed, pre-pending `destdir`.
1044
1045                 :rtype: string
1046                 """
1047                 if isinstance(self.install_to, Node.Node):
1048                         dest = self.install_to.abspath()
1049                 else:
1050                         dest = Utils.subst_vars(self.install_to, self.env)
1051                 if destdir and Options.options.destdir:
1052                         dest = os.path.join(Options.options.destdir, os.path.splitdrive(dest)[1].lstrip(os.sep))
1053                 return dest
1054
1055         def copy_fun(self, src, tgt):
1056                 """
1057                 Copies a file from src to tgt, preserving permissions and trying to work
1058                 around path limitations on Windows platforms. On Unix-like platforms,
1059                 the owner/group of the target file may be set through install_user/install_group
1060
1061                 :param src: absolute path
1062                 :type src: string
1063                 :param tgt: absolute path
1064                 :type tgt: string
1065                 """
1066                 # override this if you want to strip executables
1067                 # kw['tsk'].source is the task that created the files in the build
1068                 if Utils.is_win32 and len(tgt) > 259 and not tgt.startswith('\\\\?\\'):
1069                         tgt = '\\\\?\\' + tgt
1070                 shutil.copy2(src, tgt)
1071                 self.fix_perms(tgt)
1072
1073         def rm_empty_dirs(self, tgt):
1074                 """
1075                 Removes empty folders recursively when uninstalling.
1076
1077                 :param tgt: absolute path
1078                 :type tgt: string
1079                 """
1080                 while tgt:
1081                         tgt = os.path.dirname(tgt)
1082                         try:
1083                                 os.rmdir(tgt)
1084                         except OSError:
1085                                 break
1086
1087         def run(self):
1088                 """
1089                 Performs file or symlink installation
1090                 """
1091                 is_install = self.generator.bld.is_install
1092                 if not is_install: # unnecessary?
1093                         return
1094
1095                 for x in self.outputs:
1096                         if is_install == INSTALL:
1097                                 x.parent.mkdir()
1098                 if self.type == 'symlink_as':
1099                         fun = is_install == INSTALL and self.do_link or self.do_unlink
1100                         fun(self.link, self.outputs[0].abspath())
1101                 else:
1102                         fun = is_install == INSTALL and self.do_install or self.do_uninstall
1103                         launch_node = self.generator.bld.launch_node()
1104                         for x, y in zip(self.inputs, self.outputs):
1105                                 fun(x.abspath(), y.abspath(), x.path_from(launch_node))
1106
1107         def run_now(self):
1108                 """
1109                 Try executing the installation task right now
1110
1111                 :raises: :py:class:`waflib.Errors.TaskNotReady`
1112                 """
1113                 status = self.runnable_status()
1114                 if status not in (Task.RUN_ME, Task.SKIP_ME):
1115                         raise Errors.TaskNotReady('Could not process %r: status %r' % (self, status))
1116                 self.run()
1117                 self.hasrun = Task.SUCCESS
1118
1119         def do_install(self, src, tgt, lbl, **kw):
1120                 """
1121                 Copies a file from src to tgt with given file permissions. The actual copy is only performed
1122                 if the source and target file sizes or timestamps differ. When the copy occurs,
1123                 the file is always first removed and then copied so as to prevent stale inodes.
1124
1125                 :param src: file name as absolute path
1126                 :type src: string
1127                 :param tgt: file destination, as absolute path
1128                 :type tgt: string
1129                 :param lbl: file source description
1130                 :type lbl: string
1131                 :param chmod: installation mode
1132                 :type chmod: int
1133                 :raises: :py:class:`waflib.Errors.WafError` if the file cannot be written
1134                 """
1135                 if not Options.options.force:
1136                         # check if the file is already there to avoid a copy
1137                         try:
1138                                 st1 = os.stat(tgt)
1139                                 st2 = os.stat(src)
1140                         except OSError:
1141                                 pass
1142                         else:
1143                                 # same size and identical timestamps -> make no copy
1144                                 if st1.st_mtime + 2 >= st2.st_mtime and st1.st_size == st2.st_size:
1145                                         if not self.generator.bld.progress_bar:
1146                                                 Logs.info('- install %s (from %s)', tgt, lbl)
1147                                         return False
1148
1149                 if not self.generator.bld.progress_bar:
1150                         Logs.info('+ install %s (from %s)', tgt, lbl)
1151
1152                 # Give best attempt at making destination overwritable,
1153                 # like the 'install' utility used by 'make install' does.
1154                 try:
1155                         os.chmod(tgt, Utils.O644 | stat.S_IMODE(os.stat(tgt).st_mode))
1156                 except EnvironmentError:
1157                         pass
1158
1159                 # following is for shared libs and stale inodes (-_-)
1160                 try:
1161                         os.remove(tgt)
1162                 except OSError:
1163                         pass
1164
1165                 try:
1166                         self.copy_fun(src, tgt)
1167                 except EnvironmentError as e:
1168                         if not os.path.exists(src):
1169                                 Logs.error('File %r does not exist', src)
1170                         elif not os.path.isfile(src):
1171                                 Logs.error('Input %r is not a file', src)
1172                         raise Errors.WafError('Could not install the file %r' % tgt, e)
1173
1174         def fix_perms(self, tgt):
1175                 """
1176                 Change the ownership of the file/folder/link pointed by the given path
1177                 This looks up for `install_user` or `install_group` attributes
1178                 on the task or on the task generator::
1179
1180                         def build(bld):
1181                                 bld.install_as('${PREFIX}/wscript',
1182                                         'wscript',
1183                                         install_user='nobody', install_group='nogroup')
1184                                 bld.symlink_as('${PREFIX}/wscript_link',
1185                                         Utils.subst_vars('${PREFIX}/wscript', bld.env),
1186                                         install_user='nobody', install_group='nogroup')
1187                 """
1188                 if not Utils.is_win32:
1189                         user = getattr(self, 'install_user', None) or getattr(self.generator, 'install_user', None)
1190                         group = getattr(self, 'install_group', None) or getattr(self.generator, 'install_group', None)
1191                         if user or group:
1192                                 Utils.lchown(tgt, user or -1, group or -1)
1193                 if not os.path.islink(tgt):
1194                         os.chmod(tgt, self.chmod)
1195
1196         def do_link(self, src, tgt, **kw):
1197                 """
1198                 Creates a symlink from tgt to src.
1199
1200                 :param src: file name as absolute path
1201                 :type src: string
1202                 :param tgt: file destination, as absolute path
1203                 :type tgt: string
1204                 """
1205                 if os.path.islink(tgt) and os.readlink(tgt) == src:
1206                         if not self.generator.bld.progress_bar:
1207                                 Logs.info('- symlink %s (to %s)', tgt, src)
1208                 else:
1209                         try:
1210                                 os.remove(tgt)
1211                         except OSError:
1212                                 pass
1213                         if not self.generator.bld.progress_bar:
1214                                 Logs.info('+ symlink %s (to %s)', tgt, src)
1215                         os.symlink(src, tgt)
1216                         self.fix_perms(tgt)
1217
1218         def do_uninstall(self, src, tgt, lbl, **kw):
1219                 """
1220                 See :py:meth:`waflib.Build.inst.do_install`
1221                 """
1222                 if not self.generator.bld.progress_bar:
1223                         Logs.info('- remove %s', tgt)
1224
1225                 #self.uninstall.append(tgt)
1226                 try:
1227                         os.remove(tgt)
1228                 except OSError as e:
1229                         if e.errno != errno.ENOENT:
1230                                 if not getattr(self, 'uninstall_error', None):
1231                                         self.uninstall_error = True
1232                                         Logs.warn('build: some files could not be uninstalled (retry with -vv to list them)')
1233                                 if Logs.verbose > 1:
1234                                         Logs.warn('Could not remove %s (error code %r)', e.filename, e.errno)
1235                 self.rm_empty_dirs(tgt)
1236
1237         def do_unlink(self, src, tgt, **kw):
1238                 """
1239                 See :py:meth:`waflib.Build.inst.do_link`
1240                 """
1241                 try:
1242                         if not self.generator.bld.progress_bar:
1243                                 Logs.info('- remove %s', tgt)
1244                         os.remove(tgt)
1245                 except OSError:
1246                         pass
1247                 self.rm_empty_dirs(tgt)
1248
1249 class InstallContext(BuildContext):
1250         '''installs the targets on the system'''
1251         cmd = 'install'
1252
1253         def __init__(self, **kw):
1254                 super(InstallContext, self).__init__(**kw)
1255                 self.is_install = INSTALL
1256
1257 class UninstallContext(InstallContext):
1258         '''removes the targets installed'''
1259         cmd = 'uninstall'
1260
1261         def __init__(self, **kw):
1262                 super(UninstallContext, self).__init__(**kw)
1263                 self.is_install = UNINSTALL
1264
1265 class CleanContext(BuildContext):
1266         '''cleans the project'''
1267         cmd = 'clean'
1268         def execute(self):
1269                 """
1270                 See :py:func:`waflib.Build.BuildContext.execute`.
1271                 """
1272                 self.restore()
1273                 if not self.all_envs:
1274                         self.load_envs()
1275
1276                 self.recurse([self.run_dir])
1277                 try:
1278                         self.clean()
1279                 finally:
1280                         self.store()
1281
1282         def clean(self):
1283                 """
1284                 Remove most files from the build directory, and reset all caches.
1285
1286                 Custom lists of files to clean can be declared as `bld.clean_files`.
1287                 For example, exclude `build/program/myprogram` from getting removed::
1288
1289                         def build(bld):
1290                                 bld.clean_files = bld.bldnode.ant_glob('**',
1291                                         excl='.lock* config.log c4che/* config.h program/myprogram',
1292                                         quiet=True, generator=True)
1293                 """
1294                 Logs.debug('build: clean called')
1295
1296                 if hasattr(self, 'clean_files'):
1297                         for n in self.clean_files:
1298                                 n.delete()
1299                 elif self.bldnode != self.srcnode:
1300                         # would lead to a disaster if top == out
1301                         lst = []
1302                         for env in self.all_envs.values():
1303                                 lst.extend(self.root.find_or_declare(f) for f in env[CFG_FILES])
1304                         for n in self.bldnode.ant_glob('**/*', excl='.lock* *conf_check_*/** config.log c4che/*', quiet=True):
1305                                 if n in lst:
1306                                         continue
1307                                 n.delete()
1308                 self.root.children = {}
1309
1310                 for v in SAVED_ATTRS:
1311                         if v == 'root':
1312                                 continue
1313                         setattr(self, v, {})
1314
1315 class ListContext(BuildContext):
1316         '''lists the targets to execute'''
1317         cmd = 'list'
1318
1319         def execute(self):
1320                 """
1321                 In addition to printing the name of each build target,
1322                 a description column will include text for each task
1323                 generator which has a "description" field set.
1324
1325                 See :py:func:`waflib.Build.BuildContext.execute`.
1326                 """
1327                 self.restore()
1328                 if not self.all_envs:
1329                         self.load_envs()
1330
1331                 self.recurse([self.run_dir])
1332                 self.pre_build()
1333
1334                 # display the time elapsed in the progress bar
1335                 self.timer = Utils.Timer()
1336
1337                 for g in self.groups:
1338                         for tg in g:
1339                                 try:
1340                                         f = tg.post
1341                                 except AttributeError:
1342                                         pass
1343                                 else:
1344                                         f()
1345
1346                 try:
1347                         # force the cache initialization
1348                         self.get_tgen_by_name('')
1349                 except Errors.WafError:
1350                         pass
1351
1352                 targets = sorted(self.task_gen_cache_names)
1353
1354                 # figure out how much to left-justify, for largest target name
1355                 line_just = max(len(t) for t in targets) if targets else 0
1356
1357                 for target in targets:
1358                         tgen = self.task_gen_cache_names[target]
1359
1360                         # Support displaying the description for the target
1361                         # if it was set on the tgen
1362                         descript = getattr(tgen, 'description', '')
1363                         if descript:
1364                                 target = target.ljust(line_just)
1365                                 descript = ': %s' % descript
1366
1367                         Logs.pprint('GREEN', target, label=descript)
1368
1369 class StepContext(BuildContext):
1370         '''executes tasks in a step-by-step fashion, for debugging'''
1371         cmd = 'step'
1372
1373         def __init__(self, **kw):
1374                 super(StepContext, self).__init__(**kw)
1375                 self.files = Options.options.files
1376
1377         def compile(self):
1378                 """
1379                 Overrides :py:meth:`waflib.Build.BuildContext.compile` to perform a partial build
1380                 on tasks matching the input/output pattern given (regular expression matching)::
1381
1382                         $ waf step --files=foo.c,bar.c,in:truc.c,out:bar.o
1383                         $ waf step --files=in:foo.cpp.1.o # link task only
1384
1385                 """
1386                 if not self.files:
1387                         Logs.warn('Add a pattern for the debug build, for example "waf step --files=main.c,app"')
1388                         BuildContext.compile(self)
1389                         return
1390
1391                 targets = []
1392                 if self.targets and self.targets != '*':
1393                         targets = self.targets.split(',')
1394
1395                 for g in self.groups:
1396                         for tg in g:
1397                                 if targets and tg.name not in targets:
1398                                         continue
1399
1400                                 try:
1401                                         f = tg.post
1402                                 except AttributeError:
1403                                         pass
1404                                 else:
1405                                         f()
1406
1407                         for pat in self.files.split(','):
1408                                 matcher = self.get_matcher(pat)
1409                                 for tg in g:
1410                                         if isinstance(tg, Task.Task):
1411                                                 lst = [tg]
1412                                         else:
1413                                                 lst = tg.tasks
1414                                         for tsk in lst:
1415                                                 do_exec = False
1416                                                 for node in tsk.inputs:
1417                                                         if matcher(node, output=False):
1418                                                                 do_exec = True
1419                                                                 break
1420                                                 for node in tsk.outputs:
1421                                                         if matcher(node, output=True):
1422                                                                 do_exec = True
1423                                                                 break
1424                                                 if do_exec:
1425                                                         ret = tsk.run()
1426                                                         Logs.info('%s -> exit %r', tsk, ret)
1427
1428         def get_matcher(self, pat):
1429                 """
1430                 Converts a step pattern into a function
1431
1432                 :param: pat: pattern of the form in:truc.c,out:bar.o
1433                 :returns: Python function that uses Node objects as inputs and returns matches
1434                 :rtype: function
1435                 """
1436                 # this returns a function
1437                 inn = True
1438                 out = True
1439                 if pat.startswith('in:'):
1440                         out = False
1441                         pat = pat.replace('in:', '')
1442                 elif pat.startswith('out:'):
1443                         inn = False
1444                         pat = pat.replace('out:', '')
1445
1446                 anode = self.root.find_node(pat)
1447                 pattern = None
1448                 if not anode:
1449                         if not pat.startswith('^'):
1450                                 pat = '^.+?%s' % pat
1451                         if not pat.endswith('$'):
1452                                 pat = '%s$' % pat
1453                         pattern = re.compile(pat)
1454
1455                 def match(node, output):
1456                         if output and not out:
1457                                 return False
1458                         if not output and not inn:
1459                                 return False
1460
1461                         if anode:
1462                                 return anode == node
1463                         else:
1464                                 return pattern.match(node.abspath())
1465                 return match
1466
1467 class EnvContext(BuildContext):
1468         """Subclass EnvContext to create commands that require configuration data in 'env'"""
1469         fun = cmd = None
1470         def execute(self):
1471                 """
1472                 See :py:func:`waflib.Build.BuildContext.execute`.
1473                 """
1474                 self.restore()
1475                 if not self.all_envs:
1476                         self.load_envs()
1477                 self.recurse([self.run_dir])
1478