From 250420381b1a6974798939ad7104ab1a4b9a9994 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>
---
 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:
