blob: b662894a05eeb3d675959c00a129b2321ac6b98c [file] [log] [blame]
From 553ca1e649293ef87e96dd3e7621fd87e0b59986 Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@tiptoe.de>
Date: Tue, 29 Aug 2023 12:41:46 +0200
Subject: [PATCH] Update to waf 2.0.26
This makes waf compatible with Python 3.12 again.
Also, apply modifications needed for MacOS and add as a patch file (see
commits 0f2e3b2 and dc6c995).
Signed-off-by: Nils Philippsen <nils@tiptoe.de>
Upstream: https://github.com/jackaudio/jack2/commit/250420381b1a6974798939ad7104ab1a4b9a9994
Signed-off-by: Fabrice Fontaine <fontaine.fabrice@gmail.com>
---
waf | 7 +-
waflib-macos-mods.patch | 18 +
waflib/Build.py | 47 +-
waflib/ConfigSet.py | 2 +-
waflib/Configure.py | 46 +-
waflib/Context.py | 24 +-
waflib/Logs.py | 9 +-
waflib/Node.py | 3 +-
waflib/Options.py | 31 +-
waflib/Runner.py | 27 +-
waflib/Scripting.py | 27 +-
waflib/Task.py | 48 ++-
waflib/TaskGen.py | 16 +-
waflib/Tools/c_aliases.py | 10 +-
waflib/Tools/c_config.py | 37 +-
waflib/Tools/c_preproc.py | 6 +-
waflib/Tools/c_tests.py | 18 +-
waflib/Tools/ccroot.py | 20 +-
waflib/Tools/compiler_c.py | 25 +-
waflib/Tools/compiler_cxx.py | 25 +-
waflib/Tools/irixcc.py | 14 +-
waflib/Tools/msvc.py | 45 +-
waflib/Tools/waf_unit_test.py | 14 +-
waflib/Utils.py | 60 ++-
waflib/ansiterm.py | 2 +-
waflib/extras/clang_cross.py | 92 ++++
waflib/extras/clang_cross_common.py | 113 +++++
waflib/extras/clangxx_cross.py | 106 +++++
waflib/extras/classic_runner.py | 68 +++
waflib/extras/color_msvc.py | 59 +++
waflib/extras/fc_fujitsu.py | 52 +++
waflib/extras/fc_nfort.py | 52 +++
waflib/extras/genpybind.py | 194 +++++++++
waflib/extras/haxe.py | 154 +++++++
waflib/extras/msvc_pdb.py | 46 ++
waflib/extras/sphinx.py | 120 ++++++
waflib/extras/wafcache.py | 648 ++++++++++++++++++++++++++++
waflib/extras/xcode6.py | 18 +-
waflib/fixpy2.py | 2 +-
waflib/processor.py | 4 +
40 files changed, 2114 insertions(+), 195 deletions(-)
create mode 100644 waflib-macos-mods.patch
create mode 100644 waflib/extras/clang_cross.py
create mode 100644 waflib/extras/clang_cross_common.py
create mode 100644 waflib/extras/clangxx_cross.py
create mode 100644 waflib/extras/classic_runner.py
create mode 100644 waflib/extras/color_msvc.py
create mode 100644 waflib/extras/fc_fujitsu.py
create mode 100644 waflib/extras/fc_nfort.py
create mode 100644 waflib/extras/genpybind.py
create mode 100644 waflib/extras/haxe.py
create mode 100644 waflib/extras/msvc_pdb.py
create mode 100644 waflib/extras/sphinx.py
create mode 100644 waflib/extras/wafcache.py
diff --git a/waf b/waf
index 845fba5e9..38b2c9106 100755
--- a/waf
+++ b/waf
@@ -1,4 +1,4 @@
-#!/usr/bin/python3
+#!/usr/bin/env python
# encoding: latin-1
# Thomas Nagy, 2005-2018
#
@@ -32,7 +32,7 @@ POSSIBILITY OF SUCH DAMAGE.
import os, sys, inspect
-VERSION="2.0.12"
+VERSION="2.0.26"
REVISION="x"
GIT="x"
INSTALL="x"
@@ -142,6 +142,9 @@ def find_lib():
if name.endswith('waf-light'):
w = test(base)
if w: return w
+ for dir in sys.path:
+ if test(dir):
+ return dir
err('waf-light requires waflib -> export WAFDIR=/folder')
dirname = '%s-%s-%s' % (WAF, VERSION, REVISION)
diff --git a/waflib-macos-mods.patch b/waflib-macos-mods.patch
new file mode 100644
index 000000000..9e2c8a3de
--- /dev/null
+++ b/waflib-macos-mods.patch
@@ -0,0 +1,18 @@
+diff --git a/waflib/Tools/ccroot.py b/waflib/Tools/ccroot.py
+index cfef8bf5..484846f5 100644
+--- a/waflib/Tools/ccroot.py
++++ b/waflib/Tools/ccroot.py
+@@ -575,12 +575,10 @@ def apply_vnum(self):
+
+ cnum = getattr(self, 'cnum', str(nums[0]))
+ cnums = cnum.split('.')
+- if len(cnums)>len(nums) or nums[0:len(cnums)] != cnums:
+- raise Errors.WafError('invalid compatibility version %s' % cnum)
+
+ libname = node.name
+ if libname.endswith('.dylib'):
+- name3 = libname.replace('.dylib', '.%s.dylib' % self.vnum)
++ name3 = libname.replace('.dylib', '.%s.dylib' % cnums[0])
+ name2 = libname.replace('.dylib', '.%s.dylib' % cnum)
+ else:
+ name3 = libname + '.' + self.vnum
diff --git a/waflib/Build.py b/waflib/Build.py
index c9661df15..b49dd8302 100644
--- a/waflib/Build.py
+++ b/waflib/Build.py
@@ -104,7 +104,7 @@ def __init__(self, **kw):
"""Amount of jobs to run in parallel"""
self.targets = Options.options.targets
- """List of targets to build (default: \*)"""
+ """List of targets to build (default: \\*)"""
self.keep = Options.options.keep
"""Whether the build should continue past errors"""
@@ -753,10 +753,12 @@ def tgpost(tg):
else:
ln = self.launch_node()
if ln.is_child_of(self.bldnode):
- Logs.warn('Building from the build directory, forcing --targets=*')
+ if Logs.verbose > 1:
+ Logs.warn('Building from the build directory, forcing --targets=*')
ln = self.srcnode
elif not ln.is_child_of(self.srcnode):
- Logs.warn('CWD %s is not under %s, forcing --targets=* (run distclean?)', ln.abspath(), self.srcnode.abspath())
+ if Logs.verbose > 1:
+ Logs.warn('CWD %s is not under %s, forcing --targets=* (run distclean?)', ln.abspath(), self.srcnode.abspath())
ln = self.srcnode
def is_post(tg, ln):
@@ -1054,7 +1056,7 @@ def post_run(self):
def get_install_path(self, destdir=True):
"""
Returns the destination path where files will be installed, pre-pending `destdir`.
-
+
Relative paths will be interpreted relative to `PREFIX` if no `destdir` is given.
:rtype: string
@@ -1062,11 +1064,11 @@ def get_install_path(self, destdir=True):
if isinstance(self.install_to, Node.Node):
dest = self.install_to.abspath()
else:
- dest = Utils.subst_vars(self.install_to, self.env)
+ dest = os.path.normpath(Utils.subst_vars(self.install_to, self.env))
if not os.path.isabs(dest):
- dest = os.path.join(self.env.PREFIX, dest)
+ dest = os.path.join(self.env.PREFIX, dest)
if destdir and Options.options.destdir:
- dest = os.path.join(Options.options.destdir, os.path.splitdrive(dest)[1].lstrip(os.sep))
+ dest = Options.options.destdir.rstrip(os.sep) + os.sep + os.path.splitdrive(dest)[1].lstrip(os.sep)
return dest
def copy_fun(self, src, tgt):
@@ -1160,11 +1162,19 @@ def do_install(self, src, tgt, lbl, **kw):
# same size and identical timestamps -> make no copy
if st1.st_mtime + 2 >= st2.st_mtime and st1.st_size == st2.st_size:
if not self.generator.bld.progress_bar:
- Logs.info('- install %s (from %s)', tgt, lbl)
+
+ c1 = Logs.colors.NORMAL
+ c2 = Logs.colors.BLUE
+
+ Logs.info('%s- install %s%s%s (from %s)', c1, c2, tgt, c1, lbl)
return False
if not self.generator.bld.progress_bar:
- Logs.info('+ install %s (from %s)', tgt, lbl)
+
+ c1 = Logs.colors.NORMAL
+ c2 = Logs.colors.BLUE
+
+ Logs.info('%s+ install %s%s%s (from %s)', c1, c2, tgt, c1, lbl)
# Give best attempt at making destination overwritable,
# like the 'install' utility used by 'make install' does.
@@ -1221,14 +1231,18 @@ def do_link(self, src, tgt, **kw):
"""
if os.path.islink(tgt) and os.readlink(tgt) == src:
if not self.generator.bld.progress_bar:
- Logs.info('- symlink %s (to %s)', tgt, src)
+ c1 = Logs.colors.NORMAL
+ c2 = Logs.colors.BLUE
+ Logs.info('%s- symlink %s%s%s (to %s)', c1, c2, tgt, c1, src)
else:
try:
os.remove(tgt)
except OSError:
pass
if not self.generator.bld.progress_bar:
- Logs.info('+ symlink %s (to %s)', tgt, src)
+ c1 = Logs.colors.NORMAL
+ c2 = Logs.colors.BLUE
+ Logs.info('%s+ symlink %s%s%s (to %s)', c1, c2, tgt, c1, src)
os.symlink(src, tgt)
self.fix_perms(tgt)
@@ -1237,7 +1251,9 @@ def do_uninstall(self, src, tgt, lbl, **kw):
See :py:meth:`waflib.Build.inst.do_install`
"""
if not self.generator.bld.progress_bar:
- Logs.info('- remove %s', tgt)
+ c1 = Logs.colors.NORMAL
+ c2 = Logs.colors.BLUE
+ Logs.info('%s- remove %s%s%s', c1, c2, tgt, c1)
#self.uninstall.append(tgt)
try:
@@ -1257,7 +1273,9 @@ def do_unlink(self, src, tgt, **kw):
"""
try:
if not self.generator.bld.progress_bar:
- Logs.info('- remove %s', tgt)
+ c1 = Logs.colors.NORMAL
+ c2 = Logs.colors.BLUE
+ Logs.info('%s- remove %s%s%s', c1, c2, tgt, c1)
os.remove(tgt)
except OSError:
pass
@@ -1318,7 +1336,8 @@ def build(bld):
lst = []
for env in self.all_envs.values():
lst.extend(self.root.find_or_declare(f) for f in env[CFG_FILES])
- for n in self.bldnode.ant_glob('**/*', excl='.lock* *conf_check_*/** config.log c4che/*', quiet=True):
+ excluded_dirs = '.lock* *conf_check_*/** config.log %s/*' % CACHE_DIR
+ for n in self.bldnode.ant_glob('**/*', excl=excluded_dirs, quiet=True):
if n in lst:
continue
n.delete()
diff --git a/waflib/ConfigSet.py b/waflib/ConfigSet.py
index 84736c9c8..901fba6c0 100644
--- a/waflib/ConfigSet.py
+++ b/waflib/ConfigSet.py
@@ -11,7 +11,7 @@
import copy, re, os
from waflib import Logs, Utils
-re_imp = re.compile('^(#)*?([^#=]*?)\ =\ (.*?)$', re.M)
+re_imp = re.compile(r'^(#)*?([^#=]*?)\ =\ (.*?)$', re.M)
class ConfigSet(object):
"""
diff --git a/waflib/Configure.py b/waflib/Configure.py
index d0a4793a8..f6fdc4e94 100644
--- a/waflib/Configure.py
+++ b/waflib/Configure.py
@@ -125,7 +125,7 @@ def init_dirs(self):
self.bldnode.mkdir()
if not os.path.isdir(self.bldnode.abspath()):
- conf.fatal('Could not create the build directory %s' % self.bldnode.abspath())
+ self.fatal('Could not create the build directory %s' % self.bldnode.abspath())
def execute(self):
"""
@@ -180,6 +180,7 @@ def execute(self):
env.hash = self.hash
env.files = self.files
env.environ = dict(self.environ)
+ env.launch_dir = Context.launch_dir
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))
@@ -438,7 +439,7 @@ def find_program(self, filename, **kw):
var = kw.get('var', '')
if not var:
- var = re.sub(r'[-.]', '_', filename[0].upper())
+ var = re.sub(r'\W', '_', filename[0].upper())
path_list = kw.get('path_list', '')
if path_list:
@@ -507,23 +508,27 @@ def find_binary(self, filenames, exts, paths):
@conf
def run_build(self, *k, **kw):
"""
- Create a temporary build context to execute a build. A reference to that build
- context is kept on self.test_bld for debugging purposes, and you should not rely
- on it too much (read the note on the cache below).
- The parameters given in the arguments to this function are passed as arguments for
- a single task generator created in the build. Only three parameters are obligatory:
+ Create a temporary build context to execute a build. A temporary reference to that build
+ context is kept on self.test_bld for debugging purposes.
+ The arguments to this function are passed to a single task generator for that build.
+ Only three parameters are mandatory:
:param features: features to pass to a task generator created in the build
:type features: list of string
:param compile_filename: file to create for the compilation (default: *test.c*)
:type compile_filename: string
- :param code: code to write in the filename to compile
+ :param code: input file contents
:type code: string
- Though this function returns *0* by default, the build may set an attribute named *retval* on the
+ Though this function returns *0* by default, the build may bind attribute named *retval* on the
build context object to return a particular value. See :py:func:`waflib.Tools.c_config.test_exec_fun` for example.
- This function also provides a limited cache. To use it, provide the following option::
+ The temporary builds creates a temporary folder; the name of that folder is calculated
+ by hashing input arguments to this function, with the exception of :py:class:`waflib.ConfigSet.ConfigSet`
+ objects which are used for both reading and writing values.
+
+ This function also features a cache which is disabled by default; that cache relies
+ on the hash value calculated as indicated above::
def options(opt):
opt.add_option('--confcache', dest='confcache', default=0,
@@ -534,10 +539,24 @@ def options(opt):
$ waf configure --confcache
"""
- lst = [str(v) for (p, v) in kw.items() if p != 'env']
- h = Utils.h_list(lst)
+ buf = []
+ for key in sorted(kw.keys()):
+ v = kw[key]
+ if isinstance(v, ConfigSet.ConfigSet):
+ # values are being written to, so they are excluded from contributing to the hash
+ continue
+ elif hasattr(v, '__call__'):
+ buf.append(Utils.h_fun(v))
+ else:
+ buf.append(str(v))
+ h = Utils.h_list(buf)
dir = self.bldnode.abspath() + os.sep + (not Utils.is_win32 and '.' or '') + 'conf_check_' + Utils.to_hex(h)
+ cachemode = kw.get('confcache', getattr(Options.options, 'confcache', None))
+
+ if not cachemode and os.path.exists(dir):
+ shutil.rmtree(dir)
+
try:
os.makedirs(dir)
except OSError:
@@ -548,7 +567,6 @@ def options(opt):
except OSError:
self.fatal('cannot use the configuration test folder %r' % dir)
- cachemode = getattr(Options.options, 'confcache', None)
if cachemode == 1:
try:
proj = ConfigSet.ConfigSet(os.path.join(dir, 'cache_run_build'))
@@ -588,7 +606,7 @@ def options(opt):
else:
ret = getattr(bld, 'retval', 0)
finally:
- if cachemode == 1:
+ if cachemode:
# cache the results each time
proj = ConfigSet.ConfigSet()
proj['cache_run_build'] = ret
diff --git a/waflib/Context.py b/waflib/Context.py
index 761b521f5..369664819 100644
--- a/waflib/Context.py
+++ b/waflib/Context.py
@@ -6,20 +6,30 @@
Classes and functions enabling the command system
"""
-import os, re, imp, sys
+import os, re, sys
from waflib import Utils, Errors, Logs
import waflib.Node
+if sys.hexversion > 0x3040000:
+ import types
+ class imp(object):
+ new_module = lambda x: types.ModuleType(x)
+else:
+ import imp
+
# the following 3 constants are updated on each new release (do not touch)
-HEXVERSION=0x2000c00
+HEXVERSION=0x2001a00
"""Constant updated on new releases"""
-WAFVERSION="2.0.12"
+WAFVERSION="2.0.26"
"""Constant updated on new releases"""
-WAFREVISION="54841218840ffa34fddf834680a5a17db69caa12"
+WAFREVISION="0fb985ce1932c6f3e7533f435e4ee209d673776e"
"""Git revision when the waf version is updated"""
+WAFNAME="waf"
+"""Application name displayed on --help"""
+
ABI = 20
"""Version of the build data cache file format (used in :py:const:`waflib.Context.DBFILE`)"""
@@ -134,7 +144,7 @@ def foo(ctx):
:type fun: string
.. inheritance-diagram:: waflib.Context.Context waflib.Build.BuildContext waflib.Build.InstallContext waflib.Build.UninstallContext waflib.Build.StepContext waflib.Build.ListContext waflib.Configure.ConfigurationContext waflib.Scripting.Dist waflib.Scripting.DistCheck waflib.Build.CleanContext
-
+ :top-classes: waflib.Context.Context
"""
errors = Errors
@@ -613,7 +623,7 @@ def load_special_tools(self, var, ban=[]):
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'
+ :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
@@ -678,7 +688,7 @@ def load_module(path, encoding=None):
def load_tool(tool, tooldir=None, ctx=None, with_sys_path=True):
"""
- Importx a Waf tool as a python module, and stores it in the dict :py:const:`waflib.Context.Context.tools`
+ Imports 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
diff --git a/waflib/Logs.py b/waflib/Logs.py
index 2a475169b..298411db5 100644
--- a/waflib/Logs.py
+++ b/waflib/Logs.py
@@ -237,7 +237,10 @@ def format(self, rec):
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
+ try:
+ return msg % rec.args
+ except UnicodeDecodeError:
+ return msg.encode('utf-8') % rec.args
return msg
rec.msg = msg
@@ -276,9 +279,9 @@ def error(*k, **kw):
def warn(*k, **kw):
"""
- Wraps logging.warn
+ Wraps logging.warning
"""
- log.warn(*k, **kw)
+ log.warning(*k, **kw)
def info(*k, **kw):
"""
diff --git a/waflib/Node.py b/waflib/Node.py
index 4ac1ea8a0..2ad184669 100644
--- a/waflib/Node.py
+++ b/waflib/Node.py
@@ -73,7 +73,7 @@ def ant_matcher(s, ignorecase):
if k == '**':
accu.append(k)
else:
- k = k.replace('.', '[.]').replace('*','.*').replace('?', '.').replace('+', '\\+')
+ k = k.replace('.', '[.]').replace('*', '.*').replace('?', '.').replace('+', '\\+')
k = '^%s$' % k
try:
exp = re.compile(k, flags=reflags)
@@ -595,7 +595,6 @@ def ant_iter(self, accept=None, maxdepth=25, pats=[], dir=False, src=True, remov
:rtype: iterator
"""
dircont = self.listdir()
- dircont.sort()
try:
lst = set(self.children.keys())
diff --git a/waflib/Options.py b/waflib/Options.py
index ad802d4b9..d4104917c 100644
--- a/waflib/Options.py
+++ b/waflib/Options.py
@@ -44,7 +44,7 @@ class opt_parser(optparse.OptionParser):
"""
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))
+ version='%s %s (%s)' % (Context.WAFNAME, Context.WAFVERSION, Context.WAFREVISION))
self.formatter.width = Logs.get_term_cols()
self.ctx = ctx
self.allow_unknown = allow_unknown
@@ -62,6 +62,21 @@ def _process_args(self, largs, rargs, values):
else:
self.error(str(e))
+ def _process_long_opt(self, rargs, values):
+ # --custom-option=-ftxyz is interpreted as -f -t... see #2280
+ if self.allow_unknown:
+ back = [] + rargs
+ try:
+ optparse.OptionParser._process_long_opt(self, rargs, values)
+ except optparse.BadOptionError:
+ while rargs:
+ rargs.pop()
+ rargs.extend(back)
+ rargs.pop(0)
+ raise
+ else:
+ optparse.OptionParser._process_long_opt(self, rargs, values)
+
def print_usage(self, file=None):
return self.print_help(file)
@@ -96,11 +111,11 @@ def get_usage(self):
lst.sort()
ret = '\n'.join(lst)
- return '''waf [commands] [options]
+ return '''%s [commands] [options]
-Main commands (example: ./waf build -j4)
+Main commands (example: ./%s build -j4)
%s
-''' % ret
+''' % (Context.WAFNAME, Context.WAFNAME, ret)
class OptionsContext(Context.Context):
@@ -141,9 +156,9 @@ def __init__(self, **kw):
gr.add_option('-o', '--out', action='store', default='', help='build dir for the project', dest='out')
gr.add_option('-t', '--top', action='store', default='', help='src dir for the project', dest='top')
- gr.add_option('--no-lock-in-run', action='store_true', default='', help=optparse.SUPPRESS_HELP, dest='no_lock_in_run')
- gr.add_option('--no-lock-in-out', action='store_true', default='', help=optparse.SUPPRESS_HELP, dest='no_lock_in_out')
- gr.add_option('--no-lock-in-top', action='store_true', default='', help=optparse.SUPPRESS_HELP, dest='no_lock_in_top')
+ gr.add_option('--no-lock-in-run', action='store_true', default=os.environ.get('NO_LOCK_IN_RUN', ''), help=optparse.SUPPRESS_HELP, dest='no_lock_in_run')
+ gr.add_option('--no-lock-in-out', action='store_true', default=os.environ.get('NO_LOCK_IN_OUT', ''), help=optparse.SUPPRESS_HELP, dest='no_lock_in_out')
+ gr.add_option('--no-lock-in-top', action='store_true', default=os.environ.get('NO_LOCK_IN_TOP', ''), help=optparse.SUPPRESS_HELP, dest='no_lock_in_top')
default_prefix = getattr(Context.g_module, 'default_prefix', os.environ.get('PREFIX'))
if not default_prefix:
@@ -282,6 +297,8 @@ def parse_cmd_args(self, _args=None, cwd=None, allow_unknown=False):
elif arg != 'options':
commands.append(arg)
+ if options.jobs < 1:
+ options.jobs = 1
for name in 'top out destdir prefix bindir libdir'.split():
# those paths are usually expanded from Context.launch_dir
if getattr(options, name, None):
diff --git a/waflib/Runner.py b/waflib/Runner.py
index 261084d27..350c86a22 100644
--- a/waflib/Runner.py
+++ b/waflib/Runner.py
@@ -37,6 +37,8 @@ def __len__(self):
return len(self.lst)
def __iter__(self):
return iter(self.lst)
+ def __str__(self):
+ return 'PriorityTasks: [%s]' % '\n '.join(str(x) for x in self.lst)
def clear(self):
self.lst = []
def append(self, task):
@@ -69,7 +71,7 @@ def __init__(self, spawner, task):
"""Task to execute"""
self.spawner = spawner
"""Coordinator object"""
- self.setDaemon(1)
+ self.daemon = True
self.start()
def run(self):
"""
@@ -96,7 +98,7 @@ def __init__(self, master):
""":py:class:`waflib.Runner.Parallel` producer instance"""
self.sem = Utils.threading.Semaphore(master.numjobs)
"""Bounded semaphore that prevents spawning more than *n* concurrent consumers"""
- self.setDaemon(1)
+ self.daemon = True
self.start()
def run(self):
"""
@@ -181,10 +183,12 @@ def __init__(self, bld, j=2):
The reverse dependency graph of dependencies obtained from Task.run_after
"""
- self.spawner = Spawner(self)
+ self.spawner = None
"""
Coordinating daemon thread that spawns thread consumers
"""
+ if self.numjobs > 1:
+ self.spawner = Spawner(self)
def get_next_task(self):
"""
@@ -254,6 +258,8 @@ def refill_task_list(self):
self.outstanding.append(x)
break
else:
+ if self.stop or self.error:
+ break
raise Errors.WafError('Broken revdeps detected on %r' % self.incomplete)
else:
tasks = next(self.biter)
@@ -331,11 +337,16 @@ def try_unfreeze(x):
if hasattr(tsk, 'semaphore'):
sem = tsk.semaphore
- sem.release(tsk)
- while sem.waiting and not sem.is_locked():
- # take a frozen task, make it ready to run
- x = sem.waiting.pop()
- self._add_task(x)
+ try:
+ sem.release(tsk)
+ except KeyError:
+ # TODO
+ pass
+ else:
+ while sem.waiting and not sem.is_locked():
+ # take a frozen task, make it ready to run
+ x = sem.waiting.pop()
+ self._add_task(x)
def get_out(self):
"""
diff --git a/waflib/Scripting.py b/waflib/Scripting.py
index 749d4f2e6..a80cb3678 100644
--- a/waflib/Scripting.py
+++ b/waflib/Scripting.py
@@ -216,7 +216,10 @@ def parse_options():
ctx = Context.create_context('options')
ctx.execute()
if not Options.commands:
- Options.commands.append(default_cmd)
+ if isinstance(default_cmd, list):
+ Options.commands.extend(default_cmd)
+ else:
+ Options.commands.append(default_cmd)
if Options.options.whelp:
ctx.parser.print_help()
sys.exit(0)
@@ -280,7 +283,7 @@ def distclean_dir(dirname):
pass
try:
- shutil.rmtree('c4che')
+ shutil.rmtree(Build.CACHE_DIR)
except OSError:
pass
@@ -303,7 +306,7 @@ def remove_and_log(k, fun):
# remove a build folder, if any
cur = '.'
- if ctx.options.no_lock_in_top:
+ if os.environ.get('NO_LOCK_IN_TOP') or ctx.options.no_lock_in_top:
cur = ctx.options.out
try:
@@ -329,7 +332,12 @@ def remove_and_log(k, fun):
else:
remove_and_log(env.out_dir, shutil.rmtree)
- for k in (env.out_dir, env.top_dir, env.run_dir):
+ env_dirs = [env.out_dir]
+ if not (os.environ.get('NO_LOCK_IN_TOP') or ctx.options.no_lock_in_top):
+ env_dirs.append(env.top_dir)
+ if not (os.environ.get('NO_LOCK_IN_RUN') or ctx.options.no_lock_in_run):
+ env_dirs.append(env.run_dir)
+ for k in env_dirs:
p = os.path.join(k, Options.lockfile)
remove_and_log(p, os.remove)
@@ -380,7 +388,11 @@ def archive(self):
for x in files:
archive_name = self.get_base_name() + '/' + x.path_from(self.base_path)
- zip.write(x.abspath(), archive_name, zipfile.ZIP_DEFLATED)
+ if os.environ.get('SOURCE_DATE_EPOCH'):
+ # TODO: parse that timestamp
+ zip.writestr(zipfile.ZipInfo(archive_name), x.read(), zipfile.ZIP_DEFLATED)
+ else:
+ zip.write(x.abspath(), archive_name, zipfile.ZIP_DEFLATED)
zip.close()
else:
self.fatal('Valid algo types are tar.bz2, tar.gz, tar.xz or zip')
@@ -417,6 +429,8 @@ def add_tar_file(self, x, tar):
tinfo.gid = 0
tinfo.uname = 'root'
tinfo.gname = 'root'
+ if os.environ.get('SOURCE_DATE_EPOCH'):
+ tinfo.mtime = int(os.environ.get('SOURCE_DATE_EPOCH'))
if os.path.isfile(p):
with open(p, 'rb') as f:
@@ -598,12 +612,15 @@ def execute(self):
cmd = env.config_cmd or 'configure'
if Configure.autoconfig == 'clobber':
tmp = Options.options.__dict__
+ launch_dir_tmp = Context.launch_dir
if env.options:
Options.options.__dict__ = env.options
+ Context.launch_dir = env.launch_dir
try:
run_command(cmd)
finally:
Options.options.__dict__ = tmp
+ Context.launch_dir = launch_dir_tmp
else:
run_command(cmd)
run_command(self.cmd)
diff --git a/waflib/Task.py b/waflib/Task.py
index 6aebc6074..cb49a7394 100644
--- a/waflib/Task.py
+++ b/waflib/Task.py
@@ -163,10 +163,10 @@ class Task(evil):
"""File extensions that objects of this task class may create"""
before = []
- """List of task class names to execute before instances of this class"""
+ """The instances of this class are executed before the instances of classes whose names are in this list"""
after = []
- """List of task class names to execute after instances of this class"""
+ """The instances of this class are executed after the instances of classes whose names are in this list"""
hcode = Utils.SIG_NIL
"""String representing an additional hash for the class representation"""
@@ -306,25 +306,31 @@ def exec_command(self, cmd, **kw):
if hasattr(self, 'stderr'):
kw['stderr'] = self.stderr
- # workaround for command line length limit:
- # http://support.microsoft.com/kb/830473
- if not isinstance(cmd, str) and (len(repr(cmd)) >= 8192 if Utils.is_win32 else len(cmd) > 200000):
- cmd, args = self.split_argfile(cmd)
- try:
- (fd, tmp) = tempfile.mkstemp()
- os.write(fd, '\r\n'.join(args).encode())
- os.close(fd)
- if Logs.verbose:
- Logs.debug('argfile: @%r -> %r', tmp, args)
- return self.generator.bld.exec_command(cmd + ['@' + tmp], **kw)
- finally:
+ if not isinstance(cmd, str):
+ if Utils.is_win32:
+ # win32 compares the resulting length http://support.microsoft.com/kb/830473
+ too_long = sum([len(arg) for arg in cmd]) + len(cmd) > 8192
+ else:
+ # non-win32 counts the amount of arguments (200k)
+ too_long = len(cmd) > 200000
+
+ if too_long and getattr(self, 'allow_argsfile', True):
+ # Shunt arguments to a temporary file if the command is too long.
+ cmd, args = self.split_argfile(cmd)
try:
- os.remove(tmp)
- except OSError:
- # anti-virus and indexers can keep files open -_-
- pass
- else:
- return self.generator.bld.exec_command(cmd, **kw)
+ (fd, tmp) = tempfile.mkstemp()
+ os.write(fd, '\r\n'.join(args).encode())
+ os.close(fd)
+ if Logs.verbose:
+ Logs.debug('argfile: @%r -> %r', tmp, args)
+ return self.generator.bld.exec_command(cmd + ['@' + tmp], **kw)
+ finally:
+ try:
+ os.remove(tmp)
+ except OSError:
+ # anti-virus and indexers can keep files open -_-
+ pass
+ return self.generator.bld.exec_command(cmd, **kw)
def process(self):
"""
@@ -1044,7 +1050,7 @@ def funex(c):
exec(c, dc)
return dc['f']
-re_cond = re.compile('(?P<var>\w+)|(?P<or>\|)|(?P<and>&)')
+re_cond = re.compile(r'(?P<var>\w+)|(?P<or>\|)|(?P<and>&)')
re_novar = re.compile(r'^(SRC|TGT)\W+.*?$')
reg_act = re.compile(r'(?P<backslash>\\)|(?P<dollar>\$\$)|(?P<subst>\$\{(?P<var>\w+)(?P<code>.*?)\})', re.M)
def compile_fun_shell(line):
diff --git a/waflib/TaskGen.py b/waflib/TaskGen.py
index a74e6431d..32468f03d 100644
--- a/waflib/TaskGen.py
+++ b/waflib/TaskGen.py
@@ -74,7 +74,7 @@ def __init__(self, *k, **kw):
else:
self.bld = kw['bld']
self.env = self.bld.env.derive()
- self.path = self.bld.path # emulate chdir when reading scripts
+ self.path = kw.get('path', self.bld.path) # by default, emulate chdir when reading scripts
# Provide a unique index per folder
# This is part of a measure to prevent output file name collisions
@@ -400,7 +400,7 @@ def feature(*k):
Decorator that registers a task generator method that will be executed when the
object attribute ``feature`` contains the corresponding key(s)::
- from waflib.Task import feature
+ from waflib.TaskGen import feature
@feature('myfeature')
def myfunction(self):
print('that is my feature!')
@@ -631,12 +631,8 @@ def chmod_fun(tsk):
cls.scan = self.scan
elif has_deps:
def scan(self):
- nodes = []
- for x in self.generator.to_list(getattr(self.generator, 'deps', None)):
- node = self.generator.path.find_resource(x)
- if not node:
- self.generator.bld.fatal('Could not find %r (was it declared?)' % x)
- nodes.append(node)
+ deps = getattr(self.generator, 'deps', None)
+ nodes = self.generator.to_nodes(deps)
return [nodes, []]
cls.scan = scan
@@ -727,7 +723,7 @@ def sequence_order(self):
self.bld.prev = self
-re_m4 = re.compile('@(\w+)@', re.M)
+re_m4 = re.compile(r'@(\w+)@', re.M)
class subst_pc(Task.Task):
"""
@@ -905,7 +901,7 @@ def build(bld):
# paranoid safety measure for the general case foo.in->foo.h with ambiguous dependencies
for xt in HEADER_EXTS:
if b.name.endswith(xt):
- tsk.ext_in = tsk.ext_in + ['.h']
+ tsk.ext_out = tsk.ext_out + ['.h']
break
inst_to = getattr(self, 'install_path', None)
diff --git a/waflib/Tools/c_aliases.py b/waflib/Tools/c_aliases.py
index c9d53692e..928cfe29c 100644
--- a/waflib/Tools/c_aliases.py
+++ b/waflib/Tools/c_aliases.py
@@ -38,7 +38,7 @@ def sniff_features(**kw):
:return: the list of features for a task generator processing the source files
:rtype: list of string
"""
- exts = get_extensions(kw['source'])
+ exts = get_extensions(kw.get('source', []))
typ = kw['typ']
feats = []
@@ -47,10 +47,12 @@ def sniff_features(**kw):
if x in exts:
feats.append('cxx')
break
-
if 'c' in exts or 'vala' in exts or 'gs' in exts:
feats.append('c')
+ if 's' in exts or 'S' in exts:
+ feats.append('asm')
+
for x in 'f f90 F F90 for FOR'.split():
if x in exts:
feats.append('fc')
@@ -66,11 +68,11 @@ def sniff_features(**kw):
if typ in ('program', 'shlib', 'stlib'):
will_link = False
for x in feats:
- if x in ('cxx', 'd', 'fc', 'c'):
+ if x in ('cxx', 'd', 'fc', 'c', 'asm'):
feats.append(x + typ)
will_link = True
if not will_link and not kw.get('features', []):
- raise Errors.WafError('Cannot link from %r, try passing eg: features="c cprogram"?' % kw)
+ raise Errors.WafError('Unable to determine how to link %r, try adding eg: features="c cshlib"?' % kw)
return feats
def set_features(kw, typ):
diff --git a/waflib/Tools/c_config.py b/waflib/Tools/c_config.py
index d2b3c0d8f..f5ab19bf6 100644
--- a/waflib/Tools/c_config.py
+++ b/waflib/Tools/c_config.py
@@ -68,6 +68,8 @@
'__s390__' : 's390',
'__sh__' : 'sh',
'__xtensa__' : 'xtensa',
+'__e2k__' : 'e2k',
+'__riscv' : 'riscv',
}
@conf
@@ -86,6 +88,10 @@ def configure(conf):
:type uselib_store: string
:param env: config set or conf.env by default
:type env: :py:class:`waflib.ConfigSet.ConfigSet`
+ :param force_static: force usage of static libraries
+ :type force_static: bool default False
+ :param posix: usage of POSIX mode for shlex lexical analiysis library
+ :type posix: bool default True
"""
assert(isinstance(line, str))
@@ -103,6 +109,8 @@ def configure(conf):
lex.commenters = ''
lst = list(lex)
+ so_re = re.compile(r"\.so(?:\.[0-9]+)*$")
+
# append_unique is not always possible
# for example, apple flags may require both -arch i386 and -arch ppc
uselib = uselib_store
@@ -144,7 +152,7 @@ def appu(var, val):
elif x.startswith('-std='):
prefix = 'CXXFLAGS' if '++' in x else 'CFLAGS'
app(prefix, x)
- elif x.startswith('+') or x in ('-pthread', '-fPIC', '-fpic', '-fPIE', '-fpie'):
+ elif x.startswith('+') or x in ('-pthread', '-fPIC', '-fpic', '-fPIE', '-fpie', '-flto', '-fno-lto'):
app('CFLAGS', x)
app('CXXFLAGS', x)
app('LINKFLAGS', x)
@@ -180,7 +188,7 @@ def appu(var, val):
app('CFLAGS', tmp)
app('CXXFLAGS', tmp)
app('LINKFLAGS', tmp)
- elif x.endswith(('.a', '.so', '.dylib', '.lib')):
+ elif x.endswith(('.a', '.dylib', '.lib')) or so_re.search(x):
appu('LINKFLAGS', x) # not cool, #762
else:
self.to_log('Unhandled flag %r' % x)
@@ -246,13 +254,15 @@ def exec_cfg(self, kw):
* if modversion is given, then return the module version
* else, execute the *-config* program with the *args* and *variables* given, and set the flags on the *conf.env.FLAGS_name* variable
+ :param path: the **-config program to use**
+ :type path: list of string
:param atleast_pkgconfig_version: minimum pkg-config version to use (disable other tests)
:type atleast_pkgconfig_version: string
:param package: package name, for example *gtk+-2.0*
:type package: string
- :param uselib_store: if the test is successful, define HAVE\_*name*. It is also used to define *conf.env.FLAGS_name* variables.
+ :param uselib_store: if the test is successful, define HAVE\\_*name*. It is also used to define *conf.env.FLAGS_name* variables.
:type uselib_store: string
- :param modversion: if provided, return the version of the given module and define *name*\_VERSION
+ :param modversion: if provided, return the version of the given module and define *name*\\_VERSION
:type modversion: string
:param args: arguments to give to *package* when retrieving flags
:type args: list of string
@@ -260,6 +270,12 @@ def exec_cfg(self, kw):
:type variables: list of string
:param define_variable: additional variables to define (also in conf.env.PKG_CONFIG_DEFINES)
:type define_variable: dict(string: string)
+ :param pkg_config_path: paths where pkg-config should search for .pc config files (overrides env.PKG_CONFIG_PATH if exists)
+ :type pkg_config_path: string, list of directories separated by colon
+ :param force_static: force usage of static libraries
+ :type force_static: bool default False
+ :param posix: usage of POSIX mode for shlex lexical analiysis library
+ :type posix: bool default True
"""
path = Utils.to_list(kw['path'])
@@ -334,6 +350,7 @@ def check_cfg(self, *k, **kw):
"""
Checks for configuration flags using a **-config**-like program (pkg-config, sdl-config, etc).
This wraps internal calls to :py:func:`waflib.Tools.c_config.validate_cfg` and :py:func:`waflib.Tools.c_config.exec_cfg`
+ so check exec_cfg parameters descriptions for more details on kw passed
A few examples::
@@ -659,20 +676,21 @@ class test_exec(Task.Task):
"""
color = 'PINK'
def run(self):
+ cmd = [self.inputs[0].abspath()] + getattr(self.generator, 'test_args', [])
if getattr(self.generator, 'rpath', None):
if getattr(self.generator, 'define_ret', False):
- self.generator.bld.retval = self.generator.bld.cmd_and_log([self.inputs[0].abspath()])
+ self.generator.bld.retval = self.generator.bld.cmd_and_log(cmd)
else:
- self.generator.bld.retval = self.generator.bld.exec_command([self.inputs[0].abspath()])
+ self.generator.bld.retval = self.generator.bld.exec_command(cmd)
else:
env = self.env.env or {}
env.update(dict(os.environ))
for var in ('LD_LIBRARY_PATH', 'DYLD_LIBRARY_PATH', 'PATH'):
env[var] = self.inputs[0].parent.abspath() + os.path.pathsep + env.get(var, '')
if getattr(self.generator, 'define_ret', False):
- self.generator.bld.retval = self.generator.bld.cmd_and_log([self.inputs[0].abspath()], env=env)
+ self.generator.bld.retval = self.generator.bld.cmd_and_log(cmd, env=env)
else:
- self.generator.bld.retval = self.generator.bld.exec_command([self.inputs[0].abspath()], env=env)
+ self.generator.bld.retval = self.generator.bld.exec_command(cmd, env=env)
@feature('test_exec')
@after_method('apply_link')
@@ -1266,10 +1284,11 @@ def to_log(self, *k, **kw):
tasks = []
id_to_task = {}
- for dct in k:
+ for counter, dct in enumerate(k):
x = Task.classes['cfgtask'](bld=bld, env=None)
tasks.append(x)
x.args = dct
+ x.args['multicheck_counter'] = counter
x.bld = bld
x.conf = self
x.args = dct
diff --git a/waflib/Tools/c_preproc.py b/waflib/Tools/c_preproc.py
index 7e04b4a7c..68e5f5aea 100644
--- a/waflib/Tools/c_preproc.py
+++ b/waflib/Tools/c_preproc.py
@@ -75,13 +75,13 @@ class PreprocError(Errors.WafError):
re.IGNORECASE | re.MULTILINE)
"""Match #include lines"""
-re_mac = re.compile("^[a-zA-Z_]\w*")
+re_mac = re.compile(r"^[a-zA-Z_]\w*")
"""Match macro definitions"""
re_fun = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*[(]')
"""Match macro functions"""
-re_pragma_once = re.compile('^\s*once\s*', re.IGNORECASE)
+re_pragma_once = re.compile(r'^\s*once\s*', re.IGNORECASE)
"""Match #pragma once statements"""
re_nl = re.compile('\\\\\r*\n', re.MULTILINE)
@@ -660,7 +660,7 @@ def extract_macro(txt):
# empty define, assign an empty token
return (v, [[], [('T','')]])
-re_include = re.compile('^\s*(<(?:.*)>|"(?:.*)")')
+re_include = re.compile(r'^\s*(<(?:.*)>|"(?:.*)")')
def extract_include(txt, defs):
"""
Process a line in the form::
diff --git a/waflib/Tools/c_tests.py b/waflib/Tools/c_tests.py
index f858df576..bdd186c6b 100644
--- a/waflib/Tools/c_tests.py
+++ b/waflib/Tools/c_tests.py
@@ -180,9 +180,15 @@ def check_large_file(self, **kw):
########################################################################################
ENDIAN_FRAGMENT = '''
+#ifdef _MSC_VER
+#define testshlib_EXPORT __declspec(dllexport)
+#else
+#define testshlib_EXPORT
+#endif
+
short int ascii_mm[] = { 0x4249, 0x4765, 0x6E44, 0x6961, 0x6E53, 0x7953, 0 };
short int ascii_ii[] = { 0x694C, 0x5454, 0x656C, 0x6E45, 0x6944, 0x6E61, 0 };
-int use_ascii (int i) {
+int testshlib_EXPORT use_ascii (int i) {
return ascii_mm[i] + ascii_ii[i];
}
short int ebcdic_ii[] = { 0x89D3, 0xE3E3, 0x8593, 0x95C5, 0x89C4, 0x9581, 0 };
@@ -208,12 +214,12 @@ def run(self):
return -1
@feature('grep_for_endianness')
-@after_method('process_source')
+@after_method('apply_link')
def grep_for_endianness_fun(self):
"""
Used by the endianness configuration test
"""
- self.create_task('grep_for_endianness', self.compiled_tasks[0].outputs[0])
+ self.create_task('grep_for_endianness', self.link_task.outputs[0])
@conf
def check_endianness(self):
@@ -223,7 +229,9 @@ def check_endianness(self):
tmp = []
def check_msg(self):
return tmp[0]
- self.check(fragment=ENDIAN_FRAGMENT, features='c grep_for_endianness',
- msg='Checking for endianness', define='ENDIANNESS', tmp=tmp, okmsg=check_msg)
+
+ self.check(fragment=ENDIAN_FRAGMENT, features='c cshlib grep_for_endianness',
+ msg='Checking for endianness', define='ENDIANNESS', tmp=tmp,
+ okmsg=check_msg, confcache=None)
return tmp[0]
diff --git a/waflib/Tools/ccroot.py b/waflib/Tools/ccroot.py
index 484846f5f..533992903 100644
--- a/waflib/Tools/ccroot.py
+++ b/waflib/Tools/ccroot.py
@@ -111,7 +111,7 @@ def apply_incpaths(self):
tg = bld(features='includes', includes='.')
The folders only need to be relative to the current directory, the equivalent build directory is
- added automatically (for headers created in the build directory). This enable using a build directory
+ added automatically (for headers created in the build directory). This enables using a build directory
or not (``top == out``).
This method will add a list of nodes read by :py:func:`waflib.Tools.ccroot.to_incnodes` in ``tg.env.INCPATHS``,
@@ -128,6 +128,7 @@ class link_task(Task.Task):
Base class for all link tasks. A task generator is supposed to have at most one link task bound in the attribute *link_task*. See :py:func:`waflib.Tools.ccroot.apply_link`.
.. inheritance-diagram:: waflib.Tools.ccroot.stlink_task waflib.Tools.c.cprogram waflib.Tools.c.cshlib waflib.Tools.cxx.cxxstlib waflib.Tools.cxx.cxxprogram waflib.Tools.cxx.cxxshlib waflib.Tools.d.dprogram waflib.Tools.d.dshlib waflib.Tools.d.dstlib waflib.Tools.ccroot.fake_shlib waflib.Tools.ccroot.fake_stlib waflib.Tools.asm.asmprogram waflib.Tools.asm.asmshlib waflib.Tools.asm.asmstlib
+ :top-classes: waflib.Tools.ccroot.link_task
"""
color = 'YELLOW'
@@ -238,6 +239,17 @@ def wrap(self):
setattr(cls, 'run', wrap)
rm_tgt(stlink_task)
+@feature('skip_stlib_link_deps')
+@before_method('process_use')
+def apply_skip_stlib_link_deps(self):
+ """
+ This enables an optimization in the :py:func:wafilb.Tools.ccroot.processes_use: method that skips dependency and
+ link flag optimizations for targets that generate static libraries (via the :py:class:Tools.ccroot.stlink_task task).
+ The actual behavior is implemented in :py:func:wafilb.Tools.ccroot.processes_use: method so this feature only tells waf
+ to enable the new behavior.
+ """
+ self.env.SKIP_STLIB_LINK_DEPS = True
+
@feature('c', 'cxx', 'd', 'fc', 'asm')
@after_method('process_source')
def apply_link(self):
@@ -386,7 +398,11 @@ def build(bld):
y = self.bld.get_tgen_by_name(x)
var = y.tmp_use_var
if var and link_task:
- if var == 'LIB' or y.tmp_use_stlib or x in names:
+ if self.env.SKIP_STLIB_LINK_DEPS and isinstance(link_task, stlink_task):
+ # If the skip_stlib_link_deps feature is enabled then we should
+ # avoid adding lib deps to the stlink_task instance.
+ pass
+ elif var == 'LIB' or y.tmp_use_stlib or x in names:
self.env.append_value(var, [y.target[y.target.rfind(os.sep) + 1:]])
self.link_task.dep_nodes.extend(y.link_task.outputs)
tmp_path = y.link_task.outputs[0].parent.path_from(self.get_cwd())
diff --git a/waflib/Tools/compiler_c.py b/waflib/Tools/compiler_c.py
index 2dba3f827..e033ce6c5 100644
--- a/waflib/Tools/compiler_c.py
+++ b/waflib/Tools/compiler_c.py
@@ -36,18 +36,19 @@ def build(bld):
from waflib.Logs import debug
c_compiler = {
-'win32': ['msvc', 'gcc', 'clang'],
-'cygwin': ['gcc'],
-'darwin': ['clang', 'gcc'],
-'aix': ['xlc', 'gcc', 'clang'],
-'linux': ['gcc', 'clang', 'icc'],
-'sunos': ['suncc', 'gcc'],
-'irix': ['gcc', 'irixcc'],
-'hpux': ['gcc'],
-'osf1V': ['gcc'],
-'gnu': ['gcc', 'clang'],
-'java': ['gcc', 'msvc', 'clang', 'icc'],
-'default':['clang', 'gcc'],
+'win32': ['msvc', 'gcc', 'clang'],
+'cygwin': ['gcc', 'clang'],
+'darwin': ['clang', 'gcc'],
+'aix': ['xlc', 'gcc', 'clang'],
+'linux': ['gcc', 'clang', 'icc'],
+'sunos': ['suncc', 'gcc'],
+'irix': ['gcc', 'irixcc'],
+'hpux': ['gcc'],
+'osf1V': ['gcc'],
+'gnu': ['gcc', 'clang'],
+'java': ['gcc', 'msvc', 'clang', 'icc'],
+'gnukfreebsd': ['gcc', 'clang'],
+'default': ['clang', 'gcc'],
}
"""
Dict mapping platform names to Waf tools finding specific C compilers::
diff --git a/waflib/Tools/compiler_cxx.py b/waflib/Tools/compiler_cxx.py
index 1af65a226..42658c584 100644
--- a/waflib/Tools/compiler_cxx.py
+++ b/waflib/Tools/compiler_cxx.py
@@ -37,18 +37,19 @@ def build(bld):
from waflib.Logs import debug
cxx_compiler = {
-'win32': ['msvc', 'g++', 'clang++'],
-'cygwin': ['g++'],
-'darwin': ['clang++', 'g++'],
-'aix': ['xlc++', 'g++', 'clang++'],
-'linux': ['g++', 'clang++', 'icpc'],
-'sunos': ['sunc++', 'g++'],
-'irix': ['g++'],
-'hpux': ['g++'],
-'osf1V': ['g++'],
-'gnu': ['g++', 'clang++'],
-'java': ['g++', 'msvc', 'clang++', 'icpc'],
-'default': ['clang++', 'g++']
+'win32': ['msvc', 'g++', 'clang++'],
+'cygwin': ['g++', 'clang++'],
+'darwin': ['clang++', 'g++'],
+'aix': ['xlc++', 'g++', 'clang++'],
+'linux': ['g++', 'clang++', 'icpc'],
+'sunos': ['sunc++', 'g++'],
+'irix': ['g++'],
+'hpux': ['g++'],
+'osf1V': ['g++'],
+'gnu': ['g++', 'clang++'],
+'java': ['g++', 'msvc', 'clang++', 'icpc'],
+'gnukfreebsd': ['g++', 'clang++'],
+'default': ['clang++', 'g++']
}
"""
Dict mapping the platform names to Waf tools finding specific C++ compilers::
diff --git a/waflib/Tools/irixcc.py b/waflib/Tools/irixcc.py
index c3ae1ac91..0335c13cb 100644
--- a/waflib/Tools/irixcc.py
+++ b/waflib/Tools/irixcc.py
@@ -13,22 +13,11 @@
@conf
def find_irixcc(conf):
v = conf.env
- cc = None
- if v.CC:
- cc = v.CC
- elif 'CC' in conf.environ:
- cc = conf.environ['CC']
- if not cc:
- cc = conf.find_program('cc', var='CC')
- if not cc:
- conf.fatal('irixcc was not found')
-
+ cc = conf.find_program('cc', var='CC')
try:
conf.cmd_and_log(cc + ['-version'])
except Errors.WafError:
conf.fatal('%r -version could not be executed' % cc)
-
- v.CC = cc
v.CC_NAME = 'irix'
@conf
@@ -57,7 +46,6 @@ def irixcc_common_flags(conf):
def configure(conf):
conf.find_irixcc()
- conf.find_cpp()
conf.find_ar()
conf.irixcc_common_flags()
conf.cc_load_tools()
diff --git a/waflib/Tools/msvc.py b/waflib/Tools/msvc.py
index 17b347d45..d60f67026 100644
--- a/waflib/Tools/msvc.py
+++ b/waflib/Tools/msvc.py
@@ -99,10 +99,31 @@ def build(bld):
"""List of icl platforms"""
def options(opt):
- opt.add_option('--msvc_version', type='string', help = 'msvc version, eg: "msvc 10.0,msvc 9.0"', default='')
+ default_ver = ''
+ vsver = os.getenv('VSCMD_VER')
+ if vsver:
+ m = re.match(r'(^\d+\.\d+).*', vsver)
+ if m:
+ default_ver = 'msvc %s' % m.group(1)
+ opt.add_option('--msvc_version', type='string', help = 'msvc version, eg: "msvc 10.0,msvc 9.0"', default=default_ver)
opt.add_option('--msvc_targets', type='string', help = 'msvc targets, eg: "x64,arm"', default='')
opt.add_option('--no-msvc-lazy', action='store_false', help = 'lazily check msvc target environments', default=True, dest='msvc_lazy')
+class MSVCVersion(object):
+ def __init__(self, ver):
+ m = re.search(r'^(.*)\s+(\d+[.]\d+)', ver)
+ if m:
+ self.name = m.group(1)
+ self.number = float(m.group(2))
+ else:
+ self.name = ver
+ self.number = 0.
+
+ def __lt__(self, other):
+ if self.number == other.number:
+ return self.name < other.name
+ return self.number < other.number
+
@conf
def setup_msvc(conf, versiondict):
"""
@@ -119,7 +140,7 @@ def setup_msvc(conf, versiondict):
platforms=Utils.to_list(conf.env.MSVC_TARGETS) or [i for i,j in all_msvc_platforms+all_icl_platforms+all_wince_platforms]
desired_versions = getattr(Options.options, 'msvc_version', '').split(',')
if desired_versions == ['']:
- desired_versions = conf.env.MSVC_VERSIONS or list(reversed(sorted(versiondict.keys())))
+ desired_versions = conf.env.MSVC_VERSIONS or list(sorted(versiondict.keys(), key=MSVCVersion, reverse=True))
# Override lazy detection by evaluating after the fact.
lazy_detect = getattr(Options.options, 'msvc_lazy', True)
@@ -187,7 +208,7 @@ def get_msvc_version(conf, compiler, version, target, vcvars):
echo INCLUDE=%%INCLUDE%%
echo LIB=%%LIB%%;%%LIBPATH%%
""" % (vcvars,target))
- sout = conf.cmd_and_log(['cmd.exe', '/E:on', '/V:on', '/C', batfile.abspath()])
+ sout = conf.cmd_and_log(['cmd.exe', '/E:on', '/V:on', '/C', batfile.abspath()], stdin=getattr(Utils.subprocess, 'DEVNULL', None))
lines = sout.splitlines()
if not lines[0]:
@@ -281,7 +302,7 @@ def gather_wince_supported_platforms():
def gather_msvc_detected_versions():
#Detected MSVC versions!
- version_pattern = re.compile('^(\d\d?\.\d\d?)(Exp)?$')
+ version_pattern = re.compile(r'^(\d\d?\.\d\d?)(Exp)?$')
detected_versions = []
for vcver,vcvar in (('VCExpress','Exp'), ('VisualStudio','')):
prefix = 'SOFTWARE\\Wow6432node\\Microsoft\\' + vcver
@@ -367,7 +388,7 @@ def gather_wsdk_versions(conf, versions):
:param versions: list to modify
:type versions: list
"""
- version_pattern = re.compile('^v..?.?\...?.?')
+ version_pattern = re.compile(r'^v..?.?\...?.?')
try:
all_versions = Utils.winreg.OpenKey(Utils.winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Wow6432node\\Microsoft\\Microsoft SDKs\\Windows')
except OSError:
@@ -525,7 +546,7 @@ def gather_icl_versions(conf, versions):
:param versions: list to modify
:type versions: list
"""
- version_pattern = re.compile('^...?.?\....?.?')
+ version_pattern = re.compile(r'^...?.?\....?.?')
try:
all_versions = Utils.winreg.OpenKey(Utils.winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Wow6432node\\Intel\\Compilers\\C++')
except OSError:
@@ -579,7 +600,7 @@ def gather_intel_composer_versions(conf, versions):
:param versions: list to modify
:type versions: list
"""
- version_pattern = re.compile('^...?.?\...?.?.?')
+ version_pattern = re.compile(r'^...?.?\...?.?.?')
try:
all_versions = Utils.winreg.OpenKey(Utils.winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Wow6432node\\Intel\\Suites')
except OSError:
@@ -683,7 +704,7 @@ def find_lt_names_msvc(self, libname, is_static=False):
if not is_static and ltdict.get('library_names', ''):
dllnames=ltdict['library_names'].split()
dll=dllnames[0].lower()
- dll=re.sub('\.dll$', '', dll)
+ dll=re.sub(r'\.dll$', '', dll)
return (lt_libdir, dll, False)
elif ltdict.get('old_library', ''):
olib=ltdict['old_library']
@@ -700,7 +721,7 @@ def find_lt_names_msvc(self, libname, is_static=False):
@conf
def libname_msvc(self, libname, is_static=False):
lib = libname.lower()
- lib = re.sub('\.lib$','',lib)
+ lib = re.sub(r'\.lib$','',lib)
if lib in g_msvc_systemlibs:
return lib
@@ -747,11 +768,11 @@ def libname_msvc(self, libname, is_static=False):
for libn in libnames:
if os.path.exists(os.path.join(path, libn)):
Logs.debug('msvc: lib found: %s', os.path.join(path,libn))
- return re.sub('\.lib$', '',libn)
+ return re.sub(r'\.lib$', '',libn)
#if no lib can be found, just return the libname as msvc expects it
self.fatal('The library %r could not be found' % libname)
- return re.sub('\.lib$', '', libname)
+ return re.sub(r'\.lib$', '', libname)
@conf
def check_lib_msvc(self, libname, is_static=False, uselib_store=None):
@@ -969,7 +990,7 @@ def build(bld):
if not is_static:
for f in self.env.LINKFLAGS:
d = f.lower()
- if d[1:] == 'debug':
+ if d[1:] in ('debug', 'debug:full', 'debug:fastlink'):
pdbnode = self.link_task.outputs[0].change_ext('.pdb')
self.link_task.outputs.append(pdbnode)
diff --git a/waflib/Tools/waf_unit_test.py b/waflib/Tools/waf_unit_test.py
index 74d6c0561..8cff89bde 100644
--- a/waflib/Tools/waf_unit_test.py
+++ b/waflib/Tools/waf_unit_test.py
@@ -97,6 +97,7 @@ def make_interpreted_test(self):
if isinstance(v, str):
v = v.split(os.pathsep)
self.ut_env[k] = os.pathsep.join(p + v)
+ self.env.append_value('UT_DEPS', ['%r%r' % (key, self.ut_env[key]) for key in self.ut_env])
@feature('test')
@after_method('apply_link', 'process_use')
@@ -108,7 +109,8 @@ def make_test(self):
tsk = self.create_task('utest', self.link_task.outputs)
if getattr(self, 'ut_str', None):
self.ut_run, lst = Task.compile_fun(self.ut_str, shell=getattr(self, 'ut_shell', False))
- tsk.vars = lst + tsk.vars
+ tsk.vars = tsk.vars + lst
+ self.env.append_value('UT_DEPS', self.ut_str)
self.handle_ut_cwd('ut_cwd')
@@ -139,6 +141,10 @@ def add_path(var):
if not hasattr(self, 'ut_cmd'):
self.ut_cmd = getattr(Options.options, 'testcmd', False)
+ self.env.append_value('UT_DEPS', str(self.ut_cmd))
+ self.env.append_value('UT_DEPS', self.ut_paths)
+ self.env.append_value('UT_DEPS', ['%r%r' % (key, self.ut_env[key]) for key in self.ut_env])
+
@taskgen_method
def add_test_results(self, tup):
"""Override and return tup[1] to interrupt the build immediately if a test does not run"""
@@ -159,7 +165,7 @@ class utest(Task.Task):
"""
color = 'PINK'
after = ['vnum', 'inst']
- vars = []
+ vars = ['UT_DEPS']
def runnable_status(self):
"""
@@ -200,7 +206,7 @@ def run(self):
self.ut_exec = getattr(self.generator, 'ut_exec', [self.inputs[0].abspath()])
ut_cmd = getattr(self.generator, 'ut_cmd', False)
if ut_cmd:
- self.ut_exec = shlex.split(ut_cmd % ' '.join(self.ut_exec))
+ self.ut_exec = shlex.split(ut_cmd % Utils.shell_escape(self.ut_exec))
return self.exec_command(self.ut_exec)
@@ -214,7 +220,7 @@ def exec_command(self, cmd, **kw):
'cmd': cmd
}
script_file = self.inputs[0].abspath() + '_run.py'
- Utils.writef(script_file, script_code)
+ Utils.writef(script_file, script_code, encoding='utf-8')
os.chmod(script_file, Utils.O755)
if Logs.verbose > 1:
Logs.info('Test debug file written as %r' % script_file)
diff --git a/waflib/Utils.py b/waflib/Utils.py
index a0cc2a09d..ea0f7a9db 100644
--- a/waflib/Utils.py
+++ b/waflib/Utils.py
@@ -11,7 +11,7 @@
from __future__ import with_statement
-import atexit, os, sys, errno, inspect, re, datetime, platform, base64, signal, functools, time
+import atexit, os, sys, errno, inspect, re, datetime, platform, base64, signal, functools, time, shlex
try:
import cPickle
@@ -49,10 +49,16 @@ class TimeoutExpired(Exception):
from hashlib import md5
except ImportError:
try:
- from md5 import md5
+ from hashlib import sha1 as md5
except ImportError:
- # never fail to enable fixes from another module
+ # never fail to enable potential fixes from another module
pass
+else:
+ try:
+ md5().digest()
+ except ValueError:
+ # Fips? #2213
+ from hashlib import sha1 as md5
try:
import threading
@@ -202,7 +208,7 @@ def __next__(self):
next = __next__
-is_win32 = os.sep == '\\' or sys.platform == 'win32' # msys2
+is_win32 = os.sep == '\\' or sys.platform == 'win32' or os.name == 'nt' # msys2
"""
Whether this system is a Windows series
"""
@@ -446,6 +452,8 @@ def console_encoding():
pass
else:
if codepage:
+ if 65001 == codepage and sys.version_info < (3, 3):
+ return 'utf-8'
return 'cp%d' % codepage
return sys.stdout.encoding or ('cp1252' if is_win32 else 'latin-1')
@@ -484,7 +492,9 @@ def split_path_msys(path):
if sys.platform == 'cygwin':
split_path = split_path_cygwin
elif is_win32:
- if os.environ.get('MSYSTEM'):
+ # Consider this an MSYSTEM environment if $MSYSTEM is set and python
+ # reports is executable from a unix like path on a windows host.
+ if os.environ.get('MSYSTEM') and sys.executable.startswith('/'):
split_path = split_path_msys
else:
split_path = split_path_win32
@@ -569,10 +579,13 @@ def quote_define_name(s):
fu = fu.upper()
return fu
-re_sh = re.compile('\\s|\'|"')
-"""
-Regexp used for shell_escape below
-"""
+# shlex.quote didn't exist until python 3.3. Prior to that it was a non-documented
+# function in pipes.
+try:
+ shell_quote = shlex.quote
+except AttributeError:
+ import pipes
+ shell_quote = pipes.quote
def shell_escape(cmd):
"""
@@ -581,7 +594,7 @@ def shell_escape(cmd):
"""
if isinstance(cmd, str):
return cmd
- return ' '.join(repr(x) if re_sh.search(x) else x for x in cmd)
+ return ' '.join(shell_quote(x) for x in cmd)
def h_list(lst):
"""
@@ -596,6 +609,12 @@ def h_list(lst):
"""
return md5(repr(lst).encode()).digest()
+if sys.hexversion < 0x3000000:
+ def h_list_python2(lst):
+ return md5(repr(lst)).digest()
+ h_list_python2.__doc__ = h_list.__doc__
+ h_list = h_list_python2
+
def h_fun(fun):
"""
Hash functions
@@ -615,7 +634,7 @@ def h_fun(fun):
#
# The sorting result outcome will be consistent because:
# 1. tuples are compared in order of their elements
- # 2. optional argument names are unique
+ # 2. optional argument namess are unique
code.extend(sorted(fun.keywords.items()))
code.append(h_fun(fun.func))
fun.code = h_list(code)
@@ -730,7 +749,7 @@ def unversioned_sys_platform():
if s == 'cli' and os.name == 'nt':
# ironpython is only on windows as far as we know
return 'win32'
- return re.split('\d+$', s)[0]
+ return re.split(r'\d+$', s)[0]
def nada(*k, **kw):
"""
@@ -851,6 +870,19 @@ def lib64():
return '64'
return ''
+def loose_version(ver_str):
+ # private for the time being!
+ # see #2402
+ lst = re.split(r'([.]|\\d+|[a-zA-Z])', ver_str)
+ ver = []
+ for i, val in enumerate(lst):
+ try:
+ ver.append(int(val))
+ except ValueError:
+ if val != '.':
+ ver.append(val)
+ return ver
+
def sane_path(p):
# private function for the time being!
return os.path.abspath(os.path.expanduser(p))
@@ -871,13 +903,13 @@ def get_process():
except IndexError:
filepath = os.path.dirname(os.path.abspath(__file__)) + os.sep + 'processor.py'
cmd = [sys.executable, '-c', readf(filepath)]
- return subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, bufsize=0)
+ return subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, bufsize=0, close_fds=not is_win32)
def run_prefork_process(cmd, kwargs, cargs):
"""
Delegates process execution to a pre-forked process instance.
"""
- if not 'env' in kwargs:
+ if not kwargs.get('env'):
kwargs['env'] = dict(os.environ)
try:
obj = base64.b64encode(cPickle.dumps([cmd, kwargs, cargs]))
diff --git a/waflib/ansiterm.py b/waflib/ansiterm.py
index 0d20c6374..027f0ad68 100644
--- a/waflib/ansiterm.py
+++ b/waflib/ansiterm.py
@@ -264,7 +264,7 @@ def hide_cursor(self,param):
'u': pop_cursor,
}
# Match either the escape sequence or text not containing escape sequence
- ansi_tokens = re.compile('(?:\x1b\[([0-9?;]*)([a-zA-Z])|([^\x1b]+))')
+ ansi_tokens = re.compile(r'(?:\x1b\[([0-9?;]*)([a-zA-Z])|([^\x1b]+))')
def write(self, text):
try:
wlock.acquire()
diff --git a/waflib/extras/clang_cross.py b/waflib/extras/clang_cross.py
new file mode 100644
index 000000000..1b51e2886
--- /dev/null
+++ b/waflib/extras/clang_cross.py
@@ -0,0 +1,92 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Krzysztof Kosiński 2014
+# DragoonX6 2018
+
+"""
+Detect the Clang C compiler
+This version is an attempt at supporting the -target and -sysroot flag of Clang.
+"""
+
+from waflib.Tools import ccroot, ar, gcc
+from waflib.Configure import conf
+import waflib.Context
+import waflib.extras.clang_cross_common
+
+def options(opt):
+ """
+ Target triplet for clang::
+ $ waf configure --clang-target-triple=x86_64-pc-linux-gnu
+ """
+ cc_compiler_opts = opt.add_option_group('Configuration options')
+ cc_compiler_opts.add_option('--clang-target-triple', default=None,
+ help='Target triple for clang',
+ dest='clang_target_triple')
+ cc_compiler_opts.add_option('--clang-sysroot', default=None,
+ help='Sysroot for clang',
+ dest='clang_sysroot')
+
+@conf
+def find_clang(conf):
+ """
+ Finds the program clang and executes it to ensure it really is clang
+ """
+
+ import os
+
+ cc = conf.find_program('clang', var='CC')
+
+ if conf.options.clang_target_triple != None:
+ conf.env.append_value('CC', ['-target', conf.options.clang_target_triple])
+
+ if conf.options.clang_sysroot != None:
+ sysroot = str()
+
+ if os.path.isabs(conf.options.clang_sysroot):
+ sysroot = conf.options.clang_sysroot
+ else:
+ sysroot = os.path.normpath(os.path.join(os.getcwd(), conf.options.clang_sysroot))
+
+ conf.env.append_value('CC', ['--sysroot', sysroot])
+
+ conf.get_cc_version(cc, clang=True)
+ conf.env.CC_NAME = 'clang'
+
+@conf
+def clang_modifier_x86_64_w64_mingw32(conf):
+ conf.gcc_modifier_win32()
+
+@conf
+def clang_modifier_i386_w64_mingw32(conf):
+ conf.gcc_modifier_win32()
+
+@conf
+def clang_modifier_x86_64_windows_msvc(conf):
+ conf.clang_modifier_msvc()
+
+ # Allow the user to override any flags if they so desire.
+ clang_modifier_user_func = getattr(conf, 'clang_modifier_x86_64_windows_msvc_user', None)
+ if clang_modifier_user_func:
+ clang_modifier_user_func()
+
+@conf
+def clang_modifier_i386_windows_msvc(conf):
+ conf.clang_modifier_msvc()
+
+ # Allow the user to override any flags if they so desire.
+ clang_modifier_user_func = getattr(conf, 'clang_modifier_i386_windows_msvc_user', None)
+ if clang_modifier_user_func:
+ clang_modifier_user_func()
+
+def configure(conf):
+ conf.find_clang()
+ conf.find_program(['llvm-ar', 'ar'], var='AR')
+ conf.find_ar()
+ conf.gcc_common_flags()
+ # Allow the user to provide flags for the target platform.
+ conf.gcc_modifier_platform()
+ # And allow more fine grained control based on the compiler's triplet.
+ conf.clang_modifier_target_triple()
+ conf.cc_load_tools()
+ conf.cc_add_flags()
+ conf.link_add_flags()
diff --git a/waflib/extras/clang_cross_common.py b/waflib/extras/clang_cross_common.py
new file mode 100644
index 000000000..b76a07006
--- /dev/null
+++ b/waflib/extras/clang_cross_common.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# DragoonX6 2018
+
+"""
+Common routines for cross_clang.py and cross_clangxx.py
+"""
+
+from waflib.Configure import conf
+import waflib.Context
+
+def normalize_target_triple(target_triple):
+ target_triple = target_triple[:-1]
+ normalized_triple = target_triple.replace('--', '-unknown-')
+
+ if normalized_triple.startswith('-'):
+ normalized_triple = 'unknown' + normalized_triple
+
+ if normalized_triple.endswith('-'):
+ normalized_triple += 'unknown'
+
+ # Normalize MinGW builds to *arch*-w64-mingw32
+ if normalized_triple.endswith('windows-gnu'):
+ normalized_triple = normalized_triple[:normalized_triple.index('-')] + '-w64-mingw32'
+
+ # Strip the vendor when doing msvc builds, since it's unused anyway.
+ if normalized_triple.endswith('windows-msvc'):
+ normalized_triple = normalized_triple[:normalized_triple.index('-')] + '-windows-msvc'
+
+ return normalized_triple.replace('-', '_')
+
+@conf
+def clang_modifier_msvc(conf):
+ import os
+
+ """
+ Really basic setup to use clang in msvc mode.
+ We actually don't really want to do a lot, even though clang is msvc compatible
+ in this mode, that doesn't mean we're actually using msvc.
+ It's probably the best to leave it to the user, we can assume msvc mode if the user
+ uses the clang-cl frontend, but this module only concerns itself with the gcc-like frontend.
+ """
+ v = conf.env
+ v.cprogram_PATTERN = '%s.exe'
+
+ v.cshlib_PATTERN = '%s.dll'
+ v.implib_PATTERN = '%s.lib'
+ v.IMPLIB_ST = '-Wl,-IMPLIB:%s'
+ v.SHLIB_MARKER = []
+
+ v.CFLAGS_cshlib = []
+ v.LINKFLAGS_cshlib = ['-Wl,-DLL']
+ v.cstlib_PATTERN = '%s.lib'
+ v.STLIB_MARKER = []
+
+ del(v.AR)
+ conf.find_program(['llvm-lib', 'lib'], var='AR')
+ v.ARFLAGS = ['-nologo']
+ v.AR_TGT_F = ['-out:']
+
+ # Default to the linker supplied with llvm instead of link.exe or ld
+ v.LINK_CC = v.CC + ['-fuse-ld=lld', '-nostdlib']
+ v.CCLNK_TGT_F = ['-o']
+ v.def_PATTERN = '-Wl,-def:%s'
+
+ v.LINKFLAGS = []
+
+ v.LIB_ST = '-l%s'
+ v.LIBPATH_ST = '-Wl,-LIBPATH:%s'
+ v.STLIB_ST = '-l%s'
+ v.STLIBPATH_ST = '-Wl,-LIBPATH:%s'
+
+ CFLAGS_CRT_COMMON = [
+ '-Xclang', '--dependent-lib=oldnames',
+ '-Xclang', '-fno-rtti-data',
+ '-D_MT'
+ ]
+
+ v.CFLAGS_CRT_MULTITHREADED = CFLAGS_CRT_COMMON + [
+ '-Xclang', '-flto-visibility-public-std',
+ '-Xclang', '--dependent-lib=libcmt',
+ ]
+ v.CXXFLAGS_CRT_MULTITHREADED = v.CFLAGS_CRT_MULTITHREADED
+
+ v.CFLAGS_CRT_MULTITHREADED_DBG = CFLAGS_CRT_COMMON + [
+ '-D_DEBUG',
+ '-Xclang', '-flto-visibility-public-std',
+ '-Xclang', '--dependent-lib=libcmtd',
+ ]
+ v.CXXFLAGS_CRT_MULTITHREADED_DBG = v.CFLAGS_CRT_MULTITHREADED_DBG
+
+ v.CFLAGS_CRT_MULTITHREADED_DLL = CFLAGS_CRT_COMMON + [
+ '-D_DLL',
+ '-Xclang', '--dependent-lib=msvcrt'
+ ]
+ v.CXXFLAGS_CRT_MULTITHREADED_DLL = v.CFLAGS_CRT_MULTITHREADED_DLL
+
+ v.CFLAGS_CRT_MULTITHREADED_DLL_DBG = CFLAGS_CRT_COMMON + [
+ '-D_DLL',
+ '-D_DEBUG',
+ '-Xclang', '--dependent-lib=msvcrtd',
+ ]
+ v.CXXFLAGS_CRT_MULTITHREADED_DLL_DBG = v.CFLAGS_CRT_MULTITHREADED_DLL_DBG
+
+@conf
+def clang_modifier_target_triple(conf, cpp=False):
+ compiler = conf.env.CXX if cpp else conf.env.CC
+ output = conf.cmd_and_log(compiler + ['-dumpmachine'], output=waflib.Context.STDOUT)
+
+ modifier = ('clangxx' if cpp else 'clang') + '_modifier_'
+ clang_modifier_func = getattr(conf, modifier + normalize_target_triple(output), None)
+ if clang_modifier_func:
+ clang_modifier_func()
diff --git a/waflib/extras/clangxx_cross.py b/waflib/extras/clangxx_cross.py
new file mode 100644
index 000000000..0ad38ad46
--- /dev/null
+++ b/waflib/extras/clangxx_cross.py
@@ -0,0 +1,106 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy 2009-2018 (ita)
+# DragoonX6 2018
+
+"""
+Detect the Clang++ C++ compiler
+This version is an attempt at supporting the -target and -sysroot flag of Clang++.
+"""
+
+from waflib.Tools import ccroot, ar, gxx
+from waflib.Configure import conf
+import waflib.extras.clang_cross_common
+
+def options(opt):
+ """
+ Target triplet for clang++::
+ $ waf configure --clangxx-target-triple=x86_64-pc-linux-gnu
+ """
+ cxx_compiler_opts = opt.add_option_group('Configuration options')
+ cxx_compiler_opts.add_option('--clangxx-target-triple', default=None,
+ help='Target triple for clang++',
+ dest='clangxx_target_triple')
+ cxx_compiler_opts.add_option('--clangxx-sysroot', default=None,
+ help='Sysroot for clang++',
+ dest='clangxx_sysroot')
+
+@conf
+def find_clangxx(conf):
+ """
+ Finds the program clang++, and executes it to ensure it really is clang++
+ """
+
+ import os
+
+ cxx = conf.find_program('clang++', var='CXX')
+
+ if conf.options.clangxx_target_triple != None:
+ conf.env.append_value('CXX', ['-target', conf.options.clangxx_target_triple])
+
+ if conf.options.clangxx_sysroot != None:
+ sysroot = str()
+
+ if os.path.isabs(conf.options.clangxx_sysroot):
+ sysroot = conf.options.clangxx_sysroot
+ else:
+ sysroot = os.path.normpath(os.path.join(os.getcwd(), conf.options.clangxx_sysroot))
+
+ conf.env.append_value('CXX', ['--sysroot', sysroot])
+
+ conf.get_cc_version(cxx, clang=True)
+ conf.env.CXX_NAME = 'clang'
+
+@conf
+def clangxx_modifier_x86_64_w64_mingw32(conf):
+ conf.gcc_modifier_win32()
+
+@conf
+def clangxx_modifier_i386_w64_mingw32(conf):
+ conf.gcc_modifier_win32()
+
+@conf
+def clangxx_modifier_msvc(conf):
+ v = conf.env
+ v.cxxprogram_PATTERN = v.cprogram_PATTERN
+ v.cxxshlib_PATTERN = v.cshlib_PATTERN
+
+ v.CXXFLAGS_cxxshlib = []
+ v.LINKFLAGS_cxxshlib = v.LINKFLAGS_cshlib
+ v.cxxstlib_PATTERN = v.cstlib_PATTERN
+
+ v.LINK_CXX = v.CXX + ['-fuse-ld=lld', '-nostdlib']
+ v.CXXLNK_TGT_F = v.CCLNK_TGT_F
+
+@conf
+def clangxx_modifier_x86_64_windows_msvc(conf):
+ conf.clang_modifier_msvc()
+ conf.clangxx_modifier_msvc()
+
+ # Allow the user to override any flags if they so desire.
+ clang_modifier_user_func = getattr(conf, 'clangxx_modifier_x86_64_windows_msvc_user', None)
+ if clang_modifier_user_func:
+ clang_modifier_user_func()
+
+@conf
+def clangxx_modifier_i386_windows_msvc(conf):
+ conf.clang_modifier_msvc()
+ conf.clangxx_modifier_msvc()
+
+ # Allow the user to override any flags if they so desire.
+ clang_modifier_user_func = getattr(conf, 'clangxx_modifier_i386_windows_msvc_user', None)
+ if clang_modifier_user_func:
+ clang_modifier_user_func()
+
+def configure(conf):
+ conf.find_clangxx()
+ conf.find_program(['llvm-ar', 'ar'], var='AR')
+ conf.find_ar()
+ conf.gxx_common_flags()
+ # Allow the user to provide flags for the target platform.
+ conf.gxx_modifier_platform()
+ # And allow more fine grained control based on the compiler's triplet.
+ conf.clang_modifier_target_triple(cpp=True)
+ conf.cxx_load_tools()
+ conf.cxx_add_flags()
+ conf.link_add_flags()
diff --git a/waflib/extras/classic_runner.py b/waflib/extras/classic_runner.py
new file mode 100644
index 000000000..b08c794e8
--- /dev/null
+++ b/waflib/extras/classic_runner.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2021 (ita)
+
+from waflib import Utils, Runner
+
+"""
+Re-enable the classic threading system from waf 1.x
+
+def configure(conf):
+ conf.load('classic_runner')
+"""
+
+class TaskConsumer(Utils.threading.Thread):
+ """
+ Task consumers belong to a pool of workers
+
+ They wait for tasks in the queue and then use ``task.process(...)``
+ """
+ def __init__(self, spawner):
+ Utils.threading.Thread.__init__(self)
+ """
+ Obtain :py:class:`waflib.Task.TaskBase` instances from this queue.
+ """
+ self.spawner = spawner
+ self.daemon = True
+ self.start()
+
+ def run(self):
+ """
+ Loop over the tasks to execute
+ """
+ try:
+ self.loop()
+ except Exception:
+ pass
+
+ def loop(self):
+ """
+ Obtain tasks from :py:attr:`waflib.Runner.TaskConsumer.ready` and call
+ :py:meth:`waflib.Task.TaskBase.process`. If the object is a function, execute it.
+ """
+ master = self.spawner.master
+ while 1:
+ if not master.stop:
+ try:
+ tsk = master.ready.get()
+ if tsk:
+ tsk.log_display(tsk.generator.bld)
+ master.process_task(tsk)
+ else:
+ break
+ finally:
+ master.out.put(tsk)
+
+class Spawner(object):
+ """
+ Daemon thread that consumes tasks from :py:class:`waflib.Runner.Parallel` producer and
+ spawns a consuming thread :py:class:`waflib.Runner.Consumer` for each
+ :py:class:`waflib.Task.Task` instance.
+ """
+ def __init__(self, master):
+ self.master = master
+ """:py:class:`waflib.Runner.Parallel` producer instance"""
+
+ self.pool = [TaskConsumer(self) for i in range(master.numjobs)]
+
+Runner.Spawner = Spawner
diff --git a/waflib/extras/color_msvc.py b/waflib/extras/color_msvc.py
new file mode 100644
index 000000000..60bacb7b2
--- /dev/null
+++ b/waflib/extras/color_msvc.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+# encoding: utf-8
+
+# Replaces the default formatter by one which understands MSVC output and colorizes it.
+# Modified from color_gcc.py
+
+__author__ = __maintainer__ = "Alibek Omarov <a1ba.omarov@gmail.com>"
+__copyright__ = "Alibek Omarov, 2019"
+
+import sys
+from waflib import Logs
+
+class ColorMSVCFormatter(Logs.formatter):
+ def __init__(self, colors):
+ self.colors = colors
+ Logs.formatter.__init__(self)
+
+ def parseMessage(self, line, color):
+ # Split messaage from 'disk:filepath: type: message'
+ arr = line.split(':', 3)
+ if len(arr) < 4:
+ return line
+
+ colored = self.colors.BOLD + arr[0] + ':' + arr[1] + ':' + self.colors.NORMAL
+ colored += color + arr[2] + ':' + self.colors.NORMAL
+ colored += arr[3]
+ return colored
+
+ def format(self, rec):
+ frame = sys._getframe()
+ while frame:
+ func = frame.f_code.co_name
+ if func == 'exec_command':
+ cmd = frame.f_locals.get('cmd')
+ if isinstance(cmd, list):
+ # Fix file case, it may be CL.EXE or cl.exe
+ argv0 = cmd[0].lower()
+ if 'cl.exe' in argv0:
+ lines = []
+ # This will not work with "localized" versions
+ # of MSVC
+ for line in rec.msg.splitlines():
+ if ': warning ' in line:
+ lines.append(self.parseMessage(line, self.colors.YELLOW))
+ elif ': error ' in line:
+ lines.append(self.parseMessage(line, self.colors.RED))
+ elif ': fatal error ' in line:
+ lines.append(self.parseMessage(line, self.colors.RED + self.colors.BOLD))
+ elif ': note: ' in line:
+ lines.append(self.parseMessage(line, self.colors.CYAN))
+ else:
+ lines.append(line)
+ rec.msg = "\n".join(lines)
+ frame = frame.f_back
+ return Logs.formatter.format(self, rec)
+
+def options(opt):
+ Logs.log.handlers[0].setFormatter(ColorMSVCFormatter(Logs.colors))
+
diff --git a/waflib/extras/fc_fujitsu.py b/waflib/extras/fc_fujitsu.py
new file mode 100644
index 000000000..cae676c20
--- /dev/null
+++ b/waflib/extras/fc_fujitsu.py
@@ -0,0 +1,52 @@
+#! /usr/bin/env python
+# encoding: utf-8
+# Detection of the Fujitsu Fortran compiler for ARM64FX
+
+import re
+from waflib.Tools import fc,fc_config,fc_scan
+from waflib.Configure import conf
+from waflib.Tools.compiler_fc import fc_compiler
+fc_compiler['linux'].append('fc_fujitsu')
+
+@conf
+def find_fujitsu(conf):
+ fc=conf.find_program(['frtpx'],var='FC')
+ conf.get_fujitsu_version(fc)
+ conf.env.FC_NAME='FUJITSU'
+ conf.env.FC_MOD_CAPITALIZATION='lower'
+
+@conf
+def fujitsu_flags(conf):
+ v=conf.env
+ v['_FCMODOUTFLAGS']=[]
+ v['FCFLAGS_DEBUG']=[]
+ v['FCFLAGS_fcshlib']=[]
+ v['LINKFLAGS_fcshlib']=[]
+ v['FCSTLIB_MARKER']=''
+ v['FCSHLIB_MARKER']=''
+
+@conf
+def get_fujitsu_version(conf,fc):
+ version_re=re.compile(r"frtpx\s*\(FRT\)\s*(?P<major>\d+)\.(?P<minor>\d+)\.",re.I).search
+ cmd=fc+['--version']
+ out,err=fc_config.getoutput(conf,cmd,stdin=False)
+ if out:
+ match=version_re(out)
+ else:
+ match=version_re(err)
+ if not match:
+ return(False)
+ conf.fatal('Could not determine the Fujitsu FRT Fortran compiler version.')
+ else:
+ k=match.groupdict()
+ conf.env['FC_VERSION']=(k['major'],k['minor'])
+
+def configure(conf):
+ conf.find_fujitsu()
+ conf.find_program('ar',var='AR')
+ conf.add_os_flags('ARFLAGS')
+ if not conf.env.ARFLAGS:
+ conf.env.ARFLAGS=['rcs']
+ conf.fc_flags()
+ conf.fc_add_flags()
+ conf.fujitsu_flags()
diff --git a/waflib/extras/fc_nfort.py b/waflib/extras/fc_nfort.py
new file mode 100644
index 000000000..c25886b8e
--- /dev/null
+++ b/waflib/extras/fc_nfort.py
@@ -0,0 +1,52 @@
+#! /usr/bin/env python
+# encoding: utf-8
+# Detection of the NEC Fortran compiler for Aurora Tsubasa
+
+import re
+from waflib.Tools import fc,fc_config,fc_scan
+from waflib.Configure import conf
+from waflib.Tools.compiler_fc import fc_compiler
+fc_compiler['linux'].append('fc_nfort')
+
+@conf
+def find_nfort(conf):
+ fc=conf.find_program(['nfort'],var='FC')
+ conf.get_nfort_version(fc)
+ conf.env.FC_NAME='NFORT'
+ conf.env.FC_MOD_CAPITALIZATION='lower'
+
+@conf
+def nfort_flags(conf):
+ v=conf.env
+ v['_FCMODOUTFLAGS']=[]
+ v['FCFLAGS_DEBUG']=[]
+ v['FCFLAGS_fcshlib']=[]
+ v['LINKFLAGS_fcshlib']=[]
+ v['FCSTLIB_MARKER']=''
+ v['FCSHLIB_MARKER']=''
+
+@conf
+def get_nfort_version(conf,fc):
+ version_re=re.compile(r"nfort\s*\(NFORT\)\s*(?P<major>\d+)\.(?P<minor>\d+)\.",re.I).search
+ cmd=fc+['--version']
+ out,err=fc_config.getoutput(conf,cmd,stdin=False)
+ if out:
+ match=version_re(out)
+ else:
+ match=version_re(err)
+ if not match:
+ return(False)
+ conf.fatal('Could not determine the NEC NFORT Fortran compiler version.')
+ else:
+ k=match.groupdict()
+ conf.env['FC_VERSION']=(k['major'],k['minor'])
+
+def configure(conf):
+ conf.find_nfort()
+ conf.find_program('nar',var='AR')
+ conf.add_os_flags('ARFLAGS')
+ if not conf.env.ARFLAGS:
+ conf.env.ARFLAGS=['rcs']
+ conf.fc_flags()
+ conf.fc_add_flags()
+ conf.nfort_flags()
diff --git a/waflib/extras/genpybind.py b/waflib/extras/genpybind.py
new file mode 100644
index 000000000..ac206ee8a
--- /dev/null
+++ b/waflib/extras/genpybind.py
@@ -0,0 +1,194 @@
+import os
+import pipes
+import subprocess
+import sys
+
+from waflib import Logs, Task, Context
+from waflib.Tools.c_preproc import scan as scan_impl
+# ^-- Note: waflib.extras.gccdeps.scan does not work for us,
+# due to its current implementation:
+# The -MD flag is injected into the {C,CXX}FLAGS environment variable and
+# dependencies are read out in a separate step after compiling by reading
+# the .d file saved alongside the object file.
+# As the genpybind task refers to a header file that is never compiled itself,
+# gccdeps will not be able to extract the list of dependencies.
+
+from waflib.TaskGen import feature, before_method
+
+
+def join_args(args):
+ return " ".join(pipes.quote(arg) for arg in args)
+
+
+def configure(cfg):
+ cfg.load("compiler_cxx")
+ cfg.load("python")
+ cfg.check_python_version(minver=(2, 7))
+ if not cfg.env.LLVM_CONFIG:
+ cfg.find_program("llvm-config", var="LLVM_CONFIG")
+ if not cfg.env.GENPYBIND:
+ cfg.find_program("genpybind", var="GENPYBIND")
+
+ # find clang reasource dir for builtin headers
+ cfg.env.GENPYBIND_RESOURCE_DIR = os.path.join(
+ cfg.cmd_and_log(cfg.env.LLVM_CONFIG + ["--libdir"]).strip(),
+ "clang",
+ cfg.cmd_and_log(cfg.env.LLVM_CONFIG + ["--version"]).strip())
+ if os.path.exists(cfg.env.GENPYBIND_RESOURCE_DIR):
+ cfg.msg("Checking clang resource dir", cfg.env.GENPYBIND_RESOURCE_DIR)
+ else:
+ cfg.fatal("Clang resource dir not found")
+
+
+@feature("genpybind")
+@before_method("process_source")
+def generate_genpybind_source(self):
+ """
+ Run genpybind on the headers provided in `source` and compile/link the
+ generated code instead. This works by generating the code on the fly and
+ swapping the source node before `process_source` is run.
+ """
+ # name of module defaults to name of target
+ module = getattr(self, "module", self.target)
+
+ # create temporary source file in build directory to hold generated code
+ out = "genpybind-%s.%d.cpp" % (module, self.idx)
+ out = self.path.get_bld().find_or_declare(out)
+
+ task = self.create_task("genpybind", self.to_nodes(self.source), out)
+ # used to detect whether CFLAGS or CXXFLAGS should be passed to genpybind
+ task.features = self.features
+ task.module = module
+ # can be used to select definitions to include in the current module
+ # (when header files are shared by more than one module)
+ task.genpybind_tags = self.to_list(getattr(self, "genpybind_tags", []))
+ # additional include directories
+ task.includes = self.to_list(getattr(self, "includes", []))
+ task.genpybind = self.env.GENPYBIND
+
+ # Tell waf to compile/link the generated code instead of the headers
+ # originally passed-in via the `source` parameter. (see `process_source`)
+ self.source = [out]
+
+
+class genpybind(Task.Task): # pylint: disable=invalid-name
+ """
+ Runs genpybind on headers provided as input to this task.
+ Generated code will be written to the first (and only) output node.
+ """
+ quiet = True
+ color = "PINK"
+ scan = scan_impl
+
+ @staticmethod
+ def keyword():
+ return "Analyzing"
+
+ def run(self):
+ if not self.inputs:
+ return
+
+ args = self.find_genpybind() + self._arguments(
+ resource_dir=self.env.GENPYBIND_RESOURCE_DIR)
+
+ output = self.run_genpybind(args)
+
+ # For debugging / log output
+ pasteable_command = join_args(args)
+
+ # write generated code to file in build directory
+ # (will be compiled during process_source stage)
+ (output_node,) = self.outputs
+ output_node.write("// {}\n{}\n".format(
+ pasteable_command.replace("\n", "\n// "), output))
+
+ def find_genpybind(self):
+ return self.genpybind
+
+ def run_genpybind(self, args):
+ bld = self.generator.bld
+
+ kwargs = dict(cwd=bld.variant_dir)
+ if hasattr(bld, "log_command"):
+ bld.log_command(args, kwargs)
+ else:
+ Logs.debug("runner: {!r}".format(args))
+ proc = subprocess.Popen(
+ args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)
+ stdout, stderr = proc.communicate()
+
+ if not isinstance(stdout, str):
+ stdout = stdout.decode(sys.stdout.encoding, errors="replace")
+ if not isinstance(stderr, str):
+ stderr = stderr.decode(sys.stderr.encoding, errors="replace")
+
+ if proc.returncode != 0:
+ bld.fatal(
+ "genpybind returned {code} during the following call:"
+ "\n{command}\n\n{stdout}\n\n{stderr}".format(
+ code=proc.returncode,
+ command=join_args(args),
+ stdout=stdout,
+ stderr=stderr,
+ ))
+
+ if stderr.strip():
+ Logs.debug("non-fatal warnings during genpybind run:\n{}".format(stderr))
+
+ return stdout
+
+ def _include_paths(self):
+ return self.generator.to_incnodes(self.includes + self.env.INCLUDES)
+
+ def _inputs_as_relative_includes(self):
+ include_paths = self._include_paths()
+ relative_includes = []
+ for node in self.inputs:
+ for inc in include_paths:
+ if node.is_child_of(inc):
+ relative_includes.append(node.path_from(inc))
+ break
+ else:
+ self.generator.bld.fatal("could not resolve {}".format(node))
+ return relative_includes
+
+ def _arguments(self, genpybind_parse=None, resource_dir=None):
+ args = []
+ relative_includes = self._inputs_as_relative_includes()
+ is_cxx = "cxx" in self.features
+
+ # options for genpybind
+ args.extend(["--genpybind-module", self.module])
+ if self.genpybind_tags:
+ args.extend(["--genpybind-tag"] + self.genpybind_tags)
+ if relative_includes:
+ args.extend(["--genpybind-include"] + relative_includes)
+ if genpybind_parse:
+ args.extend(["--genpybind-parse", genpybind_parse])
+
+ args.append("--")
+
+ # headers to be processed by genpybind
+ args.extend(node.abspath() for node in self.inputs)
+
+ args.append("--")
+
+ # options for clang/genpybind-parse
+ args.append("-D__GENPYBIND__")
+ args.append("-xc++" if is_cxx else "-xc")
+ has_std_argument = False
+ for flag in self.env["CXXFLAGS" if is_cxx else "CFLAGS"]:
+ flag = flag.replace("-std=gnu", "-std=c")
+ if flag.startswith("-std=c"):
+ has_std_argument = True
+ args.append(flag)
+ if not has_std_argument:
+ args.append("-std=c++14")
+ args.extend("-I{}".format(n.abspath()) for n in self._include_paths())
+ args.extend("-D{}".format(p) for p in self.env.DEFINES)
+
+ # point to clang resource dir, if specified
+ if resource_dir:
+ args.append("-resource-dir={}".format(resource_dir))
+
+ return args
diff --git a/waflib/extras/haxe.py b/waflib/extras/haxe.py
new file mode 100644
index 000000000..4ff374579
--- /dev/null
+++ b/waflib/extras/haxe.py
@@ -0,0 +1,154 @@
+import re
+
+from waflib import Utils, Task, Errors, Logs
+from waflib.Configure import conf
+from waflib.TaskGen import extension, taskgen_method
+
+HAXE_COMPILERS = {
+ 'JS': {'tgt': '--js', 'ext_out': ['.js']},
+ 'LUA': {'tgt': '--lua', 'ext_out': ['.lua']},
+ 'SWF': {'tgt': '--swf', 'ext_out': ['.swf']},
+ 'NEKO': {'tgt': '--neko', 'ext_out': ['.n']},
+ 'PHP': {'tgt': '--php', 'ext_out': ['.php']},
+ 'CPP': {'tgt': '--cpp', 'ext_out': ['.h', '.cpp']},
+ 'CPPIA': {'tgt': '--cppia', 'ext_out': ['.cppia']},
+ 'CS': {'tgt': '--cs', 'ext_out': ['.cs']},
+ 'JAVA': {'tgt': '--java', 'ext_out': ['.java']},
+ 'JVM': {'tgt': '--jvm', 'ext_out': ['.jar']},
+ 'PYTHON': {'tgt': '--python', 'ext_out': ['.py']},
+ 'HL': {'tgt': '--hl', 'ext_out': ['.hl']},
+ 'HLC': {'tgt': '--hl', 'ext_out': ['.h', '.c']},
+}
+
+@conf
+def check_haxe_pkg(self, **kw):
+ self.find_program('haxelib')
+ libs = kw.get('libs')
+ if not libs or not (type(libs) == str or (type(libs) == list and all(isinstance(s, str) for s in libs))):
+ self.fatal('Specify correct libs value in ensure call')
+ return
+ fetch = kw.get('fetch')
+ if not fetch is None and not type(fetch) == bool:
+ self.fatal('Specify correct fetch value in ensure call')
+
+ libs = [libs] if type(libs) == str else libs
+ halt = False
+ for lib in libs:
+ try:
+ self.start_msg('Checking for library %s' % lib)
+ output = self.cmd_and_log(self.env.HAXELIB + ['list', lib])
+ except Errors.WafError:
+ self.end_msg(False)
+ self.fatal('Can\'t run haxelib list, ensuring halted')
+ return
+
+ if lib in output:
+ self.end_msg(lib in output)
+ else:
+ if not fetch:
+ self.end_msg(False)
+ halt = True
+ continue
+ try:
+ status = self.exec_command(self.env.HAXELIB + ['install', lib])
+ if status:
+ self.end_msg(False)
+ self.fatal('Can\'t get %s with haxelib, ensuring halted' % lib)
+ return
+ else:
+ self.end_msg('downloaded', color='YELLOW')
+ except Errors.WafError:
+ self.end_msg(False)
+ self.fatal('Can\'t run haxelib install, ensuring halted')
+ return
+ postfix = kw.get('uselib_store') or lib.upper()
+ self.env.append_unique('LIB_' + postfix, lib)
+
+ if halt:
+ self.fatal('Can\'t find libraries in haxelib list, ensuring halted')
+ return
+
+class haxe(Task.Task):
+ vars = ['HAXE_VERSION', 'HAXE_FLAGS']
+ ext_in = ['.hx']
+
+ def run(self):
+ cmd = self.env.HAXE + self.env.HAXE_FLAGS_DEFAULT + self.env.HAXE_FLAGS
+ return self.exec_command(cmd)
+
+for COMP in HAXE_COMPILERS:
+ # create runners for each compile target
+ type("haxe_" + COMP, (haxe,), {'ext_out': HAXE_COMPILERS[COMP]['ext_out']})
+
+@taskgen_method
+def init_haxe(self):
+ errmsg = '%s not found, specify correct value'
+ try:
+ compiler = HAXE_COMPILERS[self.compiler]
+ comp_tgt = compiler['tgt']
+ comp_mod = '/main.c' if self.compiler == 'HLC' else ''
+ except (AttributeError, KeyError):
+ self.bld.fatal(errmsg % 'COMPILER' + ': ' + ', '.join(HAXE_COMPILERS.keys()))
+ return
+
+ self.env.append_value(
+ 'HAXE_FLAGS',
+ [comp_tgt, self.path.get_bld().make_node(self.target + comp_mod).abspath()])
+ if hasattr(self, 'use'):
+ if not (type(self.use) == str or type(self.use) == list):
+ self.bld.fatal(errmsg % 'USE')
+ return
+ self.use = [self.use] if type(self.use) == str else self.use
+
+ for dep in self.use:
+ if self.env['LIB_' + dep]:
+ for lib in self.env['LIB_' + dep]:
+ self.env.append_value('HAXE_FLAGS', ['-lib', lib])
+
+ if hasattr(self, 'res'):
+ if not type(self.res) == str:
+ self.bld.fatal(errmsg % 'RES')
+ return
+ self.env.append_value('HAXE_FLAGS', ['-D', 'resourcesPath=%s' % self.res])
+
+@extension('.hx')
+def haxe_hook(self, node):
+ if len(self.source) > 1:
+ self.bld.fatal('Use separate task generators for multiple files')
+ return
+
+ src = node
+ tgt = self.path.get_bld().find_or_declare(self.target)
+
+ self.init_haxe()
+ self.create_task('haxe_' + self.compiler, src, tgt)
+
+@conf
+def check_haxe(self, mini=None, maxi=None):
+ self.start_msg('Checking for haxe version')
+ try:
+ curr = re.search(
+ r'(\d+.?)+',
+ self.cmd_and_log(self.env.HAXE + ['-version'])).group()
+ except Errors.WafError:
+ self.end_msg(False)
+ self.fatal('Can\'t get haxe version')
+ return
+
+ if mini and Utils.num2ver(curr) < Utils.num2ver(mini):
+ self.end_msg('wrong', color='RED')
+ self.fatal('%s is too old, need >= %s' % (curr, mini))
+ return
+ if maxi and Utils.num2ver(curr) > Utils.num2ver(maxi):
+ self.end_msg('wrong', color='RED')
+ self.fatal('%s is too new, need <= %s' % (curr, maxi))
+ return
+ self.end_msg(curr, color='GREEN')
+ self.env.HAXE_VERSION = curr
+
+def configure(self):
+ self.env.append_value(
+ 'HAXE_FLAGS_DEFAULT',
+ ['-D', 'no-compilation', '-cp', self.path.abspath()])
+ Logs.warn('Default flags: %s' % ' '.join(self.env.HAXE_FLAGS_DEFAULT))
+ self.find_program('haxe')
diff --git a/waflib/extras/msvc_pdb.py b/waflib/extras/msvc_pdb.py
new file mode 100644
index 000000000..077656b4f
--- /dev/null
+++ b/waflib/extras/msvc_pdb.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Rafaël Kooi 2019
+
+from waflib import TaskGen
+
+@TaskGen.feature('c', 'cxx', 'fc')
+@TaskGen.after_method('propagate_uselib_vars')
+def add_pdb_per_object(self):
+ """For msvc/fortran, specify a unique compile pdb per object, to work
+ around LNK4099. Flags are updated with a unique /Fd flag based on the
+ task output name. This is separate from the link pdb.
+ """
+ if not hasattr(self, 'compiled_tasks'):
+ return
+
+ link_task = getattr(self, 'link_task', None)
+
+ for task in self.compiled_tasks:
+ if task.inputs and task.inputs[0].name.lower().endswith('.rc'):
+ continue
+
+ add_pdb = False
+ for flagname in ('CFLAGS', 'CXXFLAGS', 'FCFLAGS'):
+ # several languages may be used at once
+ for flag in task.env[flagname]:
+ if flag[1:].lower() == 'zi':
+ add_pdb = True
+ break
+
+ if add_pdb:
+ node = task.outputs[0].change_ext('.pdb')
+ pdb_flag = '/Fd:' + node.abspath()
+
+ for flagname in ('CFLAGS', 'CXXFLAGS', 'FCFLAGS'):
+ buf = [pdb_flag]
+ for flag in task.env[flagname]:
+ if flag[1:3] == 'Fd' or flag[1:].lower() == 'fs' or flag[1:].lower() == 'mp':
+ continue
+ buf.append(flag)
+ task.env[flagname] = buf
+
+ if link_task and not node in link_task.dep_nodes:
+ link_task.dep_nodes.append(node)
+ if not node in task.outputs:
+ task.outputs.append(node)
diff --git a/waflib/extras/sphinx.py b/waflib/extras/sphinx.py
new file mode 100644
index 000000000..08f3cfd8a
--- /dev/null
+++ b/waflib/extras/sphinx.py
@@ -0,0 +1,120 @@
+"""Support for Sphinx documentation
+
+This is a wrapper for sphinx-build program. Please note that sphinx-build supports only
+one output format at a time, but the tool can create multiple tasks to handle more.
+The output formats can be passed via the sphinx_output_format, which is an array of
+strings. For backwards compatibility if only one output is needed, it can be passed
+as a single string.
+The default output format is html.
+
+Specific formats can be installed in different directories by specifying the
+install_path_<FORMAT> attribute. If not defined, the standard install_path
+will be used instead.
+
+Example wscript:
+
+def configure(cnf):
+ conf.load('sphinx')
+
+def build(bld):
+ bld(
+ features='sphinx',
+ sphinx_source='sources', # path to source directory
+ sphinx_options='-a -v', # sphinx-build program additional options
+ sphinx_output_format=['html', 'man'], # output format of sphinx documentation
+ install_path_man='${DOCDIR}/man' # put man pages in a specific directory
+ )
+
+"""
+
+from waflib.Node import Node
+from waflib import Utils
+from waflib import Task
+from waflib.TaskGen import feature, after_method
+
+
+def configure(cnf):
+ """Check if sphinx-build program is available and loads gnu_dirs tool."""
+ cnf.find_program('sphinx-build', var='SPHINX_BUILD', mandatory=False)
+ cnf.load('gnu_dirs')
+
+
+@feature('sphinx')
+def build_sphinx(self):
+ """Builds sphinx sources.
+ """
+ if not self.env.SPHINX_BUILD:
+ self.bld.fatal('Program SPHINX_BUILD not defined.')
+ if not getattr(self, 'sphinx_source', None):
+ self.bld.fatal('Attribute sphinx_source not defined.')
+ if not isinstance(self.sphinx_source, Node):
+ self.sphinx_source = self.path.find_node(self.sphinx_source)
+ if not self.sphinx_source:
+ self.bld.fatal('Can\'t find sphinx_source: %r' % self.sphinx_source)
+
+ # In the taskgen we have the complete list of formats
+ Utils.def_attrs(self, sphinx_output_format='html')
+ self.sphinx_output_format = Utils.to_list(self.sphinx_output_format)
+
+ self.env.SPHINX_OPTIONS = getattr(self, 'sphinx_options', [])
+
+ for source_file in self.sphinx_source.ant_glob('**/*'):
+ self.bld.add_manual_dependency(self.sphinx_source, source_file)
+
+ for cfmt in self.sphinx_output_format:
+ sphinx_build_task = self.create_task('SphinxBuildingTask')
+ sphinx_build_task.set_inputs(self.sphinx_source)
+ # In task we keep the specific format this task is generating
+ sphinx_build_task.env.SPHINX_OUTPUT_FORMAT = cfmt
+
+ # the sphinx-build results are in <build + output_format> directory
+ sphinx_build_task.sphinx_output_directory = self.path.get_bld().make_node(cfmt)
+ sphinx_build_task.set_outputs(sphinx_build_task.sphinx_output_directory)
+ sphinx_build_task.sphinx_output_directory.mkdir()
+
+ Utils.def_attrs(sphinx_build_task, install_path=getattr(self, 'install_path_' + cfmt, getattr(self, 'install_path', get_install_path(sphinx_build_task))))
+
+
+def get_install_path(object):
+ if object.env.SPHINX_OUTPUT_FORMAT == 'man':
+ return object.env.MANDIR
+ elif object.env.SPHINX_OUTPUT_FORMAT == 'info':
+ return object.env.INFODIR
+ else:
+ return object.env.DOCDIR
+
+
+class SphinxBuildingTask(Task.Task):
+ color = 'BOLD'
+ run_str = '${SPHINX_BUILD} -M ${SPHINX_OUTPUT_FORMAT} ${SRC} ${TGT} -d ${TGT[0].bld_dir()}/doctrees-${SPHINX_OUTPUT_FORMAT} ${SPHINX_OPTIONS}'
+
+ def keyword(self):
+ return 'Compiling (%s)' % self.env.SPHINX_OUTPUT_FORMAT
+
+ def runnable_status(self):
+
+ for x in self.run_after:
+ if not x.hasrun:
+ return Task.ASK_LATER
+
+ self.signature()
+ ret = Task.Task.runnable_status(self)
+ if ret == Task.SKIP_ME:
+ # in case the files were removed
+ self.add_install()
+ return ret
+
+
+ def post_run(self):
+ self.add_install()
+ return Task.Task.post_run(self)
+
+
+ def add_install(self):
+ nodes = self.sphinx_output_directory.ant_glob('**/*', quiet=True)
+ self.outputs += nodes
+ self.generator.add_install_files(install_to=self.install_path,
+ install_from=nodes,
+ postpone=False,
+ cwd=self.sphinx_output_directory.make_node(self.env.SPHINX_OUTPUT_FORMAT),
+ relative_trick=True)
diff --git a/waflib/extras/wafcache.py b/waflib/extras/wafcache.py
new file mode 100644
index 000000000..30ac3ef51
--- /dev/null
+++ b/waflib/extras/wafcache.py
@@ -0,0 +1,648 @@
+#! /usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2019 (ita)
+
+"""
+Filesystem-based cache system to share and re-use build artifacts
+
+Cache access operations (copy to and from) are delegated to
+independent pre-forked worker subprocesses.
+
+The following environment variables may be set:
+* WAFCACHE: several possibilities:
+ - File cache:
+ absolute path of the waf cache (~/.cache/wafcache_user,
+ where `user` represents the currently logged-in user)
+ - URL to a cache server, for example:
+ export WAFCACHE=http://localhost:8080/files/
+ in that case, GET/POST requests are made to urls of the form
+ http://localhost:8080/files/000000000/0 (cache management is delegated to the server)
+ - GCS, S3 or MINIO bucket
+ gs://my-bucket/ (uses gsutil command line tool or WAFCACHE_CMD)
+ s3://my-bucket/ (uses aws command line tool or WAFCACHE_CMD)
+ minio://my-bucket/ (uses mc command line tool or WAFCACHE_CMD)
+* WAFCACHE_CMD: bucket upload/download command, for example:
+ WAFCACHE_CMD="gsutil cp %{SRC} %{TGT}"
+ Note that the WAFCACHE bucket value is used for the source or destination
+ depending on the operation (upload or download). For example, with:
+ WAFCACHE="gs://mybucket/"
+ the following commands may be run:
+ gsutil cp build/myprogram gs://mybucket/aa/aaaaa/1
+ gsutil cp gs://mybucket/bb/bbbbb/2 build/somefile
+* WAFCACHE_NO_PUSH: if set, disables pushing to the cache
+* WAFCACHE_VERBOSITY: if set, displays more detailed cache operations
+* WAFCACHE_STATS: if set, displays cache usage statistics on exit
+
+File cache specific options:
+ Files are copied using hard links by default; if the cache is located
+ onto another partition, the system switches to file copies instead.
+* WAFCACHE_TRIM_MAX_FOLDER: maximum amount of tasks to cache (1M)
+* WAFCACHE_EVICT_MAX_BYTES: maximum amount of cache size in bytes (10GB)
+* WAFCACHE_EVICT_INTERVAL_MINUTES: minimum time interval to try
+ and trim the cache (3 minutes)
+
+Upload specific options:
+* WAFCACHE_ASYNC_WORKERS: define a number of workers to upload results asynchronously
+ this may improve build performance with many/long file uploads
+ the default is unset (synchronous uploads)
+* WAFCACHE_ASYNC_NOWAIT: do not wait for uploads to complete (default: False)
+ this requires asynchonous uploads to have an effect
+
+Usage::
+
+ def build(bld):
+ bld.load('wafcache')
+ ...
+
+To troubleshoot::
+
+ waf clean build --zone=wafcache
+"""
+
+import atexit, base64, errno, fcntl, getpass, os, re, shutil, sys, time, threading, traceback, urllib3, shlex
+try:
+ import subprocess32 as subprocess
+except ImportError:
+ import subprocess
+
+base_cache = os.path.expanduser('~/.cache/')
+if not os.path.isdir(base_cache):
+ base_cache = '/tmp/'
+default_wafcache_dir = os.path.join(base_cache, 'wafcache_' + getpass.getuser())
+
+CACHE_DIR = os.environ.get('WAFCACHE', default_wafcache_dir)
+WAFCACHE_CMD = os.environ.get('WAFCACHE_CMD')
+TRIM_MAX_FOLDERS = int(os.environ.get('WAFCACHE_TRIM_MAX_FOLDER', 1000000))
+EVICT_INTERVAL_MINUTES = int(os.environ.get('WAFCACHE_EVICT_INTERVAL_MINUTES', 3))
+EVICT_MAX_BYTES = int(os.environ.get('WAFCACHE_EVICT_MAX_BYTES', 10**10))
+WAFCACHE_NO_PUSH = 1 if os.environ.get('WAFCACHE_NO_PUSH') else 0
+WAFCACHE_VERBOSITY = 1 if os.environ.get('WAFCACHE_VERBOSITY') else 0
+WAFCACHE_STATS = 1 if os.environ.get('WAFCACHE_STATS') else 0
+WAFCACHE_ASYNC_WORKERS = os.environ.get('WAFCACHE_ASYNC_WORKERS')
+WAFCACHE_ASYNC_NOWAIT = os.environ.get('WAFCACHE_ASYNC_NOWAIT')
+OK = "ok"
+
+re_waf_cmd = re.compile('(?P<src>%{SRC})|(?P<tgt>%{TGT})')
+
+try:
+ import cPickle
+except ImportError:
+ import pickle as cPickle
+
+if __name__ != '__main__':
+ from waflib import Task, Logs, Utils, Build
+
+def can_retrieve_cache(self):
+ """
+ New method for waf Task classes
+ """
+ if not self.outputs:
+ return False
+
+ self.cached = False
+
+ sig = self.signature()
+ ssig = Utils.to_hex(self.uid() + sig)
+
+ if WAFCACHE_STATS:
+ self.generator.bld.cache_reqs += 1
+
+ files_to = [node.abspath() for node in self.outputs]
+ proc = get_process()
+ err = cache_command(proc, ssig, [], files_to)
+ process_pool.append(proc)
+ if err.startswith(OK):
+ if WAFCACHE_VERBOSITY:
+ Logs.pprint('CYAN', ' Fetched %r from cache' % files_to)
+ else:
+ Logs.debug('wafcache: fetched %r from cache', files_to)
+ if WAFCACHE_STATS:
+ self.generator.bld.cache_hits += 1
+ else:
+ if WAFCACHE_VERBOSITY:
+ Logs.pprint('YELLOW', ' No cache entry %s' % files_to)
+ else:
+ Logs.debug('wafcache: No cache entry %s: %s', files_to, err)
+ return False
+
+ self.cached = True
+ return True
+
+def put_files_cache(self):
+ """
+ New method for waf Task classes
+ """
+ if WAFCACHE_NO_PUSH or getattr(self, 'cached', None) or not self.outputs:
+ return
+
+ files_from = []
+ for node in self.outputs:
+ path = node.abspath()
+ if not os.path.isfile(path):
+ return
+ files_from.append(path)
+
+ bld = self.generator.bld
+ old_sig = self.signature()
+
+ for node in self.inputs:
+ try:
+ del node.ctx.cache_sig[node]
+ except KeyError:
+ pass
+
+ delattr(self, 'cache_sig')
+ sig = self.signature()
+
+ def _async_put_files_cache(bld, ssig, files_from):
+ proc = get_process()
+ if WAFCACHE_ASYNC_WORKERS:
+ with bld.wafcache_lock:
+ if bld.wafcache_stop:
+ process_pool.append(proc)
+ return
+ bld.wafcache_procs.add(proc)
+
+ err = cache_command(proc, ssig, files_from, [])
+ process_pool.append(proc)
+ if err.startswith(OK):
+ if WAFCACHE_VERBOSITY:
+ Logs.pprint('CYAN', ' Successfully uploaded %s to cache' % files_from)
+ else:
+ Logs.debug('wafcache: Successfully uploaded %r to cache', files_from)
+ if WAFCACHE_STATS:
+ bld.cache_puts += 1
+ else:
+ if WAFCACHE_VERBOSITY:
+ Logs.pprint('RED', ' Error caching step results %s: %s' % (files_from, err))
+ else:
+ Logs.debug('wafcache: Error caching results %s: %s', files_from, err)
+
+ if old_sig == sig:
+ ssig = Utils.to_hex(self.uid() + sig)
+ if WAFCACHE_ASYNC_WORKERS:
+ fut = bld.wafcache_executor.submit(_async_put_files_cache, bld, ssig, files_from)
+ bld.wafcache_uploads.append(fut)
+ else:
+ _async_put_files_cache(bld, ssig, files_from)
+ else:
+ Logs.debug('wafcache: skipped %r upload due to late input modifications %r', self.outputs, self.inputs)
+
+ bld.task_sigs[self.uid()] = self.cache_sig
+
+def hash_env_vars(self, env, vars_lst):
+ """
+ Reimplement BuildContext.hash_env_vars so that the resulting hash does not depend on local paths
+ """
+ if not env.table:
+ env = env.parent
+ if not env:
+ return Utils.SIG_NIL
+
+ idx = str(id(env)) + str(vars_lst)
+ try:
+ cache = self.cache_env
+ except AttributeError:
+ cache = self.cache_env = {}
+ else:
+ try:
+ return self.cache_env[idx]
+ except KeyError:
+ pass
+
+ v = str([env[a] for a in vars_lst])
+ v = v.replace(self.srcnode.abspath().__repr__()[:-1], '')
+ m = Utils.md5()
+ m.update(v.encode())
+ ret = m.digest()
+
+ Logs.debug('envhash: %r %r', ret, v)
+
+ cache[idx] = ret
+
+ return ret
+
+def uid(self):
+ """
+ Reimplement Task.uid() so that the signature does not depend on local paths
+ """
+ try:
+ return self.uid_
+ except AttributeError:
+ m = Utils.md5()
+ src = self.generator.bld.srcnode
+ up = m.update
+ up(self.__class__.__name__.encode())
+ for x in self.inputs + self.outputs:
+ up(x.path_from(src).encode())
+ self.uid_ = m.digest()
+ return self.uid_
+
+
+def make_cached(cls):
+ """
+ Enable the waf cache for a given task class
+ """
+ if getattr(cls, 'nocache', None) or getattr(cls, 'has_cache', False):
+ return
+
+ full_name = "%s.%s" % (cls.__module__, cls.__name__)
+ if full_name in ('waflib.Tools.ccroot.vnum', 'waflib.Build.inst'):
+ return
+
+ m1 = getattr(cls, 'run', None)
+ def run(self):
+ if getattr(self, 'nocache', False):
+ return m1(self)
+ if self.can_retrieve_cache():
+ return 0
+ return m1(self)
+ cls.run = run
+
+ m2 = getattr(cls, 'post_run', None)
+ def post_run(self):
+ if getattr(self, 'nocache', False):
+ return m2(self)
+ ret = m2(self)
+ self.put_files_cache()
+ return ret
+ cls.post_run = post_run
+ cls.has_cache = True
+
+process_pool = []
+def get_process():
+ """
+ Returns a worker process that can process waf cache commands
+ The worker process is assumed to be returned to the process pool when unused
+ """
+ try:
+ return process_pool.pop()
+ except IndexError:
+ filepath = os.path.dirname(os.path.abspath(__file__)) + os.sep + 'wafcache.py'
+ cmd = [sys.executable, '-c', Utils.readf(filepath)]
+ return subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, bufsize=0)
+
+def atexit_pool():
+ for proc in process_pool:
+ proc.kill()
+atexit.register(atexit_pool)
+
+def build(bld):
+ """
+ Called during the build process to enable file caching
+ """
+
+ if WAFCACHE_ASYNC_WORKERS:
+ try:
+ num_workers = int(WAFCACHE_ASYNC_WORKERS)
+ except ValueError:
+ Logs.warn('Invalid WAFCACHE_ASYNC_WORKERS specified: %r' % WAFCACHE_ASYNC_WORKERS)
+ else:
+ from concurrent.futures import ThreadPoolExecutor
+ bld.wafcache_executor = ThreadPoolExecutor(max_workers=num_workers)
+ bld.wafcache_uploads = []
+ bld.wafcache_procs = set([])
+ bld.wafcache_stop = False
+ bld.wafcache_lock = threading.Lock()
+
+ def finalize_upload_async(bld):
+ if WAFCACHE_ASYNC_NOWAIT:
+ with bld.wafcache_lock:
+ bld.wafcache_stop = True
+
+ for fut in reversed(bld.wafcache_uploads):
+ fut.cancel()
+
+ for proc in bld.wafcache_procs:
+ proc.kill()
+
+ bld.wafcache_procs.clear()
+ else:
+ Logs.pprint('CYAN', '... waiting for wafcache uploads to complete (%s uploads)' % len(bld.wafcache_uploads))
+ bld.wafcache_executor.shutdown(wait=True)
+ bld.add_post_fun(finalize_upload_async)
+
+ if WAFCACHE_STATS:
+ # Init counter for statistics and hook to print results at the end
+ bld.cache_reqs = bld.cache_hits = bld.cache_puts = 0
+
+ def printstats(bld):
+ hit_ratio = 0
+ if bld.cache_reqs > 0:
+ hit_ratio = (bld.cache_hits / bld.cache_reqs) * 100
+ Logs.pprint('CYAN', ' wafcache stats: %s requests, %s hits (ratio: %.2f%%), %s writes' %
+ (bld.cache_reqs, bld.cache_hits, hit_ratio, bld.cache_puts) )
+ bld.add_post_fun(printstats)
+
+ if process_pool:
+ # already called once
+ return
+
+ # pre-allocation
+ processes = [get_process() for x in range(bld.jobs)]
+ process_pool.extend(processes)
+
+ Task.Task.can_retrieve_cache = can_retrieve_cache
+ Task.Task.put_files_cache = put_files_cache
+ Task.Task.uid = uid
+ Build.BuildContext.hash_env_vars = hash_env_vars
+ for x in reversed(list(Task.classes.values())):
+ make_cached(x)
+
+def cache_command(proc, sig, files_from, files_to):
+ """
+ Create a command for cache worker processes, returns a pickled
+ base64-encoded tuple containing the task signature, a list of files to
+ cache and a list of files files to get from cache (one of the lists
+ is assumed to be empty)
+ """
+ obj = base64.b64encode(cPickle.dumps([sig, files_from, files_to]))
+ proc.stdin.write(obj)
+ proc.stdin.write('\n'.encode())
+ proc.stdin.flush()
+ obj = proc.stdout.readline()
+ if not obj:
+ raise OSError('Preforked sub-process %r died' % proc.pid)
+ return cPickle.loads(base64.b64decode(obj))
+
+try:
+ copyfun = os.link
+except NameError:
+ copyfun = shutil.copy2
+
+def atomic_copy(orig, dest):
+ """
+ Copy files to the cache, the operation is atomic for a given file
+ """
+ global copyfun
+ tmp = dest + '.tmp'
+ up = os.path.dirname(dest)
+ try:
+ os.makedirs(up)
+ except OSError:
+ pass
+
+ try:
+ copyfun(orig, tmp)
+ except OSError as e:
+ if e.errno == errno.EXDEV:
+ copyfun = shutil.copy2
+ copyfun(orig, tmp)
+ else:
+ raise
+ os.rename(tmp, dest)
+
+def lru_trim():
+ """
+ the cache folders take the form:
+ `CACHE_DIR/0b/0b180f82246d726ece37c8ccd0fb1cde2650d7bfcf122ec1f169079a3bfc0ab9`
+ they are listed in order of last access, and then removed
+ until the amount of folders is within TRIM_MAX_FOLDERS and the total space
+ taken by files is less than EVICT_MAX_BYTES
+ """
+ lst = []
+ for up in os.listdir(CACHE_DIR):
+ if len(up) == 2:
+ sub = os.path.join(CACHE_DIR, up)
+ for hval in os.listdir(sub):
+ path = os.path.join(sub, hval)
+
+ size = 0
+ for fname in os.listdir(path):
+ try:
+ size += os.lstat(os.path.join(path, fname)).st_size
+ except OSError:
+ pass
+ lst.append((os.stat(path).st_mtime, size, path))
+
+ lst.sort(key=lambda x: x[0])
+ lst.reverse()
+
+ tot = sum(x[1] for x in lst)
+ while tot > EVICT_MAX_BYTES or len(lst) > TRIM_MAX_FOLDERS:
+ _, tmp_size, path = lst.pop()
+ tot -= tmp_size
+
+ tmp = path + '.remove'
+ try:
+ shutil.rmtree(tmp)
+ except OSError:
+ pass
+ try:
+ os.rename(path, tmp)
+ except OSError:
+ sys.stderr.write('Could not rename %r to %r\n' % (path, tmp))
+ else:
+ try:
+ shutil.rmtree(tmp)
+ except OSError:
+ sys.stderr.write('Could not remove %r\n' % tmp)
+ sys.stderr.write("Cache trimmed: %r bytes in %r folders left\n" % (tot, len(lst)))
+
+
+def lru_evict():
+ """
+ Reduce the cache size
+ """
+ lockfile = os.path.join(CACHE_DIR, 'all.lock')
+ try:
+ st = os.stat(lockfile)
+ except EnvironmentError as e:
+ if e.errno == errno.ENOENT:
+ with open(lockfile, 'w') as f:
+ f.write('')
+ return
+ else:
+ raise
+
+ if st.st_mtime < time.time() - EVICT_INTERVAL_MINUTES * 60:
+ # check every EVICT_INTERVAL_MINUTES minutes if the cache is too big
+ # OCLOEXEC is unnecessary because no processes are spawned
+ fd = os.open(lockfile, os.O_RDWR | os.O_CREAT, 0o755)
+ try:
+ try:
+ fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
+ except EnvironmentError:
+ if WAFCACHE_VERBOSITY:
+ sys.stderr.write('wafcache: another cleaning process is running\n')
+ else:
+ # now dow the actual cleanup
+ lru_trim()
+ os.utime(lockfile, None)
+ finally:
+ os.close(fd)
+
+class netcache(object):
+ def __init__(self):
+ self.http = urllib3.PoolManager()
+
+ def url_of(self, sig, i):
+ return "%s/%s/%s" % (CACHE_DIR, sig, i)
+
+ def upload(self, file_path, sig, i):
+ url = self.url_of(sig, i)
+ with open(file_path, 'rb') as f:
+ file_data = f.read()
+ r = self.http.request('POST', url, timeout=60,
+ fields={ 'file': ('%s/%s' % (sig, i), file_data), })
+ if r.status >= 400:
+ raise OSError("Invalid status %r %r" % (url, r.status))
+
+ def download(self, file_path, sig, i):
+ url = self.url_of(sig, i)
+ with self.http.request('GET', url, preload_content=False, timeout=60) as inf:
+ if inf.status >= 400:
+ raise OSError("Invalid status %r %r" % (url, inf.status))
+ with open(file_path, 'wb') as out:
+ shutil.copyfileobj(inf, out)
+
+ def copy_to_cache(self, sig, files_from, files_to):
+ try:
+ for i, x in enumerate(files_from):
+ if not os.path.islink(x):
+ self.upload(x, sig, i)
+ except Exception:
+ return traceback.format_exc()
+ return OK
+
+ def copy_from_cache(self, sig, files_from, files_to):
+ try:
+ for i, x in enumerate(files_to):
+ self.download(x, sig, i)
+ except Exception:
+ return traceback.format_exc()
+ return OK
+
+class fcache(object):
+ def __init__(self):
+ if not os.path.exists(CACHE_DIR):
+ try:
+ os.makedirs(CACHE_DIR)
+ except OSError:
+ pass
+ if not os.path.exists(CACHE_DIR):
+ raise ValueError('Could not initialize the cache directory')
+
+ def copy_to_cache(self, sig, files_from, files_to):
+ """
+ Copy files to the cache, existing files are overwritten,
+ and the copy is atomic only for a given file, not for all files
+ that belong to a given task object
+ """
+ try:
+ for i, x in enumerate(files_from):
+ dest = os.path.join(CACHE_DIR, sig[:2], sig, str(i))
+ atomic_copy(x, dest)
+ except Exception:
+ return traceback.format_exc()
+ else:
+ # attempt trimming if caching was successful:
+ # we may have things to trim!
+ try:
+ lru_evict()
+ except Exception:
+ return traceback.format_exc()
+ return OK
+
+ def copy_from_cache(self, sig, files_from, files_to):
+ """
+ Copy files from the cache
+ """
+ try:
+ for i, x in enumerate(files_to):
+ orig = os.path.join(CACHE_DIR, sig[:2], sig, str(i))
+ atomic_copy(orig, x)
+
+ # success! update the cache time
+ os.utime(os.path.join(CACHE_DIR, sig[:2], sig), None)
+ except Exception:
+ return traceback.format_exc()
+ return OK
+
+class bucket_cache(object):
+ def bucket_copy(self, source, target):
+ if WAFCACHE_CMD:
+ def replacer(match):
+ if match.group('src'):
+ return source
+ elif match.group('tgt'):
+ return target
+ cmd = [re_waf_cmd.sub(replacer, x) for x in shlex.split(WAFCACHE_CMD)]
+ elif CACHE_DIR.startswith('s3://'):
+ cmd = ['aws', 's3', 'cp', source, target]
+ elif CACHE_DIR.startswith('gs://'):
+ cmd = ['gsutil', 'cp', source, target]
+ else:
+ cmd = ['mc', 'cp', source, target]
+
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out, err = proc.communicate()
+ if proc.returncode:
+ raise OSError('Error copy %r to %r using: %r (exit %r):\n out:%s\n err:%s' % (
+ source, target, cmd, proc.returncode, out.decode(errors='replace'), err.decode(errors='replace')))
+
+ def copy_to_cache(self, sig, files_from, files_to):
+ try:
+ for i, x in enumerate(files_from):
+ dest = os.path.join(CACHE_DIR, sig[:2], sig, str(i))
+ self.bucket_copy(x, dest)
+ except Exception:
+ return traceback.format_exc()
+ return OK
+
+ def copy_from_cache(self, sig, files_from, files_to):
+ try:
+ for i, x in enumerate(files_to):
+ orig = os.path.join(CACHE_DIR, sig[:2], sig, str(i))
+ self.bucket_copy(orig, x)
+ except EnvironmentError:
+ return traceback.format_exc()
+ return OK
+
+def loop(service):
+ """
+ This function is run when this file is run as a standalone python script,
+ it assumes a parent process that will communicate the commands to it
+ as pickled-encoded tuples (one line per command)
+
+ The commands are to copy files to the cache or copy files from the
+ cache to a target destination
+ """
+ # one operation is performed at a single time by a single process
+ # therefore stdin never has more than one line
+ txt = sys.stdin.readline().strip()
+ if not txt:
+ # parent process probably ended
+ sys.exit(1)
+ ret = OK
+
+ [sig, files_from, files_to] = cPickle.loads(base64.b64decode(txt))
+ if files_from:
+ # TODO return early when pushing files upstream
+ ret = service.copy_to_cache(sig, files_from, files_to)
+ elif files_to:
+ # the build process waits for workers to (possibly) obtain files from the cache
+ ret = service.copy_from_cache(sig, files_from, files_to)
+ else:
+ ret = "Invalid command"
+
+ obj = base64.b64encode(cPickle.dumps(ret))
+ sys.stdout.write(obj.decode())
+ sys.stdout.write('\n')
+ sys.stdout.flush()
+
+if __name__ == '__main__':
+ if CACHE_DIR.startswith('s3://') or CACHE_DIR.startswith('gs://') or CACHE_DIR.startswith('minio://'):
+ if CACHE_DIR.startswith('minio://'):
+ CACHE_DIR = CACHE_DIR[8:] # minio doesn't need the protocol part, uses config aliases
+ service = bucket_cache()
+ elif CACHE_DIR.startswith('http'):
+ service = netcache()
+ else:
+ service = fcache()
+ while 1:
+ try:
+ loop(service)
+ except KeyboardInterrupt:
+ break
+
diff --git a/waflib/extras/xcode6.py b/waflib/extras/xcode6.py
index 91bbff181..c5b309120 100644
--- a/waflib/extras/xcode6.py
+++ b/waflib/extras/xcode6.py
@@ -99,7 +99,7 @@ def delete_invalid_values(dct):
...
}
'Release': {
- 'ARCHS' x86_64'
+ 'ARCHS': x86_64'
...
}
}
@@ -163,12 +163,12 @@ def tostring(self, value):
result = result + "\t\t}"
return result
elif isinstance(value, str):
- return "\"%s\"" % value
+ return '"%s"' % value.replace('"', '\\\\\\"')
elif isinstance(value, list):
result = "(\n"
for i in value:
- result = result + "\t\t\t%s,\n" % self.tostring(i)
- result = result + "\t\t)"
+ result = result + "\t\t\t\t%s,\n" % self.tostring(i)
+ result = result + "\t\t\t)"
return result
elif isinstance(value, XCodeNode):
return value._id
@@ -565,13 +565,13 @@ def process_xcode(self):
# Override target specific build settings
bldsettings = {
'HEADER_SEARCH_PATHS': ['$(inherited)'] + self.env['INCPATHS'],
- 'LIBRARY_SEARCH_PATHS': ['$(inherited)'] + Utils.to_list(self.env.LIBPATH) + Utils.to_list(self.env.STLIBPATH) + Utils.to_list(self.env.LIBDIR) ,
+ 'LIBRARY_SEARCH_PATHS': ['$(inherited)'] + Utils.to_list(self.env.LIBPATH) + Utils.to_list(self.env.STLIBPATH) + Utils.to_list(self.env.LIBDIR),
'FRAMEWORK_SEARCH_PATHS': ['$(inherited)'] + Utils.to_list(self.env.FRAMEWORKPATH),
- 'OTHER_LDFLAGS': libs + ' ' + frameworks,
- 'OTHER_LIBTOOLFLAGS': bld.env['LINKFLAGS'],
+ 'OTHER_LDFLAGS': libs + ' ' + frameworks + ' ' + ' '.join(bld.env['LINKFLAGS']),
'OTHER_CPLUSPLUSFLAGS': Utils.to_list(self.env['CXXFLAGS']),
'OTHER_CFLAGS': Utils.to_list(self.env['CFLAGS']),
- 'INSTALL_PATH': []
+ 'INSTALL_PATH': [],
+ 'GCC_PREPROCESSOR_DEFINITIONS': self.env['DEFINES']
}
# Install path
@@ -591,7 +591,7 @@ def process_xcode(self):
# The keys represents different build configuration, e.g. Debug, Release and so on..
# Insert our generated build settings to all configuration names
- keys = set(settings.keys() + bld.env.PROJ_CONFIGURATION.keys())
+ keys = set(settings.keys()) | set(bld.env.PROJ_CONFIGURATION.keys())
for k in keys:
if k in settings:
settings[k].update(bldsettings)
diff --git a/waflib/fixpy2.py b/waflib/fixpy2.py
index 24176e066..c99bff4b9 100644
--- a/waflib/fixpy2.py
+++ b/waflib/fixpy2.py
@@ -56,7 +56,7 @@ def r1(code):
@subst('Runner.py')
def r4(code):
"generator syntax"
- return code.replace('next(self.biter)', 'self.biter.next()')
+ return code.replace('next(self.biter)', 'self.biter.next()').replace('self.daemon = True', 'self.setDaemon(1)')
@subst('Context.py')
def r5(code):
diff --git a/waflib/processor.py b/waflib/processor.py
index 2eecf3bd9..eff2e69ad 100755
--- a/waflib/processor.py
+++ b/waflib/processor.py
@@ -27,6 +27,10 @@ def run():
[cmd, kwargs, cargs] = cPickle.loads(base64.b64decode(txt))
cargs = cargs or {}
+ if not 'close_fds' in kwargs:
+ # workers have no fds
+ kwargs['close_fds'] = False
+
ret = 1
out, err, ex, trace = (None, None, None, None)
try: