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