waf

FORK: waf with some random patches
git clone https://git.neptards.moe/neptards/waf.git
Log | Files | Refs | README

Build.py (43783B)


      1 #!/usr/bin/env python
      2 # encoding: utf-8
      3 # Thomas Nagy, 2005-2018 (ita)
      4 
      5 """
      6 Classes related to the build phase (build, clean, install, step, etc)
      7 
      8 The inheritance tree is the following:
      9 
     10 """
     11 
     12 import os, sys, errno, re, shutil, stat
     13 try:
     14 	import cPickle
     15 except ImportError:
     16 	import pickle as cPickle
     17 from waflib import Node, Runner, TaskGen, Utils, ConfigSet, Task, Logs, Options, Context, Errors
     18 
     19 CACHE_DIR = 'c4che'
     20 """Name of the cache directory"""
     21 
     22 CACHE_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 
     25 INSTALL = 1337
     26 """Positive value '->' install, see :py:attr:`waflib.Build.BuildContext.is_install`"""
     27 
     28 UNINSTALL = -1337
     29 """Negative value '<-' uninstall, see :py:attr:`waflib.Build.BuildContext.is_install`"""
     30 
     31 SAVED_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
     33 except for `root` which represents a :py:class:`waflib.Node.Node` instance
     34 """
     35 
     36 CFG_FILES = 'cfg_files'
     37 """Files from the build directory to hash before starting the build (``config.h`` written during the configuration)"""
     38 
     39 POST_AT_ONCE = 0
     40 """Post mode: all task generators are posted before any task executed"""
     41 
     42 POST_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 
     45 PROTOCOL = -1
     46 if sys.platform == 'cli':
     47 	PROTOCOL = 0
     48 
     49 class 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 				if Logs.verbose > 1:
    757 					Logs.warn('Building from the build directory, forcing --targets=*')
    758 				ln = self.srcnode
    759 			elif not ln.is_child_of(self.srcnode):
    760 				if Logs.verbose > 1:
    761 					Logs.warn('CWD %s is not under %s, forcing --targets=* (run distclean?)', ln.abspath(), self.srcnode.abspath())
    762 				ln = self.srcnode
    763 
    764 			def is_post(tg, ln):
    765 				try:
    766 					p = tg.path
    767 				except AttributeError:
    768 					pass
    769 				else:
    770 					if p.is_child_of(ln):
    771 						return True
    772 
    773 			def is_post_group():
    774 				for i, g in enumerate(self.groups):
    775 					if i > self.current_group:
    776 						for tg in g:
    777 							if is_post(tg, ln):
    778 								return True
    779 
    780 			if self.post_mode == POST_LAZY and ln != self.srcnode:
    781 				# partial folder builds require all targets from a previous build group
    782 				if is_post_group():
    783 					ln = self.srcnode
    784 
    785 			for tg in self.groups[self.current_group]:
    786 				if is_post(tg, ln):
    787 					tgpost(tg)
    788 
    789 	def get_tasks_group(self, idx):
    790 		"""
    791 		Returns all task instances for the build group at position idx,
    792 		used internally by :py:meth:`waflib.Build.BuildContext.get_build_iterator`
    793 
    794 		:rtype: list of :py:class:`waflib.Task.Task`
    795 		"""
    796 		tasks = []
    797 		for tg in self.groups[idx]:
    798 			try:
    799 				tasks.extend(tg.tasks)
    800 			except AttributeError: # not a task generator
    801 				tasks.append(tg)
    802 		return tasks
    803 
    804 	def get_build_iterator(self):
    805 		"""
    806 		Creates a Python generator object that returns lists of tasks that may be processed in parallel.
    807 
    808 		:return: tasks which can be executed immediately
    809 		:rtype: generator returning lists of :py:class:`waflib.Task.Task`
    810 		"""
    811 		if self.targets and self.targets != '*':
    812 			(self._min_grp, self._exact_tg) = self.get_targets()
    813 
    814 		if self.post_mode != POST_LAZY:
    815 			for self.current_group, _ in enumerate(self.groups):
    816 				self.post_group()
    817 
    818 		for self.current_group, _ in enumerate(self.groups):
    819 			# first post the task generators for the group
    820 			if self.post_mode != POST_AT_ONCE:
    821 				self.post_group()
    822 
    823 			# then extract the tasks
    824 			tasks = self.get_tasks_group(self.current_group)
    825 
    826 			# if the constraints are set properly (ext_in/ext_out, before/after)
    827 			# the call to set_file_constraints may be removed (can be a 15% penalty on no-op rebuilds)
    828 			# (but leave set_file_constraints for the installation step)
    829 			#
    830 			# if the tasks have only files, set_file_constraints is required but set_precedence_constraints is not necessary
    831 			#
    832 			Task.set_file_constraints(tasks)
    833 			Task.set_precedence_constraints(tasks)
    834 
    835 			self.cur_tasks = tasks
    836 			if tasks:
    837 				yield tasks
    838 
    839 		while 1:
    840 			# the build stops once there are no tasks to process
    841 			yield []
    842 
    843 	def install_files(self, dest, files, **kw):
    844 		"""
    845 		Creates a task generator to install files on the system::
    846 
    847 			def build(bld):
    848 				bld.install_files('${DATADIR}', self.path.find_resource('wscript'))
    849 
    850 		:param dest: path representing the destination directory
    851 		:type dest: :py:class:`waflib.Node.Node` or string (absolute path)
    852 		:param files: input files
    853 		:type files: list of strings or list of :py:class:`waflib.Node.Node`
    854 		:param env: configuration set to expand *dest*
    855 		:type env: :py:class:`waflib.ConfigSet.ConfigSet`
    856 		:param relative_trick: preserve the folder hierarchy when installing whole folders
    857 		:type relative_trick: bool
    858 		:param cwd: parent node for searching srcfile, when srcfile is not an instance of :py:class:`waflib.Node.Node`
    859 		:type cwd: :py:class:`waflib.Node.Node`
    860 		:param postpone: execute the task immediately to perform the installation (False by default)
    861 		:type postpone: bool
    862 		"""
    863 		assert(dest)
    864 		tg = self(features='install_task', install_to=dest, install_from=files, **kw)
    865 		tg.dest = tg.install_to
    866 		tg.type = 'install_files'
    867 		if not kw.get('postpone', True):
    868 			tg.post()
    869 		return tg
    870 
    871 	def install_as(self, dest, srcfile, **kw):
    872 		"""
    873 		Creates a task generator to install a file on the system with a different name::
    874 
    875 			def build(bld):
    876 				bld.install_as('${PREFIX}/bin', 'myapp', chmod=Utils.O755)
    877 
    878 		:param dest: destination file
    879 		:type dest: :py:class:`waflib.Node.Node` or string (absolute path)
    880 		:param srcfile: input file
    881 		:type srcfile: string or :py:class:`waflib.Node.Node`
    882 		:param cwd: parent node for searching srcfile, when srcfile is not an instance of :py:class:`waflib.Node.Node`
    883 		:type cwd: :py:class:`waflib.Node.Node`
    884 		:param env: configuration set for performing substitutions in dest
    885 		:type env: :py:class:`waflib.ConfigSet.ConfigSet`
    886 		:param postpone: execute the task immediately to perform the installation (False by default)
    887 		:type postpone: bool
    888 		"""
    889 		assert(dest)
    890 		tg = self(features='install_task', install_to=dest, install_from=srcfile, **kw)
    891 		tg.dest = tg.install_to
    892 		tg.type = 'install_as'
    893 		if not kw.get('postpone', True):
    894 			tg.post()
    895 		return tg
    896 
    897 	def symlink_as(self, dest, src, **kw):
    898 		"""
    899 		Creates a task generator to install a symlink::
    900 
    901 			def build(bld):
    902 				bld.symlink_as('${PREFIX}/lib/libfoo.so', 'libfoo.so.1.2.3')
    903 
    904 		:param dest: absolute path of the symlink
    905 		:type dest: :py:class:`waflib.Node.Node` or string (absolute path)
    906 		:param src: link contents, which is a relative or absolute path which may exist or not
    907 		:type src: string
    908 		:param env: configuration set for performing substitutions in dest
    909 		:type env: :py:class:`waflib.ConfigSet.ConfigSet`
    910 		:param add: add the task created to a build group - set ``False`` only if the installation task is created after the build has started
    911 		:type add: bool
    912 		:param postpone: execute the task immediately to perform the installation
    913 		:type postpone: bool
    914 		:param relative_trick: make the symlink relative (default: ``False``)
    915 		:type relative_trick: bool
    916 		"""
    917 		assert(dest)
    918 		tg = self(features='install_task', install_to=dest, install_from=src, **kw)
    919 		tg.dest = tg.install_to
    920 		tg.type = 'symlink_as'
    921 		tg.link = src
    922 		# TODO if add: self.add_to_group(tsk)
    923 		if not kw.get('postpone', True):
    924 			tg.post()
    925 		return tg
    926 
    927 @TaskGen.feature('install_task')
    928 @TaskGen.before_method('process_rule', 'process_source')
    929 def process_install_task(self):
    930 	"""Creates the installation task for the current task generator; uses :py:func:`waflib.Build.add_install_task` internally."""
    931 	self.add_install_task(**self.__dict__)
    932 
    933 @TaskGen.taskgen_method
    934 def add_install_task(self, **kw):
    935 	"""
    936 	Creates the installation task for the current task generator, and executes it immediately if necessary
    937 
    938 	:returns: An installation task
    939 	:rtype: :py:class:`waflib.Build.inst`
    940 	"""
    941 	if not self.bld.is_install:
    942 		return
    943 	if not kw['install_to']:
    944 		return
    945 
    946 	if kw['type'] == 'symlink_as' and Utils.is_win32:
    947 		if kw.get('win32_install'):
    948 			kw['type'] = 'install_as'
    949 		else:
    950 			# just exit
    951 			return
    952 
    953 	tsk = self.install_task = self.create_task('inst')
    954 	tsk.chmod = kw.get('chmod', Utils.O644)
    955 	tsk.link = kw.get('link', '') or kw.get('install_from', '')
    956 	tsk.relative_trick = kw.get('relative_trick', False)
    957 	tsk.type = kw['type']
    958 	tsk.install_to = tsk.dest = kw['install_to']
    959 	tsk.install_from = kw['install_from']
    960 	tsk.relative_base = kw.get('cwd') or kw.get('relative_base', self.path)
    961 	tsk.install_user = kw.get('install_user')
    962 	tsk.install_group = kw.get('install_group')
    963 	tsk.init_files()
    964 	if not kw.get('postpone', True):
    965 		tsk.run_now()
    966 	return tsk
    967 
    968 @TaskGen.taskgen_method
    969 def add_install_files(self, **kw):
    970 	"""
    971 	Creates an installation task for files
    972 
    973 	:returns: An installation task
    974 	:rtype: :py:class:`waflib.Build.inst`
    975 	"""
    976 	kw['type'] = 'install_files'
    977 	return self.add_install_task(**kw)
    978 
    979 @TaskGen.taskgen_method
    980 def add_install_as(self, **kw):
    981 	"""
    982 	Creates an installation task for a single file
    983 
    984 	:returns: An installation task
    985 	:rtype: :py:class:`waflib.Build.inst`
    986 	"""
    987 	kw['type'] = 'install_as'
    988 	return self.add_install_task(**kw)
    989 
    990 @TaskGen.taskgen_method
    991 def add_symlink_as(self, **kw):
    992 	"""
    993 	Creates an installation task for a symbolic link
    994 
    995 	:returns: An installation task
    996 	:rtype: :py:class:`waflib.Build.inst`
    997 	"""
    998 	kw['type'] = 'symlink_as'
    999 	return self.add_install_task(**kw)
   1000 
   1001 class inst(Task.Task):
   1002 	"""Task that installs files or symlinks; it is typically executed by :py:class:`waflib.Build.InstallContext` and :py:class:`waflib.Build.UnInstallContext`"""
   1003 	def __str__(self):
   1004 		"""Returns an empty string to disable the standard task display"""
   1005 		return ''
   1006 
   1007 	def uid(self):
   1008 		"""Returns a unique identifier for the task"""
   1009 		lst = self.inputs + self.outputs + [self.link, self.generator.path.abspath()]
   1010 		return Utils.h_list(lst)
   1011 
   1012 	def init_files(self):
   1013 		"""
   1014 		Initializes the task input and output nodes
   1015 		"""
   1016 		if self.type == 'symlink_as':
   1017 			inputs = []
   1018 		else:
   1019 			inputs = self.generator.to_nodes(self.install_from)
   1020 			if self.type == 'install_as':
   1021 				assert len(inputs) == 1
   1022 		self.set_inputs(inputs)
   1023 
   1024 		dest = self.get_install_path()
   1025 		outputs = []
   1026 		if self.type == 'symlink_as':
   1027 			if self.relative_trick:
   1028 				self.link = os.path.relpath(self.link, os.path.dirname(dest))
   1029 			outputs.append(self.generator.bld.root.make_node(dest))
   1030 		elif self.type == 'install_as':
   1031 			outputs.append(self.generator.bld.root.make_node(dest))
   1032 		else:
   1033 			for y in inputs:
   1034 				if self.relative_trick:
   1035 					destfile = os.path.join(dest, y.path_from(self.relative_base))
   1036 				else:
   1037 					destfile = os.path.join(dest, y.name)
   1038 				outputs.append(self.generator.bld.root.make_node(destfile))
   1039 		self.set_outputs(outputs)
   1040 
   1041 	def runnable_status(self):
   1042 		"""
   1043 		Installation tasks are always executed, so this method returns either :py:const:`waflib.Task.ASK_LATER` or :py:const:`waflib.Task.RUN_ME`.
   1044 		"""
   1045 		ret = super(inst, self).runnable_status()
   1046 		if ret == Task.SKIP_ME and self.generator.bld.is_install:
   1047 			return Task.RUN_ME
   1048 		return ret
   1049 
   1050 	def post_run(self):
   1051 		"""
   1052 		Disables any post-run operations
   1053 		"""
   1054 		pass
   1055 
   1056 	def get_install_path(self, destdir=True):
   1057 		"""
   1058 		Returns the destination path where files will be installed, pre-pending `destdir`.
   1059 
   1060 		Relative paths will be interpreted relative to `PREFIX` if no `destdir` is given.
   1061 
   1062 		:rtype: string
   1063 		"""
   1064 		if isinstance(self.install_to, Node.Node):
   1065 			dest = self.install_to.abspath()
   1066 		else:
   1067 			dest = os.path.normpath(Utils.subst_vars(self.install_to, self.env))
   1068 		if not os.path.isabs(dest):
   1069 			dest = os.path.join(self.env.PREFIX, dest)
   1070 		if destdir and Options.options.destdir:
   1071 			dest = Options.options.destdir.rstrip(os.sep) + os.sep + os.path.splitdrive(dest)[1].lstrip(os.sep)
   1072 		return dest
   1073 
   1074 	def copy_fun(self, src, tgt):
   1075 		"""
   1076 		Copies a file from src to tgt, preserving permissions and trying to work
   1077 		around path limitations on Windows platforms. On Unix-like platforms,
   1078 		the owner/group of the target file may be set through install_user/install_group
   1079 
   1080 		:param src: absolute path
   1081 		:type src: string
   1082 		:param tgt: absolute path
   1083 		:type tgt: string
   1084 		"""
   1085 		# override this if you want to strip executables
   1086 		# kw['tsk'].source is the task that created the files in the build
   1087 		if Utils.is_win32 and len(tgt) > 259 and not tgt.startswith('\\\\?\\'):
   1088 			tgt = '\\\\?\\' + tgt
   1089 		shutil.copy2(src, tgt)
   1090 		self.fix_perms(tgt)
   1091 
   1092 	def rm_empty_dirs(self, tgt):
   1093 		"""
   1094 		Removes empty folders recursively when uninstalling.
   1095 
   1096 		:param tgt: absolute path
   1097 		:type tgt: string
   1098 		"""
   1099 		while tgt:
   1100 			tgt = os.path.dirname(tgt)
   1101 			try:
   1102 				os.rmdir(tgt)
   1103 			except OSError:
   1104 				break
   1105 
   1106 	def run(self):
   1107 		"""
   1108 		Performs file or symlink installation
   1109 		"""
   1110 		is_install = self.generator.bld.is_install
   1111 		if not is_install: # unnecessary?
   1112 			return
   1113 
   1114 		for x in self.outputs:
   1115 			if is_install == INSTALL:
   1116 				x.parent.mkdir()
   1117 		if self.type == 'symlink_as':
   1118 			fun = is_install == INSTALL and self.do_link or self.do_unlink
   1119 			fun(self.link, self.outputs[0].abspath())
   1120 		else:
   1121 			fun = is_install == INSTALL and self.do_install or self.do_uninstall
   1122 			launch_node = self.generator.bld.launch_node()
   1123 			for x, y in zip(self.inputs, self.outputs):
   1124 				fun(x.abspath(), y.abspath(), x.path_from(launch_node))
   1125 
   1126 	def run_now(self):
   1127 		"""
   1128 		Try executing the installation task right now
   1129 
   1130 		:raises: :py:class:`waflib.Errors.TaskNotReady`
   1131 		"""
   1132 		status = self.runnable_status()
   1133 		if status not in (Task.RUN_ME, Task.SKIP_ME):
   1134 			raise Errors.TaskNotReady('Could not process %r: status %r' % (self, status))
   1135 		self.run()
   1136 		self.hasrun = Task.SUCCESS
   1137 
   1138 	def do_install(self, src, tgt, lbl, **kw):
   1139 		"""
   1140 		Copies a file from src to tgt with given file permissions. The actual copy is only performed
   1141 		if the source and target file sizes or timestamps differ. When the copy occurs,
   1142 		the file is always first removed and then copied so as to prevent stale inodes.
   1143 
   1144 		:param src: file name as absolute path
   1145 		:type src: string
   1146 		:param tgt: file destination, as absolute path
   1147 		:type tgt: string
   1148 		:param lbl: file source description
   1149 		:type lbl: string
   1150 		:param chmod: installation mode
   1151 		:type chmod: int
   1152 		:raises: :py:class:`waflib.Errors.WafError` if the file cannot be written
   1153 		"""
   1154 		if not Options.options.force:
   1155 			# check if the file is already there to avoid a copy
   1156 			try:
   1157 				st1 = os.stat(tgt)
   1158 				st2 = os.stat(src)
   1159 			except OSError:
   1160 				pass
   1161 			else:
   1162 				# same size and identical timestamps -> make no copy
   1163 				if st1.st_mtime + 2 >= st2.st_mtime and st1.st_size == st2.st_size:
   1164 					if not self.generator.bld.progress_bar:
   1165 
   1166 						c1 = Logs.colors.NORMAL
   1167 						c2 = Logs.colors.BLUE
   1168 
   1169 						Logs.info('%s- install %s%s%s (from %s)', c1, c2, tgt, c1, lbl)
   1170 					return False
   1171 
   1172 		if not self.generator.bld.progress_bar:
   1173 
   1174 			c1 = Logs.colors.NORMAL
   1175 			c2 = Logs.colors.BLUE
   1176 
   1177 			Logs.info('%s+ install %s%s%s (from %s)', c1, c2, tgt, c1, lbl)
   1178 
   1179 		# Give best attempt at making destination overwritable,
   1180 		# like the 'install' utility used by 'make install' does.
   1181 		try:
   1182 			os.chmod(tgt, Utils.O644 | stat.S_IMODE(os.stat(tgt).st_mode))
   1183 		except EnvironmentError:
   1184 			pass
   1185 
   1186 		# following is for shared libs and stale inodes (-_-)
   1187 		try:
   1188 			os.remove(tgt)
   1189 		except OSError:
   1190 			pass
   1191 
   1192 		try:
   1193 			self.copy_fun(src, tgt)
   1194 		except EnvironmentError as e:
   1195 			if not os.path.exists(src):
   1196 				Logs.error('File %r does not exist', src)
   1197 			elif not os.path.isfile(src):
   1198 				Logs.error('Input %r is not a file', src)
   1199 			raise Errors.WafError('Could not install the file %r' % tgt, e)
   1200 
   1201 	def fix_perms(self, tgt):
   1202 		"""
   1203 		Change the ownership of the file/folder/link pointed by the given path
   1204 		This looks up for `install_user` or `install_group` attributes
   1205 		on the task or on the task generator::
   1206 
   1207 			def build(bld):
   1208 				bld.install_as('${PREFIX}/wscript',
   1209 					'wscript',
   1210 					install_user='nobody', install_group='nogroup')
   1211 				bld.symlink_as('${PREFIX}/wscript_link',
   1212 					Utils.subst_vars('${PREFIX}/wscript', bld.env),
   1213 					install_user='nobody', install_group='nogroup')
   1214 		"""
   1215 		if not Utils.is_win32:
   1216 			user = getattr(self, 'install_user', None) or getattr(self.generator, 'install_user', None)
   1217 			group = getattr(self, 'install_group', None) or getattr(self.generator, 'install_group', None)
   1218 			if user or group:
   1219 				Utils.lchown(tgt, user or -1, group or -1)
   1220 		if not os.path.islink(tgt):
   1221 			os.chmod(tgt, self.chmod)
   1222 
   1223 	def do_link(self, src, tgt, **kw):
   1224 		"""
   1225 		Creates a symlink from tgt to src.
   1226 
   1227 		:param src: file name as absolute path
   1228 		:type src: string
   1229 		:param tgt: file destination, as absolute path
   1230 		:type tgt: string
   1231 		"""
   1232 		if os.path.islink(tgt) and os.readlink(tgt) == src:
   1233 			if not self.generator.bld.progress_bar:
   1234 				c1 = Logs.colors.NORMAL
   1235 				c2 = Logs.colors.BLUE
   1236 				Logs.info('%s- symlink %s%s%s (to %s)', c1, c2, tgt, c1, src)
   1237 		else:
   1238 			try:
   1239 				os.remove(tgt)
   1240 			except OSError:
   1241 				pass
   1242 			if not self.generator.bld.progress_bar:
   1243 				c1 = Logs.colors.NORMAL
   1244 				c2 = Logs.colors.BLUE
   1245 				Logs.info('%s+ symlink %s%s%s (to %s)', c1, c2, tgt, c1, src)
   1246 			os.symlink(src, tgt)
   1247 			self.fix_perms(tgt)
   1248 
   1249 	def do_uninstall(self, src, tgt, lbl, **kw):
   1250 		"""
   1251 		See :py:meth:`waflib.Build.inst.do_install`
   1252 		"""
   1253 		if not self.generator.bld.progress_bar:
   1254 			c1 = Logs.colors.NORMAL
   1255 			c2 = Logs.colors.BLUE
   1256 			Logs.info('%s- remove %s%s%s', c1, c2, tgt, c1)
   1257 
   1258 		#self.uninstall.append(tgt)
   1259 		try:
   1260 			os.remove(tgt)
   1261 		except OSError as e:
   1262 			if e.errno != errno.ENOENT:
   1263 				if not getattr(self, 'uninstall_error', None):
   1264 					self.uninstall_error = True
   1265 					Logs.warn('build: some files could not be uninstalled (retry with -vv to list them)')
   1266 				if Logs.verbose > 1:
   1267 					Logs.warn('Could not remove %s (error code %r)', e.filename, e.errno)
   1268 		self.rm_empty_dirs(tgt)
   1269 
   1270 	def do_unlink(self, src, tgt, **kw):
   1271 		"""
   1272 		See :py:meth:`waflib.Build.inst.do_link`
   1273 		"""
   1274 		try:
   1275 			if not self.generator.bld.progress_bar:
   1276 				c1 = Logs.colors.NORMAL
   1277 				c2 = Logs.colors.BLUE
   1278 				Logs.info('%s- remove %s%s%s', c1, c2, tgt, c1)
   1279 			os.remove(tgt)
   1280 		except OSError:
   1281 			pass
   1282 		self.rm_empty_dirs(tgt)
   1283 
   1284 class InstallContext(BuildContext):
   1285 	'''installs the targets on the system'''
   1286 	cmd = 'install'
   1287 
   1288 	def __init__(self, **kw):
   1289 		super(InstallContext, self).__init__(**kw)
   1290 		self.is_install = INSTALL
   1291 
   1292 class UninstallContext(InstallContext):
   1293 	'''removes the targets installed'''
   1294 	cmd = 'uninstall'
   1295 
   1296 	def __init__(self, **kw):
   1297 		super(UninstallContext, self).__init__(**kw)
   1298 		self.is_install = UNINSTALL
   1299 
   1300 class CleanContext(BuildContext):
   1301 	'''cleans the project'''
   1302 	cmd = 'clean'
   1303 	def execute(self):
   1304 		"""
   1305 		See :py:func:`waflib.Build.BuildContext.execute`.
   1306 		"""
   1307 		self.restore()
   1308 		if not self.all_envs:
   1309 			self.load_envs()
   1310 
   1311 		self.recurse([self.run_dir])
   1312 		try:
   1313 			self.clean()
   1314 		finally:
   1315 			self.store()
   1316 
   1317 	def clean(self):
   1318 		"""
   1319 		Remove most files from the build directory, and reset all caches.
   1320 
   1321 		Custom lists of files to clean can be declared as `bld.clean_files`.
   1322 		For example, exclude `build/program/myprogram` from getting removed::
   1323 
   1324 			def build(bld):
   1325 				bld.clean_files = bld.bldnode.ant_glob('**',
   1326 					excl='.lock* config.log c4che/* config.h program/myprogram',
   1327 					quiet=True, generator=True)
   1328 		"""
   1329 		Logs.debug('build: clean called')
   1330 
   1331 		if hasattr(self, 'clean_files'):
   1332 			for n in self.clean_files:
   1333 				n.delete()
   1334 		elif self.bldnode != self.srcnode:
   1335 			# would lead to a disaster if top == out
   1336 			lst = []
   1337 			for env in self.all_envs.values():
   1338 				lst.extend(self.root.find_or_declare(f) for f in env[CFG_FILES])
   1339 			excluded_dirs = '.lock* *conf_check_*/** config.log %s/*' % CACHE_DIR
   1340 			# reverse: ant_globs returns nodes in preorder, but we
   1341 			# need postorder for directory deletion
   1342 			for n in reversed(self.bldnode.ant_glob('**/*', excl=excluded_dirs, quiet=True, dir=True)):
   1343 				if n in lst:
   1344 					continue
   1345 				try:
   1346 					if os.path.isdir(n.abspath()):
   1347 						os.rmdir(n.abspath())
   1348 					else:
   1349 						os.remove(n.abspath())
   1350 				except OSError:
   1351 					pass
   1352 				n.evict()
   1353 		self.root.children = {}
   1354 
   1355 		for v in SAVED_ATTRS:
   1356 			if v == 'root':
   1357 				continue
   1358 			setattr(self, v, {})
   1359 
   1360 class ListContext(BuildContext):
   1361 	'''lists the targets to execute'''
   1362 	cmd = 'list'
   1363 
   1364 	def execute(self):
   1365 		"""
   1366 		In addition to printing the name of each build target,
   1367 		a description column will include text for each task
   1368 		generator which has a "description" field set.
   1369 
   1370 		See :py:func:`waflib.Build.BuildContext.execute`.
   1371 		"""
   1372 		self.restore()
   1373 		if not self.all_envs:
   1374 			self.load_envs()
   1375 
   1376 		self.recurse([self.run_dir])
   1377 		self.pre_build()
   1378 
   1379 		# display the time elapsed in the progress bar
   1380 		self.timer = Utils.Timer()
   1381 
   1382 		for g in self.groups:
   1383 			for tg in g:
   1384 				try:
   1385 					f = tg.post
   1386 				except AttributeError:
   1387 					pass
   1388 				else:
   1389 					f()
   1390 
   1391 		try:
   1392 			# force the cache initialization
   1393 			self.get_tgen_by_name('')
   1394 		except Errors.WafError:
   1395 			pass
   1396 
   1397 		targets = sorted(self.task_gen_cache_names)
   1398 
   1399 		# figure out how much to left-justify, for largest target name
   1400 		line_just = max(len(t) for t in targets) if targets else 0
   1401 
   1402 		for target in targets:
   1403 			tgen = self.task_gen_cache_names[target]
   1404 
   1405 			# Support displaying the description for the target
   1406 			# if it was set on the tgen
   1407 			descript = getattr(tgen, 'description', '')
   1408 			if descript:
   1409 				target = target.ljust(line_just)
   1410 				descript = ': %s' % descript
   1411 
   1412 			Logs.pprint('GREEN', target, label=descript)
   1413 
   1414 class StepContext(BuildContext):
   1415 	'''executes tasks in a step-by-step fashion, for debugging'''
   1416 	cmd = 'step'
   1417 
   1418 	def __init__(self, **kw):
   1419 		super(StepContext, self).__init__(**kw)
   1420 		self.files = Options.options.files
   1421 
   1422 	def compile(self):
   1423 		"""
   1424 		Overrides :py:meth:`waflib.Build.BuildContext.compile` to perform a partial build
   1425 		on tasks matching the input/output pattern given (regular expression matching)::
   1426 
   1427 			$ waf step --files=foo.c,bar.c,in:truc.c,out:bar.o
   1428 			$ waf step --files=in:foo.cpp.1.o # link task only
   1429 
   1430 		"""
   1431 		if not self.files:
   1432 			Logs.warn('Add a pattern for the debug build, for example "waf step --files=main.c,app"')
   1433 			BuildContext.compile(self)
   1434 			return
   1435 
   1436 		targets = []
   1437 		if self.targets and self.targets != '*':
   1438 			targets = self.targets.split(',')
   1439 
   1440 		for g in self.groups:
   1441 			for tg in g:
   1442 				if targets and tg.name not in targets:
   1443 					continue
   1444 
   1445 				try:
   1446 					f = tg.post
   1447 				except AttributeError:
   1448 					pass
   1449 				else:
   1450 					f()
   1451 
   1452 			for pat in self.files.split(','):
   1453 				matcher = self.get_matcher(pat)
   1454 				for tg in g:
   1455 					if isinstance(tg, Task.Task):
   1456 						lst = [tg]
   1457 					else:
   1458 						lst = tg.tasks
   1459 					for tsk in lst:
   1460 						do_exec = False
   1461 						for node in tsk.inputs:
   1462 							if matcher(node, output=False):
   1463 								do_exec = True
   1464 								break
   1465 						for node in tsk.outputs:
   1466 							if matcher(node, output=True):
   1467 								do_exec = True
   1468 								break
   1469 						if do_exec:
   1470 							ret = tsk.run()
   1471 							Logs.info('%s -> exit %r', tsk, ret)
   1472 
   1473 	def get_matcher(self, pat):
   1474 		"""
   1475 		Converts a step pattern into a function
   1476 
   1477 		:param: pat: pattern of the form in:truc.c,out:bar.o
   1478 		:returns: Python function that uses Node objects as inputs and returns matches
   1479 		:rtype: function
   1480 		"""
   1481 		# this returns a function
   1482 		inn = True
   1483 		out = True
   1484 		if pat.startswith('in:'):
   1485 			out = False
   1486 			pat = pat.replace('in:', '')
   1487 		elif pat.startswith('out:'):
   1488 			inn = False
   1489 			pat = pat.replace('out:', '')
   1490 
   1491 		anode = self.root.find_node(pat)
   1492 		pattern = None
   1493 		if not anode:
   1494 			if not pat.startswith('^'):
   1495 				pat = '^.+?%s' % pat
   1496 			if not pat.endswith('$'):
   1497 				pat = '%s$' % pat
   1498 			pattern = re.compile(pat)
   1499 
   1500 		def match(node, output):
   1501 			if output and not out:
   1502 				return False
   1503 			if not output and not inn:
   1504 				return False
   1505 
   1506 			if anode:
   1507 				return anode == node
   1508 			else:
   1509 				return pattern.match(node.abspath())
   1510 		return match
   1511 
   1512 class EnvContext(BuildContext):
   1513 	"""Subclass EnvContext to create commands that require configuration data in 'env'"""
   1514 	fun = cmd = None
   1515 	def execute(self):
   1516 		"""
   1517 		See :py:func:`waflib.Build.BuildContext.execute`.
   1518 		"""
   1519 		self.restore()
   1520 		if not self.all_envs:
   1521 			self.load_envs()
   1522 		self.recurse([self.run_dir])
   1523