mirror of
https://github.com/boostorg/build.git
synced 2026-02-14 00:32:11 +00:00
351 lines
13 KiB
Python
351 lines
13 KiB
Python
# 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[0].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)
|
|
|