summaryrefslogtreecommitdiffstats
path: root/Tools
diff options
context:
space:
mode:
Diffstat (limited to 'Tools')
-rw-r--r--Tools/__init__.py3
-rw-r--r--Tools/ar.py24
-rw-r--r--Tools/asm.py73
-rw-r--r--Tools/bison.py49
-rw-r--r--Tools/c.py39
-rw-r--r--Tools/c_aliases.py144
-rw-r--r--Tools/c_config.py1351
-rw-r--r--Tools/c_osx.py193
-rw-r--r--Tools/c_preproc.py1091
-rw-r--r--Tools/c_tests.py229
-rw-r--r--Tools/ccroot.py791
-rw-r--r--Tools/clang.py29
-rw-r--r--Tools/clangxx.py30
-rw-r--r--Tools/compiler_c.py110
-rw-r--r--Tools/compiler_cxx.py111
-rw-r--r--Tools/compiler_d.py85
-rw-r--r--Tools/compiler_fc.py73
-rw-r--r--Tools/cs.py211
-rw-r--r--Tools/cxx.py40
-rw-r--r--Tools/d.py97
-rw-r--r--Tools/d_config.py64
-rw-r--r--Tools/d_scan.py211
-rw-r--r--Tools/dbus.py70
-rw-r--r--Tools/dmd.py80
-rw-r--r--Tools/errcheck.py237
-rw-r--r--Tools/fc.py203
-rw-r--r--Tools/fc_config.py488
-rw-r--r--Tools/fc_scan.py120
-rw-r--r--Tools/flex.py62
-rw-r--r--Tools/g95.py66
-rw-r--r--Tools/gas.py18
-rw-r--r--Tools/gcc.py156
-rw-r--r--Tools/gdc.py55
-rw-r--r--Tools/gfortran.py93
-rw-r--r--Tools/glib2.py489
-rw-r--r--Tools/gnu_dirs.py131
-rw-r--r--Tools/gxx.py157
-rw-r--r--Tools/icc.py30
-rw-r--r--Tools/icpc.py30
-rw-r--r--Tools/ifort.py413
-rw-r--r--Tools/intltool.py231
-rw-r--r--Tools/irixcc.py66
-rw-r--r--Tools/javaw.py579
-rw-r--r--Tools/ldc2.py56
-rw-r--r--Tools/lua.py38
-rw-r--r--Tools/md5_tstamp.py38
-rw-r--r--Tools/msvc.py1020
-rw-r--r--Tools/nasm.py26
-rw-r--r--Tools/nobuild.py24
-rw-r--r--Tools/perl.py156
-rw-r--r--Tools/python.py631
-rw-r--r--Tools/qt5.py796
-rw-r--r--Tools/ruby.py186
-rw-r--r--Tools/suncc.py67
-rw-r--r--Tools/suncxx.py67
-rw-r--r--Tools/tex.py543
-rw-r--r--Tools/vala.py355
-rw-r--r--Tools/waf_unit_test.py296
-rw-r--r--Tools/winres.py78
-rw-r--r--Tools/xlc.py65
-rw-r--r--Tools/xlcxx.py65
61 files changed, 13329 insertions, 0 deletions
diff --git a/Tools/__init__.py b/Tools/__init__.py
new file mode 100644
index 0000000..079df35
--- /dev/null
+++ b/Tools/__init__.py
@@ -0,0 +1,3 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2005-2018 (ita)
diff --git a/Tools/ar.py b/Tools/ar.py
new file mode 100644
index 0000000..b39b645
--- /dev/null
+++ b/Tools/ar.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2006-2018 (ita)
+# Ralf Habacker, 2006 (rh)
+
+"""
+The **ar** program creates static libraries. This tool is almost always loaded
+from others (C, C++, D, etc) for static library support.
+"""
+
+from waflib.Configure import conf
+
+@conf
+def find_ar(conf):
+ """Configuration helper used by C/C++ tools to enable the support for static libraries"""
+ conf.load('ar')
+
+def configure(conf):
+ """Finds the ar program and sets the default flags in ``conf.env.ARFLAGS``"""
+ conf.find_program('ar', var='AR')
+ conf.add_os_flags('ARFLAGS')
+ if not conf.env.ARFLAGS:
+ conf.env.ARFLAGS = ['rcs']
+
diff --git a/Tools/asm.py b/Tools/asm.py
new file mode 100644
index 0000000..b6f26fb
--- /dev/null
+++ b/Tools/asm.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2008-2018 (ita)
+
+"""
+Assembly support, used by tools such as gas and nasm
+
+To declare targets using assembly::
+
+ def configure(conf):
+ conf.load('gcc gas')
+
+ def build(bld):
+ bld(
+ features='c cstlib asm',
+ source = 'test.S',
+ target = 'asmtest')
+
+ bld(
+ features='asm asmprogram',
+ source = 'test.S',
+ target = 'asmtest')
+
+Support for pure asm programs and libraries should also work::
+
+ def configure(conf):
+ conf.load('nasm')
+ conf.find_program('ld', 'ASLINK')
+
+ def build(bld):
+ bld(
+ features='asm asmprogram',
+ source = 'test.S',
+ target = 'asmtest')
+"""
+
+from waflib import Task
+from waflib.Tools.ccroot import link_task, stlink_task
+from waflib.TaskGen import extension
+
+class asm(Task.Task):
+ """
+ Compiles asm files by gas/nasm/yasm/...
+ """
+ color = 'BLUE'
+ run_str = '${AS} ${ASFLAGS} ${ASMPATH_ST:INCPATHS} ${DEFINES_ST:DEFINES} ${AS_SRC_F}${SRC} ${AS_TGT_F}${TGT}'
+
+@extension('.s', '.S', '.asm', '.ASM', '.spp', '.SPP')
+def asm_hook(self, node):
+ """
+ Binds the asm extension to the asm task
+
+ :param node: input file
+ :type node: :py:class:`waflib.Node.Node`
+ """
+ return self.create_compiled_task('asm', node)
+
+class asmprogram(link_task):
+ "Links object files into a c program"
+ run_str = '${ASLINK} ${ASLINKFLAGS} ${ASLNK_TGT_F}${TGT} ${ASLNK_SRC_F}${SRC}'
+ ext_out = ['.bin']
+ inst_to = '${BINDIR}'
+
+class asmshlib(asmprogram):
+ "Links object files into a c shared library"
+ inst_to = '${LIBDIR}'
+
+class asmstlib(stlink_task):
+ "Links object files into a c static library"
+ pass # do not remove
+
+def configure(conf):
+ conf.env.ASMPATH_ST = '-I%s'
diff --git a/Tools/bison.py b/Tools/bison.py
new file mode 100644
index 0000000..eef56dc
--- /dev/null
+++ b/Tools/bison.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# John O'Meara, 2006
+# Thomas Nagy 2009-2018 (ita)
+
+"""
+The **bison** program is a code generator which creates C or C++ files.
+The generated files are compiled into object files.
+"""
+
+from waflib import Task
+from waflib.TaskGen import extension
+
+class bison(Task.Task):
+ """Compiles bison files"""
+ color = 'BLUE'
+ run_str = '${BISON} ${BISONFLAGS} ${SRC[0].abspath()} -o ${TGT[0].name}'
+ ext_out = ['.h'] # just to make sure
+
+@extension('.y', '.yc', '.yy')
+def big_bison(self, node):
+ """
+ Creates a bison task, which must be executed from the directory of the output file.
+ """
+ has_h = '-d' in self.env.BISONFLAGS
+
+ outs = []
+ if node.name.endswith('.yc'):
+ outs.append(node.change_ext('.tab.cc'))
+ if has_h:
+ outs.append(node.change_ext('.tab.hh'))
+ else:
+ outs.append(node.change_ext('.tab.c'))
+ if has_h:
+ outs.append(node.change_ext('.tab.h'))
+
+ tsk = self.create_task('bison', node, outs)
+ tsk.cwd = node.parent.get_bld()
+
+ # and the c/cxx file must be compiled too
+ self.source.append(outs[0])
+
+def configure(conf):
+ """
+ Detects the *bison* program
+ """
+ conf.find_program('bison', var='BISON')
+ conf.env.BISONFLAGS = ['-d']
+
diff --git a/Tools/c.py b/Tools/c.py
new file mode 100644
index 0000000..effd6b6
--- /dev/null
+++ b/Tools/c.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2006-2018 (ita)
+
+"Base for c programs/libraries"
+
+from waflib import TaskGen, Task
+from waflib.Tools import c_preproc
+from waflib.Tools.ccroot import link_task, stlink_task
+
+@TaskGen.extension('.c')
+def c_hook(self, node):
+ "Binds the c file extensions create :py:class:`waflib.Tools.c.c` instances"
+ if not self.env.CC and self.env.CXX:
+ return self.create_compiled_task('cxx', node)
+ return self.create_compiled_task('c', node)
+
+class c(Task.Task):
+ "Compiles C files into object files"
+ run_str = '${CC} ${ARCH_ST:ARCH} ${CFLAGS} ${FRAMEWORKPATH_ST:FRAMEWORKPATH} ${CPPPATH_ST:INCPATHS} ${DEFINES_ST:DEFINES} ${CC_SRC_F}${SRC} ${CC_TGT_F}${TGT[0].abspath()} ${CPPFLAGS}'
+ vars = ['CCDEPS'] # unused variable to depend on, just in case
+ ext_in = ['.h'] # set the build order easily by using ext_out=['.h']
+ scan = c_preproc.scan
+
+class cprogram(link_task):
+ "Links object files into c programs"
+ run_str = '${LINK_CC} ${LINKFLAGS} ${CCLNK_SRC_F}${SRC} ${CCLNK_TGT_F}${TGT[0].abspath()} ${RPATH_ST:RPATH} ${FRAMEWORKPATH_ST:FRAMEWORKPATH} ${FRAMEWORK_ST:FRAMEWORK} ${ARCH_ST:ARCH} ${STLIB_MARKER} ${STLIBPATH_ST:STLIBPATH} ${STLIB_ST:STLIB} ${SHLIB_MARKER} ${LIBPATH_ST:LIBPATH} ${LIB_ST:LIB} ${LDFLAGS}'
+ ext_out = ['.bin']
+ vars = ['LINKDEPS']
+ inst_to = '${BINDIR}'
+
+class cshlib(cprogram):
+ "Links object files into c shared libraries"
+ inst_to = '${LIBDIR}'
+
+class cstlib(stlink_task):
+ "Links object files into a c static libraries"
+ pass # do not remove
+
diff --git a/Tools/c_aliases.py b/Tools/c_aliases.py
new file mode 100644
index 0000000..c9d5369
--- /dev/null
+++ b/Tools/c_aliases.py
@@ -0,0 +1,144 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2005-2015 (ita)
+
+"base for all c/c++ programs and libraries"
+
+from waflib import Utils, Errors
+from waflib.Configure import conf
+
+def get_extensions(lst):
+ """
+ Returns the file extensions for the list of files given as input
+
+ :param lst: files to process
+ :list lst: list of string or :py:class:`waflib.Node.Node`
+ :return: list of file extensions
+ :rtype: list of string
+ """
+ ret = []
+ for x in Utils.to_list(lst):
+ if not isinstance(x, str):
+ x = x.name
+ ret.append(x[x.rfind('.') + 1:])
+ return ret
+
+def sniff_features(**kw):
+ """
+ Computes and returns the features required for a task generator by
+ looking at the file extensions. This aimed for C/C++ mainly::
+
+ snif_features(source=['foo.c', 'foo.cxx'], type='shlib')
+ # returns ['cxx', 'c', 'cxxshlib', 'cshlib']
+
+ :param source: source files to process
+ :type source: list of string or :py:class:`waflib.Node.Node`
+ :param type: object type in *program*, *shlib* or *stlib*
+ :type type: string
+ :return: the list of features for a task generator processing the source files
+ :rtype: list of string
+ """
+ exts = get_extensions(kw['source'])
+ typ = kw['typ']
+ feats = []
+
+ # watch the order, cxx will have the precedence
+ for x in 'cxx cpp c++ cc C'.split():
+ if x in exts:
+ feats.append('cxx')
+ break
+
+ if 'c' in exts or 'vala' in exts or 'gs' in exts:
+ feats.append('c')
+
+ for x in 'f f90 F F90 for FOR'.split():
+ if x in exts:
+ feats.append('fc')
+ break
+
+ if 'd' in exts:
+ feats.append('d')
+
+ if 'java' in exts:
+ feats.append('java')
+ return 'java'
+
+ if typ in ('program', 'shlib', 'stlib'):
+ will_link = False
+ for x in feats:
+ if x in ('cxx', 'd', 'fc', 'c'):
+ 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)
+ return feats
+
+def set_features(kw, typ):
+ """
+ Inserts data in the input dict *kw* based on existing data and on the type of target
+ required (typ).
+
+ :param kw: task generator parameters
+ :type kw: dict
+ :param typ: type of target
+ :type typ: string
+ """
+ kw['typ'] = typ
+ kw['features'] = Utils.to_list(kw.get('features', [])) + Utils.to_list(sniff_features(**kw))
+
+@conf
+def program(bld, *k, **kw):
+ """
+ Alias for creating programs by looking at the file extensions::
+
+ def build(bld):
+ bld.program(source='foo.c', target='app')
+ # equivalent to:
+ # bld(features='c cprogram', source='foo.c', target='app')
+
+ """
+ set_features(kw, 'program')
+ return bld(*k, **kw)
+
+@conf
+def shlib(bld, *k, **kw):
+ """
+ Alias for creating shared libraries by looking at the file extensions::
+
+ def build(bld):
+ bld.shlib(source='foo.c', target='app')
+ # equivalent to:
+ # bld(features='c cshlib', source='foo.c', target='app')
+
+ """
+ set_features(kw, 'shlib')
+ return bld(*k, **kw)
+
+@conf
+def stlib(bld, *k, **kw):
+ """
+ Alias for creating static libraries by looking at the file extensions::
+
+ def build(bld):
+ bld.stlib(source='foo.cpp', target='app')
+ # equivalent to:
+ # bld(features='cxx cxxstlib', source='foo.cpp', target='app')
+
+ """
+ set_features(kw, 'stlib')
+ return bld(*k, **kw)
+
+@conf
+def objects(bld, *k, **kw):
+ """
+ Alias for creating object files by looking at the file extensions::
+
+ def build(bld):
+ bld.objects(source='foo.c', target='app')
+ # equivalent to:
+ # bld(features='c', source='foo.c', target='app')
+
+ """
+ set_features(kw, 'objects')
+ return bld(*k, **kw)
+
diff --git a/Tools/c_config.py b/Tools/c_config.py
new file mode 100644
index 0000000..d546be9
--- /dev/null
+++ b/Tools/c_config.py
@@ -0,0 +1,1351 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2005-2018 (ita)
+
+"""
+C/C++/D configuration helpers
+"""
+
+from __future__ import with_statement
+
+import os, re, shlex
+from waflib import Build, Utils, Task, Options, Logs, Errors, Runner
+from waflib.TaskGen import after_method, feature
+from waflib.Configure import conf
+
+WAF_CONFIG_H = 'config.h'
+"""default name for the config.h file"""
+
+DEFKEYS = 'define_key'
+INCKEYS = 'include_key'
+
+SNIP_EMPTY_PROGRAM = '''
+int main(int argc, char **argv) {
+ (void)argc; (void)argv;
+ return 0;
+}
+'''
+
+MACRO_TO_DESTOS = {
+'__linux__' : 'linux',
+'__GNU__' : 'gnu', # hurd
+'__FreeBSD__' : 'freebsd',
+'__NetBSD__' : 'netbsd',
+'__OpenBSD__' : 'openbsd',
+'__sun' : 'sunos',
+'__hpux' : 'hpux',
+'__sgi' : 'irix',
+'_AIX' : 'aix',
+'__CYGWIN__' : 'cygwin',
+'__MSYS__' : 'cygwin',
+'_UWIN' : 'uwin',
+'_WIN64' : 'win32',
+'_WIN32' : 'win32',
+# Note about darwin: this is also tested with 'defined __APPLE__ && defined __MACH__' somewhere below in this file.
+'__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__' : 'darwin',
+'__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__' : 'darwin', # iphone
+'__QNX__' : 'qnx',
+'__native_client__' : 'nacl' # google native client platform
+}
+
+MACRO_TO_DEST_CPU = {
+'__x86_64__' : 'x86_64',
+'__amd64__' : 'x86_64',
+'__i386__' : 'x86',
+'__ia64__' : 'ia',
+'__mips__' : 'mips',
+'__sparc__' : 'sparc',
+'__alpha__' : 'alpha',
+'__aarch64__' : 'aarch64',
+'__thumb__' : 'thumb',
+'__arm__' : 'arm',
+'__hppa__' : 'hppa',
+'__powerpc__' : 'powerpc',
+'__ppc__' : 'powerpc',
+'__convex__' : 'convex',
+'__m68k__' : 'm68k',
+'__s390x__' : 's390x',
+'__s390__' : 's390',
+'__sh__' : 'sh',
+'__xtensa__' : 'xtensa',
+}
+
+@conf
+def parse_flags(self, line, uselib_store, env=None, force_static=False, posix=None):
+ """
+ Parses flags from the input lines, and adds them to the relevant use variables::
+
+ def configure(conf):
+ conf.parse_flags('-O3', 'FOO')
+ # conf.env.CXXFLAGS_FOO = ['-O3']
+ # conf.env.CFLAGS_FOO = ['-O3']
+
+ :param line: flags
+ :type line: string
+ :param uselib_store: where to add the flags
+ :type uselib_store: string
+ :param env: config set or conf.env by default
+ :type env: :py:class:`waflib.ConfigSet.ConfigSet`
+ """
+
+ assert(isinstance(line, str))
+
+ env = env or self.env
+
+ # Issue 811 and 1371
+ if posix is None:
+ posix = True
+ if '\\' in line:
+ posix = ('\\ ' in line) or ('\\\\' in line)
+
+ lex = shlex.shlex(line, posix=posix)
+ lex.whitespace_split = True
+ lex.commenters = ''
+ lst = list(lex)
+
+ # append_unique is not always possible
+ # for example, apple flags may require both -arch i386 and -arch ppc
+ uselib = uselib_store
+ def app(var, val):
+ env.append_value('%s_%s' % (var, uselib), val)
+ def appu(var, val):
+ env.append_unique('%s_%s' % (var, uselib), val)
+ static = False
+ while lst:
+ x = lst.pop(0)
+ st = x[:2]
+ ot = x[2:]
+
+ if st == '-I' or st == '/I':
+ if not ot:
+ ot = lst.pop(0)
+ appu('INCLUDES', ot)
+ elif st == '-i':
+ tmp = [x, lst.pop(0)]
+ app('CFLAGS', tmp)
+ app('CXXFLAGS', tmp)
+ elif st == '-D' or (env.CXX_NAME == 'msvc' and st == '/D'): # not perfect but..
+ if not ot:
+ ot = lst.pop(0)
+ app('DEFINES', ot)
+ elif st == '-l':
+ if not ot:
+ ot = lst.pop(0)
+ prefix = 'STLIB' if (force_static or static) else 'LIB'
+ app(prefix, ot)
+ elif st == '-L':
+ if not ot:
+ ot = lst.pop(0)
+ prefix = 'STLIBPATH' if (force_static or static) else 'LIBPATH'
+ appu(prefix, ot)
+ elif x.startswith('/LIBPATH:'):
+ prefix = 'STLIBPATH' if (force_static or static) else 'LIBPATH'
+ appu(prefix, x.replace('/LIBPATH:', ''))
+ 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'):
+ app('CFLAGS', x)
+ app('CXXFLAGS', x)
+ app('LINKFLAGS', x)
+ elif x == '-framework':
+ appu('FRAMEWORK', lst.pop(0))
+ elif x.startswith('-F'):
+ appu('FRAMEWORKPATH', x[2:])
+ elif x == '-Wl,-rpath' or x == '-Wl,-R':
+ app('RPATH', lst.pop(0).lstrip('-Wl,'))
+ elif x.startswith('-Wl,-R,'):
+ app('RPATH', x[7:])
+ elif x.startswith('-Wl,-R'):
+ app('RPATH', x[6:])
+ elif x.startswith('-Wl,-rpath,'):
+ app('RPATH', x[11:])
+ elif x == '-Wl,-Bstatic' or x == '-Bstatic':
+ static = True
+ elif x == '-Wl,-Bdynamic' or x == '-Bdynamic':
+ static = False
+ elif x.startswith('-Wl') or x in ('-rdynamic', '-pie'):
+ app('LINKFLAGS', x)
+ elif x.startswith(('-m', '-f', '-dynamic', '-O', '-g')):
+ # Adding the -W option breaks python builds on Openindiana
+ app('CFLAGS', x)
+ app('CXXFLAGS', x)
+ elif x.startswith('-bundle'):
+ app('LINKFLAGS', x)
+ elif x.startswith(('-undefined', '-Xlinker')):
+ arg = lst.pop(0)
+ app('LINKFLAGS', [x, arg])
+ elif x.startswith(('-arch', '-isysroot')):
+ tmp = [x, lst.pop(0)]
+ app('CFLAGS', tmp)
+ app('CXXFLAGS', tmp)
+ app('LINKFLAGS', tmp)
+ elif x.endswith(('.a', '.so', '.dylib', '.lib')):
+ appu('LINKFLAGS', x) # not cool, #762
+ else:
+ self.to_log('Unhandled flag %r' % x)
+
+@conf
+def validate_cfg(self, kw):
+ """
+ Searches for the program *pkg-config* if missing, and validates the
+ parameters to pass to :py:func:`waflib.Tools.c_config.exec_cfg`.
+
+ :param path: the **-config program to use** (default is *pkg-config*)
+ :type path: list of string
+ :param msg: message to display to describe the test executed
+ :type msg: string
+ :param okmsg: message to display when the test is successful
+ :type okmsg: string
+ :param errmsg: message to display in case of error
+ :type errmsg: string
+ """
+ if not 'path' in kw:
+ if not self.env.PKGCONFIG:
+ self.find_program('pkg-config', var='PKGCONFIG')
+ kw['path'] = self.env.PKGCONFIG
+
+ # verify that exactly one action is requested
+ s = ('atleast_pkgconfig_version' in kw) + ('modversion' in kw) + ('package' in kw)
+ if s != 1:
+ raise ValueError('exactly one of atleast_pkgconfig_version, modversion and package must be set')
+ if not 'msg' in kw:
+ if 'atleast_pkgconfig_version' in kw:
+ kw['msg'] = 'Checking for pkg-config version >= %r' % kw['atleast_pkgconfig_version']
+ elif 'modversion' in kw:
+ kw['msg'] = 'Checking for %r version' % kw['modversion']
+ else:
+ kw['msg'] = 'Checking for %r' %(kw['package'])
+
+ # let the modversion check set the okmsg to the detected version
+ if not 'okmsg' in kw and not 'modversion' in kw:
+ kw['okmsg'] = 'yes'
+ if not 'errmsg' in kw:
+ kw['errmsg'] = 'not found'
+
+ # pkg-config version
+ if 'atleast_pkgconfig_version' in kw:
+ pass
+ elif 'modversion' in kw:
+ if not 'uselib_store' in kw:
+ kw['uselib_store'] = kw['modversion']
+ if not 'define_name' in kw:
+ kw['define_name'] = '%s_VERSION' % Utils.quote_define_name(kw['uselib_store'])
+ else:
+ if not 'uselib_store' in kw:
+ kw['uselib_store'] = Utils.to_list(kw['package'])[0].upper()
+ if not 'define_name' in kw:
+ kw['define_name'] = self.have_define(kw['uselib_store'])
+
+@conf
+def exec_cfg(self, kw):
+ """
+ Executes ``pkg-config`` or other ``-config`` applications to collect configuration flags:
+
+ * if atleast_pkgconfig_version is given, check that pkg-config has the version n and return
+ * 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 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.
+ :type uselib_store: string
+ :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
+ :param variables: return the values of particular variables
+ :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)
+ """
+
+ path = Utils.to_list(kw['path'])
+ env = self.env.env or None
+ if kw.get('pkg_config_path'):
+ if not env:
+ env = dict(self.environ)
+ env['PKG_CONFIG_PATH'] = kw['pkg_config_path']
+
+ def define_it():
+ define_name = kw['define_name']
+ # by default, add HAVE_X to the config.h, else provide DEFINES_X for use=X
+ if kw.get('global_define', 1):
+ self.define(define_name, 1, False)
+ else:
+ self.env.append_unique('DEFINES_%s' % kw['uselib_store'], "%s=1" % define_name)
+
+ if kw.get('add_have_to_env', 1):
+ self.env[define_name] = 1
+
+ # pkg-config version
+ if 'atleast_pkgconfig_version' in kw:
+ cmd = path + ['--atleast-pkgconfig-version=%s' % kw['atleast_pkgconfig_version']]
+ self.cmd_and_log(cmd, env=env)
+ return
+
+ # single version for a module
+ if 'modversion' in kw:
+ version = self.cmd_and_log(path + ['--modversion', kw['modversion']], env=env).strip()
+ if not 'okmsg' in kw:
+ kw['okmsg'] = version
+ self.define(kw['define_name'], version)
+ return version
+
+ lst = [] + path
+
+ defi = kw.get('define_variable')
+ if not defi:
+ defi = self.env.PKG_CONFIG_DEFINES or {}
+ for key, val in defi.items():
+ lst.append('--define-variable=%s=%s' % (key, val))
+
+ static = kw.get('force_static', False)
+ if 'args' in kw:
+ args = Utils.to_list(kw['args'])
+ if '--static' in args or '--static-libs' in args:
+ static = True
+ lst += args
+
+ # tools like pkgconf expect the package argument after the -- ones -_-
+ lst.extend(Utils.to_list(kw['package']))
+
+ # retrieving variables of a module
+ if 'variables' in kw:
+ v_env = kw.get('env', self.env)
+ vars = Utils.to_list(kw['variables'])
+ for v in vars:
+ val = self.cmd_and_log(lst + ['--variable=' + v], env=env).strip()
+ var = '%s_%s' % (kw['uselib_store'], v)
+ v_env[var] = val
+ return
+
+ # so we assume the command-line will output flags to be parsed afterwards
+ ret = self.cmd_and_log(lst, env=env)
+
+ define_it()
+ self.parse_flags(ret, kw['uselib_store'], kw.get('env', self.env), force_static=static, posix=kw.get('posix'))
+ return ret
+
+@conf
+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`
+
+ A few examples::
+
+ def configure(conf):
+ conf.load('compiler_c')
+ conf.check_cfg(package='glib-2.0', args='--libs --cflags')
+ conf.check_cfg(package='pango')
+ conf.check_cfg(package='pango', uselib_store='MYPANGO', args=['--cflags', '--libs'])
+ conf.check_cfg(package='pango',
+ args=['pango >= 0.1.0', 'pango < 9.9.9', '--cflags', '--libs'],
+ msg="Checking for 'pango 0.1.0'")
+ conf.check_cfg(path='sdl-config', args='--cflags --libs', package='', uselib_store='SDL')
+ conf.check_cfg(path='mpicc', args='--showme:compile --showme:link',
+ package='', uselib_store='OPEN_MPI', mandatory=False)
+ # variables
+ conf.check_cfg(package='gtk+-2.0', variables=['includedir', 'prefix'], uselib_store='FOO')
+ print(conf.env.FOO_includedir)
+ """
+ self.validate_cfg(kw)
+ if 'msg' in kw:
+ self.start_msg(kw['msg'], **kw)
+ ret = None
+ try:
+ ret = self.exec_cfg(kw)
+ except self.errors.WafError as e:
+ if 'errmsg' in kw:
+ self.end_msg(kw['errmsg'], 'YELLOW', **kw)
+ if Logs.verbose > 1:
+ self.to_log('Command failure: %s' % e)
+ self.fatal('The configuration failed')
+ else:
+ if not ret:
+ ret = True
+ kw['success'] = ret
+ if 'okmsg' in kw:
+ self.end_msg(self.ret_msg(kw['okmsg'], kw), **kw)
+
+ return ret
+
+def build_fun(bld):
+ """
+ Build function that is used for running configuration tests with ``conf.check()``
+ """
+ if bld.kw['compile_filename']:
+ node = bld.srcnode.make_node(bld.kw['compile_filename'])
+ node.write(bld.kw['code'])
+
+ o = bld(features=bld.kw['features'], source=bld.kw['compile_filename'], target='testprog')
+
+ for k, v in bld.kw.items():
+ setattr(o, k, v)
+
+ if not bld.kw.get('quiet'):
+ bld.conf.to_log("==>\n%s\n<==" % bld.kw['code'])
+
+@conf
+def validate_c(self, kw):
+ """
+ Pre-checks the parameters that will be given to :py:func:`waflib.Configure.run_build`
+
+ :param compiler: c or cxx (tries to guess what is best)
+ :type compiler: string
+ :param type: cprogram, cshlib, cstlib - not required if *features are given directly*
+ :type type: binary to create
+ :param feature: desired features for the task generator that will execute the test, for example ``cxx cxxstlib``
+ :type feature: list of string
+ :param fragment: provide a piece of code for the test (default is to let the system create one)
+ :type fragment: string
+ :param uselib_store: define variables after the test is executed (IMPORTANT!)
+ :type uselib_store: string
+ :param use: parameters to use for building (just like the normal *use* keyword)
+ :type use: list of string
+ :param define_name: define to set when the check is over
+ :type define_name: string
+ :param execute: execute the resulting binary
+ :type execute: bool
+ :param define_ret: if execute is set to True, use the execution output in both the define and the return value
+ :type define_ret: bool
+ :param header_name: check for a particular header
+ :type header_name: string
+ :param auto_add_header_name: if header_name was set, add the headers in env.INCKEYS so the next tests will include these headers
+ :type auto_add_header_name: bool
+ """
+ for x in ('type_name', 'field_name', 'function_name'):
+ if x in kw:
+ Logs.warn('Invalid argument %r in test' % x)
+
+ if not 'build_fun' in kw:
+ kw['build_fun'] = build_fun
+
+ if not 'env' in kw:
+ kw['env'] = self.env.derive()
+ env = kw['env']
+
+ if not 'compiler' in kw and not 'features' in kw:
+ kw['compiler'] = 'c'
+ if env.CXX_NAME and Task.classes.get('cxx'):
+ kw['compiler'] = 'cxx'
+ if not self.env.CXX:
+ self.fatal('a c++ compiler is required')
+ else:
+ if not self.env.CC:
+ self.fatal('a c compiler is required')
+
+ if not 'compile_mode' in kw:
+ kw['compile_mode'] = 'c'
+ if 'cxx' in Utils.to_list(kw.get('features', [])) or kw.get('compiler') == 'cxx':
+ kw['compile_mode'] = 'cxx'
+
+ if not 'type' in kw:
+ kw['type'] = 'cprogram'
+
+ if not 'features' in kw:
+ if not 'header_name' in kw or kw.get('link_header_test', True):
+ kw['features'] = [kw['compile_mode'], kw['type']] # "c ccprogram"
+ else:
+ kw['features'] = [kw['compile_mode']]
+ else:
+ kw['features'] = Utils.to_list(kw['features'])
+
+ if not 'compile_filename' in kw:
+ kw['compile_filename'] = 'test.c' + ((kw['compile_mode'] == 'cxx') and 'pp' or '')
+
+ def to_header(dct):
+ if 'header_name' in dct:
+ dct = Utils.to_list(dct['header_name'])
+ return ''.join(['#include <%s>\n' % x for x in dct])
+ return ''
+
+ if 'framework_name' in kw:
+ # OSX, not sure this is used anywhere
+ fwkname = kw['framework_name']
+ if not 'uselib_store' in kw:
+ kw['uselib_store'] = fwkname.upper()
+ if not kw.get('no_header'):
+ fwk = '%s/%s.h' % (fwkname, fwkname)
+ if kw.get('remove_dot_h'):
+ fwk = fwk[:-2]
+ val = kw.get('header_name', [])
+ kw['header_name'] = Utils.to_list(val) + [fwk]
+ kw['msg'] = 'Checking for framework %s' % fwkname
+ kw['framework'] = fwkname
+
+ elif 'header_name' in kw:
+ if not 'msg' in kw:
+ kw['msg'] = 'Checking for header %s' % kw['header_name']
+
+ l = Utils.to_list(kw['header_name'])
+ assert len(l), 'list of headers in header_name is empty'
+
+ kw['code'] = to_header(kw) + SNIP_EMPTY_PROGRAM
+ if not 'uselib_store' in kw:
+ kw['uselib_store'] = l[0].upper()
+ if not 'define_name' in kw:
+ kw['define_name'] = self.have_define(l[0])
+
+ if 'lib' in kw:
+ if not 'msg' in kw:
+ kw['msg'] = 'Checking for library %s' % kw['lib']
+ if not 'uselib_store' in kw:
+ kw['uselib_store'] = kw['lib'].upper()
+
+ if 'stlib' in kw:
+ if not 'msg' in kw:
+ kw['msg'] = 'Checking for static library %s' % kw['stlib']
+ if not 'uselib_store' in kw:
+ kw['uselib_store'] = kw['stlib'].upper()
+
+ if 'fragment' in kw:
+ # an additional code fragment may be provided to replace the predefined code
+ # in custom headers
+ kw['code'] = kw['fragment']
+ if not 'msg' in kw:
+ kw['msg'] = 'Checking for code snippet'
+ if not 'errmsg' in kw:
+ kw['errmsg'] = 'no'
+
+ for (flagsname,flagstype) in (('cxxflags','compiler'), ('cflags','compiler'), ('linkflags','linker')):
+ if flagsname in kw:
+ if not 'msg' in kw:
+ kw['msg'] = 'Checking for %s flags %s' % (flagstype, kw[flagsname])
+ if not 'errmsg' in kw:
+ kw['errmsg'] = 'no'
+
+ if not 'execute' in kw:
+ kw['execute'] = False
+ if kw['execute']:
+ kw['features'].append('test_exec')
+ kw['chmod'] = Utils.O755
+
+ if not 'errmsg' in kw:
+ kw['errmsg'] = 'not found'
+
+ if not 'okmsg' in kw:
+ kw['okmsg'] = 'yes'
+
+ if not 'code' in kw:
+ kw['code'] = SNIP_EMPTY_PROGRAM
+
+ # if there are headers to append automatically to the next tests
+ if self.env[INCKEYS]:
+ kw['code'] = '\n'.join(['#include <%s>' % x for x in self.env[INCKEYS]]) + '\n' + kw['code']
+
+ # in case defines lead to very long command-lines
+ if kw.get('merge_config_header') or env.merge_config_header:
+ kw['code'] = '%s\n\n%s' % (self.get_config_header(), kw['code'])
+ env.DEFINES = [] # modify the copy
+
+ if not kw.get('success'):
+ kw['success'] = None
+
+ if 'define_name' in kw:
+ self.undefine(kw['define_name'])
+ if not 'msg' in kw:
+ self.fatal('missing "msg" in conf.check(...)')
+
+@conf
+def post_check(self, *k, **kw):
+ """
+ Sets the variables after a test executed in
+ :py:func:`waflib.Tools.c_config.check` was run successfully
+ """
+ is_success = 0
+ if kw['execute']:
+ if kw['success'] is not None:
+ if kw.get('define_ret'):
+ is_success = kw['success']
+ else:
+ is_success = (kw['success'] == 0)
+ else:
+ is_success = (kw['success'] == 0)
+
+ if kw.get('define_name'):
+ comment = kw.get('comment', '')
+ define_name = kw['define_name']
+ if kw['execute'] and kw.get('define_ret') and isinstance(is_success, str):
+ if kw.get('global_define', 1):
+ self.define(define_name, is_success, quote=kw.get('quote', 1), comment=comment)
+ else:
+ if kw.get('quote', 1):
+ succ = '"%s"' % is_success
+ else:
+ succ = int(is_success)
+ val = '%s=%s' % (define_name, succ)
+ var = 'DEFINES_%s' % kw['uselib_store']
+ self.env.append_value(var, val)
+ else:
+ if kw.get('global_define', 1):
+ self.define_cond(define_name, is_success, comment=comment)
+ else:
+ var = 'DEFINES_%s' % kw['uselib_store']
+ self.env.append_value(var, '%s=%s' % (define_name, int(is_success)))
+
+ # define conf.env.HAVE_X to 1
+ if kw.get('add_have_to_env', 1):
+ if kw.get('uselib_store'):
+ self.env[self.have_define(kw['uselib_store'])] = 1
+ elif kw['execute'] and kw.get('define_ret'):
+ self.env[define_name] = is_success
+ else:
+ self.env[define_name] = int(is_success)
+
+ if 'header_name' in kw:
+ if kw.get('auto_add_header_name'):
+ self.env.append_value(INCKEYS, Utils.to_list(kw['header_name']))
+
+ if is_success and 'uselib_store' in kw:
+ from waflib.Tools import ccroot
+ # See get_uselib_vars in ccroot.py
+ _vars = set()
+ for x in kw['features']:
+ if x in ccroot.USELIB_VARS:
+ _vars |= ccroot.USELIB_VARS[x]
+
+ for k in _vars:
+ x = k.lower()
+ if x in kw:
+ self.env.append_value(k + '_' + kw['uselib_store'], kw[x])
+ return is_success
+
+@conf
+def check(self, *k, **kw):
+ """
+ Performs a configuration test by calling :py:func:`waflib.Configure.run_build`.
+ For the complete list of parameters, see :py:func:`waflib.Tools.c_config.validate_c`.
+ To force a specific compiler, pass ``compiler='c'`` or ``compiler='cxx'`` to the list of arguments
+
+ Besides build targets, complete builds can be given through a build function. All files will
+ be written to a temporary directory::
+
+ def build(bld):
+ lib_node = bld.srcnode.make_node('libdir/liblc1.c')
+ lib_node.parent.mkdir()
+ lib_node.write('#include <stdio.h>\\nint lib_func(void) { FILE *f = fopen("foo", "r");}\\n', 'w')
+ bld(features='c cshlib', source=[lib_node], linkflags=conf.env.EXTRA_LDFLAGS, target='liblc')
+ conf.check(build_fun=build, msg=msg)
+ """
+ self.validate_c(kw)
+ self.start_msg(kw['msg'], **kw)
+ ret = None
+ try:
+ ret = self.run_build(*k, **kw)
+ except self.errors.ConfigurationError:
+ self.end_msg(kw['errmsg'], 'YELLOW', **kw)
+ if Logs.verbose > 1:
+ raise
+ else:
+ self.fatal('The configuration failed')
+ else:
+ kw['success'] = ret
+
+ ret = self.post_check(*k, **kw)
+ if not ret:
+ self.end_msg(kw['errmsg'], 'YELLOW', **kw)
+ self.fatal('The configuration failed %r' % ret)
+ else:
+ self.end_msg(self.ret_msg(kw['okmsg'], kw), **kw)
+ return ret
+
+class test_exec(Task.Task):
+ """
+ A task that runs programs after they are built. See :py:func:`waflib.Tools.c_config.test_exec_fun`.
+ """
+ color = 'PINK'
+ def run(self):
+ 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()])
+ else:
+ self.generator.bld.retval = self.generator.bld.exec_command([self.inputs[0].abspath()])
+ 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)
+ else:
+ self.generator.bld.retval = self.generator.bld.exec_command([self.inputs[0].abspath()], env=env)
+
+@feature('test_exec')
+@after_method('apply_link')
+def test_exec_fun(self):
+ """
+ The feature **test_exec** is used to create a task that will to execute the binary
+ created (link task output) during the build. The exit status will be set
+ on the build context, so only one program may have the feature *test_exec*.
+ This is used by configuration tests::
+
+ def configure(conf):
+ conf.check(execute=True)
+ """
+ self.create_task('test_exec', self.link_task.outputs[0])
+
+@conf
+def check_cxx(self, *k, **kw):
+ """
+ Runs a test with a task generator of the form::
+
+ conf.check(features='cxx cxxprogram', ...)
+ """
+ kw['compiler'] = 'cxx'
+ return self.check(*k, **kw)
+
+@conf
+def check_cc(self, *k, **kw):
+ """
+ Runs a test with a task generator of the form::
+
+ conf.check(features='c cprogram', ...)
+ """
+ kw['compiler'] = 'c'
+ return self.check(*k, **kw)
+
+@conf
+def set_define_comment(self, key, comment):
+ """
+ Sets a comment that will appear in the configuration header
+
+ :type key: string
+ :type comment: string
+ """
+ coms = self.env.DEFINE_COMMENTS
+ if not coms:
+ coms = self.env.DEFINE_COMMENTS = {}
+ coms[key] = comment or ''
+
+@conf
+def get_define_comment(self, key):
+ """
+ Returns the comment associated to a define
+
+ :type key: string
+ """
+ coms = self.env.DEFINE_COMMENTS or {}
+ return coms.get(key, '')
+
+@conf
+def define(self, key, val, quote=True, comment=''):
+ """
+ Stores a single define and its state into ``conf.env.DEFINES``. The value is cast to an integer (0/1).
+
+ :param key: define name
+ :type key: string
+ :param val: value
+ :type val: int or string
+ :param quote: enclose strings in quotes (yes by default)
+ :type quote: bool
+ """
+ assert isinstance(key, str)
+ if not key:
+ return
+ if val is True:
+ val = 1
+ elif val in (False, None):
+ val = 0
+
+ if isinstance(val, int) or isinstance(val, float):
+ s = '%s=%s'
+ else:
+ s = quote and '%s="%s"' or '%s=%s'
+ app = s % (key, str(val))
+
+ ban = key + '='
+ lst = self.env.DEFINES
+ for x in lst:
+ if x.startswith(ban):
+ lst[lst.index(x)] = app
+ break
+ else:
+ self.env.append_value('DEFINES', app)
+
+ self.env.append_unique(DEFKEYS, key)
+ self.set_define_comment(key, comment)
+
+@conf
+def undefine(self, key, comment=''):
+ """
+ Removes a global define from ``conf.env.DEFINES``
+
+ :param key: define name
+ :type key: string
+ """
+ assert isinstance(key, str)
+ if not key:
+ return
+ ban = key + '='
+ lst = [x for x in self.env.DEFINES if not x.startswith(ban)]
+ self.env.DEFINES = lst
+ self.env.append_unique(DEFKEYS, key)
+ self.set_define_comment(key, comment)
+
+@conf
+def define_cond(self, key, val, comment=''):
+ """
+ Conditionally defines a name::
+
+ def configure(conf):
+ conf.define_cond('A', True)
+ # equivalent to:
+ # if val: conf.define('A', 1)
+ # else: conf.undefine('A')
+
+ :param key: define name
+ :type key: string
+ :param val: value
+ :type val: int or string
+ """
+ assert isinstance(key, str)
+ if not key:
+ return
+ if val:
+ self.define(key, 1, comment=comment)
+ else:
+ self.undefine(key, comment=comment)
+
+@conf
+def is_defined(self, key):
+ """
+ Indicates whether a particular define is globally set in ``conf.env.DEFINES``.
+
+ :param key: define name
+ :type key: string
+ :return: True if the define is set
+ :rtype: bool
+ """
+ assert key and isinstance(key, str)
+
+ ban = key + '='
+ for x in self.env.DEFINES:
+ if x.startswith(ban):
+ return True
+ return False
+
+@conf
+def get_define(self, key):
+ """
+ Returns the value of an existing define, or None if not found
+
+ :param key: define name
+ :type key: string
+ :rtype: string
+ """
+ assert key and isinstance(key, str)
+
+ ban = key + '='
+ for x in self.env.DEFINES:
+ if x.startswith(ban):
+ return x[len(ban):]
+ return None
+
+@conf
+def have_define(self, key):
+ """
+ Returns a variable suitable for command-line or header use by removing invalid characters
+ and prefixing it with ``HAVE_``
+
+ :param key: define name
+ :type key: string
+ :return: the input key prefixed by *HAVE_* and substitute any invalid characters.
+ :rtype: string
+ """
+ return (self.env.HAVE_PAT or 'HAVE_%s') % Utils.quote_define_name(key)
+
+@conf
+def write_config_header(self, configfile='', guard='', top=False, defines=True, headers=False, remove=True, define_prefix=''):
+ """
+ Writes a configuration header containing defines and includes::
+
+ def configure(cnf):
+ cnf.define('A', 1)
+ cnf.write_config_header('config.h')
+
+ This function only adds include guards (if necessary), consult
+ :py:func:`waflib.Tools.c_config.get_config_header` for details on the body.
+
+ :param configfile: path to the file to create (relative or absolute)
+ :type configfile: string
+ :param guard: include guard name to add, by default it is computed from the file name
+ :type guard: string
+ :param top: write the configuration header from the build directory (default is from the current path)
+ :type top: bool
+ :param defines: add the defines (yes by default)
+ :type defines: bool
+ :param headers: add #include in the file
+ :type headers: bool
+ :param remove: remove the defines after they are added (yes by default, works like in autoconf)
+ :type remove: bool
+ :type define_prefix: string
+ :param define_prefix: prefix all the defines in the file with a particular prefix
+ """
+ if not configfile:
+ configfile = WAF_CONFIG_H
+ waf_guard = guard or 'W_%s_WAF' % Utils.quote_define_name(configfile)
+
+ node = top and self.bldnode or self.path.get_bld()
+ node = node.make_node(configfile)
+ node.parent.mkdir()
+
+ lst = ['/* WARNING! All changes made to this file will be lost! */\n']
+ lst.append('#ifndef %s\n#define %s\n' % (waf_guard, waf_guard))
+ lst.append(self.get_config_header(defines, headers, define_prefix=define_prefix))
+ lst.append('\n#endif /* %s */\n' % waf_guard)
+
+ node.write('\n'.join(lst))
+
+ # config files must not be removed on "waf clean"
+ self.env.append_unique(Build.CFG_FILES, [node.abspath()])
+
+ if remove:
+ for key in self.env[DEFKEYS]:
+ self.undefine(key)
+ self.env[DEFKEYS] = []
+
+@conf
+def get_config_header(self, defines=True, headers=False, define_prefix=''):
+ """
+ Creates the contents of a ``config.h`` file from the defines and includes
+ set in conf.env.define_key / conf.env.include_key. No include guards are added.
+
+ A prelude will be added from the variable env.WAF_CONFIG_H_PRELUDE if provided. This
+ can be used to insert complex macros or include guards::
+
+ def configure(conf):
+ conf.env.WAF_CONFIG_H_PRELUDE = '#include <unistd.h>\\n'
+ conf.write_config_header('config.h')
+
+ :param defines: write the defines values
+ :type defines: bool
+ :param headers: write include entries for each element in self.env.INCKEYS
+ :type headers: bool
+ :type define_prefix: string
+ :param define_prefix: prefix all the defines with a particular prefix
+ :return: the contents of a ``config.h`` file
+ :rtype: string
+ """
+ lst = []
+
+ if self.env.WAF_CONFIG_H_PRELUDE:
+ lst.append(self.env.WAF_CONFIG_H_PRELUDE)
+
+ if headers:
+ for x in self.env[INCKEYS]:
+ lst.append('#include <%s>' % x)
+
+ if defines:
+ tbl = {}
+ for k in self.env.DEFINES:
+ a, _, b = k.partition('=')
+ tbl[a] = b
+
+ for k in self.env[DEFKEYS]:
+ caption = self.get_define_comment(k)
+ if caption:
+ caption = ' /* %s */' % caption
+ try:
+ txt = '#define %s%s %s%s' % (define_prefix, k, tbl[k], caption)
+ except KeyError:
+ txt = '/* #undef %s%s */%s' % (define_prefix, k, caption)
+ lst.append(txt)
+ return "\n".join(lst)
+
+@conf
+def cc_add_flags(conf):
+ """
+ Adds CFLAGS / CPPFLAGS from os.environ to conf.env
+ """
+ conf.add_os_flags('CPPFLAGS', dup=False)
+ conf.add_os_flags('CFLAGS', dup=False)
+
+@conf
+def cxx_add_flags(conf):
+ """
+ Adds CXXFLAGS / CPPFLAGS from os.environ to conf.env
+ """
+ conf.add_os_flags('CPPFLAGS', dup=False)
+ conf.add_os_flags('CXXFLAGS', dup=False)
+
+@conf
+def link_add_flags(conf):
+ """
+ Adds LINKFLAGS / LDFLAGS from os.environ to conf.env
+ """
+ conf.add_os_flags('LINKFLAGS', dup=False)
+ conf.add_os_flags('LDFLAGS', dup=False)
+
+@conf
+def cc_load_tools(conf):
+ """
+ Loads the Waf c extensions
+ """
+ if not conf.env.DEST_OS:
+ conf.env.DEST_OS = Utils.unversioned_sys_platform()
+ conf.load('c')
+
+@conf
+def cxx_load_tools(conf):
+ """
+ Loads the Waf c++ extensions
+ """
+ if not conf.env.DEST_OS:
+ conf.env.DEST_OS = Utils.unversioned_sys_platform()
+ conf.load('cxx')
+
+@conf
+def get_cc_version(conf, cc, gcc=False, icc=False, clang=False):
+ """
+ Runs the preprocessor to determine the gcc/icc/clang version
+
+ The variables CC_VERSION, DEST_OS, DEST_BINFMT and DEST_CPU will be set in *conf.env*
+
+ :raise: :py:class:`waflib.Errors.ConfigurationError`
+ """
+ cmd = cc + ['-dM', '-E', '-']
+ env = conf.env.env or None
+ try:
+ out, err = conf.cmd_and_log(cmd, output=0, input='\n'.encode(), env=env)
+ except Errors.WafError:
+ conf.fatal('Could not determine the compiler version %r' % cmd)
+
+ if gcc:
+ if out.find('__INTEL_COMPILER') >= 0:
+ conf.fatal('The intel compiler pretends to be gcc')
+ if out.find('__GNUC__') < 0 and out.find('__clang__') < 0:
+ conf.fatal('Could not determine the compiler type')
+
+ if icc and out.find('__INTEL_COMPILER') < 0:
+ conf.fatal('Not icc/icpc')
+
+ if clang and out.find('__clang__') < 0:
+ conf.fatal('Not clang/clang++')
+ if not clang and out.find('__clang__') >= 0:
+ conf.fatal('Could not find gcc/g++ (only Clang), if renamed try eg: CC=gcc48 CXX=g++48 waf configure')
+
+ k = {}
+ if icc or gcc or clang:
+ out = out.splitlines()
+ for line in out:
+ lst = shlex.split(line)
+ if len(lst)>2:
+ key = lst[1]
+ val = lst[2]
+ k[key] = val
+
+ def isD(var):
+ return var in k
+
+ # Some documentation is available at http://predef.sourceforge.net
+ # The names given to DEST_OS must match what Utils.unversioned_sys_platform() returns.
+ if not conf.env.DEST_OS:
+ conf.env.DEST_OS = ''
+ for i in MACRO_TO_DESTOS:
+ if isD(i):
+ conf.env.DEST_OS = MACRO_TO_DESTOS[i]
+ break
+ else:
+ if isD('__APPLE__') and isD('__MACH__'):
+ conf.env.DEST_OS = 'darwin'
+ elif isD('__unix__'): # unix must be tested last as it's a generic fallback
+ conf.env.DEST_OS = 'generic'
+
+ if isD('__ELF__'):
+ conf.env.DEST_BINFMT = 'elf'
+ elif isD('__WINNT__') or isD('__CYGWIN__') or isD('_WIN32'):
+ conf.env.DEST_BINFMT = 'pe'
+ if not conf.env.IMPLIBDIR:
+ conf.env.IMPLIBDIR = conf.env.LIBDIR # for .lib or .dll.a files
+ conf.env.LIBDIR = conf.env.BINDIR
+ elif isD('__APPLE__'):
+ conf.env.DEST_BINFMT = 'mac-o'
+
+ if not conf.env.DEST_BINFMT:
+ # Infer the binary format from the os name.
+ conf.env.DEST_BINFMT = Utils.destos_to_binfmt(conf.env.DEST_OS)
+
+ for i in MACRO_TO_DEST_CPU:
+ if isD(i):
+ conf.env.DEST_CPU = MACRO_TO_DEST_CPU[i]
+ break
+
+ Logs.debug('ccroot: dest platform: ' + ' '.join([conf.env[x] or '?' for x in ('DEST_OS', 'DEST_BINFMT', 'DEST_CPU')]))
+ if icc:
+ ver = k['__INTEL_COMPILER']
+ conf.env.CC_VERSION = (ver[:-2], ver[-2], ver[-1])
+ else:
+ if isD('__clang__') and isD('__clang_major__'):
+ conf.env.CC_VERSION = (k['__clang_major__'], k['__clang_minor__'], k['__clang_patchlevel__'])
+ else:
+ # older clang versions and gcc
+ conf.env.CC_VERSION = (k['__GNUC__'], k['__GNUC_MINOR__'], k.get('__GNUC_PATCHLEVEL__', '0'))
+ return k
+
+@conf
+def get_xlc_version(conf, cc):
+ """
+ Returns the Aix compiler version
+
+ :raise: :py:class:`waflib.Errors.ConfigurationError`
+ """
+ cmd = cc + ['-qversion']
+ try:
+ out, err = conf.cmd_and_log(cmd, output=0)
+ except Errors.WafError:
+ conf.fatal('Could not find xlc %r' % cmd)
+
+ # the intention is to catch the 8.0 in "IBM XL C/C++ Enterprise Edition V8.0 for AIX..."
+ for v in (r"IBM XL C/C\+\+.* V(?P<major>\d*)\.(?P<minor>\d*)",):
+ version_re = re.compile(v, re.I).search
+ match = version_re(out or err)
+ if match:
+ k = match.groupdict()
+ conf.env.CC_VERSION = (k['major'], k['minor'])
+ break
+ else:
+ conf.fatal('Could not determine the XLC version.')
+
+@conf
+def get_suncc_version(conf, cc):
+ """
+ Returns the Sun compiler version
+
+ :raise: :py:class:`waflib.Errors.ConfigurationError`
+ """
+ cmd = cc + ['-V']
+ try:
+ out, err = conf.cmd_and_log(cmd, output=0)
+ except Errors.WafError as e:
+ # Older versions of the compiler exit with non-zero status when reporting their version
+ if not (hasattr(e, 'returncode') and hasattr(e, 'stdout') and hasattr(e, 'stderr')):
+ conf.fatal('Could not find suncc %r' % cmd)
+ out = e.stdout
+ err = e.stderr
+
+ version = (out or err)
+ version = version.splitlines()[0]
+
+ # cc: Sun C 5.10 SunOS_i386 2009/06/03
+ # cc: Studio 12.5 Sun C++ 5.14 SunOS_sparc Beta 2015/11/17
+ # cc: WorkShop Compilers 5.0 98/12/15 C 5.0
+ version_re = re.compile(r'cc: (studio.*?|\s+)?(sun\s+(c\+\+|c)|(WorkShop\s+Compilers))?\s+(?P<major>\d*)\.(?P<minor>\d*)', re.I).search
+ match = version_re(version)
+ if match:
+ k = match.groupdict()
+ conf.env.CC_VERSION = (k['major'], k['minor'])
+ else:
+ conf.fatal('Could not determine the suncc version.')
+
+# ============ the --as-needed flag should added during the configuration, not at runtime =========
+
+@conf
+def add_as_needed(self):
+ """
+ Adds ``--as-needed`` to the *LINKFLAGS*
+ On some platforms, it is a default flag. In some cases (e.g., in NS-3) it is necessary to explicitly disable this feature with `-Wl,--no-as-needed` flag.
+ """
+ if self.env.DEST_BINFMT == 'elf' and 'gcc' in (self.env.CXX_NAME, self.env.CC_NAME):
+ self.env.append_unique('LINKFLAGS', '-Wl,--as-needed')
+
+# ============ parallel configuration
+
+class cfgtask(Task.Task):
+ """
+ A task that executes build configuration tests (calls conf.check)
+
+ Make sure to use locks if concurrent access to the same conf.env data is necessary.
+ """
+ def __init__(self, *k, **kw):
+ Task.Task.__init__(self, *k, **kw)
+ self.run_after = set()
+
+ def display(self):
+ return ''
+
+ def runnable_status(self):
+ for x in self.run_after:
+ if not x.hasrun:
+ return Task.ASK_LATER
+ return Task.RUN_ME
+
+ def uid(self):
+ return Utils.SIG_NIL
+
+ def signature(self):
+ return Utils.SIG_NIL
+
+ def run(self):
+ conf = self.conf
+ bld = Build.BuildContext(top_dir=conf.srcnode.abspath(), out_dir=conf.bldnode.abspath())
+ bld.env = conf.env
+ bld.init_dirs()
+ bld.in_msg = 1 # suppress top-level start_msg
+ bld.logger = self.logger
+ bld.multicheck_task = self
+ args = self.args
+ try:
+ if 'func' in args:
+ bld.test(build_fun=args['func'],
+ msg=args.get('msg', ''),
+ okmsg=args.get('okmsg', ''),
+ errmsg=args.get('errmsg', ''),
+ )
+ else:
+ args['multicheck_mandatory'] = args.get('mandatory', True)
+ args['mandatory'] = True
+ try:
+ bld.check(**args)
+ finally:
+ args['mandatory'] = args['multicheck_mandatory']
+ except Exception:
+ return 1
+
+ def process(self):
+ Task.Task.process(self)
+ if 'msg' in self.args:
+ with self.generator.bld.multicheck_lock:
+ self.conf.start_msg(self.args['msg'])
+ if self.hasrun == Task.NOT_RUN:
+ self.conf.end_msg('test cancelled', 'YELLOW')
+ elif self.hasrun != Task.SUCCESS:
+ self.conf.end_msg(self.args.get('errmsg', 'no'), 'YELLOW')
+ else:
+ self.conf.end_msg(self.args.get('okmsg', 'yes'), 'GREEN')
+
+@conf
+def multicheck(self, *k, **kw):
+ """
+ Runs configuration tests in parallel; results are printed sequentially at the end of the build
+ but each test must provide its own msg value to display a line::
+
+ def test_build(ctx):
+ ctx.in_msg = True # suppress console outputs
+ ctx.check_large_file(mandatory=False)
+
+ conf.multicheck(
+ {'header_name':'stdio.h', 'msg':'... stdio', 'uselib_store':'STDIO', 'global_define':False},
+ {'header_name':'xyztabcd.h', 'msg':'... optional xyztabcd.h', 'mandatory': False},
+ {'header_name':'stdlib.h', 'msg':'... stdlib', 'okmsg': 'aye', 'errmsg': 'nope'},
+ {'func': test_build, 'msg':'... testing an arbitrary build function', 'okmsg':'ok'},
+ msg = 'Checking for headers in parallel',
+ mandatory = True, # mandatory tests raise an error at the end
+ run_all_tests = True, # try running all tests
+ )
+
+ The configuration tests may modify the values in conf.env in any order, and the define
+ values can affect configuration tests being executed. It is hence recommended
+ to provide `uselib_store` values with `global_define=False` to prevent such issues.
+ """
+ self.start_msg(kw.get('msg', 'Executing %d configuration tests' % len(k)), **kw)
+
+ # Force a copy so that threads append to the same list at least
+ # no order is guaranteed, but the values should not disappear at least
+ for var in ('DEFINES', DEFKEYS):
+ self.env.append_value(var, [])
+ self.env.DEFINE_COMMENTS = self.env.DEFINE_COMMENTS or {}
+
+ # define a task object that will execute our tests
+ class par(object):
+ def __init__(self):
+ self.keep = False
+ self.task_sigs = {}
+ self.progress_bar = 0
+ def total(self):
+ return len(tasks)
+ def to_log(self, *k, **kw):
+ return
+
+ bld = par()
+ bld.keep = kw.get('run_all_tests', True)
+ bld.imp_sigs = {}
+ tasks = []
+
+ id_to_task = {}
+ for dct in k:
+ x = Task.classes['cfgtask'](bld=bld, env=None)
+ tasks.append(x)
+ x.args = dct
+ x.bld = bld
+ x.conf = self
+ x.args = dct
+
+ # bind a logger that will keep the info in memory
+ x.logger = Logs.make_mem_logger(str(id(x)), self.logger)
+
+ if 'id' in dct:
+ id_to_task[dct['id']] = x
+
+ # second pass to set dependencies with after_test/before_test
+ for x in tasks:
+ for key in Utils.to_list(x.args.get('before_tests', [])):
+ tsk = id_to_task[key]
+ if not tsk:
+ raise ValueError('No test named %r' % key)
+ tsk.run_after.add(x)
+ for key in Utils.to_list(x.args.get('after_tests', [])):
+ tsk = id_to_task[key]
+ if not tsk:
+ raise ValueError('No test named %r' % key)
+ x.run_after.add(tsk)
+
+ def it():
+ yield tasks
+ while 1:
+ yield []
+ bld.producer = p = Runner.Parallel(bld, Options.options.jobs)
+ bld.multicheck_lock = Utils.threading.Lock()
+ p.biter = it()
+
+ self.end_msg('started')
+ p.start()
+
+ # flush the logs in order into the config.log
+ for x in tasks:
+ x.logger.memhandler.flush()
+
+ self.start_msg('-> processing test results')
+ if p.error:
+ for x in p.error:
+ if getattr(x, 'err_msg', None):
+ self.to_log(x.err_msg)
+ self.end_msg('fail', color='RED')
+ raise Errors.WafError('There is an error in the library, read config.log for more information')
+
+ failure_count = 0
+ for x in tasks:
+ if x.hasrun not in (Task.SUCCESS, Task.NOT_RUN):
+ failure_count += 1
+
+ if failure_count:
+ self.end_msg(kw.get('errmsg', '%s test failed' % failure_count), color='YELLOW', **kw)
+ else:
+ self.end_msg('all ok', **kw)
+
+ for x in tasks:
+ if x.hasrun != Task.SUCCESS:
+ if x.args.get('mandatory', True):
+ self.fatal(kw.get('fatalmsg') or 'One of the tests has failed, read config.log for more information')
+
+@conf
+def check_gcc_o_space(self, mode='c'):
+ if int(self.env.CC_VERSION[0]) > 4:
+ # this is for old compilers
+ return
+ self.env.stash()
+ if mode == 'c':
+ self.env.CCLNK_TGT_F = ['-o', '']
+ elif mode == 'cxx':
+ self.env.CXXLNK_TGT_F = ['-o', '']
+ features = '%s %sshlib' % (mode, mode)
+ try:
+ self.check(msg='Checking if the -o link must be split from arguments', fragment=SNIP_EMPTY_PROGRAM, features=features)
+ except self.errors.ConfigurationError:
+ self.env.revert()
+ else:
+ self.env.commit()
+
diff --git a/Tools/c_osx.py b/Tools/c_osx.py
new file mode 100644
index 0000000..f70b128
--- /dev/null
+++ b/Tools/c_osx.py
@@ -0,0 +1,193 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy 2008-2018 (ita)
+
+"""
+MacOSX related tools
+"""
+
+import os, shutil, platform
+from waflib import Task, Utils
+from waflib.TaskGen import taskgen_method, feature, after_method, before_method
+
+app_info = '''
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
+<plist version="0.9">
+<dict>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleGetInfoString</key>
+ <string>Created by Waf</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>NOTE</key>
+ <string>THIS IS A GENERATED FILE, DO NOT MODIFY</string>
+ <key>CFBundleExecutable</key>
+ <string>{app_name}</string>
+</dict>
+</plist>
+'''
+"""
+plist template
+"""
+
+@feature('c', 'cxx')
+def set_macosx_deployment_target(self):
+ """
+ see WAF issue 285 and also and also http://trac.macports.org/ticket/17059
+ """
+ if self.env.MACOSX_DEPLOYMENT_TARGET:
+ os.environ['MACOSX_DEPLOYMENT_TARGET'] = self.env.MACOSX_DEPLOYMENT_TARGET
+ elif 'MACOSX_DEPLOYMENT_TARGET' not in os.environ:
+ if Utils.unversioned_sys_platform() == 'darwin':
+ os.environ['MACOSX_DEPLOYMENT_TARGET'] = '.'.join(platform.mac_ver()[0].split('.')[:2])
+
+@taskgen_method
+def create_bundle_dirs(self, name, out):
+ """
+ Creates bundle folders, used by :py:func:`create_task_macplist` and :py:func:`create_task_macapp`
+ """
+ dir = out.parent.find_or_declare(name)
+ dir.mkdir()
+ macos = dir.find_or_declare(['Contents', 'MacOS'])
+ macos.mkdir()
+ return dir
+
+def bundle_name_for_output(out):
+ name = out.name
+ k = name.rfind('.')
+ if k >= 0:
+ name = name[:k] + '.app'
+ else:
+ name = name + '.app'
+ return name
+
+@feature('cprogram', 'cxxprogram')
+@after_method('apply_link')
+def create_task_macapp(self):
+ """
+ To compile an executable into a Mac application (a .app), set its *mac_app* attribute::
+
+ def build(bld):
+ bld.shlib(source='a.c', target='foo', mac_app=True)
+
+ To force *all* executables to be transformed into Mac applications::
+
+ def build(bld):
+ bld.env.MACAPP = True
+ bld.shlib(source='a.c', target='foo')
+ """
+ if self.env.MACAPP or getattr(self, 'mac_app', False):
+ out = self.link_task.outputs[0]
+
+ name = bundle_name_for_output(out)
+ dir = self.create_bundle_dirs(name, out)
+
+ n1 = dir.find_or_declare(['Contents', 'MacOS', out.name])
+
+ self.apptask = self.create_task('macapp', self.link_task.outputs, n1)
+ inst_to = getattr(self, 'install_path', '/Applications') + '/%s/Contents/MacOS/' % name
+ self.add_install_files(install_to=inst_to, install_from=n1, chmod=Utils.O755)
+
+ if getattr(self, 'mac_files', None):
+ # this only accepts files; they will be installed as seen from mac_files_root
+ mac_files_root = getattr(self, 'mac_files_root', None)
+ if isinstance(mac_files_root, str):
+ mac_files_root = self.path.find_node(mac_files_root)
+ if not mac_files_root:
+ self.bld.fatal('Invalid mac_files_root %r' % self.mac_files_root)
+ res_dir = n1.parent.parent.make_node('Resources')
+ inst_to = getattr(self, 'install_path', '/Applications') + '/%s/Resources' % name
+ for node in self.to_nodes(self.mac_files):
+ relpath = node.path_from(mac_files_root or node.parent)
+ self.create_task('macapp', node, res_dir.make_node(relpath))
+ self.add_install_as(install_to=os.path.join(inst_to, relpath), install_from=node)
+
+ if getattr(self.bld, 'is_install', None):
+ # disable regular binary installation
+ self.install_task.hasrun = Task.SKIP_ME
+
+@feature('cprogram', 'cxxprogram')
+@after_method('apply_link')
+def create_task_macplist(self):
+ """
+ Creates a :py:class:`waflib.Tools.c_osx.macplist` instance.
+ """
+ if self.env.MACAPP or getattr(self, 'mac_app', False):
+ out = self.link_task.outputs[0]
+
+ name = bundle_name_for_output(out)
+
+ dir = self.create_bundle_dirs(name, out)
+ n1 = dir.find_or_declare(['Contents', 'Info.plist'])
+ self.plisttask = plisttask = self.create_task('macplist', [], n1)
+ plisttask.context = {
+ 'app_name': self.link_task.outputs[0].name,
+ 'env': self.env
+ }
+
+ plist_ctx = getattr(self, 'plist_context', None)
+ if (plist_ctx):
+ plisttask.context.update(plist_ctx)
+
+ if getattr(self, 'mac_plist', False):
+ node = self.path.find_resource(self.mac_plist)
+ if node:
+ plisttask.inputs.append(node)
+ else:
+ plisttask.code = self.mac_plist
+ else:
+ plisttask.code = app_info
+
+ inst_to = getattr(self, 'install_path', '/Applications') + '/%s/Contents/' % name
+ self.add_install_files(install_to=inst_to, install_from=n1)
+
+@feature('cshlib', 'cxxshlib')
+@before_method('apply_link', 'propagate_uselib_vars')
+def apply_bundle(self):
+ """
+ To make a bundled shared library (a ``.bundle``), set the *mac_bundle* attribute::
+
+ def build(bld):
+ bld.shlib(source='a.c', target='foo', mac_bundle = True)
+
+ To force *all* executables to be transformed into bundles::
+
+ def build(bld):
+ bld.env.MACBUNDLE = True
+ bld.shlib(source='a.c', target='foo')
+ """
+ if self.env.MACBUNDLE or getattr(self, 'mac_bundle', False):
+ self.env.LINKFLAGS_cshlib = self.env.LINKFLAGS_cxxshlib = [] # disable the '-dynamiclib' flag
+ self.env.cshlib_PATTERN = self.env.cxxshlib_PATTERN = self.env.macbundle_PATTERN
+ use = self.use = self.to_list(getattr(self, 'use', []))
+ if not 'MACBUNDLE' in use:
+ use.append('MACBUNDLE')
+
+app_dirs = ['Contents', 'Contents/MacOS', 'Contents/Resources']
+
+class macapp(Task.Task):
+ """
+ Creates mac applications
+ """
+ color = 'PINK'
+ def run(self):
+ self.outputs[0].parent.mkdir()
+ shutil.copy2(self.inputs[0].srcpath(), self.outputs[0].abspath())
+
+class macplist(Task.Task):
+ """
+ Creates plist files
+ """
+ color = 'PINK'
+ ext_in = ['.bin']
+ def run(self):
+ if getattr(self, 'code', None):
+ txt = self.code
+ else:
+ txt = self.inputs[0].read()
+ context = getattr(self, 'context', {})
+ txt = txt.format(**context)
+ self.outputs[0].write(txt)
+
diff --git a/Tools/c_preproc.py b/Tools/c_preproc.py
new file mode 100644
index 0000000..68e5f5a
--- /dev/null
+++ b/Tools/c_preproc.py
@@ -0,0 +1,1091 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2006-2018 (ita)
+
+"""
+C/C++ preprocessor for finding dependencies
+
+Reasons for using the Waf preprocessor by default
+
+#. Some c/c++ extensions (Qt) require a custom preprocessor for obtaining the dependencies (.moc files)
+#. Not all compilers provide .d files for obtaining the dependencies (portability)
+#. A naive file scanner will not catch the constructs such as "#include foo()"
+#. A naive file scanner will catch unnecessary dependencies (change an unused header -> recompile everything)
+
+Regarding the speed concerns:
+
+* the preprocessing is performed only when files must be compiled
+* the macros are evaluated only for #if/#elif/#include
+* system headers are not scanned by default
+
+Now if you do not want the Waf preprocessor, the tool +gccdeps* uses the .d files produced
+during the compilation to track the dependencies (useful when used with the boost libraries).
+It only works with gcc >= 4.4 though.
+
+A dumb preprocessor is also available in the tool *c_dumbpreproc*
+"""
+# TODO: more varargs, pragma once
+
+import re, string, traceback
+from waflib import Logs, Utils, Errors
+
+class PreprocError(Errors.WafError):
+ pass
+
+FILE_CACHE_SIZE = 100000
+LINE_CACHE_SIZE = 100000
+
+POPFILE = '-'
+"Constant representing a special token used in :py:meth:`waflib.Tools.c_preproc.c_parser.start` iteration to switch to a header read previously"
+
+recursion_limit = 150
+"Limit on the amount of files to read in the dependency scanner"
+
+go_absolute = False
+"Set to True to track headers on files in /usr/include, else absolute paths are ignored (but it becomes very slow)"
+
+standard_includes = ['/usr/local/include', '/usr/include']
+if Utils.is_win32:
+ standard_includes = []
+
+use_trigraphs = 0
+"""Apply trigraph rules (False by default)"""
+
+# obsolete, do not use
+strict_quotes = 0
+
+g_optrans = {
+'not':'!',
+'not_eq':'!',
+'and':'&&',
+'and_eq':'&=',
+'or':'||',
+'or_eq':'|=',
+'xor':'^',
+'xor_eq':'^=',
+'bitand':'&',
+'bitor':'|',
+'compl':'~',
+}
+"""Operators such as and/or/xor for c++. Set an empty dict to disable."""
+
+# ignore #warning and #error
+re_lines = re.compile(
+ '^[ \t]*(?:#|%:)[ \t]*(ifdef|ifndef|if|else|elif|endif|include|import|define|undef|pragma)[ \t]*(.*)\r*$',
+ re.IGNORECASE | re.MULTILINE)
+"""Match #include lines"""
+
+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(r'^\s*once\s*', re.IGNORECASE)
+"""Match #pragma once statements"""
+
+re_nl = re.compile('\\\\\r*\n', re.MULTILINE)
+"""Match newlines"""
+
+re_cpp = re.compile(r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', re.DOTALL | re.MULTILINE )
+"""Filter C/C++ comments"""
+
+trig_def = [('??'+a, b) for a, b in zip("=-/!'()<>", r'#~\|^[]{}')]
+"""Trigraph definitions"""
+
+chr_esc = {'0':0, 'a':7, 'b':8, 't':9, 'n':10, 'f':11, 'v':12, 'r':13, '\\':92, "'":39}
+"""Escape characters"""
+
+NUM = 'i'
+"""Number token"""
+
+OP = 'O'
+"""Operator token"""
+
+IDENT = 'T'
+"""Identifier token"""
+
+STR = 's'
+"""String token"""
+
+CHAR = 'c'
+"""Character token"""
+
+tok_types = [NUM, STR, IDENT, OP]
+"""Token types"""
+
+exp_types = [
+ r"""0[xX](?P<hex>[a-fA-F0-9]+)(?P<qual1>[uUlL]*)|L*?'(?P<char>(\\.|[^\\'])+)'|(?P<n1>\d+)[Ee](?P<exp0>[+-]*?\d+)(?P<float0>[fFlL]*)|(?P<n2>\d*\.\d+)([Ee](?P<exp1>[+-]*?\d+))?(?P<float1>[fFlL]*)|(?P<n4>\d+\.\d*)([Ee](?P<exp2>[+-]*?\d+))?(?P<float2>[fFlL]*)|(?P<oct>0*)(?P<n0>\d+)(?P<qual2>[uUlL]*)""",
+ r'L?"([^"\\]|\\.)*"',
+ r'[a-zA-Z_]\w*',
+ r'%:%:|<<=|>>=|\.\.\.|<<|<%|<:|<=|>>|>=|\+\+|\+=|--|->|-=|\*=|/=|%:|%=|%>|==|&&|&=|\|\||\|=|\^=|:>|!=|##|[\(\)\{\}\[\]<>\?\|\^\*\+&=:!#;,%/\-\?\~\.]',
+]
+"""Expression types"""
+
+re_clexer = re.compile('|'.join(["(?P<%s>%s)" % (name, part) for name, part in zip(tok_types, exp_types)]), re.M)
+"""Match expressions into tokens"""
+
+accepted = 'a'
+"""Parser state is *accepted*"""
+
+ignored = 'i'
+"""Parser state is *ignored*, for example preprocessor lines in an #if 0 block"""
+
+undefined = 'u'
+"""Parser state is *undefined* at the moment"""
+
+skipped = 's'
+"""Parser state is *skipped*, for example preprocessor lines in a #elif 0 block"""
+
+def repl(m):
+ """Replace function used with :py:attr:`waflib.Tools.c_preproc.re_cpp`"""
+ s = m.group()
+ if s[0] == '/':
+ return ' '
+ return s
+
+prec = {}
+"""
+Operator precedence rules required for parsing expressions of the form::
+
+ #if 1 && 2 != 0
+"""
+ops = ['* / %', '+ -', '<< >>', '< <= >= >', '== !=', '& | ^', '&& ||', ',']
+for x, syms in enumerate(ops):
+ for u in syms.split():
+ prec[u] = x
+
+def reduce_nums(val_1, val_2, val_op):
+ """
+ Apply arithmetic rules to compute a result
+
+ :param val1: input parameter
+ :type val1: int or string
+ :param val2: input parameter
+ :type val2: int or string
+ :param val_op: C operator in *+*, */*, *-*, etc
+ :type val_op: string
+ :rtype: int
+ """
+ #print val_1, val_2, val_op
+
+ # now perform the operation, make certain a and b are numeric
+ try:
+ a = 0 + val_1
+ except TypeError:
+ a = int(val_1)
+ try:
+ b = 0 + val_2
+ except TypeError:
+ b = int(val_2)
+
+ d = val_op
+ if d == '%':
+ c = a % b
+ elif d=='+':
+ c = a + b
+ elif d=='-':
+ c = a - b
+ elif d=='*':
+ c = a * b
+ elif d=='/':
+ c = a / b
+ elif d=='^':
+ c = a ^ b
+ elif d=='==':
+ c = int(a == b)
+ elif d=='|' or d == 'bitor':
+ c = a | b
+ elif d=='||' or d == 'or' :
+ c = int(a or b)
+ elif d=='&' or d == 'bitand':
+ c = a & b
+ elif d=='&&' or d == 'and':
+ c = int(a and b)
+ elif d=='!=' or d == 'not_eq':
+ c = int(a != b)
+ elif d=='^' or d == 'xor':
+ c = int(a^b)
+ elif d=='<=':
+ c = int(a <= b)
+ elif d=='<':
+ c = int(a < b)
+ elif d=='>':
+ c = int(a > b)
+ elif d=='>=':
+ c = int(a >= b)
+ elif d=='<<':
+ c = a << b
+ elif d=='>>':
+ c = a >> b
+ else:
+ c = 0
+ return c
+
+def get_num(lst):
+ """
+ Try to obtain a number from a list of tokens. The token types are defined in :py:attr:`waflib.Tools.ccroot.tok_types`.
+
+ :param lst: list of preprocessor tokens
+ :type lst: list of tuple (tokentype, value)
+ :return: a pair containing the number and the rest of the list
+ :rtype: tuple(value, list)
+ """
+ if not lst:
+ raise PreprocError('empty list for get_num')
+ (p, v) = lst[0]
+ if p == OP:
+ if v == '(':
+ count_par = 1
+ i = 1
+ while i < len(lst):
+ (p, v) = lst[i]
+
+ if p == OP:
+ if v == ')':
+ count_par -= 1
+ if count_par == 0:
+ break
+ elif v == '(':
+ count_par += 1
+ i += 1
+ else:
+ raise PreprocError('rparen expected %r' % lst)
+
+ (num, _) = get_term(lst[1:i])
+ return (num, lst[i+1:])
+
+ elif v == '+':
+ return get_num(lst[1:])
+ elif v == '-':
+ num, lst = get_num(lst[1:])
+ return (reduce_nums('-1', num, '*'), lst)
+ elif v == '!':
+ num, lst = get_num(lst[1:])
+ return (int(not int(num)), lst)
+ elif v == '~':
+ num, lst = get_num(lst[1:])
+ return (~ int(num), lst)
+ else:
+ raise PreprocError('Invalid op token %r for get_num' % lst)
+ elif p == NUM:
+ return v, lst[1:]
+ elif p == IDENT:
+ # all macros should have been replaced, remaining identifiers eval to 0
+ return 0, lst[1:]
+ else:
+ raise PreprocError('Invalid token %r for get_num' % lst)
+
+def get_term(lst):
+ """
+ Evaluate an expression recursively, for example::
+
+ 1+1+1 -> 2+1 -> 3
+
+ :param lst: list of tokens
+ :type lst: list of tuple(token, value)
+ :return: the value and the remaining tokens
+ :rtype: value, list
+ """
+
+ if not lst:
+ raise PreprocError('empty list for get_term')
+ num, lst = get_num(lst)
+ if not lst:
+ return (num, [])
+ (p, v) = lst[0]
+ if p == OP:
+ if v == ',':
+ # skip
+ return get_term(lst[1:])
+ elif v == '?':
+ count_par = 0
+ i = 1
+ while i < len(lst):
+ (p, v) = lst[i]
+
+ if p == OP:
+ if v == ')':
+ count_par -= 1
+ elif v == '(':
+ count_par += 1
+ elif v == ':':
+ if count_par == 0:
+ break
+ i += 1
+ else:
+ raise PreprocError('rparen expected %r' % lst)
+
+ if int(num):
+ return get_term(lst[1:i])
+ else:
+ return get_term(lst[i+1:])
+
+ else:
+ num2, lst = get_num(lst[1:])
+
+ if not lst:
+ # no more tokens to process
+ num2 = reduce_nums(num, num2, v)
+ return get_term([(NUM, num2)] + lst)
+
+ # operator precedence
+ p2, v2 = lst[0]
+ if p2 != OP:
+ raise PreprocError('op expected %r' % lst)
+
+ if prec[v2] >= prec[v]:
+ num2 = reduce_nums(num, num2, v)
+ return get_term([(NUM, num2)] + lst)
+ else:
+ num3, lst = get_num(lst[1:])
+ num3 = reduce_nums(num2, num3, v2)
+ return get_term([(NUM, num), (p, v), (NUM, num3)] + lst)
+
+
+ raise PreprocError('cannot reduce %r' % lst)
+
+def reduce_eval(lst):
+ """
+ Take a list of tokens and output true or false for #if/#elif conditions.
+
+ :param lst: a list of tokens
+ :type lst: list of tuple(token, value)
+ :return: a token
+ :rtype: tuple(NUM, int)
+ """
+ num, lst = get_term(lst)
+ return (NUM, num)
+
+def stringize(lst):
+ """
+ Merge a list of tokens into a string
+
+ :param lst: a list of tokens
+ :type lst: list of tuple(token, value)
+ :rtype: string
+ """
+ lst = [str(v2) for (p2, v2) in lst]
+ return "".join(lst)
+
+def paste_tokens(t1, t2):
+ """
+ Token pasting works between identifiers, particular operators, and identifiers and numbers::
+
+ a ## b -> ab
+ > ## = -> >=
+ a ## 2 -> a2
+
+ :param t1: token
+ :type t1: tuple(type, value)
+ :param t2: token
+ :type t2: tuple(type, value)
+ """
+ p1 = None
+ if t1[0] == OP and t2[0] == OP:
+ p1 = OP
+ elif t1[0] == IDENT and (t2[0] == IDENT or t2[0] == NUM):
+ p1 = IDENT
+ elif t1[0] == NUM and t2[0] == NUM:
+ p1 = NUM
+ if not p1:
+ raise PreprocError('tokens do not make a valid paste %r and %r' % (t1, t2))
+ return (p1, t1[1] + t2[1])
+
+def reduce_tokens(lst, defs, ban=[]):
+ """
+ Replace the tokens in lst, using the macros provided in defs, and a list of macros that cannot be re-applied
+
+ :param lst: list of tokens
+ :type lst: list of tuple(token, value)
+ :param defs: macro definitions
+ :type defs: dict
+ :param ban: macros that cannot be substituted (recursion is not allowed)
+ :type ban: list of string
+ :return: the new list of tokens
+ :rtype: value, list
+ """
+
+ i = 0
+ while i < len(lst):
+ (p, v) = lst[i]
+
+ if p == IDENT and v == "defined":
+ del lst[i]
+ if i < len(lst):
+ (p2, v2) = lst[i]
+ if p2 == IDENT:
+ if v2 in defs:
+ lst[i] = (NUM, 1)
+ else:
+ lst[i] = (NUM, 0)
+ elif p2 == OP and v2 == '(':
+ del lst[i]
+ (p2, v2) = lst[i]
+ del lst[i] # remove the ident, and change the ) for the value
+ if v2 in defs:
+ lst[i] = (NUM, 1)
+ else:
+ lst[i] = (NUM, 0)
+ else:
+ raise PreprocError('Invalid define expression %r' % lst)
+
+ elif p == IDENT and v in defs:
+
+ if isinstance(defs[v], str):
+ a, b = extract_macro(defs[v])
+ defs[v] = b
+ macro_def = defs[v]
+ to_add = macro_def[1]
+
+ if isinstance(macro_def[0], list):
+ # macro without arguments
+ del lst[i]
+ accu = to_add[:]
+ reduce_tokens(accu, defs, ban+[v])
+ for tmp in accu:
+ lst.insert(i, tmp)
+ i += 1
+ else:
+ # collect the arguments for the funcall
+
+ args = []
+ del lst[i]
+
+ if i >= len(lst):
+ raise PreprocError('expected ( after %r (got nothing)' % v)
+
+ (p2, v2) = lst[i]
+ if p2 != OP or v2 != '(':
+ raise PreprocError('expected ( after %r' % v)
+
+ del lst[i]
+
+ one_param = []
+ count_paren = 0
+ while i < len(lst):
+ p2, v2 = lst[i]
+
+ del lst[i]
+ if p2 == OP and count_paren == 0:
+ if v2 == '(':
+ one_param.append((p2, v2))
+ count_paren += 1
+ elif v2 == ')':
+ if one_param:
+ args.append(one_param)
+ break
+ elif v2 == ',':
+ if not one_param:
+ raise PreprocError('empty param in funcall %r' % v)
+ args.append(one_param)
+ one_param = []
+ else:
+ one_param.append((p2, v2))
+ else:
+ one_param.append((p2, v2))
+ if v2 == '(':
+ count_paren += 1
+ elif v2 == ')':
+ count_paren -= 1
+ else:
+ raise PreprocError('malformed macro')
+
+ # substitute the arguments within the define expression
+ accu = []
+ arg_table = macro_def[0]
+ j = 0
+ while j < len(to_add):
+ (p2, v2) = to_add[j]
+
+ if p2 == OP and v2 == '#':
+ # stringize is for arguments only
+ if j+1 < len(to_add) and to_add[j+1][0] == IDENT and to_add[j+1][1] in arg_table:
+ toks = args[arg_table[to_add[j+1][1]]]
+ accu.append((STR, stringize(toks)))
+ j += 1
+ else:
+ accu.append((p2, v2))
+ elif p2 == OP and v2 == '##':
+ # token pasting, how can man invent such a complicated system?
+ if accu and j+1 < len(to_add):
+ # we have at least two tokens
+
+ t1 = accu[-1]
+
+ if to_add[j+1][0] == IDENT and to_add[j+1][1] in arg_table:
+ toks = args[arg_table[to_add[j+1][1]]]
+
+ if toks:
+ accu[-1] = paste_tokens(t1, toks[0]) #(IDENT, accu[-1][1] + toks[0][1])
+ accu.extend(toks[1:])
+ else:
+ # error, case "a##"
+ accu.append((p2, v2))
+ accu.extend(toks)
+ elif to_add[j+1][0] == IDENT and to_add[j+1][1] == '__VA_ARGS__':
+ # first collect the tokens
+ va_toks = []
+ st = len(macro_def[0])
+ pt = len(args)
+ for x in args[pt-st+1:]:
+ va_toks.extend(x)
+ va_toks.append((OP, ','))
+ if va_toks:
+ va_toks.pop() # extra comma
+ if len(accu)>1:
+ (p3, v3) = accu[-1]
+ (p4, v4) = accu[-2]
+ if v3 == '##':
+ # remove the token paste
+ accu.pop()
+ if v4 == ',' and pt < st:
+ # remove the comma
+ accu.pop()
+ accu += va_toks
+ else:
+ accu[-1] = paste_tokens(t1, to_add[j+1])
+
+ j += 1
+ else:
+ # Invalid paste, case "##a" or "b##"
+ accu.append((p2, v2))
+
+ elif p2 == IDENT and v2 in arg_table:
+ toks = args[arg_table[v2]]
+ reduce_tokens(toks, defs, ban+[v])
+ accu.extend(toks)
+ else:
+ accu.append((p2, v2))
+
+ j += 1
+
+
+ reduce_tokens(accu, defs, ban+[v])
+
+ for x in range(len(accu)-1, -1, -1):
+ lst.insert(i, accu[x])
+
+ i += 1
+
+
+def eval_macro(lst, defs):
+ """
+ Reduce the tokens by :py:func:`waflib.Tools.c_preproc.reduce_tokens` and try to return a 0/1 result by :py:func:`waflib.Tools.c_preproc.reduce_eval`.
+
+ :param lst: list of tokens
+ :type lst: list of tuple(token, value)
+ :param defs: macro definitions
+ :type defs: dict
+ :rtype: int
+ """
+ reduce_tokens(lst, defs, [])
+ if not lst:
+ raise PreprocError('missing tokens to evaluate')
+
+ if lst:
+ p, v = lst[0]
+ if p == IDENT and v not in defs:
+ raise PreprocError('missing macro %r' % lst)
+
+ p, v = reduce_eval(lst)
+ return int(v) != 0
+
+def extract_macro(txt):
+ """
+ Process a macro definition of the form::
+ #define f(x, y) x * y
+
+ into a function or a simple macro without arguments
+
+ :param txt: expression to exact a macro definition from
+ :type txt: string
+ :return: a tuple containing the name, the list of arguments and the replacement
+ :rtype: tuple(string, [list, list])
+ """
+ t = tokenize(txt)
+ if re_fun.search(txt):
+ p, name = t[0]
+
+ p, v = t[1]
+ if p != OP:
+ raise PreprocError('expected (')
+
+ i = 1
+ pindex = 0
+ params = {}
+ prev = '('
+
+ while 1:
+ i += 1
+ p, v = t[i]
+
+ if prev == '(':
+ if p == IDENT:
+ params[v] = pindex
+ pindex += 1
+ prev = p
+ elif p == OP and v == ')':
+ break
+ else:
+ raise PreprocError('unexpected token (3)')
+ elif prev == IDENT:
+ if p == OP and v == ',':
+ prev = v
+ elif p == OP and v == ')':
+ break
+ else:
+ raise PreprocError('comma or ... expected')
+ elif prev == ',':
+ if p == IDENT:
+ params[v] = pindex
+ pindex += 1
+ prev = p
+ elif p == OP and v == '...':
+ raise PreprocError('not implemented (1)')
+ else:
+ raise PreprocError('comma or ... expected (2)')
+ elif prev == '...':
+ raise PreprocError('not implemented (2)')
+ else:
+ raise PreprocError('unexpected else')
+
+ #~ print (name, [params, t[i+1:]])
+ return (name, [params, t[i+1:]])
+ else:
+ (p, v) = t[0]
+ if len(t) > 1:
+ return (v, [[], t[1:]])
+ else:
+ # empty define, assign an empty token
+ return (v, [[], [('T','')]])
+
+re_include = re.compile(r'^\s*(<(?:.*)>|"(?:.*)")')
+def extract_include(txt, defs):
+ """
+ Process a line in the form::
+
+ #include foo
+
+ :param txt: include line to process
+ :type txt: string
+ :param defs: macro definitions
+ :type defs: dict
+ :return: the file name
+ :rtype: string
+ """
+ m = re_include.search(txt)
+ if m:
+ txt = m.group(1)
+ return txt[0], txt[1:-1]
+
+ # perform preprocessing and look at the result, it must match an include
+ toks = tokenize(txt)
+ reduce_tokens(toks, defs, ['waf_include'])
+
+ if not toks:
+ raise PreprocError('could not parse include %r' % txt)
+
+ if len(toks) == 1:
+ if toks[0][0] == STR:
+ return '"', toks[0][1]
+ else:
+ if toks[0][1] == '<' and toks[-1][1] == '>':
+ ret = '<', stringize(toks).lstrip('<').rstrip('>')
+ return ret
+
+ raise PreprocError('could not parse include %r' % txt)
+
+def parse_char(txt):
+ """
+ Parse a c character
+
+ :param txt: character to parse
+ :type txt: string
+ :return: a character literal
+ :rtype: string
+ """
+
+ if not txt:
+ raise PreprocError('attempted to parse a null char')
+ if txt[0] != '\\':
+ return ord(txt)
+ c = txt[1]
+ if c == 'x':
+ if len(txt) == 4 and txt[3] in string.hexdigits:
+ return int(txt[2:], 16)
+ return int(txt[2:], 16)
+ elif c.isdigit():
+ if c == '0' and len(txt)==2:
+ return 0
+ for i in 3, 2, 1:
+ if len(txt) > i and txt[1:1+i].isdigit():
+ return (1+i, int(txt[1:1+i], 8))
+ else:
+ try:
+ return chr_esc[c]
+ except KeyError:
+ raise PreprocError('could not parse char literal %r' % txt)
+
+def tokenize(s):
+ """
+ Convert a string into a list of tokens (shlex.split does not apply to c/c++/d)
+
+ :param s: input to tokenize
+ :type s: string
+ :return: a list of tokens
+ :rtype: list of tuple(token, value)
+ """
+ return tokenize_private(s)[:] # force a copy of the results
+
+def tokenize_private(s):
+ ret = []
+ for match in re_clexer.finditer(s):
+ m = match.group
+ for name in tok_types:
+ v = m(name)
+ if v:
+ if name == IDENT:
+ if v in g_optrans:
+ name = OP
+ elif v.lower() == "true":
+ v = 1
+ name = NUM
+ elif v.lower() == "false":
+ v = 0
+ name = NUM
+ elif name == NUM:
+ if m('oct'):
+ v = int(v, 8)
+ elif m('hex'):
+ v = int(m('hex'), 16)
+ elif m('n0'):
+ v = m('n0')
+ else:
+ v = m('char')
+ if v:
+ v = parse_char(v)
+ else:
+ v = m('n2') or m('n4')
+ elif name == OP:
+ if v == '%:':
+ v = '#'
+ elif v == '%:%:':
+ v = '##'
+ elif name == STR:
+ # remove the quotes around the string
+ v = v[1:-1]
+ ret.append((name, v))
+ break
+ return ret
+
+def format_defines(lst):
+ ret = []
+ for y in lst:
+ if y:
+ pos = y.find('=')
+ if pos == -1:
+ # "-DFOO" should give "#define FOO 1"
+ ret.append(y)
+ elif pos > 0:
+ # all others are assumed to be -DX=Y
+ ret.append('%s %s' % (y[:pos], y[pos+1:]))
+ else:
+ raise ValueError('Invalid define expression %r' % y)
+ return ret
+
+class c_parser(object):
+ """
+ Used by :py:func:`waflib.Tools.c_preproc.scan` to parse c/h files. Note that by default,
+ only project headers are parsed.
+ """
+ def __init__(self, nodepaths=None, defines=None):
+ self.lines = []
+ """list of lines read"""
+
+ if defines is None:
+ self.defs = {}
+ else:
+ self.defs = dict(defines) # make a copy
+ self.state = []
+
+ self.count_files = 0
+ self.currentnode_stack = []
+
+ self.nodepaths = nodepaths or []
+ """Include paths"""
+
+ self.nodes = []
+ """List of :py:class:`waflib.Node.Node` found so far"""
+
+ self.names = []
+ """List of file names that could not be matched by any file"""
+
+ self.curfile = ''
+ """Current file"""
+
+ self.ban_includes = set()
+ """Includes that must not be read (#pragma once)"""
+
+ self.listed = set()
+ """Include nodes/names already listed to avoid duplicates in self.nodes/self.names"""
+
+ def cached_find_resource(self, node, filename):
+ """
+ Find a file from the input directory
+
+ :param node: directory
+ :type node: :py:class:`waflib.Node.Node`
+ :param filename: header to find
+ :type filename: string
+ :return: the node if found, or None
+ :rtype: :py:class:`waflib.Node.Node`
+ """
+ try:
+ cache = node.ctx.preproc_cache_node
+ except AttributeError:
+ cache = node.ctx.preproc_cache_node = Utils.lru_cache(FILE_CACHE_SIZE)
+
+ key = (node, filename)
+ try:
+ return cache[key]
+ except KeyError:
+ ret = node.find_resource(filename)
+ if ret:
+ if getattr(ret, 'children', None):
+ ret = None
+ elif ret.is_child_of(node.ctx.bldnode):
+ tmp = node.ctx.srcnode.search_node(ret.path_from(node.ctx.bldnode))
+ if tmp and getattr(tmp, 'children', None):
+ ret = None
+ cache[key] = ret
+ return ret
+
+ def tryfind(self, filename, kind='"', env=None):
+ """
+ Try to obtain a node from the filename based from the include paths. Will add
+ the node found to :py:attr:`waflib.Tools.c_preproc.c_parser.nodes` or the file name to
+ :py:attr:`waflib.Tools.c_preproc.c_parser.names` if no corresponding file is found. Called by
+ :py:attr:`waflib.Tools.c_preproc.c_parser.start`.
+
+ :param filename: header to find
+ :type filename: string
+ :return: the node if found
+ :rtype: :py:class:`waflib.Node.Node`
+ """
+ if filename.endswith('.moc'):
+ # we could let the qt4 module use a subclass, but then the function "scan" below must be duplicated
+ # in the qt4 and in the qt5 classes. So we have two lines here and it is sufficient.
+ self.names.append(filename)
+ return None
+
+ self.curfile = filename
+
+ found = None
+ if kind == '"':
+ if env.MSVC_VERSION:
+ for n in reversed(self.currentnode_stack):
+ found = self.cached_find_resource(n, filename)
+ if found:
+ break
+ else:
+ found = self.cached_find_resource(self.currentnode_stack[-1], filename)
+
+ if not found:
+ for n in self.nodepaths:
+ found = self.cached_find_resource(n, filename)
+ if found:
+ break
+
+ listed = self.listed
+ if found and not found in self.ban_includes:
+ if found not in listed:
+ listed.add(found)
+ self.nodes.append(found)
+ self.addlines(found)
+ else:
+ if filename not in listed:
+ listed.add(filename)
+ self.names.append(filename)
+ return found
+
+ def filter_comments(self, node):
+ """
+ Filter the comments from a c/h file, and return the preprocessor lines.
+ The regexps :py:attr:`waflib.Tools.c_preproc.re_cpp`, :py:attr:`waflib.Tools.c_preproc.re_nl` and :py:attr:`waflib.Tools.c_preproc.re_lines` are used internally.
+
+ :return: the preprocessor directives as a list of (keyword, line)
+ :rtype: a list of string pairs
+ """
+ # return a list of tuples : keyword, line
+ code = node.read()
+ if use_trigraphs:
+ for (a, b) in trig_def:
+ code = code.split(a).join(b)
+ code = re_nl.sub('', code)
+ code = re_cpp.sub(repl, code)
+ return re_lines.findall(code)
+
+ def parse_lines(self, node):
+ try:
+ cache = node.ctx.preproc_cache_lines
+ except AttributeError:
+ cache = node.ctx.preproc_cache_lines = Utils.lru_cache(LINE_CACHE_SIZE)
+ try:
+ return cache[node]
+ except KeyError:
+ cache[node] = lines = self.filter_comments(node)
+ lines.append((POPFILE, ''))
+ lines.reverse()
+ return lines
+
+ def addlines(self, node):
+ """
+ Add the lines from a header in the list of preprocessor lines to parse
+
+ :param node: header
+ :type node: :py:class:`waflib.Node.Node`
+ """
+
+ self.currentnode_stack.append(node.parent)
+
+ self.count_files += 1
+ if self.count_files > recursion_limit:
+ # issue #812
+ raise PreprocError('recursion limit exceeded')
+
+ if Logs.verbose:
+ Logs.debug('preproc: reading file %r', node)
+ try:
+ lines = self.parse_lines(node)
+ except EnvironmentError:
+ raise PreprocError('could not read the file %r' % node)
+ except Exception:
+ if Logs.verbose > 0:
+ Logs.error('parsing %r failed %s', node, traceback.format_exc())
+ else:
+ self.lines.extend(lines)
+
+ def start(self, node, env):
+ """
+ Preprocess a source file to obtain the dependencies, which are accumulated to :py:attr:`waflib.Tools.c_preproc.c_parser.nodes`
+ and :py:attr:`waflib.Tools.c_preproc.c_parser.names`.
+
+ :param node: source file
+ :type node: :py:class:`waflib.Node.Node`
+ :param env: config set containing additional defines to take into account
+ :type env: :py:class:`waflib.ConfigSet.ConfigSet`
+ """
+ Logs.debug('preproc: scanning %s (in %s)', node.name, node.parent.name)
+
+ self.current_file = node
+ self.addlines(node)
+
+ # macros may be defined on the command-line, so they must be parsed as if they were part of the file
+ if env.DEFINES:
+ lst = format_defines(env.DEFINES)
+ lst.reverse()
+ self.lines.extend([('define', x) for x in lst])
+
+ while self.lines:
+ (token, line) = self.lines.pop()
+ if token == POPFILE:
+ self.count_files -= 1
+ self.currentnode_stack.pop()
+ continue
+
+ try:
+ state = self.state
+
+ # make certain we define the state if we are about to enter in an if block
+ if token[:2] == 'if':
+ state.append(undefined)
+ elif token == 'endif':
+ state.pop()
+
+ # skip lines when in a dead 'if' branch, wait for the endif
+ if token[0] != 'e':
+ if skipped in self.state or ignored in self.state:
+ continue
+
+ if token == 'if':
+ ret = eval_macro(tokenize(line), self.defs)
+ if ret:
+ state[-1] = accepted
+ else:
+ state[-1] = ignored
+ elif token == 'ifdef':
+ m = re_mac.match(line)
+ if m and m.group() in self.defs:
+ state[-1] = accepted
+ else:
+ state[-1] = ignored
+ elif token == 'ifndef':
+ m = re_mac.match(line)
+ if m and m.group() in self.defs:
+ state[-1] = ignored
+ else:
+ state[-1] = accepted
+ elif token == 'include' or token == 'import':
+ (kind, inc) = extract_include(line, self.defs)
+ self.current_file = self.tryfind(inc, kind, env)
+ if token == 'import':
+ self.ban_includes.add(self.current_file)
+ elif token == 'elif':
+ if state[-1] == accepted:
+ state[-1] = skipped
+ elif state[-1] == ignored:
+ if eval_macro(tokenize(line), self.defs):
+ state[-1] = accepted
+ elif token == 'else':
+ if state[-1] == accepted:
+ state[-1] = skipped
+ elif state[-1] == ignored:
+ state[-1] = accepted
+ elif token == 'define':
+ try:
+ self.defs[self.define_name(line)] = line
+ except AttributeError:
+ raise PreprocError('Invalid define line %r' % line)
+ elif token == 'undef':
+ m = re_mac.match(line)
+ if m and m.group() in self.defs:
+ self.defs.__delitem__(m.group())
+ #print "undef %s" % name
+ elif token == 'pragma':
+ if re_pragma_once.match(line.lower()):
+ self.ban_includes.add(self.current_file)
+ except Exception as e:
+ if Logs.verbose:
+ Logs.debug('preproc: line parsing failed (%s): %s %s', e, line, traceback.format_exc())
+
+ def define_name(self, line):
+ """
+ :param line: define line
+ :type line: string
+ :rtype: string
+ :return: the define name
+ """
+ return re_mac.match(line).group()
+
+def scan(task):
+ """
+ Get the dependencies using a c/c++ preprocessor, this is required for finding dependencies of the kind::
+
+ #include some_macro()
+
+ This function is bound as a task method on :py:class:`waflib.Tools.c.c` and :py:class:`waflib.Tools.cxx.cxx` for example
+ """
+ try:
+ incn = task.generator.includes_nodes
+ except AttributeError:
+ raise Errors.WafError('%r is missing a feature such as "c", "cxx" or "includes": ' % task.generator)
+
+ if go_absolute:
+ nodepaths = incn + [task.generator.bld.root.find_dir(x) for x in standard_includes]
+ else:
+ nodepaths = [x for x in incn if x.is_child_of(x.ctx.srcnode) or x.is_child_of(x.ctx.bldnode)]
+
+ tmp = c_parser(nodepaths)
+ tmp.start(task.inputs[0], task.env)
+ return (tmp.nodes, tmp.names)
diff --git a/Tools/c_tests.py b/Tools/c_tests.py
new file mode 100644
index 0000000..f858df5
--- /dev/null
+++ b/Tools/c_tests.py
@@ -0,0 +1,229 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2016-2018 (ita)
+
+"""
+Various configuration tests.
+"""
+
+from waflib import Task
+from waflib.Configure import conf
+from waflib.TaskGen import feature, before_method, after_method
+
+LIB_CODE = '''
+#ifdef _MSC_VER
+#define testEXPORT __declspec(dllexport)
+#else
+#define testEXPORT
+#endif
+testEXPORT int lib_func(void) { return 9; }
+'''
+
+MAIN_CODE = '''
+#ifdef _MSC_VER
+#define testEXPORT __declspec(dllimport)
+#else
+#define testEXPORT
+#endif
+testEXPORT int lib_func(void);
+int main(int argc, char **argv) {
+ (void)argc; (void)argv;
+ return !(lib_func() == 9);
+}
+'''
+
+@feature('link_lib_test')
+@before_method('process_source')
+def link_lib_test_fun(self):
+ """
+ The configuration test :py:func:`waflib.Configure.run_build` declares a unique task generator,
+ so we need to create other task generators from here to check if the linker is able to link libraries.
+ """
+ def write_test_file(task):
+ task.outputs[0].write(task.generator.code)
+
+ rpath = []
+ if getattr(self, 'add_rpath', False):
+ rpath = [self.bld.path.get_bld().abspath()]
+
+ mode = self.mode
+ m = '%s %s' % (mode, mode)
+ ex = self.test_exec and 'test_exec' or ''
+ bld = self.bld
+ bld(rule=write_test_file, target='test.' + mode, code=LIB_CODE)
+ bld(rule=write_test_file, target='main.' + mode, code=MAIN_CODE)
+ bld(features='%sshlib' % m, source='test.' + mode, target='test')
+ bld(features='%sprogram %s' % (m, ex), source='main.' + mode, target='app', use='test', rpath=rpath)
+
+@conf
+def check_library(self, mode=None, test_exec=True):
+ """
+ Checks if libraries can be linked with the current linker. Uses :py:func:`waflib.Tools.c_tests.link_lib_test_fun`.
+
+ :param mode: c or cxx or d
+ :type mode: string
+ """
+ if not mode:
+ mode = 'c'
+ if self.env.CXX:
+ mode = 'cxx'
+ self.check(
+ compile_filename = [],
+ features = 'link_lib_test',
+ msg = 'Checking for libraries',
+ mode = mode,
+ test_exec = test_exec)
+
+########################################################################################
+
+INLINE_CODE = '''
+typedef int foo_t;
+static %s foo_t static_foo () {return 0; }
+%s foo_t foo () {
+ return 0;
+}
+'''
+INLINE_VALUES = ['inline', '__inline__', '__inline']
+
+@conf
+def check_inline(self, **kw):
+ """
+ Checks for the right value for inline macro.
+ Define INLINE_MACRO to 1 if the define is found.
+ If the inline macro is not 'inline', add a define to the ``config.h`` (#define inline __inline__)
+
+ :param define_name: define INLINE_MACRO by default to 1 if the macro is defined
+ :type define_name: string
+ :param features: by default *c* or *cxx* depending on the compiler present
+ :type features: list of string
+ """
+ self.start_msg('Checking for inline')
+
+ if not 'define_name' in kw:
+ kw['define_name'] = 'INLINE_MACRO'
+ if not 'features' in kw:
+ if self.env.CXX:
+ kw['features'] = ['cxx']
+ else:
+ kw['features'] = ['c']
+
+ for x in INLINE_VALUES:
+ kw['fragment'] = INLINE_CODE % (x, x)
+
+ try:
+ self.check(**kw)
+ except self.errors.ConfigurationError:
+ continue
+ else:
+ self.end_msg(x)
+ if x != 'inline':
+ self.define('inline', x, quote=False)
+ return x
+ self.fatal('could not use inline functions')
+
+########################################################################################
+
+LARGE_FRAGMENT = '''#include <unistd.h>
+int main(int argc, char **argv) {
+ (void)argc; (void)argv;
+ return !(sizeof(off_t) >= 8);
+}
+'''
+
+@conf
+def check_large_file(self, **kw):
+ """
+ Checks for large file support and define the macro HAVE_LARGEFILE
+ The test is skipped on win32 systems (DEST_BINFMT == pe).
+
+ :param define_name: define to set, by default *HAVE_LARGEFILE*
+ :type define_name: string
+ :param execute: execute the test (yes by default)
+ :type execute: bool
+ """
+ if not 'define_name' in kw:
+ kw['define_name'] = 'HAVE_LARGEFILE'
+ if not 'execute' in kw:
+ kw['execute'] = True
+
+ if not 'features' in kw:
+ if self.env.CXX:
+ kw['features'] = ['cxx', 'cxxprogram']
+ else:
+ kw['features'] = ['c', 'cprogram']
+
+ kw['fragment'] = LARGE_FRAGMENT
+
+ kw['msg'] = 'Checking for large file support'
+ ret = True
+ try:
+ if self.env.DEST_BINFMT != 'pe':
+ ret = self.check(**kw)
+ except self.errors.ConfigurationError:
+ pass
+ else:
+ if ret:
+ return True
+
+ kw['msg'] = 'Checking for -D_FILE_OFFSET_BITS=64'
+ kw['defines'] = ['_FILE_OFFSET_BITS=64']
+ try:
+ ret = self.check(**kw)
+ except self.errors.ConfigurationError:
+ pass
+ else:
+ self.define('_FILE_OFFSET_BITS', 64)
+ return ret
+
+ self.fatal('There is no support for large files')
+
+########################################################################################
+
+ENDIAN_FRAGMENT = '''
+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) {
+ return ascii_mm[i] + ascii_ii[i];
+}
+short int ebcdic_ii[] = { 0x89D3, 0xE3E3, 0x8593, 0x95C5, 0x89C4, 0x9581, 0 };
+short int ebcdic_mm[] = { 0xC2C9, 0xC785, 0x95C4, 0x8981, 0x95E2, 0xA8E2, 0 };
+int use_ebcdic (int i) {
+ return ebcdic_mm[i] + ebcdic_ii[i];
+}
+extern int foo;
+'''
+
+class grep_for_endianness(Task.Task):
+ """
+ Task that reads a binary and tries to determine the endianness
+ """
+ color = 'PINK'
+ def run(self):
+ txt = self.inputs[0].read(flags='rb').decode('latin-1')
+ if txt.find('LiTTleEnDian') > -1:
+ self.generator.tmp.append('little')
+ elif txt.find('BIGenDianSyS') > -1:
+ self.generator.tmp.append('big')
+ else:
+ return -1
+
+@feature('grep_for_endianness')
+@after_method('process_source')
+def grep_for_endianness_fun(self):
+ """
+ Used by the endianness configuration test
+ """
+ self.create_task('grep_for_endianness', self.compiled_tasks[0].outputs[0])
+
+@conf
+def check_endianness(self):
+ """
+ Executes a configuration test to determine the endianness
+ """
+ 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)
+ return tmp[0]
+
diff --git a/Tools/ccroot.py b/Tools/ccroot.py
new file mode 100644
index 0000000..579d5b2
--- /dev/null
+++ b/Tools/ccroot.py
@@ -0,0 +1,791 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2005-2018 (ita)
+
+"""
+Classes and methods shared by tools providing support for C-like language such
+as C/C++/D/Assembly/Go (this support module is almost never used alone).
+"""
+
+import os, re
+from waflib import Task, Utils, Node, Errors, Logs
+from waflib.TaskGen import after_method, before_method, feature, taskgen_method, extension
+from waflib.Tools import c_aliases, c_preproc, c_config, c_osx, c_tests
+from waflib.Configure import conf
+
+SYSTEM_LIB_PATHS = ['/usr/lib64', '/usr/lib', '/usr/local/lib64', '/usr/local/lib']
+
+USELIB_VARS = Utils.defaultdict(set)
+"""
+Mapping for features to :py:class:`waflib.ConfigSet.ConfigSet` variables. See :py:func:`waflib.Tools.ccroot.propagate_uselib_vars`.
+"""
+
+USELIB_VARS['c'] = set(['INCLUDES', 'FRAMEWORKPATH', 'DEFINES', 'CPPFLAGS', 'CCDEPS', 'CFLAGS', 'ARCH'])
+USELIB_VARS['cxx'] = set(['INCLUDES', 'FRAMEWORKPATH', 'DEFINES', 'CPPFLAGS', 'CXXDEPS', 'CXXFLAGS', 'ARCH'])
+USELIB_VARS['d'] = set(['INCLUDES', 'DFLAGS'])
+USELIB_VARS['includes'] = set(['INCLUDES', 'FRAMEWORKPATH', 'ARCH'])
+
+USELIB_VARS['cprogram'] = USELIB_VARS['cxxprogram'] = set(['LIB', 'STLIB', 'LIBPATH', 'STLIBPATH', 'LINKFLAGS', 'RPATH', 'LINKDEPS', 'FRAMEWORK', 'FRAMEWORKPATH', 'ARCH', 'LDFLAGS'])
+USELIB_VARS['cshlib'] = USELIB_VARS['cxxshlib'] = set(['LIB', 'STLIB', 'LIBPATH', 'STLIBPATH', 'LINKFLAGS', 'RPATH', 'LINKDEPS', 'FRAMEWORK', 'FRAMEWORKPATH', 'ARCH', 'LDFLAGS'])
+USELIB_VARS['cstlib'] = USELIB_VARS['cxxstlib'] = set(['ARFLAGS', 'LINKDEPS'])
+
+USELIB_VARS['dprogram'] = set(['LIB', 'STLIB', 'LIBPATH', 'STLIBPATH', 'LINKFLAGS', 'RPATH', 'LINKDEPS'])
+USELIB_VARS['dshlib'] = set(['LIB', 'STLIB', 'LIBPATH', 'STLIBPATH', 'LINKFLAGS', 'RPATH', 'LINKDEPS'])
+USELIB_VARS['dstlib'] = set(['ARFLAGS', 'LINKDEPS'])
+
+USELIB_VARS['asm'] = set(['ASFLAGS'])
+
+# =================================================================================================
+
+@taskgen_method
+def create_compiled_task(self, name, node):
+ """
+ Create the compilation task: c, cxx, asm, etc. The output node is created automatically (object file with a typical **.o** extension).
+ The task is appended to the list *compiled_tasks* which is then used by :py:func:`waflib.Tools.ccroot.apply_link`
+
+ :param name: name of the task class
+ :type name: string
+ :param node: the file to compile
+ :type node: :py:class:`waflib.Node.Node`
+ :return: The task created
+ :rtype: :py:class:`waflib.Task.Task`
+ """
+ out = '%s.%d.o' % (node.name, self.idx)
+ task = self.create_task(name, node, node.parent.find_or_declare(out))
+ try:
+ self.compiled_tasks.append(task)
+ except AttributeError:
+ self.compiled_tasks = [task]
+ return task
+
+@taskgen_method
+def to_incnodes(self, inlst):
+ """
+ Task generator method provided to convert a list of string/nodes into a list of includes folders.
+
+ The paths are assumed to be relative to the task generator path, except if they begin by **#**
+ in which case they are searched from the top-level directory (``bld.srcnode``).
+ The folders are simply assumed to be existing.
+
+ The node objects in the list are returned in the output list. The strings are converted
+ into node objects if possible. The node is searched from the source directory, and if a match is found,
+ the equivalent build directory is created and added to the returned list too. When a folder cannot be found, it is ignored.
+
+ :param inlst: list of folders
+ :type inlst: space-delimited string or a list of string/nodes
+ :rtype: list of :py:class:`waflib.Node.Node`
+ :return: list of include folders as nodes
+ """
+ lst = []
+ seen = set()
+ for x in self.to_list(inlst):
+ if x in seen or not x:
+ continue
+ seen.add(x)
+
+ # with a real lot of targets, it is sometimes interesting to cache the results below
+ if isinstance(x, Node.Node):
+ lst.append(x)
+ else:
+ if os.path.isabs(x):
+ lst.append(self.bld.root.make_node(x) or x)
+ else:
+ if x[0] == '#':
+ p = self.bld.bldnode.make_node(x[1:])
+ v = self.bld.srcnode.make_node(x[1:])
+ else:
+ p = self.path.get_bld().make_node(x)
+ v = self.path.make_node(x)
+ if p.is_child_of(self.bld.bldnode):
+ p.mkdir()
+ lst.append(p)
+ lst.append(v)
+ return lst
+
+@feature('c', 'cxx', 'd', 'asm', 'fc', 'includes')
+@after_method('propagate_uselib_vars', 'process_source')
+def apply_incpaths(self):
+ """
+ Task generator method that processes the attribute *includes*::
+
+ 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 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``,
+ and the list of include paths in ``tg.env.INCLUDES``.
+ """
+
+ lst = self.to_incnodes(self.to_list(getattr(self, 'includes', [])) + self.env.INCLUDES)
+ self.includes_nodes = lst
+ cwd = self.get_cwd()
+ self.env.INCPATHS = [x.path_from(cwd) for x in lst]
+
+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
+ """
+ color = 'YELLOW'
+
+ weight = 3
+ """Try to process link tasks as early as possible"""
+
+ inst_to = None
+ """Default installation path for the link task outputs, or None to disable"""
+
+ chmod = Utils.O755
+ """Default installation mode for the link task outputs"""
+
+ def add_target(self, target):
+ """
+ Process the *target* attribute to add the platform-specific prefix/suffix such as *.so* or *.exe*.
+ The settings are retrieved from ``env.clsname_PATTERN``
+ """
+ if isinstance(target, str):
+ base = self.generator.path
+ if target.startswith('#'):
+ # for those who like flat structures
+ target = target[1:]
+ base = self.generator.bld.bldnode
+
+ pattern = self.env[self.__class__.__name__ + '_PATTERN']
+ if not pattern:
+ pattern = '%s'
+ folder, name = os.path.split(target)
+
+ if self.__class__.__name__.find('shlib') > 0 and getattr(self.generator, 'vnum', None):
+ nums = self.generator.vnum.split('.')
+ if self.env.DEST_BINFMT == 'pe':
+ # include the version in the dll file name,
+ # the import lib file name stays unversioned.
+ name = name + '-' + nums[0]
+ elif self.env.DEST_OS == 'openbsd':
+ pattern = '%s.%s' % (pattern, nums[0])
+ if len(nums) >= 2:
+ pattern += '.%s' % nums[1]
+
+ if folder:
+ tmp = folder + os.sep + pattern % name
+ else:
+ tmp = pattern % name
+ target = base.find_or_declare(tmp)
+ self.set_outputs(target)
+
+ def exec_command(self, *k, **kw):
+ ret = super(link_task, self).exec_command(*k, **kw)
+ if not ret and self.env.DO_MANIFEST:
+ ret = self.exec_mf()
+ return ret
+
+ def exec_mf(self):
+ """
+ Create manifest files for VS-like compilers (msvc, ifort, ...)
+ """
+ if not self.env.MT:
+ return 0
+
+ manifest = None
+ for out_node in self.outputs:
+ if out_node.name.endswith('.manifest'):
+ manifest = out_node.abspath()
+ break
+ else:
+ # Should never get here. If we do, it means the manifest file was
+ # never added to the outputs list, thus we don't have a manifest file
+ # to embed, so we just return.
+ return 0
+
+ # embedding mode. Different for EXE's and DLL's.
+ # see: http://msdn2.microsoft.com/en-us/library/ms235591(VS.80).aspx
+ mode = ''
+ for x in Utils.to_list(self.generator.features):
+ if x in ('cprogram', 'cxxprogram', 'fcprogram', 'fcprogram_test'):
+ mode = 1
+ elif x in ('cshlib', 'cxxshlib', 'fcshlib'):
+ mode = 2
+
+ Logs.debug('msvc: embedding manifest in mode %r', mode)
+
+ lst = [] + self.env.MT
+ lst.extend(Utils.to_list(self.env.MTFLAGS))
+ lst.extend(['-manifest', manifest])
+ lst.append('-outputresource:%s;%s' % (self.outputs[0].abspath(), mode))
+
+ return super(link_task, self).exec_command(lst)
+
+class stlink_task(link_task):
+ """
+ Base for static link tasks, which use *ar* most of the time.
+ The target is always removed before being written.
+ """
+ run_str = '${AR} ${ARFLAGS} ${AR_TGT_F}${TGT} ${AR_SRC_F}${SRC}'
+
+ chmod = Utils.O644
+ """Default installation mode for the static libraries"""
+
+def rm_tgt(cls):
+ old = cls.run
+ def wrap(self):
+ try:
+ os.remove(self.outputs[0].abspath())
+ except OSError:
+ pass
+ return old(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):
+ """
+ Collect the tasks stored in ``compiled_tasks`` (created by :py:func:`waflib.Tools.ccroot.create_compiled_task`), and
+ use the outputs for a new instance of :py:class:`waflib.Tools.ccroot.link_task`. The class to use is the first link task
+ matching a name from the attribute *features*, for example::
+
+ def build(bld):
+ tg = bld(features='cxx cxxprogram cprogram', source='main.c', target='app')
+
+ will create the task ``tg.link_task`` as a new instance of :py:class:`waflib.Tools.cxx.cxxprogram`
+ """
+
+ for x in self.features:
+ if x == 'cprogram' and 'cxx' in self.features: # limited compat
+ x = 'cxxprogram'
+ elif x == 'cshlib' and 'cxx' in self.features:
+ x = 'cxxshlib'
+
+ if x in Task.classes:
+ if issubclass(Task.classes[x], link_task):
+ link = x
+ break
+ else:
+ return
+
+ objs = [t.outputs[0] for t in getattr(self, 'compiled_tasks', [])]
+ self.link_task = self.create_task(link, objs)
+ self.link_task.add_target(self.target)
+
+ # remember that the install paths are given by the task generators
+ try:
+ inst_to = self.install_path
+ except AttributeError:
+ inst_to = self.link_task.inst_to
+ if inst_to:
+ # install a copy of the node list we have at this moment (implib not added)
+ self.install_task = self.add_install_files(
+ install_to=inst_to, install_from=self.link_task.outputs[:],
+ chmod=self.link_task.chmod, task=self.link_task)
+
+@taskgen_method
+def use_rec(self, name, **kw):
+ """
+ Processes the ``use`` keyword recursively. This method is kind of private and only meant to be used from ``process_use``
+ """
+
+ if name in self.tmp_use_not or name in self.tmp_use_seen:
+ return
+
+ try:
+ y = self.bld.get_tgen_by_name(name)
+ except Errors.WafError:
+ self.uselib.append(name)
+ self.tmp_use_not.add(name)
+ return
+
+ self.tmp_use_seen.append(name)
+ y.post()
+
+ # bind temporary attributes on the task generator
+ y.tmp_use_objects = objects = kw.get('objects', True)
+ y.tmp_use_stlib = stlib = kw.get('stlib', True)
+ try:
+ link_task = y.link_task
+ except AttributeError:
+ y.tmp_use_var = ''
+ else:
+ objects = False
+ if not isinstance(link_task, stlink_task):
+ stlib = False
+ y.tmp_use_var = 'LIB'
+ else:
+ y.tmp_use_var = 'STLIB'
+
+ p = self.tmp_use_prec
+ for x in self.to_list(getattr(y, 'use', [])):
+ if self.env["STLIB_" + x]:
+ continue
+ try:
+ p[x].append(name)
+ except KeyError:
+ p[x] = [name]
+ self.use_rec(x, objects=objects, stlib=stlib)
+
+@feature('c', 'cxx', 'd', 'use', 'fc')
+@before_method('apply_incpaths', 'propagate_uselib_vars')
+@after_method('apply_link', 'process_source')
+def process_use(self):
+ """
+ Process the ``use`` attribute which contains a list of task generator names::
+
+ def build(bld):
+ bld.shlib(source='a.c', target='lib1')
+ bld.program(source='main.c', target='app', use='lib1')
+
+ See :py:func:`waflib.Tools.ccroot.use_rec`.
+ """
+
+ use_not = self.tmp_use_not = set()
+ self.tmp_use_seen = [] # we would like an ordered set
+ use_prec = self.tmp_use_prec = {}
+ self.uselib = self.to_list(getattr(self, 'uselib', []))
+ self.includes = self.to_list(getattr(self, 'includes', []))
+ names = self.to_list(getattr(self, 'use', []))
+
+ for x in names:
+ self.use_rec(x)
+
+ for x in use_not:
+ if x in use_prec:
+ del use_prec[x]
+
+ # topological sort
+ out = self.tmp_use_sorted = []
+ tmp = []
+ for x in self.tmp_use_seen:
+ for k in use_prec.values():
+ if x in k:
+ break
+ else:
+ tmp.append(x)
+
+ while tmp:
+ e = tmp.pop()
+ out.append(e)
+ try:
+ nlst = use_prec[e]
+ except KeyError:
+ pass
+ else:
+ del use_prec[e]
+ for x in nlst:
+ for y in use_prec:
+ if x in use_prec[y]:
+ break
+ else:
+ tmp.append(x)
+ if use_prec:
+ raise Errors.WafError('Cycle detected in the use processing %r' % use_prec)
+ out.reverse()
+
+ link_task = getattr(self, 'link_task', None)
+ for x in out:
+ y = self.bld.get_tgen_by_name(x)
+ var = y.tmp_use_var
+ if var and link_task:
+ 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())
+ self.env.append_unique(var + 'PATH', [tmp_path])
+ else:
+ if y.tmp_use_objects:
+ self.add_objects_from_tgen(y)
+
+ if getattr(y, 'export_includes', None):
+ # self.includes may come from a global variable #2035
+ self.includes = self.includes + y.to_incnodes(y.export_includes)
+
+ if getattr(y, 'export_defines', None):
+ self.env.append_value('DEFINES', self.to_list(y.export_defines))
+
+
+ # and finally, add the use variables (no recursion needed)
+ for x in names:
+ try:
+ y = self.bld.get_tgen_by_name(x)
+ except Errors.WafError:
+ if not self.env['STLIB_' + x] and not x in self.uselib:
+ self.uselib.append(x)
+ else:
+ for k in self.to_list(getattr(y, 'use', [])):
+ if not self.env['STLIB_' + k] and not k in self.uselib:
+ self.uselib.append(k)
+
+@taskgen_method
+def accept_node_to_link(self, node):
+ """
+ PRIVATE INTERNAL USE ONLY
+ """
+ return not node.name.endswith('.pdb')
+
+@taskgen_method
+def add_objects_from_tgen(self, tg):
+ """
+ Add the objects from the depending compiled tasks as link task inputs.
+
+ Some objects are filtered: for instance, .pdb files are added
+ to the compiled tasks but not to the link tasks (to avoid errors)
+ PRIVATE INTERNAL USE ONLY
+ """
+ try:
+ link_task = self.link_task
+ except AttributeError:
+ pass
+ else:
+ for tsk in getattr(tg, 'compiled_tasks', []):
+ for x in tsk.outputs:
+ if self.accept_node_to_link(x):
+ link_task.inputs.append(x)
+
+@taskgen_method
+def get_uselib_vars(self):
+ """
+ :return: the *uselib* variables associated to the *features* attribute (see :py:attr:`waflib.Tools.ccroot.USELIB_VARS`)
+ :rtype: list of string
+ """
+ _vars = set()
+ for x in self.features:
+ if x in USELIB_VARS:
+ _vars |= USELIB_VARS[x]
+ return _vars
+
+@feature('c', 'cxx', 'd', 'fc', 'javac', 'cs', 'uselib', 'asm')
+@after_method('process_use')
+def propagate_uselib_vars(self):
+ """
+ Process uselib variables for adding flags. For example, the following target::
+
+ def build(bld):
+ bld.env.AFLAGS_aaa = ['bar']
+ from waflib.Tools.ccroot import USELIB_VARS
+ USELIB_VARS['aaa'] = ['AFLAGS']
+
+ tg = bld(features='aaa', aflags='test')
+
+ The *aflags* attribute will be processed and this method will set::
+
+ tg.env.AFLAGS = ['bar', 'test']
+ """
+ _vars = self.get_uselib_vars()
+ env = self.env
+ app = env.append_value
+ feature_uselib = self.features + self.to_list(getattr(self, 'uselib', []))
+ for var in _vars:
+ y = var.lower()
+ val = getattr(self, y, [])
+ if val:
+ app(var, self.to_list(val))
+
+ for x in feature_uselib:
+ val = env['%s_%s' % (var, x)]
+ if val:
+ app(var, val)
+
+# ============ the code above must not know anything about import libs ==========
+
+@feature('cshlib', 'cxxshlib', 'fcshlib')
+@after_method('apply_link')
+def apply_implib(self):
+ """
+ Handle dlls and their import libs on Windows-like systems.
+
+ A ``.dll.a`` file called *import library* is generated.
+ It must be installed as it is required for linking the library.
+ """
+ if not self.env.DEST_BINFMT == 'pe':
+ return
+
+ dll = self.link_task.outputs[0]
+ if isinstance(self.target, Node.Node):
+ name = self.target.name
+ else:
+ name = os.path.split(self.target)[1]
+ implib = self.env.implib_PATTERN % name
+ implib = dll.parent.find_or_declare(implib)
+ self.env.append_value('LINKFLAGS', self.env.IMPLIB_ST % implib.bldpath())
+ self.link_task.outputs.append(implib)
+
+ if getattr(self, 'defs', None) and self.env.DEST_BINFMT == 'pe':
+ node = self.path.find_resource(self.defs)
+ if not node:
+ raise Errors.WafError('invalid def file %r' % self.defs)
+ if self.env.def_PATTERN:
+ self.env.append_value('LINKFLAGS', self.env.def_PATTERN % node.path_from(self.get_cwd()))
+ self.link_task.dep_nodes.append(node)
+ else:
+ # gcc for windows takes *.def file as input without any special flag
+ self.link_task.inputs.append(node)
+
+ # where to put the import library
+ if getattr(self, 'install_task', None):
+ try:
+ # user has given a specific installation path for the import library
+ inst_to = self.install_path_implib
+ except AttributeError:
+ try:
+ # user has given an installation path for the main library, put the import library in it
+ inst_to = self.install_path
+ except AttributeError:
+ # else, put the library in BINDIR and the import library in LIBDIR
+ inst_to = '${IMPLIBDIR}'
+ self.install_task.install_to = '${BINDIR}'
+ if not self.env.IMPLIBDIR:
+ self.env.IMPLIBDIR = self.env.LIBDIR
+ self.implib_install_task = self.add_install_files(install_to=inst_to, install_from=implib,
+ chmod=self.link_task.chmod, task=self.link_task)
+
+# ============ the code above must not know anything about vnum processing on unix platforms =========
+
+re_vnum = re.compile('^([1-9]\\d*|0)([.]([1-9]\\d*|0)){0,2}?$')
+@feature('cshlib', 'cxxshlib', 'dshlib', 'fcshlib', 'vnum')
+@after_method('apply_link', 'propagate_uselib_vars')
+def apply_vnum(self):
+ """
+ Enforce version numbering on shared libraries. The valid version numbers must have either zero or two dots::
+
+ def build(bld):
+ bld.shlib(source='a.c', target='foo', vnum='14.15.16')
+
+ In this example on Linux platform, ``libfoo.so`` is installed as ``libfoo.so.14.15.16``, and the following symbolic links are created:
+
+ * ``libfoo.so → libfoo.so.14.15.16``
+ * ``libfoo.so.14 → libfoo.so.14.15.16``
+
+ By default, the library will be assigned SONAME ``libfoo.so.14``, effectively declaring ABI compatibility between all minor and patch releases for the major version of the library. When necessary, the compatibility can be explicitly defined using `cnum` parameter:
+
+ def build(bld):
+ bld.shlib(source='a.c', target='foo', vnum='14.15.16', cnum='14.15')
+
+ In this case, the assigned SONAME will be ``libfoo.so.14.15`` with ABI compatibility only between path releases for a specific major and minor version of the library.
+
+ On OS X platform, install-name parameter will follow the above logic for SONAME with exception that it also specifies an absolute path (based on install_path) of the library.
+ """
+ if not getattr(self, 'vnum', '') or os.name != 'posix' or self.env.DEST_BINFMT not in ('elf', 'mac-o'):
+ return
+
+ link = self.link_task
+ if not re_vnum.match(self.vnum):
+ raise Errors.WafError('Invalid vnum %r for target %r' % (self.vnum, getattr(self, 'name', self)))
+ nums = self.vnum.split('.')
+ node = link.outputs[0]
+
+ 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)
+ name2 = libname.replace('.dylib', '.%s.dylib' % cnum)
+ else:
+ name3 = libname + '.' + self.vnum
+ name2 = libname + '.' + cnum
+
+ # add the so name for the ld linker - to disable, just unset env.SONAME_ST
+ if self.env.SONAME_ST:
+ v = self.env.SONAME_ST % name2
+ self.env.append_value('LINKFLAGS', v.split())
+
+ # the following task is just to enable execution from the build dir :-/
+ if self.env.DEST_OS != 'openbsd':
+ outs = [node.parent.make_node(name3)]
+ if name2 != name3:
+ outs.append(node.parent.make_node(name2))
+ self.create_task('vnum', node, outs)
+
+ if getattr(self, 'install_task', None):
+ self.install_task.hasrun = Task.SKIPPED
+ self.install_task.no_errcheck_out = True
+ path = self.install_task.install_to
+ if self.env.DEST_OS == 'openbsd':
+ libname = self.link_task.outputs[0].name
+ t1 = self.add_install_as(install_to='%s/%s' % (path, libname), install_from=node, chmod=self.link_task.chmod)
+ self.vnum_install_task = (t1,)
+ else:
+ t1 = self.add_install_as(install_to=path + os.sep + name3, install_from=node, chmod=self.link_task.chmod)
+ t3 = self.add_symlink_as(install_to=path + os.sep + libname, install_from=name3)
+ if name2 != name3:
+ t2 = self.add_symlink_as(install_to=path + os.sep + name2, install_from=name3)
+ self.vnum_install_task = (t1, t2, t3)
+ else:
+ self.vnum_install_task = (t1, t3)
+
+ if '-dynamiclib' in self.env.LINKFLAGS:
+ # this requires after(propagate_uselib_vars)
+ try:
+ inst_to = self.install_path
+ except AttributeError:
+ inst_to = self.link_task.inst_to
+ if inst_to:
+ p = Utils.subst_vars(inst_to, self.env)
+ path = os.path.join(p, name2)
+ self.env.append_value('LINKFLAGS', ['-install_name', path])
+ self.env.append_value('LINKFLAGS', '-Wl,-compatibility_version,%s' % cnum)
+ self.env.append_value('LINKFLAGS', '-Wl,-current_version,%s' % self.vnum)
+
+class vnum(Task.Task):
+ """
+ Create the symbolic links for a versioned shared library. Instances are created by :py:func:`waflib.Tools.ccroot.apply_vnum`
+ """
+ color = 'CYAN'
+ ext_in = ['.bin']
+ def keyword(self):
+ return 'Symlinking'
+ def run(self):
+ for x in self.outputs:
+ path = x.abspath()
+ try:
+ os.remove(path)
+ except OSError:
+ pass
+
+ try:
+ os.symlink(self.inputs[0].name, path)
+ except OSError:
+ return 1
+
+class fake_shlib(link_task):
+ """
+ Task used for reading a system library and adding the dependency on it
+ """
+ def runnable_status(self):
+ for t in self.run_after:
+ if not t.hasrun:
+ return Task.ASK_LATER
+ return Task.SKIP_ME
+
+class fake_stlib(stlink_task):
+ """
+ Task used for reading a system library and adding the dependency on it
+ """
+ def runnable_status(self):
+ for t in self.run_after:
+ if not t.hasrun:
+ return Task.ASK_LATER
+ return Task.SKIP_ME
+
+@conf
+def read_shlib(self, name, paths=[], export_includes=[], export_defines=[]):
+ """
+ Read a system shared library, enabling its use as a local library. Will trigger a rebuild if the file changes::
+
+ def build(bld):
+ bld.read_shlib('m')
+ bld.program(source='main.c', use='m')
+ """
+ return self(name=name, features='fake_lib', lib_paths=paths, lib_type='shlib', export_includes=export_includes, export_defines=export_defines)
+
+@conf
+def read_stlib(self, name, paths=[], export_includes=[], export_defines=[]):
+ """
+ Read a system static library, enabling a use as a local library. Will trigger a rebuild if the file changes.
+ """
+ return self(name=name, features='fake_lib', lib_paths=paths, lib_type='stlib', export_includes=export_includes, export_defines=export_defines)
+
+lib_patterns = {
+ 'shlib' : ['lib%s.so', '%s.so', 'lib%s.dylib', 'lib%s.dll', '%s.dll'],
+ 'stlib' : ['lib%s.a', '%s.a', 'lib%s.dll', '%s.dll', 'lib%s.lib', '%s.lib'],
+}
+
+@feature('fake_lib')
+def process_lib(self):
+ """
+ Find the location of a foreign library. Used by :py:class:`waflib.Tools.ccroot.read_shlib` and :py:class:`waflib.Tools.ccroot.read_stlib`.
+ """
+ node = None
+
+ names = [x % self.name for x in lib_patterns[self.lib_type]]
+ for x in self.lib_paths + [self.path] + SYSTEM_LIB_PATHS:
+ if not isinstance(x, Node.Node):
+ x = self.bld.root.find_node(x) or self.path.find_node(x)
+ if not x:
+ continue
+
+ for y in names:
+ node = x.find_node(y)
+ if node:
+ try:
+ Utils.h_file(node.abspath())
+ except EnvironmentError:
+ raise ValueError('Could not read %r' % y)
+ break
+ else:
+ continue
+ break
+ else:
+ raise Errors.WafError('could not find library %r' % self.name)
+ self.link_task = self.create_task('fake_%s' % self.lib_type, [], [node])
+ self.target = self.name
+
+
+class fake_o(Task.Task):
+ def runnable_status(self):
+ return Task.SKIP_ME
+
+@extension('.o', '.obj')
+def add_those_o_files(self, node):
+ tsk = self.create_task('fake_o', [], node)
+ try:
+ self.compiled_tasks.append(tsk)
+ except AttributeError:
+ self.compiled_tasks = [tsk]
+
+@feature('fake_obj')
+@before_method('process_source')
+def process_objs(self):
+ """
+ Puts object files in the task generator outputs
+ """
+ for node in self.to_nodes(self.source):
+ self.add_those_o_files(node)
+ self.source = []
+
+@conf
+def read_object(self, obj):
+ """
+ Read an object file, enabling injection in libs/programs. Will trigger a rebuild if the file changes.
+
+ :param obj: object file path, as string or Node
+ """
+ if not isinstance(obj, self.path.__class__):
+ obj = self.path.find_resource(obj)
+ return self(features='fake_obj', source=obj, name=obj.name)
+
+@feature('cxxprogram', 'cprogram')
+@after_method('apply_link', 'process_use')
+def set_full_paths_hpux(self):
+ """
+ On hp-ux, extend the libpaths and static library paths to absolute paths
+ """
+ if self.env.DEST_OS != 'hp-ux':
+ return
+ base = self.bld.bldnode.abspath()
+ for var in ['LIBPATH', 'STLIBPATH']:
+ lst = []
+ for x in self.env[var]:
+ if x.startswith('/'):
+ lst.append(x)
+ else:
+ lst.append(os.path.normpath(os.path.join(base, x)))
+ self.env[var] = lst
+
diff --git a/Tools/clang.py b/Tools/clang.py
new file mode 100644
index 0000000..3828e39
--- /dev/null
+++ b/Tools/clang.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Krzysztof Kosiński 2014
+
+"""
+Detect the Clang C compiler
+"""
+
+from waflib.Tools import ccroot, ar, gcc
+from waflib.Configure import conf
+
+@conf
+def find_clang(conf):
+ """
+ Finds the program clang and executes it to ensure it really is clang
+ """
+ cc = conf.find_program('clang', var='CC')
+ conf.get_cc_version(cc, clang=True)
+ conf.env.CC_NAME = 'clang'
+
+def configure(conf):
+ conf.find_clang()
+ conf.find_program(['llvm-ar', 'ar'], var='AR')
+ conf.find_ar()
+ conf.gcc_common_flags()
+ conf.gcc_modifier_platform()
+ conf.cc_load_tools()
+ conf.cc_add_flags()
+ conf.link_add_flags()
diff --git a/Tools/clangxx.py b/Tools/clangxx.py
new file mode 100644
index 0000000..152013c
--- /dev/null
+++ b/Tools/clangxx.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy 2009-2018 (ita)
+
+"""
+Detect the Clang++ C++ compiler
+"""
+
+from waflib.Tools import ccroot, ar, gxx
+from waflib.Configure import conf
+
+@conf
+def find_clangxx(conf):
+ """
+ Finds the program clang++, and executes it to ensure it really is clang++
+ """
+ cxx = conf.find_program('clang++', var='CXX')
+ conf.get_cc_version(cxx, clang=True)
+ conf.env.CXX_NAME = 'clang'
+
+def configure(conf):
+ conf.find_clangxx()
+ conf.find_program(['llvm-ar', 'ar'], var='AR')
+ conf.find_ar()
+ conf.gxx_common_flags()
+ conf.gxx_modifier_platform()
+ conf.cxx_load_tools()
+ conf.cxx_add_flags()
+ conf.link_add_flags()
+
diff --git a/Tools/compiler_c.py b/Tools/compiler_c.py
new file mode 100644
index 0000000..2dba3f8
--- /dev/null
+++ b/Tools/compiler_c.py
@@ -0,0 +1,110 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Matthias Jahn jahn dôt matthias ât freenet dôt de, 2007 (pmarat)
+
+"""
+Try to detect a C compiler from the list of supported compilers (gcc, msvc, etc)::
+
+ def options(opt):
+ opt.load('compiler_c')
+ def configure(cnf):
+ cnf.load('compiler_c')
+ def build(bld):
+ bld.program(source='main.c', target='app')
+
+The compilers are associated to platforms in :py:attr:`waflib.Tools.compiler_c.c_compiler`. To register
+a new C compiler named *cfoo* (assuming the tool ``waflib/extras/cfoo.py`` exists), use::
+
+ from waflib.Tools.compiler_c import c_compiler
+ c_compiler['win32'] = ['cfoo', 'msvc', 'gcc']
+
+ def options(opt):
+ opt.load('compiler_c')
+ def configure(cnf):
+ cnf.load('compiler_c')
+ def build(bld):
+ bld.program(source='main.c', target='app')
+
+Not all compilers need to have a specific tool. For example, the clang compilers can be detected by the gcc tools when using::
+
+ $ CC=clang waf configure
+"""
+
+import re
+from waflib.Tools import ccroot
+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'],
+}
+"""
+Dict mapping platform names to Waf tools finding specific C compilers::
+
+ from waflib.Tools.compiler_c import c_compiler
+ c_compiler['linux'] = ['gcc', 'icc', 'suncc']
+"""
+
+def default_compilers():
+ build_platform = Utils.unversioned_sys_platform()
+ possible_compiler_list = c_compiler.get(build_platform, c_compiler['default'])
+ return ' '.join(possible_compiler_list)
+
+def configure(conf):
+ """
+ Detects a suitable C compiler
+
+ :raises: :py:class:`waflib.Errors.ConfigurationError` when no suitable compiler is found
+ """
+ try:
+ test_for_compiler = conf.options.check_c_compiler or default_compilers()
+ except AttributeError:
+ conf.fatal("Add options(opt): opt.load('compiler_c')")
+
+ for compiler in re.split('[ ,]+', test_for_compiler):
+ conf.env.stash()
+ conf.start_msg('Checking for %r (C compiler)' % compiler)
+ try:
+ conf.load(compiler)
+ except conf.errors.ConfigurationError as e:
+ conf.env.revert()
+ conf.end_msg(False)
+ debug('compiler_c: %r', e)
+ else:
+ if conf.env.CC:
+ conf.end_msg(conf.env.get_flat('CC'))
+ conf.env.COMPILER_CC = compiler
+ conf.env.commit()
+ break
+ conf.env.revert()
+ conf.end_msg(False)
+ else:
+ conf.fatal('could not configure a C compiler!')
+
+def options(opt):
+ """
+ This is how to provide compiler preferences on the command-line::
+
+ $ waf configure --check-c-compiler=gcc
+ """
+ test_for_compiler = default_compilers()
+ opt.load_special_tools('c_*.py', ban=['c_dumbpreproc.py'])
+ cc_compiler_opts = opt.add_option_group('Configuration options')
+ cc_compiler_opts.add_option('--check-c-compiler', default=None,
+ help='list of C compilers to try [%s]' % test_for_compiler,
+ dest="check_c_compiler")
+
+ for x in test_for_compiler.split():
+ opt.load('%s' % x)
+
diff --git a/Tools/compiler_cxx.py b/Tools/compiler_cxx.py
new file mode 100644
index 0000000..1af65a2
--- /dev/null
+++ b/Tools/compiler_cxx.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Matthias Jahn jahn dôt matthias ât freenet dôt de 2007 (pmarat)
+
+"""
+Try to detect a C++ compiler from the list of supported compilers (g++, msvc, etc)::
+
+ def options(opt):
+ opt.load('compiler_cxx')
+ def configure(cnf):
+ cnf.load('compiler_cxx')
+ def build(bld):
+ bld.program(source='main.cpp', target='app')
+
+The compilers are associated to platforms in :py:attr:`waflib.Tools.compiler_cxx.cxx_compiler`. To register
+a new C++ compiler named *cfoo* (assuming the tool ``waflib/extras/cfoo.py`` exists), use::
+
+ from waflib.Tools.compiler_cxx import cxx_compiler
+ cxx_compiler['win32'] = ['cfoo', 'msvc', 'gcc']
+
+ def options(opt):
+ opt.load('compiler_cxx')
+ def configure(cnf):
+ cnf.load('compiler_cxx')
+ def build(bld):
+ bld.program(source='main.c', target='app')
+
+Not all compilers need to have a specific tool. For example, the clang compilers can be detected by the gcc tools when using::
+
+ $ CXX=clang waf configure
+"""
+
+
+import re
+from waflib.Tools import ccroot
+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++']
+}
+"""
+Dict mapping the platform names to Waf tools finding specific C++ compilers::
+
+ from waflib.Tools.compiler_cxx import cxx_compiler
+ cxx_compiler['linux'] = ['gxx', 'icpc', 'suncxx']
+"""
+
+def default_compilers():
+ build_platform = Utils.unversioned_sys_platform()
+ possible_compiler_list = cxx_compiler.get(build_platform, cxx_compiler['default'])
+ return ' '.join(possible_compiler_list)
+
+def configure(conf):
+ """
+ Detects a suitable C++ compiler
+
+ :raises: :py:class:`waflib.Errors.ConfigurationError` when no suitable compiler is found
+ """
+ try:
+ test_for_compiler = conf.options.check_cxx_compiler or default_compilers()
+ except AttributeError:
+ conf.fatal("Add options(opt): opt.load('compiler_cxx')")
+
+ for compiler in re.split('[ ,]+', test_for_compiler):
+ conf.env.stash()
+ conf.start_msg('Checking for %r (C++ compiler)' % compiler)
+ try:
+ conf.load(compiler)
+ except conf.errors.ConfigurationError as e:
+ conf.env.revert()
+ conf.end_msg(False)
+ debug('compiler_cxx: %r', e)
+ else:
+ if conf.env.CXX:
+ conf.end_msg(conf.env.get_flat('CXX'))
+ conf.env.COMPILER_CXX = compiler
+ conf.env.commit()
+ break
+ conf.env.revert()
+ conf.end_msg(False)
+ else:
+ conf.fatal('could not configure a C++ compiler!')
+
+def options(opt):
+ """
+ This is how to provide compiler preferences on the command-line::
+
+ $ waf configure --check-cxx-compiler=gxx
+ """
+ test_for_compiler = default_compilers()
+ opt.load_special_tools('cxx_*.py')
+ cxx_compiler_opts = opt.add_option_group('Configuration options')
+ cxx_compiler_opts.add_option('--check-cxx-compiler', default=None,
+ help='list of C++ compilers to try [%s]' % test_for_compiler,
+ dest="check_cxx_compiler")
+
+ for x in test_for_compiler.split():
+ opt.load('%s' % x)
+
diff --git a/Tools/compiler_d.py b/Tools/compiler_d.py
new file mode 100644
index 0000000..43bb1f6
--- /dev/null
+++ b/Tools/compiler_d.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Carlos Rafael Giani, 2007 (dv)
+# Thomas Nagy, 2016-2018 (ita)
+
+"""
+Try to detect a D compiler from the list of supported compilers::
+
+ def options(opt):
+ opt.load('compiler_d')
+ def configure(cnf):
+ cnf.load('compiler_d')
+ def build(bld):
+ bld.program(source='main.d', target='app')
+
+Only three D compilers are really present at the moment:
+
+* gdc
+* dmd, the ldc compiler having a very similar command-line interface
+* ldc2
+"""
+
+import re
+from waflib import Utils, Logs
+
+d_compiler = {
+'default' : ['gdc', 'dmd', 'ldc2']
+}
+"""
+Dict mapping the platform names to lists of names of D compilers to try, in order of preference::
+
+ from waflib.Tools.compiler_d import d_compiler
+ d_compiler['default'] = ['gdc', 'dmd', 'ldc2']
+"""
+
+def default_compilers():
+ build_platform = Utils.unversioned_sys_platform()
+ possible_compiler_list = d_compiler.get(build_platform, d_compiler['default'])
+ return ' '.join(possible_compiler_list)
+
+def configure(conf):
+ """
+ Detects a suitable D compiler
+
+ :raises: :py:class:`waflib.Errors.ConfigurationError` when no suitable compiler is found
+ """
+ try:
+ test_for_compiler = conf.options.check_d_compiler or default_compilers()
+ except AttributeError:
+ conf.fatal("Add options(opt): opt.load('compiler_d')")
+
+ for compiler in re.split('[ ,]+', test_for_compiler):
+ conf.env.stash()
+ conf.start_msg('Checking for %r (D compiler)' % compiler)
+ try:
+ conf.load(compiler)
+ except conf.errors.ConfigurationError as e:
+ conf.env.revert()
+ conf.end_msg(False)
+ Logs.debug('compiler_d: %r', e)
+ else:
+ if conf.env.D:
+ conf.end_msg(conf.env.get_flat('D'))
+ conf.env.COMPILER_D = compiler
+ conf.env.commit()
+ break
+ conf.env.revert()
+ conf.end_msg(False)
+ else:
+ conf.fatal('could not configure a D compiler!')
+
+def options(opt):
+ """
+ This is how to provide compiler preferences on the command-line::
+
+ $ waf configure --check-d-compiler=dmd
+ """
+ test_for_compiler = default_compilers()
+ d_compiler_opts = opt.add_option_group('Configuration options')
+ d_compiler_opts.add_option('--check-d-compiler', default=None,
+ help='list of D compilers to try [%s]' % test_for_compiler, dest='check_d_compiler')
+
+ for x in test_for_compiler.split():
+ opt.load('%s' % x)
+
diff --git a/Tools/compiler_fc.py b/Tools/compiler_fc.py
new file mode 100644
index 0000000..96b58e7
--- /dev/null
+++ b/Tools/compiler_fc.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+# encoding: utf-8
+
+import re
+from waflib import Utils, Logs
+from waflib.Tools import fc
+
+fc_compiler = {
+ 'win32' : ['gfortran','ifort'],
+ 'darwin' : ['gfortran', 'g95', 'ifort'],
+ 'linux' : ['gfortran', 'g95', 'ifort'],
+ 'java' : ['gfortran', 'g95', 'ifort'],
+ 'default': ['gfortran'],
+ 'aix' : ['gfortran']
+}
+"""
+Dict mapping the platform names to lists of names of Fortran compilers to try, in order of preference::
+
+ from waflib.Tools.compiler_c import c_compiler
+ c_compiler['linux'] = ['gfortran', 'g95', 'ifort']
+"""
+
+def default_compilers():
+ build_platform = Utils.unversioned_sys_platform()
+ possible_compiler_list = fc_compiler.get(build_platform, fc_compiler['default'])
+ return ' '.join(possible_compiler_list)
+
+def configure(conf):
+ """
+ Detects a suitable Fortran compiler
+
+ :raises: :py:class:`waflib.Errors.ConfigurationError` when no suitable compiler is found
+ """
+ try:
+ test_for_compiler = conf.options.check_fortran_compiler or default_compilers()
+ except AttributeError:
+ conf.fatal("Add options(opt): opt.load('compiler_fc')")
+ for compiler in re.split('[ ,]+', test_for_compiler):
+ conf.env.stash()
+ conf.start_msg('Checking for %r (Fortran compiler)' % compiler)
+ try:
+ conf.load(compiler)
+ except conf.errors.ConfigurationError as e:
+ conf.env.revert()
+ conf.end_msg(False)
+ Logs.debug('compiler_fortran: %r', e)
+ else:
+ if conf.env.FC:
+ conf.end_msg(conf.env.get_flat('FC'))
+ conf.env.COMPILER_FORTRAN = compiler
+ conf.env.commit()
+ break
+ conf.env.revert()
+ conf.end_msg(False)
+ else:
+ conf.fatal('could not configure a Fortran compiler!')
+
+def options(opt):
+ """
+ This is how to provide compiler preferences on the command-line::
+
+ $ waf configure --check-fortran-compiler=ifort
+ """
+ test_for_compiler = default_compilers()
+ opt.load_special_tools('fc_*.py')
+ fortran_compiler_opts = opt.add_option_group('Configuration options')
+ fortran_compiler_opts.add_option('--check-fortran-compiler', default=None,
+ help='list of Fortran compiler to try [%s]' % test_for_compiler,
+ dest="check_fortran_compiler")
+
+ for x in test_for_compiler.split():
+ opt.load('%s' % x)
+
diff --git a/Tools/cs.py b/Tools/cs.py
new file mode 100644
index 0000000..aecca6d
--- /dev/null
+++ b/Tools/cs.py
@@ -0,0 +1,211 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2006-2018 (ita)
+
+"""
+C# support. A simple example::
+
+ def configure(conf):
+ conf.load('cs')
+ def build(bld):
+ bld(features='cs', source='main.cs', gen='foo')
+
+Note that the configuration may compile C# snippets::
+
+ FRAG = '''
+ namespace Moo {
+ public class Test { public static int Main(string[] args) { return 0; } }
+ }'''
+ def configure(conf):
+ conf.check(features='cs', fragment=FRAG, compile_filename='test.cs', gen='test.exe',
+ bintype='exe', csflags=['-pkg:gtk-sharp-2.0'], msg='Checking for Gtksharp support')
+"""
+
+from waflib import Utils, Task, Options, Errors
+from waflib.TaskGen import before_method, after_method, feature
+from waflib.Tools import ccroot
+from waflib.Configure import conf
+
+ccroot.USELIB_VARS['cs'] = set(['CSFLAGS', 'ASSEMBLIES', 'RESOURCES'])
+ccroot.lib_patterns['csshlib'] = ['%s']
+
+@feature('cs')
+@before_method('process_source')
+def apply_cs(self):
+ """
+ Create a C# task bound to the attribute *cs_task*. There can be only one C# task by task generator.
+ """
+ cs_nodes = []
+ no_nodes = []
+ for x in self.to_nodes(self.source):
+ if x.name.endswith('.cs'):
+ cs_nodes.append(x)
+ else:
+ no_nodes.append(x)
+ self.source = no_nodes
+
+ bintype = getattr(self, 'bintype', self.gen.endswith('.dll') and 'library' or 'exe')
+ self.cs_task = tsk = self.create_task('mcs', cs_nodes, self.path.find_or_declare(self.gen))
+ tsk.env.CSTYPE = '/target:%s' % bintype
+ tsk.env.OUT = '/out:%s' % tsk.outputs[0].abspath()
+ self.env.append_value('CSFLAGS', '/platform:%s' % getattr(self, 'platform', 'anycpu'))
+
+ inst_to = getattr(self, 'install_path', bintype=='exe' and '${BINDIR}' or '${LIBDIR}')
+ if inst_to:
+ # note: we are making a copy, so the files added to cs_task.outputs won't be installed automatically
+ mod = getattr(self, 'chmod', bintype=='exe' and Utils.O755 or Utils.O644)
+ self.install_task = self.add_install_files(install_to=inst_to, install_from=self.cs_task.outputs[:], chmod=mod)
+
+@feature('cs')
+@after_method('apply_cs')
+def use_cs(self):
+ """
+ C# applications honor the **use** keyword::
+
+ def build(bld):
+ bld(features='cs', source='My.cs', bintype='library', gen='my.dll', name='mylib')
+ bld(features='cs', source='Hi.cs', includes='.', bintype='exe', gen='hi.exe', use='mylib', name='hi')
+ """
+ names = self.to_list(getattr(self, 'use', []))
+ get = self.bld.get_tgen_by_name
+ for x in names:
+ try:
+ y = get(x)
+ except Errors.WafError:
+ self.env.append_value('CSFLAGS', '/reference:%s' % x)
+ continue
+ y.post()
+
+ tsk = getattr(y, 'cs_task', None) or getattr(y, 'link_task', None)
+ if not tsk:
+ self.bld.fatal('cs task has no link task for use %r' % self)
+ self.cs_task.dep_nodes.extend(tsk.outputs) # dependency
+ self.cs_task.set_run_after(tsk) # order (redundant, the order is inferred from the nodes inputs/outputs)
+ self.env.append_value('CSFLAGS', '/reference:%s' % tsk.outputs[0].abspath())
+
+@feature('cs')
+@after_method('apply_cs', 'use_cs')
+def debug_cs(self):
+ """
+ The C# targets may create .mdb or .pdb files::
+
+ def build(bld):
+ bld(features='cs', source='My.cs', bintype='library', gen='my.dll', csdebug='full')
+ # csdebug is a value in (True, 'full', 'pdbonly')
+ """
+ csdebug = getattr(self, 'csdebug', self.env.CSDEBUG)
+ if not csdebug:
+ return
+
+ node = self.cs_task.outputs[0]
+ if self.env.CS_NAME == 'mono':
+ out = node.parent.find_or_declare(node.name + '.mdb')
+ else:
+ out = node.change_ext('.pdb')
+ self.cs_task.outputs.append(out)
+
+ if getattr(self, 'install_task', None):
+ self.pdb_install_task = self.add_install_files(
+ install_to=self.install_task.install_to, install_from=out)
+
+ if csdebug == 'pdbonly':
+ val = ['/debug+', '/debug:pdbonly']
+ elif csdebug == 'full':
+ val = ['/debug+', '/debug:full']
+ else:
+ val = ['/debug-']
+ self.env.append_value('CSFLAGS', val)
+
+@feature('cs')
+@after_method('debug_cs')
+def doc_cs(self):
+ """
+ The C# targets may create .xml documentation files::
+
+ def build(bld):
+ bld(features='cs', source='My.cs', bintype='library', gen='my.dll', csdoc=True)
+ # csdoc is a boolean value
+ """
+ csdoc = getattr(self, 'csdoc', self.env.CSDOC)
+ if not csdoc:
+ return
+
+ node = self.cs_task.outputs[0]
+ out = node.change_ext('.xml')
+ self.cs_task.outputs.append(out)
+
+ if getattr(self, 'install_task', None):
+ self.doc_install_task = self.add_install_files(
+ install_to=self.install_task.install_to, install_from=out)
+
+ self.env.append_value('CSFLAGS', '/doc:%s' % out.abspath())
+
+class mcs(Task.Task):
+ """
+ Compile C# files
+ """
+ color = 'YELLOW'
+ run_str = '${MCS} ${CSTYPE} ${CSFLAGS} ${ASS_ST:ASSEMBLIES} ${RES_ST:RESOURCES} ${OUT} ${SRC}'
+
+ def split_argfile(self, cmd):
+ inline = [cmd[0]]
+ infile = []
+ for x in cmd[1:]:
+ # csc doesn't want /noconfig in @file
+ if x.lower() == '/noconfig':
+ inline.append(x)
+ else:
+ infile.append(self.quote_flag(x))
+ return (inline, infile)
+
+def configure(conf):
+ """
+ Find a C# compiler, set the variable MCS for the compiler and CS_NAME (mono or csc)
+ """
+ csc = getattr(Options.options, 'cscbinary', None)
+ if csc:
+ conf.env.MCS = csc
+ conf.find_program(['csc', 'mcs', 'gmcs'], var='MCS')
+ conf.env.ASS_ST = '/r:%s'
+ conf.env.RES_ST = '/resource:%s'
+
+ conf.env.CS_NAME = 'csc'
+ if str(conf.env.MCS).lower().find('mcs') > -1:
+ conf.env.CS_NAME = 'mono'
+
+def options(opt):
+ """
+ Add a command-line option for the configuration::
+
+ $ waf configure --with-csc-binary=/foo/bar/mcs
+ """
+ opt.add_option('--with-csc-binary', type='string', dest='cscbinary')
+
+class fake_csshlib(Task.Task):
+ """
+ Task used for reading a foreign .net assembly and adding the dependency on it
+ """
+ color = 'YELLOW'
+ inst_to = None
+
+ def runnable_status(self):
+ return Task.SKIP_ME
+
+@conf
+def read_csshlib(self, name, paths=[]):
+ """
+ Read a foreign .net assembly for the *use* system::
+
+ def build(bld):
+ bld.read_csshlib('ManagedLibrary.dll', paths=[bld.env.mylibrarypath])
+ bld(features='cs', source='Hi.cs', bintype='exe', gen='hi.exe', use='ManagedLibrary.dll')
+
+ :param name: Name of the library
+ :type name: string
+ :param paths: Folders in which the library may be found
+ :type paths: list of string
+ :return: A task generator having the feature *fake_lib* which will call :py:func:`waflib.Tools.ccroot.process_lib`
+ :rtype: :py:class:`waflib.TaskGen.task_gen`
+ """
+ return self(name=name, features='fake_lib', lib_paths=paths, lib_type='csshlib')
+
diff --git a/Tools/cxx.py b/Tools/cxx.py
new file mode 100644
index 0000000..194fad7
--- /dev/null
+++ b/Tools/cxx.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2005-2018 (ita)
+
+"Base for c++ programs and libraries"
+
+from waflib import TaskGen, Task
+from waflib.Tools import c_preproc
+from waflib.Tools.ccroot import link_task, stlink_task
+
+@TaskGen.extension('.cpp','.cc','.cxx','.C','.c++')
+def cxx_hook(self, node):
+ "Binds c++ file extensions to create :py:class:`waflib.Tools.cxx.cxx` instances"
+ return self.create_compiled_task('cxx', node)
+
+if not '.c' in TaskGen.task_gen.mappings:
+ TaskGen.task_gen.mappings['.c'] = TaskGen.task_gen.mappings['.cpp']
+
+class cxx(Task.Task):
+ "Compiles C++ files into object files"
+ run_str = '${CXX} ${ARCH_ST:ARCH} ${CXXFLAGS} ${FRAMEWORKPATH_ST:FRAMEWORKPATH} ${CPPPATH_ST:INCPATHS} ${DEFINES_ST:DEFINES} ${CXX_SRC_F}${SRC} ${CXX_TGT_F}${TGT[0].abspath()} ${CPPFLAGS}'
+ vars = ['CXXDEPS'] # unused variable to depend on, just in case
+ ext_in = ['.h'] # set the build order easily by using ext_out=['.h']
+ scan = c_preproc.scan
+
+class cxxprogram(link_task):
+ "Links object files into c++ programs"
+ run_str = '${LINK_CXX} ${LINKFLAGS} ${CXXLNK_SRC_F}${SRC} ${CXXLNK_TGT_F}${TGT[0].abspath()} ${RPATH_ST:RPATH} ${FRAMEWORKPATH_ST:FRAMEWORKPATH} ${FRAMEWORK_ST:FRAMEWORK} ${ARCH_ST:ARCH} ${STLIB_MARKER} ${STLIBPATH_ST:STLIBPATH} ${STLIB_ST:STLIB} ${SHLIB_MARKER} ${LIBPATH_ST:LIBPATH} ${LIB_ST:LIB} ${LDFLAGS}'
+ vars = ['LINKDEPS']
+ ext_out = ['.bin']
+ inst_to = '${BINDIR}'
+
+class cxxshlib(cxxprogram):
+ "Links object files into c++ shared libraries"
+ inst_to = '${LIBDIR}'
+
+class cxxstlib(stlink_task):
+ "Links object files into c++ static libraries"
+ pass # do not remove
+
diff --git a/Tools/d.py b/Tools/d.py
new file mode 100644
index 0000000..e4cf73b
--- /dev/null
+++ b/Tools/d.py
@@ -0,0 +1,97 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Carlos Rafael Giani, 2007 (dv)
+# Thomas Nagy, 2007-2018 (ita)
+
+from waflib import Utils, Task, Errors
+from waflib.TaskGen import taskgen_method, feature, extension
+from waflib.Tools import d_scan, d_config
+from waflib.Tools.ccroot import link_task, stlink_task
+
+class d(Task.Task):
+ "Compile a d file into an object file"
+ color = 'GREEN'
+ run_str = '${D} ${DFLAGS} ${DINC_ST:INCPATHS} ${D_SRC_F:SRC} ${D_TGT_F:TGT}'
+ scan = d_scan.scan
+
+class d_with_header(d):
+ "Compile a d file and generate a header"
+ run_str = '${D} ${DFLAGS} ${DINC_ST:INCPATHS} ${D_HDR_F:tgt.outputs[1].bldpath()} ${D_SRC_F:SRC} ${D_TGT_F:tgt.outputs[0].bldpath()}'
+
+class d_header(Task.Task):
+ "Compile d headers"
+ color = 'BLUE'
+ run_str = '${D} ${D_HEADER} ${SRC}'
+
+class dprogram(link_task):
+ "Link object files into a d program"
+ run_str = '${D_LINKER} ${LINKFLAGS} ${DLNK_SRC_F}${SRC} ${DLNK_TGT_F:TGT} ${RPATH_ST:RPATH} ${DSTLIB_MARKER} ${DSTLIBPATH_ST:STLIBPATH} ${DSTLIB_ST:STLIB} ${DSHLIB_MARKER} ${DLIBPATH_ST:LIBPATH} ${DSHLIB_ST:LIB}'
+ inst_to = '${BINDIR}'
+
+class dshlib(dprogram):
+ "Link object files into a d shared library"
+ inst_to = '${LIBDIR}'
+
+class dstlib(stlink_task):
+ "Link object files into a d static library"
+ pass # do not remove
+
+@extension('.d', '.di', '.D')
+def d_hook(self, node):
+ """
+ Compile *D* files. To get .di files as well as .o files, set the following::
+
+ def build(bld):
+ bld.program(source='foo.d', target='app', generate_headers=True)
+
+ """
+ ext = Utils.destos_to_binfmt(self.env.DEST_OS) == 'pe' and 'obj' or 'o'
+ out = '%s.%d.%s' % (node.name, self.idx, ext)
+ def create_compiled_task(self, name, node):
+ task = self.create_task(name, node, node.parent.find_or_declare(out))
+ try:
+ self.compiled_tasks.append(task)
+ except AttributeError:
+ self.compiled_tasks = [task]
+ return task
+
+ if getattr(self, 'generate_headers', None):
+ tsk = create_compiled_task(self, 'd_with_header', node)
+ tsk.outputs.append(node.change_ext(self.env.DHEADER_ext))
+ else:
+ tsk = create_compiled_task(self, 'd', node)
+ return tsk
+
+@taskgen_method
+def generate_header(self, filename):
+ """
+ See feature request #104::
+
+ def build(bld):
+ tg = bld.program(source='foo.d', target='app')
+ tg.generate_header('blah.d')
+ # is equivalent to:
+ #tg = bld.program(source='foo.d', target='app', header_lst='blah.d')
+
+ :param filename: header to create
+ :type filename: string
+ """
+ try:
+ self.header_lst.append([filename, self.install_path])
+ except AttributeError:
+ self.header_lst = [[filename, self.install_path]]
+
+@feature('d')
+def process_header(self):
+ """
+ Process the attribute 'header_lst' to create the d header compilation tasks::
+
+ def build(bld):
+ bld.program(source='foo.d', target='app', header_lst='blah.d')
+ """
+ for i in getattr(self, 'header_lst', []):
+ node = self.path.find_resource(i[0])
+ if not node:
+ raise Errors.WafError('file %r not found on d obj' % i[0])
+ self.create_task('d_header', node, node.change_ext('.di'))
+
diff --git a/Tools/d_config.py b/Tools/d_config.py
new file mode 100644
index 0000000..6637556
--- /dev/null
+++ b/Tools/d_config.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2016-2018 (ita)
+
+from waflib import Utils
+from waflib.Configure import conf
+
+@conf
+def d_platform_flags(self):
+ """
+ Sets the extensions dll/so for d programs and libraries
+ """
+ v = self.env
+ if not v.DEST_OS:
+ v.DEST_OS = Utils.unversioned_sys_platform()
+ binfmt = Utils.destos_to_binfmt(self.env.DEST_OS)
+ if binfmt == 'pe':
+ v.dprogram_PATTERN = '%s.exe'
+ v.dshlib_PATTERN = 'lib%s.dll'
+ v.dstlib_PATTERN = 'lib%s.a'
+ elif binfmt == 'mac-o':
+ v.dprogram_PATTERN = '%s'
+ v.dshlib_PATTERN = 'lib%s.dylib'
+ v.dstlib_PATTERN = 'lib%s.a'
+ else:
+ v.dprogram_PATTERN = '%s'
+ v.dshlib_PATTERN = 'lib%s.so'
+ v.dstlib_PATTERN = 'lib%s.a'
+
+DLIB = '''
+version(D_Version2) {
+ import std.stdio;
+ int main() {
+ writefln("phobos2");
+ return 0;
+ }
+} else {
+ version(Tango) {
+ import tango.stdc.stdio;
+ int main() {
+ printf("tango");
+ return 0;
+ }
+ } else {
+ import std.stdio;
+ int main() {
+ writefln("phobos1");
+ return 0;
+ }
+ }
+}
+'''
+"""Detection string for the D standard library"""
+
+@conf
+def check_dlibrary(self, execute=True):
+ """
+ Detects the kind of standard library that comes with the compiler,
+ and sets conf.env.DLIBRARY to tango, phobos1 or phobos2
+ """
+ ret = self.check_cc(features='d dprogram', fragment=DLIB, compile_filename='test.d', execute=execute, define_ret=True)
+ if execute:
+ self.env.DLIBRARY = ret.strip()
+
diff --git a/Tools/d_scan.py b/Tools/d_scan.py
new file mode 100644
index 0000000..4e807a6
--- /dev/null
+++ b/Tools/d_scan.py
@@ -0,0 +1,211 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2016-2018 (ita)
+
+"""
+Provide a scanner for finding dependencies on d files
+"""
+
+import re
+from waflib import Utils
+
+def filter_comments(filename):
+ """
+ :param filename: d file name
+ :type filename: string
+ :rtype: list
+ :return: a list of characters
+ """
+ txt = Utils.readf(filename)
+ i = 0
+ buf = []
+ max = len(txt)
+ begin = 0
+ while i < max:
+ c = txt[i]
+ if c == '"' or c == "'": # skip a string or character literal
+ buf.append(txt[begin:i])
+ delim = c
+ i += 1
+ while i < max:
+ c = txt[i]
+ if c == delim:
+ break
+ elif c == '\\': # skip the character following backslash
+ i += 1
+ i += 1
+ i += 1
+ begin = i
+ elif c == '/': # try to replace a comment with whitespace
+ buf.append(txt[begin:i])
+ i += 1
+ if i == max:
+ break
+ c = txt[i]
+ if c == '+': # eat nesting /+ +/ comment
+ i += 1
+ nesting = 1
+ c = None
+ while i < max:
+ prev = c
+ c = txt[i]
+ if prev == '/' and c == '+':
+ nesting += 1
+ c = None
+ elif prev == '+' and c == '/':
+ nesting -= 1
+ if nesting == 0:
+ break
+ c = None
+ i += 1
+ elif c == '*': # eat /* */ comment
+ i += 1
+ c = None
+ while i < max:
+ prev = c
+ c = txt[i]
+ if prev == '*' and c == '/':
+ break
+ i += 1
+ elif c == '/': # eat // comment
+ i += 1
+ while i < max and txt[i] != '\n':
+ i += 1
+ else: # no comment
+ begin = i - 1
+ continue
+ i += 1
+ begin = i
+ buf.append(' ')
+ else:
+ i += 1
+ buf.append(txt[begin:])
+ return buf
+
+class d_parser(object):
+ """
+ Parser for d files
+ """
+ def __init__(self, env, incpaths):
+ #self.code = ''
+ #self.module = ''
+ #self.imports = []
+
+ self.allnames = []
+
+ self.re_module = re.compile(r"module\s+([^;]+)")
+ self.re_import = re.compile(r"import\s+([^;]+)")
+ self.re_import_bindings = re.compile("([^:]+):(.*)")
+ self.re_import_alias = re.compile("[^=]+=(.+)")
+
+ self.env = env
+
+ self.nodes = []
+ self.names = []
+
+ self.incpaths = incpaths
+
+ def tryfind(self, filename):
+ """
+ Search file a file matching an module/import directive
+
+ :param filename: file to read
+ :type filename: string
+ """
+ found = 0
+ for n in self.incpaths:
+ found = n.find_resource(filename.replace('.', '/') + '.d')
+ if found:
+ self.nodes.append(found)
+ self.waiting.append(found)
+ break
+ if not found:
+ if not filename in self.names:
+ self.names.append(filename)
+
+ def get_strings(self, code):
+ """
+ :param code: d code to parse
+ :type code: string
+ :return: the modules that the code uses
+ :rtype: a list of match objects
+ """
+ #self.imports = []
+ self.module = ''
+ lst = []
+
+ # get the module name (if present)
+
+ mod_name = self.re_module.search(code)
+ if mod_name:
+ self.module = re.sub(r'\s+', '', mod_name.group(1)) # strip all whitespaces
+
+ # go through the code, have a look at all import occurrences
+
+ # first, lets look at anything beginning with "import" and ending with ";"
+ import_iterator = self.re_import.finditer(code)
+ if import_iterator:
+ for import_match in import_iterator:
+ import_match_str = re.sub(r'\s+', '', import_match.group(1)) # strip all whitespaces
+
+ # does this end with an import bindings declaration?
+ # (import bindings always terminate the list of imports)
+ bindings_match = self.re_import_bindings.match(import_match_str)
+ if bindings_match:
+ import_match_str = bindings_match.group(1)
+ # if so, extract the part before the ":" (since the module declaration(s) is/are located there)
+
+ # split the matching string into a bunch of strings, separated by a comma
+ matches = import_match_str.split(',')
+
+ for match in matches:
+ alias_match = self.re_import_alias.match(match)
+ if alias_match:
+ # is this an alias declaration? (alias = module name) if so, extract the module name
+ match = alias_match.group(1)
+
+ lst.append(match)
+ return lst
+
+ def start(self, node):
+ """
+ The parsing starts here
+
+ :param node: input file
+ :type node: :py:class:`waflib.Node.Node`
+ """
+ self.waiting = [node]
+ # while the stack is not empty, add the dependencies
+ while self.waiting:
+ nd = self.waiting.pop(0)
+ self.iter(nd)
+
+ def iter(self, node):
+ """
+ Find all the modules that a file depends on, uses :py:meth:`waflib.Tools.d_scan.d_parser.tryfind` to process dependent files
+
+ :param node: input file
+ :type node: :py:class:`waflib.Node.Node`
+ """
+ path = node.abspath() # obtain the absolute path
+ code = "".join(filter_comments(path)) # read the file and filter the comments
+ names = self.get_strings(code) # obtain the import strings
+ for x in names:
+ # optimization
+ if x in self.allnames:
+ continue
+ self.allnames.append(x)
+
+ # for each name, see if it is like a node or not
+ self.tryfind(x)
+
+def scan(self):
+ "look for .d/.di used by a d file"
+ env = self.env
+ gruik = d_parser(env, self.generator.includes_nodes)
+ node = self.inputs[0]
+ gruik.start(node)
+ nodes = gruik.nodes
+ names = gruik.names
+ return (nodes, names)
+
diff --git a/Tools/dbus.py b/Tools/dbus.py
new file mode 100644
index 0000000..d520f1c
--- /dev/null
+++ b/Tools/dbus.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Ali Sabil, 2007
+
+"""
+Compiles dbus files with **dbus-binding-tool**
+
+Typical usage::
+
+ def options(opt):
+ opt.load('compiler_c dbus')
+ def configure(conf):
+ conf.load('compiler_c dbus')
+ def build(bld):
+ tg = bld.program(
+ includes = '.',
+ source = bld.path.ant_glob('*.c'),
+ target = 'gnome-hello')
+ tg.add_dbus_file('test.xml', 'test_prefix', 'glib-server')
+"""
+
+from waflib import Task, Errors
+from waflib.TaskGen import taskgen_method, before_method
+
+@taskgen_method
+def add_dbus_file(self, filename, prefix, mode):
+ """
+ Adds a dbus file to the list of dbus files to process. Store them in the attribute *dbus_lst*.
+
+ :param filename: xml file to compile
+ :type filename: string
+ :param prefix: dbus binding tool prefix (--prefix=prefix)
+ :type prefix: string
+ :param mode: dbus binding tool mode (--mode=mode)
+ :type mode: string
+ """
+ if not hasattr(self, 'dbus_lst'):
+ self.dbus_lst = []
+ if not 'process_dbus' in self.meths:
+ self.meths.append('process_dbus')
+ self.dbus_lst.append([filename, prefix, mode])
+
+@before_method('process_source')
+def process_dbus(self):
+ """
+ Processes the dbus files stored in the attribute *dbus_lst* to create :py:class:`waflib.Tools.dbus.dbus_binding_tool` instances.
+ """
+ for filename, prefix, mode in getattr(self, 'dbus_lst', []):
+ node = self.path.find_resource(filename)
+ if not node:
+ raise Errors.WafError('file not found ' + filename)
+ tsk = self.create_task('dbus_binding_tool', node, node.change_ext('.h'))
+ tsk.env.DBUS_BINDING_TOOL_PREFIX = prefix
+ tsk.env.DBUS_BINDING_TOOL_MODE = mode
+
+class dbus_binding_tool(Task.Task):
+ """
+ Compiles a dbus file
+ """
+ color = 'BLUE'
+ ext_out = ['.h']
+ run_str = '${DBUS_BINDING_TOOL} --prefix=${DBUS_BINDING_TOOL_PREFIX} --mode=${DBUS_BINDING_TOOL_MODE} --output=${TGT} ${SRC}'
+ shell = True # temporary workaround for #795
+
+def configure(conf):
+ """
+ Detects the program dbus-binding-tool and sets ``conf.env.DBUS_BINDING_TOOL``
+ """
+ conf.find_program('dbus-binding-tool', var='DBUS_BINDING_TOOL')
+
diff --git a/Tools/dmd.py b/Tools/dmd.py
new file mode 100644
index 0000000..8917ca1
--- /dev/null
+++ b/Tools/dmd.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Carlos Rafael Giani, 2007 (dv)
+# Thomas Nagy, 2008-2018 (ita)
+
+import sys
+from waflib.Tools import ar, d
+from waflib.Configure import conf
+
+@conf
+def find_dmd(conf):
+ """
+ Finds the program *dmd*, *dmd2*, or *ldc* and set the variable *D*
+ """
+ conf.find_program(['dmd', 'dmd2', 'ldc'], var='D')
+
+ # make sure that we're dealing with dmd1, dmd2, or ldc(1)
+ out = conf.cmd_and_log(conf.env.D + ['--help'])
+ if out.find("D Compiler v") == -1:
+ out = conf.cmd_and_log(conf.env.D + ['-version'])
+ if out.find("based on DMD v1.") == -1:
+ conf.fatal("detected compiler is not dmd/ldc")
+
+@conf
+def common_flags_ldc(conf):
+ """
+ Sets the D flags required by *ldc*
+ """
+ v = conf.env
+ v.DFLAGS = ['-d-version=Posix']
+ v.LINKFLAGS = []
+ v.DFLAGS_dshlib = ['-relocation-model=pic']
+
+@conf
+def common_flags_dmd(conf):
+ """
+ Set the flags required by *dmd* or *dmd2*
+ """
+ v = conf.env
+
+ v.D_SRC_F = ['-c']
+ v.D_TGT_F = '-of%s'
+
+ v.D_LINKER = v.D
+ v.DLNK_SRC_F = ''
+ v.DLNK_TGT_F = '-of%s'
+ v.DINC_ST = '-I%s'
+
+ v.DSHLIB_MARKER = v.DSTLIB_MARKER = ''
+ v.DSTLIB_ST = v.DSHLIB_ST = '-L-l%s'
+ v.DSTLIBPATH_ST = v.DLIBPATH_ST = '-L-L%s'
+
+ v.LINKFLAGS_dprogram= ['-quiet']
+
+ v.DFLAGS_dshlib = ['-fPIC']
+ v.LINKFLAGS_dshlib = ['-L-shared']
+
+ v.DHEADER_ext = '.di'
+ v.DFLAGS_d_with_header = ['-H', '-Hf']
+ v.D_HDR_F = '%s'
+
+def configure(conf):
+ """
+ Configuration for *dmd*, *dmd2*, and *ldc*
+ """
+ conf.find_dmd()
+
+ if sys.platform == 'win32':
+ out = conf.cmd_and_log(conf.env.D + ['--help'])
+ if out.find('D Compiler v2.') > -1:
+ conf.fatal('dmd2 on Windows is not supported, use gdc or ldc2 instead')
+
+ conf.load('ar')
+ conf.load('d')
+ conf.common_flags_dmd()
+ conf.d_platform_flags()
+
+ if str(conf.env.D).find('ldc') > -1:
+ conf.common_flags_ldc()
+
diff --git a/Tools/errcheck.py b/Tools/errcheck.py
new file mode 100644
index 0000000..de8d75a
--- /dev/null
+++ b/Tools/errcheck.py
@@ -0,0 +1,237 @@
+#! /usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2011 (ita)
+
+"""
+Common mistakes highlighting.
+
+There is a performance impact, so this tool is only loaded when running ``waf -v``
+"""
+
+typos = {
+'feature':'features',
+'sources':'source',
+'targets':'target',
+'include':'includes',
+'export_include':'export_includes',
+'define':'defines',
+'importpath':'includes',
+'installpath':'install_path',
+'iscopy':'is_copy',
+'uses':'use',
+}
+
+meths_typos = ['__call__', 'program', 'shlib', 'stlib', 'objects']
+
+import sys
+from waflib import Logs, Build, Node, Task, TaskGen, ConfigSet, Errors, Utils
+from waflib.Tools import ccroot
+
+def check_same_targets(self):
+ mp = Utils.defaultdict(list)
+ uids = {}
+
+ def check_task(tsk):
+ if not isinstance(tsk, Task.Task):
+ return
+ if hasattr(tsk, 'no_errcheck_out'):
+ return
+
+ for node in tsk.outputs:
+ mp[node].append(tsk)
+ try:
+ uids[tsk.uid()].append(tsk)
+ except KeyError:
+ uids[tsk.uid()] = [tsk]
+
+ for g in self.groups:
+ for tg in g:
+ try:
+ for tsk in tg.tasks:
+ check_task(tsk)
+ except AttributeError:
+ # raised if not a task generator, which should be uncommon
+ check_task(tg)
+
+ dupe = False
+ for (k, v) in mp.items():
+ if len(v) > 1:
+ dupe = True
+ msg = '* Node %r is created more than once%s. The task generators are:' % (k, Logs.verbose == 1 and " (full message on 'waf -v -v')" or "")
+ Logs.error(msg)
+ for x in v:
+ if Logs.verbose > 1:
+ Logs.error(' %d. %r', 1 + v.index(x), x.generator)
+ else:
+ Logs.error(' %d. %r in %r', 1 + v.index(x), x.generator.name, getattr(x.generator, 'path', None))
+ Logs.error('If you think that this is an error, set no_errcheck_out on the task instance')
+
+ if not dupe:
+ for (k, v) in uids.items():
+ if len(v) > 1:
+ Logs.error('* Several tasks use the same identifier. Please check the information on\n https://waf.io/apidocs/Task.html?highlight=uid#waflib.Task.Task.uid')
+ tg_details = tsk.generator.name
+ if Logs.verbose > 2:
+ tg_details = tsk.generator
+ for tsk in v:
+ Logs.error(' - object %r (%r) defined in %r', tsk.__class__.__name__, tsk, tg_details)
+
+def check_invalid_constraints(self):
+ feat = set()
+ for x in list(TaskGen.feats.values()):
+ feat.union(set(x))
+ for (x, y) in TaskGen.task_gen.prec.items():
+ feat.add(x)
+ feat.union(set(y))
+ ext = set()
+ for x in TaskGen.task_gen.mappings.values():
+ ext.add(x.__name__)
+ invalid = ext & feat
+ if invalid:
+ Logs.error('The methods %r have invalid annotations: @extension <-> @feature/@before_method/@after_method', list(invalid))
+
+ # the build scripts have been read, so we can check for invalid after/before attributes on task classes
+ for cls in list(Task.classes.values()):
+ if sys.hexversion > 0x3000000 and issubclass(cls, Task.Task) and isinstance(cls.hcode, str):
+ raise Errors.WafError('Class %r has hcode value %r of type <str>, expecting <bytes> (use Utils.h_cmd() ?)' % (cls, cls.hcode))
+
+ for x in ('before', 'after'):
+ for y in Utils.to_list(getattr(cls, x, [])):
+ if not Task.classes.get(y):
+ Logs.error('Erroneous order constraint %r=%r on task class %r', x, y, cls.__name__)
+ if getattr(cls, 'rule', None):
+ Logs.error('Erroneous attribute "rule" on task class %r (rename to "run_str")', cls.__name__)
+
+def replace(m):
+ """
+ Replaces existing BuildContext methods to verify parameter names,
+ for example ``bld(source=)`` has no ending *s*
+ """
+ oldcall = getattr(Build.BuildContext, m)
+ def call(self, *k, **kw):
+ ret = oldcall(self, *k, **kw)
+ for x in typos:
+ if x in kw:
+ if x == 'iscopy' and 'subst' in getattr(self, 'features', ''):
+ continue
+ Logs.error('Fix the typo %r -> %r on %r', x, typos[x], ret)
+ return ret
+ setattr(Build.BuildContext, m, call)
+
+def enhance_lib():
+ """
+ Modifies existing classes and methods to enable error verification
+ """
+ for m in meths_typos:
+ replace(m)
+
+ # catch '..' in ant_glob patterns
+ def ant_glob(self, *k, **kw):
+ if k:
+ lst = Utils.to_list(k[0])
+ for pat in lst:
+ sp = pat.split('/')
+ if '..' in sp:
+ Logs.error("In ant_glob pattern %r: '..' means 'two dots', not 'parent directory'", k[0])
+ if '.' in sp:
+ Logs.error("In ant_glob pattern %r: '.' means 'one dot', not 'current directory'", k[0])
+ return self.old_ant_glob(*k, **kw)
+ Node.Node.old_ant_glob = Node.Node.ant_glob
+ Node.Node.ant_glob = ant_glob
+
+ # catch ant_glob on build folders
+ def ant_iter(self, accept=None, maxdepth=25, pats=[], dir=False, src=True, remove=True, quiet=False):
+ if remove:
+ try:
+ if self.is_child_of(self.ctx.bldnode) and not quiet:
+ quiet = True
+ Logs.error('Calling ant_glob on build folders (%r) is dangerous: add quiet=True / remove=False', self)
+ except AttributeError:
+ pass
+ return self.old_ant_iter(accept, maxdepth, pats, dir, src, remove, quiet)
+ Node.Node.old_ant_iter = Node.Node.ant_iter
+ Node.Node.ant_iter = ant_iter
+
+ # catch conflicting ext_in/ext_out/before/after declarations
+ old = Task.is_before
+ def is_before(t1, t2):
+ ret = old(t1, t2)
+ if ret and old(t2, t1):
+ Logs.error('Contradictory order constraints in classes %r %r', t1, t2)
+ return ret
+ Task.is_before = is_before
+
+ # check for bld(feature='cshlib') where no 'c' is given - this can be either a mistake or on purpose
+ # so we only issue a warning
+ def check_err_features(self):
+ lst = self.to_list(self.features)
+ if 'shlib' in lst:
+ Logs.error('feature shlib -> cshlib, dshlib or cxxshlib')
+ for x in ('c', 'cxx', 'd', 'fc'):
+ if not x in lst and lst and lst[0] in [x+y for y in ('program', 'shlib', 'stlib')]:
+ Logs.error('%r features is probably missing %r', self, x)
+ TaskGen.feature('*')(check_err_features)
+
+ # check for erroneous order constraints
+ def check_err_order(self):
+ if not hasattr(self, 'rule') and not 'subst' in Utils.to_list(self.features):
+ for x in ('before', 'after', 'ext_in', 'ext_out'):
+ if hasattr(self, x):
+ Logs.warn('Erroneous order constraint %r on non-rule based task generator %r', x, self)
+ else:
+ for x in ('before', 'after'):
+ for y in self.to_list(getattr(self, x, [])):
+ if not Task.classes.get(y):
+ Logs.error('Erroneous order constraint %s=%r on %r (no such class)', x, y, self)
+ TaskGen.feature('*')(check_err_order)
+
+ # check for @extension used with @feature/@before_method/@after_method
+ def check_compile(self):
+ check_invalid_constraints(self)
+ try:
+ ret = self.orig_compile()
+ finally:
+ check_same_targets(self)
+ return ret
+ Build.BuildContext.orig_compile = Build.BuildContext.compile
+ Build.BuildContext.compile = check_compile
+
+ # check for invalid build groups #914
+ def use_rec(self, name, **kw):
+ try:
+ y = self.bld.get_tgen_by_name(name)
+ except Errors.WafError:
+ pass
+ else:
+ idx = self.bld.get_group_idx(self)
+ odx = self.bld.get_group_idx(y)
+ if odx > idx:
+ msg = "Invalid 'use' across build groups:"
+ if Logs.verbose > 1:
+ msg += '\n target %r\n uses:\n %r' % (self, y)
+ else:
+ msg += " %r uses %r (try 'waf -v -v' for the full error)" % (self.name, name)
+ raise Errors.WafError(msg)
+ self.orig_use_rec(name, **kw)
+ TaskGen.task_gen.orig_use_rec = TaskGen.task_gen.use_rec
+ TaskGen.task_gen.use_rec = use_rec
+
+ # check for env.append
+ def _getattr(self, name, default=None):
+ if name == 'append' or name == 'add':
+ raise Errors.WafError('env.append and env.add do not exist: use env.append_value/env.append_unique')
+ elif name == 'prepend':
+ raise Errors.WafError('env.prepend does not exist: use env.prepend_value')
+ if name in self.__slots__:
+ return super(ConfigSet.ConfigSet, self).__getattr__(name, default)
+ else:
+ return self[name]
+ ConfigSet.ConfigSet.__getattr__ = _getattr
+
+
+def options(opt):
+ """
+ Error verification can be enabled by default (not just on ``waf -v``) by adding to the user script options
+ """
+ enhance_lib()
+
diff --git a/Tools/fc.py b/Tools/fc.py
new file mode 100644
index 0000000..fd4d39c
--- /dev/null
+++ b/Tools/fc.py
@@ -0,0 +1,203 @@
+#! /usr/bin/env python
+# encoding: utf-8
+# DC 2008
+# Thomas Nagy 2016-2018 (ita)
+
+"""
+Fortran support
+"""
+
+from waflib import Utils, Task, Errors
+from waflib.Tools import ccroot, fc_config, fc_scan
+from waflib.TaskGen import extension
+from waflib.Configure import conf
+
+ccroot.USELIB_VARS['fc'] = set(['FCFLAGS', 'DEFINES', 'INCLUDES', 'FCPPFLAGS'])
+ccroot.USELIB_VARS['fcprogram_test'] = ccroot.USELIB_VARS['fcprogram'] = set(['LIB', 'STLIB', 'LIBPATH', 'STLIBPATH', 'LINKFLAGS', 'RPATH', 'LINKDEPS'])
+ccroot.USELIB_VARS['fcshlib'] = set(['LIB', 'STLIB', 'LIBPATH', 'STLIBPATH', 'LINKFLAGS', 'RPATH', 'LINKDEPS'])
+ccroot.USELIB_VARS['fcstlib'] = set(['ARFLAGS', 'LINKDEPS'])
+
+@extension('.f','.F','.f90','.F90','.for','.FOR','.f95','.F95','.f03','.F03','.f08','.F08')
+def fc_hook(self, node):
+ "Binds the Fortran file extensions create :py:class:`waflib.Tools.fc.fc` instances"
+ return self.create_compiled_task('fc', node)
+
+@conf
+def modfile(conf, name):
+ """
+ Turns a module name into the right module file name.
+ Defaults to all lower case.
+ """
+ if name.find(':') >= 0:
+ # Depending on a submodule!
+ separator = conf.env.FC_SUBMOD_SEPARATOR or '@'
+ # Ancestors of the submodule will be prefixed to the
+ # submodule name, separated by a colon.
+ modpath = name.split(':')
+ # Only the ancestor (actual) module and the submodule name
+ # will be used for the filename.
+ modname = modpath[0] + separator + modpath[-1]
+ suffix = conf.env.FC_SUBMOD_SUFFIX or '.smod'
+ else:
+ modname = name
+ suffix = '.mod'
+
+ return {'lower' :modname.lower() + suffix.lower(),
+ 'lower.MOD' :modname.lower() + suffix.upper(),
+ 'UPPER.mod' :modname.upper() + suffix.lower(),
+ 'UPPER' :modname.upper() + suffix.upper()}[conf.env.FC_MOD_CAPITALIZATION or 'lower']
+
+def get_fortran_tasks(tsk):
+ """
+ Obtains all fortran tasks from the same build group. Those tasks must not have
+ the attribute 'nomod' or 'mod_fortran_done'
+
+ :return: a list of :py:class:`waflib.Tools.fc.fc` instances
+ """
+ bld = tsk.generator.bld
+ tasks = bld.get_tasks_group(bld.get_group_idx(tsk.generator))
+ return [x for x in tasks if isinstance(x, fc) and not getattr(x, 'nomod', None) and not getattr(x, 'mod_fortran_done', None)]
+
+class fc(Task.Task):
+ """
+ Fortran tasks can only run when all fortran tasks in a current task group are ready to be executed
+ This may cause a deadlock if some fortran task is waiting for something that cannot happen (circular dependency)
+ Should this ever happen, set the 'nomod=True' on those tasks instances to break the loop
+ """
+ color = 'GREEN'
+ run_str = '${FC} ${FCFLAGS} ${FCINCPATH_ST:INCPATHS} ${FCDEFINES_ST:DEFINES} ${_FCMODOUTFLAGS} ${FC_TGT_F}${TGT[0].abspath()} ${FC_SRC_F}${SRC[0].abspath()} ${FCPPFLAGS}'
+ vars = ["FORTRANMODPATHFLAG"]
+
+ def scan(self):
+ """Fortran dependency scanner"""
+ tmp = fc_scan.fortran_parser(self.generator.includes_nodes)
+ tmp.task = self
+ tmp.start(self.inputs[0])
+ return (tmp.nodes, tmp.names)
+
+ def runnable_status(self):
+ """
+ Sets the mod file outputs and the dependencies on the mod files over all Fortran tasks
+ executed by the main thread so there are no concurrency issues
+ """
+ if getattr(self, 'mod_fortran_done', None):
+ return super(fc, self).runnable_status()
+
+ # now, if we reach this part it is because this fortran task is the first in the list
+ bld = self.generator.bld
+
+ # obtain the fortran tasks
+ lst = get_fortran_tasks(self)
+
+ # disable this method for other tasks
+ for tsk in lst:
+ tsk.mod_fortran_done = True
+
+ # wait for all the .f tasks to be ready for execution
+ # and ensure that the scanners are called at least once
+ for tsk in lst:
+ ret = tsk.runnable_status()
+ if ret == Task.ASK_LATER:
+ # we have to wait for one of the other fortran tasks to be ready
+ # this may deadlock if there are dependencies between fortran tasks
+ # but this should not happen (we are setting them here!)
+ for x in lst:
+ x.mod_fortran_done = None
+
+ return Task.ASK_LATER
+
+ ins = Utils.defaultdict(set)
+ outs = Utils.defaultdict(set)
+
+ # the .mod files to create
+ for tsk in lst:
+ key = tsk.uid()
+ for x in bld.raw_deps[key]:
+ if x.startswith('MOD@'):
+ name = bld.modfile(x.replace('MOD@', ''))
+ node = bld.srcnode.find_or_declare(name)
+ tsk.set_outputs(node)
+ outs[node].add(tsk)
+
+ # the .mod files to use
+ for tsk in lst:
+ key = tsk.uid()
+ for x in bld.raw_deps[key]:
+ if x.startswith('USE@'):
+ name = bld.modfile(x.replace('USE@', ''))
+ node = bld.srcnode.find_resource(name)
+ if node and node not in tsk.outputs:
+ if not node in bld.node_deps[key]:
+ bld.node_deps[key].append(node)
+ ins[node].add(tsk)
+
+ # if the intersection matches, set the order
+ for k in ins.keys():
+ for a in ins[k]:
+ a.run_after.update(outs[k])
+ for x in outs[k]:
+ self.generator.bld.producer.revdeps[x].add(a)
+
+ # the scanner cannot output nodes, so we have to set them
+ # ourselves as task.dep_nodes (additional input nodes)
+ tmp = []
+ for t in outs[k]:
+ tmp.extend(t.outputs)
+ a.dep_nodes.extend(tmp)
+ a.dep_nodes.sort(key=lambda x: x.abspath())
+
+ # the task objects have changed: clear the signature cache
+ for tsk in lst:
+ try:
+ delattr(tsk, 'cache_sig')
+ except AttributeError:
+ pass
+
+ return super(fc, self).runnable_status()
+
+class fcprogram(ccroot.link_task):
+ """Links Fortran programs"""
+ color = 'YELLOW'
+ run_str = '${FC} ${LINKFLAGS} ${FCLNK_SRC_F}${SRC} ${FCLNK_TGT_F}${TGT[0].abspath()} ${RPATH_ST:RPATH} ${FCSTLIB_MARKER} ${FCSTLIBPATH_ST:STLIBPATH} ${FCSTLIB_ST:STLIB} ${FCSHLIB_MARKER} ${FCLIBPATH_ST:LIBPATH} ${FCLIB_ST:LIB} ${LDFLAGS}'
+ inst_to = '${BINDIR}'
+
+class fcshlib(fcprogram):
+ """Links Fortran libraries"""
+ inst_to = '${LIBDIR}'
+
+class fcstlib(ccroot.stlink_task):
+ """Links Fortran static libraries (uses ar by default)"""
+ pass # do not remove the pass statement
+
+class fcprogram_test(fcprogram):
+ """Custom link task to obtain compiler outputs for Fortran configuration tests"""
+
+ def runnable_status(self):
+ """This task is always executed"""
+ ret = super(fcprogram_test, self).runnable_status()
+ if ret == Task.SKIP_ME:
+ ret = Task.RUN_ME
+ return ret
+
+ def exec_command(self, cmd, **kw):
+ """Stores the compiler std our/err onto the build context, to bld.out + bld.err"""
+ bld = self.generator.bld
+
+ kw['shell'] = isinstance(cmd, str)
+ kw['stdout'] = kw['stderr'] = Utils.subprocess.PIPE
+ kw['cwd'] = self.get_cwd()
+ bld.out = bld.err = ''
+
+ bld.to_log('command: %s\n' % cmd)
+
+ kw['output'] = 0
+ try:
+ (bld.out, bld.err) = bld.cmd_and_log(cmd, **kw)
+ except Errors.WafError:
+ return -1
+
+ if bld.out:
+ bld.to_log('out: %s\n' % bld.out)
+ if bld.err:
+ bld.to_log('err: %s\n' % bld.err)
+
diff --git a/Tools/fc_config.py b/Tools/fc_config.py
new file mode 100644
index 0000000..dc5e5c9
--- /dev/null
+++ b/Tools/fc_config.py
@@ -0,0 +1,488 @@
+#! /usr/bin/env python
+# encoding: utf-8
+# DC 2008
+# Thomas Nagy 2016-2018 (ita)
+
+"""
+Fortran configuration helpers
+"""
+
+import re, os, sys, shlex
+from waflib.Configure import conf
+from waflib.TaskGen import feature, before_method
+
+FC_FRAGMENT = ' program main\n end program main\n'
+FC_FRAGMENT2 = ' PROGRAM MAIN\n END\n' # what's the actual difference between these?
+
+@conf
+def fc_flags(conf):
+ """
+ Defines common fortran configuration flags and file extensions
+ """
+ v = conf.env
+
+ v.FC_SRC_F = []
+ v.FC_TGT_F = ['-c', '-o']
+ v.FCINCPATH_ST = '-I%s'
+ v.FCDEFINES_ST = '-D%s'
+
+ if not v.LINK_FC:
+ v.LINK_FC = v.FC
+
+ v.FCLNK_SRC_F = []
+ v.FCLNK_TGT_F = ['-o']
+
+ v.FCFLAGS_fcshlib = ['-fpic']
+ v.LINKFLAGS_fcshlib = ['-shared']
+ v.fcshlib_PATTERN = 'lib%s.so'
+
+ v.fcstlib_PATTERN = 'lib%s.a'
+
+ v.FCLIB_ST = '-l%s'
+ v.FCLIBPATH_ST = '-L%s'
+ v.FCSTLIB_ST = '-l%s'
+ v.FCSTLIBPATH_ST = '-L%s'
+ v.FCSTLIB_MARKER = '-Wl,-Bstatic'
+ v.FCSHLIB_MARKER = '-Wl,-Bdynamic'
+
+ v.SONAME_ST = '-Wl,-h,%s'
+
+@conf
+def fc_add_flags(conf):
+ """
+ Adds FCFLAGS / LDFLAGS / LINKFLAGS from os.environ to conf.env
+ """
+ conf.add_os_flags('FCPPFLAGS', dup=False)
+ conf.add_os_flags('FCFLAGS', dup=False)
+ conf.add_os_flags('LINKFLAGS', dup=False)
+ conf.add_os_flags('LDFLAGS', dup=False)
+
+@conf
+def check_fortran(self, *k, **kw):
+ """
+ Compiles a Fortran program to ensure that the settings are correct
+ """
+ self.check_cc(
+ fragment = FC_FRAGMENT,
+ compile_filename = 'test.f',
+ features = 'fc fcprogram',
+ msg = 'Compiling a simple fortran app')
+
+@conf
+def check_fc(self, *k, **kw):
+ """
+ Same as :py:func:`waflib.Tools.c_config.check` but defaults to the *Fortran* programming language
+ (this overrides the C defaults in :py:func:`waflib.Tools.c_config.validate_c`)
+ """
+ kw['compiler'] = 'fc'
+ if not 'compile_mode' in kw:
+ kw['compile_mode'] = 'fc'
+ if not 'type' in kw:
+ kw['type'] = 'fcprogram'
+ if not 'compile_filename' in kw:
+ kw['compile_filename'] = 'test.f90'
+ if not 'code' in kw:
+ kw['code'] = FC_FRAGMENT
+ return self.check(*k, **kw)
+
+# ------------------------------------------------------------------------
+# --- These are the default platform modifiers, refactored here for
+# convenience. gfortran and g95 have much overlap.
+# ------------------------------------------------------------------------
+
+@conf
+def fortran_modifier_darwin(conf):
+ """
+ Defines Fortran flags and extensions for OSX systems
+ """
+ v = conf.env
+ v.FCFLAGS_fcshlib = ['-fPIC']
+ v.LINKFLAGS_fcshlib = ['-dynamiclib']
+ v.fcshlib_PATTERN = 'lib%s.dylib'
+ v.FRAMEWORKPATH_ST = '-F%s'
+ v.FRAMEWORK_ST = ['-framework']
+
+ v.LINKFLAGS_fcstlib = []
+
+ v.FCSHLIB_MARKER = ''
+ v.FCSTLIB_MARKER = ''
+ v.SONAME_ST = ''
+
+@conf
+def fortran_modifier_win32(conf):
+ """
+ Defines Fortran flags for Windows platforms
+ """
+ v = conf.env
+ v.fcprogram_PATTERN = v.fcprogram_test_PATTERN = '%s.exe'
+
+ v.fcshlib_PATTERN = '%s.dll'
+ v.implib_PATTERN = '%s.dll.a'
+ v.IMPLIB_ST = '-Wl,--out-implib,%s'
+
+ v.FCFLAGS_fcshlib = []
+
+ # Auto-import is enabled by default even without this option,
+ # but enabling it explicitly has the nice effect of suppressing the rather boring, debug-level messages
+ # that the linker emits otherwise.
+ v.append_value('LINKFLAGS', ['-Wl,--enable-auto-import'])
+
+@conf
+def fortran_modifier_cygwin(conf):
+ """
+ Defines Fortran flags for use on cygwin
+ """
+ fortran_modifier_win32(conf)
+ v = conf.env
+ v.fcshlib_PATTERN = 'cyg%s.dll'
+ v.append_value('LINKFLAGS_fcshlib', ['-Wl,--enable-auto-image-base'])
+ v.FCFLAGS_fcshlib = []
+
+# ------------------------------------------------------------------------
+
+@conf
+def check_fortran_dummy_main(self, *k, **kw):
+ """
+ Determines if a main function is needed by compiling a code snippet with
+ the C compiler and linking it with the Fortran compiler (useful on unix-like systems)
+ """
+ if not self.env.CC:
+ self.fatal('A c compiler is required for check_fortran_dummy_main')
+
+ lst = ['MAIN__', '__MAIN', '_MAIN', 'MAIN_', 'MAIN']
+ lst.extend([m.lower() for m in lst])
+ lst.append('')
+
+ self.start_msg('Detecting whether we need a dummy main')
+ for main in lst:
+ kw['fortran_main'] = main
+ try:
+ self.check_cc(
+ fragment = 'int %s() { return 0; }\n' % (main or 'test'),
+ features = 'c fcprogram',
+ mandatory = True
+ )
+ if not main:
+ self.env.FC_MAIN = -1
+ self.end_msg('no')
+ else:
+ self.env.FC_MAIN = main
+ self.end_msg('yes %s' % main)
+ break
+ except self.errors.ConfigurationError:
+ pass
+ else:
+ self.end_msg('not found')
+ self.fatal('could not detect whether fortran requires a dummy main, see the config.log')
+
+# ------------------------------------------------------------------------
+
+GCC_DRIVER_LINE = re.compile('^Driving:')
+POSIX_STATIC_EXT = re.compile(r'\S+\.a')
+POSIX_LIB_FLAGS = re.compile(r'-l\S+')
+
+@conf
+def is_link_verbose(self, txt):
+ """Returns True if 'useful' link options can be found in txt"""
+ assert isinstance(txt, str)
+ for line in txt.splitlines():
+ if not GCC_DRIVER_LINE.search(line):
+ if POSIX_STATIC_EXT.search(line) or POSIX_LIB_FLAGS.search(line):
+ return True
+ return False
+
+@conf
+def check_fortran_verbose_flag(self, *k, **kw):
+ """
+ Checks what kind of verbose (-v) flag works, then sets it to env.FC_VERBOSE_FLAG
+ """
+ self.start_msg('fortran link verbose flag')
+ for x in ('-v', '--verbose', '-verbose', '-V'):
+ try:
+ self.check_cc(
+ features = 'fc fcprogram_test',
+ fragment = FC_FRAGMENT2,
+ compile_filename = 'test.f',
+ linkflags = [x],
+ mandatory=True)
+ except self.errors.ConfigurationError:
+ pass
+ else:
+ # output is on stderr or stdout (for xlf)
+ if self.is_link_verbose(self.test_bld.err) or self.is_link_verbose(self.test_bld.out):
+ self.end_msg(x)
+ break
+ else:
+ self.end_msg('failure')
+ self.fatal('Could not obtain the fortran link verbose flag (see config.log)')
+
+ self.env.FC_VERBOSE_FLAG = x
+ return x
+
+# ------------------------------------------------------------------------
+
+# linkflags which match those are ignored
+LINKFLAGS_IGNORED = [r'-lang*', r'-lcrt[a-zA-Z0-9\.]*\.o', r'-lc$', r'-lSystem', r'-libmil', r'-LIST:*', r'-LNO:*']
+if os.name == 'nt':
+ LINKFLAGS_IGNORED.extend([r'-lfrt*', r'-luser32', r'-lkernel32', r'-ladvapi32', r'-lmsvcrt', r'-lshell32', r'-lmingw', r'-lmoldname'])
+else:
+ LINKFLAGS_IGNORED.append(r'-lgcc*')
+RLINKFLAGS_IGNORED = [re.compile(f) for f in LINKFLAGS_IGNORED]
+
+def _match_ignore(line):
+ """Returns True if the line should be ignored (Fortran verbose flag test)"""
+ for i in RLINKFLAGS_IGNORED:
+ if i.match(line):
+ return True
+ return False
+
+def parse_fortran_link(lines):
+ """Given the output of verbose link of Fortran compiler, this returns a
+ list of flags necessary for linking using the standard linker."""
+ final_flags = []
+ for line in lines:
+ if not GCC_DRIVER_LINE.match(line):
+ _parse_flink_line(line, final_flags)
+ return final_flags
+
+SPACE_OPTS = re.compile('^-[LRuYz]$')
+NOSPACE_OPTS = re.compile('^-[RL]')
+
+def _parse_flink_token(lexer, token, tmp_flags):
+ # Here we go (convention for wildcard is shell, not regex !)
+ # 1 TODO: we first get some root .a libraries
+ # 2 TODO: take everything starting by -bI:*
+ # 3 Ignore the following flags: -lang* | -lcrt*.o | -lc |
+ # -lgcc* | -lSystem | -libmil | -LANG:=* | -LIST:* | -LNO:*)
+ # 4 take into account -lkernel32
+ # 5 For options of the kind -[[LRuYz]], as they take one argument
+ # after, the actual option is the next token
+ # 6 For -YP,*: take and replace by -Larg where arg is the old
+ # argument
+ # 7 For -[lLR]*: take
+
+ # step 3
+ if _match_ignore(token):
+ pass
+ # step 4
+ elif token.startswith('-lkernel32') and sys.platform == 'cygwin':
+ tmp_flags.append(token)
+ # step 5
+ elif SPACE_OPTS.match(token):
+ t = lexer.get_token()
+ if t.startswith('P,'):
+ t = t[2:]
+ for opt in t.split(os.pathsep):
+ tmp_flags.append('-L%s' % opt)
+ # step 6
+ elif NOSPACE_OPTS.match(token):
+ tmp_flags.append(token)
+ # step 7
+ elif POSIX_LIB_FLAGS.match(token):
+ tmp_flags.append(token)
+ else:
+ # ignore anything not explicitly taken into account
+ pass
+
+ t = lexer.get_token()
+ return t
+
+def _parse_flink_line(line, final_flags):
+ """private"""
+ lexer = shlex.shlex(line, posix = True)
+ lexer.whitespace_split = True
+
+ t = lexer.get_token()
+ tmp_flags = []
+ while t:
+ t = _parse_flink_token(lexer, t, tmp_flags)
+
+ final_flags.extend(tmp_flags)
+ return final_flags
+
+@conf
+def check_fortran_clib(self, autoadd=True, *k, **kw):
+ """
+ Obtains the flags for linking with the C library
+ if this check works, add uselib='CLIB' to your task generators
+ """
+ if not self.env.FC_VERBOSE_FLAG:
+ self.fatal('env.FC_VERBOSE_FLAG is not set: execute check_fortran_verbose_flag?')
+
+ self.start_msg('Getting fortran runtime link flags')
+ try:
+ self.check_cc(
+ fragment = FC_FRAGMENT2,
+ compile_filename = 'test.f',
+ features = 'fc fcprogram_test',
+ linkflags = [self.env.FC_VERBOSE_FLAG]
+ )
+ except Exception:
+ self.end_msg(False)
+ if kw.get('mandatory', True):
+ conf.fatal('Could not find the c library flags')
+ else:
+ out = self.test_bld.err
+ flags = parse_fortran_link(out.splitlines())
+ self.end_msg('ok (%s)' % ' '.join(flags))
+ self.env.LINKFLAGS_CLIB = flags
+ return flags
+ return []
+
+def getoutput(conf, cmd, stdin=False):
+ """
+ Obtains Fortran command outputs
+ """
+ from waflib import Errors
+ if conf.env.env:
+ env = conf.env.env
+ else:
+ env = dict(os.environ)
+ env['LANG'] = 'C'
+ input = stdin and '\n'.encode() or None
+ try:
+ out, err = conf.cmd_and_log(cmd, env=env, output=0, input=input)
+ except Errors.WafError as e:
+ # An WafError might indicate an error code during the command
+ # execution, in this case we still obtain the stderr and stdout,
+ # which we can use to find the version string.
+ if not (hasattr(e, 'stderr') and hasattr(e, 'stdout')):
+ raise e
+ else:
+ # Ignore the return code and return the original
+ # stdout and stderr.
+ out = e.stdout
+ err = e.stderr
+ except Exception:
+ conf.fatal('could not determine the compiler version %r' % cmd)
+ return (out, err)
+
+# ------------------------------------------------------------------------
+
+ROUTINES_CODE = """\
+ subroutine foobar()
+ return
+ end
+ subroutine foo_bar()
+ return
+ end
+"""
+
+MAIN_CODE = """
+void %(dummy_func_nounder)s(void);
+void %(dummy_func_under)s(void);
+int %(main_func_name)s() {
+ %(dummy_func_nounder)s();
+ %(dummy_func_under)s();
+ return 0;
+}
+"""
+
+@feature('link_main_routines_func')
+@before_method('process_source')
+def link_main_routines_tg_method(self):
+ """
+ The configuration test declares a unique task generator,
+ so we create other task generators from there for fortran link tests
+ """
+ def write_test_file(task):
+ task.outputs[0].write(task.generator.code)
+ bld = self.bld
+ bld(rule=write_test_file, target='main.c', code=MAIN_CODE % self.__dict__)
+ bld(rule=write_test_file, target='test.f', code=ROUTINES_CODE)
+ bld(features='fc fcstlib', source='test.f', target='test')
+ bld(features='c fcprogram', source='main.c', target='app', use='test')
+
+def mangling_schemes():
+ """
+ Generate triplets for use with mangle_name
+ (used in check_fortran_mangling)
+ the order is tuned for gfortan
+ """
+ for u in ('_', ''):
+ for du in ('', '_'):
+ for c in ("lower", "upper"):
+ yield (u, du, c)
+
+def mangle_name(u, du, c, name):
+ """Mangle a name from a triplet (used in check_fortran_mangling)"""
+ return getattr(name, c)() + u + (name.find('_') != -1 and du or '')
+
+@conf
+def check_fortran_mangling(self, *k, **kw):
+ """
+ Detect the mangling scheme, sets FORTRAN_MANGLING to the triplet found
+
+ This test will compile a fortran static library, then link a c app against it
+ """
+ if not self.env.CC:
+ self.fatal('A c compiler is required for link_main_routines')
+ if not self.env.FC:
+ self.fatal('A fortran compiler is required for link_main_routines')
+ if not self.env.FC_MAIN:
+ self.fatal('Checking for mangling requires self.env.FC_MAIN (execute "check_fortran_dummy_main" first?)')
+
+ self.start_msg('Getting fortran mangling scheme')
+ for (u, du, c) in mangling_schemes():
+ try:
+ self.check_cc(
+ compile_filename = [],
+ features = 'link_main_routines_func',
+ msg = 'nomsg',
+ errmsg = 'nomsg',
+ dummy_func_nounder = mangle_name(u, du, c, 'foobar'),
+ dummy_func_under = mangle_name(u, du, c, 'foo_bar'),
+ main_func_name = self.env.FC_MAIN
+ )
+ except self.errors.ConfigurationError:
+ pass
+ else:
+ self.end_msg("ok ('%s', '%s', '%s-case')" % (u, du, c))
+ self.env.FORTRAN_MANGLING = (u, du, c)
+ break
+ else:
+ self.end_msg(False)
+ self.fatal('mangler not found')
+ return (u, du, c)
+
+@feature('pyext')
+@before_method('propagate_uselib_vars', 'apply_link')
+def set_lib_pat(self):
+ """Sets the Fortran flags for linking with Python"""
+ self.env.fcshlib_PATTERN = self.env.pyext_PATTERN
+
+@conf
+def detect_openmp(self):
+ """
+ Detects openmp flags and sets the OPENMP ``FCFLAGS``/``LINKFLAGS``
+ """
+ for x in ('-fopenmp','-openmp','-mp','-xopenmp','-omp','-qsmp=omp'):
+ try:
+ self.check_fc(
+ msg = 'Checking for OpenMP flag %s' % x,
+ fragment = 'program main\n call omp_get_num_threads()\nend program main',
+ fcflags = x,
+ linkflags = x,
+ uselib_store = 'OPENMP'
+ )
+ except self.errors.ConfigurationError:
+ pass
+ else:
+ break
+ else:
+ self.fatal('Could not find OpenMP')
+
+@conf
+def check_gfortran_o_space(self):
+ if self.env.FC_NAME != 'GFORTRAN' or int(self.env.FC_VERSION[0]) > 4:
+ # This is for old compilers and only for gfortran.
+ # No idea how other implementations handle this. Be safe and bail out.
+ return
+ self.env.stash()
+ self.env.FCLNK_TGT_F = ['-o', '']
+ try:
+ self.check_fc(msg='Checking if the -o link must be split from arguments', fragment=FC_FRAGMENT, features='fc fcshlib')
+ except self.errors.ConfigurationError:
+ self.env.revert()
+ else:
+ self.env.commit()
diff --git a/Tools/fc_scan.py b/Tools/fc_scan.py
new file mode 100644
index 0000000..0824c92
--- /dev/null
+++ b/Tools/fc_scan.py
@@ -0,0 +1,120 @@
+#! /usr/bin/env python
+# encoding: utf-8
+# DC 2008
+# Thomas Nagy 2016-2018 (ita)
+
+import re
+
+INC_REGEX = r"""(?:^|['">]\s*;)\s*(?:|#\s*)INCLUDE\s+(?:\w+_)?[<"'](.+?)(?=["'>])"""
+USE_REGEX = r"""(?:^|;)\s*USE(?:\s+|(?:(?:\s*,\s*(?:NON_)?INTRINSIC)?\s*::))\s*(\w+)"""
+MOD_REGEX = r"""(?:^|;)\s*MODULE(?!\s+(?:PROCEDURE|SUBROUTINE|FUNCTION))\s+(\w+)"""
+SMD_REGEX = r"""(?:^|;)\s*SUBMODULE\s*\(([\w:]+)\)\s*(\w+)"""
+
+re_inc = re.compile(INC_REGEX, re.I)
+re_use = re.compile(USE_REGEX, re.I)
+re_mod = re.compile(MOD_REGEX, re.I)
+re_smd = re.compile(SMD_REGEX, re.I)
+
+class fortran_parser(object):
+ """
+ This parser returns:
+
+ * the nodes corresponding to the module names to produce
+ * the nodes corresponding to the include files used
+ * the module names used by the fortran files
+ """
+ def __init__(self, incpaths):
+ self.seen = []
+ """Files already parsed"""
+
+ self.nodes = []
+ """List of :py:class:`waflib.Node.Node` representing the dependencies to return"""
+
+ self.names = []
+ """List of module names to return"""
+
+ self.incpaths = incpaths
+ """List of :py:class:`waflib.Node.Node` representing the include paths"""
+
+ def find_deps(self, node):
+ """
+ Parses a Fortran file to obtain the dependencies used/provided
+
+ :param node: fortran file to read
+ :type node: :py:class:`waflib.Node.Node`
+ :return: lists representing the includes, the modules used, and the modules created by a fortran file
+ :rtype: tuple of list of strings
+ """
+ txt = node.read()
+ incs = []
+ uses = []
+ mods = []
+ for line in txt.splitlines():
+ # line by line regexp search? optimize?
+ m = re_inc.search(line)
+ if m:
+ incs.append(m.group(1))
+ m = re_use.search(line)
+ if m:
+ uses.append(m.group(1))
+ m = re_mod.search(line)
+ if m:
+ mods.append(m.group(1))
+ m = re_smd.search(line)
+ if m:
+ uses.append(m.group(1))
+ mods.append('{0}:{1}'.format(m.group(1),m.group(2)))
+ return (incs, uses, mods)
+
+ def start(self, node):
+ """
+ Start parsing. Use the stack ``self.waiting`` to hold nodes to iterate on
+
+ :param node: fortran file
+ :type node: :py:class:`waflib.Node.Node`
+ """
+ self.waiting = [node]
+ while self.waiting:
+ nd = self.waiting.pop(0)
+ self.iter(nd)
+
+ def iter(self, node):
+ """
+ Processes a single file during dependency parsing. Extracts files used
+ modules used and modules provided.
+ """
+ incs, uses, mods = self.find_deps(node)
+ for x in incs:
+ if x in self.seen:
+ continue
+ self.seen.append(x)
+ self.tryfind_header(x)
+
+ for x in uses:
+ name = "USE@%s" % x
+ if not name in self.names:
+ self.names.append(name)
+
+ for x in mods:
+ name = "MOD@%s" % x
+ if not name in self.names:
+ self.names.append(name)
+
+ def tryfind_header(self, filename):
+ """
+ Adds an include file to the list of nodes to process
+
+ :param filename: file name
+ :type filename: string
+ """
+ found = None
+ for n in self.incpaths:
+ found = n.find_resource(filename)
+ if found:
+ self.nodes.append(found)
+ self.waiting.append(found)
+ break
+ if not found:
+ if not filename in self.names:
+ self.names.append(filename)
+
diff --git a/Tools/flex.py b/Tools/flex.py
new file mode 100644
index 0000000..2256657
--- /dev/null
+++ b/Tools/flex.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# John O'Meara, 2006
+# Thomas Nagy, 2006-2018 (ita)
+
+"""
+The **flex** program is a code generator which creates C or C++ files.
+The generated files are compiled into object files.
+"""
+
+import os, re
+from waflib import Task, TaskGen
+from waflib.Tools import ccroot
+
+def decide_ext(self, node):
+ if 'cxx' in self.features:
+ return ['.lex.cc']
+ return ['.lex.c']
+
+def flexfun(tsk):
+ env = tsk.env
+ bld = tsk.generator.bld
+ wd = bld.variant_dir
+ def to_list(xx):
+ if isinstance(xx, str):
+ return [xx]
+ return xx
+ tsk.last_cmd = lst = []
+ lst.extend(to_list(env.FLEX))
+ lst.extend(to_list(env.FLEXFLAGS))
+ inputs = [a.path_from(tsk.get_cwd()) for a in tsk.inputs]
+ if env.FLEX_MSYS:
+ inputs = [x.replace(os.sep, '/') for x in inputs]
+ lst.extend(inputs)
+ lst = [x for x in lst if x]
+ txt = bld.cmd_and_log(lst, cwd=wd, env=env.env or None, quiet=0)
+ tsk.outputs[0].write(txt.replace('\r\n', '\n').replace('\r', '\n')) # issue #1207
+
+TaskGen.declare_chain(
+ name = 'flex',
+ rule = flexfun, # issue #854
+ ext_in = '.l',
+ decider = decide_ext,
+)
+
+# To support the following:
+# bld(features='c', flexflags='-P/foo')
+Task.classes['flex'].vars = ['FLEXFLAGS', 'FLEX']
+ccroot.USELIB_VARS['c'].add('FLEXFLAGS')
+ccroot.USELIB_VARS['cxx'].add('FLEXFLAGS')
+
+def configure(conf):
+ """
+ Detect the *flex* program
+ """
+ conf.find_program('flex', var='FLEX')
+ conf.env.FLEXFLAGS = ['-t']
+
+ if re.search (r"\\msys\\[0-9.]+\\bin\\flex.exe$", conf.env.FLEX[0]):
+ # this is the flex shipped with MSYS
+ conf.env.FLEX_MSYS = True
+
diff --git a/Tools/g95.py b/Tools/g95.py
new file mode 100644
index 0000000..f69ba4f
--- /dev/null
+++ b/Tools/g95.py
@@ -0,0 +1,66 @@
+#! /usr/bin/env python
+# encoding: utf-8
+# KWS 2010
+# Thomas Nagy 2016-2018 (ita)
+
+import re
+from waflib import Utils
+from waflib.Tools import fc, fc_config, fc_scan, ar
+from waflib.Configure import conf
+
+@conf
+def find_g95(conf):
+ fc = conf.find_program('g95', var='FC')
+ conf.get_g95_version(fc)
+ conf.env.FC_NAME = 'G95'
+
+@conf
+def g95_flags(conf):
+ v = conf.env
+ v.FCFLAGS_fcshlib = ['-fPIC']
+ v.FORTRANMODFLAG = ['-fmod=', ''] # template for module path
+ v.FCFLAGS_DEBUG = ['-Werror'] # why not
+
+@conf
+def g95_modifier_win32(conf):
+ fc_config.fortran_modifier_win32(conf)
+
+@conf
+def g95_modifier_cygwin(conf):
+ fc_config.fortran_modifier_cygwin(conf)
+
+@conf
+def g95_modifier_darwin(conf):
+ fc_config.fortran_modifier_darwin(conf)
+
+@conf
+def g95_modifier_platform(conf):
+ dest_os = conf.env.DEST_OS or Utils.unversioned_sys_platform()
+ g95_modifier_func = getattr(conf, 'g95_modifier_' + dest_os, None)
+ if g95_modifier_func:
+ g95_modifier_func()
+
+@conf
+def get_g95_version(conf, fc):
+ """get the compiler version"""
+
+ version_re = re.compile(r"g95\s*(?P<major>\d*)\.(?P<minor>\d*)").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:
+ conf.fatal('cannot determine g95 version')
+ k = match.groupdict()
+ conf.env.FC_VERSION = (k['major'], k['minor'])
+
+def configure(conf):
+ conf.find_g95()
+ conf.find_ar()
+ conf.fc_flags()
+ conf.fc_add_flags()
+ conf.g95_flags()
+ conf.g95_modifier_platform()
+
diff --git a/Tools/gas.py b/Tools/gas.py
new file mode 100644
index 0000000..77afed7
--- /dev/null
+++ b/Tools/gas.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2008-2018 (ita)
+
+"Detect as/gas/gcc for compiling assembly files"
+
+import waflib.Tools.asm # - leave this
+from waflib.Tools import ar
+
+def configure(conf):
+ """
+ Find the programs gas/as/gcc and set the variable *AS*
+ """
+ conf.find_program(['gas', 'gcc'], var='AS')
+ conf.env.AS_TGT_F = ['-c', '-o']
+ conf.env.ASLNK_TGT_F = ['-o']
+ conf.find_ar()
+ conf.load('asm')
diff --git a/Tools/gcc.py b/Tools/gcc.py
new file mode 100644
index 0000000..acdd473
--- /dev/null
+++ b/Tools/gcc.py
@@ -0,0 +1,156 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2006-2018 (ita)
+# Ralf Habacker, 2006 (rh)
+# Yinon Ehrlich, 2009
+
+"""
+gcc/llvm detection.
+"""
+
+from waflib.Tools import ccroot, ar
+from waflib.Configure import conf
+
+@conf
+def find_gcc(conf):
+ """
+ Find the program gcc, and if present, try to detect its version number
+ """
+ cc = conf.find_program(['gcc', 'cc'], var='CC')
+ conf.get_cc_version(cc, gcc=True)
+ conf.env.CC_NAME = 'gcc'
+
+@conf
+def gcc_common_flags(conf):
+ """
+ Common flags for gcc on nearly all platforms
+ """
+ v = conf.env
+
+ v.CC_SRC_F = []
+ v.CC_TGT_F = ['-c', '-o']
+
+ if not v.LINK_CC:
+ v.LINK_CC = v.CC
+
+ v.CCLNK_SRC_F = []
+ v.CCLNK_TGT_F = ['-o']
+ v.CPPPATH_ST = '-I%s'
+ v.DEFINES_ST = '-D%s'
+
+ v.LIB_ST = '-l%s' # template for adding libs
+ v.LIBPATH_ST = '-L%s' # template for adding libpaths
+ v.STLIB_ST = '-l%s'
+ v.STLIBPATH_ST = '-L%s'
+ v.RPATH_ST = '-Wl,-rpath,%s'
+
+ v.SONAME_ST = '-Wl,-h,%s'
+ v.SHLIB_MARKER = '-Wl,-Bdynamic'
+ v.STLIB_MARKER = '-Wl,-Bstatic'
+
+ v.cprogram_PATTERN = '%s'
+
+ v.CFLAGS_cshlib = ['-fPIC']
+ v.LINKFLAGS_cshlib = ['-shared']
+ v.cshlib_PATTERN = 'lib%s.so'
+
+ v.LINKFLAGS_cstlib = ['-Wl,-Bstatic']
+ v.cstlib_PATTERN = 'lib%s.a'
+
+ v.LINKFLAGS_MACBUNDLE = ['-bundle', '-undefined', 'dynamic_lookup']
+ v.CFLAGS_MACBUNDLE = ['-fPIC']
+ v.macbundle_PATTERN = '%s.bundle'
+
+@conf
+def gcc_modifier_win32(conf):
+ """Configuration flags for executing gcc on Windows"""
+ v = conf.env
+ v.cprogram_PATTERN = '%s.exe'
+
+ v.cshlib_PATTERN = '%s.dll'
+ v.implib_PATTERN = '%s.dll.a'
+ v.IMPLIB_ST = '-Wl,--out-implib,%s'
+
+ v.CFLAGS_cshlib = []
+
+ # Auto-import is enabled by default even without this option,
+ # but enabling it explicitly has the nice effect of suppressing the rather boring, debug-level messages
+ # that the linker emits otherwise.
+ v.append_value('LINKFLAGS', ['-Wl,--enable-auto-import'])
+
+@conf
+def gcc_modifier_cygwin(conf):
+ """Configuration flags for executing gcc on Cygwin"""
+ gcc_modifier_win32(conf)
+ v = conf.env
+ v.cshlib_PATTERN = 'cyg%s.dll'
+ v.append_value('LINKFLAGS_cshlib', ['-Wl,--enable-auto-image-base'])
+ v.CFLAGS_cshlib = []
+
+@conf
+def gcc_modifier_darwin(conf):
+ """Configuration flags for executing gcc on MacOS"""
+ v = conf.env
+ v.CFLAGS_cshlib = ['-fPIC']
+ v.LINKFLAGS_cshlib = ['-dynamiclib']
+ v.cshlib_PATTERN = 'lib%s.dylib'
+ v.FRAMEWORKPATH_ST = '-F%s'
+ v.FRAMEWORK_ST = ['-framework']
+ v.ARCH_ST = ['-arch']
+
+ v.LINKFLAGS_cstlib = []
+
+ v.SHLIB_MARKER = []
+ v.STLIB_MARKER = []
+ v.SONAME_ST = []
+
+@conf
+def gcc_modifier_aix(conf):
+ """Configuration flags for executing gcc on AIX"""
+ v = conf.env
+ v.LINKFLAGS_cprogram = ['-Wl,-brtl']
+ v.LINKFLAGS_cshlib = ['-shared','-Wl,-brtl,-bexpfull']
+ v.SHLIB_MARKER = []
+
+@conf
+def gcc_modifier_hpux(conf):
+ v = conf.env
+ v.SHLIB_MARKER = []
+ v.STLIB_MARKER = []
+ v.CFLAGS_cshlib = ['-fPIC','-DPIC']
+ v.cshlib_PATTERN = 'lib%s.sl'
+
+@conf
+def gcc_modifier_openbsd(conf):
+ conf.env.SONAME_ST = []
+
+@conf
+def gcc_modifier_osf1V(conf):
+ v = conf.env
+ v.SHLIB_MARKER = []
+ v.STLIB_MARKER = []
+ v.SONAME_ST = []
+
+@conf
+def gcc_modifier_platform(conf):
+ """Execute platform-specific functions based on *gcc_modifier_+NAME*"""
+ # * set configurations specific for a platform.
+ # * the destination platform is detected automatically by looking at the macros the compiler predefines,
+ # and if it's not recognised, it fallbacks to sys.platform.
+ gcc_modifier_func = getattr(conf, 'gcc_modifier_' + conf.env.DEST_OS, None)
+ if gcc_modifier_func:
+ gcc_modifier_func()
+
+def configure(conf):
+ """
+ Configuration for gcc
+ """
+ conf.find_gcc()
+ conf.find_ar()
+ conf.gcc_common_flags()
+ conf.gcc_modifier_platform()
+ conf.cc_load_tools()
+ conf.cc_add_flags()
+ conf.link_add_flags()
+ conf.check_gcc_o_space()
+
diff --git a/Tools/gdc.py b/Tools/gdc.py
new file mode 100644
index 0000000..d89a66d
--- /dev/null
+++ b/Tools/gdc.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Carlos Rafael Giani, 2007 (dv)
+
+from waflib.Tools import ar, d
+from waflib.Configure import conf
+
+@conf
+def find_gdc(conf):
+ """
+ Finds the program gdc and set the variable *D*
+ """
+ conf.find_program('gdc', var='D')
+
+ out = conf.cmd_and_log(conf.env.D + ['--version'])
+ if out.find("gdc") == -1:
+ conf.fatal("detected compiler is not gdc")
+
+@conf
+def common_flags_gdc(conf):
+ """
+ Sets the flags required by *gdc*
+ """
+ v = conf.env
+
+ v.DFLAGS = []
+
+ v.D_SRC_F = ['-c']
+ v.D_TGT_F = '-o%s'
+
+ v.D_LINKER = v.D
+ v.DLNK_SRC_F = ''
+ v.DLNK_TGT_F = '-o%s'
+ v.DINC_ST = '-I%s'
+
+ v.DSHLIB_MARKER = v.DSTLIB_MARKER = ''
+ v.DSTLIB_ST = v.DSHLIB_ST = '-l%s'
+ v.DSTLIBPATH_ST = v.DLIBPATH_ST = '-L%s'
+
+ v.LINKFLAGS_dshlib = ['-shared']
+
+ v.DHEADER_ext = '.di'
+ v.DFLAGS_d_with_header = '-fintfc'
+ v.D_HDR_F = '-fintfc-file=%s'
+
+def configure(conf):
+ """
+ Configuration for gdc
+ """
+ conf.find_gdc()
+ conf.load('ar')
+ conf.load('d')
+ conf.common_flags_gdc()
+ conf.d_platform_flags()
+
diff --git a/Tools/gfortran.py b/Tools/gfortran.py
new file mode 100644
index 0000000..1050667
--- /dev/null
+++ b/Tools/gfortran.py
@@ -0,0 +1,93 @@
+#! /usr/bin/env python
+# encoding: utf-8
+# DC 2008
+# Thomas Nagy 2016-2018 (ita)
+
+import re
+from waflib import Utils
+from waflib.Tools import fc, fc_config, fc_scan, ar
+from waflib.Configure import conf
+
+@conf
+def find_gfortran(conf):
+ """Find the gfortran program (will look in the environment variable 'FC')"""
+ fc = conf.find_program(['gfortran','g77'], var='FC')
+ # (fallback to g77 for systems, where no gfortran is available)
+ conf.get_gfortran_version(fc)
+ conf.env.FC_NAME = 'GFORTRAN'
+
+@conf
+def gfortran_flags(conf):
+ v = conf.env
+ v.FCFLAGS_fcshlib = ['-fPIC']
+ v.FORTRANMODFLAG = ['-J', ''] # template for module path
+ v.FCFLAGS_DEBUG = ['-Werror'] # why not
+
+@conf
+def gfortran_modifier_win32(conf):
+ fc_config.fortran_modifier_win32(conf)
+
+@conf
+def gfortran_modifier_cygwin(conf):
+ fc_config.fortran_modifier_cygwin(conf)
+
+@conf
+def gfortran_modifier_darwin(conf):
+ fc_config.fortran_modifier_darwin(conf)
+
+@conf
+def gfortran_modifier_platform(conf):
+ dest_os = conf.env.DEST_OS or Utils.unversioned_sys_platform()
+ gfortran_modifier_func = getattr(conf, 'gfortran_modifier_' + dest_os, None)
+ if gfortran_modifier_func:
+ gfortran_modifier_func()
+
+@conf
+def get_gfortran_version(conf, fc):
+ """Get the compiler version"""
+
+ # ensure this is actually gfortran, not an imposter.
+ version_re = re.compile(r"GNU\s*Fortran", 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:
+ conf.fatal('Could not determine the compiler type')
+
+ # --- now get more detailed info -- see c_config.get_cc_version
+ cmd = fc + ['-dM', '-E', '-']
+ out, err = fc_config.getoutput(conf, cmd, stdin=True)
+
+ if out.find('__GNUC__') < 0:
+ conf.fatal('Could not determine the compiler type')
+
+ k = {}
+ out = out.splitlines()
+ import shlex
+
+ for line in out:
+ lst = shlex.split(line)
+ if len(lst)>2:
+ key = lst[1]
+ val = lst[2]
+ k[key] = val
+
+ def isD(var):
+ return var in k
+
+ def isT(var):
+ return var in k and k[var] != '0'
+
+ conf.env.FC_VERSION = (k['__GNUC__'], k['__GNUC_MINOR__'], k['__GNUC_PATCHLEVEL__'])
+
+def configure(conf):
+ conf.find_gfortran()
+ conf.find_ar()
+ conf.fc_flags()
+ conf.fc_add_flags()
+ conf.gfortran_flags()
+ conf.gfortran_modifier_platform()
+ conf.check_gfortran_o_space()
diff --git a/Tools/glib2.py b/Tools/glib2.py
new file mode 100644
index 0000000..949fe37
--- /dev/null
+++ b/Tools/glib2.py
@@ -0,0 +1,489 @@
+#! /usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2006-2018 (ita)
+
+"""
+Support for GLib2 tools:
+
+* marshal
+* enums
+* gsettings
+* gresource
+"""
+
+import os
+import functools
+from waflib import Context, Task, Utils, Options, Errors, Logs
+from waflib.TaskGen import taskgen_method, before_method, feature, extension
+from waflib.Configure import conf
+
+################## marshal files
+
+@taskgen_method
+def add_marshal_file(self, filename, prefix):
+ """
+ Adds a file to the list of marshal files to process. Store them in the attribute *marshal_list*.
+
+ :param filename: xml file to compile
+ :type filename: string
+ :param prefix: marshal prefix (--prefix=prefix)
+ :type prefix: string
+ """
+ if not hasattr(self, 'marshal_list'):
+ self.marshal_list = []
+ self.meths.append('process_marshal')
+ self.marshal_list.append((filename, prefix))
+
+@before_method('process_source')
+def process_marshal(self):
+ """
+ Processes the marshal files stored in the attribute *marshal_list* to create :py:class:`waflib.Tools.glib2.glib_genmarshal` instances.
+ Adds the c file created to the list of source to process.
+ """
+ for f, prefix in getattr(self, 'marshal_list', []):
+ node = self.path.find_resource(f)
+
+ if not node:
+ raise Errors.WafError('file not found %r' % f)
+
+ h_node = node.change_ext('.h')
+ c_node = node.change_ext('.c')
+
+ task = self.create_task('glib_genmarshal', node, [h_node, c_node])
+ task.env.GLIB_GENMARSHAL_PREFIX = prefix
+ self.source = self.to_nodes(getattr(self, 'source', []))
+ self.source.append(c_node)
+
+class glib_genmarshal(Task.Task):
+ vars = ['GLIB_GENMARSHAL_PREFIX', 'GLIB_GENMARSHAL']
+ color = 'BLUE'
+ ext_out = ['.h']
+ def run(self):
+ bld = self.generator.bld
+
+ get = self.env.get_flat
+ cmd1 = "%s %s --prefix=%s --header > %s" % (
+ get('GLIB_GENMARSHAL'),
+ self.inputs[0].srcpath(),
+ get('GLIB_GENMARSHAL_PREFIX'),
+ self.outputs[0].abspath()
+ )
+
+ ret = bld.exec_command(cmd1)
+ if ret:
+ return ret
+
+ #print self.outputs[1].abspath()
+ c = '''#include "%s"\n''' % self.outputs[0].name
+ self.outputs[1].write(c)
+
+ cmd2 = "%s %s --prefix=%s --body >> %s" % (
+ get('GLIB_GENMARSHAL'),
+ self.inputs[0].srcpath(),
+ get('GLIB_GENMARSHAL_PREFIX'),
+ self.outputs[1].abspath()
+ )
+ return bld.exec_command(cmd2)
+
+########################## glib-mkenums
+
+@taskgen_method
+def add_enums_from_template(self, source='', target='', template='', comments=''):
+ """
+ Adds a file to the list of enum files to process. Stores them in the attribute *enums_list*.
+
+ :param source: enum file to process
+ :type source: string
+ :param target: target file
+ :type target: string
+ :param template: template file
+ :type template: string
+ :param comments: comments
+ :type comments: string
+ """
+ if not hasattr(self, 'enums_list'):
+ self.enums_list = []
+ self.meths.append('process_enums')
+ self.enums_list.append({'source': source,
+ 'target': target,
+ 'template': template,
+ 'file-head': '',
+ 'file-prod': '',
+ 'file-tail': '',
+ 'enum-prod': '',
+ 'value-head': '',
+ 'value-prod': '',
+ 'value-tail': '',
+ 'comments': comments})
+
+@taskgen_method
+def add_enums(self, source='', target='',
+ file_head='', file_prod='', file_tail='', enum_prod='',
+ value_head='', value_prod='', value_tail='', comments=''):
+ """
+ Adds a file to the list of enum files to process. Stores them in the attribute *enums_list*.
+
+ :param source: enum file to process
+ :type source: string
+ :param target: target file
+ :type target: string
+ :param file_head: unused
+ :param file_prod: unused
+ :param file_tail: unused
+ :param enum_prod: unused
+ :param value_head: unused
+ :param value_prod: unused
+ :param value_tail: unused
+ :param comments: comments
+ :type comments: string
+ """
+ if not hasattr(self, 'enums_list'):
+ self.enums_list = []
+ self.meths.append('process_enums')
+ self.enums_list.append({'source': source,
+ 'template': '',
+ 'target': target,
+ 'file-head': file_head,
+ 'file-prod': file_prod,
+ 'file-tail': file_tail,
+ 'enum-prod': enum_prod,
+ 'value-head': value_head,
+ 'value-prod': value_prod,
+ 'value-tail': value_tail,
+ 'comments': comments})
+
+@before_method('process_source')
+def process_enums(self):
+ """
+ Processes the enum files stored in the attribute *enum_list* to create :py:class:`waflib.Tools.glib2.glib_mkenums` instances.
+ """
+ for enum in getattr(self, 'enums_list', []):
+ task = self.create_task('glib_mkenums')
+ env = task.env
+
+ inputs = []
+
+ # process the source
+ source_list = self.to_list(enum['source'])
+ if not source_list:
+ raise Errors.WafError('missing source ' + str(enum))
+ source_list = [self.path.find_resource(k) for k in source_list]
+ inputs += source_list
+ env.GLIB_MKENUMS_SOURCE = [k.abspath() for k in source_list]
+
+ # find the target
+ if not enum['target']:
+ raise Errors.WafError('missing target ' + str(enum))
+ tgt_node = self.path.find_or_declare(enum['target'])
+ if tgt_node.name.endswith('.c'):
+ self.source.append(tgt_node)
+ env.GLIB_MKENUMS_TARGET = tgt_node.abspath()
+
+
+ options = []
+
+ if enum['template']: # template, if provided
+ template_node = self.path.find_resource(enum['template'])
+ options.append('--template %s' % (template_node.abspath()))
+ inputs.append(template_node)
+ params = {'file-head' : '--fhead',
+ 'file-prod' : '--fprod',
+ 'file-tail' : '--ftail',
+ 'enum-prod' : '--eprod',
+ 'value-head' : '--vhead',
+ 'value-prod' : '--vprod',
+ 'value-tail' : '--vtail',
+ 'comments': '--comments'}
+ for param, option in params.items():
+ if enum[param]:
+ options.append('%s %r' % (option, enum[param]))
+
+ env.GLIB_MKENUMS_OPTIONS = ' '.join(options)
+
+ # update the task instance
+ task.set_inputs(inputs)
+ task.set_outputs(tgt_node)
+
+class glib_mkenums(Task.Task):
+ """
+ Processes enum files
+ """
+ run_str = '${GLIB_MKENUMS} ${GLIB_MKENUMS_OPTIONS} ${GLIB_MKENUMS_SOURCE} > ${GLIB_MKENUMS_TARGET}'
+ color = 'PINK'
+ ext_out = ['.h']
+
+######################################### gsettings
+
+@taskgen_method
+def add_settings_schemas(self, filename_list):
+ """
+ Adds settings files to process to *settings_schema_files*
+
+ :param filename_list: files
+ :type filename_list: list of string
+ """
+ if not hasattr(self, 'settings_schema_files'):
+ self.settings_schema_files = []
+
+ if not isinstance(filename_list, list):
+ filename_list = [filename_list]
+
+ self.settings_schema_files.extend(filename_list)
+
+@taskgen_method
+def add_settings_enums(self, namespace, filename_list):
+ """
+ Called only once by task generator to set the enums namespace.
+
+ :param namespace: namespace
+ :type namespace: string
+ :param filename_list: enum files to process
+ :type filename_list: file list
+ """
+ if hasattr(self, 'settings_enum_namespace'):
+ raise Errors.WafError("Tried to add gsettings enums to %r more than once" % self.name)
+ self.settings_enum_namespace = namespace
+
+ if not isinstance(filename_list, list):
+ filename_list = [filename_list]
+ self.settings_enum_files = filename_list
+
+@feature('glib2')
+def process_settings(self):
+ """
+ Processes the schema files in *settings_schema_files* to create :py:class:`waflib.Tools.glib2.glib_mkenums` instances. The
+ same files are validated through :py:class:`waflib.Tools.glib2.glib_validate_schema` tasks.
+
+ """
+ enums_tgt_node = []
+ install_files = []
+
+ settings_schema_files = getattr(self, 'settings_schema_files', [])
+ if settings_schema_files and not self.env.GLIB_COMPILE_SCHEMAS:
+ raise Errors.WafError ("Unable to process GSettings schemas - glib-compile-schemas was not found during configure")
+
+ # 1. process gsettings_enum_files (generate .enums.xml)
+ #
+ if hasattr(self, 'settings_enum_files'):
+ enums_task = self.create_task('glib_mkenums')
+
+ source_list = self.settings_enum_files
+ source_list = [self.path.find_resource(k) for k in source_list]
+ enums_task.set_inputs(source_list)
+ enums_task.env.GLIB_MKENUMS_SOURCE = [k.abspath() for k in source_list]
+
+ target = self.settings_enum_namespace + '.enums.xml'
+ tgt_node = self.path.find_or_declare(target)
+ enums_task.set_outputs(tgt_node)
+ enums_task.env.GLIB_MKENUMS_TARGET = tgt_node.abspath()
+ enums_tgt_node = [tgt_node]
+
+ install_files.append(tgt_node)
+
+ options = '--comments "<!-- @comment@ -->" --fhead "<schemalist>" --vhead " <@type@ id=\\"%s.@EnumName@\\">" --vprod " <value nick=\\"@valuenick@\\" value=\\"@valuenum@\\"/>" --vtail " </@type@>" --ftail "</schemalist>" ' % (self.settings_enum_namespace)
+ enums_task.env.GLIB_MKENUMS_OPTIONS = options
+
+ # 2. process gsettings_schema_files (validate .gschema.xml files)
+ #
+ for schema in settings_schema_files:
+ schema_task = self.create_task ('glib_validate_schema')
+
+ schema_node = self.path.find_resource(schema)
+ if not schema_node:
+ raise Errors.WafError("Cannot find the schema file %r" % schema)
+ install_files.append(schema_node)
+ source_list = enums_tgt_node + [schema_node]
+
+ schema_task.set_inputs (source_list)
+ schema_task.env.GLIB_COMPILE_SCHEMAS_OPTIONS = [("--schema-file=" + k.abspath()) for k in source_list]
+
+ target_node = schema_node.change_ext('.xml.valid')
+ schema_task.set_outputs (target_node)
+ schema_task.env.GLIB_VALIDATE_SCHEMA_OUTPUT = target_node.abspath()
+
+ # 3. schemas install task
+ def compile_schemas_callback(bld):
+ if not bld.is_install:
+ return
+ compile_schemas = Utils.to_list(bld.env.GLIB_COMPILE_SCHEMAS)
+ destdir = Options.options.destdir
+ paths = bld._compile_schemas_registered
+ if destdir:
+ paths = (os.path.join(destdir, path.lstrip(os.sep)) for path in paths)
+ for path in paths:
+ Logs.pprint('YELLOW', 'Updating GSettings schema cache %r' % path)
+ if self.bld.exec_command(compile_schemas + [path]):
+ Logs.warn('Could not update GSettings schema cache %r' % path)
+
+ if self.bld.is_install:
+ schemadir = self.env.GSETTINGSSCHEMADIR
+ if not schemadir:
+ raise Errors.WafError ('GSETTINGSSCHEMADIR not defined (should have been set up automatically during configure)')
+
+ if install_files:
+ self.add_install_files(install_to=schemadir, install_from=install_files)
+ registered_schemas = getattr(self.bld, '_compile_schemas_registered', None)
+ if not registered_schemas:
+ registered_schemas = self.bld._compile_schemas_registered = set()
+ self.bld.add_post_fun(compile_schemas_callback)
+ registered_schemas.add(schemadir)
+
+class glib_validate_schema(Task.Task):
+ """
+ Validates schema files
+ """
+ run_str = 'rm -f ${GLIB_VALIDATE_SCHEMA_OUTPUT} && ${GLIB_COMPILE_SCHEMAS} --dry-run ${GLIB_COMPILE_SCHEMAS_OPTIONS} && touch ${GLIB_VALIDATE_SCHEMA_OUTPUT}'
+ color = 'PINK'
+
+################## gresource
+
+@extension('.gresource.xml')
+def process_gresource_source(self, node):
+ """
+ Creates tasks that turn ``.gresource.xml`` files to C code
+ """
+ if not self.env.GLIB_COMPILE_RESOURCES:
+ raise Errors.WafError ("Unable to process GResource file - glib-compile-resources was not found during configure")
+
+ if 'gresource' in self.features:
+ return
+
+ h_node = node.change_ext('_xml.h')
+ c_node = node.change_ext('_xml.c')
+ self.create_task('glib_gresource_source', node, [h_node, c_node])
+ self.source.append(c_node)
+
+@feature('gresource')
+def process_gresource_bundle(self):
+ """
+ Creates tasks to turn ``.gresource`` files from ``.gresource.xml`` files::
+
+ def build(bld):
+ bld(
+ features='gresource',
+ source=['resources1.gresource.xml', 'resources2.gresource.xml'],
+ install_path='${LIBDIR}/${PACKAGE}'
+ )
+
+ :param source: XML files to process
+ :type source: list of string
+ :param install_path: installation path
+ :type install_path: string
+ """
+ for i in self.to_list(self.source):
+ node = self.path.find_resource(i)
+
+ task = self.create_task('glib_gresource_bundle', node, node.change_ext(''))
+ inst_to = getattr(self, 'install_path', None)
+ if inst_to:
+ self.add_install_files(install_to=inst_to, install_from=task.outputs)
+
+class glib_gresource_base(Task.Task):
+ """
+ Base class for gresource based tasks
+ """
+ color = 'BLUE'
+ base_cmd = '${GLIB_COMPILE_RESOURCES} --sourcedir=${SRC[0].parent.srcpath()} --sourcedir=${SRC[0].bld_dir()}'
+
+ def scan(self):
+ """
+ Scans gresource dependencies through ``glib-compile-resources --generate-dependencies command``
+ """
+ bld = self.generator.bld
+ kw = {}
+ kw['cwd'] = self.get_cwd()
+ kw['quiet'] = Context.BOTH
+
+ cmd = Utils.subst_vars('${GLIB_COMPILE_RESOURCES} --sourcedir=%s --sourcedir=%s --generate-dependencies %s' % (
+ self.inputs[0].parent.srcpath(),
+ self.inputs[0].bld_dir(),
+ self.inputs[0].bldpath()
+ ), self.env)
+
+ output = bld.cmd_and_log(cmd, **kw)
+
+ nodes = []
+ names = []
+ for dep in output.splitlines():
+ if dep:
+ node = bld.bldnode.find_node(dep)
+ if node:
+ nodes.append(node)
+ else:
+ names.append(dep)
+
+ return (nodes, names)
+
+class glib_gresource_source(glib_gresource_base):
+ """
+ Task to generate C source code (.h and .c files) from a gresource.xml file
+ """
+ vars = ['GLIB_COMPILE_RESOURCES']
+ fun_h = Task.compile_fun_shell(glib_gresource_base.base_cmd + ' --target=${TGT[0].abspath()} --generate-header ${SRC}')
+ fun_c = Task.compile_fun_shell(glib_gresource_base.base_cmd + ' --target=${TGT[1].abspath()} --generate-source ${SRC}')
+ ext_out = ['.h']
+
+ def run(self):
+ return self.fun_h[0](self) or self.fun_c[0](self)
+
+class glib_gresource_bundle(glib_gresource_base):
+ """
+ Task to generate a .gresource binary file from a gresource.xml file
+ """
+ run_str = glib_gresource_base.base_cmd + ' --target=${TGT} ${SRC}'
+ shell = True # temporary workaround for #795
+
+@conf
+def find_glib_genmarshal(conf):
+ conf.find_program('glib-genmarshal', var='GLIB_GENMARSHAL')
+
+@conf
+def find_glib_mkenums(conf):
+ if not conf.env.PERL:
+ conf.find_program('perl', var='PERL')
+ conf.find_program('glib-mkenums', interpreter='PERL', var='GLIB_MKENUMS')
+
+@conf
+def find_glib_compile_schemas(conf):
+ # when cross-compiling, gsettings.m4 locates the program with the following:
+ # pkg-config --variable glib_compile_schemas gio-2.0
+ conf.find_program('glib-compile-schemas', var='GLIB_COMPILE_SCHEMAS')
+
+ def getstr(varname):
+ return getattr(Options.options, varname, getattr(conf.env,varname, ''))
+
+ gsettingsschemadir = getstr('GSETTINGSSCHEMADIR')
+ if not gsettingsschemadir:
+ datadir = getstr('DATADIR')
+ if not datadir:
+ prefix = conf.env.PREFIX
+ datadir = os.path.join(prefix, 'share')
+ gsettingsschemadir = os.path.join(datadir, 'glib-2.0', 'schemas')
+
+ conf.env.GSETTINGSSCHEMADIR = gsettingsschemadir
+
+@conf
+def find_glib_compile_resources(conf):
+ conf.find_program('glib-compile-resources', var='GLIB_COMPILE_RESOURCES')
+
+def configure(conf):
+ """
+ Finds the following programs:
+
+ * *glib-genmarshal* and set *GLIB_GENMARSHAL*
+ * *glib-mkenums* and set *GLIB_MKENUMS*
+ * *glib-compile-schemas* and set *GLIB_COMPILE_SCHEMAS* (not mandatory)
+ * *glib-compile-resources* and set *GLIB_COMPILE_RESOURCES* (not mandatory)
+ """
+ conf.find_glib_genmarshal()
+ conf.find_glib_mkenums()
+ conf.find_glib_compile_schemas(mandatory=False)
+ conf.find_glib_compile_resources(mandatory=False)
+
+def options(opt):
+ """
+ Adds the ``--gsettingsschemadir`` command-line option
+ """
+ gr = opt.add_option_group('Installation directories')
+ gr.add_option('--gsettingsschemadir', help='GSettings schema location [DATADIR/glib-2.0/schemas]', default='', dest='GSETTINGSSCHEMADIR')
+
diff --git a/Tools/gnu_dirs.py b/Tools/gnu_dirs.py
new file mode 100644
index 0000000..2847071
--- /dev/null
+++ b/Tools/gnu_dirs.py
@@ -0,0 +1,131 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Ali Sabil, 2007
+
+"""
+Sets various standard variables such as INCLUDEDIR. SBINDIR and others. To use this module just call::
+
+ opt.load('gnu_dirs')
+
+and::
+
+ conf.load('gnu_dirs')
+
+Add options for the standard GNU directories, this tool will add the options
+found in autotools, and will update the environment with the following
+installation variables:
+
+============== ========================================= =======================
+Variable Description Default Value
+============== ========================================= =======================
+PREFIX installation prefix /usr/local
+EXEC_PREFIX installation prefix for binaries PREFIX
+BINDIR user commands EXEC_PREFIX/bin
+SBINDIR system binaries EXEC_PREFIX/sbin
+LIBEXECDIR program-specific binaries EXEC_PREFIX/libexec
+SYSCONFDIR host-specific configuration PREFIX/etc
+SHAREDSTATEDIR architecture-independent variable data PREFIX/com
+LOCALSTATEDIR variable data PREFIX/var
+LIBDIR object code libraries EXEC_PREFIX/lib
+INCLUDEDIR header files PREFIX/include
+OLDINCLUDEDIR header files for non-GCC compilers /usr/include
+DATAROOTDIR architecture-independent data root PREFIX/share
+DATADIR architecture-independent data DATAROOTDIR
+INFODIR GNU "info" documentation DATAROOTDIR/info
+LOCALEDIR locale-dependent data DATAROOTDIR/locale
+MANDIR manual pages DATAROOTDIR/man
+DOCDIR documentation root DATAROOTDIR/doc/APPNAME
+HTMLDIR HTML documentation DOCDIR
+DVIDIR DVI documentation DOCDIR
+PDFDIR PDF documentation DOCDIR
+PSDIR PostScript documentation DOCDIR
+============== ========================================= =======================
+"""
+
+import os, re
+from waflib import Utils, Options, Context
+
+gnuopts = '''
+bindir, user commands, ${EXEC_PREFIX}/bin
+sbindir, system binaries, ${EXEC_PREFIX}/sbin
+libexecdir, program-specific binaries, ${EXEC_PREFIX}/libexec
+sysconfdir, host-specific configuration, ${PREFIX}/etc
+sharedstatedir, architecture-independent variable data, ${PREFIX}/com
+localstatedir, variable data, ${PREFIX}/var
+libdir, object code libraries, ${EXEC_PREFIX}/lib%s
+includedir, header files, ${PREFIX}/include
+oldincludedir, header files for non-GCC compilers, /usr/include
+datarootdir, architecture-independent data root, ${PREFIX}/share
+datadir, architecture-independent data, ${DATAROOTDIR}
+infodir, GNU "info" documentation, ${DATAROOTDIR}/info
+localedir, locale-dependent data, ${DATAROOTDIR}/locale
+mandir, manual pages, ${DATAROOTDIR}/man
+docdir, documentation root, ${DATAROOTDIR}/doc/${PACKAGE}
+htmldir, HTML documentation, ${DOCDIR}
+dvidir, DVI documentation, ${DOCDIR}
+pdfdir, PDF documentation, ${DOCDIR}
+psdir, PostScript documentation, ${DOCDIR}
+''' % Utils.lib64()
+
+_options = [x.split(', ') for x in gnuopts.splitlines() if x]
+
+def configure(conf):
+ """
+ Reads the command-line options to set lots of variables in *conf.env*. The variables
+ BINDIR and LIBDIR will be overwritten.
+ """
+ def get_param(varname, default):
+ return getattr(Options.options, varname, '') or default
+
+ env = conf.env
+ env.LIBDIR = env.BINDIR = []
+ env.EXEC_PREFIX = get_param('EXEC_PREFIX', env.PREFIX)
+ env.PACKAGE = getattr(Context.g_module, 'APPNAME', None) or env.PACKAGE
+
+ complete = False
+ iter = 0
+ while not complete and iter < len(_options) + 1:
+ iter += 1
+ complete = True
+ for name, help, default in _options:
+ name = name.upper()
+ if not env[name]:
+ try:
+ env[name] = Utils.subst_vars(get_param(name, default).replace('/', os.sep), env)
+ except TypeError:
+ complete = False
+
+ if not complete:
+ lst = [x for x, _, _ in _options if not env[x.upper()]]
+ raise conf.errors.WafError('Variable substitution failure %r' % lst)
+
+def options(opt):
+ """
+ Adds lots of command-line options, for example::
+
+ --exec-prefix: EXEC_PREFIX
+ """
+ inst_dir = opt.add_option_group('Installation prefix',
+'By default, "waf install" will put the files in\
+ "/usr/local/bin", "/usr/local/lib" etc. An installation prefix other\
+ than "/usr/local" can be given using "--prefix", for example "--prefix=$HOME"')
+
+ for k in ('--prefix', '--destdir'):
+ option = opt.parser.get_option(k)
+ if option:
+ opt.parser.remove_option(k)
+ inst_dir.add_option(option)
+
+ inst_dir.add_option('--exec-prefix',
+ help = 'installation prefix for binaries [PREFIX]',
+ default = '',
+ dest = 'EXEC_PREFIX')
+
+ dirs_options = opt.add_option_group('Installation directories')
+
+ for name, help, default in _options:
+ option_name = '--' + name
+ str_default = default
+ str_help = '%s [%s]' % (help, re.sub(r'\$\{([^}]+)\}', r'\1', str_default))
+ dirs_options.add_option(option_name, help=str_help, default='', dest=name.upper())
+
diff --git a/Tools/gxx.py b/Tools/gxx.py
new file mode 100644
index 0000000..22c5d26
--- /dev/null
+++ b/Tools/gxx.py
@@ -0,0 +1,157 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2006-2018 (ita)
+# Ralf Habacker, 2006 (rh)
+# Yinon Ehrlich, 2009
+
+"""
+g++/llvm detection.
+"""
+
+from waflib.Tools import ccroot, ar
+from waflib.Configure import conf
+
+@conf
+def find_gxx(conf):
+ """
+ Finds the program g++, and if present, try to detect its version number
+ """
+ cxx = conf.find_program(['g++', 'c++'], var='CXX')
+ conf.get_cc_version(cxx, gcc=True)
+ conf.env.CXX_NAME = 'gcc'
+
+@conf
+def gxx_common_flags(conf):
+ """
+ Common flags for g++ on nearly all platforms
+ """
+ v = conf.env
+
+ v.CXX_SRC_F = []
+ v.CXX_TGT_F = ['-c', '-o']
+
+ if not v.LINK_CXX:
+ v.LINK_CXX = v.CXX
+
+ v.CXXLNK_SRC_F = []
+ v.CXXLNK_TGT_F = ['-o']
+ v.CPPPATH_ST = '-I%s'
+ v.DEFINES_ST = '-D%s'
+
+ v.LIB_ST = '-l%s' # template for adding libs
+ v.LIBPATH_ST = '-L%s' # template for adding libpaths
+ v.STLIB_ST = '-l%s'
+ v.STLIBPATH_ST = '-L%s'
+ v.RPATH_ST = '-Wl,-rpath,%s'
+
+ v.SONAME_ST = '-Wl,-h,%s'
+ v.SHLIB_MARKER = '-Wl,-Bdynamic'
+ v.STLIB_MARKER = '-Wl,-Bstatic'
+
+ v.cxxprogram_PATTERN = '%s'
+
+ v.CXXFLAGS_cxxshlib = ['-fPIC']
+ v.LINKFLAGS_cxxshlib = ['-shared']
+ v.cxxshlib_PATTERN = 'lib%s.so'
+
+ v.LINKFLAGS_cxxstlib = ['-Wl,-Bstatic']
+ v.cxxstlib_PATTERN = 'lib%s.a'
+
+ v.LINKFLAGS_MACBUNDLE = ['-bundle', '-undefined', 'dynamic_lookup']
+ v.CXXFLAGS_MACBUNDLE = ['-fPIC']
+ v.macbundle_PATTERN = '%s.bundle'
+
+@conf
+def gxx_modifier_win32(conf):
+ """Configuration flags for executing gcc on Windows"""
+ v = conf.env
+ v.cxxprogram_PATTERN = '%s.exe'
+
+ v.cxxshlib_PATTERN = '%s.dll'
+ v.implib_PATTERN = '%s.dll.a'
+ v.IMPLIB_ST = '-Wl,--out-implib,%s'
+
+ v.CXXFLAGS_cxxshlib = []
+
+ # Auto-import is enabled by default even without this option,
+ # but enabling it explicitly has the nice effect of suppressing the rather boring, debug-level messages
+ # that the linker emits otherwise.
+ v.append_value('LINKFLAGS', ['-Wl,--enable-auto-import'])
+
+@conf
+def gxx_modifier_cygwin(conf):
+ """Configuration flags for executing g++ on Cygwin"""
+ gxx_modifier_win32(conf)
+ v = conf.env
+ v.cxxshlib_PATTERN = 'cyg%s.dll'
+ v.append_value('LINKFLAGS_cxxshlib', ['-Wl,--enable-auto-image-base'])
+ v.CXXFLAGS_cxxshlib = []
+
+@conf
+def gxx_modifier_darwin(conf):
+ """Configuration flags for executing g++ on MacOS"""
+ v = conf.env
+ v.CXXFLAGS_cxxshlib = ['-fPIC']
+ v.LINKFLAGS_cxxshlib = ['-dynamiclib']
+ v.cxxshlib_PATTERN = 'lib%s.dylib'
+ v.FRAMEWORKPATH_ST = '-F%s'
+ v.FRAMEWORK_ST = ['-framework']
+ v.ARCH_ST = ['-arch']
+
+ v.LINKFLAGS_cxxstlib = []
+
+ v.SHLIB_MARKER = []
+ v.STLIB_MARKER = []
+ v.SONAME_ST = []
+
+@conf
+def gxx_modifier_aix(conf):
+ """Configuration flags for executing g++ on AIX"""
+ v = conf.env
+ v.LINKFLAGS_cxxprogram= ['-Wl,-brtl']
+
+ v.LINKFLAGS_cxxshlib = ['-shared', '-Wl,-brtl,-bexpfull']
+ v.SHLIB_MARKER = []
+
+@conf
+def gxx_modifier_hpux(conf):
+ v = conf.env
+ v.SHLIB_MARKER = []
+ v.STLIB_MARKER = []
+ v.CFLAGS_cxxshlib = ['-fPIC','-DPIC']
+ v.cxxshlib_PATTERN = 'lib%s.sl'
+
+@conf
+def gxx_modifier_openbsd(conf):
+ conf.env.SONAME_ST = []
+
+@conf
+def gcc_modifier_osf1V(conf):
+ v = conf.env
+ v.SHLIB_MARKER = []
+ v.STLIB_MARKER = []
+ v.SONAME_ST = []
+
+@conf
+def gxx_modifier_platform(conf):
+ """Execute platform-specific functions based on *gxx_modifier_+NAME*"""
+ # * set configurations specific for a platform.
+ # * the destination platform is detected automatically by looking at the macros the compiler predefines,
+ # and if it's not recognised, it fallbacks to sys.platform.
+ gxx_modifier_func = getattr(conf, 'gxx_modifier_' + conf.env.DEST_OS, None)
+ if gxx_modifier_func:
+ gxx_modifier_func()
+
+def configure(conf):
+ """
+ Configuration for g++
+ """
+ conf.find_gxx()
+ conf.find_ar()
+ conf.gxx_common_flags()
+ conf.gxx_modifier_platform()
+ conf.cxx_load_tools()
+ conf.cxx_add_flags()
+ conf.link_add_flags()
+ conf.check_gcc_o_space('cxx')
+
diff --git a/Tools/icc.py b/Tools/icc.py
new file mode 100644
index 0000000..b6492c8
--- /dev/null
+++ b/Tools/icc.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Stian Selnes 2008
+# Thomas Nagy 2009-2018 (ita)
+
+"""
+Detects the Intel C compiler
+"""
+
+import sys
+from waflib.Tools import ccroot, ar, gcc
+from waflib.Configure import conf
+
+@conf
+def find_icc(conf):
+ """
+ Finds the program icc and execute it to ensure it really is icc
+ """
+ cc = conf.find_program(['icc', 'ICL'], var='CC')
+ conf.get_cc_version(cc, icc=True)
+ conf.env.CC_NAME = 'icc'
+
+def configure(conf):
+ conf.find_icc()
+ conf.find_ar()
+ conf.gcc_common_flags()
+ conf.gcc_modifier_platform()
+ conf.cc_load_tools()
+ conf.cc_add_flags()
+ conf.link_add_flags()
diff --git a/Tools/icpc.py b/Tools/icpc.py
new file mode 100644
index 0000000..8a6cc6c
--- /dev/null
+++ b/Tools/icpc.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy 2009-2018 (ita)
+
+"""
+Detects the Intel C++ compiler
+"""
+
+import sys
+from waflib.Tools import ccroot, ar, gxx
+from waflib.Configure import conf
+
+@conf
+def find_icpc(conf):
+ """
+ Finds the program icpc, and execute it to ensure it really is icpc
+ """
+ cxx = conf.find_program('icpc', var='CXX')
+ conf.get_cc_version(cxx, icc=True)
+ conf.env.CXX_NAME = 'icc'
+
+def configure(conf):
+ conf.find_icpc()
+ conf.find_ar()
+ conf.gxx_common_flags()
+ conf.gxx_modifier_platform()
+ conf.cxx_load_tools()
+ conf.cxx_add_flags()
+ conf.link_add_flags()
+
diff --git a/Tools/ifort.py b/Tools/ifort.py
new file mode 100644
index 0000000..17d3052
--- /dev/null
+++ b/Tools/ifort.py
@@ -0,0 +1,413 @@
+#! /usr/bin/env python
+# encoding: utf-8
+# DC 2008
+# Thomas Nagy 2016-2018 (ita)
+
+import os, re, traceback
+from waflib import Utils, Logs, Errors
+from waflib.Tools import fc, fc_config, fc_scan, ar, ccroot
+from waflib.Configure import conf
+from waflib.TaskGen import after_method, feature
+
+@conf
+def find_ifort(conf):
+ fc = conf.find_program('ifort', var='FC')
+ conf.get_ifort_version(fc)
+ conf.env.FC_NAME = 'IFORT'
+
+@conf
+def ifort_modifier_win32(self):
+ v = self.env
+ v.IFORT_WIN32 = True
+ v.FCSTLIB_MARKER = ''
+ v.FCSHLIB_MARKER = ''
+
+ v.FCLIB_ST = v.FCSTLIB_ST = '%s.lib'
+ v.FCLIBPATH_ST = v.STLIBPATH_ST = '/LIBPATH:%s'
+ v.FCINCPATH_ST = '/I%s'
+ v.FCDEFINES_ST = '/D%s'
+
+ v.fcprogram_PATTERN = v.fcprogram_test_PATTERN = '%s.exe'
+ v.fcshlib_PATTERN = '%s.dll'
+ v.fcstlib_PATTERN = v.implib_PATTERN = '%s.lib'
+
+ v.FCLNK_TGT_F = '/out:'
+ v.FC_TGT_F = ['/c', '/o', '']
+ v.FCFLAGS_fcshlib = ''
+ v.LINKFLAGS_fcshlib = '/DLL'
+ v.AR_TGT_F = '/out:'
+ v.IMPLIB_ST = '/IMPLIB:%s'
+
+ v.append_value('LINKFLAGS', '/subsystem:console')
+ if v.IFORT_MANIFEST:
+ v.append_value('LINKFLAGS', ['/MANIFEST'])
+
+@conf
+def ifort_modifier_darwin(conf):
+ fc_config.fortran_modifier_darwin(conf)
+
+@conf
+def ifort_modifier_platform(conf):
+ dest_os = conf.env.DEST_OS or Utils.unversioned_sys_platform()
+ ifort_modifier_func = getattr(conf, 'ifort_modifier_' + dest_os, None)
+ if ifort_modifier_func:
+ ifort_modifier_func()
+
+@conf
+def get_ifort_version(conf, fc):
+ """
+ Detects the compiler version and sets ``conf.env.FC_VERSION``
+ """
+ version_re = re.compile(r"\bIntel\b.*\bVersion\s*(?P<major>\d*)\.(?P<minor>\d*)",re.I).search
+ if Utils.is_win32:
+ cmd = fc
+ else:
+ cmd = fc + ['-logo']
+
+ out, err = fc_config.getoutput(conf, cmd, stdin=False)
+ match = version_re(out) or version_re(err)
+ if not match:
+ conf.fatal('cannot determine ifort version.')
+ k = match.groupdict()
+ conf.env.FC_VERSION = (k['major'], k['minor'])
+
+def configure(conf):
+ """
+ Detects the Intel Fortran compilers
+ """
+ if Utils.is_win32:
+ compiler, version, path, includes, libdirs, arch = conf.detect_ifort()
+ v = conf.env
+ v.DEST_CPU = arch
+ v.PATH = path
+ v.INCLUDES = includes
+ v.LIBPATH = libdirs
+ v.MSVC_COMPILER = compiler
+ try:
+ v.MSVC_VERSION = float(version)
+ except ValueError:
+ v.MSVC_VERSION = float(version[:-3])
+
+ conf.find_ifort_win32()
+ conf.ifort_modifier_win32()
+ else:
+ conf.find_ifort()
+ conf.find_program('xiar', var='AR')
+ conf.find_ar()
+ conf.fc_flags()
+ conf.fc_add_flags()
+ conf.ifort_modifier_platform()
+
+
+all_ifort_platforms = [ ('intel64', 'amd64'), ('em64t', 'amd64'), ('ia32', 'x86'), ('Itanium', 'ia64')]
+"""List of icl platforms"""
+
+@conf
+def gather_ifort_versions(conf, versions):
+ """
+ List compiler versions by looking up registry keys
+ """
+ version_pattern = re.compile(r'^...?.?\....?.?')
+ try:
+ all_versions = Utils.winreg.OpenKey(Utils.winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Wow6432node\\Intel\\Compilers\\Fortran')
+ except OSError:
+ try:
+ all_versions = Utils.winreg.OpenKey(Utils.winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Intel\\Compilers\\Fortran')
+ except OSError:
+ return
+ index = 0
+ while 1:
+ try:
+ version = Utils.winreg.EnumKey(all_versions, index)
+ except OSError:
+ break
+ index += 1
+ if not version_pattern.match(version):
+ continue
+ targets = {}
+ for target,arch in all_ifort_platforms:
+ if target=='intel64':
+ targetDir='EM64T_NATIVE'
+ else:
+ targetDir=target
+ try:
+ Utils.winreg.OpenKey(all_versions,version+'\\'+targetDir)
+ icl_version=Utils.winreg.OpenKey(all_versions,version)
+ path,type=Utils.winreg.QueryValueEx(icl_version,'ProductDir')
+ except OSError:
+ pass
+ else:
+ batch_file=os.path.join(path,'bin','ifortvars.bat')
+ if os.path.isfile(batch_file):
+ targets[target] = target_compiler(conf, 'intel', arch, version, target, batch_file)
+
+ for target,arch in all_ifort_platforms:
+ try:
+ icl_version = Utils.winreg.OpenKey(all_versions, version+'\\'+target)
+ path,type = Utils.winreg.QueryValueEx(icl_version,'ProductDir')
+ except OSError:
+ continue
+ else:
+ batch_file=os.path.join(path,'bin','ifortvars.bat')
+ if os.path.isfile(batch_file):
+ targets[target] = target_compiler(conf, 'intel', arch, version, target, batch_file)
+ major = version[0:2]
+ versions['intel ' + major] = targets
+
+@conf
+def setup_ifort(conf, versiondict):
+ """
+ Checks installed compilers and targets and returns the first combination from the user's
+ options, env, or the global supported lists that checks.
+
+ :param versiondict: dict(platform -> dict(architecture -> configuration))
+ :type versiondict: dict(string -> dict(string -> target_compiler)
+ :return: the compiler, revision, path, include dirs, library paths and target architecture
+ :rtype: tuple of strings
+ """
+ platforms = Utils.to_list(conf.env.MSVC_TARGETS) or [i for i,j in all_ifort_platforms]
+ desired_versions = conf.env.MSVC_VERSIONS or list(reversed(list(versiondict.keys())))
+ for version in desired_versions:
+ try:
+ targets = versiondict[version]
+ except KeyError:
+ continue
+ for arch in platforms:
+ try:
+ cfg = targets[arch]
+ except KeyError:
+ continue
+ cfg.evaluate()
+ if cfg.is_valid:
+ compiler,revision = version.rsplit(' ', 1)
+ return compiler,revision,cfg.bindirs,cfg.incdirs,cfg.libdirs,cfg.cpu
+ conf.fatal('ifort: Impossible to find a valid architecture for building %r - %r' % (desired_versions, list(versiondict.keys())))
+
+@conf
+def get_ifort_version_win32(conf, compiler, version, target, vcvars):
+ # FIXME hack
+ try:
+ conf.msvc_cnt += 1
+ except AttributeError:
+ conf.msvc_cnt = 1
+ batfile = conf.bldnode.make_node('waf-print-msvc-%d.bat' % conf.msvc_cnt)
+ batfile.write("""@echo off
+set INCLUDE=
+set LIB=
+call "%s" %s
+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()])
+ batfile.delete()
+ lines = sout.splitlines()
+
+ if not lines[0]:
+ lines.pop(0)
+
+ MSVC_PATH = MSVC_INCDIR = MSVC_LIBDIR = None
+ for line in lines:
+ if line.startswith('PATH='):
+ path = line[5:]
+ MSVC_PATH = path.split(';')
+ elif line.startswith('INCLUDE='):
+ MSVC_INCDIR = [i for i in line[8:].split(';') if i]
+ elif line.startswith('LIB='):
+ MSVC_LIBDIR = [i for i in line[4:].split(';') if i]
+ if None in (MSVC_PATH, MSVC_INCDIR, MSVC_LIBDIR):
+ conf.fatal('ifort: Could not find a valid architecture for building (get_ifort_version_win32)')
+
+ # Check if the compiler is usable at all.
+ # The detection may return 64-bit versions even on 32-bit systems, and these would fail to run.
+ env = dict(os.environ)
+ env.update(PATH = path)
+ compiler_name, linker_name, lib_name = _get_prog_names(conf, compiler)
+ fc = conf.find_program(compiler_name, path_list=MSVC_PATH)
+
+ # delete CL if exists. because it could contain parameters which can change cl's behaviour rather catastrophically.
+ if 'CL' in env:
+ del(env['CL'])
+
+ try:
+ conf.cmd_and_log(fc + ['/help'], env=env)
+ except UnicodeError:
+ st = traceback.format_exc()
+ if conf.logger:
+ conf.logger.error(st)
+ conf.fatal('ifort: Unicode error - check the code page?')
+ except Exception as e:
+ Logs.debug('ifort: get_ifort_version: %r %r %r -> failure %s', compiler, version, target, str(e))
+ conf.fatal('ifort: cannot run the compiler in get_ifort_version (run with -v to display errors)')
+ else:
+ Logs.debug('ifort: get_ifort_version: %r %r %r -> OK', compiler, version, target)
+ finally:
+ conf.env[compiler_name] = ''
+
+ return (MSVC_PATH, MSVC_INCDIR, MSVC_LIBDIR)
+
+class target_compiler(object):
+ """
+ Wraps a compiler configuration; call evaluate() to determine
+ whether the configuration is usable.
+ """
+ def __init__(self, ctx, compiler, cpu, version, bat_target, bat, callback=None):
+ """
+ :param ctx: configuration context to use to eventually get the version environment
+ :param compiler: compiler name
+ :param cpu: target cpu
+ :param version: compiler version number
+ :param bat_target: ?
+ :param bat: path to the batch file to run
+ :param callback: optional function to take the realized environment variables tup and map it (e.g. to combine other constant paths)
+ """
+ self.conf = ctx
+ self.name = None
+ self.is_valid = False
+ self.is_done = False
+
+ self.compiler = compiler
+ self.cpu = cpu
+ self.version = version
+ self.bat_target = bat_target
+ self.bat = bat
+ self.callback = callback
+
+ def evaluate(self):
+ if self.is_done:
+ return
+ self.is_done = True
+ try:
+ vs = self.conf.get_ifort_version_win32(self.compiler, self.version, self.bat_target, self.bat)
+ except Errors.ConfigurationError:
+ self.is_valid = False
+ return
+ if self.callback:
+ vs = self.callback(self, vs)
+ self.is_valid = True
+ (self.bindirs, self.incdirs, self.libdirs) = vs
+
+ def __str__(self):
+ return str((self.bindirs, self.incdirs, self.libdirs))
+
+ def __repr__(self):
+ return repr((self.bindirs, self.incdirs, self.libdirs))
+
+@conf
+def detect_ifort(self):
+ return self.setup_ifort(self.get_ifort_versions(False))
+
+@conf
+def get_ifort_versions(self, eval_and_save=True):
+ """
+ :return: platforms to compiler configurations
+ :rtype: dict
+ """
+ dct = {}
+ self.gather_ifort_versions(dct)
+ return dct
+
+def _get_prog_names(self, compiler):
+ if compiler=='intel':
+ compiler_name = 'ifort'
+ linker_name = 'XILINK'
+ lib_name = 'XILIB'
+ else:
+ # assumes CL.exe
+ compiler_name = 'CL'
+ linker_name = 'LINK'
+ lib_name = 'LIB'
+ return compiler_name, linker_name, lib_name
+
+@conf
+def find_ifort_win32(conf):
+ # the autodetection is supposed to be performed before entering in this method
+ v = conf.env
+ path = v.PATH
+ compiler = v.MSVC_COMPILER
+ version = v.MSVC_VERSION
+
+ compiler_name, linker_name, lib_name = _get_prog_names(conf, compiler)
+ v.IFORT_MANIFEST = (compiler == 'intel' and version >= 11)
+
+ # compiler
+ fc = conf.find_program(compiler_name, var='FC', path_list=path)
+
+ # before setting anything, check if the compiler is really intel fortran
+ env = dict(conf.environ)
+ if path:
+ env.update(PATH = ';'.join(path))
+ if not conf.cmd_and_log(fc + ['/nologo', '/help'], env=env):
+ conf.fatal('not intel fortran compiler could not be identified')
+
+ v.FC_NAME = 'IFORT'
+
+ if not v.LINK_FC:
+ conf.find_program(linker_name, var='LINK_FC', path_list=path, mandatory=True)
+
+ if not v.AR:
+ conf.find_program(lib_name, path_list=path, var='AR', mandatory=True)
+ v.ARFLAGS = ['/nologo']
+
+ # manifest tool. Not required for VS 2003 and below. Must have for VS 2005 and later
+ if v.IFORT_MANIFEST:
+ conf.find_program('MT', path_list=path, var='MT')
+ v.MTFLAGS = ['/nologo']
+
+ try:
+ conf.load('winres')
+ except Errors.WafError:
+ Logs.warn('Resource compiler not found. Compiling resource file is disabled')
+
+#######################################################################################################
+##### conf above, build below
+
+@after_method('apply_link')
+@feature('fc')
+def apply_flags_ifort(self):
+ """
+ Adds additional flags implied by msvc, such as subsystems and pdb files::
+
+ def build(bld):
+ bld.stlib(source='main.c', target='bar', subsystem='gruik')
+ """
+ if not self.env.IFORT_WIN32 or not getattr(self, 'link_task', None):
+ return
+
+ is_static = isinstance(self.link_task, ccroot.stlink_task)
+
+ subsystem = getattr(self, 'subsystem', '')
+ if subsystem:
+ subsystem = '/subsystem:%s' % subsystem
+ flags = is_static and 'ARFLAGS' or 'LINKFLAGS'
+ self.env.append_value(flags, subsystem)
+
+ if not is_static:
+ for f in self.env.LINKFLAGS:
+ d = f.lower()
+ if d[1:] == 'debug':
+ pdbnode = self.link_task.outputs[0].change_ext('.pdb')
+ self.link_task.outputs.append(pdbnode)
+
+ if getattr(self, 'install_task', None):
+ self.pdb_install_task = self.add_install_files(install_to=self.install_task.install_to, install_from=pdbnode)
+
+ break
+
+@feature('fcprogram', 'fcshlib', 'fcprogram_test')
+@after_method('apply_link')
+def apply_manifest_ifort(self):
+ """
+ Enables manifest embedding in Fortran DLLs when using ifort on Windows
+ See: http://msdn2.microsoft.com/en-us/library/ms235542(VS.80).aspx
+ """
+ if self.env.IFORT_WIN32 and getattr(self, 'link_task', None):
+ # it seems ifort.exe cannot be called for linking
+ self.link_task.env.FC = self.env.LINK_FC
+
+ if self.env.IFORT_WIN32 and self.env.IFORT_MANIFEST and getattr(self, 'link_task', None):
+ out_node = self.link_task.outputs[0]
+ man_node = out_node.parent.find_or_declare(out_node.name + '.manifest')
+ self.link_task.outputs.append(man_node)
+ self.env.DO_MANIFEST = True
+
diff --git a/Tools/intltool.py b/Tools/intltool.py
new file mode 100644
index 0000000..af95ba8
--- /dev/null
+++ b/Tools/intltool.py
@@ -0,0 +1,231 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2006-2018 (ita)
+
+"""
+Support for translation tools such as msgfmt and intltool
+
+Usage::
+
+ def configure(conf):
+ conf.load('gnu_dirs intltool')
+
+ def build(bld):
+ # process the .po files into .gmo files, and install them in LOCALEDIR
+ bld(features='intltool_po', appname='myapp', podir='po', install_path="${LOCALEDIR}")
+
+ # process an input file, substituting the translations from the po dir
+ bld(
+ features = "intltool_in",
+ podir = "../po",
+ style = "desktop",
+ flags = ["-u"],
+ source = 'kupfer.desktop.in',
+ install_path = "${DATADIR}/applications",
+ )
+
+Usage of the :py:mod:`waflib.Tools.gnu_dirs` is recommended, but not obligatory.
+"""
+
+from __future__ import with_statement
+
+import os, re
+from waflib import Context, Task, Utils, Logs
+import waflib.Tools.ccroot
+from waflib.TaskGen import feature, before_method, taskgen_method
+from waflib.Logs import error
+from waflib.Configure import conf
+
+_style_flags = {
+ 'ba': '-b',
+ 'desktop': '-d',
+ 'keys': '-k',
+ 'quoted': '--quoted-style',
+ 'quotedxml': '--quotedxml-style',
+ 'rfc822deb': '-r',
+ 'schemas': '-s',
+ 'xml': '-x',
+}
+
+@taskgen_method
+def ensure_localedir(self):
+ """
+ Expands LOCALEDIR from DATAROOTDIR/locale if possible, or falls back to PREFIX/share/locale
+ """
+ # use the tool gnu_dirs to provide options to define this
+ if not self.env.LOCALEDIR:
+ if self.env.DATAROOTDIR:
+ self.env.LOCALEDIR = os.path.join(self.env.DATAROOTDIR, 'locale')
+ else:
+ self.env.LOCALEDIR = os.path.join(self.env.PREFIX, 'share', 'locale')
+
+@before_method('process_source')
+@feature('intltool_in')
+def apply_intltool_in_f(self):
+ """
+ Creates tasks to translate files by intltool-merge::
+
+ def build(bld):
+ bld(
+ features = "intltool_in",
+ podir = "../po",
+ style = "desktop",
+ flags = ["-u"],
+ source = 'kupfer.desktop.in',
+ install_path = "${DATADIR}/applications",
+ )
+
+ :param podir: location of the .po files
+ :type podir: string
+ :param source: source files to process
+ :type source: list of string
+ :param style: the intltool-merge mode of operation, can be one of the following values:
+ ``ba``, ``desktop``, ``keys``, ``quoted``, ``quotedxml``, ``rfc822deb``, ``schemas`` and ``xml``.
+ See the ``intltool-merge`` man page for more information about supported modes of operation.
+ :type style: string
+ :param flags: compilation flags ("-quc" by default)
+ :type flags: list of string
+ :param install_path: installation path
+ :type install_path: string
+ """
+ try:
+ self.meths.remove('process_source')
+ except ValueError:
+ pass
+
+ self.ensure_localedir()
+
+ podir = getattr(self, 'podir', '.')
+ podirnode = self.path.find_dir(podir)
+ if not podirnode:
+ error("could not find the podir %r" % podir)
+ return
+
+ cache = getattr(self, 'intlcache', '.intlcache')
+ self.env.INTLCACHE = [os.path.join(str(self.path.get_bld()), podir, cache)]
+ self.env.INTLPODIR = podirnode.bldpath()
+ self.env.append_value('INTLFLAGS', getattr(self, 'flags', self.env.INTLFLAGS_DEFAULT))
+
+ if '-c' in self.env.INTLFLAGS:
+ self.bld.fatal('Redundant -c flag in intltool task %r' % self)
+
+ style = getattr(self, 'style', None)
+ if style:
+ try:
+ style_flag = _style_flags[style]
+ except KeyError:
+ self.bld.fatal('intltool_in style "%s" is not valid' % style)
+
+ self.env.append_unique('INTLFLAGS', [style_flag])
+
+ for i in self.to_list(self.source):
+ node = self.path.find_resource(i)
+
+ task = self.create_task('intltool', node, node.change_ext(''))
+ inst = getattr(self, 'install_path', None)
+ if inst:
+ self.add_install_files(install_to=inst, install_from=task.outputs)
+
+@feature('intltool_po')
+def apply_intltool_po(self):
+ """
+ Creates tasks to process po files::
+
+ def build(bld):
+ bld(features='intltool_po', appname='myapp', podir='po', install_path="${LOCALEDIR}")
+
+ The relevant task generator arguments are:
+
+ :param podir: directory of the .po files
+ :type podir: string
+ :param appname: name of the application
+ :type appname: string
+ :param install_path: installation directory
+ :type install_path: string
+
+ The file LINGUAS must be present in the directory pointed by *podir* and list the translation files to process.
+ """
+ try:
+ self.meths.remove('process_source')
+ except ValueError:
+ pass
+
+ self.ensure_localedir()
+
+ appname = getattr(self, 'appname', getattr(Context.g_module, Context.APPNAME, 'set_your_app_name'))
+ podir = getattr(self, 'podir', '.')
+ inst = getattr(self, 'install_path', '${LOCALEDIR}')
+
+ linguas = self.path.find_node(os.path.join(podir, 'LINGUAS'))
+ if linguas:
+ # scan LINGUAS file for locales to process
+ with open(linguas.abspath()) as f:
+ langs = []
+ for line in f.readlines():
+ # ignore lines containing comments
+ if not line.startswith('#'):
+ langs += line.split()
+ re_linguas = re.compile('[-a-zA-Z_@.]+')
+ for lang in langs:
+ # Make sure that we only process lines which contain locales
+ if re_linguas.match(lang):
+ node = self.path.find_resource(os.path.join(podir, re_linguas.match(lang).group() + '.po'))
+ task = self.create_task('po', node, node.change_ext('.mo'))
+
+ if inst:
+ filename = task.outputs[0].name
+ (langname, ext) = os.path.splitext(filename)
+ inst_file = inst + os.sep + langname + os.sep + 'LC_MESSAGES' + os.sep + appname + '.mo'
+ self.add_install_as(install_to=inst_file, install_from=task.outputs[0],
+ chmod=getattr(self, 'chmod', Utils.O644))
+
+ else:
+ Logs.pprint('RED', "Error no LINGUAS file found in po directory")
+
+class po(Task.Task):
+ """
+ Compiles .po files into .gmo files
+ """
+ run_str = '${MSGFMT} -o ${TGT} ${SRC}'
+ color = 'BLUE'
+
+class intltool(Task.Task):
+ """
+ Calls intltool-merge to update translation files
+ """
+ run_str = '${INTLTOOL} ${INTLFLAGS} ${INTLCACHE_ST:INTLCACHE} ${INTLPODIR} ${SRC} ${TGT}'
+ color = 'BLUE'
+
+@conf
+def find_msgfmt(conf):
+ """
+ Detects msgfmt and sets the ``MSGFMT`` variable
+ """
+ conf.find_program('msgfmt', var='MSGFMT')
+
+@conf
+def find_intltool_merge(conf):
+ """
+ Detects intltool-merge
+ """
+ if not conf.env.PERL:
+ conf.find_program('perl', var='PERL')
+ conf.env.INTLCACHE_ST = '--cache=%s'
+ conf.env.INTLFLAGS_DEFAULT = ['-q', '-u']
+ conf.find_program('intltool-merge', interpreter='PERL', var='INTLTOOL')
+
+def configure(conf):
+ """
+ Detects the program *msgfmt* and set *conf.env.MSGFMT*.
+ Detects the program *intltool-merge* and set *conf.env.INTLTOOL*.
+ It is possible to set INTLTOOL in the environment, but it must not have spaces in it::
+
+ $ INTLTOOL="/path/to/the program/intltool" waf configure
+
+ If a C/C++ compiler is present, execute a compilation test to find the header *locale.h*.
+ """
+ conf.find_msgfmt()
+ conf.find_intltool_merge()
+ if conf.env.CC or conf.env.CXX:
+ conf.check(header_name='locale.h')
+
diff --git a/Tools/irixcc.py b/Tools/irixcc.py
new file mode 100644
index 0000000..c3ae1ac
--- /dev/null
+++ b/Tools/irixcc.py
@@ -0,0 +1,66 @@
+#! /usr/bin/env python
+# encoding: utf-8
+# imported from samba
+
+"""
+Compiler definition for irix/MIPSpro cc compiler
+"""
+
+from waflib import Errors
+from waflib.Tools import ccroot, ar
+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')
+
+ 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
+def irixcc_common_flags(conf):
+ v = conf.env
+
+ v.CC_SRC_F = ''
+ v.CC_TGT_F = ['-c', '-o']
+ v.CPPPATH_ST = '-I%s'
+ v.DEFINES_ST = '-D%s'
+
+ if not v.LINK_CC:
+ v.LINK_CC = v.CC
+
+ v.CCLNK_SRC_F = ''
+ v.CCLNK_TGT_F = ['-o']
+
+ v.LIB_ST = '-l%s' # template for adding libs
+ v.LIBPATH_ST = '-L%s' # template for adding libpaths
+ v.STLIB_ST = '-l%s'
+ v.STLIBPATH_ST = '-L%s'
+
+ v.cprogram_PATTERN = '%s'
+ v.cshlib_PATTERN = 'lib%s.so'
+ v.cstlib_PATTERN = 'lib%s.a'
+
+def configure(conf):
+ conf.find_irixcc()
+ conf.find_cpp()
+ conf.find_ar()
+ conf.irixcc_common_flags()
+ conf.cc_load_tools()
+ conf.cc_add_flags()
+ conf.link_add_flags()
+
diff --git a/Tools/javaw.py b/Tools/javaw.py
new file mode 100644
index 0000000..9daed39
--- /dev/null
+++ b/Tools/javaw.py
@@ -0,0 +1,579 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2006-2018 (ita)
+
+"""
+Java support
+
+Javac is one of the few compilers that behaves very badly:
+
+#. it outputs files where it wants to (-d is only for the package root)
+
+#. it recompiles files silently behind your back
+
+#. it outputs an undefined amount of files (inner classes)
+
+Remember that the compilation can be performed using Jython[1] rather than regular Python. Instead of
+running one of the following commands::
+
+ ./waf configure
+ python waf configure
+
+You would have to run::
+
+ java -jar /path/to/jython.jar waf configure
+
+[1] http://www.jython.org/
+
+Usage
+=====
+
+Load the "java" tool.
+
+def configure(conf):
+ conf.load('java')
+
+Java tools will be autodetected and eventually, if present, the quite
+standard JAVA_HOME environment variable will be used. The also standard
+CLASSPATH variable is used for library searching.
+
+In configuration phase checks can be done on the system environment, for
+example to check if a class is known in the classpath::
+
+ conf.check_java_class('java.io.FileOutputStream')
+
+or if the system supports JNI applications building::
+
+ conf.check_jni_headers()
+
+
+The java tool supports compiling java code, creating jar files and
+creating javadoc documentation. This can be either done separately or
+together in a single definition. For example to manage them separately::
+
+ bld(features = 'javac',
+ srcdir = 'src',
+ compat = '1.7',
+ use = 'animals',
+ name = 'cats-src',
+ )
+
+ bld(features = 'jar',
+ basedir = '.',
+ destfile = '../cats.jar',
+ name = 'cats',
+ use = 'cats-src'
+ )
+
+
+Or together by defining all the needed attributes::
+
+ bld(features = 'javac jar javadoc',
+ srcdir = 'src/', # folder containing the sources to compile
+ outdir = 'src', # folder where to output the classes (in the build directory)
+ compat = '1.6', # java compatibility version number
+ classpath = ['.', '..'],
+
+ # jar
+ basedir = 'src', # folder containing the classes and other files to package (must match outdir)
+ destfile = 'foo.jar', # do not put the destfile in the folder of the java classes!
+ use = 'NNN',
+ jaropts = ['-C', 'default/src/', '.'], # can be used to give files
+ manifest = 'src/Manifest.mf', # Manifest file to include
+
+ # javadoc
+ javadoc_package = ['com.meow' , 'com.meow.truc.bar', 'com.meow.truc.foo'],
+ javadoc_output = 'javadoc',
+ )
+
+External jar dependencies can be mapped to a standard waf "use" dependency by
+setting an environment variable with a CLASSPATH prefix in the configuration,
+for example::
+
+ conf.env.CLASSPATH_NNN = ['aaaa.jar', 'bbbb.jar']
+
+and then NNN can be freely used in rules as::
+
+ use = 'NNN',
+
+In the java tool the dependencies via use are not transitive by default, as
+this necessity depends on the code. To enable recursive dependency scanning
+use on a specific rule:
+
+ recurse_use = True
+
+Or build-wise by setting RECURSE_JAVA:
+
+ bld.env.RECURSE_JAVA = True
+
+Unit tests can be integrated in the waf unit test environment using the javatest extra.
+"""
+
+import os, shutil
+from waflib import Task, Utils, Errors, Node
+from waflib.Configure import conf
+from waflib.TaskGen import feature, before_method, after_method, taskgen_method
+
+from waflib.Tools import ccroot
+ccroot.USELIB_VARS['javac'] = set(['CLASSPATH', 'JAVACFLAGS'])
+
+SOURCE_RE = '**/*.java'
+JAR_RE = '**/*'
+
+class_check_source = '''
+public class Test {
+ public static void main(String[] argv) {
+ Class lib;
+ if (argv.length < 1) {
+ System.err.println("Missing argument");
+ System.exit(77);
+ }
+ try {
+ lib = Class.forName(argv[0]);
+ } catch (ClassNotFoundException e) {
+ System.err.println("ClassNotFoundException");
+ System.exit(1);
+ }
+ lib = null;
+ System.exit(0);
+ }
+}
+'''
+
+@feature('javac')
+@before_method('process_source')
+def apply_java(self):
+ """
+ Create a javac task for compiling *.java files*. There can be
+ only one javac task by task generator.
+ """
+ Utils.def_attrs(self, jarname='', classpath='',
+ sourcepath='.', srcdir='.',
+ jar_mf_attributes={}, jar_mf_classpath=[])
+
+ outdir = getattr(self, 'outdir', None)
+ if outdir:
+ if not isinstance(outdir, Node.Node):
+ outdir = self.path.get_bld().make_node(self.outdir)
+ else:
+ outdir = self.path.get_bld()
+ outdir.mkdir()
+ self.outdir = outdir
+ self.env.OUTDIR = outdir.abspath()
+
+ self.javac_task = tsk = self.create_task('javac')
+ tmp = []
+
+ srcdir = getattr(self, 'srcdir', '')
+ if isinstance(srcdir, Node.Node):
+ srcdir = [srcdir]
+ for x in Utils.to_list(srcdir):
+ if isinstance(x, Node.Node):
+ y = x
+ else:
+ y = self.path.find_dir(x)
+ if not y:
+ self.bld.fatal('Could not find the folder %s from %s' % (x, self.path))
+ tmp.append(y)
+
+ tsk.srcdir = tmp
+
+ if getattr(self, 'compat', None):
+ tsk.env.append_value('JAVACFLAGS', ['-source', str(self.compat)])
+
+ if hasattr(self, 'sourcepath'):
+ fold = [isinstance(x, Node.Node) and x or self.path.find_dir(x) for x in self.to_list(self.sourcepath)]
+ names = os.pathsep.join([x.srcpath() for x in fold])
+ else:
+ names = [x.srcpath() for x in tsk.srcdir]
+
+ if names:
+ tsk.env.append_value('JAVACFLAGS', ['-sourcepath', names])
+
+
+@taskgen_method
+def java_use_rec(self, name, **kw):
+ """
+ Processes recursively the *use* attribute for each referred java compilation
+ """
+ if name in self.tmp_use_seen:
+ return
+
+ self.tmp_use_seen.append(name)
+
+ try:
+ y = self.bld.get_tgen_by_name(name)
+ except Errors.WafError:
+ self.uselib.append(name)
+ return
+ else:
+ y.post()
+ # Add generated JAR name for CLASSPATH. Task ordering (set_run_after)
+ # is already guaranteed by ordering done between the single tasks
+ if hasattr(y, 'jar_task'):
+ self.use_lst.append(y.jar_task.outputs[0].abspath())
+
+ for x in self.to_list(getattr(y, 'use', [])):
+ self.java_use_rec(x)
+
+@feature('javac')
+@before_method('propagate_uselib_vars')
+@after_method('apply_java')
+def use_javac_files(self):
+ """
+ Processes the *use* attribute referring to other java compilations
+ """
+ self.use_lst = []
+ self.tmp_use_seen = []
+ self.uselib = self.to_list(getattr(self, 'uselib', []))
+ names = self.to_list(getattr(self, 'use', []))
+ get = self.bld.get_tgen_by_name
+ for x in names:
+ try:
+ y = get(x)
+ except Errors.WafError:
+ self.uselib.append(x)
+ else:
+ y.post()
+ if hasattr(y, 'jar_task'):
+ self.use_lst.append(y.jar_task.outputs[0].abspath())
+ self.javac_task.set_run_after(y.jar_task)
+ else:
+ for tsk in y.tasks:
+ self.javac_task.set_run_after(tsk)
+
+ # If recurse use scan is enabled recursively add use attribute for each used one
+ if getattr(self, 'recurse_use', False) or self.bld.env.RECURSE_JAVA:
+ self.java_use_rec(x)
+
+ self.env.append_value('CLASSPATH', self.use_lst)
+
+@feature('javac')
+@after_method('apply_java', 'propagate_uselib_vars', 'use_javac_files')
+def set_classpath(self):
+ """
+ Sets the CLASSPATH value on the *javac* task previously created.
+ """
+ if getattr(self, 'classpath', None):
+ self.env.append_unique('CLASSPATH', getattr(self, 'classpath', []))
+ for x in self.tasks:
+ x.env.CLASSPATH = os.pathsep.join(self.env.CLASSPATH) + os.pathsep
+
+@feature('jar')
+@after_method('apply_java', 'use_javac_files')
+@before_method('process_source')
+def jar_files(self):
+ """
+ Creates a jar task (one maximum per task generator)
+ """
+ destfile = getattr(self, 'destfile', 'test.jar')
+ jaropts = getattr(self, 'jaropts', [])
+ manifest = getattr(self, 'manifest', None)
+
+ basedir = getattr(self, 'basedir', None)
+ if basedir:
+ if not isinstance(self.basedir, Node.Node):
+ basedir = self.path.get_bld().make_node(basedir)
+ else:
+ basedir = self.path.get_bld()
+ if not basedir:
+ self.bld.fatal('Could not find the basedir %r for %r' % (self.basedir, self))
+
+ self.jar_task = tsk = self.create_task('jar_create')
+ if manifest:
+ jarcreate = getattr(self, 'jarcreate', 'cfm')
+ if not isinstance(manifest,Node.Node):
+ node = self.path.find_resource(manifest)
+ else:
+ node = manifest
+ if not node:
+ self.bld.fatal('invalid manifest file %r for %r' % (manifest, self))
+ tsk.dep_nodes.append(node)
+ jaropts.insert(0, node.abspath())
+ else:
+ jarcreate = getattr(self, 'jarcreate', 'cf')
+ if not isinstance(destfile, Node.Node):
+ destfile = self.path.find_or_declare(destfile)
+ if not destfile:
+ self.bld.fatal('invalid destfile %r for %r' % (destfile, self))
+ tsk.set_outputs(destfile)
+ tsk.basedir = basedir
+
+ jaropts.append('-C')
+ jaropts.append(basedir.bldpath())
+ jaropts.append('.')
+
+ tsk.env.JAROPTS = jaropts
+ tsk.env.JARCREATE = jarcreate
+
+ if getattr(self, 'javac_task', None):
+ tsk.set_run_after(self.javac_task)
+
+@feature('jar')
+@after_method('jar_files')
+def use_jar_files(self):
+ """
+ Processes the *use* attribute to set the build order on the
+ tasks created by another task generator.
+ """
+ self.uselib = self.to_list(getattr(self, 'uselib', []))
+ names = self.to_list(getattr(self, 'use', []))
+ get = self.bld.get_tgen_by_name
+ for x in names:
+ try:
+ y = get(x)
+ except Errors.WafError:
+ self.uselib.append(x)
+ else:
+ y.post()
+ self.jar_task.run_after.update(y.tasks)
+
+class JTask(Task.Task):
+ """
+ Base class for java and jar tasks; provides functionality to run long commands
+ """
+ def split_argfile(self, cmd):
+ inline = [cmd[0]]
+ infile = []
+ for x in cmd[1:]:
+ # jar and javac do not want -J flags in @file
+ if x.startswith('-J'):
+ inline.append(x)
+ else:
+ infile.append(self.quote_flag(x))
+ return (inline, infile)
+
+class jar_create(JTask):
+ """
+ Creates a jar file
+ """
+ color = 'GREEN'
+ run_str = '${JAR} ${JARCREATE} ${TGT} ${JAROPTS}'
+
+ def runnable_status(self):
+ """
+ Wait for dependent tasks to be executed, then read the
+ files to update the list of inputs.
+ """
+ for t in self.run_after:
+ if not t.hasrun:
+ return Task.ASK_LATER
+ if not self.inputs:
+ try:
+ self.inputs = [x for x in self.basedir.ant_glob(JAR_RE, remove=False, quiet=True) if id(x) != id(self.outputs[0])]
+ except Exception:
+ raise Errors.WafError('Could not find the basedir %r for %r' % (self.basedir, self))
+ return super(jar_create, self).runnable_status()
+
+class javac(JTask):
+ """
+ Compiles java files
+ """
+ color = 'BLUE'
+ run_str = '${JAVAC} -classpath ${CLASSPATH} -d ${OUTDIR} ${JAVACFLAGS} ${SRC}'
+ vars = ['CLASSPATH', 'JAVACFLAGS', 'JAVAC', 'OUTDIR']
+ """
+ The javac task will be executed again if the variables CLASSPATH, JAVACFLAGS, JAVAC or OUTDIR change.
+ """
+ def uid(self):
+ """Identify java tasks by input&output folder"""
+ lst = [self.__class__.__name__, self.generator.outdir.abspath()]
+ for x in self.srcdir:
+ lst.append(x.abspath())
+ return Utils.h_list(lst)
+
+ def runnable_status(self):
+ """
+ Waits for dependent tasks to be complete, then read the file system to find the input nodes.
+ """
+ for t in self.run_after:
+ if not t.hasrun:
+ return Task.ASK_LATER
+
+ if not self.inputs:
+ self.inputs = []
+ for x in self.srcdir:
+ if x.exists():
+ self.inputs.extend(x.ant_glob(SOURCE_RE, remove=False, quiet=True))
+ return super(javac, self).runnable_status()
+
+ def post_run(self):
+ """
+ List class files created
+ """
+ for node in self.generator.outdir.ant_glob('**/*.class', quiet=True):
+ self.generator.bld.node_sigs[node] = self.uid()
+ self.generator.bld.task_sigs[self.uid()] = self.cache_sig
+
+@feature('javadoc')
+@after_method('process_rule')
+def create_javadoc(self):
+ """
+ Creates a javadoc task (feature 'javadoc')
+ """
+ tsk = self.create_task('javadoc')
+ tsk.classpath = getattr(self, 'classpath', [])
+ self.javadoc_package = Utils.to_list(self.javadoc_package)
+ if not isinstance(self.javadoc_output, Node.Node):
+ self.javadoc_output = self.bld.path.find_or_declare(self.javadoc_output)
+
+class javadoc(Task.Task):
+ """
+ Builds java documentation
+ """
+ color = 'BLUE'
+
+ def __str__(self):
+ return '%s: %s -> %s\n' % (self.__class__.__name__, self.generator.srcdir, self.generator.javadoc_output)
+
+ def run(self):
+ env = self.env
+ bld = self.generator.bld
+ wd = bld.bldnode
+
+ #add src node + bld node (for generated java code)
+ srcpath = self.generator.path.abspath() + os.sep + self.generator.srcdir
+ srcpath += os.pathsep
+ srcpath += self.generator.path.get_bld().abspath() + os.sep + self.generator.srcdir
+
+ classpath = env.CLASSPATH
+ classpath += os.pathsep
+ classpath += os.pathsep.join(self.classpath)
+ classpath = "".join(classpath)
+
+ self.last_cmd = lst = []
+ lst.extend(Utils.to_list(env.JAVADOC))
+ lst.extend(['-d', self.generator.javadoc_output.abspath()])
+ lst.extend(['-sourcepath', srcpath])
+ lst.extend(['-classpath', classpath])
+ lst.extend(['-subpackages'])
+ lst.extend(self.generator.javadoc_package)
+ lst = [x for x in lst if x]
+
+ self.generator.bld.cmd_and_log(lst, cwd=wd, env=env.env or None, quiet=0)
+
+ def post_run(self):
+ nodes = self.generator.javadoc_output.ant_glob('**', quiet=True)
+ for node in nodes:
+ self.generator.bld.node_sigs[node] = self.uid()
+ self.generator.bld.task_sigs[self.uid()] = self.cache_sig
+
+def configure(self):
+ """
+ Detects the javac, java and jar programs
+ """
+ # If JAVA_PATH is set, we prepend it to the path list
+ java_path = self.environ['PATH'].split(os.pathsep)
+ v = self.env
+
+ if 'JAVA_HOME' in self.environ:
+ java_path = [os.path.join(self.environ['JAVA_HOME'], 'bin')] + java_path
+ self.env.JAVA_HOME = [self.environ['JAVA_HOME']]
+
+ for x in 'javac java jar javadoc'.split():
+ self.find_program(x, var=x.upper(), path_list=java_path)
+
+ if 'CLASSPATH' in self.environ:
+ v.CLASSPATH = self.environ['CLASSPATH']
+
+ if not v.JAR:
+ self.fatal('jar is required for making java packages')
+ if not v.JAVAC:
+ self.fatal('javac is required for compiling java classes')
+
+ v.JARCREATE = 'cf' # can use cvf
+ v.JAVACFLAGS = []
+
+@conf
+def check_java_class(self, classname, with_classpath=None):
+ """
+ Checks if the specified java class exists
+
+ :param classname: class to check, like java.util.HashMap
+ :type classname: string
+ :param with_classpath: additional classpath to give
+ :type with_classpath: string
+ """
+ javatestdir = '.waf-javatest'
+
+ classpath = javatestdir
+ if self.env.CLASSPATH:
+ classpath += os.pathsep + self.env.CLASSPATH
+ if isinstance(with_classpath, str):
+ classpath += os.pathsep + with_classpath
+
+ shutil.rmtree(javatestdir, True)
+ os.mkdir(javatestdir)
+
+ Utils.writef(os.path.join(javatestdir, 'Test.java'), class_check_source)
+
+ # Compile the source
+ self.exec_command(self.env.JAVAC + [os.path.join(javatestdir, 'Test.java')], shell=False)
+
+ # Try to run the app
+ cmd = self.env.JAVA + ['-cp', classpath, 'Test', classname]
+ self.to_log("%s\n" % str(cmd))
+ found = self.exec_command(cmd, shell=False)
+
+ self.msg('Checking for java class %s' % classname, not found)
+
+ shutil.rmtree(javatestdir, True)
+
+ return found
+
+@conf
+def check_jni_headers(conf):
+ """
+ Checks for jni headers and libraries. On success the conf.env variables xxx_JAVA are added for use in C/C++ targets::
+
+ def options(opt):
+ opt.load('compiler_c')
+
+ def configure(conf):
+ conf.load('compiler_c java')
+ conf.check_jni_headers()
+
+ def build(bld):
+ bld.shlib(source='a.c', target='app', use='JAVA')
+ """
+ if not conf.env.CC_NAME and not conf.env.CXX_NAME:
+ conf.fatal('load a compiler first (gcc, g++, ..)')
+
+ if not conf.env.JAVA_HOME:
+ conf.fatal('set JAVA_HOME in the system environment')
+
+ # jni requires the jvm
+ javaHome = conf.env.JAVA_HOME[0]
+
+ dir = conf.root.find_dir(conf.env.JAVA_HOME[0] + '/include')
+ if dir is None:
+ dir = conf.root.find_dir(conf.env.JAVA_HOME[0] + '/../Headers') # think different?!
+ if dir is None:
+ conf.fatal('JAVA_HOME does not seem to be set properly')
+
+ f = dir.ant_glob('**/(jni|jni_md).h')
+ incDirs = [x.parent.abspath() for x in f]
+
+ dir = conf.root.find_dir(conf.env.JAVA_HOME[0])
+ f = dir.ant_glob('**/*jvm.(so|dll|dylib)')
+ libDirs = [x.parent.abspath() for x in f] or [javaHome]
+
+ # On windows, we need both the .dll and .lib to link. On my JDK, they are
+ # in different directories...
+ f = dir.ant_glob('**/*jvm.(lib)')
+ if f:
+ libDirs = [[x, y.parent.abspath()] for x in libDirs for y in f]
+
+ if conf.env.DEST_OS == 'freebsd':
+ conf.env.append_unique('LINKFLAGS_JAVA', '-pthread')
+ for d in libDirs:
+ try:
+ conf.check(header_name='jni.h', define_name='HAVE_JNI_H', lib='jvm',
+ libpath=d, includes=incDirs, uselib_store='JAVA', uselib='JAVA')
+ except Exception:
+ pass
+ else:
+ break
+ else:
+ conf.fatal('could not find lib jvm in %r (see config.log)' % libDirs)
+
diff --git a/Tools/ldc2.py b/Tools/ldc2.py
new file mode 100644
index 0000000..a51c344
--- /dev/null
+++ b/Tools/ldc2.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Alex Rønne Petersen, 2012 (alexrp/Zor)
+
+from waflib.Tools import ar, d
+from waflib.Configure import conf
+
+@conf
+def find_ldc2(conf):
+ """
+ Finds the program *ldc2* and set the variable *D*
+ """
+ conf.find_program(['ldc2'], var='D')
+
+ out = conf.cmd_and_log(conf.env.D + ['-version'])
+ if out.find("based on DMD v2.") == -1:
+ conf.fatal("detected compiler is not ldc2")
+
+@conf
+def common_flags_ldc2(conf):
+ """
+ Sets the D flags required by *ldc2*
+ """
+ v = conf.env
+
+ v.D_SRC_F = ['-c']
+ v.D_TGT_F = '-of%s'
+
+ v.D_LINKER = v.D
+ v.DLNK_SRC_F = ''
+ v.DLNK_TGT_F = '-of%s'
+ v.DINC_ST = '-I%s'
+
+ v.DSHLIB_MARKER = v.DSTLIB_MARKER = ''
+ v.DSTLIB_ST = v.DSHLIB_ST = '-L-l%s'
+ v.DSTLIBPATH_ST = v.DLIBPATH_ST = '-L-L%s'
+
+ v.LINKFLAGS_dshlib = ['-L-shared']
+
+ v.DHEADER_ext = '.di'
+ v.DFLAGS_d_with_header = ['-H', '-Hf']
+ v.D_HDR_F = '%s'
+
+ v.LINKFLAGS = []
+ v.DFLAGS_dshlib = ['-relocation-model=pic']
+
+def configure(conf):
+ """
+ Configuration for *ldc2*
+ """
+ conf.find_ldc2()
+ conf.load('ar')
+ conf.load('d')
+ conf.common_flags_ldc2()
+ conf.d_platform_flags()
+
diff --git a/Tools/lua.py b/Tools/lua.py
new file mode 100644
index 0000000..15a333a
--- /dev/null
+++ b/Tools/lua.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Sebastian Schlingmann, 2008
+# Thomas Nagy, 2008-2018 (ita)
+
+"""
+Lua support.
+
+Compile *.lua* files into *.luac*::
+
+ def configure(conf):
+ conf.load('lua')
+ conf.env.LUADIR = '/usr/local/share/myapp/scripts/'
+ def build(bld):
+ bld(source='foo.lua')
+"""
+
+from waflib.TaskGen import extension
+from waflib import Task
+
+@extension('.lua')
+def add_lua(self, node):
+ tsk = self.create_task('luac', node, node.change_ext('.luac'))
+ inst_to = getattr(self, 'install_path', self.env.LUADIR and '${LUADIR}' or None)
+ if inst_to:
+ self.add_install_files(install_to=inst_to, install_from=tsk.outputs)
+ return tsk
+
+class luac(Task.Task):
+ run_str = '${LUAC} -s -o ${TGT} ${SRC}'
+ color = 'PINK'
+
+def configure(conf):
+ """
+ Detect the luac compiler and set *conf.env.LUAC*
+ """
+ conf.find_program('luac', var='LUAC')
+
diff --git a/Tools/md5_tstamp.py b/Tools/md5_tstamp.py
new file mode 100644
index 0000000..2a58792
--- /dev/null
+++ b/Tools/md5_tstamp.py
@@ -0,0 +1,38 @@
+#! /usr/bin/env python
+# encoding: utf-8
+
+"""
+Re-calculate md5 hashes of files only when the file time have changed.
+
+The hashes can also reflect either the file contents (STRONGEST=True) or the
+file time and file size.
+
+The performance benefits of this module are usually insignificant.
+"""
+
+import os, stat
+from waflib import Utils, Build, Node
+
+STRONGEST = True
+
+Build.SAVED_ATTRS.append('hashes_md5_tstamp')
+def h_file(self):
+ filename = self.abspath()
+ st = os.stat(filename)
+
+ cache = self.ctx.hashes_md5_tstamp
+ if filename in cache and cache[filename][0] == st.st_mtime:
+ return cache[filename][1]
+
+ if STRONGEST:
+ ret = Utils.h_file(filename)
+ else:
+ if stat.S_ISDIR(st[stat.ST_MODE]):
+ raise IOError('Not a file')
+ ret = Utils.md5(str((st.st_mtime, st.st_size)).encode()).digest()
+
+ cache[filename] = (st.st_mtime, ret)
+ return ret
+h_file.__doc__ = Node.Node.h_file.__doc__
+Node.Node.h_file = h_file
+
diff --git a/Tools/msvc.py b/Tools/msvc.py
new file mode 100644
index 0000000..ff58449
--- /dev/null
+++ b/Tools/msvc.py
@@ -0,0 +1,1020 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Carlos Rafael Giani, 2006 (dv)
+# Tamas Pal, 2007 (folti)
+# Nicolas Mercier, 2009
+# Matt Clarkson, 2012
+
+"""
+Microsoft Visual C++/Intel C++ compiler support
+
+If you get detection problems, first try any of the following::
+
+ chcp 65001
+ set PYTHONIOENCODING=...
+ set PYTHONLEGACYWINDOWSSTDIO=1
+
+Usage::
+
+ $ waf configure --msvc_version="msvc 10.0,msvc 9.0" --msvc_target="x64"
+
+or::
+
+ def configure(conf):
+ conf.env.MSVC_VERSIONS = ['msvc 10.0', 'msvc 9.0', 'msvc 8.0', 'msvc 7.1', 'msvc 7.0', 'msvc 6.0', 'wsdk 7.0', 'intel 11', 'PocketPC 9.0', 'Smartphone 8.0']
+ conf.env.MSVC_TARGETS = ['x64']
+ conf.load('msvc')
+
+or::
+
+ def configure(conf):
+ conf.load('msvc', funs='no_autodetect')
+ conf.check_lib_msvc('gdi32')
+ conf.check_libs_msvc('kernel32 user32')
+ def build(bld):
+ tg = bld.program(source='main.c', target='app', use='KERNEL32 USER32 GDI32')
+
+Platforms and targets will be tested in the order they appear;
+the first good configuration will be used.
+
+To force testing all the configurations that are not used, use the ``--no-msvc-lazy`` option
+or set ``conf.env.MSVC_LAZY_AUTODETECT=False``.
+
+Supported platforms: ia64, x64, x86, x86_amd64, x86_ia64, x86_arm, amd64_x86, amd64_arm
+
+Compilers supported:
+
+* msvc => Visual Studio, versions 6.0 (VC 98, VC .NET 2002) to 15 (Visual Studio 2017)
+* wsdk => Windows SDK, versions 6.0, 6.1, 7.0, 7.1, 8.0
+* icl => Intel compiler, versions 9, 10, 11, 13
+* winphone => Visual Studio to target Windows Phone 8 native (version 8.0 for now)
+* Smartphone => Compiler/SDK for Smartphone devices (armv4/v4i)
+* PocketPC => Compiler/SDK for PocketPC devices (armv4/v4i)
+
+To use WAF in a VS2008 Make file project (see http://code.google.com/p/waf/issues/detail?id=894)
+You may consider to set the environment variable "VS_UNICODE_OUTPUT" to nothing before calling waf.
+So in your project settings use something like 'cmd.exe /C "set VS_UNICODE_OUTPUT=& set PYTHONUNBUFFERED=true & waf build"'.
+cmd.exe /C "chcp 1252 & set PYTHONUNBUFFERED=true && set && waf configure"
+Setting PYTHONUNBUFFERED gives the unbuffered output.
+"""
+
+import os, sys, re, traceback
+from waflib import Utils, Logs, Options, Errors
+from waflib.TaskGen import after_method, feature
+
+from waflib.Configure import conf
+from waflib.Tools import ccroot, c, cxx, ar
+
+g_msvc_systemlibs = '''
+aclui activeds ad1 adptif adsiid advapi32 asycfilt authz bhsupp bits bufferoverflowu cabinet
+cap certadm certidl ciuuid clusapi comctl32 comdlg32 comsupp comsuppd comsuppw comsuppwd comsvcs
+credui crypt32 cryptnet cryptui d3d8thk daouuid dbgeng dbghelp dciman32 ddao35 ddao35d
+ddao35u ddao35ud delayimp dhcpcsvc dhcpsapi dlcapi dnsapi dsprop dsuiext dtchelp
+faultrep fcachdll fci fdi framedyd framedyn gdi32 gdiplus glauxglu32 gpedit gpmuuid
+gtrts32w gtrtst32hlink htmlhelp httpapi icm32 icmui imagehlp imm32 iphlpapi iprop
+kernel32 ksguid ksproxy ksuser libcmt libcmtd libcpmt libcpmtd loadperf lz32 mapi
+mapi32 mgmtapi minidump mmc mobsync mpr mprapi mqoa mqrt msacm32 mscms mscoree
+msdasc msimg32 msrating mstask msvcmrt msvcurt msvcurtd mswsock msxml2 mtx mtxdm
+netapi32 nmapinmsupp npptools ntdsapi ntdsbcli ntmsapi ntquery odbc32 odbcbcp
+odbccp32 oldnames ole32 oleacc oleaut32 oledb oledlgolepro32 opends60 opengl32
+osptk parser pdh penter pgobootrun pgort powrprof psapi ptrustm ptrustmd ptrustu
+ptrustud qosname rasapi32 rasdlg rassapi resutils riched20 rpcndr rpcns4 rpcrt4 rtm
+rtutils runtmchk scarddlg scrnsave scrnsavw secur32 sensapi setupapi sfc shell32
+shfolder shlwapi sisbkup snmpapi sporder srclient sti strsafe svcguid tapi32 thunk32
+traffic unicows url urlmon user32 userenv usp10 uuid uxtheme vcomp vcompd vdmdbg
+version vfw32 wbemuuid webpost wiaguid wininet winmm winscard winspool winstrm
+wintrust wldap32 wmiutils wow32 ws2_32 wsnmp32 wsock32 wst wtsapi32 xaswitch xolehlp
+'''.split()
+"""importlibs provided by MSVC/Platform SDK. Do NOT search them"""
+
+all_msvc_platforms = [ ('x64', 'amd64'), ('x86', 'x86'), ('ia64', 'ia64'),
+ ('x86_amd64', 'amd64'), ('x86_ia64', 'ia64'), ('x86_arm', 'arm'), ('x86_arm64', 'arm64'),
+ ('amd64_x86', 'x86'), ('amd64_arm', 'arm'), ('amd64_arm64', 'arm64') ]
+"""List of msvc platforms"""
+
+all_wince_platforms = [ ('armv4', 'arm'), ('armv4i', 'arm'), ('mipsii', 'mips'), ('mipsii_fp', 'mips'), ('mipsiv', 'mips'), ('mipsiv_fp', 'mips'), ('sh4', 'sh'), ('x86', 'cex86') ]
+"""List of wince platforms"""
+
+all_icl_platforms = [ ('intel64', 'amd64'), ('em64t', 'amd64'), ('ia32', 'x86'), ('Itanium', 'ia64')]
+"""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='')
+ 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')
+
+@conf
+def setup_msvc(conf, versiondict):
+ """
+ Checks installed compilers and targets and returns the first combination from the user's
+ options, env, or the global supported lists that checks.
+
+ :param versiondict: dict(platform -> dict(architecture -> configuration))
+ :type versiondict: dict(string -> dict(string -> target_compiler)
+ :return: the compiler, revision, path, include dirs, library paths and target architecture
+ :rtype: tuple of strings
+ """
+ platforms = getattr(Options.options, 'msvc_targets', '').split(',')
+ if platforms == ['']:
+ 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())))
+
+ # Override lazy detection by evaluating after the fact.
+ lazy_detect = getattr(Options.options, 'msvc_lazy', True)
+ if conf.env.MSVC_LAZY_AUTODETECT is False:
+ lazy_detect = False
+
+ if not lazy_detect:
+ for val in versiondict.values():
+ for arch in list(val.keys()):
+ cfg = val[arch]
+ cfg.evaluate()
+ if not cfg.is_valid:
+ del val[arch]
+ conf.env.MSVC_INSTALLED_VERSIONS = versiondict
+
+ for version in desired_versions:
+ Logs.debug('msvc: detecting %r - %r', version, desired_versions)
+ try:
+ targets = versiondict[version]
+ except KeyError:
+ continue
+
+ seen = set()
+ for arch in platforms:
+ if arch in seen:
+ continue
+ else:
+ seen.add(arch)
+ try:
+ cfg = targets[arch]
+ except KeyError:
+ continue
+
+ cfg.evaluate()
+ if cfg.is_valid:
+ compiler,revision = version.rsplit(' ', 1)
+ return compiler,revision,cfg.bindirs,cfg.incdirs,cfg.libdirs,cfg.cpu
+ conf.fatal('msvc: Impossible to find a valid architecture for building %r - %r' % (desired_versions, list(versiondict.keys())))
+
+@conf
+def get_msvc_version(conf, compiler, version, target, vcvars):
+ """
+ Checks that an installed compiler actually runs and uses vcvars to obtain the
+ environment needed by the compiler.
+
+ :param compiler: compiler type, for looking up the executable name
+ :param version: compiler version, for debugging only
+ :param target: target architecture
+ :param vcvars: batch file to run to check the environment
+ :return: the location of the compiler executable, the location of include dirs, and the library paths
+ :rtype: tuple of strings
+ """
+ Logs.debug('msvc: get_msvc_version: %r %r %r', compiler, version, target)
+
+ try:
+ conf.msvc_cnt += 1
+ except AttributeError:
+ conf.msvc_cnt = 1
+ batfile = conf.bldnode.make_node('waf-print-msvc-%d.bat' % conf.msvc_cnt)
+ batfile.write("""@echo off
+set INCLUDE=
+set LIB=
+call "%s" %s
+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()])
+ lines = sout.splitlines()
+
+ if not lines[0]:
+ lines.pop(0)
+
+ MSVC_PATH = MSVC_INCDIR = MSVC_LIBDIR = None
+ for line in lines:
+ if line.startswith('PATH='):
+ path = line[5:]
+ MSVC_PATH = path.split(';')
+ elif line.startswith('INCLUDE='):
+ MSVC_INCDIR = [i for i in line[8:].split(';') if i]
+ elif line.startswith('LIB='):
+ MSVC_LIBDIR = [i for i in line[4:].split(';') if i]
+ if None in (MSVC_PATH, MSVC_INCDIR, MSVC_LIBDIR):
+ conf.fatal('msvc: Could not find a valid architecture for building (get_msvc_version_3)')
+
+ # Check if the compiler is usable at all.
+ # The detection may return 64-bit versions even on 32-bit systems, and these would fail to run.
+ env = dict(os.environ)
+ env.update(PATH = path)
+ compiler_name, linker_name, lib_name = _get_prog_names(conf, compiler)
+ cxx = conf.find_program(compiler_name, path_list=MSVC_PATH)
+
+ # delete CL if exists. because it could contain parameters which can change cl's behaviour rather catastrophically.
+ if 'CL' in env:
+ del(env['CL'])
+
+ try:
+ conf.cmd_and_log(cxx + ['/help'], env=env)
+ except UnicodeError:
+ st = traceback.format_exc()
+ if conf.logger:
+ conf.logger.error(st)
+ conf.fatal('msvc: Unicode error - check the code page?')
+ except Exception as e:
+ Logs.debug('msvc: get_msvc_version: %r %r %r -> failure %s', compiler, version, target, str(e))
+ conf.fatal('msvc: cannot run the compiler in get_msvc_version (run with -v to display errors)')
+ else:
+ Logs.debug('msvc: get_msvc_version: %r %r %r -> OK', compiler, version, target)
+ finally:
+ conf.env[compiler_name] = ''
+
+ return (MSVC_PATH, MSVC_INCDIR, MSVC_LIBDIR)
+
+def gather_wince_supported_platforms():
+ """
+ Checks SmartPhones SDKs
+
+ :param versions: list to modify
+ :type versions: list
+ """
+ supported_wince_platforms = []
+ try:
+ ce_sdk = Utils.winreg.OpenKey(Utils.winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Wow6432node\\Microsoft\\Windows CE Tools\\SDKs')
+ except OSError:
+ try:
+ ce_sdk = Utils.winreg.OpenKey(Utils.winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Microsoft\\Windows CE Tools\\SDKs')
+ except OSError:
+ ce_sdk = ''
+ if not ce_sdk:
+ return supported_wince_platforms
+
+ index = 0
+ while 1:
+ try:
+ sdk_device = Utils.winreg.EnumKey(ce_sdk, index)
+ sdk = Utils.winreg.OpenKey(ce_sdk, sdk_device)
+ except OSError:
+ break
+ index += 1
+ try:
+ path,type = Utils.winreg.QueryValueEx(sdk, 'SDKRootDir')
+ except OSError:
+ try:
+ path,type = Utils.winreg.QueryValueEx(sdk,'SDKInformation')
+ except OSError:
+ continue
+ path,xml = os.path.split(path)
+ path = str(path)
+ path,device = os.path.split(path)
+ if not device:
+ path,device = os.path.split(path)
+ platforms = []
+ for arch,compiler in all_wince_platforms:
+ if os.path.isdir(os.path.join(path, device, 'Lib', arch)):
+ platforms.append((arch, compiler, os.path.join(path, device, 'Include', arch), os.path.join(path, device, 'Lib', arch)))
+ if platforms:
+ supported_wince_platforms.append((device, platforms))
+ return supported_wince_platforms
+
+def gather_msvc_detected_versions():
+ #Detected MSVC versions!
+ version_pattern = re.compile(r'^(\d\d?\.\d\d?)(Exp)?$')
+ detected_versions = []
+ for vcver,vcvar in (('VCExpress','Exp'), ('VisualStudio','')):
+ prefix = 'SOFTWARE\\Wow6432node\\Microsoft\\' + vcver
+ try:
+ all_versions = Utils.winreg.OpenKey(Utils.winreg.HKEY_LOCAL_MACHINE, prefix)
+ except OSError:
+ prefix = 'SOFTWARE\\Microsoft\\' + vcver
+ try:
+ all_versions = Utils.winreg.OpenKey(Utils.winreg.HKEY_LOCAL_MACHINE, prefix)
+ except OSError:
+ continue
+
+ index = 0
+ while 1:
+ try:
+ version = Utils.winreg.EnumKey(all_versions, index)
+ except OSError:
+ break
+ index += 1
+ match = version_pattern.match(version)
+ if match:
+ versionnumber = float(match.group(1))
+ else:
+ continue
+ detected_versions.append((versionnumber, version+vcvar, prefix+'\\'+version))
+ def fun(tup):
+ return tup[0]
+
+ detected_versions.sort(key = fun)
+ return detected_versions
+
+class target_compiler(object):
+ """
+ Wrap a compiler configuration; call evaluate() to determine
+ whether the configuration is usable.
+ """
+ def __init__(self, ctx, compiler, cpu, version, bat_target, bat, callback=None):
+ """
+ :param ctx: configuration context to use to eventually get the version environment
+ :param compiler: compiler name
+ :param cpu: target cpu
+ :param version: compiler version number
+ :param bat_target: ?
+ :param bat: path to the batch file to run
+ """
+ self.conf = ctx
+ self.name = None
+ self.is_valid = False
+ self.is_done = False
+
+ self.compiler = compiler
+ self.cpu = cpu
+ self.version = version
+ self.bat_target = bat_target
+ self.bat = bat
+ self.callback = callback
+
+ def evaluate(self):
+ if self.is_done:
+ return
+ self.is_done = True
+ try:
+ vs = self.conf.get_msvc_version(self.compiler, self.version, self.bat_target, self.bat)
+ except Errors.ConfigurationError:
+ self.is_valid = False
+ return
+ if self.callback:
+ vs = self.callback(self, vs)
+ self.is_valid = True
+ (self.bindirs, self.incdirs, self.libdirs) = vs
+
+ def __str__(self):
+ return str((self.compiler, self.cpu, self.version, self.bat_target, self.bat))
+
+ def __repr__(self):
+ return repr((self.compiler, self.cpu, self.version, self.bat_target, self.bat))
+
+@conf
+def gather_wsdk_versions(conf, versions):
+ """
+ Use winreg to add the msvc versions to the input list
+
+ :param versions: list to modify
+ :type versions: list
+ """
+ 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:
+ try:
+ all_versions = Utils.winreg.OpenKey(Utils.winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows')
+ except OSError:
+ return
+ index = 0
+ while 1:
+ try:
+ version = Utils.winreg.EnumKey(all_versions, index)
+ except OSError:
+ break
+ index += 1
+ if not version_pattern.match(version):
+ continue
+ try:
+ msvc_version = Utils.winreg.OpenKey(all_versions, version)
+ path,type = Utils.winreg.QueryValueEx(msvc_version,'InstallationFolder')
+ except OSError:
+ continue
+ if path and os.path.isfile(os.path.join(path, 'bin', 'SetEnv.cmd')):
+ targets = {}
+ for target,arch in all_msvc_platforms:
+ targets[target] = target_compiler(conf, 'wsdk', arch, version, '/'+target, os.path.join(path, 'bin', 'SetEnv.cmd'))
+ versions['wsdk ' + version[1:]] = targets
+
+@conf
+def gather_msvc_targets(conf, versions, version, vc_path):
+ #Looking for normal MSVC compilers!
+ targets = {}
+
+ if os.path.isfile(os.path.join(vc_path, 'VC', 'Auxiliary', 'Build', 'vcvarsall.bat')):
+ for target,realtarget in all_msvc_platforms[::-1]:
+ targets[target] = target_compiler(conf, 'msvc', realtarget, version, target, os.path.join(vc_path, 'VC', 'Auxiliary', 'Build', 'vcvarsall.bat'))
+ elif os.path.isfile(os.path.join(vc_path, 'vcvarsall.bat')):
+ for target,realtarget in all_msvc_platforms[::-1]:
+ targets[target] = target_compiler(conf, 'msvc', realtarget, version, target, os.path.join(vc_path, 'vcvarsall.bat'))
+ elif os.path.isfile(os.path.join(vc_path, 'Common7', 'Tools', 'vsvars32.bat')):
+ targets['x86'] = target_compiler(conf, 'msvc', 'x86', version, 'x86', os.path.join(vc_path, 'Common7', 'Tools', 'vsvars32.bat'))
+ elif os.path.isfile(os.path.join(vc_path, 'Bin', 'vcvars32.bat')):
+ targets['x86'] = target_compiler(conf, 'msvc', 'x86', version, '', os.path.join(vc_path, 'Bin', 'vcvars32.bat'))
+ if targets:
+ versions['msvc %s' % version] = targets
+
+@conf
+def gather_wince_targets(conf, versions, version, vc_path, vsvars, supported_platforms):
+ #Looking for Win CE compilers!
+ for device,platforms in supported_platforms:
+ targets = {}
+ for platform,compiler,include,lib in platforms:
+ winCEpath = os.path.join(vc_path, 'ce')
+ if not os.path.isdir(winCEpath):
+ continue
+
+ if os.path.isdir(os.path.join(winCEpath, 'lib', platform)):
+ bindirs = [os.path.join(winCEpath, 'bin', compiler), os.path.join(winCEpath, 'bin', 'x86_'+compiler)]
+ incdirs = [os.path.join(winCEpath, 'include'), os.path.join(winCEpath, 'atlmfc', 'include'), include]
+ libdirs = [os.path.join(winCEpath, 'lib', platform), os.path.join(winCEpath, 'atlmfc', 'lib', platform), lib]
+ def combine_common(obj, compiler_env):
+ # TODO this is likely broken, remove in waf 2.1
+ (common_bindirs,_1,_2) = compiler_env
+ return (bindirs + common_bindirs, incdirs, libdirs)
+ targets[platform] = target_compiler(conf, 'msvc', platform, version, 'x86', vsvars, combine_common)
+ if targets:
+ versions[device + ' ' + version] = targets
+
+@conf
+def gather_winphone_targets(conf, versions, version, vc_path, vsvars):
+ #Looking for WinPhone compilers
+ targets = {}
+ for target,realtarget in all_msvc_platforms[::-1]:
+ targets[target] = target_compiler(conf, 'winphone', realtarget, version, target, vsvars)
+ if targets:
+ versions['winphone ' + version] = targets
+
+@conf
+def gather_vswhere_versions(conf, versions):
+ try:
+ import json
+ except ImportError:
+ Logs.error('Visual Studio 2017 detection requires Python 2.6')
+ return
+
+ prg_path = os.environ.get('ProgramFiles(x86)', os.environ.get('ProgramFiles', 'C:\\Program Files (x86)'))
+
+ vswhere = os.path.join(prg_path, 'Microsoft Visual Studio', 'Installer', 'vswhere.exe')
+ args = [vswhere, '-products', '*', '-legacy', '-format', 'json']
+ try:
+ txt = conf.cmd_and_log(args)
+ except Errors.WafError as e:
+ Logs.debug('msvc: vswhere.exe failed %s', e)
+ return
+
+ if sys.version_info[0] < 3:
+ txt = txt.decode(Utils.console_encoding())
+
+ arr = json.loads(txt)
+ arr.sort(key=lambda x: x['installationVersion'])
+ for entry in arr:
+ ver = entry['installationVersion']
+ ver = str('.'.join(ver.split('.')[:2]))
+ path = str(os.path.abspath(entry['installationPath']))
+ if os.path.exists(path) and ('msvc %s' % ver) not in versions:
+ conf.gather_msvc_targets(versions, ver, path)
+
+@conf
+def gather_msvc_versions(conf, versions):
+ vc_paths = []
+ for (v,version,reg) in gather_msvc_detected_versions():
+ try:
+ try:
+ msvc_version = Utils.winreg.OpenKey(Utils.winreg.HKEY_LOCAL_MACHINE, reg + "\\Setup\\VC")
+ except OSError:
+ msvc_version = Utils.winreg.OpenKey(Utils.winreg.HKEY_LOCAL_MACHINE, reg + "\\Setup\\Microsoft Visual C++")
+ path,type = Utils.winreg.QueryValueEx(msvc_version, 'ProductDir')
+ except OSError:
+ try:
+ msvc_version = Utils.winreg.OpenKey(Utils.winreg.HKEY_LOCAL_MACHINE, "SOFTWARE\\Wow6432node\\Microsoft\\VisualStudio\\SxS\\VS7")
+ path,type = Utils.winreg.QueryValueEx(msvc_version, version)
+ except OSError:
+ continue
+ else:
+ vc_paths.append((version, os.path.abspath(str(path))))
+ continue
+ else:
+ vc_paths.append((version, os.path.abspath(str(path))))
+
+ wince_supported_platforms = gather_wince_supported_platforms()
+
+ for version,vc_path in vc_paths:
+ vs_path = os.path.dirname(vc_path)
+ vsvars = os.path.join(vs_path, 'Common7', 'Tools', 'vsvars32.bat')
+ if wince_supported_platforms and os.path.isfile(vsvars):
+ conf.gather_wince_targets(versions, version, vc_path, vsvars, wince_supported_platforms)
+
+ # WP80 works with 11.0Exp and 11.0, both of which resolve to the same vc_path.
+ # Stop after one is found.
+ for version,vc_path in vc_paths:
+ vs_path = os.path.dirname(vc_path)
+ vsvars = os.path.join(vs_path, 'VC', 'WPSDK', 'WP80', 'vcvarsphoneall.bat')
+ if os.path.isfile(vsvars):
+ conf.gather_winphone_targets(versions, '8.0', vc_path, vsvars)
+ break
+
+ for version,vc_path in vc_paths:
+ vs_path = os.path.dirname(vc_path)
+ conf.gather_msvc_targets(versions, version, vc_path)
+
+@conf
+def gather_icl_versions(conf, versions):
+ """
+ Checks ICL compilers
+
+ :param versions: list to modify
+ :type versions: list
+ """
+ version_pattern = re.compile(r'^...?.?\....?.?')
+ try:
+ all_versions = Utils.winreg.OpenKey(Utils.winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Wow6432node\\Intel\\Compilers\\C++')
+ except OSError:
+ try:
+ all_versions = Utils.winreg.OpenKey(Utils.winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Intel\\Compilers\\C++')
+ except OSError:
+ return
+ index = 0
+ while 1:
+ try:
+ version = Utils.winreg.EnumKey(all_versions, index)
+ except OSError:
+ break
+ index += 1
+ if not version_pattern.match(version):
+ continue
+ targets = {}
+ for target,arch in all_icl_platforms:
+ if target=='intel64':
+ targetDir='EM64T_NATIVE'
+ else:
+ targetDir=target
+ try:
+ Utils.winreg.OpenKey(all_versions,version+'\\'+targetDir)
+ icl_version=Utils.winreg.OpenKey(all_versions,version)
+ path,type=Utils.winreg.QueryValueEx(icl_version,'ProductDir')
+ except OSError:
+ pass
+ else:
+ batch_file=os.path.join(path,'bin','iclvars.bat')
+ if os.path.isfile(batch_file):
+ targets[target] = target_compiler(conf, 'intel', arch, version, target, batch_file)
+ for target,arch in all_icl_platforms:
+ try:
+ icl_version = Utils.winreg.OpenKey(all_versions, version+'\\'+target)
+ path,type = Utils.winreg.QueryValueEx(icl_version,'ProductDir')
+ except OSError:
+ continue
+ else:
+ batch_file=os.path.join(path,'bin','iclvars.bat')
+ if os.path.isfile(batch_file):
+ targets[target] = target_compiler(conf, 'intel', arch, version, target, batch_file)
+ major = version[0:2]
+ versions['intel ' + major] = targets
+
+@conf
+def gather_intel_composer_versions(conf, versions):
+ """
+ Checks ICL compilers that are part of Intel Composer Suites
+
+ :param versions: list to modify
+ :type versions: list
+ """
+ version_pattern = re.compile(r'^...?.?\...?.?.?')
+ try:
+ all_versions = Utils.winreg.OpenKey(Utils.winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Wow6432node\\Intel\\Suites')
+ except OSError:
+ try:
+ all_versions = Utils.winreg.OpenKey(Utils.winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Intel\\Suites')
+ except OSError:
+ return
+ index = 0
+ while 1:
+ try:
+ version = Utils.winreg.EnumKey(all_versions, index)
+ except OSError:
+ break
+ index += 1
+ if not version_pattern.match(version):
+ continue
+ targets = {}
+ for target,arch in all_icl_platforms:
+ if target=='intel64':
+ targetDir='EM64T_NATIVE'
+ else:
+ targetDir=target
+ try:
+ try:
+ defaults = Utils.winreg.OpenKey(all_versions,version+'\\Defaults\\C++\\'+targetDir)
+ except OSError:
+ if targetDir == 'EM64T_NATIVE':
+ defaults = Utils.winreg.OpenKey(all_versions,version+'\\Defaults\\C++\\EM64T')
+ else:
+ raise
+ uid,type = Utils.winreg.QueryValueEx(defaults, 'SubKey')
+ Utils.winreg.OpenKey(all_versions,version+'\\'+uid+'\\C++\\'+targetDir)
+ icl_version=Utils.winreg.OpenKey(all_versions,version+'\\'+uid+'\\C++')
+ path,type=Utils.winreg.QueryValueEx(icl_version,'ProductDir')
+ except OSError:
+ pass
+ else:
+ batch_file=os.path.join(path,'bin','iclvars.bat')
+ if os.path.isfile(batch_file):
+ targets[target] = target_compiler(conf, 'intel', arch, version, target, batch_file)
+ # The intel compilervar_arch.bat is broken when used with Visual Studio Express 2012
+ # http://software.intel.com/en-us/forums/topic/328487
+ compilervars_warning_attr = '_compilervars_warning_key'
+ if version[0:2] == '13' and getattr(conf, compilervars_warning_attr, True):
+ setattr(conf, compilervars_warning_attr, False)
+ patch_url = 'http://software.intel.com/en-us/forums/topic/328487'
+ compilervars_arch = os.path.join(path, 'bin', 'compilervars_arch.bat')
+ for vscomntool in ('VS110COMNTOOLS', 'VS100COMNTOOLS'):
+ if vscomntool in os.environ:
+ vs_express_path = os.environ[vscomntool] + r'..\IDE\VSWinExpress.exe'
+ dev_env_path = os.environ[vscomntool] + r'..\IDE\devenv.exe'
+ if (r'if exist "%VS110COMNTOOLS%..\IDE\VSWinExpress.exe"' in Utils.readf(compilervars_arch) and
+ not os.path.exists(vs_express_path) and not os.path.exists(dev_env_path)):
+ Logs.warn(('The Intel compilervar_arch.bat only checks for one Visual Studio SKU '
+ '(VSWinExpress.exe) but it does not seem to be installed at %r. '
+ 'The intel command line set up will fail to configure unless the file %r'
+ 'is patched. See: %s') % (vs_express_path, compilervars_arch, patch_url))
+ major = version[0:2]
+ versions['intel ' + major] = targets
+
+@conf
+def detect_msvc(self):
+ return self.setup_msvc(self.get_msvc_versions())
+
+@conf
+def get_msvc_versions(self):
+ """
+ :return: platform to compiler configurations
+ :rtype: dict
+ """
+ dct = Utils.ordered_iter_dict()
+ self.gather_icl_versions(dct)
+ self.gather_intel_composer_versions(dct)
+ self.gather_wsdk_versions(dct)
+ self.gather_msvc_versions(dct)
+ self.gather_vswhere_versions(dct)
+ Logs.debug('msvc: detected versions %r', list(dct.keys()))
+ return dct
+
+@conf
+def find_lt_names_msvc(self, libname, is_static=False):
+ """
+ Win32/MSVC specific code to glean out information from libtool la files.
+ this function is not attached to the task_gen class. Returns a triplet:
+ (library absolute path, library name without extension, whether the library is static)
+ """
+ lt_names=[
+ 'lib%s.la' % libname,
+ '%s.la' % libname,
+ ]
+
+ for path in self.env.LIBPATH:
+ for la in lt_names:
+ laf=os.path.join(path,la)
+ dll=None
+ if os.path.exists(laf):
+ ltdict = Utils.read_la_file(laf)
+ lt_libdir=None
+ if ltdict.get('libdir', ''):
+ lt_libdir = ltdict['libdir']
+ if not is_static and ltdict.get('library_names', ''):
+ dllnames=ltdict['library_names'].split()
+ dll=dllnames[0].lower()
+ dll=re.sub(r'\.dll$', '', dll)
+ return (lt_libdir, dll, False)
+ elif ltdict.get('old_library', ''):
+ olib=ltdict['old_library']
+ if os.path.exists(os.path.join(path,olib)):
+ return (path, olib, True)
+ elif lt_libdir != '' and os.path.exists(os.path.join(lt_libdir,olib)):
+ return (lt_libdir, olib, True)
+ else:
+ return (None, olib, True)
+ else:
+ raise self.errors.WafError('invalid libtool object file: %s' % laf)
+ return (None, None, None)
+
+@conf
+def libname_msvc(self, libname, is_static=False):
+ lib = libname.lower()
+ lib = re.sub(r'\.lib$','',lib)
+
+ if lib in g_msvc_systemlibs:
+ return lib
+
+ lib=re.sub('^lib','',lib)
+
+ if lib == 'm':
+ return None
+
+ (lt_path, lt_libname, lt_static) = self.find_lt_names_msvc(lib, is_static)
+
+ if lt_path != None and lt_libname != None:
+ if lt_static:
+ # file existence check has been made by find_lt_names
+ return os.path.join(lt_path,lt_libname)
+
+ if lt_path != None:
+ _libpaths = [lt_path] + self.env.LIBPATH
+ else:
+ _libpaths = self.env.LIBPATH
+
+ static_libs=[
+ 'lib%ss.lib' % lib,
+ 'lib%s.lib' % lib,
+ '%ss.lib' % lib,
+ '%s.lib' %lib,
+ ]
+
+ dynamic_libs=[
+ 'lib%s.dll.lib' % lib,
+ 'lib%s.dll.a' % lib,
+ '%s.dll.lib' % lib,
+ '%s.dll.a' % lib,
+ 'lib%s_d.lib' % lib,
+ '%s_d.lib' % lib,
+ '%s.lib' %lib,
+ ]
+
+ libnames=static_libs
+ if not is_static:
+ libnames=dynamic_libs + static_libs
+
+ for path in _libpaths:
+ 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(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(r'\.lib$', '', libname)
+
+@conf
+def check_lib_msvc(self, libname, is_static=False, uselib_store=None):
+ """
+ Ideally we should be able to place the lib in the right env var, either STLIB or LIB,
+ but we don't distinguish static libs from shared libs.
+ This is ok since msvc doesn't have any special linker flag to select static libs (no env.STLIB_MARKER)
+ """
+ libn = self.libname_msvc(libname, is_static)
+
+ if not uselib_store:
+ uselib_store = libname.upper()
+
+ if False and is_static: # disabled
+ self.env['STLIB_' + uselib_store] = [libn]
+ else:
+ self.env['LIB_' + uselib_store] = [libn]
+
+@conf
+def check_libs_msvc(self, libnames, is_static=False):
+ for libname in Utils.to_list(libnames):
+ self.check_lib_msvc(libname, is_static)
+
+def configure(conf):
+ """
+ Configuration methods to call for detecting msvc
+ """
+ conf.autodetect(True)
+ conf.find_msvc()
+ conf.msvc_common_flags()
+ conf.cc_load_tools()
+ conf.cxx_load_tools()
+ conf.cc_add_flags()
+ conf.cxx_add_flags()
+ conf.link_add_flags()
+ conf.visual_studio_add_flags()
+
+@conf
+def no_autodetect(conf):
+ conf.env.NO_MSVC_DETECT = 1
+ configure(conf)
+
+@conf
+def autodetect(conf, arch=False):
+ v = conf.env
+ if v.NO_MSVC_DETECT:
+ return
+
+ compiler, version, path, includes, libdirs, cpu = conf.detect_msvc()
+ if arch:
+ v.DEST_CPU = cpu
+
+ v.PATH = path
+ v.INCLUDES = includes
+ v.LIBPATH = libdirs
+ v.MSVC_COMPILER = compiler
+ try:
+ v.MSVC_VERSION = float(version)
+ except ValueError:
+ v.MSVC_VERSION = float(version[:-3])
+
+def _get_prog_names(conf, compiler):
+ if compiler == 'intel':
+ compiler_name = 'ICL'
+ linker_name = 'XILINK'
+ lib_name = 'XILIB'
+ else:
+ # assumes CL.exe
+ compiler_name = 'CL'
+ linker_name = 'LINK'
+ lib_name = 'LIB'
+ return compiler_name, linker_name, lib_name
+
+@conf
+def find_msvc(conf):
+ """Due to path format limitations, limit operation only to native Win32. Yeah it sucks."""
+ if sys.platform == 'cygwin':
+ conf.fatal('MSVC module does not work under cygwin Python!')
+
+ # the autodetection is supposed to be performed before entering in this method
+ v = conf.env
+ path = v.PATH
+ compiler = v.MSVC_COMPILER
+ version = v.MSVC_VERSION
+
+ compiler_name, linker_name, lib_name = _get_prog_names(conf, compiler)
+ v.MSVC_MANIFEST = (compiler == 'msvc' and version >= 8) or (compiler == 'wsdk' and version >= 6) or (compiler == 'intel' and version >= 11)
+
+ # compiler
+ cxx = conf.find_program(compiler_name, var='CXX', path_list=path)
+
+ # before setting anything, check if the compiler is really msvc
+ env = dict(conf.environ)
+ if path:
+ env.update(PATH = ';'.join(path))
+ if not conf.cmd_and_log(cxx + ['/nologo', '/help'], env=env):
+ conf.fatal('the msvc compiler could not be identified')
+
+ # c/c++ compiler
+ v.CC = v.CXX = cxx
+ v.CC_NAME = v.CXX_NAME = 'msvc'
+
+ # linker
+ if not v.LINK_CXX:
+ conf.find_program(linker_name, path_list=path, errmsg='%s was not found (linker)' % linker_name, var='LINK_CXX')
+
+ if not v.LINK_CC:
+ v.LINK_CC = v.LINK_CXX
+
+ # staticlib linker
+ if not v.AR:
+ stliblink = conf.find_program(lib_name, path_list=path, var='AR')
+ if not stliblink:
+ return
+ v.ARFLAGS = ['/nologo']
+
+ # manifest tool. Not required for VS 2003 and below. Must have for VS 2005 and later
+ if v.MSVC_MANIFEST:
+ conf.find_program('MT', path_list=path, var='MT')
+ v.MTFLAGS = ['/nologo']
+
+ try:
+ conf.load('winres')
+ except Errors.ConfigurationError:
+ Logs.warn('Resource compiler not found. Compiling resource file is disabled')
+
+@conf
+def visual_studio_add_flags(self):
+ """visual studio flags found in the system environment"""
+ v = self.env
+ if self.environ.get('INCLUDE'):
+ v.prepend_value('INCLUDES', [x for x in self.environ['INCLUDE'].split(';') if x]) # notice the 'S'
+ if self.environ.get('LIB'):
+ v.prepend_value('LIBPATH', [x for x in self.environ['LIB'].split(';') if x])
+
+@conf
+def msvc_common_flags(conf):
+ """
+ Setup the flags required for executing the msvc compiler
+ """
+ v = conf.env
+
+ v.DEST_BINFMT = 'pe'
+ v.append_value('CFLAGS', ['/nologo'])
+ v.append_value('CXXFLAGS', ['/nologo'])
+ v.append_value('LINKFLAGS', ['/nologo'])
+ v.DEFINES_ST = '/D%s'
+
+ v.CC_SRC_F = ''
+ v.CC_TGT_F = ['/c', '/Fo']
+ v.CXX_SRC_F = ''
+ v.CXX_TGT_F = ['/c', '/Fo']
+
+ if (v.MSVC_COMPILER == 'msvc' and v.MSVC_VERSION >= 8) or (v.MSVC_COMPILER == 'wsdk' and v.MSVC_VERSION >= 6):
+ v.CC_TGT_F = ['/FC'] + v.CC_TGT_F
+ v.CXX_TGT_F = ['/FC'] + v.CXX_TGT_F
+
+ v.CPPPATH_ST = '/I%s' # template for adding include paths
+
+ v.AR_TGT_F = v.CCLNK_TGT_F = v.CXXLNK_TGT_F = '/OUT:'
+
+ # CRT specific flags
+ v.CFLAGS_CRT_MULTITHREADED = v.CXXFLAGS_CRT_MULTITHREADED = ['/MT']
+ v.CFLAGS_CRT_MULTITHREADED_DLL = v.CXXFLAGS_CRT_MULTITHREADED_DLL = ['/MD']
+
+ v.CFLAGS_CRT_MULTITHREADED_DBG = v.CXXFLAGS_CRT_MULTITHREADED_DBG = ['/MTd']
+ v.CFLAGS_CRT_MULTITHREADED_DLL_DBG = v.CXXFLAGS_CRT_MULTITHREADED_DLL_DBG = ['/MDd']
+
+ v.LIB_ST = '%s.lib'
+ v.LIBPATH_ST = '/LIBPATH:%s'
+ v.STLIB_ST = '%s.lib'
+ v.STLIBPATH_ST = '/LIBPATH:%s'
+
+ if v.MSVC_MANIFEST:
+ v.append_value('LINKFLAGS', ['/MANIFEST'])
+
+ v.CFLAGS_cshlib = []
+ v.CXXFLAGS_cxxshlib = []
+ v.LINKFLAGS_cshlib = v.LINKFLAGS_cxxshlib = ['/DLL']
+ v.cshlib_PATTERN = v.cxxshlib_PATTERN = '%s.dll'
+ v.implib_PATTERN = '%s.lib'
+ v.IMPLIB_ST = '/IMPLIB:%s'
+
+ v.LINKFLAGS_cstlib = []
+ v.cstlib_PATTERN = v.cxxstlib_PATTERN = '%s.lib'
+
+ v.cprogram_PATTERN = v.cxxprogram_PATTERN = '%s.exe'
+
+ v.def_PATTERN = '/def:%s'
+
+
+#######################################################################################################
+##### conf above, build below
+
+@after_method('apply_link')
+@feature('c', 'cxx')
+def apply_flags_msvc(self):
+ """
+ Add additional flags implied by msvc, such as subsystems and pdb files::
+
+ def build(bld):
+ bld.stlib(source='main.c', target='bar', subsystem='gruik')
+ """
+ if self.env.CC_NAME != 'msvc' or not getattr(self, 'link_task', None):
+ return
+
+ is_static = isinstance(self.link_task, ccroot.stlink_task)
+
+ subsystem = getattr(self, 'subsystem', '')
+ if subsystem:
+ subsystem = '/subsystem:%s' % subsystem
+ flags = is_static and 'ARFLAGS' or 'LINKFLAGS'
+ self.env.append_value(flags, subsystem)
+
+ if not is_static:
+ for f in self.env.LINKFLAGS:
+ d = f.lower()
+ if d[1:] == 'debug':
+ pdbnode = self.link_task.outputs[0].change_ext('.pdb')
+ self.link_task.outputs.append(pdbnode)
+
+ if getattr(self, 'install_task', None):
+ self.pdb_install_task = self.add_install_files(
+ install_to=self.install_task.install_to, install_from=pdbnode)
+ break
+
+@feature('cprogram', 'cshlib', 'cxxprogram', 'cxxshlib')
+@after_method('apply_link')
+def apply_manifest(self):
+ """
+ Special linker for MSVC with support for embedding manifests into DLL's
+ and executables compiled by Visual Studio 2005 or probably later. Without
+ the manifest file, the binaries are unusable.
+ See: http://msdn2.microsoft.com/en-us/library/ms235542(VS.80).aspx
+ """
+ if self.env.CC_NAME == 'msvc' and self.env.MSVC_MANIFEST and getattr(self, 'link_task', None):
+ out_node = self.link_task.outputs[0]
+ man_node = out_node.parent.find_or_declare(out_node.name + '.manifest')
+ self.link_task.outputs.append(man_node)
+ self.env.DO_MANIFEST = True
+
+def make_winapp(self, family):
+ append = self.env.append_unique
+ append('DEFINES', 'WINAPI_FAMILY=%s' % family)
+ append('CXXFLAGS', ['/ZW', '/TP'])
+ for lib_path in self.env.LIBPATH:
+ append('CXXFLAGS','/AI%s'%lib_path)
+
+@feature('winphoneapp')
+@after_method('process_use')
+@after_method('propagate_uselib_vars')
+def make_winphone_app(self):
+ """
+ Insert configuration flags for windows phone applications (adds /ZW, /TP...)
+ """
+ make_winapp(self, 'WINAPI_FAMILY_PHONE_APP')
+ self.env.append_unique('LINKFLAGS', ['/NODEFAULTLIB:ole32.lib', 'PhoneAppModelHost.lib'])
+
+@feature('winapp')
+@after_method('process_use')
+@after_method('propagate_uselib_vars')
+def make_windows_app(self):
+ """
+ Insert configuration flags for windows applications (adds /ZW, /TP...)
+ """
+ make_winapp(self, 'WINAPI_FAMILY_DESKTOP_APP')
diff --git a/Tools/nasm.py b/Tools/nasm.py
new file mode 100644
index 0000000..411d582
--- /dev/null
+++ b/Tools/nasm.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2008-2018 (ita)
+
+"""
+Nasm tool (asm processing)
+"""
+
+import os
+import waflib.Tools.asm # leave this
+from waflib.TaskGen import feature
+
+@feature('asm')
+def apply_nasm_vars(self):
+ """provided for compatibility"""
+ self.env.append_value('ASFLAGS', self.to_list(getattr(self, 'nasm_flags', [])))
+
+def configure(conf):
+ """
+ Detect nasm/yasm and set the variable *AS*
+ """
+ conf.find_program(['nasm', 'yasm'], var='AS')
+ conf.env.AS_TGT_F = ['-o']
+ conf.env.ASLNK_TGT_F = ['-o']
+ conf.load('asm')
+ conf.env.ASMPATH_ST = '-I%s' + os.sep
diff --git a/Tools/nobuild.py b/Tools/nobuild.py
new file mode 100644
index 0000000..2e4b055
--- /dev/null
+++ b/Tools/nobuild.py
@@ -0,0 +1,24 @@
+#! /usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2015 (ita)
+
+"""
+Override the build commands to write empty files.
+This is useful for profiling and evaluating the Python overhead.
+
+To use::
+
+ def build(bld):
+ ...
+ bld.load('nobuild')
+
+"""
+
+from waflib import Task
+def build(bld):
+ def run(self):
+ for x in self.outputs:
+ x.write('')
+ for (name, cls) in Task.classes.items():
+ cls.run = run
+
diff --git a/Tools/perl.py b/Tools/perl.py
new file mode 100644
index 0000000..32b03fb
--- /dev/null
+++ b/Tools/perl.py
@@ -0,0 +1,156 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# andersg at 0x63.nu 2007
+# Thomas Nagy 2016-2018 (ita)
+
+"""
+Support for Perl extensions. A C/C++ compiler is required::
+
+ def options(opt):
+ opt.load('compiler_c perl')
+ def configure(conf):
+ conf.load('compiler_c perl')
+ conf.check_perl_version((5,6,0))
+ conf.check_perl_ext_devel()
+ conf.check_perl_module('Cairo')
+ conf.check_perl_module('Devel::PPPort 4.89')
+ def build(bld):
+ bld(
+ features = 'c cshlib perlext',
+ source = 'Mytest.xs',
+ target = 'Mytest',
+ install_path = '${ARCHDIR_PERL}/auto')
+ bld.install_files('${ARCHDIR_PERL}', 'Mytest.pm')
+"""
+
+import os
+from waflib import Task, Options, Utils, Errors
+from waflib.Configure import conf
+from waflib.TaskGen import extension, feature, before_method
+
+@before_method('apply_incpaths', 'apply_link', 'propagate_uselib_vars')
+@feature('perlext')
+def init_perlext(self):
+ """
+ Change the values of *cshlib_PATTERN* and *cxxshlib_PATTERN* to remove the
+ *lib* prefix from library names.
+ """
+ self.uselib = self.to_list(getattr(self, 'uselib', []))
+ if not 'PERLEXT' in self.uselib:
+ self.uselib.append('PERLEXT')
+ self.env.cshlib_PATTERN = self.env.cxxshlib_PATTERN = self.env.perlext_PATTERN
+
+@extension('.xs')
+def xsubpp_file(self, node):
+ """
+ Create :py:class:`waflib.Tools.perl.xsubpp` tasks to process *.xs* files
+ """
+ outnode = node.change_ext('.c')
+ self.create_task('xsubpp', node, outnode)
+ self.source.append(outnode)
+
+class xsubpp(Task.Task):
+ """
+ Process *.xs* files
+ """
+ run_str = '${PERL} ${XSUBPP} -noprototypes -typemap ${EXTUTILS_TYPEMAP} ${SRC} > ${TGT}'
+ color = 'BLUE'
+ ext_out = ['.h']
+
+@conf
+def check_perl_version(self, minver=None):
+ """
+ Check if Perl is installed, and set the variable PERL.
+ minver is supposed to be a tuple
+ """
+ res = True
+ if minver:
+ cver = '.'.join(map(str,minver))
+ else:
+ cver = ''
+
+ self.start_msg('Checking for minimum perl version %s' % cver)
+
+ perl = self.find_program('perl', var='PERL', value=getattr(Options.options, 'perlbinary', None))
+ version = self.cmd_and_log(perl + ["-e", 'printf \"%vd\", $^V'])
+ if not version:
+ res = False
+ version = "Unknown"
+ elif not minver is None:
+ ver = tuple(map(int, version.split(".")))
+ if ver < minver:
+ res = False
+
+ self.end_msg(version, color=res and 'GREEN' or 'YELLOW')
+ return res
+
+@conf
+def check_perl_module(self, module):
+ """
+ Check if specified perlmodule is installed.
+
+ The minimum version can be specified by specifying it after modulename
+ like this::
+
+ def configure(conf):
+ conf.check_perl_module("Some::Module 2.92")
+ """
+ cmd = self.env.PERL + ['-e', 'use %s' % module]
+ self.start_msg('perl module %s' % module)
+ try:
+ r = self.cmd_and_log(cmd)
+ except Errors.WafError:
+ self.end_msg(False)
+ return None
+ self.end_msg(r or True)
+ return r
+
+@conf
+def check_perl_ext_devel(self):
+ """
+ Check for configuration needed to build perl extensions.
+
+ Sets different xxx_PERLEXT variables in the environment.
+
+ Also sets the ARCHDIR_PERL variable useful as installation path,
+ which can be overridden by ``--with-perl-archdir`` option.
+ """
+
+ env = self.env
+ perl = env.PERL
+ if not perl:
+ self.fatal('find perl first')
+
+ def cmd_perl_config(s):
+ return perl + ['-MConfig', '-e', 'print \"%s\"' % s]
+ def cfg_str(cfg):
+ return self.cmd_and_log(cmd_perl_config(cfg))
+ def cfg_lst(cfg):
+ return Utils.to_list(cfg_str(cfg))
+ def find_xsubpp():
+ for var in ('privlib', 'vendorlib'):
+ xsubpp = cfg_lst('$Config{%s}/ExtUtils/xsubpp$Config{exe_ext}' % var)
+ if xsubpp and os.path.isfile(xsubpp[0]):
+ return xsubpp
+ return self.find_program('xsubpp')
+
+ env.LINKFLAGS_PERLEXT = cfg_lst('$Config{lddlflags}')
+ env.INCLUDES_PERLEXT = cfg_lst('$Config{archlib}/CORE')
+ env.CFLAGS_PERLEXT = cfg_lst('$Config{ccflags} $Config{cccdlflags}')
+ env.EXTUTILS_TYPEMAP = cfg_lst('$Config{privlib}/ExtUtils/typemap')
+ env.XSUBPP = find_xsubpp()
+
+ if not getattr(Options.options, 'perlarchdir', None):
+ env.ARCHDIR_PERL = cfg_str('$Config{sitearch}')
+ else:
+ env.ARCHDIR_PERL = getattr(Options.options, 'perlarchdir')
+
+ env.perlext_PATTERN = '%s.' + cfg_str('$Config{dlext}')
+
+def options(opt):
+ """
+ Add the ``--with-perl-archdir`` and ``--with-perl-binary`` command-line options.
+ """
+ opt.add_option('--with-perl-binary', type='string', dest='perlbinary', help = 'Specify alternate perl binary', default=None)
+ opt.add_option('--with-perl-archdir', type='string', dest='perlarchdir', help = 'Specify directory where to install arch specific files', default=None)
+
diff --git a/Tools/python.py b/Tools/python.py
new file mode 100644
index 0000000..01a2c9a
--- /dev/null
+++ b/Tools/python.py
@@ -0,0 +1,631 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2007-2015 (ita)
+# Gustavo Carneiro (gjc), 2007
+
+"""
+Support for Python, detect the headers and libraries and provide
+*use* variables to link C/C++ programs against them::
+
+ def options(opt):
+ opt.load('compiler_c python')
+ def configure(conf):
+ conf.load('compiler_c python')
+ conf.check_python_version((2,4,2))
+ conf.check_python_headers()
+ def build(bld):
+ bld.program(features='pyembed', source='a.c', target='myprog')
+ bld.shlib(features='pyext', source='b.c', target='mylib')
+"""
+
+import os, sys
+from waflib import Errors, Logs, Node, Options, Task, Utils
+from waflib.TaskGen import extension, before_method, after_method, feature
+from waflib.Configure import conf
+
+FRAG = '''
+#include <Python.h>
+#ifdef __cplusplus
+extern "C" {
+#endif
+ void Py_Initialize(void);
+ void Py_Finalize(void);
+#ifdef __cplusplus
+}
+#endif
+int main(int argc, char **argv)
+{
+ (void)argc; (void)argv;
+ Py_Initialize();
+ Py_Finalize();
+ return 0;
+}
+'''
+"""
+Piece of C/C++ code used in :py:func:`waflib.Tools.python.check_python_headers`
+"""
+
+INST = '''
+import sys, py_compile
+py_compile.compile(sys.argv[1], sys.argv[2], sys.argv[3], True)
+'''
+"""
+Piece of Python code used in :py:class:`waflib.Tools.python.pyo` and :py:class:`waflib.Tools.python.pyc` for byte-compiling python files
+"""
+
+DISTUTILS_IMP = ['from distutils.sysconfig import get_config_var, get_python_lib']
+
+@before_method('process_source')
+@feature('py')
+def feature_py(self):
+ """
+ Create tasks to byte-compile .py files and install them, if requested
+ """
+ self.install_path = getattr(self, 'install_path', '${PYTHONDIR}')
+ install_from = getattr(self, 'install_from', None)
+ if install_from and not isinstance(install_from, Node.Node):
+ install_from = self.path.find_dir(install_from)
+ self.install_from = install_from
+
+ ver = self.env.PYTHON_VERSION
+ if not ver:
+ self.bld.fatal('Installing python files requires PYTHON_VERSION, try conf.check_python_version')
+
+ if int(ver.replace('.', '')) > 31:
+ self.install_32 = True
+
+@extension('.py')
+def process_py(self, node):
+ """
+ Add signature of .py file, so it will be byte-compiled when necessary
+ """
+ assert(hasattr(self, 'install_path')), 'add features="py"'
+
+ # where to install the python file
+ if self.install_path:
+ if self.install_from:
+ self.add_install_files(install_to=self.install_path, install_from=node, cwd=self.install_from, relative_trick=True)
+ else:
+ self.add_install_files(install_to=self.install_path, install_from=node, relative_trick=True)
+
+ lst = []
+ if self.env.PYC:
+ lst.append('pyc')
+ if self.env.PYO:
+ lst.append('pyo')
+
+ if self.install_path:
+ if self.install_from:
+ pyd = Utils.subst_vars("%s/%s" % (self.install_path, node.path_from(self.install_from)), self.env)
+ else:
+ pyd = Utils.subst_vars("%s/%s" % (self.install_path, node.path_from(self.path)), self.env)
+ else:
+ pyd = node.abspath()
+
+ for ext in lst:
+ if self.env.PYTAG and not self.env.NOPYCACHE:
+ # __pycache__ installation for python 3.2 - PEP 3147
+ name = node.name[:-3]
+ pyobj = node.parent.get_bld().make_node('__pycache__').make_node("%s.%s.%s" % (name, self.env.PYTAG, ext))
+ pyobj.parent.mkdir()
+ else:
+ pyobj = node.change_ext(".%s" % ext)
+
+ tsk = self.create_task(ext, node, pyobj)
+ tsk.pyd = pyd
+
+ if self.install_path:
+ self.add_install_files(install_to=os.path.dirname(pyd), install_from=pyobj, cwd=node.parent.get_bld(), relative_trick=True)
+
+class pyc(Task.Task):
+ """
+ Byte-compiling python files
+ """
+ color = 'PINK'
+ def __str__(self):
+ node = self.outputs[0]
+ return node.path_from(node.ctx.launch_node())
+ def run(self):
+ cmd = [Utils.subst_vars('${PYTHON}', self.env), '-c', INST, self.inputs[0].abspath(), self.outputs[0].abspath(), self.pyd]
+ ret = self.generator.bld.exec_command(cmd)
+ return ret
+
+class pyo(Task.Task):
+ """
+ Byte-compiling python files
+ """
+ color = 'PINK'
+ def __str__(self):
+ node = self.outputs[0]
+ return node.path_from(node.ctx.launch_node())
+ def run(self):
+ cmd = [Utils.subst_vars('${PYTHON}', self.env), Utils.subst_vars('${PYFLAGS_OPT}', self.env), '-c', INST, self.inputs[0].abspath(), self.outputs[0].abspath(), self.pyd]
+ ret = self.generator.bld.exec_command(cmd)
+ return ret
+
+@feature('pyext')
+@before_method('propagate_uselib_vars', 'apply_link')
+@after_method('apply_bundle')
+def init_pyext(self):
+ """
+ Change the values of *cshlib_PATTERN* and *cxxshlib_PATTERN* to remove the
+ *lib* prefix from library names.
+ """
+ self.uselib = self.to_list(getattr(self, 'uselib', []))
+ if not 'PYEXT' in self.uselib:
+ self.uselib.append('PYEXT')
+ # override shlib_PATTERN set by the osx module
+ self.env.cshlib_PATTERN = self.env.cxxshlib_PATTERN = self.env.macbundle_PATTERN = self.env.pyext_PATTERN
+ self.env.fcshlib_PATTERN = self.env.dshlib_PATTERN = self.env.pyext_PATTERN
+
+ try:
+ if not self.install_path:
+ return
+ except AttributeError:
+ self.install_path = '${PYTHONARCHDIR}'
+
+@feature('pyext')
+@before_method('apply_link', 'apply_bundle')
+def set_bundle(self):
+ """Mac-specific pyext extension that enables bundles from c_osx.py"""
+ if Utils.unversioned_sys_platform() == 'darwin':
+ self.mac_bundle = True
+
+@before_method('propagate_uselib_vars')
+@feature('pyembed')
+def init_pyembed(self):
+ """
+ Add the PYEMBED variable.
+ """
+ self.uselib = self.to_list(getattr(self, 'uselib', []))
+ if not 'PYEMBED' in self.uselib:
+ self.uselib.append('PYEMBED')
+
+@conf
+def get_python_variables(self, variables, imports=None):
+ """
+ Spawn a new python process to dump configuration variables
+
+ :param variables: variables to print
+ :type variables: list of string
+ :param imports: one import by element
+ :type imports: list of string
+ :return: the variable values
+ :rtype: list of string
+ """
+ if not imports:
+ try:
+ imports = self.python_imports
+ except AttributeError:
+ imports = DISTUTILS_IMP
+
+ program = list(imports) # copy
+ program.append('')
+ for v in variables:
+ program.append("print(repr(%s))" % v)
+ os_env = dict(os.environ)
+ try:
+ del os_env['MACOSX_DEPLOYMENT_TARGET'] # see comments in the OSX tool
+ except KeyError:
+ pass
+
+ try:
+ out = self.cmd_and_log(self.env.PYTHON + ['-c', '\n'.join(program)], env=os_env)
+ except Errors.WafError:
+ self.fatal('The distutils module is unusable: install "python-devel"?')
+ self.to_log(out)
+ return_values = []
+ for s in out.splitlines():
+ s = s.strip()
+ if not s:
+ continue
+ if s == 'None':
+ return_values.append(None)
+ elif (s[0] == "'" and s[-1] == "'") or (s[0] == '"' and s[-1] == '"'):
+ return_values.append(eval(s))
+ elif s[0].isdigit():
+ return_values.append(int(s))
+ else: break
+ return return_values
+
+@conf
+def test_pyembed(self, mode, msg='Testing pyembed configuration'):
+ self.check(header_name='Python.h', define_name='HAVE_PYEMBED', msg=msg,
+ fragment=FRAG, errmsg='Could not build a python embedded interpreter',
+ features='%s %sprogram pyembed' % (mode, mode))
+
+@conf
+def test_pyext(self, mode, msg='Testing pyext configuration'):
+ self.check(header_name='Python.h', define_name='HAVE_PYEXT', msg=msg,
+ fragment=FRAG, errmsg='Could not build python extensions',
+ features='%s %sshlib pyext' % (mode, mode))
+
+@conf
+def python_cross_compile(self, features='pyembed pyext'):
+ """
+ For cross-compilation purposes, it is possible to bypass the normal detection and set the flags that you want:
+ PYTHON_VERSION='3.4' PYTAG='cpython34' pyext_PATTERN="%s.so" PYTHON_LDFLAGS='-lpthread -ldl' waf configure
+
+ The following variables are used:
+ PYTHON_VERSION required
+ PYTAG required
+ PYTHON_LDFLAGS required
+ pyext_PATTERN required
+ PYTHON_PYEXT_LDFLAGS
+ PYTHON_PYEMBED_LDFLAGS
+ """
+ features = Utils.to_list(features)
+ if not ('PYTHON_LDFLAGS' in self.environ or 'PYTHON_PYEXT_LDFLAGS' in self.environ or 'PYTHON_PYEMBED_LDFLAGS' in self.environ):
+ return False
+
+ for x in 'PYTHON_VERSION PYTAG pyext_PATTERN'.split():
+ if not x in self.environ:
+ self.fatal('Please set %s in the os environment' % x)
+ else:
+ self.env[x] = self.environ[x]
+
+ xx = self.env.CXX_NAME and 'cxx' or 'c'
+ if 'pyext' in features:
+ flags = self.environ.get('PYTHON_PYEXT_LDFLAGS', self.environ.get('PYTHON_LDFLAGS'))
+ if flags is None:
+ self.fatal('No flags provided through PYTHON_PYEXT_LDFLAGS as required')
+ else:
+ self.parse_flags(flags, 'PYEXT')
+ self.test_pyext(xx)
+ if 'pyembed' in features:
+ flags = self.environ.get('PYTHON_PYEMBED_LDFLAGS', self.environ.get('PYTHON_LDFLAGS'))
+ if flags is None:
+ self.fatal('No flags provided through PYTHON_PYEMBED_LDFLAGS as required')
+ else:
+ self.parse_flags(flags, 'PYEMBED')
+ self.test_pyembed(xx)
+ return True
+
+@conf
+def check_python_headers(conf, features='pyembed pyext'):
+ """
+ Check for headers and libraries necessary to extend or embed python by using the module *distutils*.
+ On success the environment variables xxx_PYEXT and xxx_PYEMBED are added:
+
+ * PYEXT: for compiling python extensions
+ * PYEMBED: for embedding a python interpreter
+ """
+ features = Utils.to_list(features)
+ assert ('pyembed' in features) or ('pyext' in features), "check_python_headers features must include 'pyembed' and/or 'pyext'"
+ env = conf.env
+ if not env.CC_NAME and not env.CXX_NAME:
+ conf.fatal('load a compiler first (gcc, g++, ..)')
+
+ # bypass all the code below for cross-compilation
+ if conf.python_cross_compile(features):
+ return
+
+ if not env.PYTHON_VERSION:
+ conf.check_python_version()
+
+ pybin = env.PYTHON
+ if not pybin:
+ conf.fatal('Could not find the python executable')
+
+ # so we actually do all this for compatibility reasons and for obtaining pyext_PATTERN below
+ v = 'prefix SO LDFLAGS LIBDIR LIBPL INCLUDEPY Py_ENABLE_SHARED MACOSX_DEPLOYMENT_TARGET LDSHARED CFLAGS LDVERSION'.split()
+ try:
+ lst = conf.get_python_variables(["get_config_var('%s') or ''" % x for x in v])
+ except RuntimeError:
+ conf.fatal("Python development headers not found (-v for details).")
+
+ vals = ['%s = %r' % (x, y) for (x, y) in zip(v, lst)]
+ conf.to_log("Configuration returned from %r:\n%s\n" % (pybin, '\n'.join(vals)))
+
+ dct = dict(zip(v, lst))
+ x = 'MACOSX_DEPLOYMENT_TARGET'
+ if dct[x]:
+ env[x] = conf.environ[x] = dct[x]
+ env.pyext_PATTERN = '%s' + dct['SO'] # not a mistake
+
+
+ # Try to get pythonX.Y-config
+ num = '.'.join(env.PYTHON_VERSION.split('.')[:2])
+ conf.find_program([''.join(pybin) + '-config', 'python%s-config' % num, 'python-config-%s' % num, 'python%sm-config' % num], var='PYTHON_CONFIG', msg="python-config", mandatory=False)
+
+ if env.PYTHON_CONFIG:
+ # check python-config output only once
+ if conf.env.HAVE_PYTHON_H:
+ return
+
+ # python2.6-config requires 3 runs
+ all_flags = [['--cflags', '--libs', '--ldflags']]
+ if sys.hexversion < 0x2070000:
+ all_flags = [[k] for k in all_flags[0]]
+
+ xx = env.CXX_NAME and 'cxx' or 'c'
+
+ if 'pyembed' in features:
+ for flags in all_flags:
+ conf.check_cfg(msg='Asking python-config for pyembed %r flags' % ' '.join(flags), path=env.PYTHON_CONFIG, package='', uselib_store='PYEMBED', args=flags)
+
+ try:
+ conf.test_pyembed(xx)
+ except conf.errors.ConfigurationError:
+ # python bug 7352
+ if dct['Py_ENABLE_SHARED'] and dct['LIBDIR']:
+ env.append_unique('LIBPATH_PYEMBED', [dct['LIBDIR']])
+ conf.test_pyembed(xx)
+ else:
+ raise
+
+ if 'pyext' in features:
+ for flags in all_flags:
+ conf.check_cfg(msg='Asking python-config for pyext %r flags' % ' '.join(flags), path=env.PYTHON_CONFIG, package='', uselib_store='PYEXT', args=flags)
+
+ try:
+ conf.test_pyext(xx)
+ except conf.errors.ConfigurationError:
+ # python bug 7352
+ if dct['Py_ENABLE_SHARED'] and dct['LIBDIR']:
+ env.append_unique('LIBPATH_PYEXT', [dct['LIBDIR']])
+ conf.test_pyext(xx)
+ else:
+ raise
+
+ conf.define('HAVE_PYTHON_H', 1)
+ return
+
+ # No python-config, do something else on windows systems
+ all_flags = dct['LDFLAGS'] + ' ' + dct['CFLAGS']
+ conf.parse_flags(all_flags, 'PYEMBED')
+
+ all_flags = dct['LDFLAGS'] + ' ' + dct['LDSHARED'] + ' ' + dct['CFLAGS']
+ conf.parse_flags(all_flags, 'PYEXT')
+
+ result = None
+ if not dct["LDVERSION"]:
+ dct["LDVERSION"] = env.PYTHON_VERSION
+
+ # further simplification will be complicated
+ for name in ('python' + dct['LDVERSION'], 'python' + env.PYTHON_VERSION + 'm', 'python' + env.PYTHON_VERSION.replace('.', '')):
+
+ # LIBPATH_PYEMBED is already set; see if it works.
+ if not result and env.LIBPATH_PYEMBED:
+ path = env.LIBPATH_PYEMBED
+ conf.to_log("\n\n# Trying default LIBPATH_PYEMBED: %r\n" % path)
+ result = conf.check(lib=name, uselib='PYEMBED', libpath=path, mandatory=False, msg='Checking for library %s in LIBPATH_PYEMBED' % name)
+
+ if not result and dct['LIBDIR']:
+ path = [dct['LIBDIR']]
+ conf.to_log("\n\n# try again with -L$python_LIBDIR: %r\n" % path)
+ result = conf.check(lib=name, uselib='PYEMBED', libpath=path, mandatory=False, msg='Checking for library %s in LIBDIR' % name)
+
+ if not result and dct['LIBPL']:
+ path = [dct['LIBPL']]
+ conf.to_log("\n\n# try again with -L$python_LIBPL (some systems don't install the python library in $prefix/lib)\n")
+ result = conf.check(lib=name, uselib='PYEMBED', libpath=path, mandatory=False, msg='Checking for library %s in python_LIBPL' % name)
+
+ if not result:
+ path = [os.path.join(dct['prefix'], "libs")]
+ conf.to_log("\n\n# try again with -L$prefix/libs, and pythonXY name rather than pythonX.Y (win32)\n")
+ result = conf.check(lib=name, uselib='PYEMBED', libpath=path, mandatory=False, msg='Checking for library %s in $prefix/libs' % name)
+
+ if result:
+ break # do not forget to set LIBPATH_PYEMBED
+
+ if result:
+ env.LIBPATH_PYEMBED = path
+ env.append_value('LIB_PYEMBED', [name])
+ else:
+ conf.to_log("\n\n### LIB NOT FOUND\n")
+
+ # under certain conditions, python extensions must link to
+ # python libraries, not just python embedding programs.
+ if Utils.is_win32 or dct['Py_ENABLE_SHARED']:
+ env.LIBPATH_PYEXT = env.LIBPATH_PYEMBED
+ env.LIB_PYEXT = env.LIB_PYEMBED
+
+ conf.to_log("Include path for Python extensions (found via distutils module): %r\n" % (dct['INCLUDEPY'],))
+ env.INCLUDES_PYEXT = [dct['INCLUDEPY']]
+ env.INCLUDES_PYEMBED = [dct['INCLUDEPY']]
+
+ # Code using the Python API needs to be compiled with -fno-strict-aliasing
+ if env.CC_NAME == 'gcc':
+ env.append_value('CFLAGS_PYEMBED', ['-fno-strict-aliasing'])
+ env.append_value('CFLAGS_PYEXT', ['-fno-strict-aliasing'])
+ if env.CXX_NAME == 'gcc':
+ env.append_value('CXXFLAGS_PYEMBED', ['-fno-strict-aliasing'])
+ env.append_value('CXXFLAGS_PYEXT', ['-fno-strict-aliasing'])
+
+ if env.CC_NAME == "msvc":
+ from distutils.msvccompiler import MSVCCompiler
+ dist_compiler = MSVCCompiler()
+ dist_compiler.initialize()
+ env.append_value('CFLAGS_PYEXT', dist_compiler.compile_options)
+ env.append_value('CXXFLAGS_PYEXT', dist_compiler.compile_options)
+ env.append_value('LINKFLAGS_PYEXT', dist_compiler.ldflags_shared)
+
+ # See if it compiles
+ conf.check(header_name='Python.h', define_name='HAVE_PYTHON_H', uselib='PYEMBED', fragment=FRAG, errmsg='Distutils not installed? Broken python installation? Get python-config now!')
+
+@conf
+def check_python_version(conf, minver=None):
+ """
+ Check if the python interpreter is found matching a given minimum version.
+ minver should be a tuple, eg. to check for python >= 2.4.2 pass (2,4,2) as minver.
+
+ If successful, PYTHON_VERSION is defined as 'MAJOR.MINOR' (eg. '2.4')
+ of the actual python version found, and PYTHONDIR and PYTHONARCHDIR
+ are defined, pointing to the site-packages directories appropriate for
+ this python version, where modules/packages/extensions should be
+ installed.
+
+ :param minver: minimum version
+ :type minver: tuple of int
+ """
+ assert minver is None or isinstance(minver, tuple)
+ pybin = conf.env.PYTHON
+ if not pybin:
+ conf.fatal('could not find the python executable')
+
+ # Get python version string
+ cmd = pybin + ['-c', 'import sys\nfor x in sys.version_info: print(str(x))']
+ Logs.debug('python: Running python command %r', cmd)
+ lines = conf.cmd_and_log(cmd).split()
+ assert len(lines) == 5, "found %r lines, expected 5: %r" % (len(lines), lines)
+ pyver_tuple = (int(lines[0]), int(lines[1]), int(lines[2]), lines[3], int(lines[4]))
+
+ # Compare python version with the minimum required
+ result = (minver is None) or (pyver_tuple >= minver)
+
+ if result:
+ # define useful environment variables
+ pyver = '.'.join([str(x) for x in pyver_tuple[:2]])
+ conf.env.PYTHON_VERSION = pyver
+
+ if 'PYTHONDIR' in conf.env:
+ # Check if --pythondir was specified
+ pydir = conf.env.PYTHONDIR
+ elif 'PYTHONDIR' in conf.environ:
+ # Check environment for PYTHONDIR
+ pydir = conf.environ['PYTHONDIR']
+ else:
+ # Finally, try to guess
+ if Utils.is_win32:
+ (python_LIBDEST, pydir) = conf.get_python_variables(
+ ["get_config_var('LIBDEST') or ''",
+ "get_python_lib(standard_lib=0) or ''"])
+ else:
+ python_LIBDEST = None
+ (pydir,) = conf.get_python_variables( ["get_python_lib(standard_lib=0, prefix=%r) or ''" % conf.env.PREFIX])
+ if python_LIBDEST is None:
+ if conf.env.LIBDIR:
+ python_LIBDEST = os.path.join(conf.env.LIBDIR, 'python' + pyver)
+ else:
+ python_LIBDEST = os.path.join(conf.env.PREFIX, 'lib', 'python' + pyver)
+
+ if 'PYTHONARCHDIR' in conf.env:
+ # Check if --pythonarchdir was specified
+ pyarchdir = conf.env.PYTHONARCHDIR
+ elif 'PYTHONARCHDIR' in conf.environ:
+ # Check environment for PYTHONDIR
+ pyarchdir = conf.environ['PYTHONARCHDIR']
+ else:
+ # Finally, try to guess
+ (pyarchdir, ) = conf.get_python_variables( ["get_python_lib(plat_specific=1, standard_lib=0, prefix=%r) or ''" % conf.env.PREFIX])
+ if not pyarchdir:
+ pyarchdir = pydir
+
+ if hasattr(conf, 'define'): # conf.define is added by the C tool, so may not exist
+ conf.define('PYTHONDIR', pydir)
+ conf.define('PYTHONARCHDIR', pyarchdir)
+
+ conf.env.PYTHONDIR = pydir
+ conf.env.PYTHONARCHDIR = pyarchdir
+
+ # Feedback
+ pyver_full = '.'.join(map(str, pyver_tuple[:3]))
+ if minver is None:
+ conf.msg('Checking for python version', pyver_full)
+ else:
+ minver_str = '.'.join(map(str, minver))
+ conf.msg('Checking for python version >= %s' % (minver_str,), pyver_full, color=result and 'GREEN' or 'YELLOW')
+
+ if not result:
+ conf.fatal('The python version is too old, expecting %r' % (minver,))
+
+PYTHON_MODULE_TEMPLATE = '''
+import %s as current_module
+version = getattr(current_module, '__version__', None)
+if version is not None:
+ print(str(version))
+else:
+ print('unknown version')
+'''
+
+@conf
+def check_python_module(conf, module_name, condition=''):
+ """
+ Check if the selected python interpreter can import the given python module::
+
+ def configure(conf):
+ conf.check_python_module('pygccxml')
+ conf.check_python_module('re', condition="ver > num(2, 0, 4) and ver <= num(3, 0, 0)")
+
+ :param module_name: module
+ :type module_name: string
+ """
+ msg = "Checking for python module %r" % module_name
+ if condition:
+ msg = '%s (%s)' % (msg, condition)
+ conf.start_msg(msg)
+ try:
+ ret = conf.cmd_and_log(conf.env.PYTHON + ['-c', PYTHON_MODULE_TEMPLATE % module_name])
+ except Errors.WafError:
+ conf.end_msg(False)
+ conf.fatal('Could not find the python module %r' % module_name)
+
+ ret = ret.strip()
+ if condition:
+ conf.end_msg(ret)
+ if ret == 'unknown version':
+ conf.fatal('Could not check the %s version' % module_name)
+
+ from distutils.version import LooseVersion
+ def num(*k):
+ if isinstance(k[0], int):
+ return LooseVersion('.'.join([str(x) for x in k]))
+ else:
+ return LooseVersion(k[0])
+ d = {'num': num, 'ver': LooseVersion(ret)}
+ ev = eval(condition, {}, d)
+ if not ev:
+ conf.fatal('The %s version does not satisfy the requirements' % module_name)
+ else:
+ if ret == 'unknown version':
+ conf.end_msg(True)
+ else:
+ conf.end_msg(ret)
+
+def configure(conf):
+ """
+ Detect the python interpreter
+ """
+ v = conf.env
+ if getattr(Options.options, 'pythondir', None):
+ v.PYTHONDIR = Options.options.pythondir
+ if getattr(Options.options, 'pythonarchdir', None):
+ v.PYTHONARCHDIR = Options.options.pythonarchdir
+ if getattr(Options.options, 'nopycache', None):
+ v.NOPYCACHE=Options.options.nopycache
+
+ if not v.PYTHON:
+ v.PYTHON = [getattr(Options.options, 'python', None) or sys.executable]
+ v.PYTHON = Utils.to_list(v.PYTHON)
+ conf.find_program('python', var='PYTHON')
+
+ v.PYFLAGS = ''
+ v.PYFLAGS_OPT = '-O'
+
+ v.PYC = getattr(Options.options, 'pyc', 1)
+ v.PYO = getattr(Options.options, 'pyo', 1)
+
+ try:
+ v.PYTAG = conf.cmd_and_log(conf.env.PYTHON + ['-c', "import imp;print(imp.get_tag())"]).strip()
+ except Errors.WafError:
+ pass
+
+def options(opt):
+ """
+ Add python-specific options
+ """
+ pyopt=opt.add_option_group("Python Options")
+ pyopt.add_option('--nopyc', dest = 'pyc', action='store_false', default=1,
+ help = 'Do not install bytecode compiled .pyc files (configuration) [Default:install]')
+ pyopt.add_option('--nopyo', dest='pyo', action='store_false', default=1,
+ help='Do not install optimised compiled .pyo files (configuration) [Default:install]')
+ pyopt.add_option('--nopycache',dest='nopycache', action='store_true',
+ help='Do not use __pycache__ directory to install objects [Default:auto]')
+ pyopt.add_option('--python', dest="python",
+ help='python binary to be used [Default: %s]' % sys.executable)
+ pyopt.add_option('--pythondir', dest='pythondir',
+ help='Installation path for python modules (py, platform-independent .py and .pyc files)')
+ pyopt.add_option('--pythonarchdir', dest='pythonarchdir',
+ help='Installation path for python extension (pyext, platform-dependent .so or .dylib files)')
+
diff --git a/Tools/qt5.py b/Tools/qt5.py
new file mode 100644
index 0000000..9f43280
--- /dev/null
+++ b/Tools/qt5.py
@@ -0,0 +1,796 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2006-2018 (ita)
+
+"""
+This tool helps with finding Qt5 tools and libraries,
+and also provides syntactic sugar for using Qt5 tools.
+
+The following snippet illustrates the tool usage::
+
+ def options(opt):
+ opt.load('compiler_cxx qt5')
+
+ def configure(conf):
+ conf.load('compiler_cxx qt5')
+
+ def build(bld):
+ bld(
+ features = 'qt5 cxx cxxprogram',
+ uselib = 'QT5CORE QT5GUI QT5OPENGL QT5SVG',
+ source = 'main.cpp textures.qrc aboutDialog.ui',
+ target = 'window',
+ )
+
+Here, the UI description and resource files will be processed
+to generate code.
+
+Usage
+=====
+
+Load the "qt5" tool.
+
+You also need to edit your sources accordingly:
+
+- the normal way of doing things is to have your C++ files
+ include the .moc file.
+ This is regarded as the best practice (and provides much faster
+ compilations).
+ It also implies that the include paths have beenset properly.
+
+- to have the include paths added automatically, use the following::
+
+ from waflib.TaskGen import feature, before_method, after_method
+ @feature('cxx')
+ @after_method('process_source')
+ @before_method('apply_incpaths')
+ def add_includes_paths(self):
+ incs = set(self.to_list(getattr(self, 'includes', '')))
+ for x in self.compiled_tasks:
+ incs.add(x.inputs[0].parent.path_from(self.path))
+ self.includes = sorted(incs)
+
+Note: another tool provides Qt processing that does not require
+.moc includes, see 'playground/slow_qt/'.
+
+A few options (--qt{dir,bin,...}) and environment variables
+(QT5_{ROOT,DIR,MOC,UIC,XCOMPILE}) allow finer tuning of the tool,
+tool path selection, etc; please read the source for more info.
+
+The detection uses pkg-config on Linux by default. To force static library detection use:
+QT5_XCOMPILE=1 QT5_FORCE_STATIC=1 waf configure
+"""
+
+from __future__ import with_statement
+
+try:
+ from xml.sax import make_parser
+ from xml.sax.handler import ContentHandler
+except ImportError:
+ has_xml = False
+ ContentHandler = object
+else:
+ has_xml = True
+
+import os, sys, re
+from waflib.Tools import cxx
+from waflib import Task, Utils, Options, Errors, Context
+from waflib.TaskGen import feature, after_method, extension, before_method
+from waflib.Configure import conf
+from waflib import Logs
+
+MOC_H = ['.h', '.hpp', '.hxx', '.hh']
+"""
+File extensions associated to .moc files
+"""
+
+EXT_RCC = ['.qrc']
+"""
+File extension for the resource (.qrc) files
+"""
+
+EXT_UI = ['.ui']
+"""
+File extension for the user interface (.ui) files
+"""
+
+EXT_QT5 = ['.cpp', '.cc', '.cxx', '.C']
+"""
+File extensions of C++ files that may require a .moc processing
+"""
+
+class qxx(Task.classes['cxx']):
+ """
+ Each C++ file can have zero or several .moc files to create.
+ They are known only when the files are scanned (preprocessor)
+ To avoid scanning the c++ files each time (parsing C/C++), the results
+ are retrieved from the task cache (bld.node_deps/bld.raw_deps).
+ The moc tasks are also created *dynamically* during the build.
+ """
+
+ def __init__(self, *k, **kw):
+ Task.Task.__init__(self, *k, **kw)
+ self.moc_done = 0
+
+ def runnable_status(self):
+ """
+ Compute the task signature to make sure the scanner was executed. Create the
+ moc tasks by using :py:meth:`waflib.Tools.qt5.qxx.add_moc_tasks` (if necessary),
+ then postpone the task execution (there is no need to recompute the task signature).
+ """
+ if self.moc_done:
+ return Task.Task.runnable_status(self)
+ else:
+ for t in self.run_after:
+ if not t.hasrun:
+ return Task.ASK_LATER
+ self.add_moc_tasks()
+ return Task.Task.runnable_status(self)
+
+ def create_moc_task(self, h_node, m_node):
+ """
+ If several libraries use the same classes, it is possible that moc will run several times (Issue 1318)
+ It is not possible to change the file names, but we can assume that the moc transformation will be identical,
+ and the moc tasks can be shared in a global cache.
+ """
+ try:
+ moc_cache = self.generator.bld.moc_cache
+ except AttributeError:
+ moc_cache = self.generator.bld.moc_cache = {}
+
+ try:
+ return moc_cache[h_node]
+ except KeyError:
+ tsk = moc_cache[h_node] = Task.classes['moc'](env=self.env, generator=self.generator)
+ tsk.set_inputs(h_node)
+ tsk.set_outputs(m_node)
+ tsk.env.append_unique('MOC_FLAGS', '-i')
+
+ if self.generator:
+ self.generator.tasks.append(tsk)
+
+ # direct injection in the build phase (safe because called from the main thread)
+ gen = self.generator.bld.producer
+ gen.outstanding.append(tsk)
+ gen.total += 1
+
+ return tsk
+
+ else:
+ # remove the signature, it must be recomputed with the moc task
+ delattr(self, 'cache_sig')
+
+ def add_moc_tasks(self):
+ """
+ Creates moc tasks by looking in the list of file dependencies ``bld.raw_deps[self.uid()]``
+ """
+ node = self.inputs[0]
+ bld = self.generator.bld
+
+ try:
+ # compute the signature once to know if there is a moc file to create
+ self.signature()
+ except KeyError:
+ # the moc file may be referenced somewhere else
+ pass
+ else:
+ # remove the signature, it must be recomputed with the moc task
+ delattr(self, 'cache_sig')
+
+ include_nodes = [node.parent] + self.generator.includes_nodes
+
+ moctasks = []
+ mocfiles = set()
+ for d in bld.raw_deps.get(self.uid(), []):
+ if not d.endswith('.moc'):
+ continue
+
+ # process that base.moc only once
+ if d in mocfiles:
+ continue
+ mocfiles.add(d)
+
+ # find the source associated with the moc file
+ h_node = None
+ base2 = d[:-4]
+
+ # foo.moc from foo.cpp
+ prefix = node.name[:node.name.rfind('.')]
+ if base2 == prefix:
+ h_node = node
+ else:
+ # this deviates from the standard
+ # if bar.cpp includes foo.moc, then assume it is from foo.h
+ for x in include_nodes:
+ for e in MOC_H:
+ h_node = x.find_node(base2 + e)
+ if h_node:
+ break
+ else:
+ continue
+ break
+ if h_node:
+ m_node = h_node.change_ext('.moc')
+ else:
+ raise Errors.WafError('No source found for %r which is a moc file' % d)
+
+ # create the moc task
+ task = self.create_moc_task(h_node, m_node)
+ moctasks.append(task)
+
+ # simple scheduler dependency: run the moc task before others
+ self.run_after.update(set(moctasks))
+ self.moc_done = 1
+
+class trans_update(Task.Task):
+ """Updates a .ts files from a list of C++ files"""
+ run_str = '${QT_LUPDATE} ${SRC} -ts ${TGT}'
+ color = 'BLUE'
+
+class XMLHandler(ContentHandler):
+ """
+ Parses ``.qrc`` files
+ """
+ def __init__(self):
+ ContentHandler.__init__(self)
+ self.buf = []
+ self.files = []
+ def startElement(self, name, attrs):
+ if name == 'file':
+ self.buf = []
+ def endElement(self, name):
+ if name == 'file':
+ self.files.append(str(''.join(self.buf)))
+ def characters(self, cars):
+ self.buf.append(cars)
+
+@extension(*EXT_RCC)
+def create_rcc_task(self, node):
+ "Creates rcc and cxx tasks for ``.qrc`` files"
+ rcnode = node.change_ext('_rc.%d.cpp' % self.idx)
+ self.create_task('rcc', node, rcnode)
+ cpptask = self.create_task('cxx', rcnode, rcnode.change_ext('.o'))
+ try:
+ self.compiled_tasks.append(cpptask)
+ except AttributeError:
+ self.compiled_tasks = [cpptask]
+ return cpptask
+
+@extension(*EXT_UI)
+def create_uic_task(self, node):
+ "Create uic tasks for user interface ``.ui`` definition files"
+
+ """
+ If UIC file is used in more than one bld, we would have a conflict in parallel execution
+ It is not possible to change the file names (like .self.idx. as for objects) as they have
+ to be referenced by the source file, but we can assume that the transformation will be identical
+ and the tasks can be shared in a global cache.
+ """
+ try:
+ uic_cache = self.bld.uic_cache
+ except AttributeError:
+ uic_cache = self.bld.uic_cache = {}
+
+ if node not in uic_cache:
+ uictask = uic_cache[node] = self.create_task('ui5', node)
+ uictask.outputs = [node.parent.find_or_declare(self.env.ui_PATTERN % node.name[:-3])]
+
+@extension('.ts')
+def add_lang(self, node):
+ """Adds all the .ts file into ``self.lang``"""
+ self.lang = self.to_list(getattr(self, 'lang', [])) + [node]
+
+@feature('qt5')
+@before_method('process_source')
+def process_mocs(self):
+ """
+ Processes MOC files included in headers::
+
+ def build(bld):
+ bld.program(features='qt5', source='main.cpp', target='app', use='QT5CORE', moc='foo.h')
+
+ The build will run moc on foo.h to create moc_foo.n.cpp. The number in the file name
+ is provided to avoid name clashes when the same headers are used by several targets.
+ """
+ lst = self.to_nodes(getattr(self, 'moc', []))
+ self.source = self.to_list(getattr(self, 'source', []))
+ for x in lst:
+ prefix = x.name[:x.name.rfind('.')] # foo.h -> foo
+ moc_target = 'moc_%s.%d.cpp' % (prefix, self.idx)
+ moc_node = x.parent.find_or_declare(moc_target)
+ self.source.append(moc_node)
+
+ self.create_task('moc', x, moc_node)
+
+@feature('qt5')
+@after_method('apply_link')
+def apply_qt5(self):
+ """
+ Adds MOC_FLAGS which may be necessary for moc::
+
+ def build(bld):
+ bld.program(features='qt5', source='main.cpp', target='app', use='QT5CORE')
+
+ The additional parameters are:
+
+ :param lang: list of translation files (\\*.ts) to process
+ :type lang: list of :py:class:`waflib.Node.Node` or string without the .ts extension
+ :param update: whether to process the C++ files to update the \\*.ts files (use **waf --translate**)
+ :type update: bool
+ :param langname: if given, transform the \\*.ts files into a .qrc files to include in the binary file
+ :type langname: :py:class:`waflib.Node.Node` or string without the .qrc extension
+ """
+ if getattr(self, 'lang', None):
+ qmtasks = []
+ for x in self.to_list(self.lang):
+ if isinstance(x, str):
+ x = self.path.find_resource(x + '.ts')
+ qmtasks.append(self.create_task('ts2qm', x, x.change_ext('.%d.qm' % self.idx)))
+
+ if getattr(self, 'update', None) and Options.options.trans_qt5:
+ cxxnodes = [a.inputs[0] for a in self.compiled_tasks] + [
+ a.inputs[0] for a in self.tasks if a.inputs and a.inputs[0].name.endswith('.ui')]
+ for x in qmtasks:
+ self.create_task('trans_update', cxxnodes, x.inputs)
+
+ if getattr(self, 'langname', None):
+ qmnodes = [x.outputs[0] for x in qmtasks]
+ rcnode = self.langname
+ if isinstance(rcnode, str):
+ rcnode = self.path.find_or_declare(rcnode + ('.%d.qrc' % self.idx))
+ t = self.create_task('qm2rcc', qmnodes, rcnode)
+ k = create_rcc_task(self, t.outputs[0])
+ self.link_task.inputs.append(k.outputs[0])
+
+ lst = []
+ for flag in self.to_list(self.env.CXXFLAGS):
+ if len(flag) < 2:
+ continue
+ f = flag[0:2]
+ if f in ('-D', '-I', '/D', '/I'):
+ if (f[0] == '/'):
+ lst.append('-' + flag[1:])
+ else:
+ lst.append(flag)
+ self.env.append_value('MOC_FLAGS', lst)
+
+@extension(*EXT_QT5)
+def cxx_hook(self, node):
+ """
+ Re-maps C++ file extensions to the :py:class:`waflib.Tools.qt5.qxx` task.
+ """
+ return self.create_compiled_task('qxx', node)
+
+class rcc(Task.Task):
+ """
+ Processes ``.qrc`` files
+ """
+ color = 'BLUE'
+ run_str = '${QT_RCC} -name ${tsk.rcname()} ${SRC[0].abspath()} ${RCC_ST} -o ${TGT}'
+ ext_out = ['.h']
+
+ def rcname(self):
+ return os.path.splitext(self.inputs[0].name)[0]
+
+ def scan(self):
+ """Parse the *.qrc* files"""
+ if not has_xml:
+ Logs.error('No xml.sax support was found, rcc dependencies will be incomplete!')
+ return ([], [])
+
+ parser = make_parser()
+ curHandler = XMLHandler()
+ parser.setContentHandler(curHandler)
+ with open(self.inputs[0].abspath(), 'r') as f:
+ parser.parse(f)
+
+ nodes = []
+ names = []
+ root = self.inputs[0].parent
+ for x in curHandler.files:
+ nd = root.find_resource(x)
+ if nd:
+ nodes.append(nd)
+ else:
+ names.append(x)
+ return (nodes, names)
+
+ def quote_flag(self, x):
+ """
+ Override Task.quote_flag. QT parses the argument files
+ differently than cl.exe and link.exe
+
+ :param x: flag
+ :type x: string
+ :return: quoted flag
+ :rtype: string
+ """
+ return x
+
+
+class moc(Task.Task):
+ """
+ Creates ``.moc`` files
+ """
+ color = 'BLUE'
+ run_str = '${QT_MOC} ${MOC_FLAGS} ${MOCCPPPATH_ST:INCPATHS} ${MOCDEFINES_ST:DEFINES} ${SRC} ${MOC_ST} ${TGT}'
+
+ def quote_flag(self, x):
+ """
+ Override Task.quote_flag. QT parses the argument files
+ differently than cl.exe and link.exe
+
+ :param x: flag
+ :type x: string
+ :return: quoted flag
+ :rtype: string
+ """
+ return x
+
+
+class ui5(Task.Task):
+ """
+ Processes ``.ui`` files
+ """
+ color = 'BLUE'
+ run_str = '${QT_UIC} ${SRC} -o ${TGT}'
+ ext_out = ['.h']
+
+class ts2qm(Task.Task):
+ """
+ Generates ``.qm`` files from ``.ts`` files
+ """
+ color = 'BLUE'
+ run_str = '${QT_LRELEASE} ${QT_LRELEASE_FLAGS} ${SRC} -qm ${TGT}'
+
+class qm2rcc(Task.Task):
+ """
+ Generates ``.qrc`` files from ``.qm`` files
+ """
+ color = 'BLUE'
+ after = 'ts2qm'
+ def run(self):
+ """Create a qrc file including the inputs"""
+ txt = '\n'.join(['<file>%s</file>' % k.path_from(self.outputs[0].parent) for k in self.inputs])
+ code = '<!DOCTYPE RCC><RCC version="1.0">\n<qresource>\n%s\n</qresource>\n</RCC>' % txt
+ self.outputs[0].write(code)
+
+def configure(self):
+ """
+ Besides the configuration options, the environment variable QT5_ROOT may be used
+ to give the location of the qt5 libraries (absolute path).
+
+ The detection uses the program ``pkg-config`` through :py:func:`waflib.Tools.config_c.check_cfg`
+ """
+ self.find_qt5_binaries()
+ self.set_qt5_libs_dir()
+ self.set_qt5_libs_to_check()
+ self.set_qt5_defines()
+ self.find_qt5_libraries()
+ self.add_qt5_rpath()
+ self.simplify_qt5_libs()
+
+ # warn about this during the configuration too
+ if not has_xml:
+ Logs.error('No xml.sax support was found, rcc dependencies will be incomplete!')
+
+ if 'COMPILER_CXX' not in self.env:
+ self.fatal('No CXX compiler defined: did you forget to configure compiler_cxx first?')
+
+ # Qt5 may be compiled with '-reduce-relocations' which requires dependent programs to have -fPIE or -fPIC?
+ frag = '#include <QApplication>\nint main(int argc, char **argv) {return 0;}\n'
+ uses = 'QT5CORE QT5WIDGETS QT5GUI'
+ for flag in [[], '-fPIE', '-fPIC', '-std=c++11' , ['-std=c++11', '-fPIE'], ['-std=c++11', '-fPIC']]:
+ msg = 'See if Qt files compile '
+ if flag:
+ msg += 'with %s' % flag
+ try:
+ self.check(features='qt5 cxx', use=uses, uselib_store='qt5', cxxflags=flag, fragment=frag, msg=msg)
+ except self.errors.ConfigurationError:
+ pass
+ else:
+ break
+ else:
+ self.fatal('Could not build a simple Qt application')
+
+ # FreeBSD does not add /usr/local/lib and the pkg-config files do not provide it either :-/
+ if Utils.unversioned_sys_platform() == 'freebsd':
+ frag = '#include <QApplication>\nint main(int argc, char **argv) { QApplication app(argc, argv); return NULL != (void*) (&app);}\n'
+ try:
+ self.check(features='qt5 cxx cxxprogram', use=uses, fragment=frag, msg='Can we link Qt programs on FreeBSD directly?')
+ except self.errors.ConfigurationError:
+ self.check(features='qt5 cxx cxxprogram', use=uses, uselib_store='qt5', libpath='/usr/local/lib', fragment=frag, msg='Is /usr/local/lib required?')
+
+@conf
+def find_qt5_binaries(self):
+ """
+ Detects Qt programs such as qmake, moc, uic, lrelease
+ """
+ env = self.env
+ opt = Options.options
+
+ qtdir = getattr(opt, 'qtdir', '')
+ qtbin = getattr(opt, 'qtbin', '')
+
+ paths = []
+
+ if qtdir:
+ qtbin = os.path.join(qtdir, 'bin')
+
+ # the qt directory has been given from QT5_ROOT - deduce the qt binary path
+ if not qtdir:
+ qtdir = self.environ.get('QT5_ROOT', '')
+ qtbin = self.environ.get('QT5_BIN') or os.path.join(qtdir, 'bin')
+
+ if qtbin:
+ paths = [qtbin]
+
+ # no qtdir, look in the path and in /usr/local/Trolltech
+ if not qtdir:
+ paths = self.environ.get('PATH', '').split(os.pathsep)
+ paths.extend(['/usr/share/qt5/bin', '/usr/local/lib/qt5/bin'])
+ try:
+ lst = Utils.listdir('/usr/local/Trolltech/')
+ except OSError:
+ pass
+ else:
+ if lst:
+ lst.sort()
+ lst.reverse()
+
+ # keep the highest version
+ qtdir = '/usr/local/Trolltech/%s/' % lst[0]
+ qtbin = os.path.join(qtdir, 'bin')
+ paths.append(qtbin)
+
+ # at the end, try to find qmake in the paths given
+ # keep the one with the highest version
+ cand = None
+ prev_ver = ['5', '0', '0']
+ for qmk in ('qmake-qt5', 'qmake5', 'qmake'):
+ try:
+ qmake = self.find_program(qmk, path_list=paths)
+ except self.errors.ConfigurationError:
+ pass
+ else:
+ try:
+ version = self.cmd_and_log(qmake + ['-query', 'QT_VERSION']).strip()
+ except self.errors.WafError:
+ pass
+ else:
+ if version:
+ new_ver = version.split('.')
+ if new_ver > prev_ver:
+ cand = qmake
+ prev_ver = new_ver
+
+ # qmake could not be found easily, rely on qtchooser
+ if not cand:
+ try:
+ self.find_program('qtchooser')
+ except self.errors.ConfigurationError:
+ pass
+ else:
+ cmd = self.env.QTCHOOSER + ['-qt=5', '-run-tool=qmake']
+ try:
+ version = self.cmd_and_log(cmd + ['-query', 'QT_VERSION'])
+ except self.errors.WafError:
+ pass
+ else:
+ cand = cmd
+
+ if cand:
+ self.env.QMAKE = cand
+ else:
+ self.fatal('Could not find qmake for qt5')
+
+ self.env.QT_HOST_BINS = qtbin = self.cmd_and_log(self.env.QMAKE + ['-query', 'QT_HOST_BINS']).strip()
+ paths.insert(0, qtbin)
+
+ def find_bin(lst, var):
+ if var in env:
+ return
+ for f in lst:
+ try:
+ ret = self.find_program(f, path_list=paths)
+ except self.errors.ConfigurationError:
+ pass
+ else:
+ env[var]=ret
+ break
+
+ find_bin(['uic-qt5', 'uic'], 'QT_UIC')
+ if not env.QT_UIC:
+ self.fatal('cannot find the uic compiler for qt5')
+
+ self.start_msg('Checking for uic version')
+ uicver = self.cmd_and_log(env.QT_UIC + ['-version'], output=Context.BOTH)
+ uicver = ''.join(uicver).strip()
+ uicver = uicver.replace('Qt User Interface Compiler ','').replace('User Interface Compiler for Qt', '')
+ self.end_msg(uicver)
+ if uicver.find(' 3.') != -1 or uicver.find(' 4.') != -1:
+ self.fatal('this uic compiler is for qt3 or qt4, add uic for qt5 to your path')
+
+ find_bin(['moc-qt5', 'moc'], 'QT_MOC')
+ find_bin(['rcc-qt5', 'rcc'], 'QT_RCC')
+ find_bin(['lrelease-qt5', 'lrelease'], 'QT_LRELEASE')
+ find_bin(['lupdate-qt5', 'lupdate'], 'QT_LUPDATE')
+
+ env.UIC_ST = '%s -o %s'
+ env.MOC_ST = '-o'
+ env.ui_PATTERN = 'ui_%s.h'
+ env.QT_LRELEASE_FLAGS = ['-silent']
+ env.MOCCPPPATH_ST = '-I%s'
+ env.MOCDEFINES_ST = '-D%s'
+
+@conf
+def set_qt5_libs_dir(self):
+ env = self.env
+ qtlibs = getattr(Options.options, 'qtlibs', None) or self.environ.get('QT5_LIBDIR')
+ if not qtlibs:
+ try:
+ qtlibs = self.cmd_and_log(env.QMAKE + ['-query', 'QT_INSTALL_LIBS']).strip()
+ except Errors.WafError:
+ qtdir = self.cmd_and_log(env.QMAKE + ['-query', 'QT_INSTALL_PREFIX']).strip()
+ qtlibs = os.path.join(qtdir, 'lib')
+ self.msg('Found the Qt5 libraries in', qtlibs)
+ env.QTLIBS = qtlibs
+
+@conf
+def find_single_qt5_lib(self, name, uselib, qtlibs, qtincludes, force_static):
+ env = self.env
+ if force_static:
+ exts = ('.a', '.lib')
+ prefix = 'STLIB'
+ else:
+ exts = ('.so', '.lib')
+ prefix = 'LIB'
+
+ def lib_names():
+ for x in exts:
+ for k in ('', '5') if Utils.is_win32 else ['']:
+ for p in ('lib', ''):
+ yield (p, name, k, x)
+
+ for tup in lib_names():
+ k = ''.join(tup)
+ path = os.path.join(qtlibs, k)
+ if os.path.exists(path):
+ if env.DEST_OS == 'win32':
+ libval = ''.join(tup[:-1])
+ else:
+ libval = name
+ env.append_unique(prefix + '_' + uselib, libval)
+ env.append_unique('%sPATH_%s' % (prefix, uselib), qtlibs)
+ env.append_unique('INCLUDES_' + uselib, qtincludes)
+ env.append_unique('INCLUDES_' + uselib, os.path.join(qtincludes, name.replace('Qt5', 'Qt')))
+ return k
+ return False
+
+@conf
+def find_qt5_libraries(self):
+ env = self.env
+
+ qtincludes = self.environ.get('QT5_INCLUDES') or self.cmd_and_log(env.QMAKE + ['-query', 'QT_INSTALL_HEADERS']).strip()
+ force_static = self.environ.get('QT5_FORCE_STATIC')
+ try:
+ if self.environ.get('QT5_XCOMPILE'):
+ self.fatal('QT5_XCOMPILE Disables pkg-config detection')
+ self.check_cfg(atleast_pkgconfig_version='0.1')
+ except self.errors.ConfigurationError:
+ for i in self.qt5_vars:
+ uselib = i.upper()
+ if Utils.unversioned_sys_platform() == 'darwin':
+ # Since at least qt 4.7.3 each library locates in separate directory
+ fwk = i.replace('Qt5', 'Qt')
+ frameworkName = fwk + '.framework'
+
+ qtDynamicLib = os.path.join(env.QTLIBS, frameworkName, fwk)
+ if os.path.exists(qtDynamicLib):
+ env.append_unique('FRAMEWORK_' + uselib, fwk)
+ env.append_unique('FRAMEWORKPATH_' + uselib, env.QTLIBS)
+ self.msg('Checking for %s' % i, qtDynamicLib, 'GREEN')
+ else:
+ self.msg('Checking for %s' % i, False, 'YELLOW')
+ env.append_unique('INCLUDES_' + uselib, os.path.join(env.QTLIBS, frameworkName, 'Headers'))
+ else:
+ ret = self.find_single_qt5_lib(i, uselib, env.QTLIBS, qtincludes, force_static)
+ if not force_static and not ret:
+ ret = self.find_single_qt5_lib(i, uselib, env.QTLIBS, qtincludes, True)
+ self.msg('Checking for %s' % i, ret, 'GREEN' if ret else 'YELLOW')
+ else:
+ path = '%s:%s:%s/pkgconfig:/usr/lib/qt5/lib/pkgconfig:/opt/qt5/lib/pkgconfig:/usr/lib/qt5/lib:/opt/qt5/lib' % (
+ self.environ.get('PKG_CONFIG_PATH', ''), env.QTLIBS, env.QTLIBS)
+ for i in self.qt5_vars:
+ self.check_cfg(package=i, args='--cflags --libs', mandatory=False, force_static=force_static, pkg_config_path=path)
+
+@conf
+def simplify_qt5_libs(self):
+ """
+ Since library paths make really long command-lines,
+ and since everything depends on qtcore, remove the qtcore ones from qtgui, etc
+ """
+ env = self.env
+ def process_lib(vars_, coreval):
+ for d in vars_:
+ var = d.upper()
+ if var == 'QTCORE':
+ continue
+
+ value = env['LIBPATH_'+var]
+ if value:
+ core = env[coreval]
+ accu = []
+ for lib in value:
+ if lib in core:
+ continue
+ accu.append(lib)
+ env['LIBPATH_'+var] = accu
+ process_lib(self.qt5_vars, 'LIBPATH_QTCORE')
+
+@conf
+def add_qt5_rpath(self):
+ """
+ Defines rpath entries for Qt libraries
+ """
+ env = self.env
+ if getattr(Options.options, 'want_rpath', False):
+ def process_rpath(vars_, coreval):
+ for d in vars_:
+ var = d.upper()
+ value = env['LIBPATH_' + var]
+ if value:
+ core = env[coreval]
+ accu = []
+ for lib in value:
+ if var != 'QTCORE':
+ if lib in core:
+ continue
+ accu.append('-Wl,--rpath='+lib)
+ env['RPATH_' + var] = accu
+ process_rpath(self.qt5_vars, 'LIBPATH_QTCORE')
+
+@conf
+def set_qt5_libs_to_check(self):
+ self.qt5_vars = Utils.to_list(getattr(self, 'qt5_vars', []))
+ if not self.qt5_vars:
+ dirlst = Utils.listdir(self.env.QTLIBS)
+
+ pat = self.env.cxxshlib_PATTERN
+ if Utils.is_win32:
+ pat = pat.replace('.dll', '.lib')
+ if self.environ.get('QT5_FORCE_STATIC'):
+ pat = self.env.cxxstlib_PATTERN
+ if Utils.unversioned_sys_platform() == 'darwin':
+ pat = r"%s\.framework"
+ re_qt = re.compile(pat%'Qt5?(?P<name>.*)'+'$')
+ for x in dirlst:
+ m = re_qt.match(x)
+ if m:
+ self.qt5_vars.append("Qt5%s" % m.group('name'))
+ if not self.qt5_vars:
+ self.fatal('cannot find any Qt5 library (%r)' % self.env.QTLIBS)
+
+ qtextralibs = getattr(Options.options, 'qtextralibs', None)
+ if qtextralibs:
+ self.qt5_vars.extend(qtextralibs.split(','))
+
+@conf
+def set_qt5_defines(self):
+ if sys.platform != 'win32':
+ return
+ for x in self.qt5_vars:
+ y=x.replace('Qt5', 'Qt')[2:].upper()
+ self.env.append_unique('DEFINES_%s' % x.upper(), 'QT_%s_LIB' % y)
+
+def options(opt):
+ """
+ Command-line options
+ """
+ opt.add_option('--want-rpath', action='store_true', default=False, dest='want_rpath', help='enable the rpath for qt libraries')
+ for i in 'qtdir qtbin qtlibs'.split():
+ opt.add_option('--'+i, type='string', default='', dest=i)
+
+ opt.add_option('--translate', action='store_true', help='collect translation strings', dest='trans_qt5', default=False)
+ opt.add_option('--qtextralibs', type='string', default='', dest='qtextralibs', help='additional qt libraries on the system to add to default ones, comma separated')
+
diff --git a/Tools/ruby.py b/Tools/ruby.py
new file mode 100644
index 0000000..8d92a79
--- /dev/null
+++ b/Tools/ruby.py
@@ -0,0 +1,186 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# daniel.svensson at purplescout.se 2008
+# Thomas Nagy 2016-2018 (ita)
+
+"""
+Support for Ruby extensions. A C/C++ compiler is required::
+
+ def options(opt):
+ opt.load('compiler_c ruby')
+ def configure(conf):
+ conf.load('compiler_c ruby')
+ conf.check_ruby_version((1,8,0))
+ conf.check_ruby_ext_devel()
+ conf.check_ruby_module('libxml')
+ def build(bld):
+ bld(
+ features = 'c cshlib rubyext',
+ source = 'rb_mytest.c',
+ target = 'mytest_ext',
+ install_path = '${ARCHDIR_RUBY}')
+ bld.install_files('${LIBDIR_RUBY}', 'Mytest.rb')
+"""
+
+import os
+from waflib import Errors, Options, Task, Utils
+from waflib.TaskGen import before_method, feature, extension
+from waflib.Configure import conf
+
+@feature('rubyext')
+@before_method('apply_incpaths', 'process_source', 'apply_bundle', 'apply_link')
+def init_rubyext(self):
+ """
+ Add required variables for ruby extensions
+ """
+ self.install_path = '${ARCHDIR_RUBY}'
+ self.uselib = self.to_list(getattr(self, 'uselib', ''))
+ if not 'RUBY' in self.uselib:
+ self.uselib.append('RUBY')
+ if not 'RUBYEXT' in self.uselib:
+ self.uselib.append('RUBYEXT')
+
+@feature('rubyext')
+@before_method('apply_link', 'propagate_uselib_vars')
+def apply_ruby_so_name(self):
+ """
+ Strip the *lib* prefix from ruby extensions
+ """
+ self.env.cshlib_PATTERN = self.env.cxxshlib_PATTERN = self.env.rubyext_PATTERN
+
+@conf
+def check_ruby_version(self, minver=()):
+ """
+ Checks if ruby is installed.
+ If installed the variable RUBY will be set in environment.
+ The ruby binary can be overridden by ``--with-ruby-binary`` command-line option.
+ """
+
+ ruby = self.find_program('ruby', var='RUBY', value=Options.options.rubybinary)
+
+ try:
+ version = self.cmd_and_log(ruby + ['-e', 'puts defined?(VERSION) ? VERSION : RUBY_VERSION']).strip()
+ except Errors.WafError:
+ self.fatal('could not determine ruby version')
+ self.env.RUBY_VERSION = version
+
+ try:
+ ver = tuple(map(int, version.split('.')))
+ except Errors.WafError:
+ self.fatal('unsupported ruby version %r' % version)
+
+ cver = ''
+ if minver:
+ cver = '> ' + '.'.join(str(x) for x in minver)
+ if ver < minver:
+ self.fatal('ruby is too old %r' % ver)
+
+ self.msg('Checking for ruby version %s' % cver, version)
+
+@conf
+def check_ruby_ext_devel(self):
+ """
+ Check if a ruby extension can be created
+ """
+ if not self.env.RUBY:
+ self.fatal('ruby detection is required first')
+
+ if not self.env.CC_NAME and not self.env.CXX_NAME:
+ self.fatal('load a c/c++ compiler first')
+
+ version = tuple(map(int, self.env.RUBY_VERSION.split(".")))
+
+ def read_out(cmd):
+ return Utils.to_list(self.cmd_and_log(self.env.RUBY + ['-rrbconfig', '-e', cmd]))
+
+ def read_config(key):
+ return read_out('puts RbConfig::CONFIG[%r]' % key)
+
+ cpppath = archdir = read_config('archdir')
+
+ if version >= (1, 9, 0):
+ ruby_hdrdir = read_config('rubyhdrdir')
+ cpppath += ruby_hdrdir
+ if version >= (2, 0, 0):
+ cpppath += read_config('rubyarchhdrdir')
+ cpppath += [os.path.join(ruby_hdrdir[0], read_config('arch')[0])]
+
+ self.check(header_name='ruby.h', includes=cpppath, errmsg='could not find ruby header file', link_header_test=False)
+
+ self.env.LIBPATH_RUBYEXT = read_config('libdir')
+ self.env.LIBPATH_RUBYEXT += archdir
+ self.env.INCLUDES_RUBYEXT = cpppath
+ self.env.CFLAGS_RUBYEXT = read_config('CCDLFLAGS')
+ self.env.rubyext_PATTERN = '%s.' + read_config('DLEXT')[0]
+
+ # ok this is really stupid, but the command and flags are combined.
+ # so we try to find the first argument...
+ flags = read_config('LDSHARED')
+ while flags and flags[0][0] != '-':
+ flags = flags[1:]
+
+ # we also want to strip out the deprecated ppc flags
+ if len(flags) > 1 and flags[1] == "ppc":
+ flags = flags[2:]
+
+ self.env.LINKFLAGS_RUBYEXT = flags
+ self.env.LINKFLAGS_RUBYEXT += read_config('LIBS')
+ self.env.LINKFLAGS_RUBYEXT += read_config('LIBRUBYARG_SHARED')
+
+ if Options.options.rubyarchdir:
+ self.env.ARCHDIR_RUBY = Options.options.rubyarchdir
+ else:
+ self.env.ARCHDIR_RUBY = read_config('sitearchdir')[0]
+
+ if Options.options.rubylibdir:
+ self.env.LIBDIR_RUBY = Options.options.rubylibdir
+ else:
+ self.env.LIBDIR_RUBY = read_config('sitelibdir')[0]
+
+@conf
+def check_ruby_module(self, module_name):
+ """
+ Check if the selected ruby interpreter can require the given ruby module::
+
+ def configure(conf):
+ conf.check_ruby_module('libxml')
+
+ :param module_name: module
+ :type module_name: string
+ """
+ self.start_msg('Ruby module %s' % module_name)
+ try:
+ self.cmd_and_log(self.env.RUBY + ['-e', 'require \'%s\';puts 1' % module_name])
+ except Errors.WafError:
+ self.end_msg(False)
+ self.fatal('Could not find the ruby module %r' % module_name)
+ self.end_msg(True)
+
+@extension('.rb')
+def process(self, node):
+ return self.create_task('run_ruby', node)
+
+class run_ruby(Task.Task):
+ """
+ Task to run ruby files detected by file extension .rb::
+
+ def options(opt):
+ opt.load('ruby')
+
+ def configure(ctx):
+ ctx.check_ruby_version()
+
+ def build(bld):
+ bld.env.RBFLAGS = '-e puts "hello world"'
+ bld(source='a_ruby_file.rb')
+ """
+ run_str = '${RUBY} ${RBFLAGS} -I ${SRC[0].parent.abspath()} ${SRC}'
+
+def options(opt):
+ """
+ Add the ``--with-ruby-archdir``, ``--with-ruby-libdir`` and ``--with-ruby-binary`` options
+ """
+ opt.add_option('--with-ruby-archdir', type='string', dest='rubyarchdir', help='Specify directory where to install arch specific files')
+ opt.add_option('--with-ruby-libdir', type='string', dest='rubylibdir', help='Specify alternate ruby library path')
+ opt.add_option('--with-ruby-binary', type='string', dest='rubybinary', help='Specify alternate ruby binary')
+
diff --git a/Tools/suncc.py b/Tools/suncc.py
new file mode 100644
index 0000000..33d34fc
--- /dev/null
+++ b/Tools/suncc.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2006-2018 (ita)
+# Ralf Habacker, 2006 (rh)
+
+from waflib import Errors
+from waflib.Tools import ccroot, ar
+from waflib.Configure import conf
+
+@conf
+def find_scc(conf):
+ """
+ Detects the Sun C compiler
+ """
+ v = conf.env
+ cc = conf.find_program('cc', var='CC')
+ try:
+ conf.cmd_and_log(cc + ['-flags'])
+ except Errors.WafError:
+ conf.fatal('%r is not a Sun compiler' % cc)
+ v.CC_NAME = 'sun'
+ conf.get_suncc_version(cc)
+
+@conf
+def scc_common_flags(conf):
+ """
+ Flags required for executing the sun C compiler
+ """
+ v = conf.env
+
+ v.CC_SRC_F = []
+ v.CC_TGT_F = ['-c', '-o', '']
+
+ if not v.LINK_CC:
+ v.LINK_CC = v.CC
+
+ v.CCLNK_SRC_F = ''
+ v.CCLNK_TGT_F = ['-o', '']
+ v.CPPPATH_ST = '-I%s'
+ v.DEFINES_ST = '-D%s'
+
+ v.LIB_ST = '-l%s' # template for adding libs
+ v.LIBPATH_ST = '-L%s' # template for adding libpaths
+ v.STLIB_ST = '-l%s'
+ v.STLIBPATH_ST = '-L%s'
+
+ v.SONAME_ST = '-Wl,-h,%s'
+ v.SHLIB_MARKER = '-Bdynamic'
+ v.STLIB_MARKER = '-Bstatic'
+
+ v.cprogram_PATTERN = '%s'
+
+ v.CFLAGS_cshlib = ['-xcode=pic32', '-DPIC']
+ v.LINKFLAGS_cshlib = ['-G']
+ v.cshlib_PATTERN = 'lib%s.so'
+
+ v.LINKFLAGS_cstlib = ['-Bstatic']
+ v.cstlib_PATTERN = 'lib%s.a'
+
+def configure(conf):
+ conf.find_scc()
+ conf.find_ar()
+ conf.scc_common_flags()
+ conf.cc_load_tools()
+ conf.cc_add_flags()
+ conf.link_add_flags()
+
diff --git a/Tools/suncxx.py b/Tools/suncxx.py
new file mode 100644
index 0000000..3b384f6
--- /dev/null
+++ b/Tools/suncxx.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2006-2018 (ita)
+# Ralf Habacker, 2006 (rh)
+
+from waflib import Errors
+from waflib.Tools import ccroot, ar
+from waflib.Configure import conf
+
+@conf
+def find_sxx(conf):
+ """
+ Detects the sun C++ compiler
+ """
+ v = conf.env
+ cc = conf.find_program(['CC', 'c++'], var='CXX')
+ try:
+ conf.cmd_and_log(cc + ['-flags'])
+ except Errors.WafError:
+ conf.fatal('%r is not a Sun compiler' % cc)
+ v.CXX_NAME = 'sun'
+ conf.get_suncc_version(cc)
+
+@conf
+def sxx_common_flags(conf):
+ """
+ Flags required for executing the sun C++ compiler
+ """
+ v = conf.env
+
+ v.CXX_SRC_F = []
+ v.CXX_TGT_F = ['-c', '-o', '']
+
+ if not v.LINK_CXX:
+ v.LINK_CXX = v.CXX
+
+ v.CXXLNK_SRC_F = []
+ v.CXXLNK_TGT_F = ['-o', '']
+ v.CPPPATH_ST = '-I%s'
+ v.DEFINES_ST = '-D%s'
+
+ v.LIB_ST = '-l%s' # template for adding libs
+ v.LIBPATH_ST = '-L%s' # template for adding libpaths
+ v.STLIB_ST = '-l%s'
+ v.STLIBPATH_ST = '-L%s'
+
+ v.SONAME_ST = '-Wl,-h,%s'
+ v.SHLIB_MARKER = '-Bdynamic'
+ v.STLIB_MARKER = '-Bstatic'
+
+ v.cxxprogram_PATTERN = '%s'
+
+ v.CXXFLAGS_cxxshlib = ['-xcode=pic32', '-DPIC']
+ v.LINKFLAGS_cxxshlib = ['-G']
+ v.cxxshlib_PATTERN = 'lib%s.so'
+
+ v.LINKFLAGS_cxxstlib = ['-Bstatic']
+ v.cxxstlib_PATTERN = 'lib%s.a'
+
+def configure(conf):
+ conf.find_sxx()
+ conf.find_ar()
+ conf.sxx_common_flags()
+ conf.cxx_load_tools()
+ conf.cxx_add_flags()
+ conf.link_add_flags()
+
diff --git a/Tools/tex.py b/Tools/tex.py
new file mode 100644
index 0000000..eaf9fdb
--- /dev/null
+++ b/Tools/tex.py
@@ -0,0 +1,543 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2006-2018 (ita)
+
+"""
+TeX/LaTeX/PDFLaTeX/XeLaTeX support
+
+Example::
+
+ def configure(conf):
+ conf.load('tex')
+ if not conf.env.LATEX:
+ conf.fatal('The program LaTex is required')
+
+ def build(bld):
+ bld(
+ features = 'tex',
+ type = 'latex', # pdflatex or xelatex
+ source = 'document.ltx', # mandatory, the source
+ outs = 'ps', # 'pdf' or 'ps pdf'
+ deps = 'crossreferencing.lst', # to give dependencies directly
+ prompt = 1, # 0 for the batch mode
+ )
+
+Notes:
+
+- To configure with a special program, use::
+
+ $ PDFLATEX=luatex waf configure
+
+- This tool does not use the target attribute of the task generator
+ (``bld(target=...)``); the target file name is built from the source
+ base name and the output type(s)
+"""
+
+import os, re
+from waflib import Utils, Task, Errors, Logs, Node
+from waflib.TaskGen import feature, before_method
+
+re_bibunit = re.compile(r'\\(?P<type>putbib)\[(?P<file>[^\[\]]*)\]',re.M)
+def bibunitscan(self):
+ """
+ Parses TeX inputs and try to find the *bibunit* file dependencies
+
+ :return: list of bibunit files
+ :rtype: list of :py:class:`waflib.Node.Node`
+ """
+ node = self.inputs[0]
+
+ nodes = []
+ if not node:
+ return nodes
+
+ code = node.read()
+ for match in re_bibunit.finditer(code):
+ path = match.group('file')
+ if path:
+ found = None
+ for k in ('', '.bib'):
+ # add another loop for the tex include paths?
+ Logs.debug('tex: trying %s%s', path, k)
+ fi = node.parent.find_resource(path + k)
+ if fi:
+ found = True
+ nodes.append(fi)
+ # no break
+ if not found:
+ Logs.debug('tex: could not find %s', path)
+
+ Logs.debug('tex: found the following bibunit files: %s', nodes)
+ return nodes
+
+exts_deps_tex = ['', '.ltx', '.tex', '.bib', '.pdf', '.png', '.eps', '.ps', '.sty']
+"""List of typical file extensions included in latex files"""
+
+exts_tex = ['.ltx', '.tex']
+"""List of typical file extensions that contain latex"""
+
+re_tex = re.compile(r'\\(?P<type>usepackage|RequirePackage|include|bibliography([^\[\]{}]*)|putbib|includegraphics|input|import|bringin|lstinputlisting)(\[[^\[\]]*\])?{(?P<file>[^{}]*)}',re.M)
+"""Regexp for expressions that may include latex files"""
+
+g_bibtex_re = re.compile('bibdata', re.M)
+"""Regexp for bibtex files"""
+
+g_glossaries_re = re.compile('\\@newglossary', re.M)
+"""Regexp for expressions that create glossaries"""
+
+class tex(Task.Task):
+ """
+ Compiles a tex/latex file.
+
+ .. inheritance-diagram:: waflib.Tools.tex.latex waflib.Tools.tex.xelatex waflib.Tools.tex.pdflatex
+ """
+
+ bibtex_fun, _ = Task.compile_fun('${BIBTEX} ${BIBTEXFLAGS} ${SRCFILE}', shell=False)
+ bibtex_fun.__doc__ = """
+ Execute the program **bibtex**
+ """
+
+ makeindex_fun, _ = Task.compile_fun('${MAKEINDEX} ${MAKEINDEXFLAGS} ${SRCFILE}', shell=False)
+ makeindex_fun.__doc__ = """
+ Execute the program **makeindex**
+ """
+
+ makeglossaries_fun, _ = Task.compile_fun('${MAKEGLOSSARIES} ${SRCFILE}', shell=False)
+ makeglossaries_fun.__doc__ = """
+ Execute the program **makeglossaries**
+ """
+
+ def exec_command(self, cmd, **kw):
+ """
+ Executes TeX commands without buffering (latex may prompt for inputs)
+
+ :return: the return code
+ :rtype: int
+ """
+ if self.env.PROMPT_LATEX:
+ # capture the outputs in configuration tests
+ kw['stdout'] = kw['stderr'] = None
+ return super(tex, self).exec_command(cmd, **kw)
+
+ def scan_aux(self, node):
+ """
+ Recursive regex-based scanner that finds included auxiliary files.
+ """
+ nodes = [node]
+ re_aux = re.compile(r'\\@input{(?P<file>[^{}]*)}', re.M)
+
+ def parse_node(node):
+ code = node.read()
+ for match in re_aux.finditer(code):
+ path = match.group('file')
+ found = node.parent.find_or_declare(path)
+ if found and found not in nodes:
+ Logs.debug('tex: found aux node %r', found)
+ nodes.append(found)
+ parse_node(found)
+ parse_node(node)
+ return nodes
+
+ def scan(self):
+ """
+ Recursive regex-based scanner that finds latex dependencies. It uses :py:attr:`waflib.Tools.tex.re_tex`
+
+ Depending on your needs you might want:
+
+ * to change re_tex::
+
+ from waflib.Tools import tex
+ tex.re_tex = myregex
+
+ * or to change the method scan from the latex tasks::
+
+ from waflib.Task import classes
+ classes['latex'].scan = myscanfunction
+ """
+ node = self.inputs[0]
+
+ nodes = []
+ names = []
+ seen = []
+ if not node:
+ return (nodes, names)
+
+ def parse_node(node):
+ if node in seen:
+ return
+ seen.append(node)
+ code = node.read()
+ for match in re_tex.finditer(code):
+
+ multibib = match.group('type')
+ if multibib and multibib.startswith('bibliography'):
+ multibib = multibib[len('bibliography'):]
+ if multibib.startswith('style'):
+ continue
+ else:
+ multibib = None
+
+ for path in match.group('file').split(','):
+ if path:
+ add_name = True
+ found = None
+ for k in exts_deps_tex:
+
+ # issue 1067, scan in all texinputs folders
+ for up in self.texinputs_nodes:
+ Logs.debug('tex: trying %s%s', path, k)
+ found = up.find_resource(path + k)
+ if found:
+ break
+
+
+ for tsk in self.generator.tasks:
+ if not found or found in tsk.outputs:
+ break
+ else:
+ nodes.append(found)
+ add_name = False
+ for ext in exts_tex:
+ if found.name.endswith(ext):
+ parse_node(found)
+ break
+
+ # multibib stuff
+ if found and multibib and found.name.endswith('.bib'):
+ try:
+ self.multibibs.append(found)
+ except AttributeError:
+ self.multibibs = [found]
+
+ # no break, people are crazy
+ if add_name:
+ names.append(path)
+ parse_node(node)
+
+ for x in nodes:
+ x.parent.get_bld().mkdir()
+
+ Logs.debug("tex: found the following : %s and names %s", nodes, names)
+ return (nodes, names)
+
+ def check_status(self, msg, retcode):
+ """
+ Checks an exit status and raise an error with a particular message
+
+ :param msg: message to display if the code is non-zero
+ :type msg: string
+ :param retcode: condition
+ :type retcode: boolean
+ """
+ if retcode != 0:
+ raise Errors.WafError('%r command exit status %r' % (msg, retcode))
+
+ def info(self, *k, **kw):
+ try:
+ info = self.generator.bld.conf.logger.info
+ except AttributeError:
+ info = Logs.info
+ info(*k, **kw)
+
+ def bibfile(self):
+ """
+ Parses *.aux* files to find bibfiles to process.
+ If present, execute :py:meth:`waflib.Tools.tex.tex.bibtex_fun`
+ """
+ for aux_node in self.aux_nodes:
+ try:
+ ct = aux_node.read()
+ except EnvironmentError:
+ Logs.error('Error reading %s: %r', aux_node.abspath())
+ continue
+
+ if g_bibtex_re.findall(ct):
+ self.info('calling bibtex')
+
+ self.env.env = {}
+ self.env.env.update(os.environ)
+ self.env.env.update({'BIBINPUTS': self.texinputs(), 'BSTINPUTS': self.texinputs()})
+ self.env.SRCFILE = aux_node.name[:-4]
+ self.check_status('error when calling bibtex', self.bibtex_fun())
+
+ for node in getattr(self, 'multibibs', []):
+ self.env.env = {}
+ self.env.env.update(os.environ)
+ self.env.env.update({'BIBINPUTS': self.texinputs(), 'BSTINPUTS': self.texinputs()})
+ self.env.SRCFILE = node.name[:-4]
+ self.check_status('error when calling bibtex', self.bibtex_fun())
+
+ def bibunits(self):
+ """
+ Parses *.aux* file to find bibunit files. If there are bibunit files,
+ runs :py:meth:`waflib.Tools.tex.tex.bibtex_fun`.
+ """
+ try:
+ bibunits = bibunitscan(self)
+ except OSError:
+ Logs.error('error bibunitscan')
+ else:
+ if bibunits:
+ fn = ['bu' + str(i) for i in range(1, len(bibunits) + 1)]
+ if fn:
+ self.info('calling bibtex on bibunits')
+
+ for f in fn:
+ self.env.env = {'BIBINPUTS': self.texinputs(), 'BSTINPUTS': self.texinputs()}
+ self.env.SRCFILE = f
+ self.check_status('error when calling bibtex', self.bibtex_fun())
+
+ def makeindex(self):
+ """
+ Searches the filesystem for *.idx* files to process. If present,
+ runs :py:meth:`waflib.Tools.tex.tex.makeindex_fun`
+ """
+ self.idx_node = self.inputs[0].change_ext('.idx')
+ try:
+ idx_path = self.idx_node.abspath()
+ os.stat(idx_path)
+ except OSError:
+ self.info('index file %s absent, not calling makeindex', idx_path)
+ else:
+ self.info('calling makeindex')
+
+ self.env.SRCFILE = self.idx_node.name
+ self.env.env = {}
+ self.check_status('error when calling makeindex %s' % idx_path, self.makeindex_fun())
+
+ def bibtopic(self):
+ """
+ Lists additional .aux files from the bibtopic package
+ """
+ p = self.inputs[0].parent.get_bld()
+ if os.path.exists(os.path.join(p.abspath(), 'btaux.aux')):
+ self.aux_nodes += p.ant_glob('*[0-9].aux')
+
+ def makeglossaries(self):
+ """
+ Lists additional glossaries from .aux files. If present, runs the makeglossaries program.
+ """
+ src_file = self.inputs[0].abspath()
+ base_file = os.path.basename(src_file)
+ base, _ = os.path.splitext(base_file)
+ for aux_node in self.aux_nodes:
+ try:
+ ct = aux_node.read()
+ except EnvironmentError:
+ Logs.error('Error reading %s: %r', aux_node.abspath())
+ continue
+
+ if g_glossaries_re.findall(ct):
+ if not self.env.MAKEGLOSSARIES:
+ raise Errors.WafError("The program 'makeglossaries' is missing!")
+ Logs.warn('calling makeglossaries')
+ self.env.SRCFILE = base
+ self.check_status('error when calling makeglossaries %s' % base, self.makeglossaries_fun())
+ return
+
+ def texinputs(self):
+ """
+ Returns the list of texinput nodes as a string suitable for the TEXINPUTS environment variables
+
+ :rtype: string
+ """
+ return os.pathsep.join([k.abspath() for k in self.texinputs_nodes]) + os.pathsep
+
+ def run(self):
+ """
+ Runs the whole TeX build process
+
+ Multiple passes are required depending on the usage of cross-references,
+ bibliographies, glossaries, indexes and additional contents
+ The appropriate TeX compiler is called until the *.aux* files stop changing.
+ """
+ env = self.env
+
+ if not env.PROMPT_LATEX:
+ env.append_value('LATEXFLAGS', '-interaction=batchmode')
+ env.append_value('PDFLATEXFLAGS', '-interaction=batchmode')
+ env.append_value('XELATEXFLAGS', '-interaction=batchmode')
+
+ # important, set the cwd for everybody
+ self.cwd = self.inputs[0].parent.get_bld()
+
+ self.info('first pass on %s', self.__class__.__name__)
+
+ # Hash .aux files before even calling the LaTeX compiler
+ cur_hash = self.hash_aux_nodes()
+
+ self.call_latex()
+
+ # Find the .aux files again since bibtex processing can require it
+ self.hash_aux_nodes()
+
+ self.bibtopic()
+ self.bibfile()
+ self.bibunits()
+ self.makeindex()
+ self.makeglossaries()
+
+ for i in range(10):
+ # There is no need to call latex again if the .aux hash value has not changed
+ prev_hash = cur_hash
+ cur_hash = self.hash_aux_nodes()
+ if not cur_hash:
+ Logs.error('No aux.h to process')
+ if cur_hash and cur_hash == prev_hash:
+ break
+
+ # run the command
+ self.info('calling %s', self.__class__.__name__)
+ self.call_latex()
+
+ def hash_aux_nodes(self):
+ """
+ Returns a hash of the .aux file contents
+
+ :rtype: string or bytes
+ """
+ try:
+ self.aux_nodes
+ except AttributeError:
+ try:
+ self.aux_nodes = self.scan_aux(self.inputs[0].change_ext('.aux'))
+ except IOError:
+ return None
+ return Utils.h_list([Utils.h_file(x.abspath()) for x in self.aux_nodes])
+
+ def call_latex(self):
+ """
+ Runs the TeX compiler once
+ """
+ self.env.env = {}
+ self.env.env.update(os.environ)
+ self.env.env.update({'TEXINPUTS': self.texinputs()})
+ self.env.SRCFILE = self.inputs[0].abspath()
+ self.check_status('error when calling latex', self.texfun())
+
+class latex(tex):
+ "Compiles LaTeX files"
+ texfun, vars = Task.compile_fun('${LATEX} ${LATEXFLAGS} ${SRCFILE}', shell=False)
+
+class pdflatex(tex):
+ "Compiles PdfLaTeX files"
+ texfun, vars = Task.compile_fun('${PDFLATEX} ${PDFLATEXFLAGS} ${SRCFILE}', shell=False)
+
+class xelatex(tex):
+ "XeLaTeX files"
+ texfun, vars = Task.compile_fun('${XELATEX} ${XELATEXFLAGS} ${SRCFILE}', shell=False)
+
+class dvips(Task.Task):
+ "Converts dvi files to postscript"
+ run_str = '${DVIPS} ${DVIPSFLAGS} ${SRC} -o ${TGT}'
+ color = 'BLUE'
+ after = ['latex', 'pdflatex', 'xelatex']
+
+class dvipdf(Task.Task):
+ "Converts dvi files to pdf"
+ run_str = '${DVIPDF} ${DVIPDFFLAGS} ${SRC} ${TGT}'
+ color = 'BLUE'
+ after = ['latex', 'pdflatex', 'xelatex']
+
+class pdf2ps(Task.Task):
+ "Converts pdf files to postscript"
+ run_str = '${PDF2PS} ${PDF2PSFLAGS} ${SRC} ${TGT}'
+ color = 'BLUE'
+ after = ['latex', 'pdflatex', 'xelatex']
+
+@feature('tex')
+@before_method('process_source')
+def apply_tex(self):
+ """
+ Creates :py:class:`waflib.Tools.tex.tex` objects, and
+ dvips/dvipdf/pdf2ps tasks if necessary (outs='ps', etc).
+ """
+ if not getattr(self, 'type', None) in ('latex', 'pdflatex', 'xelatex'):
+ self.type = 'pdflatex'
+
+ outs = Utils.to_list(getattr(self, 'outs', []))
+
+ # prompt for incomplete files (else the batchmode is used)
+ try:
+ self.generator.bld.conf
+ except AttributeError:
+ default_prompt = False
+ else:
+ default_prompt = True
+ self.env.PROMPT_LATEX = getattr(self, 'prompt', default_prompt)
+
+ deps_lst = []
+
+ if getattr(self, 'deps', None):
+ deps = self.to_list(self.deps)
+ for dep in deps:
+ if isinstance(dep, str):
+ n = self.path.find_resource(dep)
+ if not n:
+ self.bld.fatal('Could not find %r for %r' % (dep, self))
+ if not n in deps_lst:
+ deps_lst.append(n)
+ elif isinstance(dep, Node.Node):
+ deps_lst.append(dep)
+
+ for node in self.to_nodes(self.source):
+ if self.type == 'latex':
+ task = self.create_task('latex', node, node.change_ext('.dvi'))
+ elif self.type == 'pdflatex':
+ task = self.create_task('pdflatex', node, node.change_ext('.pdf'))
+ elif self.type == 'xelatex':
+ task = self.create_task('xelatex', node, node.change_ext('.pdf'))
+
+ task.env = self.env
+
+ # add the manual dependencies
+ if deps_lst:
+ for n in deps_lst:
+ if not n in task.dep_nodes:
+ task.dep_nodes.append(n)
+
+ # texinputs is a nasty beast
+ if hasattr(self, 'texinputs_nodes'):
+ task.texinputs_nodes = self.texinputs_nodes
+ else:
+ task.texinputs_nodes = [node.parent, node.parent.get_bld(), self.path, self.path.get_bld()]
+ lst = os.environ.get('TEXINPUTS', '')
+ if self.env.TEXINPUTS:
+ lst += os.pathsep + self.env.TEXINPUTS
+ if lst:
+ lst = lst.split(os.pathsep)
+ for x in lst:
+ if x:
+ if os.path.isabs(x):
+ p = self.bld.root.find_node(x)
+ if p:
+ task.texinputs_nodes.append(p)
+ else:
+ Logs.error('Invalid TEXINPUTS folder %s', x)
+ else:
+ Logs.error('Cannot resolve relative paths in TEXINPUTS %s', x)
+
+ if self.type == 'latex':
+ if 'ps' in outs:
+ tsk = self.create_task('dvips', task.outputs, node.change_ext('.ps'))
+ tsk.env.env = dict(os.environ)
+ if 'pdf' in outs:
+ tsk = self.create_task('dvipdf', task.outputs, node.change_ext('.pdf'))
+ tsk.env.env = dict(os.environ)
+ elif self.type == 'pdflatex':
+ if 'ps' in outs:
+ self.create_task('pdf2ps', task.outputs, node.change_ext('.ps'))
+ self.source = []
+
+def configure(self):
+ """
+ Find the programs tex, latex and others without raising errors.
+ """
+ v = self.env
+ for p in 'tex latex pdflatex xelatex bibtex dvips dvipdf ps2pdf makeindex pdf2ps makeglossaries'.split():
+ try:
+ self.find_program(p, var=p.upper())
+ except self.errors.ConfigurationError:
+ pass
+ v.DVIPSFLAGS = '-Ppdf'
+
diff --git a/Tools/vala.py b/Tools/vala.py
new file mode 100644
index 0000000..822ec50
--- /dev/null
+++ b/Tools/vala.py
@@ -0,0 +1,355 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Ali Sabil, 2007
+# Radosław Szkodziński, 2010
+
+"""
+At this point, vala is still unstable, so do not expect
+this tool to be too stable either (apis, etc)
+"""
+
+import re
+from waflib import Build, Context, Errors, Logs, Node, Options, Task, Utils
+from waflib.TaskGen import extension, taskgen_method
+from waflib.Configure import conf
+
+class valac(Task.Task):
+ """
+ Compiles vala files
+ """
+ #run_str = "${VALAC} ${VALAFLAGS}" # ideally
+ #vars = ['VALAC_VERSION']
+ vars = ["VALAC", "VALAC_VERSION", "VALAFLAGS"]
+ ext_out = ['.h']
+
+ def run(self):
+ cmd = self.env.VALAC + self.env.VALAFLAGS
+ resources = getattr(self, 'vala_exclude', [])
+ cmd.extend([a.abspath() for a in self.inputs if a not in resources])
+ ret = self.exec_command(cmd, cwd=self.vala_dir_node.abspath())
+
+ if ret:
+ return ret
+
+ if self.generator.dump_deps_node:
+ self.generator.dump_deps_node.write('\n'.join(self.generator.packages))
+
+ return ret
+
+@taskgen_method
+def init_vala_task(self):
+ """
+ Initializes the vala task with the relevant data (acts as a constructor)
+ """
+ self.profile = getattr(self, 'profile', 'gobject')
+
+ self.packages = packages = Utils.to_list(getattr(self, 'packages', []))
+ self.use = Utils.to_list(getattr(self, 'use', []))
+ if packages and not self.use:
+ self.use = packages[:] # copy
+
+ if self.profile == 'gobject':
+ if not 'GOBJECT' in self.use:
+ self.use.append('GOBJECT')
+
+ def addflags(flags):
+ self.env.append_value('VALAFLAGS', flags)
+
+ if self.profile:
+ addflags('--profile=%s' % self.profile)
+
+ valatask = self.valatask
+
+ # output directory
+ if hasattr(self, 'vala_dir'):
+ if isinstance(self.vala_dir, str):
+ valatask.vala_dir_node = self.path.get_bld().make_node(self.vala_dir)
+ try:
+ valatask.vala_dir_node.mkdir()
+ except OSError:
+ raise self.bld.fatal('Cannot create the vala dir %r' % valatask.vala_dir_node)
+ else:
+ valatask.vala_dir_node = self.vala_dir
+ else:
+ valatask.vala_dir_node = self.path.get_bld()
+ addflags('--directory=%s' % valatask.vala_dir_node.abspath())
+
+ if hasattr(self, 'thread'):
+ if self.profile == 'gobject':
+ if not 'GTHREAD' in self.use:
+ self.use.append('GTHREAD')
+ else:
+ #Vala doesn't have threading support for dova nor posix
+ Logs.warn('Profile %s means no threading support', self.profile)
+ self.thread = False
+
+ if self.thread:
+ addflags('--thread')
+
+ self.is_lib = 'cprogram' not in self.features
+ if self.is_lib:
+ addflags('--library=%s' % self.target)
+
+ h_node = valatask.vala_dir_node.find_or_declare('%s.h' % self.target)
+ valatask.outputs.append(h_node)
+ addflags('--header=%s' % h_node.name)
+
+ valatask.outputs.append(valatask.vala_dir_node.find_or_declare('%s.vapi' % self.target))
+
+ if getattr(self, 'gir', None):
+ gir_node = valatask.vala_dir_node.find_or_declare('%s.gir' % self.gir)
+ addflags('--gir=%s' % gir_node.name)
+ valatask.outputs.append(gir_node)
+
+ self.vala_target_glib = getattr(self, 'vala_target_glib', getattr(Options.options, 'vala_target_glib', None))
+ if self.vala_target_glib:
+ addflags('--target-glib=%s' % self.vala_target_glib)
+
+ addflags(['--define=%s' % x for x in Utils.to_list(getattr(self, 'vala_defines', []))])
+
+ packages_private = Utils.to_list(getattr(self, 'packages_private', []))
+ addflags(['--pkg=%s' % x for x in packages_private])
+
+ def _get_api_version():
+ api_version = '1.0'
+ if hasattr(Context.g_module, 'API_VERSION'):
+ version = Context.g_module.API_VERSION.split(".")
+ if version[0] == "0":
+ api_version = "0." + version[1]
+ else:
+ api_version = version[0] + ".0"
+ return api_version
+
+ self.includes = Utils.to_list(getattr(self, 'includes', []))
+ valatask.install_path = getattr(self, 'install_path', '')
+
+ valatask.vapi_path = getattr(self, 'vapi_path', '${DATAROOTDIR}/vala/vapi')
+ valatask.pkg_name = getattr(self, 'pkg_name', self.env.PACKAGE)
+ valatask.header_path = getattr(self, 'header_path', '${INCLUDEDIR}/%s-%s' % (valatask.pkg_name, _get_api_version()))
+ valatask.install_binding = getattr(self, 'install_binding', True)
+
+ self.vapi_dirs = vapi_dirs = Utils.to_list(getattr(self, 'vapi_dirs', []))
+ #includes = []
+
+ if hasattr(self, 'use'):
+ local_packages = Utils.to_list(self.use)[:] # make sure to have a copy
+ seen = []
+ while len(local_packages) > 0:
+ package = local_packages.pop()
+ if package in seen:
+ continue
+ seen.append(package)
+
+ # check if the package exists
+ try:
+ package_obj = self.bld.get_tgen_by_name(package)
+ except Errors.WafError:
+ continue
+
+ # in practice the other task is already processed
+ # but this makes it explicit
+ package_obj.post()
+ package_name = package_obj.target
+ task = getattr(package_obj, 'valatask', None)
+ if task:
+ for output in task.outputs:
+ if output.name == package_name + ".vapi":
+ valatask.set_run_after(task)
+ if package_name not in packages:
+ packages.append(package_name)
+ if output.parent not in vapi_dirs:
+ vapi_dirs.append(output.parent)
+ if output.parent not in self.includes:
+ self.includes.append(output.parent)
+
+ if hasattr(package_obj, 'use'):
+ lst = self.to_list(package_obj.use)
+ lst.reverse()
+ local_packages = [pkg for pkg in lst if pkg not in seen] + local_packages
+
+ addflags(['--pkg=%s' % p for p in packages])
+
+ for vapi_dir in vapi_dirs:
+ if isinstance(vapi_dir, Node.Node):
+ v_node = vapi_dir
+ else:
+ v_node = self.path.find_dir(vapi_dir)
+ if not v_node:
+ Logs.warn('Unable to locate Vala API directory: %r', vapi_dir)
+ else:
+ addflags('--vapidir=%s' % v_node.abspath())
+
+ self.dump_deps_node = None
+ if self.is_lib and self.packages:
+ self.dump_deps_node = valatask.vala_dir_node.find_or_declare('%s.deps' % self.target)
+ valatask.outputs.append(self.dump_deps_node)
+
+ if self.is_lib and valatask.install_binding:
+ headers_list = [o for o in valatask.outputs if o.suffix() == ".h"]
+ if headers_list:
+ self.install_vheader = self.add_install_files(install_to=valatask.header_path, install_from=headers_list)
+
+ vapi_list = [o for o in valatask.outputs if (o.suffix() in (".vapi", ".deps"))]
+ if vapi_list:
+ self.install_vapi = self.add_install_files(install_to=valatask.vapi_path, install_from=vapi_list)
+
+ gir_list = [o for o in valatask.outputs if o.suffix() == '.gir']
+ if gir_list:
+ self.install_gir = self.add_install_files(
+ install_to=getattr(self, 'gir_path', '${DATAROOTDIR}/gir-1.0'), install_from=gir_list)
+
+ if hasattr(self, 'vala_resources'):
+ nodes = self.to_nodes(self.vala_resources)
+ valatask.vala_exclude = getattr(valatask, 'vala_exclude', []) + nodes
+ valatask.inputs.extend(nodes)
+ for x in nodes:
+ addflags(['--gresources', x.abspath()])
+
+@extension('.vala', '.gs')
+def vala_file(self, node):
+ """
+ Compile a vala file and bind the task to *self.valatask*. If an existing vala task is already set, add the node
+ to its inputs. The typical example is::
+
+ def build(bld):
+ bld.program(
+ packages = 'gtk+-2.0',
+ target = 'vala-gtk-example',
+ use = 'GTK GLIB',
+ source = 'vala-gtk-example.vala foo.vala',
+ vala_defines = ['DEBUG'] # adds --define=<xyz> values to the command-line
+
+ # the following arguments are for libraries
+ #gir = 'hello-1.0',
+ #gir_path = '/tmp',
+ #vapi_path = '/tmp',
+ #pkg_name = 'hello'
+ # disable installing of gir, vapi and header
+ #install_binding = False
+
+ # profile = 'xyz' # adds --profile=<xyz> to enable profiling
+ # thread = True, # adds --thread, except if profile is on or not on 'gobject'
+ # vala_target_glib = 'xyz' # adds --target-glib=<xyz>, can be given through the command-line option --vala-target-glib=<xyz>
+ )
+
+
+ :param node: vala file
+ :type node: :py:class:`waflib.Node.Node`
+ """
+
+ try:
+ valatask = self.valatask
+ except AttributeError:
+ valatask = self.valatask = self.create_task('valac')
+ self.init_vala_task()
+
+ valatask.inputs.append(node)
+ name = node.name[:node.name.rfind('.')] + '.c'
+ c_node = valatask.vala_dir_node.find_or_declare(name)
+ valatask.outputs.append(c_node)
+ self.source.append(c_node)
+
+@extension('.vapi')
+def vapi_file(self, node):
+ try:
+ valatask = self.valatask
+ except AttributeError:
+ valatask = self.valatask = self.create_task('valac')
+ self.init_vala_task()
+ valatask.inputs.append(node)
+
+@conf
+def find_valac(self, valac_name, min_version):
+ """
+ Find the valac program, and execute it to store the version
+ number in *conf.env.VALAC_VERSION*
+
+ :param valac_name: program name
+ :type valac_name: string or list of string
+ :param min_version: minimum version acceptable
+ :type min_version: tuple of int
+ """
+ valac = self.find_program(valac_name, var='VALAC')
+ try:
+ output = self.cmd_and_log(valac + ['--version'])
+ except Errors.WafError:
+ valac_version = None
+ else:
+ ver = re.search(r'\d+.\d+.\d+', output).group().split('.')
+ valac_version = tuple([int(x) for x in ver])
+
+ self.msg('Checking for %s version >= %r' % (valac_name, min_version),
+ valac_version, valac_version and valac_version >= min_version)
+ if valac and valac_version < min_version:
+ self.fatal("%s version %r is too old, need >= %r" % (valac_name, valac_version, min_version))
+
+ self.env.VALAC_VERSION = valac_version
+ return valac
+
+@conf
+def check_vala(self, min_version=(0,8,0), branch=None):
+ """
+ Check if vala compiler from a given branch exists of at least a given
+ version.
+
+ :param min_version: minimum version acceptable (0.8.0)
+ :type min_version: tuple
+ :param branch: first part of the version number, in case a snapshot is used (0, 8)
+ :type branch: tuple of int
+ """
+ if self.env.VALA_MINVER:
+ min_version = self.env.VALA_MINVER
+ if self.env.VALA_MINVER_BRANCH:
+ branch = self.env.VALA_MINVER_BRANCH
+ if not branch:
+ branch = min_version[:2]
+ try:
+ find_valac(self, 'valac-%d.%d' % (branch[0], branch[1]), min_version)
+ except self.errors.ConfigurationError:
+ find_valac(self, 'valac', min_version)
+
+@conf
+def check_vala_deps(self):
+ """
+ Load the gobject and gthread packages if they are missing.
+ """
+ if not self.env.HAVE_GOBJECT:
+ pkg_args = {'package': 'gobject-2.0',
+ 'uselib_store': 'GOBJECT',
+ 'args': '--cflags --libs'}
+ if getattr(Options.options, 'vala_target_glib', None):
+ pkg_args['atleast_version'] = Options.options.vala_target_glib
+ self.check_cfg(**pkg_args)
+
+ if not self.env.HAVE_GTHREAD:
+ pkg_args = {'package': 'gthread-2.0',
+ 'uselib_store': 'GTHREAD',
+ 'args': '--cflags --libs'}
+ if getattr(Options.options, 'vala_target_glib', None):
+ pkg_args['atleast_version'] = Options.options.vala_target_glib
+ self.check_cfg(**pkg_args)
+
+def configure(self):
+ """
+ Use the following to enforce minimum vala version::
+
+ def configure(conf):
+ conf.env.VALA_MINVER = (0, 10, 0)
+ conf.load('vala')
+ """
+ self.load('gnu_dirs')
+ self.check_vala_deps()
+ self.check_vala()
+ self.add_os_flags('VALAFLAGS')
+ self.env.append_unique('VALAFLAGS', ['-C'])
+
+def options(opt):
+ """
+ Load the :py:mod:`waflib.Tools.gnu_dirs` tool and add the ``--vala-target-glib`` command-line option
+ """
+ opt.load('gnu_dirs')
+ valaopts = opt.add_option_group('Vala Compiler Options')
+ valaopts.add_option('--vala-target-glib', default=None,
+ dest='vala_target_glib', metavar='MAJOR.MINOR',
+ help='Target version of glib for Vala GObject code generation')
+
diff --git a/Tools/waf_unit_test.py b/Tools/waf_unit_test.py
new file mode 100644
index 0000000..74d6c05
--- /dev/null
+++ b/Tools/waf_unit_test.py
@@ -0,0 +1,296 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Carlos Rafael Giani, 2006
+# Thomas Nagy, 2010-2018 (ita)
+
+"""
+Unit testing system for C/C++/D and interpreted languages providing test execution:
+
+* in parallel, by using ``waf -j``
+* partial (only the tests that have changed) or full (by using ``waf --alltests``)
+
+The tests are declared by adding the **test** feature to programs::
+
+ def options(opt):
+ opt.load('compiler_cxx waf_unit_test')
+ def configure(conf):
+ conf.load('compiler_cxx waf_unit_test')
+ def build(bld):
+ bld(features='cxx cxxprogram test', source='main.cpp', target='app')
+ # or
+ bld.program(features='test', source='main2.cpp', target='app2')
+
+When the build is executed, the program 'test' will be built and executed without arguments.
+The success/failure is detected by looking at the return code. The status and the standard output/error
+are stored on the build context.
+
+The results can be displayed by registering a callback function. Here is how to call
+the predefined callback::
+
+ def build(bld):
+ bld(features='cxx cxxprogram test', source='main.c', target='app')
+ from waflib.Tools import waf_unit_test
+ bld.add_post_fun(waf_unit_test.summary)
+
+By passing --dump-test-scripts the build outputs corresponding python files
+(with extension _run.py) that are useful for debugging purposes.
+"""
+
+import os, shlex, sys
+from waflib.TaskGen import feature, after_method, taskgen_method
+from waflib import Utils, Task, Logs, Options
+from waflib.Tools import ccroot
+testlock = Utils.threading.Lock()
+
+SCRIPT_TEMPLATE = """#! %(python)s
+import subprocess, sys
+cmd = %(cmd)r
+# if you want to debug with gdb:
+#cmd = ['gdb', '-args'] + cmd
+env = %(env)r
+status = subprocess.call(cmd, env=env, cwd=%(cwd)r, shell=isinstance(cmd, str))
+sys.exit(status)
+"""
+
+@taskgen_method
+def handle_ut_cwd(self, key):
+ """
+ Task generator method, used internally to limit code duplication.
+ This method may disappear anytime.
+ """
+ cwd = getattr(self, key, None)
+ if cwd:
+ if isinstance(cwd, str):
+ # we want a Node instance
+ if os.path.isabs(cwd):
+ self.ut_cwd = self.bld.root.make_node(cwd)
+ else:
+ self.ut_cwd = self.path.make_node(cwd)
+
+@feature('test_scripts')
+def make_interpreted_test(self):
+ """Create interpreted unit tests."""
+ for x in ['test_scripts_source', 'test_scripts_template']:
+ if not hasattr(self, x):
+ Logs.warn('a test_scripts taskgen i missing %s' % x)
+ return
+
+ self.ut_run, lst = Task.compile_fun(self.test_scripts_template, shell=getattr(self, 'test_scripts_shell', False))
+
+ script_nodes = self.to_nodes(self.test_scripts_source)
+ for script_node in script_nodes:
+ tsk = self.create_task('utest', [script_node])
+ tsk.vars = lst + tsk.vars
+ tsk.env['SCRIPT'] = script_node.path_from(tsk.get_cwd())
+
+ self.handle_ut_cwd('test_scripts_cwd')
+
+ env = getattr(self, 'test_scripts_env', None)
+ if env:
+ self.ut_env = env
+ else:
+ self.ut_env = dict(os.environ)
+
+ paths = getattr(self, 'test_scripts_paths', {})
+ for (k,v) in paths.items():
+ p = self.ut_env.get(k, '').split(os.pathsep)
+ if isinstance(v, str):
+ v = v.split(os.pathsep)
+ self.ut_env[k] = os.pathsep.join(p + v)
+
+@feature('test')
+@after_method('apply_link', 'process_use')
+def make_test(self):
+ """Create the unit test task. There can be only one unit test task by task generator."""
+ if not getattr(self, 'link_task', None):
+ return
+
+ 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
+
+ self.handle_ut_cwd('ut_cwd')
+
+ if not hasattr(self, 'ut_paths'):
+ paths = []
+ for x in self.tmp_use_sorted:
+ try:
+ y = self.bld.get_tgen_by_name(x).link_task
+ except AttributeError:
+ pass
+ else:
+ if not isinstance(y, ccroot.stlink_task):
+ paths.append(y.outputs[0].parent.abspath())
+ self.ut_paths = os.pathsep.join(paths) + os.pathsep
+
+ if not hasattr(self, 'ut_env'):
+ self.ut_env = dct = dict(os.environ)
+ def add_path(var):
+ dct[var] = self.ut_paths + dct.get(var,'')
+ if Utils.is_win32:
+ add_path('PATH')
+ elif Utils.unversioned_sys_platform() == 'darwin':
+ add_path('DYLD_LIBRARY_PATH')
+ add_path('LD_LIBRARY_PATH')
+ else:
+ add_path('LD_LIBRARY_PATH')
+
+ if not hasattr(self, 'ut_cmd'):
+ self.ut_cmd = getattr(Options.options, 'testcmd', False)
+
+@taskgen_method
+def add_test_results(self, tup):
+ """Override and return tup[1] to interrupt the build immediately if a test does not run"""
+ Logs.debug("ut: %r", tup)
+ try:
+ self.utest_results.append(tup)
+ except AttributeError:
+ self.utest_results = [tup]
+ try:
+ self.bld.utest_results.append(tup)
+ except AttributeError:
+ self.bld.utest_results = [tup]
+
+@Task.deep_inputs
+class utest(Task.Task):
+ """
+ Execute a unit test
+ """
+ color = 'PINK'
+ after = ['vnum', 'inst']
+ vars = []
+
+ def runnable_status(self):
+ """
+ Always execute the task if `waf --alltests` was used or no
+ tests if ``waf --notests`` was used
+ """
+ if getattr(Options.options, 'no_tests', False):
+ return Task.SKIP_ME
+
+ ret = super(utest, self).runnable_status()
+ if ret == Task.SKIP_ME:
+ if getattr(Options.options, 'all_tests', False):
+ return Task.RUN_ME
+ return ret
+
+ def get_test_env(self):
+ """
+ In general, tests may require any library built anywhere in the project.
+ Override this method if fewer paths are needed
+ """
+ return self.generator.ut_env
+
+ def post_run(self):
+ super(utest, self).post_run()
+ if getattr(Options.options, 'clear_failed_tests', False) and self.waf_unit_test_results[1]:
+ self.generator.bld.task_sigs[self.uid()] = None
+
+ def run(self):
+ """
+ Execute the test. The execution is always successful, and the results
+ are stored on ``self.generator.bld.utest_results`` for postprocessing.
+
+ Override ``add_test_results`` to interrupt the build
+ """
+ if hasattr(self.generator, 'ut_run'):
+ return self.generator.ut_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))
+
+ return self.exec_command(self.ut_exec)
+
+ def exec_command(self, cmd, **kw):
+ self.generator.bld.log_command(cmd, kw)
+ if getattr(Options.options, 'dump_test_scripts', False):
+ script_code = SCRIPT_TEMPLATE % {
+ 'python': sys.executable,
+ 'env': self.get_test_env(),
+ 'cwd': self.get_cwd().abspath(),
+ 'cmd': cmd
+ }
+ script_file = self.inputs[0].abspath() + '_run.py'
+ Utils.writef(script_file, script_code)
+ os.chmod(script_file, Utils.O755)
+ if Logs.verbose > 1:
+ Logs.info('Test debug file written as %r' % script_file)
+
+ proc = Utils.subprocess.Popen(cmd, cwd=self.get_cwd().abspath(), env=self.get_test_env(),
+ stderr=Utils.subprocess.PIPE, stdout=Utils.subprocess.PIPE, shell=isinstance(cmd,str))
+ (stdout, stderr) = proc.communicate()
+ self.waf_unit_test_results = tup = (self.inputs[0].abspath(), proc.returncode, stdout, stderr)
+ testlock.acquire()
+ try:
+ return self.generator.add_test_results(tup)
+ finally:
+ testlock.release()
+
+ def get_cwd(self):
+ return getattr(self.generator, 'ut_cwd', self.inputs[0].parent)
+
+def summary(bld):
+ """
+ Display an execution summary::
+
+ def build(bld):
+ bld(features='cxx cxxprogram test', source='main.c', target='app')
+ from waflib.Tools import waf_unit_test
+ bld.add_post_fun(waf_unit_test.summary)
+ """
+ lst = getattr(bld, 'utest_results', [])
+ if lst:
+ Logs.pprint('CYAN', 'execution summary')
+
+ total = len(lst)
+ tfail = len([x for x in lst if x[1]])
+
+ Logs.pprint('GREEN', ' tests that pass %d/%d' % (total-tfail, total))
+ for (f, code, out, err) in lst:
+ if not code:
+ Logs.pprint('GREEN', ' %s' % f)
+
+ Logs.pprint('GREEN' if tfail == 0 else 'RED', ' tests that fail %d/%d' % (tfail, total))
+ for (f, code, out, err) in lst:
+ if code:
+ Logs.pprint('RED', ' %s' % f)
+
+def set_exit_code(bld):
+ """
+ If any of the tests fail waf will exit with that exit code.
+ This is useful if you have an automated build system which need
+ to report on errors from the tests.
+ You may use it like this:
+
+ def build(bld):
+ bld(features='cxx cxxprogram test', source='main.c', target='app')
+ from waflib.Tools import waf_unit_test
+ bld.add_post_fun(waf_unit_test.set_exit_code)
+ """
+ lst = getattr(bld, 'utest_results', [])
+ for (f, code, out, err) in lst:
+ if code:
+ msg = []
+ if out:
+ msg.append('stdout:%s%s' % (os.linesep, out.decode('utf-8')))
+ if err:
+ msg.append('stderr:%s%s' % (os.linesep, err.decode('utf-8')))
+ bld.fatal(os.linesep.join(msg))
+
+
+def options(opt):
+ """
+ Provide the ``--alltests``, ``--notests`` and ``--testcmd`` command-line options.
+ """
+ opt.add_option('--notests', action='store_true', default=False, help='Exec no unit tests', dest='no_tests')
+ opt.add_option('--alltests', action='store_true', default=False, help='Exec all unit tests', dest='all_tests')
+ opt.add_option('--clear-failed', action='store_true', default=False,
+ help='Force failed unit tests to run again next time', dest='clear_failed_tests')
+ opt.add_option('--testcmd', action='store', default=False, dest='testcmd',
+ help='Run the unit tests using the test-cmd string example "--testcmd="valgrind --error-exitcode=1 %s" to run under valgrind')
+ opt.add_option('--dump-test-scripts', action='store_true', default=False,
+ help='Create python scripts to help debug tests', dest='dump_test_scripts')
+
diff --git a/Tools/winres.py b/Tools/winres.py
new file mode 100644
index 0000000..9be1ed6
--- /dev/null
+++ b/Tools/winres.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Brant Young, 2007
+
+"Process *.rc* files for C/C++: X{.rc -> [.res|.rc.o]}"
+
+import re
+from waflib import Task
+from waflib.TaskGen import extension
+from waflib.Tools import c_preproc
+
+@extension('.rc')
+def rc_file(self, node):
+ """
+ Binds the .rc extension to a winrc task
+ """
+ obj_ext = '.rc.o'
+ if self.env.WINRC_TGT_F == '/fo':
+ obj_ext = '.res'
+ rctask = self.create_task('winrc', node, node.change_ext(obj_ext))
+ try:
+ self.compiled_tasks.append(rctask)
+ except AttributeError:
+ self.compiled_tasks = [rctask]
+
+re_lines = re.compile(
+ r'(?:^[ \t]*(#|%:)[ \t]*(ifdef|ifndef|if|else|elif|endif|include|import|define|undef|pragma)[ \t]*(.*?)\s*$)|'\
+ r'(?:^\w+[ \t]*(ICON|BITMAP|CURSOR|HTML|FONT|MESSAGETABLE|TYPELIB|REGISTRY|D3DFX)[ \t]*(.*?)\s*$)',
+ re.IGNORECASE | re.MULTILINE)
+
+class rc_parser(c_preproc.c_parser):
+ """
+ Calculates dependencies in .rc files
+ """
+ def filter_comments(self, node):
+ """
+ Overrides :py:meth:`waflib.Tools.c_preproc.c_parser.filter_comments`
+ """
+ code = node.read()
+ if c_preproc.use_trigraphs:
+ for (a, b) in c_preproc.trig_def:
+ code = code.split(a).join(b)
+ code = c_preproc.re_nl.sub('', code)
+ code = c_preproc.re_cpp.sub(c_preproc.repl, code)
+ ret = []
+ for m in re.finditer(re_lines, code):
+ if m.group(2):
+ ret.append((m.group(2), m.group(3)))
+ else:
+ ret.append(('include', m.group(5)))
+ return ret
+
+class winrc(Task.Task):
+ """
+ Compiles resource files
+ """
+ run_str = '${WINRC} ${WINRCFLAGS} ${CPPPATH_ST:INCPATHS} ${DEFINES_ST:DEFINES} ${WINRC_TGT_F} ${TGT} ${WINRC_SRC_F} ${SRC}'
+ color = 'BLUE'
+ def scan(self):
+ tmp = rc_parser(self.generator.includes_nodes)
+ tmp.start(self.inputs[0], self.env)
+ return (tmp.nodes, tmp.names)
+
+def configure(conf):
+ """
+ Detects the programs RC or windres, depending on the C/C++ compiler in use
+ """
+ v = conf.env
+ if not v.WINRC:
+ if v.CC_NAME == 'msvc':
+ conf.find_program('RC', var='WINRC', path_list=v.PATH)
+ v.WINRC_TGT_F = '/fo'
+ v.WINRC_SRC_F = ''
+ else:
+ conf.find_program('windres', var='WINRC', path_list=v.PATH)
+ v.WINRC_TGT_F = '-o'
+ v.WINRC_SRC_F = '-i'
+
diff --git a/Tools/xlc.py b/Tools/xlc.py
new file mode 100644
index 0000000..134dd41
--- /dev/null
+++ b/Tools/xlc.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2006-2018 (ita)
+# Ralf Habacker, 2006 (rh)
+# Yinon Ehrlich, 2009
+# Michael Kuhn, 2009
+
+from waflib.Tools import ccroot, ar
+from waflib.Configure import conf
+
+@conf
+def find_xlc(conf):
+ """
+ Detects the Aix C compiler
+ """
+ cc = conf.find_program(['xlc_r', 'xlc'], var='CC')
+ conf.get_xlc_version(cc)
+ conf.env.CC_NAME = 'xlc'
+
+@conf
+def xlc_common_flags(conf):
+ """
+ Flags required for executing the Aix C compiler
+ """
+ v = conf.env
+
+ v.CC_SRC_F = []
+ v.CC_TGT_F = ['-c', '-o']
+
+ if not v.LINK_CC:
+ v.LINK_CC = v.CC
+
+ v.CCLNK_SRC_F = []
+ v.CCLNK_TGT_F = ['-o']
+ v.CPPPATH_ST = '-I%s'
+ v.DEFINES_ST = '-D%s'
+
+ v.LIB_ST = '-l%s' # template for adding libs
+ v.LIBPATH_ST = '-L%s' # template for adding libpaths
+ v.STLIB_ST = '-l%s'
+ v.STLIBPATH_ST = '-L%s'
+ v.RPATH_ST = '-Wl,-rpath,%s'
+
+ v.SONAME_ST = []
+ v.SHLIB_MARKER = []
+ v.STLIB_MARKER = []
+
+ v.LINKFLAGS_cprogram = ['-Wl,-brtl']
+ v.cprogram_PATTERN = '%s'
+
+ v.CFLAGS_cshlib = ['-fPIC']
+ v.LINKFLAGS_cshlib = ['-G', '-Wl,-brtl,-bexpfull']
+ v.cshlib_PATTERN = 'lib%s.so'
+
+ v.LINKFLAGS_cstlib = []
+ v.cstlib_PATTERN = 'lib%s.a'
+
+def configure(conf):
+ conf.find_xlc()
+ conf.find_ar()
+ conf.xlc_common_flags()
+ conf.cc_load_tools()
+ conf.cc_add_flags()
+ conf.link_add_flags()
+
diff --git a/Tools/xlcxx.py b/Tools/xlcxx.py
new file mode 100644
index 0000000..76aa59b
--- /dev/null
+++ b/Tools/xlcxx.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2006-2018 (ita)
+# Ralf Habacker, 2006 (rh)
+# Yinon Ehrlich, 2009
+# Michael Kuhn, 2009
+
+from waflib.Tools import ccroot, ar
+from waflib.Configure import conf
+
+@conf
+def find_xlcxx(conf):
+ """
+ Detects the Aix C++ compiler
+ """
+ cxx = conf.find_program(['xlc++_r', 'xlc++'], var='CXX')
+ conf.get_xlc_version(cxx)
+ conf.env.CXX_NAME = 'xlc++'
+
+@conf
+def xlcxx_common_flags(conf):
+ """
+ Flags required for executing the Aix C++ compiler
+ """
+ v = conf.env
+
+ v.CXX_SRC_F = []
+ v.CXX_TGT_F = ['-c', '-o']
+
+ if not v.LINK_CXX:
+ v.LINK_CXX = v.CXX
+
+ v.CXXLNK_SRC_F = []
+ v.CXXLNK_TGT_F = ['-o']
+ v.CPPPATH_ST = '-I%s'
+ v.DEFINES_ST = '-D%s'
+
+ v.LIB_ST = '-l%s' # template for adding libs
+ v.LIBPATH_ST = '-L%s' # template for adding libpaths
+ v.STLIB_ST = '-l%s'
+ v.STLIBPATH_ST = '-L%s'
+ v.RPATH_ST = '-Wl,-rpath,%s'
+
+ v.SONAME_ST = []
+ v.SHLIB_MARKER = []
+ v.STLIB_MARKER = []
+
+ v.LINKFLAGS_cxxprogram= ['-Wl,-brtl']
+ v.cxxprogram_PATTERN = '%s'
+
+ v.CXXFLAGS_cxxshlib = ['-fPIC']
+ v.LINKFLAGS_cxxshlib = ['-G', '-Wl,-brtl,-bexpfull']
+ v.cxxshlib_PATTERN = 'lib%s.so'
+
+ v.LINKFLAGS_cxxstlib = []
+ v.cxxstlib_PATTERN = 'lib%s.a'
+
+def configure(conf):
+ conf.find_xlcxx()
+ conf.find_ar()
+ conf.xlcxx_common_flags()
+ conf.cxx_load_tools()
+ conf.cxx_add_flags()
+ conf.link_add_flags()
+