$include_dir="/home/hyper-archives/boost-commit/include"; include("$include_dir/msg-header.inc") ?>
Subject: [Boost-commit] svn:boost r64458 - in trunk/tools/build/v2: build tools
From: ghost_at_[hidden]
Date: 2010-07-30 03:38:19
Author: vladimir_prus
Date: 2010-07-30 03:38:13 EDT (Fri, 30 Jul 2010)
New Revision: 64458
URL: http://svn.boost.org/trac/boost/changeset/64458
Log:
Port tools/stage.jam.
Added:
   trunk/tools/build/v2/tools/stage.py   (contents, props changed)
Text files modified: 
   trunk/tools/build/v2/build/property_set.py   |    17 ++++++++++++-                           
   trunk/tools/build/v2/build/targets.py        |    15 +++++++-----                            
   trunk/tools/build/v2/build/type.py           |     5 +++                                     
   trunk/tools/build/v2/build/virtual_target.py |    48 +++++++++++++++++++++++++-------------- 
   trunk/tools/build/v2/tools/builtin.py        |     2 +                                       
   5 files changed, 61 insertions(+), 26 deletions(-)
Modified: trunk/tools/build/v2/build/property_set.py
==============================================================================
--- trunk/tools/build/v2/build/property_set.py	(original)
+++ trunk/tools/build/v2/build/property_set.py	2010-07-30 03:38:13 EDT (Fri, 30 Jul 2010)
@@ -366,7 +366,7 @@
             # change the location of generated targets
             l = self.get ('<location>')
             if l:
-                computed = l
+                computed = l[0]
                 is_relative = False
 
             else:
@@ -394,7 +394,7 @@
                 is_relative = True
 
             self.target_path_ = (computed, is_relative)
-            
+
         return self.target_path_
                     
     def add (self, ps):
@@ -428,6 +428,19 @@
         
         return self.feature_map_.get(feature, [])
 
+    # FIXME: make this cached
+    def get_properties(self, feature):
+        """Returns all contained properties associated with 'feature'"""
+
+        if not isinstance(feature, b2.build.feature.Feature):
+            feature = b2.build.feature.get(feature)
+
+        result = []        
+        for p in self.all_:
+            if p.feature() == feature:
+                result.append(p)
+        return result
+    
     def __contains__(self, item):
         for p in self.all_set_:
             if p.feature().name() == "toolset":
Modified: trunk/tools/build/v2/build/targets.py
==============================================================================
--- trunk/tools/build/v2/build/targets.py	(original)
+++ trunk/tools/build/v2/build/targets.py	2010-07-30 03:38:13 EDT (Fri, 30 Jul 2010)
@@ -346,9 +346,6 @@
         self.default_build_ = default_build
        
         self.build_dir_ = None
-
-        if parent_project:
-            self.inherit (parent_project)
         
         # A cache of IDs
         self.ids_cache_ = {}
@@ -372,6 +369,10 @@
         # Whether targets for all main target are already created.
         self.built_main_targets_ = 0
 
+        if parent_project:
+            self.inherit (parent_project)
+
+
     # TODO: This is needed only by the 'make' rule. Need to find the
     # way to make 'make' work without this method.
     def project_module (self):
@@ -563,7 +564,7 @@
             value = os.path.join(self.location_, value)
             # Now make the value absolute path
             value = os.path.join(os.getcwd(), value)
-            
+
         self.constants_[name] = value
         bjam.call("set-variable", self.project_module(), name, value)
 
@@ -571,7 +572,7 @@
         for c in parent_project.constants_:
             # No need to pass the type. Path constants were converted to
             # absolute paths already by parent.
-            self.add-constant(parent_project.constants_[c])
+            self.add_constant(c, parent_project.constants_[c])
         
         # Import rules from parent 
         this_module = self.project_module()
@@ -1122,7 +1123,9 @@
                 # usage requirements.
                 source_targets = unique (source_targets)
 
-                result = self.construct (self.name_, source_targets, rproperties)
+                # FIXME: figure why this call messes up source_targets in-place
+                result = self.construct (self.name_, source_targets[:], rproperties)
+
                 if result:
                     assert len(result) == 2
                     gur = result [0]
Modified: trunk/tools/build/v2/build/type.py
==============================================================================
--- trunk/tools/build/v2/build/type.py	(original)
+++ trunk/tools/build/v2/build/type.py	2010-07-30 03:38:13 EDT (Fri, 30 Jul 2010)
@@ -14,6 +14,7 @@
 from b2.util.utility import replace_grist, os_name
 from b2.exceptions import *
 from b2.build import feature, property, scanner
+from b2.util import bjam_signature
 
 __re_hyphen = re.compile ('-')
 
@@ -53,7 +54,7 @@
     
 reset ()
 
-
+@bjam_signature((["type"], ["suffixes", "*"], ["base_type"]))
 def register (type, suffixes = [], base_type = None):
     """ Registers a target type, possibly derived from a 'base-type'. 
         If 'suffixes' are provided, they list all the suffixes that mean a file is of 'type'.
@@ -175,6 +176,7 @@
     # TODO: remove this method
     return is_derived (type, base)
 
+@bjam_signature((["type"], ["properties", "*"], ["suffix"]))
 def set_generated_target_suffix (type, properties, suffix):
     """ Sets a target suffix that should be used when generating target 
         of 'type' with the specified properties. Can be called with
@@ -209,6 +211,7 @@
 # should be used.
 #
 # Usage example: library names use the "lib" prefix on unix.
