mirror of
https://gitlab.com/ita1024/waf.git
synced 2024-11-29 05:21:23 +01:00
776 lines
25 KiB
Python
776 lines
25 KiB
Python
#!/usr/bin/env python
|
|
# encoding: utf-8
|
|
# Thomas Nagy, 2005-2017 (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 unversionned.
|
|
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 'msvc' in (self.env.CC_NAME, self.env.CXX_NAME):
|
|
self.env.append_value('LINKFLAGS', '/def:%s' % node.path_from(self.get_cwd()))
|
|
self.link_task.dep_nodes.append(node)
|
|
else:
|
|
#gcc for windows takes *.def file a an 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
|
|
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
|
|
|