third_party: Update waf to version 2.0.25
[Samba.git] / third_party / waf / waflib / Context.py
blobee8c5c9c5dfaa27d4a9101662f174fdce5ccd0c0
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # Thomas Nagy, 2010-2018 (ita)
5 """
6 Classes and functions enabling the command system
7 """
9 import os, re, sys
10 from waflib import Utils, Errors, Logs
11 import waflib.Node
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
20 # the following 3 constants are updated on each new release (do not touch)
21 HEXVERSION=0x2001900
22 """Constant updated on new releases"""
24 WAFVERSION="2.0.25"
25 """Constant updated on new releases"""
27 WAFREVISION="2db0b41b2805cd5db3b55476c06b23c1e46d319f"
28 """Git revision when the waf version is updated"""
30 WAFNAME="waf"
31 """Application name displayed on --help"""
33 ABI = 20
34 """Version of the build data cache file format (used in :py:const:`waflib.Context.DBFILE`)"""
36 DBFILE = '.wafpickle-%s-%d-%d' % (sys.platform, sys.hexversion, ABI)
37 """Name of the pickle file for storing the build data"""
39 APPNAME = 'APPNAME'
40 """Default application name (used by ``waf dist``)"""
42 VERSION = 'VERSION'
43 """Default application version (used by ``waf dist``)"""
45 TOP = 'top'
46 """The variable name for the top-level directory in wscript files"""
48 OUT = 'out'
49 """The variable name for the output directory in wscript files"""
51 WSCRIPT_FILE = 'wscript'
52 """Name of the waf script files"""
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"""
65 default_encoding = Utils.console_encoding()
66 """Encoding to use when reading outputs from other processes"""
68 g_module = None
69 """
70 Module representing the top-level wscript file (see :py:const:`waflib.Context.run_dir`)
71 """
73 STDOUT = 1
74 STDERR = -1
75 BOTH = 0
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 """
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`
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
104 class store_context(type):
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.
110 def __init__(cls, name, bases, dct):
111 super(store_context, cls).__init__(name, bases, dct)
112 name = cls.__name__
114 if name in ('ctx', 'Context'):
115 return
117 try:
118 cls.cmd
119 except AttributeError:
120 raise Errors.WafError('Missing command for the context class %r (cmd)' % name)
122 if not getattr(cls, 'fun', None):
123 cls.fun = cls.cmd
125 classes.insert(0, cls)
127 ctx = store_context('ctx', (object,), {})
128 """Base class for all :py:class:`waflib.Context.Context` classes"""
130 class Context(ctx):
132 Default context for waf commands, and base class for new command contexts.
134 Context objects are passed to top-level functions::
136 def foo(ctx):
137 print(ctx.__class__.__name__) # waflib.Context.Context
139 Subclasses must define the class attributes 'cmd' and 'fun':
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
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
150 errors = Errors
152 Shortcut to :py:mod:`waflib.Errors` provided for convenience
155 tools = {}
157 A module cache for wscript files; see :py:meth:`Context.Context.load`
160 def __init__(self, **kw):
161 try:
162 rd = kw['run_dir']
163 except KeyError:
164 rd = run_dir
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
171 self.root = self.node_class('', None)
172 self.cur_script = None
173 self.path = self.root.find_dir(rd)
175 self.stack_path = []
176 self.exec_dict = {'ctx':self, 'conf':self, 'bld':self, 'opt':self}
177 self.logger = None
179 def finalize(self):
181 Called to free resources such as logger files
183 try:
184 logger = self.logger
185 except AttributeError:
186 pass
187 else:
188 Logs.free_logger(logger)
189 delattr(self, 'logger')
191 def load(self, tool_list, *k, **kw):
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.
196 :param tool_list: list of Waf tool names to load
197 :type tool_list: list of string or space-separated string
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)
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)
209 def execute(self):
211 Here, it calls the function name in the top-level wscript file. Most subclasses
212 redefine this method to provide additional functionality.
214 self.recurse([os.path.dirname(g_module.root_path)])
216 def pre_recurse(self, node):
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``
222 :param node: script
223 :type node: :py:class:`waflib.Node.Node`
225 self.stack_path.append(self.cur_script)
227 self.cur_script = node
228 self.path = node.parent
230 def post_recurse(self, node):
232 Restores ``self.cur_script`` and ``self.path`` right after :py:meth:`waflib.Context.Context.recurse` terminates.
234 :param node: script
235 :type node: :py:class:`waflib.Node.Node`
237 self.cur_script = self.stack_path.pop()
238 if self.cur_script:
239 self.path = self.cur_script.parent
241 def recurse(self, dirs, name=None, mandatory=True, once=True, encoding=None):
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
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.
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
260 try:
261 cache = self.recurse_cache
262 except AttributeError:
263 cache = self.recurse_cache = {}
265 for d in Utils.to_list(dirs):
267 if not os.path.isabs(d):
268 # absolute paths only
269 d = os.path.join(self.path.abspath(), d)
271 WSCRIPT = os.path.join(d, WSCRIPT_FILE)
272 WSCRIPT_FUN = WSCRIPT + '_' + (name or self.fun)
274 node = self.root.find_node(WSCRIPT_FUN)
275 if node and (not once or node not in cache):
276 cache[node] = True
277 self.pre_recurse(node)
278 try:
279 function_code = node.read('r', encoding)
280 exec(compile(function_code, node.abspath(), 'exec'), self.exec_dict)
281 finally:
282 self.post_recurse(node)
283 elif not node:
284 node = self.root.find_node(WSCRIPT)
285 tup = (node, name or self.fun)
286 if node and (not once or tup not in cache):
287 cache[tup] = True
288 self.pre_recurse(node)
289 try:
290 wscript_module = load_module(node.abspath(), encoding=encoding)
291 user_function = getattr(wscript_module, (name or self.fun), None)
292 if not user_function:
293 if not mandatory:
294 continue
295 raise Errors.WafError('No function %r defined in %s' % (name or self.fun, node.abspath()))
296 user_function(self)
297 finally:
298 self.post_recurse(node)
299 elif not node:
300 if not mandatory:
301 continue
302 try:
303 os.listdir(d)
304 except OSError:
305 raise Errors.WafError('Cannot read the folder %r' % d)
306 raise Errors.WafError('No wscript file in directory %s' % d)
308 def log_command(self, cmd, kw):
309 if Logs.verbose:
310 fmt = os.environ.get('WAF_CMD_FORMAT')
311 if fmt == 'string':
312 if not isinstance(cmd, str):
313 cmd = Utils.shell_escape(cmd)
314 Logs.debug('runner: %r', cmd)
315 Logs.debug('runner_env: kw=%s', kw)
317 def exec_command(self, cmd, **kw):
319 Runs an external process and returns the exit status::
321 def run(tsk):
322 ret = tsk.generator.bld.exec_command('touch foo.txt')
323 return ret
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.
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
338 subprocess = Utils.subprocess
339 kw['shell'] = isinstance(cmd, str)
340 self.log_command(cmd, kw)
342 if self.logger:
343 self.logger.info(cmd)
345 if 'stdout' not in kw:
346 kw['stdout'] = subprocess.PIPE
347 if 'stderr' not in kw:
348 kw['stderr'] = subprocess.PIPE
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])
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']
366 if 'cwd' in kw:
367 if not isinstance(kw['cwd'], str):
368 kw['cwd'] = kw['cwd'].abspath()
370 encoding = kw.pop('decode_as', default_encoding)
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)
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': ''})
392 return ret
394 def cmd_and_log(self, cmd, **kw):
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)::
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)
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
418 subprocess = Utils.subprocess
419 kw['shell'] = isinstance(cmd, str)
420 self.log_command(cmd, kw)
422 quiet = kw.pop('quiet', None)
423 to_ret = kw.pop('output', STDOUT)
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])
428 kw['stdout'] = kw['stderr'] = subprocess.PIPE
429 if quiet is None:
430 self.to_log(cmd)
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']
445 if 'cwd' in kw:
446 if not isinstance(kw['cwd'], str):
447 kw['cwd'] = kw['cwd'].abspath()
449 encoding = kw.pop('decode_as', default_encoding)
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)
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')
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)
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
473 if to_ret == BOTH:
474 return (out, err)
475 elif to_ret == STDERR:
476 return err
477 return out
479 def fatal(self, msg, ex=None):
481 Prints an error message in red and stops command execution; this is
482 usually used in the configuration section::
484 def configure(conf):
485 conf.fatal('a requirement is missing')
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`
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)
507 def to_log(self, msg):
509 Logs information to the logger (if present), or to stderr.
510 Empty messages are not printed::
512 def build(bld):
513 bld.to_log('starting the build')
515 Provide a logger on the context class or override this method if necessary.
517 :param msg: message
518 :type msg: string
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()
529 def msg(self, *k, **kw):
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::
535 def configure(conf):
536 self.in_msg = 1
537 conf.msg('Checking for library foo', 'ok')
538 # no output
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
547 try:
548 msg = kw['msg']
549 except KeyError:
550 msg = k[0]
552 self.start_msg(msg, **kw)
554 try:
555 result = kw['result']
556 except KeyError:
557 result = k[1]
559 color = kw.get('color')
560 if not isinstance(color, str):
561 color = result and 'GREEN' or 'YELLOW'
563 self.end_msg(result, color, **kw)
565 def start_msg(self, *k, **kw):
567 Prints the beginning of a 'Checking for xxx' message. See :py:meth:`waflib.Context.Context.msg`
569 if kw.get('quiet'):
570 return
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
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='')
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
597 result = kw.get('result') or k[0]
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)
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)
619 def load_special_tools(self, var, ban=[]):
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`
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
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)
653 cache_modules = {}
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`
659 def load_module(path, encoding=None):
661 Loads a wscript file as a python module. This method caches results in :py:attr:`waflib.Context.cache_modules`
663 :param path: file path
664 :type path: string
665 :return: Loaded Python module
666 :rtype: module
668 try:
669 return cache_modules[path]
670 except KeyError:
671 pass
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)
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)
686 cache_modules[path] = module
687 return module
689 def load_tool(tool, tooldir=None, ctx=None, with_sys_path=True):
691 Imports a Waf tool as a python module, and stores it in the dict :py:const:`waflib.Context.Context.tools`
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
700 if tool == 'java':
701 tool = 'javaw' # jython
702 else:
703 tool = tool.replace('++', 'xx')
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