+@bjam_signature((["type"], ["properties", "*"], ["suffix"]))
 def set_generated_target_prefix(type, properties, prefix):
     set_generated_target_ps(0, type, properties, prefix)
 
Modified: trunk/tools/build/v2/build/virtual_target.py
==============================================================================
--- trunk/tools/build/v2/build/virtual_target.py	(original)
+++ trunk/tools/build/v2/build/virtual_target.py	2010-07-30 03:38:13 EDT (Fri, 30 Jul 2010)
@@ -65,6 +65,7 @@
 import re
 import os.path
 import string
+import types
 
 from b2.util import path, utility, set
 from b2.util.utility import add_grist, get_grist, ungrist, replace_grist, get_value
@@ -73,7 +74,8 @@
 from b2.exceptions import *
 import b2.build.type
 import b2.build.property_set as property_set
-import type
+
+import b2.build.property as property
 
 __re_starts_with_at = re.compile ('^@(.*)')
 
@@ -158,7 +160,7 @@
         if self.files_.has_key (path):
             return self.files_ [path]
 
-        file_type = type.type (file)
+        file_type = b2.build.type.type (file)
 
         result = FileTarget (file, file_type, project,
                              None, file_location)       
@@ -184,7 +186,7 @@
     # Returns all targets from 'targets' with types
     # equal to 'type' or derived from it.
     def select_by_type(self, type, targets):
-        return [t for t in targets if type.is_sybtype(t.type(), type)]
+        return [t for t in targets if b2.build.type.is_sybtype(t.type(), type)]
 
     def register_actual_name (self, actual_name, virtual_target):
         if self.actual_.has_key (actual_name):
@@ -231,7 +233,7 @@
         """ Appends the suffix appropriate to 'type/property_set' combination
             to the specified name and returns the result.
         """
-        suffix = type.generated_target_suffix (file_type, prop_set)
+        suffix = b2.build.type.generated_target_suffix (file_type, prop_set)
 
         if suffix:
             return specified_name + '.' + suffix
@@ -616,7 +618,10 @@
         self.path_ = path
 
     def __str__(self):
-        return self.name_ + "." + self.type_
+        if self.type_:
+            return self.name_ + "." + self.type_
+        else:
+            return self.name_
 
     def clone_with_different_type(self, new_type):
         return FileTarget(self.name_, new_type, self.project_,
@@ -677,13 +682,13 @@
         """
         if not self.path_:
             if self.action_:
-                p = self.action_.properties ()
-                target_path = p.target_path ()
+                p = self.action_.properties ()            
+                (target_path, relative_to_build_dir) = p.target_path ()
                 
-                if target_path [1] == True:
+                if relative_to_build_dir:
                     # Indicates that the path is relative to
                     # build dir.
-                    target_path = os.path.join (self.project_.build_dir (), target_path [0])
+                    target_path = os.path.join (self.project_.build_dir (), target_path)
                                 
                 # Store the computed path, so that it's not recomputed
                 # any more
@@ -717,6 +722,7 @@
     """
     def __init__ (self, manager, sources, action_name, prop_set):
         assert(isinstance(prop_set, property_set.PropertySet))
+        assert type(sources) == types.ListType
         self.sources_ = sources
         self.action_name_ = action_name
         if not prop_set:
@@ -815,7 +821,7 @@
 #                i = self.manager_.get_object (i)
                 
             if i.type ():
-                scanner = type.get_scanner (i.type (), prop_set)
+                scanner = b2.build.type.get_scanner (i.type (), prop_set)
 
             r = i.actualize (scanner)
             result.append (r)
@@ -938,7 +944,7 @@
     if not new_properties:
         new_properties = action.properties()
 
-    closed_action = action.__class__(action.sources(), new_action_name,
+    cloned_action = action.__class__(action.manager_, action.sources(), new_action_name,
                                      new_properties)
                           
     cloned_targets = []
@@ -1018,17 +1024,25 @@
         either directly or indirectly, and either as sources,
         or as dependency properties. Targets referred with
         dependency property are returned a properties, not targets."""
-        
+
         # Find directly referenced targets.
         deps = self.build_properties().dependency()
         all_targets = self.sources_ + deps
         
         # Find other subvariants.
         r = []
-        for t in all_targets:
-            if not t in result:
-                result.add(t)
-                r.append(t.creating_subvariant)
+        for e in all_targets:
+            if not e in result:
+                result.add(e)
+                if isinstance(e, property.Property):
+                    t = e.value()
+                else:
+                    t = e
+                                    
+                # FIXME: how can this be?
+                cs = t.creating_subvariant()
+                if cs:
+                    r.append(cs)
         r = unique(r)
         for s in r:
             if s != self:
@@ -1069,7 +1083,7 @@
     def compute_target_directories(self, target_type=None):
         result = []
         for t in self.created_targets():
-            if not target_type or type.is_derived(t.type(), target_type):
+            if not target_type or b2.build.type.is_derived(t.type(), target_type):
                 result.append(t.path())
 
         for d in self.other_dg_:
Modified: trunk/tools/build/v2/tools/builtin.py
==============================================================================
--- trunk/tools/build/v2/tools/builtin.py	(original)
+++ trunk/tools/build/v2/tools/builtin.py	2010-07-30 03:38:13 EDT (Fri, 30 Jul 2010)
@@ -724,3 +724,5 @@
 ### 
 
 get_manager().projects().add_rule("variant", variant)
+
+import stage
Added: trunk/tools/build/v2/tools/stage.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/tools/stage.py	2010-07-30 03:38:13 EDT (Fri, 30 Jul 2010)
@@ -0,0 +1,349 @@
+# 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):
+
+    # 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)
+