Browse Source

Update to waf 2.0.11

- Migrate pkg-config checks from atleast_version.
- Check ppoll with a fragment, since the function_name argument has been
  removed.
tags/v1.9.13
Karl Linden 3 years ago
parent
commit
630c6145b8
No known key found for this signature in database GPG Key ID: C0F669D8CE2576AB
53 changed files with 5487 additions and 4479 deletions
  1. +12
    -16
      .wafupdaterc
  2. +1
    -1
      dbus/wscript
  3. +4
    -4
      waf
  4. +635
    -492
      waflib/Build.py
  5. +46
    -34
      waflib/ConfigSet.py
  6. +78
    -105
      waflib/Configure.py
  7. +169
    -139
      waflib/Context.py
  8. +9
    -11
      waflib/Errors.py
  9. +66
    -32
      waflib/Logs.py
  10. +376
    -254
      waflib/Node.py
  11. +116
    -47
      waflib/Options.py
  12. +407
    -169
      waflib/Runner.py
  13. +190
    -212
      waflib/Scripting.py
  14. +653
    -488
      waflib/Task.py
  15. +205
    -148
      waflib/TaskGen.py
  16. +1
    -1
      waflib/Tools/__init__.py
  17. +2
    -2
      waflib/Tools/ar.py
  18. +7
    -7
      waflib/Tools/c.py
  19. +21
    -12
      waflib/Tools/c_aliases.py
  20. +366
    -281
      waflib/Tools/c_config.py
  21. +18
    -36
      waflib/Tools/c_osx.py
  22. +260
    -210
      waflib/Tools/c_preproc.py
  23. +13
    -12
      waflib/Tools/c_tests.py
  24. +97
    -43
      waflib/Tools/ccroot.py
  25. +1
    -1
      waflib/Tools/clang.py
  26. +2
    -2
      waflib/Tools/clangxx.py
  27. +15
    -9
      waflib/Tools/compiler_c.py
  28. +14
    -8
      waflib/Tools/compiler_cxx.py
  29. +7
    -7
      waflib/Tools/cxx.py
  30. +51
    -37
      waflib/Tools/errcheck.py
  31. +55
    -58
      waflib/Tools/gcc.py
  32. +56
    -58
      waflib/Tools/gxx.py
  33. +3
    -6
      waflib/Tools/icc.py
  34. +3
    -6
      waflib/Tools/icpc.py
  35. +30
    -24
      waflib/Tools/irixcc.py
  36. +357
    -512
      waflib/Tools/msvc.py
  37. +27
    -29
      waflib/Tools/suncc.py
  38. +26
    -27
      waflib/Tools/suncxx.py
  39. +152
    -63
      waflib/Tools/waf_unit_test.py
  40. +26
    -28
      waflib/Tools/xlc.py
  41. +26
    -28
      waflib/Tools/xlcxx.py
  42. +448
    -204
      waflib/Utils.py
  43. +1
    -1
      waflib/__init__.py
  44. +3
    -3
      waflib/ansiterm.py
  45. +18
    -14
      waflib/extras/batched_cc.py
  46. +7
    -11
      waflib/extras/build_file_tracker.py
  47. +3
    -4
      waflib/extras/build_logs.py
  48. +36
    -33
      waflib/extras/c_nec.py
  49. +0
    -312
      waflib/extras/xcode.py
  50. +268
    -197
      waflib/extras/xcode6.py
  51. +13
    -21
      waflib/fixpy2.py
  52. +64
    -0
      waflib/processor.py
  53. +23
    -20
      wscript

+ 12
- 16
.wafupdaterc View File

@@ -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


+ 1
- 1
dbus/wscript View File

@@ -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



+ 4
- 4
waf View File

@@ -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"


+ 635
- 492
waflib/Build.py
File diff suppressed because it is too large
View File


+ 46
- 34
waflib/ConfigSet.py View File

@@ -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()


+ 78
- 105
waflib/Configure.py View File

@@ -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




+ 169
- 139
waflib/Context.py View File

@@ -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


+ 9
- 11
waflib/Errors.py View File

@@ -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


+ 66
- 32
waflib/Logs.py View File

@@ -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})


+ 376
- 254
waflib/Node.py
File diff suppressed because it is too large
View File


+ 116
- 47
waflib/Options.py View File

@@ -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):