Update changelog
[jack2.git] / waflib / Context.py
blob7c6cd9608f7a3768386729a38c13fddbde0104ae
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, imp, sys
10 from waflib import Utils, Errors, Logs
11 import waflib.Node
13 # the following 3 constants are updated on each new release (do not touch)
14 HEXVERSION=0x2000c00
15 """Constant updated on new releases"""
17 WAFVERSION="2.0.12"
18 """Constant updated on new releases"""
20 WAFREVISION="54841218840ffa34fddf834680a5a17db69caa12"
21 """Git revision when the waf version is updated"""
23 ABI = 20
24 """Version of the build data cache file format (used in :py:const:`waflib.Context.DBFILE`)"""
26 DBFILE = '.wafpickle-%s-%d-%d' % (sys.platform, sys.hexversion, ABI)
27 """Name of the pickle file for storing the build data"""
29 APPNAME = 'APPNAME'
30 """Default application name (used by ``waf dist``)"""
32 VERSION = 'VERSION'
33 """Default application version (used by ``waf dist``)"""
35 TOP = 'top'
36 """The variable name for the top-level directory in wscript files"""
38 OUT = 'out'
39 """The variable name for the output directory in wscript files"""
41 WSCRIPT_FILE = 'wscript'
42 """Name of the waf script files"""
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"""
55 default_encoding = Utils.console_encoding()
56 """Encoding to use when reading outputs from other processes"""
58 g_module = None
59 """
60 Module representing the top-level wscript file (see :py:const:`waflib.Context.run_dir`)
61 """
63 STDOUT = 1
64 STDERR = -1
65 BOTH = 0
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 """
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`
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
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__
104 if name in ('ctx', 'Context'):
105 return
107 try:
108 cls.cmd
109 except AttributeError:
110 raise Errors.WafError('Missing command for the context class %r (cmd)' % name)
112 if not getattr(cls, 'fun', None):
113 cls.fun = cls.cmd
115 classes.insert(0, cls)
117 ctx = store_context('ctx', (object,), {})
118 """Base class for all :py:class:`waflib.Context.Context` classes"""
120 class Context(ctx):
122 Default context for waf commands, and base class for new command contexts.
124 Context objects are passed to top-level functions::
126 def foo(ctx):
127 print(ctx.__class__.__name__) # waflib.Context.Context
129 Subclasses must define the class attributes 'cmd' and 'fun':
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
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
140 errors = Errors
142 Shortcut to :py:mod:`waflib.Errors` provided for convenience
145 tools = {}
147 A module cache for wscript files; see :py:meth:`Context.Context.load`
150 def __init__(self, **kw):
151 try:
152 rd = kw['run_dir']
153 except KeyError:
154 rd = run_dir
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
161 self.root = self.node_class('', None)
162 self.cur_script = None
163 self.path = self.root.find_dir(rd)
165 self.stack_path = []
166 self.exec_dict = {'ctx':self, 'conf':self, 'bld':self, 'opt':self}
167 self.logger = None
169 def finalize(self):
171 Called to free resources such as logger files
173 try:
174 logger = self.logger
175 except AttributeError:
176 pass
177 else:
178 Logs.free_logger(logger)
179 delattr(self, 'logger')
181 def load(self, tool_list, *k, **kw):
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.
186 :param tool_list: list of Waf tool names to load
187 :type tool_list: list of string or space-separated string
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)
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)
199 def execute(self):
201 Here, it calls the function name in the top-level wscript file. Most subclasses
202 redefine this method to provide additional functionality.
204 self.recurse([os.path.dirname(g_module.root_path)])
206 def pre_recurse(self, node):
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``
212 :param node: script
213 :type node: :py:class:`waflib.Node.Node`
215 self.stack_path.append(self.cur_script)
217 self.cur_script = node
218 self.path = node.parent
220 def post_recurse(self, node):
222 Restores ``self.cur_script`` and ``self.path`` right after :py:meth:`waflib.Context.Context.recurse` terminates.
224 :param node: script
225 :type node: :py:class:`waflib.Node.Node`
227 self.cur_script = self.stack_path.pop()
228 if self.cur_script:
229 self.path = self.cur_script.parent
231 def recurse(self, dirs, name=None, mandatory=True, once=True, encoding=None):
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
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.
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
250 try:
251 cache = self.recurse_cache
252 except AttributeError:
253 cache = self.recurse_cache = {}
255 for d in Utils.to_list(dirs):
257 if not os.path.isabs(d):
258 # absolute paths only
259 d = os.path.join(self.path.abspath(), d)
261 WSCRIPT = os.path.join(d, WSCRIPT_FILE)
262 WSCRIPT_FUN = WSCRIPT + '_' + (name or self.fun)
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)
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)
307 def exec_command(self, cmd, **kw):
309 Runs an external process and returns the exit status::
311 def run(tsk):
312 ret = tsk.generator.bld.exec_command('touch foo.txt')
313 return ret
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.
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
328 subprocess = Utils.subprocess
329 kw['shell'] = isinstance(cmd, str)
330 self.log_command(cmd, kw)
332 if self.logger:
333 self.logger.info(cmd)
335 if 'stdout' not in kw:
336 kw['stdout'] = subprocess.PIPE
337 if 'stderr' not in kw:
338 kw['stderr'] = subprocess.PIPE
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])
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']
356 if 'cwd' in kw:
357 if not isinstance(kw['cwd'], str):
358 kw['cwd'] = kw['cwd'].abspath()
360 encoding = kw.pop('decode_as', default_encoding)
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)
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': ''})
382 return ret
384 def cmd_and_log(self, cmd, **kw):
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)::
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)
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
408 subprocess = Utils.subprocess
409 kw['shell'] = isinstance(cmd, str)
410 self.log_command(cmd, kw)
412 quiet = kw.pop('quiet', None)
413 to_ret = kw.pop('output', STDOUT)
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])
418 kw['stdout'] = kw['stderr'] = subprocess.PIPE
419 if quiet is None:
420 self.to_log(cmd)
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']
435 if 'cwd' in kw:
436 if not isinstance(kw['cwd'], str):
437 kw['cwd'] = kw['cwd'].abspath()
439 encoding = kw.pop('decode_as', default_encoding)
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)
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')
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)
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
463 if to_ret == BOTH:
464 return (out, err)
465 elif to_ret == STDERR:
466 return err
467 return out
469 def fatal(self, msg, ex=None):
471 Prints an error message in red and stops command execution; this is
472 usually used in the configuration section::
474 def configure(conf):
475 conf.fatal('a requirement is missing')
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`
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)
497 def to_log(self, msg):
499 Logs information to the logger (if present), or to stderr.
500 Empty messages are not printed::
502 def build(bld):
503 bld.to_log('starting the build')
505 Provide a logger on the context class or override this method if necessary.
507 :param msg: message
508 :type msg: string
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()
519 def msg(self, *k, **kw):
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::
525 def configure(conf):
526 self.in_msg = 1
527 conf.msg('Checking for library foo', 'ok')
528 # no output
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
537 try:
538 msg = kw['msg']
539 except KeyError:
540 msg = k[0]
542 self.start_msg(msg, **kw)
544 try:
545 result = kw['result']
546 except KeyError:
547 result = k[1]
549 color = kw.get('color')
550 if not isinstance(color, str):
551 color = result and 'GREEN' or 'YELLOW'
553 self.end_msg(result, color, **kw)
555 def start_msg(self, *k, **kw):
557 Prints the beginning of a 'Checking for xxx' message. See :py:meth:`waflib.Context.Context.msg`
559 if kw.get('quiet'):
560 return
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
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='')
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
587 result = kw.get('result') or k[0]
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)
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)
609 def load_special_tools(self, var, ban=[]):
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`
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
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)
643 cache_modules = {}
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`
649 def load_module(path, encoding=None):
651 Loads a wscript file as a python module. This method caches results in :py:attr:`waflib.Context.cache_modules`
653 :param path: file path
654 :type path: string
655 :return: Loaded Python module
656 :rtype: module
658 try:
659 return cache_modules[path]
660 except KeyError:
661 pass
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)
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)
676 cache_modules[path] = module
677 return module
679 def load_tool(tool, tooldir=None, ctx=None, with_sys_path=True):
681 Importx a Waf tool as a python module, and stores it in the dict :py:const:`waflib.Context.Context.tools`
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
690 if tool == 'java':
691 tool = 'javaw' # jython
692 else:
693 tool = tool.replace('++', 'xx')
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