3 # Thomas Nagy, 2010-2018 (ita)
6 Classes and functions enabling the command system
9 import os
, re
, imp
, sys
10 from waflib
import Utils
, Errors
, Logs
13 # the following 3 constants are updated on each new release (do not touch)
15 """Constant updated on new releases"""
18 """Constant updated on new releases"""
20 WAFREVISION
="54841218840ffa34fddf834680a5a17db69caa12"
21 """Git revision when the waf version is updated"""
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"""
30 """Default application name (used by ``waf dist``)"""
33 """Default application version (used by ``waf dist``)"""
36 """The variable name for the top-level directory in wscript files"""
39 """The variable name for the output directory in wscript files"""
41 WSCRIPT_FILE
= 'wscript'
42 """Name of the waf script files"""
45 """Directory from which waf has been called"""
47 """Location of the wscript file to use as the entry point"""
49 """Location of the project directory (top), if the project was configured"""
51 """Location of the build directory (out), if the project was configured"""
53 """Directory containing the waf modules"""
55 default_encoding
= Utils
.console_encoding()
56 """Encoding to use when reading outputs from other processes"""
60 Module representing the top-level wscript file (see :py:const:`waflib.Context.run_dir`)
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.
73 def create_context(cmd_name
, *k
, **kw
):
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
82 :param k: keyword arguments to give to the context class initializer
84 :return: Context object
85 :rtype: :py:class:`waflib.Context.Context`
90 ctx
= Context(*k
, **kw
)
94 class store_context(type):
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.
100 def __init__(cls
, name
, bases
, dct
):
101 super(store_context
, cls
).__init
__(name
, bases
, dct
)
104 if name
in ('ctx', 'Context'):
109 except AttributeError:
110 raise Errors
.WafError('Missing command for the context class %r (cmd)' % name
)
112 if not getattr(cls
, 'fun', None):
115 classes
.insert(0, cls
)
117 ctx
= store_context('ctx', (object,), {})
118 """Base class for all :py:class:`waflib.Context.Context` classes"""
122 Default context for waf commands, and base class for new command contexts.
124 Context objects are passed to top-level functions::
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``
133 :param fun: function name to execute when the command is called
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
142 Shortcut to :py:mod:`waflib.Errors` provided for convenience
147 A module cache for wscript files; see :py:meth:`Context.Context.load`
150 def __init__(self
, **kw
):
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
)
166 self
.exec_dict
= {'ctx':self
, 'conf':self
, 'bld':self
, 'opt':self
}
171 Called to free resources such as logger files
175 except AttributeError:
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)
194 module
= load_tool(t
, path
, with_sys_path
=with_sys_path
)
195 fun
= getattr(module
, kw
.get('name', self
.fun
), None)
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``
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.
225 :type node: :py:class:`waflib.Node.Node`
227 self
.cur_script
= self
.stack_path
.pop()
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
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
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
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
):
267 self
.pre_recurse(node
)
269 function_code
= node
.read('rU', encoding
)
270 exec(compile(function_code
, node
.abspath(), 'exec'), self
.exec_dict
)
272 self
.post_recurse(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
):
278 self
.pre_recurse(node
)
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
:
285 raise Errors
.WafError('No function %r defined in %s' % (name
or self
.fun
, node
.abspath()))
288 self
.post_recurse(node
)
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
):
300 fmt
= os
.environ
.get('WAF_CMD_FORMAT')
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::
312 ret = tsk.generator.bld.exec_command('touch foo.txt')
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.
323 :returns: process exit status
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
)
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])
345 if sys
.hexversion
>= 0x3030000:
346 cargs
['timeout'] = kw
['timeout']
347 if not 'start_new_session' in kw
:
348 kw
['start_new_session'] = True
352 cargs
['input'] = kw
['input']
353 kw
['stdin'] = subprocess
.PIPE
357 if not isinstance(kw
['cwd'], str):
358 kw
['cwd'] = kw
['cwd'].abspath()
360 encoding
= kw
.pop('decode_as', default_encoding
)
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
)
368 if not isinstance(out
, str):
369 out
= out
.decode(encoding
, errors
='replace')
371 self
.logger
.debug('out: %s', out
)
373 Logs
.info(out
, extra
={'stream':sys
.stdout
, 'c1': ''})
375 if not isinstance(err
, str):
376 err
= err
.decode(encoding
, errors
='replace')
378 self
.logger
.error('err: %s' % err
)
380 Logs
.info(err
, extra
={'stream':sys
.stderr
, 'c1': ''})
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)::
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)
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.
403 :returns: a tuple containing the contents of stdout and stderr
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
424 if sys
.hexversion
>= 0x3030000:
425 cargs
['timeout'] = kw
['timeout']
426 if not 'start_new_session' in kw
:
427 kw
['start_new_session'] = True
431 cargs
['input'] = kw
['input']
432 kw
['stdin'] = subprocess
.PIPE
436 if not isinstance(kw
['cwd'], str):
437 kw
['cwd'] = kw
['cwd'].abspath()
439 encoding
= kw
.pop('decode_as', default_encoding
)
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
)
457 e
= Errors
.WafError('Command %r returned %r' % (cmd
, ret
))
465 elif to_ret
== STDERR
:
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::
475 conf.fatal('a requirement is missing')
477 :param msg: message to display
479 :param ex: optional exception object
481 :raises: :py:class:`waflib.Errors.ConfigurationError`
484 self
.logger
.info('from %s: %s' % (self
.path
.abspath(), msg
))
486 logfile
= self
.logger
.handlers
[0].baseFilename
487 except AttributeError:
490 if os
.environ
.get('WAF_PRINT_FAILURE_LOG'):
492 msg
= 'Log from (%s):\n%s\n' % (logfile
, Utils
.readf(logfile
))
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::
503 bld.to_log('starting the build')
505 Provide a logger on the context class or override this method if necessary.
513 self
.logger
.info(msg
)
515 sys
.stderr
.write(str(msg
))
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 easily by setting ``in_msg`` to a positive value::
527 conf.msg('Checking for library foo', 'ok')
530 :param msg: message to display to the user
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`
542 self
.start_msg(msg
, **kw
)
545 result
= kw
['result']
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`
562 msg
= kw
.get('msg') or k
[0]
567 except AttributeError:
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
):
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`"""
587 result
= kw
.get('result') or k
[0]
602 if len(k
) > 1 and k
[1] in Logs
.colors_lst
:
603 # compatibility waf 1.7
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'
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
)
624 if not x
.name
in ban
:
625 load_tool(x
.name
.replace('.py', ''))
627 from zipfile
import PyZipFile
628 waflibs
= PyZipFile(waf_dir
)
629 lst
= waflibs
.namelist()
631 if not re
.match('waflib/extras/%s' % var
.replace('*', '.*'), var
):
633 f
= os
.path
.basename(x
)
636 r
= b
.replace('*', '.*')
640 f
= f
.replace('.py', '')
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
655 :return: Loaded Python module
659 return cache_modules
[path
]
663 module
= imp
.new_module(WSCRIPT_FILE
)
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
)
672 exec(compile(code
, path
, 'exec'), module
.__dict
__)
674 sys
.path
.remove(module_dir
)
676 cache_modules
[path
] = 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`
684 :param tool: Name of the tool
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
691 tool
= 'javaw' # jython
693 tool
= tool
.replace('++', 'xx')
695 if not with_sys_path
:
700 assert isinstance(tooldir
, list)
701 sys
.path
= tooldir
+ sys
.path
704 except ImportError as e
:
705 e
.waf_sys_path
= list(sys
.path
)
710 ret
= sys
.modules
[tool
]
711 Context
.tools
[tool
] = ret
714 if not with_sys_path
:
715 sys
.path
.insert(0, waf_dir
)
717 for x
in ('waflib.Tools.%s', 'waflib.extras.%s', 'waflib.%s', '%s'):
723 else: # raise an exception
725 except ImportError as e
:
726 e
.waf_sys_path
= list(sys
.path
)
729 if not with_sys_path
:
730 sys
.path
.remove(waf_dir
)
731 ret
= sys
.modules
[x
% tool
]
732 Context
.tools
[tool
] = ret
735 if not with_sys_path
:
736 sys
.path
+= back_path