diff options
Diffstat (limited to 'Tools/ccroot.py')
-rw-r--r-- | Tools/ccroot.py | 776 |
1 files changed, 776 insertions, 0 deletions
diff --git a/Tools/ccroot.py b/Tools/ccroot.py new file mode 100644 index 00000000..cfef8bf5 --- /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 + |