diff options
Diffstat (limited to 'site_scons/libdeps.py')
-rw-r--r-- | site_scons/libdeps.py | 189 |
1 files changed, 189 insertions, 0 deletions
diff --git a/site_scons/libdeps.py b/site_scons/libdeps.py new file mode 100644 index 00000000000..24b857c16e8 --- /dev/null +++ b/site_scons/libdeps.py @@ -0,0 +1,189 @@ +"""Extension to SCons providing advanced static library dependency tracking. + +These modifications to a build environment, which can be attached to +StaticLibrary and Program builders via a call to setup_environment(env), +cause the build system to track library dependencies through static libraries, +and to add them to the link command executed when building programs. + +For example, consider a program 'try' that depends on a lib 'tc', which in +turn uses a symbol from a lib 'tb' which in turn uses a library from 'ta'. +Without this package, the Program declaration for "try" looks like this: + +Program('try', ['try.c', 'path/to/${LIBPREFIX}tc${LIBSUFFIX}', + 'path/to/${LIBPREFIX}tc${LIBSUFFIX}', + 'path/to/${LIBPREFIX}tc${LIBSUFFIX}',]) + +With this library, we can instead write the following + +Program('try', ['try.c'], LIBDEPS=['path/to/tc']) +StaticLibrary('tc', ['c.c'], LIBDEPS=['path/to/tb']) +StaticLibrary('tb', ['b.c'], LIBDEPS=['path/to/ta']) +StaticLibrary('ta', ['a.c']) + +And the build system will figure out that it needs to link libta.a and libtb.a +when building 'try'. +""" + +# Copyright (c) 2010, Corensic Inc., All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import os + +import SCons.Errors +import SCons.Scanner +import SCons.Util + +class DependencyCycleError(SCons.Errors.UserError): + """Exception representing a cycle discovered in library dependencies.""" + + def __init__(self, first_node ): + super(DependencyCycleError, self).__init__() + self.cycle_nodes = [first_node] + + def __str__(self): + return " => ".join(str(n) for n in self.cycle_nodes) + +def __get_libdeps(node, env_var): + """Given a SCons Node, return its library dependencies. + + Computes the dependencies if they're not already cached. + """ + + cached_var_name = env_var + '_cached' + + if not hasattr(node.attributes, cached_var_name): + setattr(node.attributes, cached_var_name, __compute_libdeps(node, env_var)) + return getattr(node.attributes, cached_var_name) + +def __compute_libdeps(node, env_var): + """Recursively identify all library dependencies for a node.""" + + if getattr(node.attributes, 'libdeps_exploring', False): + raise DependencyCycleError(node) + + env = node.get_env() + deps = set() + node.attributes.libdeps_exploring = True + try: + try: + for child in env.Flatten(env.get(env_var, [])): + if not child: + continue + deps.add(child) + deps.update(__get_libdeps(child, env_var)) + + except DependencyCycleError, e: + if len(e.cycle_nodes) == 1 or e.cycle_nodes[0] != e.cycle_nodes[-1]: + e.cycle_nodes.append(node) + raise + finally: + node.attributes.libdeps_exploring = False + + return deps + +def update_scanner(builder): + """Update the scanner for "builder" to also scan library dependencies.""" + + old_scanner = builder.target_scanner + + if old_scanner: + path_function = old_scanner.path_function + def new_scanner(node, env, path=()): + result = set(old_scanner.function(node, env, path)) + result.update(__get_libdeps(node, 'LIBDEPS')) + result.update(__get_libdeps(node, 'SYSLIBDEPS')) + return sorted(result) + else: + path_function = None + def new_scanner(node, env, path=()): + result = set(__get_libdeps(node, 'LIBDEPS')) + result.update(__get_libdeps(node, 'SYSLIBDEPS')) + return sorted(result) + + builder.target_scanner = SCons.Scanner.Scanner(function=new_scanner, + path_function=path_function) + +def get_libdeps(source, target, env, for_signature): + """Implementation of the special _LIBDEPS environment variable. + + Expands to the library dependencies for a target. + """ + + if for_signature: + return [] + return list(__get_libdeps(target[0], 'LIBDEPS')) + +def get_syslibdeps(source, target, env, for_signature): + if for_signature: + return[] + deps = list(__get_libdeps(target[0], 'SYSLIBDEPS')) + return deps + +def libdeps_emitter(target, source, env): + """SCons emitter that takes values from the LIBDEPS environment variable and + converts them to File node objects, binding correct path information into + those File objects. + + Emitters run on a particular "target" node during the initial execution of + the SConscript file, rather than during the later build phase. When they + run, the "env" environment's working directory information is what you + expect it to be -- that is, the working directory is considered to be the + one that contains the SConscript file. This allows specification of + relative paths to LIBDEPS elements. + + This emitter also adds LIBSUFFIX and LIBPREFIX appropriately. + """ + + libdep_files = [] + lib_suffix = env.subst('$LIBSUFFIX', target=target, source=source) + lib_prefix = env.subst('$LIBPREFIX', target=target, source=source) + for dep in env.Flatten([env.get('LIBDEPS', [])]): + full_path = env.subst(str(dep), target=target, source=source) + dir_name = os.path.dirname(full_path) + file_name = os.path.basename(full_path) + if not file_name.startswith(lib_prefix): + file_name = '${LIBPREFIX}' + file_name + if not file_name.endswith(lib_suffix): + file_name += '${LIBSUFFIX}' + libdep_files.append(env.File(os.path.join(dir_name, file_name))) + + env['LIBDEPS'] = libdep_files + + return target, source + +def setup_environment(env): + """Set up the given build environment to do LIBDEPS tracking.""" + + env['_LIBDEPS'] = get_libdeps + env['_SYSLIBDEPS'] = ' ${_stripixes(LIBLINKPREFIX, SYSLIBDEPS, LIBLINKSUFFIX, LIBPREFIXES, LIBSUFFIXES, __env__)} ' + env['_SHLIBDEPS'] = '$SHLIBDEP_GROUP_START ${_concat(SHLIBDEPPREFIX, __env__.subst(_LIBDEPS, target=TARGET, source=SOURCE), SHLIBDEPSUFFIX, __env__, target=TARGET, source=SOURCE)} $SHLIBDEP_GROUP_END' + + env['LIBDEPS'] = SCons.Util.CLVar() + env['SYSLIBDEPS'] = SCons.Util.CLVar() + env.Append(LIBEMITTER=libdeps_emitter, + PROGEMITTER=libdeps_emitter, + SHLIBEMITTER=libdeps_emitter) + env.Prepend(_LIBFLAGS=' $LINK_LIBGROUP_START $_LIBDEPS $LINK_LIBGROUP_END $_SYSLIBDEPS ') + for builder_name in ('Program', 'SharedLibrary', 'LoadableModule'): + try: + update_scanner(env['BUILDERS'][builder_name]) + except KeyError: + pass |