This commit is contained in:
Thomas Nagy 2016-06-25 02:38:26 +02:00
parent 21e9be8cdb
commit ccc77cd9f5
No known key found for this signature in database
GPG Key ID: 67A565EDFDF90E64
3 changed files with 82 additions and 52 deletions

View File

@ -3,7 +3,7 @@
# Thomas Nagy, 2010-2016 (ita) # Thomas Nagy, 2010-2016 (ita)
""" """
Classes and functions required for waf commands Classes and functions enabling the command system
""" """
import os, re, imp, sys import os, re, imp, sys
@ -54,7 +54,7 @@ waf_dir = ''
g_module = None g_module = None
""" """
Module representing the main wscript file (see :py:const:`waflib.Context.run_dir`) Module representing the top-level wscript file (see :py:const:`waflib.Context.run_dir`)
""" """
STDOUT = 1 STDOUT = 1
@ -69,15 +69,17 @@ are added automatically by a metaclass.
def create_context(cmd_name, *k, **kw): def create_context(cmd_name, *k, **kw):
""" """
Create a new :py:class:`waflib.Context.Context` instance corresponding to the given command. Returns a new :py:class:`waflib.Context.Context` instance corresponding to the given command.
Used in particular by :py:func:`waflib.Scripting.run_command` Used in particular by :py:func:`waflib.Scripting.run_command`
:param cmd_name: command :param cmd_name: command name
:type cmd_name: string :type cmd_name: string
:param k: arguments to give to the context class initializer :param k: arguments to give to the context class initializer
:type k: list :type k: list
:param k: keyword arguments to give to the context class initializer :param k: keyword arguments to give to the context class initializer
:type k: dict :type k: dict
:return: Context object
:rtype: :py:class:`waflib.Context.Context`
""" """
global classes global classes
for x in classes: for x in classes:
@ -89,8 +91,9 @@ def create_context(cmd_name, *k, **kw):
class store_context(type): class store_context(type):
""" """
Metaclass for storing the command classes into the list :py:const:`waflib.Context.classes` Metaclass that registers command classes into the list :py:const:`waflib.Context.classes`
Context classes must provide an attribute 'cmd' representing the command to execute Context classes must provide an attribute 'cmd' representing the command name, and a function
attribute 'fun' representing the function name that the command uses.
""" """
def __init__(cls, name, bases, dict): def __init__(cls, name, bases, dict):
super(store_context, cls).__init__(name, bases, dict) super(store_context, cls).__init__(name, bases, dict)
@ -111,7 +114,7 @@ class store_context(type):
classes.insert(0, cls) classes.insert(0, cls)
ctx = store_context('ctx', (object,), {}) ctx = store_context('ctx', (object,), {})
"""Base class for the :py:class:`waflib.Context.Context` classes""" """Base class for all :py:class:`waflib.Context.Context` classes"""
class Context(ctx): class Context(ctx):
""" """
@ -122,7 +125,7 @@ class Context(ctx):
def foo(ctx): def foo(ctx):
print(ctx.__class__.__name__) # waflib.Context.Context print(ctx.__class__.__name__) # waflib.Context.Context
Subclasses must define the attribute 'cmd': Subclasses must define the class attributes 'cmd' and 'fun':
:param cmd: command to execute as in ``waf cmd`` :param cmd: command to execute as in ``waf cmd``
:type cmd: string :type cmd: string
@ -140,7 +143,7 @@ class Context(ctx):
tools = {} tools = {}
""" """
A cache for modules (wscript files) read by :py:meth:`Context.Context.load` A module cache for wscript files; see :py:meth:`Context.Context.load`
""" """
def __init__(self, **kw): def __init__(self, **kw):
@ -152,7 +155,7 @@ class Context(ctx):
# binds the context to the nodes in use to avoid a context singleton # binds the context to the nodes in use to avoid a context singleton
self.node_class = type('Nod3', (waflib.Node.Node,), {}) self.node_class = type('Nod3', (waflib.Node.Node,), {})
self.node_class.__module__ = "waflib.Node" self.node_class.__module__ = 'waflib.Node'
self.node_class.ctx = self self.node_class.ctx = self
self.root = self.node_class('', None) self.root = self.node_class('', None)
@ -165,7 +168,7 @@ class Context(ctx):
def finalize(self): def finalize(self):
""" """
Use to free resources such as open files potentially held by the logger Called to free resources such as logger files
""" """
try: try:
logger = self.logger logger = self.logger
@ -177,11 +180,11 @@ class Context(ctx):
def load(self, tool_list, *k, **kw): def load(self, tool_list, *k, **kw):
""" """
Load a Waf tool as a module, and try calling the function named :py:const:`waflib.Context.Context.fun` from it. Loads a Waf tool as a module, and try calling the function named :py:const:`waflib.Context.Context.fun`
A ``tooldir`` value may be provided as a list of module paths. from it. A ``tooldir`` argument may be provided as a list of module paths.
:param tool_list: list of Waf tool names to load
:type tool_list: list of string or space-separated string :type tool_list: list of string or space-separated string
:param tool_list: list of Waf tools to use
""" """
tools = Utils.to_list(tool_list) tools = Utils.to_list(tool_list)
path = Utils.to_list(kw.get('tooldir', '')) path = Utils.to_list(kw.get('tooldir', ''))
@ -195,15 +198,17 @@ class Context(ctx):
def execute(self): def execute(self):
""" """
Execute the command. Redefine this method in subclasses. Here, it calls the function name in the top-level wscript file. Most subclasses
redefine this method to provide additional functionality.
""" """
global g_module global g_module
self.recurse([os.path.dirname(g_module.root_path)]) self.recurse([os.path.dirname(g_module.root_path)])
def pre_recurse(self, node): def pre_recurse(self, node):
""" """
Method executed immediately before a folder is read by :py:meth:`waflib.Context.Context.recurse`. The node given is set Method executed immediately before a folder is read by :py:meth:`waflib.Context.Context.recurse`.
as an attribute ``self.cur_script``, and as the current path ``self.path`` The current script is bound as a Node object on ``self.cur_script``, and the current path
is bound to ``self.path``
:param node: script :param node: script
:type node: :py:class:`waflib.Node.Node` :type node: :py:class:`waflib.Node.Node`
@ -215,7 +220,7 @@ class Context(ctx):
def post_recurse(self, node): def post_recurse(self, node):
""" """
Restore ``self.cur_script`` and ``self.path`` right after :py:meth:`waflib.Context.Context.recurse` terminates. Restores ``self.cur_script`` and ``self.path`` right after :py:meth:`waflib.Context.Context.recurse` terminates.
:param node: script :param node: script
:type node: :py:class:`waflib.Node.Node` :type node: :py:class:`waflib.Node.Node`
@ -226,10 +231,13 @@ class Context(ctx):
def recurse(self, dirs, name=None, mandatory=True, once=True, encoding=None): def recurse(self, dirs, name=None, mandatory=True, once=True, encoding=None):
""" """
Run user code from the supplied list of directories. Runs user-provided functions from the supplied list of directories.
The directories can be either absolute, or relative to the directory The directories can be either absolute, or relative to the directory
of the wscript file. The methods :py:meth:`waflib.Context.Context.pre_recurse` and :py:meth:`waflib.Context.Context.post_recurse` of the wscript file
are called immediately before and after a script has been executed.
The methods :py:meth:`waflib.Context.Context.pre_recurse` and
:py:meth:`waflib.Context.Context.post_recurse` are called immediately before
and after a script has been executed.
:param dirs: List of directories to visit :param dirs: List of directories to visit
:type dirs: list of string or space-separated string :type dirs: list of string or space-separated string
@ -290,18 +298,22 @@ class Context(ctx):
def exec_command(self, cmd, **kw): def exec_command(self, cmd, **kw):
""" """
Execute a command and return the exit status. If the context has the attribute 'log', Runs an external process and returns the exit status::
capture and log the process stderr/stdout for logging purposes::
def run(tsk): def run(tsk):
ret = tsk.generator.bld.exec_command('touch foo.txt') ret = tsk.generator.bld.exec_command('touch foo.txt')
return ret return ret
This method captures the standard/error outputs (Issue 1101), but it does not return the values If the context has the attribute 'log', then captures and logs the process stderr/stdout.
unlike :py:meth:`waflib.Context.Context.cmd_and_log` Unlike :py:meth:`waflib.Context.Context.cmd_and_log`, this method does not return the
stdout/stderr values captured.
:param cmd: command argument for subprocess.Popen :param cmd: command argument for subprocess.Popen
:type cmd: string or list
:param kw: keyword arguments for subprocess.Popen. The parameters input/timeout will be passed to wait/communicate. :param kw: keyword arguments for subprocess.Popen. The parameters input/timeout will be passed to wait/communicate.
:type kw: dict
:returns: process exit status
:rtype: integer
""" """
subprocess = Utils.subprocess subprocess = Utils.subprocess
kw['shell'] = isinstance(cmd, str) kw['shell'] = isinstance(cmd, str)
@ -358,7 +370,7 @@ class Context(ctx):
def cmd_and_log(self, cmd, **kw): def cmd_and_log(self, cmd, **kw):
""" """
Execute a command and return stdout/stderr if the execution is successful. Executes a proces and returns stdout/stderr if the execution is successful.
An exception is thrown when the exit status is non-0. In that case, both stderr and stdout An exception is thrown when the exit status is non-0. In that case, both stderr and stdout
will be bound to the WafError object:: will be bound to the WafError object::
@ -372,7 +384,13 @@ class Context(ctx):
print(e.stdout, e.stderr) print(e.stdout, e.stderr)
:param cmd: args for subprocess.Popen :param cmd: args for subprocess.Popen
:type cmd: list or string
:param kw: keyword arguments for subprocess.Popen. The parameters input/timeout will be passed to wait/communicate. :param kw: keyword arguments for subprocess.Popen. The parameters input/timeout will be passed to wait/communicate.
:type kw: dict
:returns: process exit status
:rtype: integer
:raises: :py:class:`waflib.Errors.WafError` if an invalid executable is specified for a non-shell process
:raises: :py:class:`waflib.Errors.WafError` in case of execution failure; stdout/stderr/returncode are bound to the exception object
""" """
subprocess = Utils.subprocess subprocess = Utils.subprocess
kw['shell'] = isinstance(cmd, str) kw['shell'] = isinstance(cmd, str)
@ -442,7 +460,8 @@ class Context(ctx):
def fatal(self, msg, ex=None): def fatal(self, msg, ex=None):
""" """
Raise a configuration error to interrupt the execution immediately:: Prints an error message in red and stops command execution; this is
usually used in the configuration section::
def configure(conf): def configure(conf):
conf.fatal('a requirement is missing') conf.fatal('a requirement is missing')
@ -451,6 +470,7 @@ class Context(ctx):
:type msg: string :type msg: string
:param ex: optional exception object :param ex: optional exception object
:type ex: exception :type ex: exception
:raises: :py:class:`waflib.Errors.ConfigurationError`
""" """
if self.logger: if self.logger:
self.logger.info('from %s: %s' % (self.path.abspath(), msg)) self.logger.info('from %s: %s' % (self.path.abspath(), msg))
@ -462,13 +482,13 @@ class Context(ctx):
def to_log(self, msg): def to_log(self, msg):
""" """
Log some information to the logger (if present), or to stderr. If the message is empty, Logs information to the logger (if present), or to stderr.
it is not printed:: Empty messages are not printed::
def build(bld): def build(bld):
bld.to_log('starting the build') bld.to_log('starting the build')
When in doubt, override this method, or provide a logger on the context class. Provide a logger on the context class or override this methid if necessary.
:param msg: message :param msg: message
:type msg: string :type msg: string
@ -484,7 +504,7 @@ class Context(ctx):
def msg(self, *k, **kw): def msg(self, *k, **kw):
""" """
Print a configuration message of the form ``msg: result``. Prints a configuration message of the form ``msg: result``.
The second part of the message will be in colors. The output The second part of the message will be in colors. The output
can be disabled easly by setting ``in_msg`` to a positive value:: can be disabled easly by setting ``in_msg`` to a positive value::
@ -520,7 +540,7 @@ class Context(ctx):
def start_msg(self, *k, **kw): def start_msg(self, *k, **kw):
""" """
Print the beginning of a 'Checking for xxx' message. See :py:meth:`waflib.Context.Context.msg` Prints the beginning of a 'Checking for xxx' message. See :py:meth:`waflib.Context.Context.msg`
""" """
if kw.get('quiet'): if kw.get('quiet'):
return return
@ -543,7 +563,7 @@ class Context(ctx):
Logs.pprint('NORMAL', "%s :" % msg.ljust(self.line_just), sep='') Logs.pprint('NORMAL', "%s :" % msg.ljust(self.line_just), sep='')
def end_msg(self, *k, **kw): def end_msg(self, *k, **kw):
"""Print the end of a 'Checking for' message. See :py:meth:`waflib.Context.Context.msg`""" """Prints the end of a 'Checking for' message. See :py:meth:`waflib.Context.Context.msg`"""
if kw.get('quiet'): if kw.get('quiet'):
return return
self.in_msg -= 1 self.in_msg -= 1
@ -573,6 +593,17 @@ class Context(ctx):
Logs.pprint(color, msg) Logs.pprint(color, msg)
def load_special_tools(self, var, ban=[]): def load_special_tools(self, var, ban=[]):
"""
Loads third-party extensions modules for certain programming languages
by trying to list certain files in the extras/ directory. This method
is typically called once for a programming language group, see for
example :py:mod:`waflib.Tools.compiler_c`
:param var: glob expression, for example 'cxx\_\*.py'
:type var: string
:param ban: list of exact file names to exclude
:type ban: list of string
"""
global waf_dir global waf_dir
if os.path.isdir(waf_dir): if os.path.isdir(waf_dir):
lst = self.root.find_node(waf_dir).find_node('waflib/extras').ant_glob(var) lst = self.root.find_node(waf_dir).find_node('waflib/extras').ant_glob(var)
@ -598,13 +629,13 @@ class Context(ctx):
cache_modules = {} cache_modules = {}
""" """
Dictionary holding already loaded modules, keyed by their absolute path. Dictionary holding already loaded modules (wscript), indexed by their absolute path.
The modules are added automatically by :py:func:`waflib.Context.load_module` The modules are added automatically by :py:func:`waflib.Context.load_module`
""" """
def load_module(path, encoding=None): def load_module(path, encoding=None):
""" """
Load a source file as a python module. Loads a wscript file as a python module. This method caches results in :py:attr:`waflib.Context.cache_modules`
:param path: file path :param path: file path
:type path: string :type path: string
@ -624,17 +655,17 @@ def load_module(path, encoding=None):
module_dir = os.path.dirname(path) module_dir = os.path.dirname(path)
sys.path.insert(0, module_dir) sys.path.insert(0, module_dir)
try:
try : exec(compile(code, path, 'exec'), module.__dict__) exec(compile(code, path, 'exec'), module.__dict__)
finally: sys.path.remove(module_dir) finally:
sys.path.remove(module_dir)
cache_modules[path] = module cache_modules[path] = module
return module return module
def load_tool(tool, tooldir=None, ctx=None, with_sys_path=True): def load_tool(tool, tooldir=None, ctx=None, with_sys_path=True):
""" """
Import a Waf tool (python module), and store it in the dict :py:const:`waflib.Context.Context.tools` Importx a Waf tool as a python module, and stores it in the dict :py:const:`waflib.Context.Context.tools`
:type tool: string :type tool: string
:param tool: Name of the tool :param tool: Name of the tool
@ -648,8 +679,9 @@ def load_tool(tool, tooldir=None, ctx=None, with_sys_path=True):
else: else:
tool = tool.replace('++', 'xx') tool = tool.replace('++', 'xx')
origSysPath = sys.path if not with_sys_path:
if not with_sys_path: sys.path = [] back_path = sys.path
sys.path = []
try: try:
if tooldir: if tooldir:
assert isinstance(tooldir, list) assert isinstance(tooldir, list)
@ -679,5 +711,6 @@ def load_tool(tool, tooldir=None, ctx=None, with_sys_path=True):
Context.tools[tool] = ret Context.tools[tool] = ret
return ret return ret
finally: finally:
if not with_sys_path: sys.path += origSysPath if not with_sys_path:
sys.path += back_path

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# encoding: utf-8 # encoding: utf-8
# Thomas Nagy, 2005-2010 (ita) # Thomas Nagy, 2005-2016 (ita)
""" """
logging, colors, terminal width and pretty-print logging, colors, terminal width and pretty-print
@ -81,8 +81,9 @@ get_term_cols.__doc__ = """
""" """
def get_color(cl): def get_color(cl):
if not colors_lst['USE']: return '' if colors_lst['USE']:
return colors_lst.get(cl, '') return colors_lst.get(cl, '')
return ''
class color_dict(object): class color_dict(object):
"""attribute-based color access, eg: colors.PINK""" """attribute-based color access, eg: colors.PINK"""

View File

@ -2,23 +2,19 @@
# encoding: utf-8 # encoding: utf-8
# Thomas Nagy, 2010-2015 (ita) # Thomas Nagy, 2010-2015 (ita)
"""
burn a book, save a tree
"""
import os import os
all_modifs = {} all_modifs = {}
def fixdir(dir): def fixdir(dir):
"""call all the substitution functions on the waf folders""" """Call all substitution functions on Waf folders"""
global all_modifs global all_modifs
for k in all_modifs: for k in all_modifs:
for v in all_modifs[k]: for v in all_modifs[k]:
modif(os.path.join(dir, 'waflib'), k, v) modif(os.path.join(dir, 'waflib'), k, v)
def modif(dir, name, fun): def modif(dir, name, fun):
"""execute a substitution function""" """Call a substitution function"""
if name == '*': if name == '*':
lst = [] lst = []
for y in '. Tools extras'.split(): for y in '. Tools extras'.split():