Browse Source

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>
pull/947/merge
Nils Philippsen Filipe Coelho <falktx@falktx.com> 3 months ago
parent
commit
250420381b
40 changed files with 2114 additions and 195 deletions
  1. +5
    -2
      waf
  2. +18
    -0
      waflib-macos-mods.patch
  3. +33
    -14
      waflib/Build.py
  4. +1
    -1
      waflib/ConfigSet.py
  5. +32
    -14
      waflib/Configure.py
  6. +17
    -7
      waflib/Context.py
  7. +6
    -3
      waflib/Logs.py
  8. +1
    -2
      waflib/Node.py
  9. +24
    -7
      waflib/Options.py
  10. +19
    -8
      waflib/Runner.py
  11. +22
    -5
      waflib/Scripting.py
  12. +27
    -21
      waflib/Task.py
  13. +6
    -10
      waflib/TaskGen.py
  14. +6
    -4
      waflib/Tools/c_aliases.py
  15. +28
    -9
      waflib/Tools/c_config.py
  16. +3
    -3
      waflib/Tools/c_preproc.py
  17. +13
    -5
      waflib/Tools/c_tests.py
  18. +18
    -2
      waflib/Tools/ccroot.py
  19. +13
    -12
      waflib/Tools/compiler_c.py
  20. +13
    -12
      waflib/Tools/compiler_cxx.py
  21. +1
    -13
      waflib/Tools/irixcc.py
  22. +33
    -12
      waflib/Tools/msvc.py
  23. +10
    -4
      waflib/Tools/waf_unit_test.py
  24. +46
    -14
      waflib/Utils.py
  25. +1
    -1
      waflib/ansiterm.py
  26. +92
    -0
      waflib/extras/clang_cross.py
  27. +113
    -0
      waflib/extras/clang_cross_common.py
  28. +106
    -0
      waflib/extras/clangxx_cross.py
  29. +68
    -0
      waflib/extras/classic_runner.py
  30. +59
    -0
      waflib/extras/color_msvc.py
  31. +52
    -0
      waflib/extras/fc_fujitsu.py
  32. +52
    -0
      waflib/extras/fc_nfort.py
  33. +194
    -0
      waflib/extras/genpybind.py
  34. +154
    -0
      waflib/extras/haxe.py
  35. +46
    -0
      waflib/extras/msvc_pdb.py
  36. +120
    -0
      waflib/extras/sphinx.py
  37. +648
    -0
      waflib/extras/wafcache.py
  38. +9
    -9
      waflib/extras/xcode6.py
  39. +1
    -1
      waflib/fixpy2.py
  40. +4
    -0
      waflib/processor.py

+ 5
- 2
waf View File

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


+ 18
- 0
waflib-macos-mods.patch View File

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

+ 33
- 14
waflib/Build.py View File

@@ -104,7 +104,7 @@ class BuildContext(Context.Context):
"""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 @@ class BuildContext(Context.Context):
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 @@ class inst(Task.Task):
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 @@ class inst(Task.Task):
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 @@ class inst(Task.Task):
# 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 @@ class inst(Task.Task):
"""
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 @@ class inst(Task.Task):
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 @@ class inst(Task.Task):
"""
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 @@ class CleanContext(BuildContext):
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()


+ 1
- 1
waflib/ConfigSet.py View File

@@ -11,7 +11,7 @@ The values put in :py:class:`ConfigSet` must be serializable (dicts, lists, stri

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


+ 32
- 14
waflib/Configure.py View File

@@ -125,7 +125,7 @@ class ConfigurationContext(Context.Context):
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 @@ class ConfigurationContext(Context.Context):
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 run_build(self, *k, **kw):
$ waf configure --confcache

"""
lst = [str(v) for (p, v) in kw.items() if p != 'env']
h = Utils.h_list(lst)
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 run_build(self, *k, **kw):
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 run_build(self, *k, **kw):
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


+ 17
- 7
waflib/Context.py View File

@@ -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 @@ class Context(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 @@ class Context(ctx):
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


+ 6
- 3
waflib/Logs.py View File

@@ -237,7 +237,10 @@ class formatter(logging.Formatter):
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):
"""


+ 1
- 2
waflib/Node.py View File

@@ -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 @@ class Node(object):
:rtype: iterator
"""
dircont = self.listdir()
dircont.sort()

