3 # Thomas Nagy, 2010-2018 (ita)
6 Classes and functions enabling the command system
10 from waflib
import Utils
, Errors
, Logs
13 if sys
.hexversion
> 0x3040000:
16 new_module
= lambda x
: types
.ModuleType(x
)
20 # the following 3 constants are updated on each new release (do not touch)
22 """Constant updated on new releases"""
25 """Constant updated on new releases"""
27 WAFREVISION
="0fb985ce1932c6f3e7533f435e4ee209d673776e"
28 """Git revision when the waf version is updated"""
31 """Application name displayed on --help"""
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"""
40 """Default application name (used by ``waf dist``)"""
43 """Default application version (used by ``waf dist``)"""
46 """The variable name for the top-level directory in wscript files"""
49 """The variable name for the output directory in wscript files"""
51 WSCRIPT_FILE
= 'wscript'
52 """Name of the waf script files"""
55 """Directory from which waf has been called"""
57 """Location of the wscript file to use as the entry point"""
59 """Location of the project directory (top), if the project was configured"""
61 """Location of the build directory (out), if the project was configured"""
63 """Directory containing the waf modules"""
65 default_encoding
= Utils
.console_encoding()
66 """Encoding to use when reading outputs from other processes"""
70 Module representing the top-level wscript file (see :py:const:`waflib.Context.run_dir`)
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.
83 def create_context(cmd_name
, *k
, **kw
):
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
92 :param k: keyword arguments to give to the context class initializer
94 :return: Context object
95 :rtype: :py:class:`waflib.Context.Context`
100 ctx
= Context(*k
, **kw
)
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
)
114 if name
in ('ctx', 'Context'):
119 except AttributeError:
120 raise Errors
.WafError('Missing command for the context class %r (cmd)' % name
)
122 if not getattr(cls
, 'fun', None):
125 classes
.insert(0, cls
)
127 ctx
= store_context('ctx', (object,), {})
128 """Base class for all :py:class:`waflib.Context.Context` classes"""
132 Default context for waf commands, and base class for new command contexts.
134 Context objects are passed to top-level functions::
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``
143 :param fun: function name to execute when the command is called
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
152 Shortcut to :py:mod:`waflib.Errors` provided for convenience
157 A module cache for wscript files; see :py:meth:`Context.Context.load`
160 def __init__(self
, **kw
):
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
)
176 self
.exec_dict
= {'ctx':self
, 'conf':self
, 'bld':self
, 'opt':self
}
181 Called to free resources such as logger files
185 except AttributeError:
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)
204 module
= load_tool(t
, path
, with_sys_path
=with_sys_path
)
205 fun
= getattr(module
, kw
.get('name', self
.fun
), None)
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``
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.
235 :type node: :py:class:`waflib.Node.Node`
237 self
.cur_script
= self
.stack_path
.pop()
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
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
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
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
):
277 self
.pre_recurse(node
)
279 function_code
= node
.read('r', encoding
)
280 exec(compile(function_code
, node
.abspath(), 'exec'), self
.exec_dict
)
282 self
.post_recurse(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
):
288 self
.pre_recurse(node
)
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
:
295 raise Errors
.WafError('No function %r defined in %s' % (name
or self
.fun
, node
.abspath()))
298 self
.post_recurse(node
)
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
):
310 fmt
= os
.environ
.get('WAF_CMD_FORMAT')
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::
322 ret = tsk.generator.bld.exec_command('touch foo.txt')
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.
333 :returns: process exit status
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
)
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])
355 if sys
.hexversion
>= 0x3030000:
356 cargs
['timeout'] = kw
['timeout']
357 if not 'start_new_session' in kw
:
358 kw
['start_new_session'] = True
362 cargs
['input'] = kw
['input']
363 kw
['stdin'] = subprocess
.PIPE
367 if not isinstance(kw
['cwd'], str):
368 kw
['cwd'] = kw
['cwd'].abspath()
370 encoding
= kw
.pop('decode_as', default_encoding
)
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
)
378 if not isinstance(out
, str):
379 out
= out
.decode(encoding
, errors
='replace')
381 self
.logger
.debug('out: %s', out
)
383 Logs
.info(out
, extra
={'stream':sys
.stdout
, 'c1': ''})
385 if not isinstance(err
, str):
386 err
= err
.decode(encoding
, errors
='replace')
388 self
.logger
.error('err: %s' % err
)
390 Logs
.info(err
, extra
={'stream':sys
.stderr
, 'c1': ''})
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)::
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)
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.
413 :returns: a tuple containing the contents of stdout and stderr
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
434 if sys
.hexversion
>= 0x3030000:
435 cargs
['timeout'] = kw
['timeout']
436 if not 'start_new_session' in kw
:
437 kw
['start_new_session'] = True
441 cargs
['input'] = kw
['input']
442 kw
['stdin'] = subprocess
.PIPE
446 if not isinstance(kw
['cwd'], str):
447 kw
['cwd'] = kw
['cwd'].abspath()
449 encoding
= kw
.pop('decode_as', default_encoding
)
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
)
467 e
= Errors
.WafError('Command %r returned %r' % (cmd
, ret
))
475 elif to_ret
== STDERR
:
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::
485 conf.fatal('a requirement is missing')
487 :param msg: message to display
489 :param ex: optional exception object
491 :raises: :py:class:`waflib.Errors.ConfigurationError`
494 self
.logger
.info('from %s: %s' % (self
.path
.abspath(), msg
))
496 logfile
= self
.logger
.handlers
[0].baseFilename
497 except AttributeError:
500 if os
.environ
.get('WAF_PRINT_FAILURE_LOG'):
502 msg
= 'Log from (%s):\n%s\n' % (logfile
, Utils
.readf(logfile
))
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::
513 bld.to_log('starting the build')
515 Provide a logger on the context class or override this method if necessary.
523 self
.logger
.info(msg
)
525 sys
.stderr
.write(str(msg
))
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::
537 conf.msg('Checking for library foo', 'ok')
540 :param msg: message to display to the user
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`
552 self
.start_msg(msg
, **kw
)
555 result
= kw
['result']
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`
572 msg
= kw
.get('msg') or k
[0]
577 except AttributeError:
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
):
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`"""
597 result
= kw
.get('result') or k
[0]
612 if len(k
) > 1 and k
[1] in Logs
.colors_lst
:
613 # compatibility waf 1.7
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'
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
)
634 if not x
.name
in ban
:
635 load_tool(x
.name
.replace('.py', ''))
637 from zipfile
import PyZipFile
638 waflibs
= PyZipFile(waf_dir
)
639 lst
= waflibs
.namelist()
641 if not re
.match('waflib/extras/%s' % var
.replace('*', '.*'), var
):
643 f
= os
.path
.basename(x
)
646 r
= b
.replace('*', '.*')
650 f
= f
.replace('.py', '')
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
665 :return: Loaded Python module
669 return cache_modules
[path
]
673 module
= imp
.new_module(WSCRIPT_FILE
)
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
)
682 exec(compile(code
, path
, 'exec'), module
.__dict
__)
684 sys
.path
.remove(module_dir
)
686 cache_modules
[path
] = 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`
694 :param tool: Name of the tool
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
701 tool
= 'javaw' # jython
703 tool
= tool
.replace('++', 'xx')
705 if not with_sys_path
:
710 assert isinstance(tooldir
, list)
711 sys
.path
= tooldir
+ sys
.path
714 except ImportError as e
:
715 e
.waf_sys_path
= list(sys
.path
)
720 ret
= sys
.modules
[tool
]
721 Context
.tools
[tool
] = ret
724 if not with_sys_path
:
725 sys
.path
.insert(0, waf_dir
)
727 for x
in ('waflib.Tools.%s', 'waflib.extras.%s', 'waflib.%s', '%s'):
733 else: # raise an exception
735 except ImportError as e
:
736 e
.waf_sys_path
= list(sys
.path
)
739 if not with_sys_path
:
740 sys
.path
.remove(waf_dir
)
741 ret
= sys
.modules
[x
% tool
]
742 Context
.tools
[tool
] = ret
745 if not with_sys_path
:
746 sys
.path
+= back_path