| # Status: ported. |
| # Base revision 64444. |
| # |
| # Copyright 2003 Dave Abrahams |
| # Copyright 2005, 2006 Rene Rivera |
| # Copyright 2002, 2003, 2004, 2005, 2006, 2010 Vladimir Prus |
| # Distributed under the Boost Software License, Version 1.0. |
| # (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) |
| |
| # This module defines the 'install' rule, used to copy a set of targets to a |
| # single location. |
| |
| import b2.build.feature as feature |
| import b2.build.targets as targets |
| import b2.build.property as property |
| import b2.build.property_set as property_set |
| import b2.build.generators as generators |
| import b2.build.virtual_target as virtual_target |
| |
| from b2.manager import get_manager |
| from b2.util.sequence import unique |
| from b2.util import bjam_signature |
| |
| import b2.build.type |
| |
| import os.path |
| import re |
| import types |
| |
| feature.feature('install-dependencies', ['off', 'on'], ['incidental']) |
| feature.feature('install-type', [], ['free', 'incidental']) |
| feature.feature('install-source-root', [], ['free', 'path']) |
| feature.feature('so-version', [], ['free', 'incidental']) |
| |
| # If 'on', version symlinks for shared libraries will not be created. Affects |
| # Unix builds only. |
| feature.feature('install-no-version-symlinks', ['on'], ['optional', 'incidental']) |
| |
| class InstallTargetClass(targets.BasicTarget): |
| |
| def update_location(self, ps): |
| """If <location> is not set, sets it based on the project data.""" |
| |
| loc = ps.get('location') |
| if not loc: |
| loc = os.path.join(self.project().get('location'), self.name()) |
| ps = ps.add_raw(["<location>" + loc]) |
| |
| return ps |
| |
| def adjust_properties(self, target, build_ps): |
| a = target.action() |
| properties = [] |
| if a: |
| ps = a.properties() |
| properties = ps.all() |
| |
| # Unless <hardcode-dll-paths>true is in properties, which can happen |
| # only if the user has explicitly requested it, nuke all <dll-path> |
| # properties. |
| |
| if build_ps.get('hardcode-dll-paths') != ['true']: |
| properties = [p for p in properties if p.feature().name() != 'dll-path'] |
| |
| # If any <dll-path> properties were specified for installing, add |
| # them. |
| properties.extend(build_ps.get_properties('dll-path')) |
| |
| # Also copy <linkflags> feature from current build set, to be used |
| # for relinking. |
| properties.extend(build_ps.get_properties('linkflags')) |
| |
| # Remove the <tag> feature on original targets. |
| # And <location>. If stage target has another stage target in |
| # sources, then we shall get virtual targets with the <location> |
| # property set. |
| properties = [p for p in properties |
| if not p.feature().name() in ['tag', 'location']] |
| |
| properties.extend(build_ps.get_properties('dependency')) |
| |
| properties.extend(build_ps.get_properties('location')) |
| |
| |
| properties.extend(build_ps.get_properties('install-no-version-symlinks')) |
| |
| d = build_ps.get_properties('install-source-root') |
| |
| # Make the path absolute: we shall use it to compute relative paths and |
| # making the path absolute will help. |
| if d: |
| p = d[0] |
| properties.append(property.Property(p.feature(), os.path.abspath(p.value()))) |
| |
| return property_set.create(properties) |
| |
| |
| def construct(self, name, source_targets, ps): |
| |
| source_targets = self.targets_to_stage(source_targets, ps) |
| ps = self.update_location(ps) |
| |
| ename = ps.get('name') |
| if ename: |
| ename = ename[0] |
| if ename and len(source_targets) > 1: |
| get_manager().errors()("When <name> property is used in 'install', only one source is allowed") |
| |
| result = [] |
| |
| for i in source_targets: |
| |
| staged_targets = [] |
| new_ps = self.adjust_properties(i, ps) |
| |
| # See if something special should be done when staging this type. It |
| # is indicated by the presence of a special "INSTALLED_" type. |
| t = i.type() |
| if t and b2.build.type.registered("INSTALLED_" + t): |
| |
| if ename: |
| get_manager().errors()("In 'install': <name> property specified with target that requires relinking.") |
| else: |
| (r, targets) = generators.construct(self.project(), name, "INSTALLED_" + t, |
| new_ps, [i]) |
| assert isinstance(r, property_set.PropertySet) |
| staged_targets.extend(targets) |
| |
| else: |
| staged_targets.append(copy_file(self.project(), ename, i, new_ps)) |
| |
| if not staged_targets: |
| get_manager().errors()("Unable to generate staged version of " + i) |
| |
| result.extend(get_manager().virtual_targets().register(t) for t in staged_targets) |
| |
| return (property_set.empty(), result) |
| |
| def targets_to_stage(self, source_targets, ps): |
| """Given the list of source targets explicitly passed to 'stage', returns the |
| list of targets which must be staged.""" |
| |
| result = [] |
| |
| # Traverse the dependencies, if needed. |
| if ps.get('install-dependencies') == ['on']: |
| source_targets = self.collect_targets(source_targets) |
| |
| # Filter the target types, if needed. |
| included_types = ps.get('install-type') |
| for r in source_targets: |
| ty = r.type() |
| if ty: |
| # Do not stage searched libs. |
| if ty != "SEARCHED_LIB": |
| if included_types: |
| if self.include_type(ty, included_types): |
| result.append(r) |
| else: |
| result.append(r) |
| elif not included_types: |
| # Don't install typeless target if there is an explicit list of |
| # allowed types. |
| result.append(r) |
| |
| return result |
| |
| # CONSIDER: figure out why we can not use virtual-target.traverse here. |
| # |
| def collect_targets(self, targets): |
| |
| s = [t.creating_subvariant() for t in targets] |
| s = unique(s) |
| |
| result = set(targets) |
| for i in s: |
| i.all_referenced_targets(result) |
| |
| result2 = [] |
| for r in result: |
| if isinstance(r, property.Property): |
| |
| if r.feature().name() != 'use': |
| result2.append(r.value()) |
| else: |
| result2.append(r) |
| result2 = unique(result2) |
| return result2 |
| |
| # Returns true iff 'type' is subtype of some element of 'types-to-include'. |
| # |
| def include_type(self, type, types_to_include): |
| return any(b2.build.type.is_subtype(type, ti) for ti in types_to_include) |
| |
| # Creates a copy of target 'source'. The 'properties' object should have a |
| # <location> property which specifies where the target must be placed. |
| # |
| def copy_file(project, name, source, ps): |
| |
| if not name: |
| name = source.name() |
| |
| relative = "" |
| |
| new_a = virtual_target.NonScanningAction([source], "common.copy", ps) |
| source_root = ps.get('install-source-root') |
| if source_root: |
| source_root = source_root[0] |
| # Get the real path of the target. We probably need to strip relative |
| # path from the target name at construction. |
| path = os.path.join(source.path(), os.path.dirname(name)) |
| # Make the path absolute. Otherwise, it would be hard to compute the |
| # relative path. The 'source-root' is already absolute, see the |
| # 'adjust-properties' method above. |
| path = os.path.abspath(path) |
| |
| relative = os.path.relpath(path, source_root) |
| |
| name = os.path.join(relative, os.path.basename(name)) |
| return virtual_target.FileTarget(name, source.type(), project, new_a, exact=True) |
| |
| def symlink(name, project, source, ps): |
| a = virtual_target.Action([source], "symlink.ln", ps) |
| return virtual_target.FileTarget(name, source.type(), project, a, exact=True) |
| |
| def relink_file(project, source, ps): |
| action = source.action() |
| cloned_action = virtual_target.clone_action(action, project, "", ps) |
| targets = cloned_action.targets() |
| # We relink only on Unix, where exe or shared lib is always a single file. |
| assert len(targets) == 1 |
| return targets[0] |
| |
| |
| # Declare installed version of the EXE type. Generator for this type will cause |
| # relinking to the new location. |
| b2.build.type.register('INSTALLED_EXE', [], 'EXE') |
| |
| class InstalledExeGenerator(generators.Generator): |
| |
| def __init__(self): |
| generators.Generator.__init__(self, "install-exe", False, ['EXE'], ['INSTALLED_EXE']) |
| |
| def run(self, project, name, ps, source): |
| |
| need_relink = False; |
| |
| if ps.get('os') in ['NT', 'CYGWIN'] or ps.get('target-os') in ['windows', 'cygwin']: |
| # Never relink |
| pass |
| else: |
| # See if the dll-path properties are not changed during |
| # install. If so, copy, don't relink. |
| need_relink = ps.get('dll-path') != source[0].action().properties().get('dll-path') |
| |
| if need_relink: |
| return [relink_file(project, source, ps)] |
| else: |
| return [copy_file(project, None, source[0], ps)] |
| |
| generators.register(InstalledExeGenerator()) |
| |
| |
| # Installing a shared link on Unix might cause a creation of versioned symbolic |
| # links. |
| b2.build.type.register('INSTALLED_SHARED_LIB', [], 'SHARED_LIB') |
| |
| class InstalledSharedLibGenerator(generators.Generator): |
| |
| def __init__(self): |
| generators.Generator.__init__(self, 'install-shared-lib', False, ['SHARED_LIB'], ['INSTALLED_SHARED_LIB']) |
| |
| def run(self, project, name, ps, source): |
| |
| source = source[0] |
| if ps.get('os') in ['NT', 'CYGWIN'] or ps.get('target-os') in ['windows', 'cygwin']: |
| copied = copy_file(project, None, source, ps) |
| return [get_manager().virtual_targets().register(copied)] |
| else: |
| a = source.action() |
| if not a: |
| # Non-derived file, just copy. |
| copied = copy_file(project, source, ps) |
| else: |
| |
| need_relink = ps.get('dll-path') != source.action().properties().get('dll-path') |
| |
| if need_relink: |
| # Rpath changed, need to relink. |
| copied = relink_file(project, source, ps) |
| else: |
| copied = copy_file(project, None, source, ps) |
| |
| result = [get_manager().virtual_targets().register(copied)] |
| # If the name is in the form NNN.XXX.YYY.ZZZ, where all 'X', 'Y' and |
| # 'Z' are numbers, we need to create NNN.XXX and NNN.XXX.YYY |
| # symbolic links. |
| m = re.match("(.*)\\.([0123456789]+)\\.([0123456789]+)\\.([0123456789]+)$", |
| copied.name()); |
| if m: |
| # Symlink without version at all is used to make |
| # -lsome_library work. |
| result.append(symlink(m.group(1), project, copied, ps)) |
| |
| # Symlinks of some libfoo.N and libfoo.N.M are used so that |
| # library can found at runtime, if libfoo.N.M.X has soname of |
| # libfoo.N. That happens when the library makes some binary |
| # compatibility guarantees. If not, it is possible to skip those |
| # symlinks. |
| if ps.get('install-no-version-symlinks') != ['on']: |
| |
| result.append(symlink(m.group(1) + '.' + m.group(2), project, copied, ps)) |
| result.append(symlink(m.group(1) + '.' + m.group(2) + '.' + m.group(3), |
| project, copied, ps)) |
| |
| return result |
| |
| generators.register(InstalledSharedLibGenerator()) |
| |
| |
| # Main target rule for 'install'. |
| # |
| @bjam_signature((["name"], ["sources", "*"], ["requirements", "*"], |
| ["default_build", "*"], ["usage_requirements", "*"])) |
| def install(name, sources, requirements=[], default_build=[], usage_requirements=[]): |
| |
| requirements = requirements[:] |
| # Unless the user has explicitly asked us to hardcode dll paths, add |
| # <hardcode-dll-paths>false in requirements, to override default value. |
| if not '<hardcode-dll-paths>true' in requirements: |
| requirements.append('<hardcode-dll-paths>false') |
| |
| if any(r.startswith('<tag>') for r in requirements): |
| get_manager().errors()("The <tag> property is not allowed for the 'install' rule") |
| |
| from b2.manager import get_manager |
| t = get_manager().targets() |
| |
| project = get_manager().projects().current() |
| |
| return t.main_target_alternative( |
| InstallTargetClass(name, project, |
| t.main_target_sources(sources, name), |
| t.main_target_requirements(requirements, project), |
| t.main_target_default_build(default_build, project), |
| t.main_target_usage_requirements(usage_requirements, project))) |
| |
| get_manager().projects().add_rule("install", install) |
| get_manager().projects().add_rule("stage", install) |
| |