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