waf

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

Context.py (21285B)


      1 #!/usr/bin/env python
      2 # encoding: utf-8
      3 # Thomas Nagy, 2010-2018 (ita)
      4 
      5 """
      6 Classes and functions enabling the command system
      7 """
      8 
      9 import os, re, sys
     10 from waflib import Utils, Errors, Logs
     11 import waflib.Node
     12 
     13 if sys.hexversion > 0x3040000:
     14 	import types
     15 	class imp(object):
     16 		new_module = lambda x: types.ModuleType(x)
     17 else:
     18 	import imp
     19 
     20 # the following 3 constants are updated on each new release (do not touch)
     21 HEXVERSION=0x2001900
     22 """Constant updated on new releases"""
     23 
     24 WAFVERSION="2.0.25"
     25 """Constant updated on new releases"""
     26 
     27 WAFREVISION="2db0b41b2805cd5db3b55476c06b23c1e46d319f"
     28 """Git revision when the waf version is updated"""
     29 
     30 WAFNAME="waf"
     31 """Application name displayed on --help"""
     32 
     33 ABI = 20
     34 """Version of the build data cache file format (used in :py:const:`waflib.Context.DBFILE`)"""
     35 
     36 DBFILE = '.wafpickle-%s-%d-%d' % (sys.platform, sys.hexversion, ABI)
     37 """Name of the pickle file for storing the build data"""
     38 
     39 APPNAME = 'APPNAME'
     40 """Default application name (used by ``waf dist``)"""
     41 
     42 VERSION = 'VERSION'
     43 """Default application version (used by ``waf dist``)"""
     44 
     45 TOP  = 'top'
     46 """The variable name for the top-level directory in wscript files"""
     47 
     48 OUT  = 'out'
     49 """The variable name for the output directory in wscript files"""
     50 
     51 WSCRIPT_FILE = 'wscript'
     52 """Name of the waf script files"""
     53 
     54 launch_dir = ''
     55 """Directory from which waf has been called"""
     56 run_dir = ''
     57 """Location of the wscript file to use as the entry point"""
     58 top_dir = ''
     59 """Location of the project directory (top), if the project was configured"""
     60 out_dir = ''
     61 """Location of the build directory (out), if the project was configured"""
     62 waf_dir = ''
     63 """Directory containing the waf modules"""
     64 
     65 default_encoding = Utils.console_encoding()
     66 """Encoding to use when reading outputs from other processes"""
     67 
     68 g_module = None
     69 """
     70 Module representing the top-level wscript file (see :py:const:`waflib.Context.run_dir`)
     71 """
     72 
     73 STDOUT = 1
     74 STDERR = -1
     75 BOTH   = 0
     76 
     77 classes = []
     78 """
     79 List of :py:class:`waflib.Context.Context` subclasses that can be used as waf commands. The classes
     80 are added automatically by a metaclass.
     81 """
     82 
     83 def create_context(cmd_name, *k, **kw):
     84 	"""
     85 	Returns a new :py:class:`waflib.Context.Context` instance corresponding to the given command.
     86 	Used in particular by :py:func:`waflib.Scripting.run_command`
     87 
     88 	:param cmd_name: command name
     89 	:type cmd_name: string
     90 	:param k: arguments to give to the context class initializer
     91 	:type k: list
     92 	:param k: keyword arguments to give to the context class initializer
     93 	:type k: dict
     94 	:return: Context object
     95 	:rtype: :py:class:`waflib.Context.Context`
     96 	"""
     97 	for x in classes:
     98 		if x.cmd == cmd_name:
     99 			return x(*k, **kw)
    100 	ctx = Context(*k, **kw)
    101 	ctx.fun = cmd_name
    102 	return ctx
    103 
    104 class store_context(type):
    105 	"""
    106 	Metaclass that registers command classes into the list :py:const:`waflib.Context.classes`
    107 	Context classes must provide an attribute 'cmd' representing the command name, and a function
    108 	attribute 'fun' representing the function name that the command uses.
    109 	"""
    110 	def __init__(cls, name, bases, dct):
    111 		super(store_context, cls).__init__(name, bases, dct)
    112 		name = cls.__name__
    113 
    114 		if name in ('ctx', 'Context'):
    115 			return
    116 
    117 		try:
    118 			cls.cmd
    119 		except AttributeError:
    120 			raise Errors.WafError('Missing command for the context class %r (cmd)' % name)
    121 
    122 		if not getattr(cls, 'fun', None):
    123 			cls.fun = cls.cmd
    124 
    125 		classes.insert(0, cls)
    126 
    127 ctx = store_context('ctx', (object,), {})
    128 """Base class for all :py:class:`waflib.Context.Context` classes"""
    129 
    130 class Context(ctx):
    131 	"""
    132 	Default context for waf commands, and base class for new command contexts.
    133 
    134 	Context objects are passed to top-level functions::
    135 
    136 		def foo(ctx):
    137 			print(ctx.__class__.__name__) # waflib.Context.Context
    138 
    139 	Subclasses must define the class attributes 'cmd' and 'fun':
    140 
    141 	:param cmd: command to execute as in ``waf cmd``
    142 	:type cmd: string
    143 	:param fun: function name to execute when the command is called
    144 	:type fun: string
    145 
    146 	.. inheritance-diagram:: waflib.Context.Context waflib.Build.BuildContext waflib.Build.InstallContext waflib.Build.UninstallContext waflib.Build.StepContext waflib.Build.ListContext waflib.Configure.ConfigurationContext waflib.Scripting.Dist waflib.Scripting.DistCheck waflib.Build.CleanContext
    147 	   :top-classes: waflib.Context.Context
    148 	"""
    149 
    150 	errors = Errors
    151 	"""
    152 	Shortcut to :py:mod:`waflib.Errors` provided for convenience
    153 	"""
    154 
    155 	tools = {}
    156 	"""
    157 	A module cache for wscript files; see :py:meth:`Context.Context.load`
    158 	"""
    159 
    160 	def __init__(self, **kw):
    161 		try:
    162 			rd = kw['run_dir']
    163 		except KeyError:
    164 			rd = run_dir
    165 
    166 		# binds the context to the nodes in use to avoid a context singleton
    167 		self.node_class = type('Nod3', (waflib.Node.Node,), {})
    168 		self.node_class.__module__ = 'waflib.Node'
    169 		self.node_class.ctx = self
    170 
    171 		self.root = self.node_class('', None)
    172 		self.cur_script = None
    173 		self.path = self.root.find_dir(rd)
    174 
    175 		self.stack_path = []
    176 		self.exec_dict = {'ctx':self, 'conf':self, 'bld':self, 'opt':self}
    177 		self.logger = None
    178 
    179 	def finalize(self):
    180 		"""
    181 		Called to free resources such as logger files
    182 		"""
    183 		try:
    184 			logger = self.logger
    185 		except AttributeError:
    186 			pass
    187 		else:
    188 			Logs.free_logger(logger)
    189 			delattr(self, 'logger')
    190 
    191 	def load(self, tool_list, *k, **kw):
    192 		"""
    193 		Loads a Waf tool as a module, and try calling the function named :py:const:`waflib.Context.Context.fun`
    194 		from it.  A ``tooldir`` argument may be provided as a list of module paths.
    195 
    196 		:param tool_list: list of Waf tool names to load
    197 		:type tool_list: list of string or space-separated string
    198 		"""
    199 		tools = Utils.to_list(tool_list)
    200 		path = Utils.to_list(kw.get('tooldir', ''))
    201 		with_sys_path = kw.get('with_sys_path', True)
    202 
    203 		for t in tools:
    204 			module = load_tool(t, path, with_sys_path=with_sys_path)
    205 			fun = getattr(module, kw.get('name', self.fun), None)
    206 			if fun:
    207 				fun(self)
    208 
    209 	def execute(self):
    210 		"""
    211 		Here, it calls the function name in the top-level wscript file. Most subclasses
    212 		redefine this method to provide additional functionality.
    213 		"""
    214 		self.recurse([os.path.dirname(g_module.root_path)])
    215 
    216 	def pre_recurse(self, node):
    217 		"""
    218 		Method executed immediately before a folder is read by :py:meth:`waflib.Context.Context.recurse`.
    219 		The current script is bound as a Node object on ``self.cur_script``, and the current path
    220 		is bound to ``self.path``
    221 
    222 		:param node: script
    223 		:type node: :py:class:`waflib.Node.Node`
    224 		"""
    225 		self.stack_path.append(self.cur_script)
    226 
    227 		self.cur_script = node
    228 		self.path = node.parent
    229 
    230 	def post_recurse(self, node):
    231 		"""
    232 		Restores ``self.cur_script`` and ``self.path`` right after :py:meth:`waflib.Context.Context.recurse` terminates.
    233 
    234 		:param node: script
    235 		:type node: :py:class:`waflib.Node.Node`
    236 		"""
    237 		self.cur_script = self.stack_path.pop()
    238 		if self.cur_script:
    239 			self.path = self.cur_script.parent
    240 
    241 	def recurse(self, dirs, name=None, mandatory=True, once=True, encoding=None, wscript=WSCRIPT_FILE):
    242 		"""
    243 		Runs user-provided functions from the supplied list of directories.
    244 		The directories can be either absolute, or relative to the directory
    245 		of the wscript file
    246 
    247 		The methods :py:meth:`waflib.Context.Context.pre_recurse` and
    248 		:py:meth:`waflib.Context.Context.post_recurse` are called immediately before
    249 		and after a script has been executed.
    250 
    251 		:param dirs: List of directories to visit
    252 		:type dirs: list of string or space-separated string
    253 		:param name: Name of function to invoke from the wscript
    254 		:type  name: string
    255 		:param mandatory: whether sub wscript files are required to exist
    256 		:type  mandatory: bool
    257 		:param once: read the script file once for a particular context
    258 		:type once: bool
    259 		:param wscript: use file named this instead of `wscript`.
    260 		:type wscript: string
    261 		"""
    262 		try:
    263 			cache = self.recurse_cache
    264 		except AttributeError:
    265 			cache = self.recurse_cache = {}
    266 
    267 		for d in Utils.to_list(dirs):
    268 
    269 			if not os.path.isabs(d):
    270 				# absolute paths only
    271 				d = os.path.join(self.path.abspath(), d)
    272 
    273 			WSCRIPT     = os.path.join(d, wscript)
    274 			WSCRIPT_FUN = WSCRIPT + '_' + (name or self.fun)
    275 
    276 			node = self.root.find_node(WSCRIPT_FUN)
    277 			if node and (not once or node not in cache):
    278 				cache[node] = True
    279 				self.pre_recurse(node)
    280 				try:
    281 					function_code = node.read('r', encoding)
    282 					exec(compile(function_code, node.abspath(), 'exec'), self.exec_dict)
    283 				finally:
    284 					self.post_recurse(node)
    285 			elif not node:
    286 				node = self.root.find_node(WSCRIPT)
    287 				tup = (node, name or self.fun)
    288 				if node and (not once or tup not in cache):
    289 					cache[tup] = True
    290 					self.pre_recurse(node)
    291 					try:
    292 						wscript_module = load_module(node.abspath(), encoding=encoding)
    293 						user_function = getattr(wscript_module, (name or self.fun), None)
    294 						if not user_function:
    295 							if not mandatory:
    296 								continue
    297 							raise Errors.WafError('No function %r defined in %s' % (name or self.fun, node.abspath()))
    298 						user_function(self)
    299 					finally:
    300 						self.post_recurse(node)
    301 				elif not node:
    302 					if not mandatory:
    303 						continue
    304 					try:
    305 						os.listdir(d)
    306 					except OSError:
    307 						raise Errors.WafError('Cannot read the folder %r' % d)
    308 					raise Errors.WafError('No wscript file in directory %s' % d)
    309 
    310 	def log_command(self, cmd, kw):
    311 		if Logs.verbose:
    312 			if not isinstance(cmd, str):
    313 				cmd = Utils.shell_escape(cmd)
    314 			Logs.debug('runner: %s', cmd)
    315 			Logs.debug('runner_env: kw=%s', kw)
    316 
    317 	def exec_command(self, cmd, **kw):
    318 		"""
    319 		Runs an external process and returns the exit status::
    320 
    321 			def run(tsk):
    322 				ret = tsk.generator.bld.exec_command('touch foo.txt')
    323 				return ret
    324 
    325 		If the context has the attribute 'log', then captures and logs the process stderr/stdout.
    326 		Unlike :py:meth:`waflib.Context.Context.cmd_and_log`, this method does not return the
    327 		stdout/stderr values captured.
    328 
    329 		:param cmd: command argument for subprocess.Popen
    330 		:type cmd: string or list
    331 		:param kw: keyword arguments for subprocess.Popen. The parameters input/timeout will be passed to wait/communicate.
    332 		:type kw: dict
    333 		:returns: process exit status
    334 		:rtype: integer
    335 		:raises: :py:class:`waflib.Errors.WafError` if an invalid executable is specified for a non-shell process
    336 		:raises: :py:class:`waflib.Errors.WafError` in case of execution failure
    337 		"""
    338 		subprocess = Utils.subprocess
    339 		kw['shell'] = isinstance(cmd, str)
    340 		self.log_command(cmd, kw)
    341 
    342 		if self.logger:
    343 			self.logger.info(cmd)
    344 
    345 		if 'stdout' not in kw:
    346 			kw['stdout'] = subprocess.PIPE
    347 		if 'stderr' not in kw:
    348 			kw['stderr'] = subprocess.PIPE
    349 
    350 		if Logs.verbose and not kw['shell'] and not Utils.check_exe(cmd[0]):
    351 			raise Errors.WafError('Program %s not found!' % cmd[0])
    352 
    353 		cargs = {}
    354 		if 'timeout' in kw:
    355 			if sys.hexversion >= 0x3030000:
    356 				cargs['timeout'] = kw['timeout']
    357 				if not 'start_new_session' in kw:
    358 					kw['start_new_session'] = True
    359 			del kw['timeout']
    360 		if 'input' in kw:
    361 			if kw['input']:
    362 				cargs['input'] = kw['input']
    363 				kw['stdin'] = subprocess.PIPE
    364 			del kw['input']
    365 
    366 		if 'cwd' in kw:
    367 			if not isinstance(kw['cwd'], str):
    368 				kw['cwd'] = kw['cwd'].abspath()
    369 
    370 		encoding = kw.pop('decode_as', default_encoding)
    371 
    372 		try:
    373 			ret, out, err = Utils.run_process(cmd, kw, cargs)
    374 		except Exception as e:
    375 			raise Errors.WafError('Execution failure: %s' % str(e), ex=e)
    376 
    377 		if out:
    378 			if not isinstance(out, str):
    379 				out = out.decode(encoding, errors='replace')
    380 			if self.logger:
    381 				self.logger.debug('out: %s', out)
    382 			else:
    383 				Logs.info(out, extra={'stream':sys.stdout, 'c1': ''})
    384 		if err:
    385 			if not isinstance(err, str):
    386 				err = err.decode(encoding, errors='replace')
    387 			if self.logger:
    388 				self.logger.error('err: %s' % err)
    389 			else:
    390 				Logs.info(err, extra={'stream':sys.stderr, 'c1': ''})
    391 
    392 		return ret
    393 
    394 	def cmd_and_log(self, cmd, **kw):
    395 		"""
    396 		Executes a process and returns stdout/stderr if the execution is successful.
    397 		An exception is thrown when the exit status is non-0. In that case, both stderr and stdout
    398 		will be bound to the WafError object (configuration tests)::
    399 
    400 			def configure(conf):
    401 				out = conf.cmd_and_log(['echo', 'hello'], output=waflib.Context.STDOUT, quiet=waflib.Context.BOTH)
    402 				(out, err) = conf.cmd_and_log(['echo', 'hello'], output=waflib.Context.BOTH)
    403 				(out, err) = conf.cmd_and_log(cmd, input='\\n'.encode(), output=waflib.Context.STDOUT)
    404 				try:
    405 					conf.cmd_and_log(['which', 'someapp'], output=waflib.Context.BOTH)
    406 				except Errors.WafError as e:
    407 					print(e.stdout, e.stderr)
    408 
    409 		:param cmd: args for subprocess.Popen
    410 		:type cmd: list or string
    411 		:param kw: keyword arguments for subprocess.Popen. The parameters input/timeout will be passed to wait/communicate.
    412 		:type kw: dict
    413 		:returns: a tuple containing the contents of stdout and stderr
    414 		:rtype: string
    415 		:raises: :py:class:`waflib.Errors.WafError` if an invalid executable is specified for a non-shell process
    416 		:raises: :py:class:`waflib.Errors.WafError` in case of execution failure; stdout/stderr/returncode are bound to the exception object
    417 		"""
    418 		subprocess = Utils.subprocess
    419 		kw['shell'] = isinstance(cmd, str)
    420 		self.log_command(cmd, kw)
    421 
    422 		quiet = kw.pop('quiet', None)
    423 		to_ret = kw.pop('output', STDOUT)
    424 
    425 		if Logs.verbose and not kw['shell'] and not Utils.check_exe(cmd[0]):
    426 			raise Errors.WafError('Program %r not found!' % cmd[0])
    427 
    428 		kw['stdout'] = kw['stderr'] = subprocess.PIPE
    429 		if quiet is None:
    430 			self.to_log(cmd)
    431 
    432 		cargs = {}
    433 		if 'timeout' in kw:
    434 			if sys.hexversion >= 0x3030000:
    435 				cargs['timeout'] = kw['timeout']
    436 				if not 'start_new_session' in kw:
    437 					kw['start_new_session'] = True
    438 			del kw['timeout']
    439 		if 'input' in kw:
    440 			if kw['input']:
    441 				cargs['input'] = kw['input']
    442 				kw['stdin'] = subprocess.PIPE
    443 			del kw['input']
    444 
    445 		if 'cwd' in kw:
    446 			if not isinstance(kw['cwd'], str):
    447 				kw['cwd'] = kw['cwd'].abspath()
    448 
    449 		encoding = kw.pop('decode_as', default_encoding)
    450 
    451 		try:
    452 			ret, out, err = Utils.run_process(cmd, kw, cargs)
    453 		except Exception as e:
    454 			raise Errors.WafError('Execution failure: %s' % str(e), ex=e)
    455 
    456 		if not isinstance(out, str):
    457 			out = out.decode(encoding, errors='replace')
    458 		if not isinstance(err, str):
    459 			err = err.decode(encoding, errors='replace')
    460 
    461 		if out and quiet != STDOUT and quiet != BOTH:
    462 			self.to_log('out: %s' % out)
    463 		if err and quiet != STDERR and quiet != BOTH:
    464 			self.to_log('err: %s' % err)
    465 
    466 		if ret:
    467 			e = Errors.WafError('Command %r returned %r' % (cmd, ret))
    468 			e.returncode = ret
    469 			e.stderr = err
    470 			e.stdout = out
    471 			raise e
    472 
    473 		if to_ret == BOTH:
    474 			return (out, err)
    475 		elif to_ret == STDERR:
    476 			return err
    477 		return out
    478 
    479 	def fatal(self, msg, ex=None):
    480 		"""
    481 		Prints an error message in red and stops command execution; this is
    482 		usually used in the configuration section::
    483 
    484 			def configure(conf):
    485 				conf.fatal('a requirement is missing')
    486 
    487 		:param msg: message to display
    488 		:type msg: string
    489 		:param ex: optional exception object
    490 		:type ex: exception
    491 		:raises: :py:class:`waflib.Errors.ConfigurationError`
    492 		"""
    493 		if self.logger:
    494 			self.logger.info('from %s: %s' % (self.path.abspath(), msg))
    495 		try:
    496 			logfile = self.logger.handlers[0].baseFilename
    497 		except AttributeError:
    498 			pass
    499 		else:
    500 			if os.environ.get('WAF_PRINT_FAILURE_LOG'):
    501 				# see #1930
    502 				msg = 'Log from (%s):\n%s\n' % (logfile, Utils.readf(logfile))
    503 			else:
    504 				msg = '%s\n(complete log in %s)' % (msg, logfile)
    505 		raise self.errors.ConfigurationError(msg, ex=ex)
    506 
    507 	def to_log(self, msg):
    508 		"""
    509 		Logs information to the logger (if present), or to stderr.
    510 		Empty messages are not printed::
    511 
    512 			def build(bld):
    513 				bld.to_log('starting the build')
    514 
    515 		Provide a logger on the context class or override this method if necessary.
    516 
    517 		:param msg: message
    518 		:type msg: string
    519 		"""
    520 		if not msg:
    521 			return
    522 		if self.logger:
    523 			self.logger.info(msg)
    524 		else:
    525 			sys.stderr.write(str(msg))
    526 			sys.stderr.flush()
    527 
    528 
    529 	def msg(self, *k, **kw):
    530 		"""
    531 		Prints a configuration message of the form ``msg: result``.
    532 		The second part of the message will be in colors. The output
    533 		can be disabled easily by setting ``in_msg`` to a positive value::
    534 
    535 			def configure(conf):
    536 				self.in_msg = 1
    537 				conf.msg('Checking for library foo', 'ok')
    538 				# no output
    539 
    540 		:param msg: message to display to the user
    541 		:type msg: string
    542 		:param result: result to display
    543 		:type result: string or boolean
    544 		:param color: color to use, see :py:const:`waflib.Logs.colors_lst`
    545 		:type color: string
    546 		"""
    547 		try:
    548 			msg = kw['msg']
    549 		except KeyError:
    550 			msg = k[0]
    551 
    552 		self.start_msg(msg, **kw)
    553 
    554 		try:
    555 			result = kw['result']
    556 		except KeyError:
    557 			result = k[1]
    558 
    559 		color = kw.get('color')
    560 		if not isinstance(color, str):
    561 			color = result and 'GREEN' or 'YELLOW'
    562 
    563 		self.end_msg(result, color, **kw)
    564 
    565 	def start_msg(self, *k, **kw):
    566 		"""
    567 		Prints the beginning of a 'Checking for xxx' message. See :py:meth:`waflib.Context.Context.msg`
    568 		"""
    569 		if kw.get('quiet'):
    570 			return
    571 
    572 		msg = kw.get('msg') or k[0]
    573 		try:
    574 			if self.in_msg:
    575 				self.in_msg += 1
    576 				return
    577 		except AttributeError:
    578 			self.in_msg = 0
    579 		self.in_msg += 1
    580 
    581 		try:
    582 			self.line_just = max(self.line_just, len(msg))
    583 		except AttributeError:
    584 			self.line_just = max(40, len(msg))
    585 		for x in (self.line_just * '-', msg):
    586 			self.to_log(x)
    587 		Logs.pprint('NORMAL', "%s :" % msg.ljust(self.line_just), sep='')
    588 
    589 	def end_msg(self, *k, **kw):
    590 		"""Prints the end of a 'Checking for' message. See :py:meth:`waflib.Context.Context.msg`"""
    591 		if kw.get('quiet'):
    592 			return
    593 		self.in_msg -= 1
    594 		if self.in_msg:
    595 			return
    596 
    597 		result = kw.get('result') or k[0]
    598 
    599 		defcolor = 'GREEN'
    600 		if result is True:
    601 			msg = 'ok'
    602 		elif not result:
    603 			msg = 'not found'
    604 			defcolor = 'YELLOW'
    605 		else:
    606 			msg = str(result)
    607 
    608 		self.to_log(msg)
    609 		try:
    610 			color = kw['color']
    611 		except KeyError:
    612 			if len(k) > 1 and k[1] in Logs.colors_lst:
    613 				# compatibility waf 1.7
    614 				color = k[1]
    615 			else:
    616 				color = defcolor
    617 		Logs.pprint(color, msg)
    618 
    619 	def load_special_tools(self, var, ban=[]):
    620 		"""
    621 		Loads third-party extensions modules for certain programming languages
    622 		by trying to list certain files in the extras/ directory. This method
    623 		is typically called once for a programming language group, see for
    624 		example :py:mod:`waflib.Tools.compiler_c`
    625 
    626 		:param var: glob expression, for example 'cxx\\_\\*.py'
    627 		:type var: string
    628 		:param ban: list of exact file names to exclude
    629 		:type ban: list of string
    630 		"""
    631 		if os.path.isdir(waf_dir):
    632 			lst = self.root.find_node(waf_dir).find_node('waflib/extras').ant_glob(var)
    633 			for x in lst:
    634 				if not x.name in ban:
    635 					load_tool(x.name.replace('.py', ''))
    636 		else:
    637 			from zipfile import PyZipFile
    638 			waflibs = PyZipFile(waf_dir)
    639 			lst = waflibs.namelist()
    640 			for x in lst:
    641 				if not re.match('waflib/extras/%s' % var.replace('*', '.*'), var):
    642 					continue
    643 				f = os.path.basename(x)
    644 				doban = False
    645 				for b in ban:
    646 					r = b.replace('*', '.*')
    647 					if re.match(r, f):
    648 						doban = True
    649 				if not doban:
    650 					f = f.replace('.py', '')
    651 					load_tool(f)
    652 
    653 cache_modules = {}
    654 """
    655 Dictionary holding already loaded modules (wscript), indexed by their absolute path.
    656 The modules are added automatically by :py:func:`waflib.Context.load_module`
    657 """
    658 
    659 def load_module(path, encoding=None):
    660 	"""
    661 	Loads a wscript file as a python module. This method caches results in :py:attr:`waflib.Context.cache_modules`
    662 
    663 	:param path: file path
    664 	:type path: string
    665 	:return: Loaded Python module
    666 	:rtype: module
    667 	"""
    668 	try:
    669 		return cache_modules[path]
    670 	except KeyError:
    671 		pass
    672 
    673 	module = imp.new_module(WSCRIPT_FILE)
    674 	try:
    675 		code = Utils.readf(path, m='r', encoding=encoding)
    676 	except EnvironmentError:
    677 		raise Errors.WafError('Could not read the file %r' % path)
    678 
    679 	module_dir = os.path.dirname(path)
    680 	sys.path.insert(0, module_dir)
    681 	try:
    682 		exec(compile(code, path, 'exec'), module.__dict__)
    683 	finally:
    684 		sys.path.remove(module_dir)
    685 
    686 	cache_modules[path] = module
    687 	return module
    688 
    689 def load_tool(tool, tooldir=None, ctx=None, with_sys_path=True):
    690 	"""
    691 	Imports a Waf tool as a python module, and stores it in the dict :py:const:`waflib.Context.Context.tools`
    692 
    693 	:type  tool: string
    694 	:param tool: Name of the tool
    695 	:type  tooldir: list
    696 	:param tooldir: List of directories to search for the tool module
    697 	:type  with_sys_path: boolean
    698 	:param with_sys_path: whether or not to search the regular sys.path, besides waf_dir and potentially given tooldirs
    699 	"""
    700 	if tool == 'java':
    701 		tool = 'javaw' # jython
    702 	else:
    703 		tool = tool.replace('++', 'xx')
    704 
    705 	if not with_sys_path:
    706 		back_path = sys.path
    707 		sys.path = []
    708 	try:
    709 		if tooldir:
    710 			assert isinstance(tooldir, list)
    711 			sys.path = tooldir + sys.path
    712 			try:
    713 				__import__(tool)
    714 			except ImportError as e:
    715 				e.waf_sys_path = list(sys.path)
    716 				raise
    717 			finally:
    718 				for d in tooldir:
    719 					sys.path.remove(d)
    720 			ret = sys.modules[tool]
    721 			Context.tools[tool] = ret
    722 			return ret
    723 		else:
    724 			if not with_sys_path:
    725 				sys.path.insert(0, waf_dir)
    726 			try:
    727 				for x in ('waflib.Tools.%s', 'waflib.extras.%s', 'waflib.%s', '%s'):
    728 					try:
    729 						__import__(x % tool)
    730 						break
    731 					except ImportError:
    732 						x = None
    733 				else: # raise an exception
    734 					__import__(tool)
    735 			except ImportError as e:
    736 				e.waf_sys_path = list(sys.path)
    737 				raise
    738 			finally:
    739 				if not with_sys_path:
    740 					sys.path.remove(waf_dir)
    741 			ret = sys.modules[x % tool]
    742 			Context.tools[tool] = ret
    743 			return ret
    744 	finally:
    745 		if not with_sys_path:
    746 			sys.path += back_path