new file mode 100644
@@ -0,0 +1,3480 @@
+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/pull/953/commits/553ca1e649293ef87e96dd3e7621fd87e0b59986
+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:
Fix the following build failure with python 3.12 (which removed imp module): Traceback (most recent call last): File "/home/autobuild/autobuild/instance-7/output-1/build/jack2-1.9.22/./waf", line 166, in <module> from waflib import Scripting File "/home/autobuild/autobuild/instance-7/output-1/build/jack2-1.9.22/waflib/Scripting.py", line 10, in <module> from waflib import Utils, Configure, Logs, Options, ConfigSet, Context, Errors, Build, Node File "/home/autobuild/autobuild/instance-7/output-1/build/jack2-1.9.22/waflib/Configure.py", line 16, in <module> from waflib import ConfigSet, Utils, Options, Logs, Context, Build, Errors File "/home/autobuild/autobuild/instance-7/output-1/build/jack2-1.9.22/waflib/Options.py", line 14, in <module> from waflib import Logs, Utils, Context, Errors File "/home/autobuild/autobuild/instance-7/output-1/build/jack2-1.9.22/waflib/Context.py", line 9, in <module> import os, re, imp, sys ModuleNotFoundError: No module named 'imp' Fixes: 36e635d2d5c0166476858aa239ccbe78e8f2af14 - http://autobuild.buildroot.org/results/1bfe34e10ffdab80647ac01863165e93bcc9b0d8 Signed-off-by: Fabrice Fontaine <fontaine.fabrice@gmail.com> --- package/jack2/0001-Update-to-waf-2-0-26.patch | 3480 +++++++++++++++++ 1 file changed, 3480 insertions(+) create mode 100644 package/jack2/0001-Update-to-waf-2-0-26.patch