- Migrate pkg-config checks from atleast_version. - Check ppoll with a fragment, since the function_name argument has been removed.tags/v1.9.13
@@ -25,13 +25,13 @@ WAFLIB_STRIP_TOOLS=" | |||
ifort | |||
intltool | |||
javaw | |||
kde4 | |||
ldc2 | |||
lua | |||
md5_tstamp | |||
nasm | |||
nobuild | |||
perl | |||
python | |||
qt4 | |||
qt5 | |||
ruby | |||
tex | |||
@@ -40,17 +40,16 @@ WAFLIB_STRIP_TOOLS=" | |||
" | |||
WAFLIB_STRIP_EXTRAS=" | |||
add_objects | |||
biber | |||
bjam | |||
blender | |||
boo | |||
boost | |||
buildcopy | |||
c_dumbpreproc | |||
c_emscripten | |||
cabal | |||
cfg_altoptions | |||
cfg_cross_gnu | |||
clang_compilation_database | |||
codelite | |||
compat15 | |||
@@ -58,6 +57,7 @@ WAFLIB_STRIP_EXTRAS=" | |||
color_rvct | |||
cppcheck | |||
cpplint | |||
cross_gnu | |||
cython | |||
dcc | |||
distnet | |||
@@ -65,6 +65,7 @@ WAFLIB_STRIP_EXTRAS=" | |||
dpapi | |||
eclipse | |||
erlang | |||
fast_partial | |||
fc_bgxlf | |||
fc_cray | |||
fc_nag | |||
@@ -79,19 +80,17 @@ WAFLIB_STRIP_EXTRAS=" | |||
fsb | |||
fsc | |||
gccdeps | |||
go | |||
gdbus | |||
gob2 | |||
halide | |||
javatest | |||
kde4 | |||
local_rpath | |||
make | |||
md5_tstamp | |||
mem_reducer | |||
midl | |||
misc | |||
msvcdeps | |||
msvs | |||
netcache_client | |||
nobuild | |||
objcopy | |||
ocaml | |||
package | |||
@@ -100,13 +99,12 @@ WAFLIB_STRIP_EXTRAS=" | |||
pep8 | |||
pgicc | |||
pgicxx | |||
prefork | |||
preforkjava | |||
preforkunix | |||
print_commands | |||
proc | |||
protoc | |||
pyqt5 | |||
pytest | |||
qnxnto | |||
qt4 | |||
relocation | |||
remote | |||
resx | |||
@@ -120,18 +118,16 @@ WAFLIB_STRIP_EXTRAS=" | |||
satellite_assembly | |||
scala | |||
slow_qt4 | |||
smart_continue | |||
softlink_libs | |||
stale | |||
stracedeps | |||
swig | |||
syms | |||
sync_exec | |||
ticgt | |||
unc | |||
unity | |||
use_config | |||
valadoc | |||
waf_xattr | |||
why | |||
win32_opts | |||
wix | |||
@@ -12,7 +12,7 @@ def options(opt): | |||
def configure(conf): | |||
conf.env['BUILD_JACKDBUS'] = False | |||
if not conf.check_cfg(package='dbus-1', atleast_version='1.0.0', args='--cflags --libs', mandatory=False): | |||
if not conf.check_cfg(package='dbus-1 >= 1.0.0', args='--cflags --libs', mandatory=False): | |||
print(Logs.colors.RED + 'ERROR !! jackdbus will not be built because libdbus-dev is missing' + Logs.colors.NORMAL) | |||
return | |||
@@ -1,7 +1,7 @@ | |||
#!/usr/bin/env python | |||
# encoding: ISO8859-1 | |||
# Thomas Nagy, 2005-2016 | |||
# encoding: latin-1 | |||
# Thomas Nagy, 2005-2018 | |||
# | |||
""" | |||
Redistribution and use in source and binary forms, with or without | |||
modification, are permitted provided that the following conditions | |||
@@ -32,7 +32,7 @@ POSSIBILITY OF SUCH DAMAGE. | |||
import os, sys, inspect | |||
VERSION="1.8.22" | |||
VERSION="2.0.11" | |||
REVISION="x" | |||
GIT="x" | |||
INSTALL="x" | |||
@@ -1,12 +1,12 @@ | |||
#!/usr/bin/env python | |||
# encoding: utf-8 | |||
# Thomas Nagy, 2005-2010 (ita) | |||
# Thomas Nagy, 2005-2018 (ita) | |||
""" | |||
ConfigSet: a special dict | |||
The values put in :py:class:`ConfigSet` must be lists | |||
The values put in :py:class:`ConfigSet` must be serializable (dicts, lists, strings) | |||
""" | |||
import copy, re, os | |||
@@ -15,7 +15,7 @@ re_imp = re.compile('^(#)*?([^#=]*?)\ =\ (.*?)$', re.M) | |||
class ConfigSet(object): | |||
""" | |||
A dict that honor serialization and parent relationships. The serialization format | |||
A copy-on-write dict with human-readable serialized format. The serialization format | |||
is human-readable (python-like) and performed by using eval() and repr(). | |||
For high performance prefer pickle. Do not store functions as they are not serializable. | |||
@@ -39,17 +39,20 @@ class ConfigSet(object): | |||
def __contains__(self, key): | |||
""" | |||
Enable the *in* syntax:: | |||
Enables the *in* syntax:: | |||
if 'foo' in env: | |||
print(env['foo']) | |||
""" | |||
if key in self.table: return True | |||
try: return self.parent.__contains__(key) | |||
except AttributeError: return False # parent may not exist | |||
if key in self.table: | |||
return True | |||
try: | |||
return self.parent.__contains__(key) | |||
except AttributeError: | |||
return False # parent may not exist | |||
def keys(self): | |||
"""Dict interface (unknown purpose)""" | |||
"""Dict interface""" | |||
keys = set() | |||
cur = self | |||
while cur: | |||
@@ -59,6 +62,9 @@ class ConfigSet(object): | |||
keys.sort() | |||
return keys | |||
def __iter__(self): | |||
return iter(self.keys()) | |||
def __str__(self): | |||
"""Text representation of the ConfigSet (for debugging purposes)""" | |||
return "\n".join(["%r %r" % (x, self.__getitem__(x)) for x in self.keys()]) | |||
@@ -73,7 +79,7 @@ class ConfigSet(object): | |||
""" | |||
try: | |||
while 1: | |||
x = self.table.get(key, None) | |||
x = self.table.get(key) | |||
if not x is None: | |||
return x | |||
self = self.parent | |||
@@ -82,13 +88,13 @@ class ConfigSet(object): | |||
def __setitem__(self, key, value): | |||
""" | |||
Dictionary interface: get value from key | |||
Dictionary interface: set value from key | |||
""" | |||
self.table[key] = value | |||
def __delitem__(self, key): | |||
""" | |||
Dictionary interface: get value from key | |||
Dictionary interface: mark the value as missing | |||
""" | |||
self[key] = [] | |||
@@ -101,7 +107,7 @@ class ConfigSet(object): | |||
conf.env['value'] | |||
""" | |||
if name in self.__slots__: | |||
return object.__getattr__(self, name) | |||
return object.__getattribute__(self, name) | |||
else: | |||
return self[name] | |||
@@ -152,7 +158,7 @@ class ConfigSet(object): | |||
def detach(self): | |||
""" | |||
Detach self from its parent (if existing) | |||
Detaches this instance from its parent (if present) | |||
Modifying the parent :py:class:`ConfigSet` will not change the current object | |||
Modifying this :py:class:`ConfigSet` will not modify the parent one. | |||
@@ -171,18 +177,19 @@ class ConfigSet(object): | |||
def get_flat(self, key): | |||
""" | |||
Return a value as a string. If the input is a list, the value returned is space-separated. | |||
Returns a value as a string. If the input is a list, the value returned is space-separated. | |||
:param key: key to use | |||
:type key: string | |||
""" | |||
s = self[key] | |||
if isinstance(s, str): return s | |||
if isinstance(s, str): | |||
return s | |||
return ' '.join(s) | |||
def _get_list_value_for_modification(self, key): | |||
""" | |||
Return a list value for further modification. | |||
Returns a list value for further modification. | |||
The list may be modified inplace and there is no need to do this afterwards:: | |||
@@ -191,16 +198,20 @@ class ConfigSet(object): | |||
try: | |||
value = self.table[key] | |||
except KeyError: | |||
try: value = self.parent[key] | |||
except AttributeError: value = [] | |||
if isinstance(value, list): | |||
value = value[:] | |||
try: | |||
value = self.parent[key] | |||
except AttributeError: | |||
value = [] | |||
else: | |||
value = [value] | |||
if isinstance(value, list): | |||
# force a copy | |||
value = value[:] | |||
else: | |||
value = [value] | |||
self.table[key] = value | |||
else: | |||
if not isinstance(value, list): | |||
value = [value] | |||
self.table[key] = value | |||
self.table[key] = value = [value] | |||
return value | |||
def append_value(self, var, val): | |||
@@ -232,7 +243,7 @@ class ConfigSet(object): | |||
def append_unique(self, var, val): | |||
""" | |||
Append a value to the specified item only if it's not already present:: | |||
Appends a value to the specified item only if it's not already present:: | |||
def build(bld): | |||
bld.env.append_unique('CFLAGS', ['-O2', '-g']) | |||
@@ -249,7 +260,7 @@ class ConfigSet(object): | |||
def get_merged_dict(self): | |||
""" | |||
Compute the merged dictionary from the fusion of self and all its parent | |||
Computes the merged dictionary from the fusion of self and all its parent | |||
:rtype: a ConfigSet object | |||
""" | |||
@@ -257,8 +268,10 @@ class ConfigSet(object): | |||
env = self | |||
while 1: | |||
table_list.insert(0, env.table) | |||
try: env = env.parent | |||
except AttributeError: break | |||
try: | |||
env = env.parent | |||
except AttributeError: | |||
break | |||
merged_table = {} | |||
for table in table_list: | |||
merged_table.update(table) | |||
@@ -266,7 +279,7 @@ class ConfigSet(object): | |||
def store(self, filename): | |||
""" | |||
Write the :py:class:`ConfigSet` data into a file. See :py:meth:`ConfigSet.load` for reading such files. | |||
Serializes the :py:class:`ConfigSet` data to a file. See :py:meth:`ConfigSet.load` for reading such files. | |||
:param filename: file to use | |||
:type filename: string | |||
@@ -293,7 +306,7 @@ class ConfigSet(object): | |||
def load(self, filename): | |||
""" | |||
Retrieve the :py:class:`ConfigSet` data from a file. See :py:meth:`ConfigSet.store` for writing such files | |||
Restores contents from a file (current values are not cleared). Files are written using :py:meth:`ConfigSet.store`. | |||
:param filename: file to use | |||
:type filename: string | |||
@@ -303,21 +316,20 @@ class ConfigSet(object): | |||
for m in re_imp.finditer(code): | |||
g = m.group | |||
tbl[g(2)] = eval(g(3)) | |||
Logs.debug('env: %s' % str(self.table)) | |||
Logs.debug('env: %s', self.table) | |||
def update(self, d): | |||
""" | |||
Dictionary interface: replace values from another dict | |||
Dictionary interface: replace values with the ones from another dict | |||
:param d: object to use the value from | |||
:type d: dict-like object | |||
""" | |||
for k, v in d.items(): | |||
self[k] = v | |||
self.table.update(d) | |||
def stash(self): | |||
""" | |||
Store the object state, to provide a kind of transaction support:: | |||
Stores the object state to provide transactionality semantics:: | |||
env = ConfigSet() | |||
env.stash() | |||
@@ -1,6 +1,6 @@ | |||
#!/usr/bin/env python | |||
# encoding: utf-8 | |||
# Thomas Nagy, 2005-2010 (ita) | |||
# Thomas Nagy, 2005-2018 (ita) | |||
""" | |||
Configuration system | |||
@@ -12,15 +12,9 @@ A :py:class:`waflib.Configure.ConfigurationContext` instance is created when ``w | |||
* hold configuration routines such as ``find_program``, etc | |||
""" | |||
import os, shlex, sys, time, re, shutil | |||
import os, re, shlex, shutil, sys, time, traceback | |||
from waflib import ConfigSet, Utils, Options, Logs, Context, Build, Errors | |||
BREAK = 'break' | |||
"""In case of a configuration error, break""" | |||
CONTINUE = 'continue' | |||
"""In case of a configuration error, continue""" | |||
WAF_CONFIG_LOG = 'config.log' | |||
"""Name of the configuration log file""" | |||
@@ -157,7 +151,7 @@ class ConfigurationContext(Context.Context): | |||
self.msg('Setting out to', self.bldnode.abspath()) | |||
if id(self.srcnode) == id(self.bldnode): | |||
Logs.warn('Setting top == out (remember to use "update_outputs")') | |||
Logs.warn('Setting top == out') | |||
elif id(self.path) != id(self.srcnode): | |||
if self.srcnode.is_child_of(self.path): | |||
Logs.warn('Are you certain that you do not want to set top="." ?') | |||
@@ -173,8 +167,9 @@ class ConfigurationContext(Context.Context): | |||
# consider the current path as the root directory (see prepare_impl). | |||
# to remove: use 'waf distclean' | |||
env = ConfigSet.ConfigSet() | |||
env['argv'] = sys.argv | |||
env['options'] = Options.options.__dict__ | |||
env.argv = sys.argv | |||
env.options = Options.options.__dict__ | |||
env.config_cmd = self.cmd | |||
env.run_dir = Context.run_dir | |||
env.top_dir = Context.top_dir | |||
@@ -182,15 +177,15 @@ class ConfigurationContext(Context.Context): | |||
# conf.hash & conf.files hold wscript files paths and hash | |||
# (used only by Configure.autoconfig) | |||
env['hash'] = self.hash | |||
env['files'] = self.files | |||
env['environ'] = dict(self.environ) | |||
env.hash = self.hash | |||
env.files = self.files | |||
env.environ = dict(self.environ) | |||
if not self.env.NO_LOCK_IN_RUN and not getattr(Options.options, 'no_lock_in_run'): | |||
if not (self.env.NO_LOCK_IN_RUN or env.environ.get('NO_LOCK_IN_RUN') or getattr(Options.options, 'no_lock_in_run')): | |||
env.store(os.path.join(Context.run_dir, Options.lockfile)) | |||
if not self.env.NO_LOCK_IN_TOP and not getattr(Options.options, 'no_lock_in_top'): | |||
if not (self.env.NO_LOCK_IN_TOP or env.environ.get('NO_LOCK_IN_TOP') or getattr(Options.options, 'no_lock_in_top')): | |||
env.store(os.path.join(Context.top_dir, Options.lockfile)) | |||
if not self.env.NO_LOCK_IN_OUT and not getattr(Options.options, 'no_lock_in_out'): | |||
if not (self.env.NO_LOCK_IN_OUT or env.environ.get('NO_LOCK_IN_OUT') or getattr(Options.options, 'no_lock_in_out')): | |||
env.store(os.path.join(Context.out_dir, Options.lockfile)) | |||
def prepare_env(self, env): | |||
@@ -202,17 +197,17 @@ class ConfigurationContext(Context.Context): | |||
""" | |||
if not env.PREFIX: | |||
if Options.options.prefix or Utils.is_win32: | |||
env.PREFIX = Utils.sane_path(Options.options.prefix) | |||
env.PREFIX = Options.options.prefix | |||
else: | |||
env.PREFIX = '' | |||
env.PREFIX = '/' | |||
if not env.BINDIR: | |||
if Options.options.bindir: | |||
env.BINDIR = Utils.sane_path(Options.options.bindir) | |||
env.BINDIR = Options.options.bindir | |||
else: | |||
env.BINDIR = Utils.subst_vars('${PREFIX}/bin', env) | |||
if not env.LIBDIR: | |||
if Options.options.libdir: | |||
env.LIBDIR = Utils.sane_path(Options.options.libdir) | |||
env.LIBDIR = Options.options.libdir | |||
else: | |||
env.LIBDIR = Utils.subst_vars('${PREFIX}/lib%s' % Utils.lib64(), env) | |||
@@ -228,38 +223,42 @@ class ConfigurationContext(Context.Context): | |||
tmpenv = self.all_envs[key] | |||
tmpenv.store(os.path.join(self.cachedir.abspath(), key + Build.CACHE_SUFFIX)) | |||
def load(self, input, tooldir=None, funs=None, with_sys_path=True): | |||
def load(self, tool_list, tooldir=None, funs=None, with_sys_path=True, cache=False): | |||
""" | |||
Load Waf tools, which will be imported whenever a build is started. | |||
:param input: waf tools to import | |||
:type input: list of string | |||
:param tool_list: waf tools to import | |||
:type tool_list: list of string | |||
:param tooldir: paths for the imports | |||
:type tooldir: list of string | |||
:param funs: functions to execute from the waf tools | |||
:type funs: list of string | |||
:param cache: whether to prevent the tool from running twice | |||
:type cache: bool | |||
""" | |||
tools = Utils.to_list(input) | |||
if tooldir: tooldir = Utils.to_list(tooldir) | |||
tools = Utils.to_list(tool_list) | |||
if tooldir: | |||
tooldir = Utils.to_list(tooldir) | |||
for tool in tools: | |||
# avoid loading the same tool more than once with the same functions | |||
# used by composite projects | |||
mag = (tool, id(self.env), tooldir, funs) | |||
if mag in self.tool_cache: | |||
self.to_log('(tool %s is already loaded, skipping)' % tool) | |||
continue | |||
self.tool_cache.append(mag) | |||
if cache: | |||
mag = (tool, id(self.env), tooldir, funs) | |||
if mag in self.tool_cache: | |||
self.to_log('(tool %s is already loaded, skipping)' % tool) | |||
continue | |||
self.tool_cache.append(mag) | |||
module = None | |||
try: | |||
module = Context.load_tool(tool, tooldir, ctx=self, with_sys_path=with_sys_path) | |||
except ImportError as e: | |||
self.fatal('Could not load the Waf tool %r from %r\n%s' % (tool, sys.path, e)) | |||
self.fatal('Could not load the Waf tool %r from %r\n%s' % (tool, getattr(e, 'waf_sys_path', sys.path), e)) | |||
except Exception as e: | |||
self.to_log('imp %r (%r & %r)' % (tool, tooldir, funs)) | |||
self.to_log(Utils.ex_stack()) | |||
self.to_log(traceback.format_exc()) | |||
raise | |||
if funs is not None: | |||
@@ -267,8 +266,10 @@ class ConfigurationContext(Context.Context): | |||
else: | |||
func = getattr(module, 'configure', None) | |||
if func: | |||
if type(func) is type(Utils.readf): func(self) | |||
else: self.eval_rules(func) | |||
if type(func) is type(Utils.readf): | |||
func(self) | |||
else: | |||
self.eval_rules(func) | |||
self.tools.append({'tool':tool, 'tooldir':tooldir, 'funs':funs}) | |||
@@ -285,8 +286,7 @@ class ConfigurationContext(Context.Context): | |||
def eval_rules(self, rules): | |||
""" | |||
Execute the configuration tests. The method :py:meth:`waflib.Configure.ConfigurationContext.err_handler` | |||
is used to process the eventual exceptions | |||
Execute configuration tests provided as list of functions to run | |||
:param rules: list of configuration method names | |||
:type rules: list of string | |||
@@ -294,28 +294,9 @@ class ConfigurationContext(Context.Context): | |||
self.rules = Utils.to_list(rules) | |||
for x in self.rules: | |||
f = getattr(self, x) | |||
if not f: self.fatal("No such method '%s'." % x) | |||
try: | |||
f() | |||
except Exception as e: | |||
ret = self.err_handler(x, e) | |||
if ret == BREAK: | |||
break | |||
elif ret == CONTINUE: | |||
continue | |||
else: | |||
raise | |||
def err_handler(self, fun, error): | |||
""" | |||
Error handler for the configuration tests, the default is to let the exception raise | |||
:param fun: configuration test | |||
:type fun: method | |||
:param error: exception | |||
:type error: exception | |||
""" | |||
pass | |||
if not f: | |||
self.fatal('No such configuration function %r' % x) | |||
f() | |||
def conf(f): | |||
""" | |||
@@ -330,11 +311,7 @@ def conf(f): | |||
:type f: function | |||
""" | |||
def fun(*k, **kw): | |||
mandatory = True | |||
if 'mandatory' in kw: | |||
mandatory = kw['mandatory'] | |||
del kw['mandatory'] | |||
mandatory = kw.pop('mandatory', True) | |||
try: | |||
return f(*k, **kw) | |||
except Errors.ConfigurationError: | |||
@@ -347,7 +324,7 @@ def conf(f): | |||
return f | |||
@conf | |||
def add_os_flags(self, var, dest=None, dup=True): | |||
def add_os_flags(self, var, dest=None, dup=False): | |||
""" | |||
Import operating system environment values into ``conf.env`` dict:: | |||
@@ -365,7 +342,6 @@ def add_os_flags(self, var, dest=None, dup=True): | |||
flags = shlex.split(self.environ[var]) | |||
except KeyError: | |||
return | |||
# TODO: in waf 1.9, make dup=False the default | |||
if dup or ''.join(flags) not in ''.join(Utils.to_list(self.env[dest or var])): | |||
self.env.append_value(dest or var, flags) | |||
@@ -377,21 +353,26 @@ def cmd_to_list(self, cmd): | |||
:param cmd: command | |||
:type cmd: a string or a list of string | |||
""" | |||
if isinstance(cmd, str) and cmd.find(' '): | |||
try: | |||
os.stat(cmd) | |||
except OSError: | |||
if isinstance(cmd, str): | |||
if os.path.isfile(cmd): | |||
# do not take any risk | |||
return [cmd] | |||
if os.sep == '/': | |||
return shlex.split(cmd) | |||
else: | |||
return [cmd] | |||
try: | |||
return shlex.split(cmd, posix=False) | |||
except TypeError: | |||
# Python 2.5 on windows? | |||
return shlex.split(cmd) | |||
return cmd | |||
@conf | |||
def check_waf_version(self, mini='1.7.99', maxi='1.9.0', **kw): | |||
def check_waf_version(self, mini='1.9.99', maxi='2.1.0', **kw): | |||
""" | |||
Raise a Configuration error if the Waf version does not strictly match the given bounds:: | |||
conf.check_waf_version(mini='1.8.0', maxi='1.9.0') | |||
conf.check_waf_version(mini='1.9.99', maxi='2.1.0') | |||
:type mini: number, tuple or string | |||
:param mini: Minimum required version | |||
@@ -413,7 +394,7 @@ def find_file(self, filename, path_list=[]): | |||
:param filename: name of the file to search for | |||
:param path_list: list of directories to search | |||
:return: the first occurrence filename or '' if filename could not be found | |||
:return: the first matching filename; else a configuration exception is raised | |||
""" | |||
for n in Utils.to_list(filename): | |||
for d in Utils.to_list(path_list): | |||
@@ -433,14 +414,17 @@ def find_program(self, filename, **kw): | |||
:param path_list: paths to use for searching | |||
:type param_list: list of string | |||
:param var: store the result to conf.env[var], by default use filename.upper() | |||
:param var: store the result to conf.env[var] where var defaults to filename.upper() if not provided; the result is stored as a list of strings | |||
:type var: string | |||
:param ext: list of extensions for the binary (do not add an extension for portability) | |||
:type ext: list of string | |||
:param value: obtain the program from the value passed exclusively | |||
:type value: list or string (list is preferred) | |||
:param exts: list of extensions for the binary (do not add an extension for portability) | |||
:type exts: list of string | |||
:param msg: name to display in the log, by default filename is used | |||
:type msg: string | |||
:param interpreter: interpreter for the program | |||
:type interpreter: ConfigSet variable key | |||
:raises: :py:class:`waflib.Errors.ConfigurationError` | |||
""" | |||
exts = kw.get('exts', Utils.is_win32 and '.exe,.com,.bat,.cmd' or ',.sh,.pl,.py') | |||
@@ -462,18 +446,15 @@ def find_program(self, filename, **kw): | |||
else: | |||
path_list = environ.get('PATH', '').split(os.pathsep) | |||
if var in environ: | |||
filename = environ[var] | |||
if os.path.isfile(filename): | |||
# typical CC=/usr/bin/gcc waf configure build | |||
ret = [filename] | |||
else: | |||
# case CC='ccache gcc' waf configure build | |||
ret = self.cmd_to_list(filename) | |||
if kw.get('value'): | |||
# user-provided in command-line options and passed to find_program | |||
ret = self.cmd_to_list(kw['value']) | |||
elif environ.get(var): | |||
# user-provided in the os environment | |||
ret = self.cmd_to_list(environ[var]) | |||
elif self.env[var]: | |||
# set by the user in the wscript file | |||
ret = self.env[var] | |||
ret = self.cmd_to_list(ret) | |||
# a default option in the wscript file | |||
ret = self.cmd_to_list(self.env[var]) | |||
else: | |||
if not ret: | |||
ret = self.find_binary(filename, exts.split(','), path_list) | |||
@@ -483,7 +464,6 @@ def find_program(self, filename, **kw): | |||
ret = Utils.get_registry_app_path(Utils.winreg.HKEY_LOCAL_MACHINE, filename) | |||
ret = self.cmd_to_list(ret) | |||
if ret: | |||
if len(ret) == 1: | |||
retmsg = ret[0] | |||
@@ -492,14 +472,14 @@ def find_program(self, filename, **kw): | |||
else: | |||
retmsg = False | |||
self.msg("Checking for program '%s'" % msg, retmsg, **kw) | |||
if not kw.get('quiet', None): | |||
self.msg('Checking for program %r' % msg, retmsg, **kw) | |||
if not kw.get('quiet'): | |||
self.to_log('find program=%r paths=%r var=%r -> %r' % (filename, path_list, var, ret)) | |||
if not ret: | |||
self.fatal(kw.get('errmsg', '') or 'Could not find the program %r' % filename) | |||
interpreter = kw.get('interpreter', None) | |||
interpreter = kw.get('interpreter') | |||
if interpreter is None: | |||
if not Utils.check_exe(ret[0], env=environ): | |||
self.fatal('Program %r is not executable' % ret) | |||
@@ -554,7 +534,6 @@ def run_build(self, *k, **kw): | |||
$ waf configure --confcache | |||
""" | |||
lst = [str(v) for (p, v) in kw.items() if p != 'env'] | |||
h = Utils.h_list(lst) | |||
dir = self.bldnode.abspath() + os.sep + (not Utils.is_win32 and '.' or '') + 'conf_check_' + Utils.to_hex(h) | |||
@@ -573,9 +552,7 @@ def run_build(self, *k, **kw): | |||
if cachemode == 1: | |||
try: | |||
proj = ConfigSet.ConfigSet(os.path.join(dir, 'cache_run_build')) | |||
except OSError: | |||
pass | |||
except IOError: | |||
except EnvironmentError: | |||
pass | |||
else: | |||
ret = proj['cache_run_build'] | |||
@@ -588,7 +565,8 @@ def run_build(self, *k, **kw): | |||
if not os.path.exists(bdir): | |||
os.makedirs(bdir) | |||
self.test_bld = bld = Build.BuildContext(top_dir=dir, out_dir=bdir) | |||
cls_name = kw.get('run_build_cls') or getattr(self, 'run_build_cls', 'build') | |||
self.test_bld = bld = Context.create_context(cls_name, top_dir=dir, out_dir=bdir) | |||
bld.init_dirs() | |||
bld.progress_bar = 0 | |||
bld.targets = '*' | |||
@@ -597,17 +575,15 @@ def run_build(self, *k, **kw): | |||
bld.all_envs.update(self.all_envs) # not really necessary | |||
bld.env = kw['env'] | |||
# OMG huge hack | |||
bld.kw = kw | |||
bld.conf = self | |||
kw['build_fun'](bld) | |||
ret = -1 | |||
try: | |||
try: | |||
bld.compile() | |||
except Errors.WafError: | |||
ret = 'Test does not build: %s' % Utils.ex_stack() | |||
ret = 'Test does not build: %s' % traceback.format_exc() | |||
self.fatal(ret) | |||
else: | |||
ret = getattr(bld, 'retval', 0) | |||
@@ -619,7 +595,6 @@ def run_build(self, *k, **kw): | |||
proj.store(os.path.join(dir, 'cache_run_build')) | |||
else: | |||
shutil.rmtree(dir) | |||
return ret | |||
@conf | |||
@@ -635,7 +610,7 @@ def test(self, *k, **kw): | |||
kw['env'] = self.env.derive() | |||
# validate_c for example | |||
if kw.get('validate', None): | |||
if kw.get('validate'): | |||
kw['validate'](kw) | |||
self.start_msg(kw['msg'], **kw) | |||
@@ -651,7 +626,7 @@ def test(self, *k, **kw): | |||
else: | |||
kw['success'] = ret | |||
if kw.get('post_check', None): | |||
if kw.get('post_check'): | |||
ret = kw['post_check'](kw) | |||
if ret: | |||
@@ -661,5 +636,3 @@ def test(self, *k, **kw): | |||
self.end_msg(self.ret_msg(kw['okmsg'], kw), **kw) | |||
return ret | |||
@@ -1,9 +1,9 @@ | |||
#!/usr/bin/env python | |||
# encoding: utf-8 | |||
# Thomas Nagy, 2010-2016 (ita) | |||
# Thomas Nagy, 2010-2018 (ita) | |||
""" | |||
Classes and functions required for waf commands | |||
Classes and functions enabling the command system | |||
""" | |||
import os, re, imp, sys | |||
@@ -11,16 +11,16 @@ from waflib import Utils, Errors, Logs | |||
import waflib.Node | |||
# the following 3 constants are updated on each new release (do not touch) | |||
HEXVERSION=0x1081600 | |||
HEXVERSION=0x2000b00 | |||
"""Constant updated on new releases""" | |||
WAFVERSION="1.8.22" | |||
WAFVERSION="2.0.11" | |||
"""Constant updated on new releases""" | |||
WAFREVISION="17d4d4faa52c454eb3580e482df69b2a80e19fa7" | |||
WAFREVISION="a97f6fb0941091b4966b625f15ec32fa783a8bec" | |||
"""Git revision when the waf version is updated""" | |||
ABI = 98 | |||
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) | |||
@@ -41,7 +41,6 @@ OUT = 'out' | |||
WSCRIPT_FILE = 'wscript' | |||
"""Name of the waf script files""" | |||
launch_dir = '' | |||
"""Directory from which waf has been called""" | |||
run_dir = '' | |||
@@ -53,23 +52,12 @@ out_dir = '' | |||
waf_dir = '' | |||
"""Directory containing the waf modules""" | |||
local_repo = '' | |||
"""Local repository containing additional Waf tools (plugins)""" | |||
remote_repo = 'https://raw.githubusercontent.com/waf-project/waf/master/' | |||
""" | |||
Remote directory containing downloadable waf tools. The missing tools can be downloaded by using:: | |||
$ waf configure --download | |||
""" | |||
remote_locs = ['waflib/extras', 'waflib/Tools'] | |||
""" | |||
Remote directories for use with :py:const:`waflib.Context.remote_repo` | |||
""" | |||
default_encoding = Utils.console_encoding() | |||
"""Encoding to use when reading outputs from other processes""" | |||
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 | |||
@@ -82,20 +70,20 @@ List of :py:class:`waflib.Context.Context` subclasses that can be used as waf co | |||
are added automatically by a metaclass. | |||
""" | |||
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` | |||
:param cmd_name: 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` | |||
""" | |||
global classes | |||
for x in classes: | |||
if x.cmd == cmd_name: | |||
return x(*k, **kw) | |||
@@ -105,14 +93,15 @@ def create_context(cmd_name, *k, **kw): | |||
class store_context(type): | |||
""" | |||
Metaclass for storing the command classes into the list :py:const:`waflib.Context.classes` | |||
Context classes must provide an attribute 'cmd' representing the command to execute | |||
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, dict): | |||
super(store_context, cls).__init__(name, bases, dict) | |||
def __init__(cls, name, bases, dct): | |||
super(store_context, cls).__init__(name, bases, dct) | |||
name = cls.__name__ | |||
if name == 'ctx' or name == 'Context': | |||
if name in ('ctx', 'Context'): | |||
return | |||
try: | |||
@@ -123,11 +112,10 @@ class store_context(type): | |||
if not getattr(cls, 'fun', None): | |||
cls.fun = cls.cmd | |||
global classes | |||
classes.insert(0, cls) | |||
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): | |||
""" | |||
@@ -138,7 +126,7 @@ class Context(ctx): | |||
def foo(ctx): | |||
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`` | |||
:type cmd: string | |||
@@ -156,19 +144,18 @@ class Context(ctx): | |||
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): | |||
try: | |||
rd = kw['run_dir'] | |||
except KeyError: | |||
global run_dir | |||
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 = type('Nod3', (waflib.Node.Node,), {}) | |||
self.node_class.__module__ = 'waflib.Node' | |||
self.node_class.ctx = self | |||
self.root = self.node_class('', None) | |||
@@ -179,18 +166,9 @@ class Context(ctx): | |||
self.exec_dict = {'ctx':self, 'conf':self, 'bld':self, 'opt':self} | |||
self.logger = None | |||
def __hash__(self): | |||
""" | |||
Return a hash value for storing context objects in dicts or sets. The value is not persistent. | |||
:return: hash value | |||
:rtype: int | |||
""" | |||
return id(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: | |||
logger = self.logger | |||
@@ -202,11 +180,11 @@ class Context(ctx): | |||
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. | |||
A ``tooldir`` value may be provided as a list of module paths. | |||
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 | |||
:param tool_list: list of Waf tools to use | |||
""" | |||
tools = Utils.to_list(tool_list) | |||
path = Utils.to_list(kw.get('tooldir', '')) | |||
@@ -220,15 +198,16 @@ class Context(ctx): | |||
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 | |||
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 node given is set | |||
as an attribute ``self.cur_script``, and as the current path ``self.path`` | |||
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` | |||
@@ -240,7 +219,7 @@ class Context(ctx): | |||
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 | |||
:type node: :py:class:`waflib.Node.Node` | |||
@@ -251,10 +230,13 @@ class Context(ctx): | |||
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 | |||
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. | |||
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 | |||
@@ -300,7 +282,7 @@ class Context(ctx): | |||
if not user_function: | |||
if not mandatory: | |||
continue | |||
raise Errors.WafError('No function %s defined in %s' % (name or self.fun, node.abspath())) | |||
raise Errors.WafError('No function %r defined in %s' % (name or self.fun, node.abspath())) | |||
user_function(self) | |||
finally: | |||
self.post_recurse(node) | |||
@@ -313,25 +295,39 @@ class Context(ctx): | |||
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): | |||
""" | |||
Execute a command and return the exit status. If the context has the attribute 'log', | |||
capture and log the process stderr/stdout for logging purposes:: | |||
Runs an external process and returns the exit status:: | |||
def run(tsk): | |||
ret = tsk.generator.bld.exec_command('touch foo.txt') | |||
return ret | |||
This method captures the standard/error outputs (Issue 1101), but it does not return the values | |||
unlike :py:meth:`waflib.Context.Context.cmd_and_log` | |||
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) | |||
Logs.debug('runner: %r' % (cmd,)) | |||
Logs.debug('runner_env: kw=%s' % kw) | |||
self.log_command(cmd, kw) | |||
if self.logger: | |||
self.logger.info(cmd) | |||
@@ -342,40 +338,42 @@ class Context(ctx): | |||
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]) | |||
raise Errors.WafError('Program %s not found!' % cmd[0]) | |||
wargs = {} | |||
cargs = {} | |||
if 'timeout' in kw: | |||
if kw['timeout'] is not None: | |||
wargs['timeout'] = kw['timeout'] | |||
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']: | |||
wargs['input'] = 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: | |||
if kw['stdout'] or kw['stderr']: | |||
p = subprocess.Popen(cmd, **kw) | |||
(out, err) = p.communicate(**wargs) | |||
ret = p.returncode | |||
else: | |||
out, err = (None, None) | |||
ret = subprocess.Popen(cmd, **kw).wait(**wargs) | |||
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(sys.stdout.encoding or 'iso8859-1') | |||
out = out.decode(encoding, errors='replace') | |||
if self.logger: | |||
self.logger.debug('out: %s' % out) | |||
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(sys.stdout.encoding or 'iso8859-1') | |||
err = err.decode(encoding, errors='replace') | |||
if self.logger: | |||
self.logger.error('err: %s' % err) | |||
else: | |||
@@ -385,9 +383,9 @@ class Context(ctx): | |||
def cmd_and_log(self, cmd, **kw): | |||
""" | |||
Execute a command and return stdout/stderr if the execution is successful. | |||
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:: | |||
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) | |||
@@ -395,65 +393,69 @@ class Context(ctx): | |||
(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 Exception as e: | |||
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) | |||
Logs.debug('runner: %r' % (cmd,)) | |||
if 'quiet' in kw: | |||
quiet = kw['quiet'] | |||
del kw['quiet'] | |||
else: | |||
quiet = None | |||
self.log_command(cmd, kw) | |||
if 'output' in kw: | |||
to_ret = kw['output'] | |||
del kw['output'] | |||
else: | |||
to_ret = STDOUT | |||
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 %s not found!" % 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) | |||
wargs = {} | |||
cargs = {} | |||
if 'timeout' in kw: | |||
if kw['timeout'] is not None: | |||
wargs['timeout'] = kw['timeout'] | |||
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']: | |||
wargs['input'] = 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: | |||
p = subprocess.Popen(cmd, **kw) | |||
(out, err) = p.communicate(**wargs) | |||
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(sys.stdout.encoding or 'iso8859-1') | |||
out = out.decode(encoding, errors='replace') | |||
if not isinstance(err, str): | |||
err = err.decode(sys.stdout.encoding or 'iso8859-1') | |||
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 p.returncode: | |||
e = Errors.WafError('Command %r returned %r' % (cmd, p.returncode)) | |||
e.returncode = p.returncode | |||
if ret: | |||
e = Errors.WafError('Command %r returned %r' % (cmd, ret)) | |||
e.returncode = ret | |||
e.stderr = err | |||
e.stdout = out | |||
raise e | |||
@@ -466,7 +468,8 @@ class Context(ctx): | |||
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): | |||
conf.fatal('a requirement is missing') | |||
@@ -475,24 +478,31 @@ class Context(ctx): | |||
: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: | |||
msg = '%s\n(complete log in %s)' % (msg, self.logger.handlers[0].baseFilename) | |||
except Exception: | |||
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): | |||
""" | |||
Log some information to the logger (if present), or to stderr. If the message is empty, | |||
it is not printed:: | |||
Logs information to the logger (if present), or to stderr. | |||
Empty messages are not printed:: | |||
def build(bld): | |||
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 method if necessary. | |||
:param msg: message | |||
:type msg: string | |||
@@ -508,7 +518,7 @@ class Context(ctx): | |||
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 | |||
can be disabled easly by setting ``in_msg`` to a positive value:: | |||
@@ -536,7 +546,7 @@ class Context(ctx): | |||
except KeyError: | |||
result = k[1] | |||
color = kw.get('color', None) | |||
color = kw.get('color') | |||
if not isinstance(color, str): | |||
color = result and 'GREEN' or 'YELLOW' | |||
@@ -544,12 +554,12 @@ class Context(ctx): | |||
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', None): | |||
if kw.get('quiet'): | |||
return | |||
msg = kw.get('msg', None) or k[0] | |||
msg = kw.get('msg') or k[0] | |||
try: | |||
if self.in_msg: | |||
self.in_msg += 1 | |||
@@ -567,19 +577,19 @@ class Context(ctx): | |||
Logs.pprint('NORMAL', "%s :" % msg.ljust(self.line_just), sep='') | |||
def end_msg(self, *k, **kw): | |||
"""Print the end of a 'Checking for' message. See :py:meth:`waflib.Context.Context.msg`""" | |||
if kw.get('quiet', None): | |||
"""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', None) or k[0] | |||
result = kw.get('result') or k[0] | |||
defcolor = 'GREEN' | |||
if result == True: | |||
if result is True: | |||
msg = 'ok' | |||
elif result == False: | |||
elif not result: | |||
msg = 'not found' | |||
defcolor = 'YELLOW' | |||
else: | |||
@@ -597,7 +607,17 @@ class Context(ctx): | |||
Logs.pprint(color, msg) | |||
def load_special_tools(self, var, ban=[]): | |||
global waf_dir | |||
""" | |||
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: | |||
@@ -608,12 +628,12 @@ class Context(ctx): | |||
waflibs = PyZipFile(waf_dir) | |||
lst = waflibs.namelist() | |||
for x in lst: | |||
if not re.match("waflib/extras/%s" % var.replace("*", ".*"), var): | |||
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("*", ".*") | |||
r = b.replace('*', '.*') | |||
if re.match(r, f): | |||
doban = True | |||
if not doban: | |||
@@ -622,13 +642,13 @@ class Context(ctx): | |||
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` | |||
""" | |||
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 | |||
:type path: string | |||
@@ -648,17 +668,17 @@ def load_module(path, encoding=None): | |||
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) | |||
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): | |||
""" | |||
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 | |||
:param tool: Name of the tool | |||
@@ -672,14 +692,18 @@ def load_tool(tool, tooldir=None, ctx=None, with_sys_path=True): | |||
else: | |||
tool = tool.replace('++', 'xx') | |||
origSysPath = sys.path | |||
if not with_sys_path: sys.path = [] | |||
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) | |||
@@ -687,7 +711,8 @@ def load_tool(tool, tooldir=None, ctx=None, with_sys_path=True): | |||
Context.tools[tool] = ret | |||
return ret | |||
else: | |||
if not with_sys_path: sys.path.insert(0, waf_dir) | |||
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: | |||
@@ -695,13 +720,18 @@ def load_tool(tool, tooldir=None, ctx=None, with_sys_path=True): | |||
break | |||
except ImportError: | |||
x = None | |||
if x is None: # raise an exception | |||
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) | |||
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 += origSysPath | |||
if not with_sys_path: | |||
sys.path += back_path | |||
@@ -1,6 +1,6 @@ | |||
#!/usr/bin/env python | |||
# encoding: utf-8 | |||
# Thomas Nagy, 2010 (ita) | |||
# Thomas Nagy, 2010-2018 (ita) | |||
""" | |||
Exceptions used in the Waf code | |||
@@ -17,6 +17,7 @@ class WafError(Exception): | |||
:param ex: exception causing this error (optional) | |||
:type ex: exception | |||
""" | |||
Exception.__init__(self) | |||
self.msg = msg | |||
assert not isinstance(msg, Exception) | |||
@@ -35,9 +36,7 @@ class WafError(Exception): | |||
return str(self.msg) | |||
class BuildError(WafError): | |||
""" | |||
Errors raised during the build and install phases | |||
""" | |||
"""Error raised during the build and install phases""" | |||
def __init__(self, error_tasks=[]): | |||
""" | |||
:param error_tasks: tasks that could not complete normally | |||
@@ -47,24 +46,23 @@ class BuildError(WafError): | |||
WafError.__init__(self, self.format_error()) | |||
def format_error(self): | |||
"""format the error messages from the tasks that failed""" | |||
"""Formats the error messages from the tasks that failed""" | |||
lst = ['Build failed'] | |||
for tsk in self.tasks: | |||
txt = tsk.format_error() | |||
if txt: lst.append(txt) | |||
if txt: | |||
lst.append(txt) | |||
return '\n'.join(lst) | |||
class ConfigurationError(WafError): | |||
""" | |||
Configuration exception raised in particular by :py:meth:`waflib.Context.Context.fatal` | |||
""" | |||
"""Configuration exception raised in particular by :py:meth:`waflib.Context.Context.fatal`""" | |||
pass | |||
class TaskRescan(WafError): | |||
"""task-specific exception type, trigger a signature recomputation""" | |||
"""Task-specific exception type signalling required signature recalculations""" | |||
pass | |||
class TaskNotReady(WafError): | |||
"""task-specific exception type, raised when the task signature cannot be computed""" | |||
"""Task-specific exception type signalling that task signatures cannot be computed""" | |||
pass | |||
@@ -1,6 +1,6 @@ | |||
#!/usr/bin/env python | |||
# encoding: utf-8 | |||
# Thomas Nagy, 2005-2010 (ita) | |||
# Thomas Nagy, 2005-2018 (ita) | |||
""" | |||
logging, colors, terminal width and pretty-print | |||
@@ -23,8 +23,15 @@ import logging | |||
LOG_FORMAT = os.environ.get('WAF_LOG_FORMAT', '%(asctime)s %(c1)s%(zone)s%(c2)s %(message)s') | |||
HOUR_FORMAT = os.environ.get('WAF_HOUR_FORMAT', '%H:%M:%S') | |||
zones = '' | |||
zones = [] | |||
""" | |||
See :py:class:`waflib.Logs.log_filter` | |||
""" | |||
verbose = 0 | |||
""" | |||
Global verbosity level, see :py:func:`waflib.Logs.debug` and :py:func:`waflib.Logs.error` | |||
""" | |||
colors_lst = { | |||
'USE' : True, | |||
@@ -49,6 +56,15 @@ except NameError: | |||
unicode = None | |||
def enable_colors(use): | |||
""" | |||
If *1* is given, then the system will perform a few verifications | |||
before enabling colors, such as checking whether the interpreter | |||
is running in a terminal. A value of zero will disable colors, | |||
and a value above *1* will force colors. | |||
:param use: whether to enable colors or not | |||
:type use: integer | |||
""" | |||
if use == 1: | |||
if not (sys.stderr.isatty() or sys.stdout.isatty()): | |||
use = 0 | |||
@@ -74,15 +90,23 @@ except AttributeError: | |||
return 80 | |||
get_term_cols.__doc__ = """ | |||
Get the console width in characters. | |||
Returns the console width in characters. | |||
:return: the number of characters per line | |||
:rtype: int | |||
""" | |||
def get_color(cl): | |||
if not colors_lst['USE']: return '' | |||
return colors_lst.get(cl, '') | |||
""" | |||
Returns the ansi sequence corresponding to the given color name. | |||
An empty string is returned when coloring is globally disabled. | |||
:param cl: color name in capital letters | |||
:type cl: string | |||
""" | |||
if colors_lst['USE']: | |||
return colors_lst.get(cl, '') | |||
return '' | |||
class color_dict(object): | |||
"""attribute-based color access, eg: colors.PINK""" | |||
@@ -96,7 +120,7 @@ colors = color_dict() | |||
re_log = re.compile(r'(\w+): (.*)', re.M) | |||
class log_filter(logging.Filter): | |||
""" | |||
The waf logs are of the form 'name: message', and can be filtered by 'waf --zones=name'. | |||
Waf logs are of the form 'name: message', and can be filtered by 'waf --zones=name'. | |||
For example, the following:: | |||
from waflib import Logs | |||
@@ -106,17 +130,14 @@ class log_filter(logging.Filter): | |||
$ waf --zones=test | |||
""" | |||
def __init__(self, name=None): | |||
pass | |||
def __init__(self, name=''): | |||
logging.Filter.__init__(self, name) | |||
def filter(self, rec): | |||
""" | |||
filter a record, adding the colors automatically | |||
Filters log records by zone and by logging level | |||
* error: red | |||
* warning: yellow | |||
:param rec: message to record | |||
:param rec: log entry | |||
""" | |||
rec.zone = rec.module | |||
if rec.levelno >= logging.INFO: | |||
@@ -136,6 +157,9 @@ class log_filter(logging.Filter): | |||
class log_handler(logging.StreamHandler): | |||
"""Dispatches messages to stderr/stdout depending on the severity level""" | |||
def emit(self, record): | |||
""" | |||
Delegates the functionality to :py:meth:`waflib.Log.log_handler.emit_override` | |||
""" | |||
# default implementation | |||
try: | |||
try: | |||
@@ -153,6 +177,9 @@ class log_handler(logging.StreamHandler): | |||
self.handleError(record) | |||
def emit_override(self, record, **kw): | |||
""" | |||
Writes the log record to the desired stream (stderr/stdout) | |||
""" | |||
self.terminator = getattr(record, 'terminator', '\n') | |||
stream = self.stream | |||
if unicode: | |||
@@ -179,7 +206,10 @@ class formatter(logging.Formatter): | |||
logging.Formatter.__init__(self, LOG_FORMAT, HOUR_FORMAT) | |||
def format(self, rec): | |||
"""Messages in warning, error or info mode are displayed in color by default""" | |||
""" | |||
Formats records and adds colors as needed. The records do not get | |||
a leading hour format if the logging level is above *INFO*. | |||
""" | |||
try: | |||
msg = rec.msg.decode('utf-8') | |||
except Exception: | |||
@@ -204,7 +234,10 @@ class formatter(logging.Formatter): | |||
# and other terminal commands | |||
msg = re.sub(r'\r(?!\n)|\x1B\[(K|.*?(m|h|l))', '', msg) | |||
if rec.levelno >= logging.INFO: # ?? | |||
if rec.levelno >= logging.INFO: | |||
# the goal of this is to format without the leading "Logs, hour" prefix | |||
if rec.args: | |||
return msg % rec.args | |||
return msg | |||
rec.msg = msg | |||
@@ -217,19 +250,17 @@ log = None | |||
def debug(*k, **kw): | |||
""" | |||
Wrap logging.debug, the output is filtered for performance reasons | |||
Wraps logging.debug and discards messages if the verbosity level :py:attr:`waflib.Logs.verbose` โค 0 | |||
""" | |||
if verbose: | |||
k = list(k) | |||
k[0] = k[0].replace('\n', ' ') | |||
global log | |||
log.debug(*k, **kw) | |||
def error(*k, **kw): | |||
""" | |||
Wrap logging.errors, display the origin of the message when '-vv' is set | |||
Wrap logging.errors, adds the stack trace when the verbosity level :py:attr:`waflib.Logs.verbose` โฅ 2 | |||
""" | |||
global log | |||
log.error(*k, **kw) | |||
if verbose > 2: | |||
st = traceback.extract_stack() | |||
@@ -237,28 +268,27 @@ def error(*k, **kw): | |||
st = st[:-1] | |||
buf = [] | |||
for filename, lineno, name, line in st: | |||
buf.append(' File "%s", line %d, in %s' % (filename, lineno, name)) | |||
buf.append(' File %r, line %d, in %s' % (filename, lineno, name)) | |||
if line: | |||
buf.append(' %s' % line.strip()) | |||
if buf: log.error("\n".join(buf)) | |||
if buf: | |||
log.error('\n'.join(buf)) | |||
def warn(*k, **kw): | |||
""" | |||
Wrap logging.warn | |||
Wraps logging.warn | |||
""" | |||
global log | |||
log.warn(*k, **kw) | |||
def info(*k, **kw): | |||
""" | |||
Wrap logging.info | |||
Wraps logging.info | |||
""" | |||
global log | |||
log.info(*k, **kw) | |||
def init_log(): | |||
""" | |||
Initialize the loggers globally | |||
Initializes the logger :py:attr:`waflib.Logs.log` | |||
""" | |||
global log | |||
log = logging.getLogger('waflib') | |||
@@ -272,7 +302,7 @@ def init_log(): | |||
def make_logger(path, name): | |||
""" | |||
Create a simple logger, which is often used to redirect the context command output:: | |||
Creates a simple logger, which is often used to redirect the context command output:: | |||
from waflib import Logs | |||
bld.logger = Logs.make_logger('test.log', 'build') | |||
@@ -292,7 +322,11 @@ def make_logger(path, name): | |||
:type name: string | |||
""" | |||
logger = logging.getLogger(name) | |||
hdlr = logging.FileHandler(path, 'w') | |||
if sys.hexversion > 0x3000000: | |||
encoding = sys.stdout.encoding | |||
else: | |||
encoding = None | |||
hdlr = logging.FileHandler(path, 'w', encoding=encoding) | |||
formatter = logging.Formatter('%(message)s') | |||
hdlr.setFormatter(formatter) | |||
logger.addHandler(hdlr) | |||
@@ -301,7 +335,7 @@ def make_logger(path, name): | |||
def make_mem_logger(name, to_log, size=8192): | |||
""" | |||
Create a memory logger to avoid writing concurrently to the main logger | |||
Creates a memory logger to avoid writing concurrently to the main logger | |||
""" | |||
from logging.handlers import MemoryHandler | |||
logger = logging.getLogger(name) | |||
@@ -315,7 +349,7 @@ def make_mem_logger(name, to_log, size=8192): | |||
def free_logger(logger): | |||
""" | |||
Free the resources held by the loggers created through make_logger or make_mem_logger. | |||
Frees the resources held by the loggers created through make_logger or make_mem_logger. | |||
This is used for file cleanup and for handler removal (logger objects are re-used). | |||
""" | |||
try: | |||
@@ -327,7 +361,7 @@ def free_logger(logger): | |||
def pprint(col, msg, label='', sep='\n'): | |||
""" | |||
Print messages in color immediately on stderr:: | |||
Prints messages in color immediately on stderr:: | |||
from waflib import Logs | |||
Logs.pprint('RED', 'Something bad just happened') | |||
@@ -341,5 +375,5 @@ def pprint(col, msg, label='', sep='\n'): | |||
:param sep: a string to append at the end (line separator) | |||
:type sep: string | |||
""" | |||
info("%s%s%s %s" % (colors(col), msg, colors.NORMAL, label), extra={'terminator':sep}) | |||
info('%s%s%s %s', colors(col), msg, colors.NORMAL, label, extra={'terminator':sep}) | |||
@@ -1,66 +1,75 @@ | |||
#!/usr/bin/env python | |||
# encoding: utf-8 | |||
# Scott Newton, 2005 (scottn) | |||
# Thomas Nagy, 2006-2010 (ita) | |||
# Thomas Nagy, 2006-2018 (ita) | |||
""" | |||
Support for waf command-line options | |||
Provides default command-line options, | |||
as well as custom ones, used by the ``options`` wscript function. | |||
Provides default and command-line options, as well the command | |||
that reads the ``options`` wscript function. | |||
""" | |||
import os, tempfile, optparse, sys, re | |||
from waflib import Logs, Utils, Context | |||
from waflib import Logs, Utils, Context, Errors | |||
cmds = 'distclean configure build install clean uninstall check dist distcheck'.split() | |||
options = optparse.Values() | |||
""" | |||
Constant representing the default waf commands displayed in:: | |||
$ waf --help | |||
""" | |||
options = {} | |||
""" | |||
A dictionary representing the command-line options:: | |||
A global dictionary representing user-provided command-line options:: | |||
$ waf --foo=bar | |||
""" | |||
commands = [] | |||
""" | |||
List of commands to execute extracted from the command-line. This list is consumed during the execution, see :py:func:`waflib.Scripting.run_commands`. | |||
List of commands to execute extracted from the command-line. This list | |||
is consumed during the execution by :py:func:`waflib.Scripting.run_commands`. | |||
""" | |||
envvars = [] | |||
""" | |||
List of environment variable declarations placed after the Waf executable name. | |||
These are detected by searching for "=" in the rest arguments. | |||
These are detected by searching for "=" in the remaining arguments. | |||
You probably do not want to use this. | |||
""" | |||
lockfile = os.environ.get('WAFLOCK', '.lock-waf_%s_build' % sys.platform) | |||
platform = Utils.unversioned_sys_platform() | |||
""" | |||
Name of the lock file that marks a project as configured | |||
""" | |||
class opt_parser(optparse.OptionParser): | |||
""" | |||
Command-line options parser. | |||
""" | |||
def __init__(self, ctx): | |||
optparse.OptionParser.__init__(self, conflict_handler="resolve", version='waf %s (%s)' % (Context.WAFVERSION, Context.WAFREVISION)) | |||
def __init__(self, ctx, allow_unknown=False): | |||
optparse.OptionParser.__init__(self, conflict_handler='resolve', add_help_option=False, | |||
version='waf %s (%s)' % (Context.WAFVERSION, Context.WAFREVISION)) | |||
self.formatter.width = Logs.get_term_cols() | |||
self.ctx = ctx | |||
self.allow_unknown = allow_unknown | |||
def _process_args(self, largs, rargs, values): | |||
""" | |||
Custom _process_args to allow unknown options according to the allow_unknown status | |||
""" | |||
while rargs: | |||
try: | |||
optparse.OptionParser._process_args(self,largs,rargs,values) | |||
except (optparse.BadOptionError, optparse.AmbiguousOptionError) as e: | |||
if self.allow_unknown: | |||
largs.append(e.opt_str) | |||
else: | |||
self.error(str(e)) | |||
def print_usage(self, file=None): | |||
return self.print_help(file) | |||
def get_usage(self): | |||
""" | |||
Return the message to print on ``waf --help`` | |||
Builds the message to print on ``waf --help`` | |||
:rtype: string | |||
""" | |||
cmds_str = {} | |||
for cls in Context.classes: | |||
@@ -96,10 +105,9 @@ Main commands (example: ./waf build -j4) | |||
class OptionsContext(Context.Context): | |||
""" | |||
Collect custom options from wscript files and parses the command line. | |||
Set the global :py:const:`waflib.Options.commands` and :py:const:`waflib.Options.options` values. | |||
Collects custom options from wscript files and parses the command line. | |||
Sets the global :py:const:`waflib.Options.commands` and :py:const:`waflib.Options.options` values. | |||
""" | |||
cmd = 'options' | |||
fun = 'options' | |||
@@ -114,11 +122,18 @@ class OptionsContext(Context.Context): | |||