summaryrefslogtreecommitdiffstats
path: root/Tools/ccroot.py
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2018-11-24 13:44:02 +0100
committerDavid Robillard <d@drobilla.net>2018-11-24 13:44:02 +0100
commitd74de7f575a9ec49f96138c3c5251f28946c0c0e (patch)
treed9b620bfba1e7462df4ddb3f6225cc5216c0ca81 /Tools/ccroot.py
parentc6e874c2cc1c5c99a3859112e1bba0f07bcdb8ba (diff)
downloadganv-d74de7f575a9ec49f96138c3c5251f28946c0c0e.tar.gz
ganv-d74de7f575a9ec49f96138c3c5251f28946c0c0e.tar.bz2
ganv-d74de7f575a9ec49f96138c3c5251f28946c0c0e.zip
Squashed 'waflib/' changes from 6e726eb..5ea8f99
5ea8f99 Improve test output spacing 0e23b29 Raise exception when test suite fails to ensure non-zero exit status d6de073 Show run time of unit tests 5b65554 Add short configure option for ultra-strict flags 4687ba6 Use gtest-like test output 258903d Fix failure count in test group summaries da07e73 Fix verbose tests with Python 3 git-subtree-dir: waflib git-subtree-split: 5ea8f99f6e1246079c1fe6bb590c38a53aadd40d
Diffstat (limited to 'Tools/ccroot.py')
-rw-r--r--Tools/ccroot.py776
1 files changed, 776 insertions, 0 deletions
diff --git a/Tools/ccroot.py b/Tools/ccroot.py
new file mode 100644
index 0000000..cfef8bf
--- /dev/null
+++ b/Tools/ccroot.py
@@ -0,0 +1,776 @@
+#!/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 enable 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('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 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
+