123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737 |
- #!/usr/bin/env python
- # encoding: utf-8
- # Thomas Nagy, 2010-2018 (ita)
- """
- Classes and functions enabling the command system
- """
- import os, re, imp, sys
- from waflib import Utils, Errors, Logs
- import waflib.Node
- # the following 3 constants are updated on each new release (do not touch)
- HEXVERSION=0x2000900
- """Constant updated on new releases"""
- WAFVERSION="2.0.9"
- """Constant updated on new releases"""
- WAFREVISION="8a950e7bca9a3a9b1ae62aae039ef76e2adc4177"
- """Git revision when the waf version is updated"""
- ABI = 20
- """Version of the build data cache file format (used in :py:const:`waflib.Context.DBFILE`)"""
- DBFILE = '.wafpickle-%s-%d-%d' % (sys.platform, sys.hexversion, ABI)
- """Name of the pickle file for storing the build data"""
- APPNAME = 'APPNAME'
- """Default application name (used by ``waf dist``)"""
- VERSION = 'VERSION'
- """Default application version (used by ``waf dist``)"""
- TOP = 'top'
- """The variable name for the top-level directory in wscript files"""
- OUT = 'out'
- """The variable name for the output directory in wscript files"""
- WSCRIPT_FILE = 'wscript'
- """Name of the waf script files"""
- launch_dir = ''
- """Directory from which waf has been called"""
- run_dir = ''
- """Location of the wscript file to use as the entry point"""
- top_dir = ''
- """Location of the project directory (top), if the project was configured"""
- out_dir = ''
- """Location of the build directory (out), if the project was configured"""
- waf_dir = ''
- """Directory containing the waf modules"""
- default_encoding = Utils.console_encoding()
- """Encoding to use when reading outputs from other processes"""
- g_module = None
- """
- Module representing the top-level wscript file (see :py:const:`waflib.Context.run_dir`)
- """
- STDOUT = 1
- STDERR = -1
- BOTH = 0
- classes = []
- """
- List of :py:class:`waflib.Context.Context` subclasses that can be used as waf commands. The classes
- are added automatically by a metaclass.
- """
- def create_context(cmd_name, *k, **kw):
- """
- Returns a new :py:class:`waflib.Context.Context` instance corresponding to the given command.
- Used in particular by :py:func:`waflib.Scripting.run_command`
- :param cmd_name: command name
- :type cmd_name: string
- :param k: arguments to give to the context class initializer
- :type k: list
- :param k: keyword arguments to give to the context class initializer
- :type k: dict
- :return: Context object
- :rtype: :py:class:`waflib.Context.Context`
- """
- for x in classes:
- if x.cmd == cmd_name:
- return x(*k, **kw)
- ctx = Context(*k, **kw)
- ctx.fun = cmd_name
- return ctx
- class store_context(type):
- """
- Metaclass that registers command classes into the list :py:const:`waflib.Context.classes`
- 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, dct):
- super(store_context, cls).__init__(name, bases, dct)
- name = cls.__name__
- if name in ('ctx', 'Context'):
- return
- try:
- cls.cmd
- except AttributeError:
- raise Errors.WafError('Missing command for the context class %r (cmd)' % name)
- if not getattr(cls, 'fun', None):
- cls.fun = cls.cmd
- classes.insert(0, cls)
- ctx = store_context('ctx', (object,), {})
- """Base class for all :py:class:`waflib.Context.Context` classes"""
- class Context(ctx):
- """
- Default context for waf commands, and base class for new command contexts.
- Context objects are passed to top-level functions::
- def foo(ctx):
- print(ctx.__class__.__name__) # waflib.Context.Context
- Subclasses must define the class attributes 'cmd' and 'fun':
- :param cmd: command to execute as in ``waf cmd``
- :type cmd: string
- :param fun: function name to execute when the command is called
- :type fun: string
- .. 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
- """
- errors = Errors
- """
- Shortcut to :py:mod:`waflib.Errors` provided for convenience
- """
- tools = {}
- """
- A module cache for wscript files; see :py:meth:`Context.Context.load`
- """
- def __init__(self, **kw):
- try:
- rd = kw['run_dir']
- except KeyError:
- rd = run_dir
- # binds the context to the nodes in use to avoid a context singleton
- self.node_class = type('Nod3', (waflib.Node.Node,), {})
- self.node_class.__module__ = 'waflib.Node'
- self.node_class.ctx = self
- self.root = self.node_class('', None)
- self.cur_script = None
- self.path = self.root.find_dir(rd)
- self.stack_path = []
- self.exec_dict = {'ctx':self, 'conf':self, 'bld':self, 'opt':self}
- self.logger = None
- def finalize(self):
- """
- Called to free resources such as logger files
- """
- try:
- logger = self.logger
- except AttributeError:
- pass
- else:
- Logs.free_logger(logger)
- delattr(self, 'logger')
- def load(self, tool_list, *k, **kw):
- """
- Loads a Waf tool as a module, and try calling the function named :py:const:`waflib.Context.Context.fun`
- 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
- """
- tools = Utils.to_list(tool_list)
- path = Utils.to_list(kw.get('tooldir', ''))
- with_sys_path = kw.get('with_sys_path', True)
- for t in tools:
- module = load_tool(t, path, with_sys_path=with_sys_path)
- fun = getattr(module, kw.get('name', self.fun), None)
- if fun:
- fun(self)
- def execute(self):
- """
- Here, it calls the function name in the top-level wscript file. Most subclasses
- redefine this method to provide additional functionality.
- """
- self.recurse([os.path.dirname(g_module.root_path)])
- def pre_recurse(self, node):
- """
- Method executed immediately before a folder is read by :py:meth:`waflib.Context.Context.recurse`.
- 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
- :type node: :py:class:`waflib.Node.Node`
- """
- self.stack_path.append(self.cur_script)
- self.cur_script = node
- self.path = node.parent
- def post_recurse(self, node):
- """
- Restores ``self.cur_script`` and ``self.path`` right after :py:meth:`waflib.Context.Context.recurse` terminates.
- :param node: script
- :type node: :py:class:`waflib.Node.Node`
- """
- self.cur_script = self.stack_path.pop()
- if self.cur_script:
- self.path = self.cur_script.parent
- def recurse(self, dirs, name=None, mandatory=True, once=True, encoding=None):
- """
- Runs user-provided functions from the supplied list of directories.
- 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` are called immediately before
- and after a script has been executed.
- :param dirs: List of directories to visit
- :type dirs: list of string or space-separated string
- :param name: Name of function to invoke from the wscript
- :type name: string
- :param mandatory: whether sub wscript files are required to exist
- :type mandatory: bool
- :param once: read the script file once for a particular context
- :type once: bool
- """
- try:
- cache = self.recurse_cache
- except AttributeError:
- cache = self.recurse_cache = {}
- for d in Utils.to_list(dirs):
- if not os.path.isabs(d):
- # absolute paths only
- d = os.path.join(self.path.abspath(), d)
- WSCRIPT = os.path.join(d, WSCRIPT_FILE)
- WSCRIPT_FUN = WSCRIPT + '_' + (name or self.fun)
- node = self.root.find_node(WSCRIPT_FUN)
- if node and (not once or node not in cache):
- cache[node] = True
- self.pre_recurse(node)
- try:
- function_code = node.read('rU', encoding)
- exec(compile(function_code, node.abspath(), 'exec'), self.exec_dict)
- finally:
- self.post_recurse(node)
- elif not node:
- node = self.root.find_node(WSCRIPT)
- tup = (node, name or self.fun)
- if node and (not once or tup not in cache):
- cache[tup] = True
- self.pre_recurse(node)
- try:
- wscript_module = load_module(node.abspath(), encoding=encoding)
- user_function = getattr(wscript_module, (name or self.fun), None)
- if not user_function:
- if not mandatory:
- continue
- raise Errors.WafError('No function %r defined in %s' % (name or self.fun, node.abspath()))
- user_function(self)
- finally:
- self.post_recurse(node)
- elif not node:
- if not mandatory:
- continue
- try:
- os.listdir(d)
- except OSError:
- raise Errors.WafError('Cannot read the folder %r' % d)
- raise Errors.WafError('No wscript file in directory %s' % d)
- def log_command(self, cmd, kw):
- if Logs.verbose:
- fmt = os.environ.get('WAF_CMD_FORMAT')
- if fmt == 'string':
- if not isinstance(cmd, str):
- cmd = Utils.shell_escape(cmd)
- Logs.debug('runner: %r', cmd)
- Logs.debug('runner_env: kw=%s', kw)
- def exec_command(self, cmd, **kw):
- """
- Runs an external process and returns the exit status::
- def run(tsk):
- ret = tsk.generator.bld.exec_command('touch foo.txt')
- return ret
- If the context has the attribute 'log', then captures and logs the process stderr/stdout.
- 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
- :type cmd: string or list
- :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
- """
- subprocess = Utils.subprocess
- kw['shell'] = isinstance(cmd, str)
- self.log_command(cmd, kw)
- if self.logger:
- self.logger.info(cmd)
- if 'stdout' not in kw:
- kw['stdout'] = subprocess.PIPE
- if 'stderr' not in kw:
- kw['stderr'] = subprocess.PIPE
- if Logs.verbose and not kw['shell'] and not Utils.check_exe(cmd[0]):
- raise Errors.WafError('Program %s not found!' % cmd[0])
- cargs = {}
- if 'timeout' in kw:
- if sys.hexversion >= 0x3030000:
- cargs['timeout'] = kw['timeout']
- if not 'start_new_session' in kw:
- kw['start_new_session'] = True
- del kw['timeout']
- if 'input' in kw:
- if kw['input']:
- cargs['input'] = kw['input']
- kw['stdin'] = subprocess.PIPE
- del kw['input']
- if 'cwd' in kw:
- if not isinstance(kw['cwd'], str):
- kw['cwd'] = kw['cwd'].abspath()
- encoding = kw.pop('decode_as', default_encoding)
- try:
- ret, out, err = Utils.run_process(cmd, kw, cargs)
- except Exception as e:
- raise Errors.WafError('Execution failure: %s' % str(e), ex=e)
- if out:
- if not isinstance(out, str):
- out = out.decode(encoding, errors='replace')
- if self.logger:
- self.logger.debug('out: %s', out)
- else:
- Logs.info(out, extra={'stream':sys.stdout, 'c1': ''})
- if err:
- if not isinstance(err, str):
- err = err.decode(encoding, errors='replace')
- if self.logger:
- self.logger.error('err: %s' % err)
- else:
- Logs.info(err, extra={'stream':sys.stderr, 'c1': ''})
- return ret
- def cmd_and_log(self, cmd, **kw):
- """
- Executes a process 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
- will be bound to the WafError object (configuration tests)::
- def configure(conf):
- out = conf.cmd_and_log(['echo', 'hello'], output=waflib.Context.STDOUT, quiet=waflib.Context.BOTH)
- (out, err) = conf.cmd_and_log(['echo', 'hello'], output=waflib.Context.BOTH)
- (out, err) = conf.cmd_and_log(cmd, input='\\n'.encode(), output=waflib.Context.STDOUT)
- try:
- conf.cmd_and_log(['which', 'someapp'], output=waflib.Context.BOTH)
- except Errors.WafError as e:
- print(e.stdout, e.stderr)
- :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.
- :type kw: dict
- :returns: a tuple containing the contents of stdout and stderr
- :rtype: string
- :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
- kw['shell'] = isinstance(cmd, str)
- self.log_command(cmd, kw)
- quiet = kw.pop('quiet', None)
- to_ret = kw.pop('output', STDOUT)
- if Logs.verbose and not kw['shell'] and not Utils.check_exe(cmd[0]):
- raise Errors.WafError('Program %r not found!' % cmd[0])
- kw['stdout'] = kw['stderr'] = subprocess.PIPE
- if quiet is None:
- self.to_log(cmd)
- cargs = {}
- if 'timeout' in kw:
- if sys.hexversion >= 0x3030000:
- cargs['timeout'] = kw['timeout']
- if not 'start_new_session' in kw:
- kw['start_new_session'] = True
- del kw['timeout']
- if 'input' in kw:
- if kw['input']:
- cargs['input'] = kw['input']
- kw['stdin'] = subprocess.PIPE
- del kw['input']
- if 'cwd' in kw:
- if not isinstance(kw['cwd'], str):
- kw['cwd'] = kw['cwd'].abspath()
- encoding = kw.pop('decode_as', default_encoding)
- try:
- ret, out, err = Utils.run_process(cmd, kw, cargs)
- except Exception as e:
- raise Errors.WafError('Execution failure: %s' % str(e), ex=e)
- if not isinstance(out, str):
- out = out.decode(encoding, errors='replace')
- if not isinstance(err, str):
- err = err.decode(encoding, errors='replace')
- if out and quiet != STDOUT and quiet != BOTH:
- self.to_log('out: %s' % out)
- if err and quiet != STDERR and quiet != BOTH:
- self.to_log('err: %s' % err)
- if ret:
- e = Errors.WafError('Command %r returned %r' % (cmd, ret))
- e.returncode = ret
- e.stderr = err
- e.stdout = out
- raise e
- if to_ret == BOTH:
- return (out, err)
- elif to_ret == STDERR:
- return err
- return out
- def fatal(self, msg, ex=None):
- """
- Prints an error message in red and stops command execution; this is
- usually used in the configuration section::
- def configure(conf):
- conf.fatal('a requirement is missing')
- :param msg: message to display
- :type msg: string
- :param ex: optional exception object
- :type ex: exception
- :raises: :py:class:`waflib.Errors.ConfigurationError`
- """
- if self.logger:
- self.logger.info('from %s: %s' % (self.path.abspath(), msg))
- try:
- logfile = self.logger.handlers[0].baseFilename
- except AttributeError:
- pass
- else:
- if os.environ.get('WAF_PRINT_FAILURE_LOG'):
- # see #1930
- msg = 'Log from (%s):\n%s\n' % (logfile, Utils.readf(logfile))
- else:
- msg = '%s\n(complete log in %s)' % (msg, logfile)
- raise self.errors.ConfigurationError(msg, ex=ex)
- def to_log(self, msg):
- """
- Logs information to the logger (if present), or to stderr.
- Empty messages are not printed::
- def build(bld):
- bld.to_log('starting the build')
- Provide a logger on the context class or override this methid if necessary.
- :param msg: message
- :type msg: string
- """
- if not msg:
- return
- if self.logger:
- self.logger.info(msg)
- else:
- sys.stderr.write(str(msg))
- sys.stderr.flush()
- def msg(self, *k, **kw):
- """
- Prints a configuration message of the form ``msg: result``.
- The second part of the message will be in colors. The output
- can be disabled easly by setting ``in_msg`` to a positive value::
- def configure(conf):
- self.in_msg = 1
- conf.msg('Checking for library foo', 'ok')
- # no output
- :param msg: message to display to the user
- :type msg: string
- :param result: result to display
- :type result: string or boolean
- :param color: color to use, see :py:const:`waflib.Logs.colors_lst`
- :type color: string
- """
- try:
- msg = kw['msg']
- except KeyError:
- msg = k[0]
- self.start_msg(msg, **kw)
- try:
- result = kw['result']
- except KeyError:
- result = k[1]
- color = kw.get('color')
- if not isinstance(color, str):
- color = result and 'GREEN' or 'YELLOW'
- self.end_msg(result, color, **kw)
- def start_msg(self, *k, **kw):
- """
- Prints the beginning of a 'Checking for xxx' message. See :py:meth:`waflib.Context.Context.msg`
- """
- if kw.get('quiet'):
- return
- msg = kw.get('msg') or k[0]
- try:
- if self.in_msg:
- self.in_msg += 1
- return
- except AttributeError:
- self.in_msg = 0
- self.in_msg += 1
- try:
- self.line_just = max(self.line_just, len(msg))
- except AttributeError:
- self.line_just = max(40, len(msg))
- for x in (self.line_just * '-', msg):
- self.to_log(x)
- Logs.pprint('NORMAL', "%s :" % msg.ljust(self.line_just), sep='')
- def end_msg(self, *k, **kw):
- """Prints the end of a 'Checking for' message. See :py:meth:`waflib.Context.Context.msg`"""
- if kw.get('quiet'):
- return
- self.in_msg -= 1
- if self.in_msg:
- return
- result = kw.get('result') or k[0]
- defcolor = 'GREEN'
- if result is True:
- msg = 'ok'
- elif not result:
- msg = 'not found'
- defcolor = 'YELLOW'
- else:
- msg = str(result)
- self.to_log(msg)
- try:
- color = kw['color']
- except KeyError:
- if len(k) > 1 and k[1] in Logs.colors_lst:
- # compatibility waf 1.7
- color = k[1]
- else:
- color = defcolor
- Logs.pprint(color, msg)
- 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
- """
- if os.path.isdir(waf_dir):
- lst = self.root.find_node(waf_dir).find_node('waflib/extras').ant_glob(var)
- for x in lst:
- if not x.name in ban:
- load_tool(x.name.replace('.py', ''))
- else:
- from zipfile import PyZipFile
- waflibs = PyZipFile(waf_dir)
- lst = waflibs.namelist()
- for x in lst:
- if not re.match('waflib/extras/%s' % var.replace('*', '.*'), var):
- continue
- f = os.path.basename(x)
- doban = False
- for b in ban:
- r = b.replace('*', '.*')
- if re.match(r, f):
- doban = True
- if not doban:
- f = f.replace('.py', '')
- load_tool(f)
- cache_modules = {}
- """
- Dictionary holding already loaded modules (wscript), indexed by their absolute path.
- The modules are added automatically by :py:func:`waflib.Context.load_module`
- """
- def load_module(path, encoding=None):
- """
- Loads a wscript file as a python module. This method caches results in :py:attr:`waflib.Context.cache_modules`
- :param path: file path
- :type path: string
- :return: Loaded Python module
- :rtype: module
- """
- try:
- return cache_modules[path]
- except KeyError:
- pass
- module = imp.new_module(WSCRIPT_FILE)
- try:
- code = Utils.readf(path, m='rU', encoding=encoding)
- except EnvironmentError:
- raise Errors.WafError('Could not read the file %r' % path)
- module_dir = os.path.dirname(path)
- sys.path.insert(0, module_dir)
- try:
- exec(compile(code, path, 'exec'), module.__dict__)
- finally:
- sys.path.remove(module_dir)
- cache_modules[path] = module
- return module
- def load_tool(tool, tooldir=None, ctx=None, with_sys_path=True):
- """
- Importx a Waf tool as a python module, and stores it in the dict :py:const:`waflib.Context.Context.tools`
- :type tool: string
- :param tool: Name of the tool
- :type tooldir: list
- :param tooldir: List of directories to search for the tool module
- :type with_sys_path: boolean
- :param with_sys_path: whether or not to search the regular sys.path, besides waf_dir and potentially given tooldirs
- """
- if tool == 'java':
- tool = 'javaw' # jython
- else:
- tool = tool.replace('++', 'xx')
- if not with_sys_path:
- back_path = sys.path
- sys.path = []
- try:
- if tooldir:
- assert isinstance(tooldir, list)
- sys.path = tooldir + sys.path
- try:
- __import__(tool)
- except ImportError as e:
- e.waf_sys_path = list(sys.path)
- raise
- finally:
- for d in tooldir:
- sys.path.remove(d)
- ret = sys.modules[tool]
- Context.tools[tool] = ret
- return ret
- else:
- if not with_sys_path:
- sys.path.insert(0, waf_dir)
- try:
- for x in ('waflib.Tools.%s', 'waflib.extras.%s', 'waflib.%s', '%s'):
- try:
- __import__(x % tool)
- break
- except ImportError:
- x = None
- else: # raise an exception
- __import__(tool)
- except ImportError as e:
- e.waf_sys_path = list(sys.path)
- raise
- finally:
- if not with_sys_path:
- sys.path.remove(waf_dir)
- ret = sys.modules[x % tool]
- Context.tools[tool] = ret
- return ret
- finally:
- if not with_sys_path:
- sys.path += back_path
|