try:
lst = set(self.children.keys())


+ 24
- 7
waflib/Options.py View File

@@ -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 @@ class opt_parser(optparse.OptionParser):
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 @@ class opt_parser(optparse.OptionParser):
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 @@ class OptionsContext(Context.Context):
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 @@ class OptionsContext(Context.Context):
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):


+ 19
- 8
waflib/Runner.py View File

@@ -37,6 +37,8 @@ class PriorityTasks(object):
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 @@ class Consumer(Utils.threading.Thread):
"""Task to execute"""
self.spawner = spawner
"""Coordinator object"""
self.setDaemon(1)
self.daemon = True
self.start()
def run(self):
"""
@@ -96,7 +98,7 @@ class Spawner(Utils.threading.Thread):
""":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 @@ class Parallel(object):
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 @@ class Parallel(object):
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 @@ class Parallel(object):

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


+ 22
- 5
waflib/Scripting.py View File

@@ -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 distclean(ctx):

# 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 distclean(ctx):
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 @@ class Dist(Context.Context):

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 @@ class Dist(Context.Context):
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 autoconfigure(execute_method):
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)


+ 27
- 21
waflib/Task.py View File

@@ -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 @@ class Task(evil):
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):


+ 6
- 10
waflib/TaskGen.py View File

@@ -74,7 +74,7 @@ class task_gen(object):
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 process_rule(self):
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 process_subst(self):
# 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)


+ 6
- 4
waflib/Tools/c_aliases.py View File

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


+ 28
- 9
waflib/Tools/c_config.py View File

@@ -68,6 +68,8 @@ MACRO_TO_DEST_CPU = {
'__s390__' : 's390',
'__sh__' : 'sh',
'__xtensa__' : 'xtensa',
'__e2k__' : 'e2k',
'__riscv' : 'riscv',
}

@conf
@@ -86,6 +88,10 @@ def parse_flags(self, line, uselib_store, env=None, force_static=False, posix=No
: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 parse_flags(self, line, uselib_store, env=None, force_static=False, posix=No
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 parse_flags(self, line, uselib_store, env=None, force_static=False, posix=No
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 parse_flags(self, line, uselib_store, env=None, force_static=False, posix=No
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 multicheck(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


+ 3
- 3
waflib/Tools/c_preproc.py View File

@@ -75,13 +75,13 @@ re_lines = re.compile(
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::


+ 13
- 5
waflib/Tools/c_tests.py View File

@@ -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 @@ class grep_for_endianness(Task.Task):
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]


+ 18
- 2
waflib/Tools/ccroot.py View File

@@ -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 rm_tgt(cls):
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 process_use(self):
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())


+ 13
- 12
waflib/Tools/compiler_c.py View File

@@ -36,18 +36,19 @@ from waflib import Utils
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::


+ 13
- 12
waflib/Tools/compiler_cxx.py View File

@@ -37,18 +37,19 @@ from waflib import Utils
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::


+ 1
- 13
waflib/Tools/irixcc.py View File

@@ -13,22 +13,11 @@ from waflib.Configure import conf
@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()


+ 33
- 12
waflib/Tools/msvc.py View File

@@ -99,10 +99,31 @@ all_icl_platforms = [ ('intel64', 'amd64'), ('em64t', 'amd64'), ('ia32', 'x86'),
"""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 @@ echo PATH=%%PATH%%
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 apply_flags_msvc(self):
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)



+ 10
- 4
waflib/Tools/waf_unit_test.py View File

@@ -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 make_test(self):
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 @@ class utest(Task.Task):
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 @@ class utest(Task.Task):
'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)


+ 46
- 14
waflib/Utils.py View File

@@ -11,7 +11,7 @@ through Python versions 2.5 to 3.X and across different platforms (win32, linux,

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 @@ try:
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 @@ class lazy_generator(object):

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]))


+ 1
- 1
waflib/ansiterm.py View File

@@ -264,7 +264,7 @@ else:
'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()


+ 92
- 0
waflib/extras/clang_cross.py View File

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

+ 113
- 0
waflib/extras/clang_cross_common.py View File

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

+ 106
- 0
waflib/extras/clangxx_cross.py View File

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

+ 68
- 0
waflib/extras/classic_runner.py View File

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

+ 59
- 0
waflib/extras/color_msvc.py