Boost logo

Boost-Commit :

Subject: [Boost-commit] svn:boost r55201 - in trunk/tools/build/v2: . build kernel tools tools/types util
From: ghost_at_[hidden]
Date: 2009-07-30 14:43:15


Author: vladimir_prus
Date: 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
New Revision: 55201
URL: http://svn.boost.org/trac/boost/changeset/55201

Log:
Add experimental Python port
Added:
   trunk/tools/build/v2/build/__init__.py (contents, props changed)
   trunk/tools/build/v2/build/alias.py (contents, props changed)
   trunk/tools/build/v2/build/build_request.py (contents, props changed)
   trunk/tools/build/v2/build/engine.py (contents, props changed)
   trunk/tools/build/v2/build/errors.py (contents, props changed)
   trunk/tools/build/v2/build/feature.py (contents, props changed)
   trunk/tools/build/v2/build/generators.py (contents, props changed)
   trunk/tools/build/v2/build/project.ann.py (contents, props changed)
   trunk/tools/build/v2/build/project.py (contents, props changed)
   trunk/tools/build/v2/build/property.py (contents, props changed)
   trunk/tools/build/v2/build/property_set.py (contents, props changed)
   trunk/tools/build/v2/build/scanner.py (contents, props changed)
   trunk/tools/build/v2/build/targets.py (contents, props changed)
   trunk/tools/build/v2/build/toolset.py (contents, props changed)
   trunk/tools/build/v2/build/type.py (contents, props changed)
   trunk/tools/build/v2/build/virtual_target.py (contents, props changed)
   trunk/tools/build/v2/build_system.py (contents, props changed)
   trunk/tools/build/v2/exceptions.py (contents, props changed)
   trunk/tools/build/v2/manager.py (contents, props changed)
   trunk/tools/build/v2/tools/__init__.py (contents, props changed)
   trunk/tools/build/v2/tools/builtin.py (contents, props changed)
   trunk/tools/build/v2/tools/common.py (contents, props changed)
   trunk/tools/build/v2/tools/darwin.py (contents, props changed)
   trunk/tools/build/v2/tools/gcc.py (contents, props changed)
   trunk/tools/build/v2/tools/make.py (contents, props changed)
   trunk/tools/build/v2/tools/pch.py (contents, props changed)
   trunk/tools/build/v2/tools/rc.py (contents, props changed)
   trunk/tools/build/v2/tools/types/__init__.py (contents, props changed)
   trunk/tools/build/v2/tools/types/asm.py (contents, props changed)
   trunk/tools/build/v2/tools/types/cpp.py (contents, props changed)
   trunk/tools/build/v2/tools/types/exe.py (contents, props changed)
   trunk/tools/build/v2/tools/types/html.py (contents, props changed)
   trunk/tools/build/v2/tools/types/lib.py (contents, props changed)
   trunk/tools/build/v2/tools/types/obj.py (contents, props changed)
   trunk/tools/build/v2/tools/types/rsp.py (contents, props changed)
   trunk/tools/build/v2/tools/unix.py (contents, props changed)
   trunk/tools/build/v2/util/__init__.py (contents, props changed)
   trunk/tools/build/v2/util/logger.py (contents, props changed)
   trunk/tools/build/v2/util/order.py (contents, props changed)
   trunk/tools/build/v2/util/path.py (contents, props changed)
   trunk/tools/build/v2/util/regex.py (contents, props changed)
   trunk/tools/build/v2/util/sequence.py (contents, props changed)
   trunk/tools/build/v2/util/set.py (contents, props changed)
   trunk/tools/build/v2/util/utility.py (contents, props changed)
Text files modified:
   trunk/tools/build/v2/kernel/bootstrap.jam | 113 +++++++++++++++++++++++++++++++++++++--
   1 files changed, 107 insertions(+), 6 deletions(-)

Added: trunk/tools/build/v2/build/__init__.py
==============================================================================

Added: trunk/tools/build/v2/build/alias.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/build/alias.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,62 @@
+# Copyright 2003, 2004, 2006 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)
+
+# Status: ported (danielw)
+# Base revision: 40480
+
+# This module defines the 'alias' rule and associated class.
+#
+# Alias is just a main target which returns its source targets without any
+# processing. For example::
+#
+# alias bin : hello test_hello ;
+# alias lib : helpers xml_parser ;
+#
+# Another important use of 'alias' is to conveniently group source files::
+#
+# alias platform-src : win.cpp : <os>NT ;
+# alias platform-src : linux.cpp : <os>LINUX ;
+# exe main : main.cpp platform-src ;
+#
+# Lastly, it's possible to create local alias for some target, with different
+# properties::
+#
+# alias big_lib : : @/external_project/big_lib/<link>static ;
+#
+
+import targets
+import property_set
+from b2.manager import get_manager
+
+class AliasTarget(targets.BasicTarget):
+
+ def __init__(self, *args):
+ targets.BasicTarget.__init__(self, *args)
+
+ def construct(self, name, source_targets, properties):
+ return [property_set.empty(), source_targets]
+
+ def compute_usage_requirements(self, subvariant):
+ base = targets.BasicTarget.compute_usage_requirements(self, subvariant)
+ # Add source's usage requirement. If we don't do this, "alias" does not
+ # look like 100% alias.
+ return base.add(subvariant.sources_usage_requirements())
+
+def alias(name, sources, requirements=None, default_build=None, usage_requirements=None):
+ project = get_manager().projects().current()
+ targets = get_manager().targets()
+
+ if default_build:
+ default_build = default_build[0]
+
+ targets.main_target_alternative(AliasTarget(
+ name[0], project,
+ targets.main_target_sources(sources, name),
+ targets.main_target_requirements(requirements or [], project),
+ targets.main_target_default_build(default_build, project),
+ targets.main_target_usage_requirements(usage_requirements or [], project)))
+
+# Declares the 'alias' target. It will build sources, and return them unaltered.
+get_manager().projects().add_rule("alias", alias)
+

Added: trunk/tools/build/v2/build/build_request.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/build/build_request.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,211 @@
+# Status: being ported by Vladimir Prus
+# TODO: need to re-compare with mainline of .jam
+# Base revision: 40480
+#
+# (C) Copyright David Abrahams 2002. Permission to copy, use, modify, sell and
+# distribute this software is granted provided this copyright notice appears in
+# all copies. This software is provided "as is" without express or implied
+# warranty, and with no claim as to its suitability for any purpose.
+
+import feature
+from b2.util import set
+from b2.util.utility import *
+
+def expand_no_defaults (property_sets):
+ """ Expand the given build request by combining all property_sets which don't
+ specify conflicting non-free features.
+ """
+ # First make all features and subfeatures explicit
+ expanded_property_sets = [ __apply_to_property_set (feature.expand_subfeatures, x) for x in property_sets ]
+
+ # Now combine all of the expanded property_sets
+ product = __x_product (expanded_property_sets)
+
+ return product
+
+def __apply_to_property_set (f, property_set):
+ """ Transform property_set by applying f to each component property.
+ """
+ properties = feature.split (property_set)
+ return '/'.join (f (properties))
+
+
+
+def __x_product (property_sets):
+ """ Return the cross-product of all elements of property_sets, less any
+ that would contain conflicting values for single-valued features.
+ """
+ x_product_seen = []
+ x_product_used = []
+ feature_space = []
+ return __x_product_aux (property_sets, x_product_seen, x_product_used, feature_space)
+
+def __x_product_aux (property_sets, x_product_seen, x_product_used, feature_space):
+ """ Implementation of __x_product.
+ """
+ result = []
+
+ if property_sets:
+ p = feature.split (property_sets [0])
+ else:
+ p = []
+
+ f = set.difference (get_grist (p), feature.free_features ())
+
+ seen = []
+ # No conflict with things used at a higher level?
+ if not set.intersection (f, x_product_used):
+ # don't mix in any conflicting features
+ local_x_product_used = x_product_used + f
+ local_x_product_seen = []
+
+ if len (property_sets) > 1:
+ rest = __x_product_aux (property_sets [1:], local_x_product_seen, local_x_product_used, feature_space)
+ result = [ property_sets [0] + '/' + x for x in rest]
+
+ if not result and property_sets:
+ result = [property_sets [0]]
+
+ # If we didn't encounter a conflicting feature lower down,
+ # don't recurse again.
+ if not set.intersection (f, local_x_product_seen):
+ property_sets = []
+
+ seen = local_x_product_seen
+
+ if len (property_sets) > 1:
+ result.extend (__x_product_aux (property_sets [1:], x_product_seen, x_product_used, feature_space))
+ x_product_seen += f + seen
+
+ # Note that we've seen these features so that higher levels will
+ # recurse again without them set.
+
+ return result
+
+def looks_like_implicit_value(v):
+ """Returns true if 'v' is either implicit value, or
+ the part before the first '-' symbol is implicit value."""
+ if feature.is_implicit_value(v):
+ return 1
+ else:
+ split = v.split("-")
+ if feature.is_implicit_value(split[0]):
+ return 1
+
+ return 0
+
+def from_command_line(command_line):
+ """Takes the command line tokens (such as taken from ARGV rule)
+ and constructs build request from it. Returns a list of two
+ lists. First is the set of targets specified in the command line,
+ and second is the set of requested build properties."""
+
+ targets = []
+ properties = []
+
+ for e in command_line:
+ if e[0] != "-":
+ # Build request spec either has "=" in it, or completely
+ # consists of implicit feature values.
+ if e.find("=") != -1 or looks_like_implicit_value(e.split("/")[0]):
+ properties += convert_command_line_element(e)
+ else:
+ targets.append(e)
+
+ return [targets, properties]
+
+# Converts one element of command line build request specification into
+# internal form.
+def convert_command_line_element(e):
+
+ result = None
+ parts = e.split("/")
+ for p in parts:
+ m = p.split("=")
+ if len(m) > 1:
+ feature = m[0]
+ values = m[1].split(",")
+ lresult = [("<%s>%s" % (feature, v)) for v in values]
+ else:
+ lresult = p.split(",")
+
+ if p.find('-') == -1:
+ # FIXME: first port property.validate
+ # property.validate cannot handle subfeatures,
+ # so we avoid the check here.
+ #for p in lresult:
+ # property.validate(p)
+ pass
+
+ if not result:
+ result = lresult
+ else:
+ result = [e1 + "/" + e2 for e1 in result for e2 in lresult]
+
+ return result
+
+###
+### rule __test__ ( )
+### {
+### import assert feature ;
+###
+### feature.prepare-test build-request-test-temp ;
+###
+### import build-request ;
+### import build-request : expand_no_defaults : build-request.expand_no_defaults ;
+### import errors : try catch ;
+### import feature : feature subfeature ;
+###
+### feature toolset : gcc msvc borland : implicit ;
+### subfeature toolset gcc : version : 2.95.2 2.95.3 2.95.4
+### 3.0 3.0.1 3.0.2 : optional ;
+###
+### feature variant : debug release : implicit composite ;
+### feature inlining : on off ;
+### feature "include" : : free ;
+###
+### feature stdlib : native stlport : implicit ;
+###
+### feature runtime-link : dynamic static : symmetric ;
+###
+###
+### local r ;
+###
+### r = [ build-request.from-command-line bjam debug runtime-link=dynamic ] ;
+### assert.equal [ $(r).get-at 1 ] : ;
+### assert.equal [ $(r).get-at 2 ] : debug <runtime-link>dynamic ;
+###
+### try ;
+### {
+###
+### build-request.from-command-line bjam gcc/debug runtime-link=dynamic/static ;
+### }
+### catch \"static\" is not a value of an implicit feature ;
+###
+###
+### r = [ build-request.from-command-line bjam -d2 --debug debug target runtime-link=dynamic ] ;
+### assert.equal [ $(r).get-at 1 ] : target ;
+### assert.equal [ $(r).get-at 2 ] : debug <runtime-link>dynamic ;
+###
+### r = [ build-request.from-command-line bjam debug runtime-link=dynamic,static ] ;
+### assert.equal [ $(r).get-at 1 ] : ;
+### assert.equal [ $(r).get-at 2 ] : debug <runtime-link>dynamic <runtime-link>static ;
+###
+### r = [ build-request.from-command-line bjam debug gcc/runtime-link=dynamic,static ] ;
+### assert.equal [ $(r).get-at 1 ] : ;
+### assert.equal [ $(r).get-at 2 ] : debug gcc/<runtime-link>dynamic
+### gcc/<runtime-link>static ;
+###
+### r = [ build-request.from-command-line bjam msvc gcc,borland/runtime-link=static ] ;
+### assert.equal [ $(r).get-at 1 ] : ;
+### assert.equal [ $(r).get-at 2 ] : msvc gcc/<runtime-link>static
+### borland/<runtime-link>static ;
+###
+### r = [ build-request.from-command-line bjam gcc-3.0 ] ;
+### assert.equal [ $(r).get-at 1 ] : ;
+### assert.equal [ $(r).get-at 2 ] : gcc-3.0 ;
+###
+### feature.finish-test build-request-test-temp ;
+### }
+###
+###

Added: trunk/tools/build/v2/build/engine.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/build/engine.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,159 @@
+# Copyright Pedro Ferreira 2005.
+# Copyright Vladimir Prus 2007.
+# Distributed under the Boost
+# Software License, Version 1.0. (See accompanying
+# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+bjam_interface = __import__('bjam')
+
+import operator
+
+class BjamAction:
+ """Class representing bjam action defined from Python."""
+
+ def __init__(self, action_name, function):
+ self.action_name = action_name
+ self.function = function
+
+ def __call__(self, targets, sources, property_set):
+ if self.function:
+ self.function(targets, sources, property_set)
+ # Bjam actions defined from Python have only the command
+ # to execute, and no associated jam procedural code. So
+ # passing 'property_set' to it is not necessary.
+ bjam_interface.call("set-update-action", self.action_name,
+ targets, sources, [])
+
+class BjamNativeAction:
+ """Class representing bjam action fully defined by Jam code."""
+
+ def __init__(self, action_name):
+ self.action_name = action_name
+
+ def __call__(self, targets, sources, property_set):
+ if property_set:
+ bjam_interface.call("set-update-action", self.action_name,
+ targets, sources, property_set.raw())
+ else:
+ bjam_interface.call("set-update-action", self.action_name,
+ targets, sources, [])
+
+action_modifiers = {"updated": 0x01,
+ "together": 0x02,
+ "ignore": 0x04,
+ "quietly": 0x08,
+ "piecemeal": 0x10,
+ "existing": 0x20}
+
+class Engine:
+ """ The abstract interface to a build engine.
+
+ For now, the naming of targets, and special handling of some
+ target variables like SEARCH and LOCATE make this class coupled
+ to bjam engine.
+ """
+ def __init__ (self):
+ self.actions = {}
+
+ def add_dependency (self, targets, sources):
+ """Adds a dependency from 'targets' to 'sources'
+
+ Both 'targets' and 'sources' can be either list
+ of target names, or a single target name.
+ """
+ if isinstance (targets, str):
+ targets = [targets]
+ if isinstance (sources, str):
+ sources = [sources]
+
+ for target in targets:
+ for source in sources:
+ self.do_add_dependency (target, source)
+
+ def set_target_variable (self, targets, variable, value, append=0):
+ """ Sets a target variable.
+
+ The 'variable' will be available to bjam when it decides
+ where to generate targets, and will also be available to
+ updating rule for that 'taret'.
+ """
+ if isinstance (targets, str):
+ targets = [targets]
+
+ for target in targets:
+ self.do_set_target_variable (target, variable, value, append)
+
+ def set_update_action (self, action_name, targets, sources, properties):
+ """ Binds a target to the corresponding update action.
+ If target needs to be updated, the action registered
+ with action_name will be used.
+ The 'action_name' must be previously registered by
+ either 'register_action' or 'register_bjam_action'
+ method.
+ """
+ if isinstance (targets, str):
+ targets = [targets]
+ self.do_set_update_action (action_name, targets, sources, properties)
+
+ def register_action (self, action_name, command, bound_list = [], flags = [],
+ function = None):
+ """Creates a new build engine action.
+
+ Creates on bjam side an action named 'action_name', with
+ 'command' as the command to be executed, 'bound_variables'
+ naming the list of variables bound when the command is executed
+ and specified flag.
+ If 'function' is not None, it should be a callable taking three
+ parameters:
+ - targets
+ - sources
+ - instance of the property_set class
+ This function will be called by set_update_action, and can
+ set additional target variables.
+ """
+ if self.actions.has_key(action_name):
+ raise "Bjam action %s is already defined" % action_name
+
+ assert(isinstance(flags, list))
+
+ bjam_flags = reduce(operator.or_,
+ (action_modifiers[flag] for flag in flags), 0)
+
+ bjam_interface.define_action(action_name, command, bound_list, bjam_flags)
+
+ self.actions[action_name] = BjamAction(action_name, function)
+
+ def register_bjam_action (self, action_name):
+ """Informs self that 'action_name' is declared in bjam.
+
+ From this point, 'action_name' is a valid argument to the
+ set_update_action method. The action_name should be callable
+ in the global module of bjam.
+ """
+
+ # We allow duplicate calls to this rule for the same
+ # action name. This way, jamfile rules that take action names
+ # can just register them without specially checking if
+ # action is already registered.
+ if not self.actions.has_key(action_name):
+ self.actions[action_name] = BjamNativeAction(action_name)
+
+ # Overridables
+
+
+ def do_set_update_action (self, action_name, targets, sources, property_set):
+ action = self.actions.get(action_name)
+ if not action:
+ raise "No action %s was registered" % action_name
+ action(targets, sources, property_set)
+
+ def do_set_target_variable (self, target, variable, value, append):
+ if append:
+ bjam_interface.call("set-target-variable", target, variable, value, "true")
+ else:
+ bjam_interface.call("set-target-variable", target, variable, value)
+
+ def do_add_dependency (self, target, source):
+ bjam_interface.call("DEPENDS", target, source)
+
+

Added: trunk/tools/build/v2/build/errors.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/build/errors.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,122 @@
+# Status: being written afresh by Vladimir Prus
+
+# Copyright 2007 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 file is supposed to implement error reporting for Boost.Build.
+# Experience with jam version has shown that printing full backtrace
+# on each error is buffling. Further, for errors printed after parsing --
+# during target building, the stacktrace does not even mention what
+# target is being built.
+
+# This module implements explicit contexts -- where other code can
+# communicate which projects/targets are being built, and error
+# messages will show those contexts. For programming errors,
+# Python assertions are to be used.
+
+import bjam
+import traceback
+import sys
+
+def format(message, prefix=""):
+ parts = message.split("\n")
+ return "\n".join(prefix+p for p in parts)
+
+
+class Context:
+
+ def __init__(self, message, nested=None):
+ self.message_ = message
+ self.nested_ = nested
+
+ def report(self, indent=""):
+ print indent + " -", self.message_
+ if self.nested_:
+ print indent + " declared at:"
+ for n in self.nested_:
+ n.report(indent + " ")
+
+class JamfileContext:
+
+ def __init__(self):
+ raw = bjam.backtrace()
+ self.raw_ = raw
+
+ def report(self, indent=""):
+ for r in self.raw_:
+ print indent + " - %s:%s" % (r[0], r[1])
+
+class ExceptionWithUserContext(Exception):
+
+ def __init__(self, message, context,
+ original_exception=None, original_tb=None, stack=None):
+ Exception.__init__(self, message)
+ self.context_ = context
+ self.original_exception_ = original_exception
+ self.original_tb_ = original_tb
+ self.stack_ = stack
+
+ def report(self):
+ print "error:", self.message
+ if self.original_exception_:
+ print format(self.original_exception_.message, " ")
+ print
+ print " error context (most recent first):"
+ for c in self.context_[::-1]:
+ c.report()
+ print
+ if "--stacktrace" in bjam.variable("ARGV"):
+ if self.original_tb_:
+ traceback.print_tb(self.original_tb_)
+ elif self.stack_:
+ for l in traceback.format_list(self.stack_):
+ print l,
+ else:
+ print " use the '--stacktrace' option to get Python stacktrace"
+ print
+
+def user_error_checkpoint(callable):
+ def wrapper(self, *args):
+ errors = self.manager().errors()
+ try:
+ return callable(self, *args)
+ except ExceptionWithUserContext, e:
+ raise
+ except Exception, e:
+ errors.handle_stray_exception(e)
+ finally:
+ errors.pop_user_context()
+
+ return wrapper
+
+class Errors:
+
+ def __init__(self):
+ self.contexts_ = []
+
+ def push_user_context(self, message, nested=None):
+ self.contexts_.append(Context(message, nested))
+
+ def pop_user_context(self):
+ del self.contexts_[-1]
+
+ def push_jamfile_context(self):
+ self.contexts_.append(JamfileContext())
+
+ def pop_jamfile_context(self):
+ del self.contexts_[-1]
+
+ def capture_user_context(self):
+ return self.contexts_[:]
+
+ def handle_stray_exception(self, e):
+ raise ExceptionWithUserContext("unexpected exception", self.contexts_[:],
+ e, sys.exc_info()[2])
+ def __call__(self, message):
+ raise ExceptionWithUserContext(message, self.contexts_[:],
+ stack=traceback.extract_stack())
+
+
+
+

Added: trunk/tools/build/v2/build/feature.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/build/feature.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,891 @@
+# Status: mostly ported.
+# TODO: carry over tests.
+# Base revision: 40480
+#
+# Copyright 2001, 2002, 2003 Dave Abrahams
+# Copyright 2002, 2006 Rene Rivera
+# Copyright 2002, 2003, 2004, 2005, 2006 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)
+
+# TODO: stop using grists to identify the name of features?
+# create a class for Features and Properties?
+# represent conditions using object trees, composite pattern?
+
+import re
+
+from b2.util import set, utility
+from b2.util.utility import add_grist, get_grist, ungrist, replace_grist, to_seq
+from b2.exceptions import *
+
+__re_split_subfeatures = re.compile ('<(.*):(.*)>')
+__re_no_hyphen = re.compile ('^([^:]+)$')
+__re_slash_or_backslash = re.compile (r'[\\/]')
+
+def reset ():
+ """ Clear the module state. This is mainly for testing purposes.
+ """
+ global __all_attributes, __all_features, __implicit_features, __composite_properties
+ global __features_with_attributes, __subfeature_value_to_name, __all_top_features, __free_features
+ global __all_subfeatures
+
+ # The list with all attribute names.
+ __all_attributes = [ 'implicit',
+ 'executed',
+ 'composite',
+ 'optional',
+ 'symmetric',
+ 'free',
+ 'incidental',
+ 'path',
+ 'dependency',
+ 'propagated',
+ 'link-incompatible',
+ 'subfeature',
+ 'order-sensitive'
+ ]
+
+ # A map containing all features. The key is the gristed feature name. The value is a map with:
+ # 'values': [],
+ # 'attributes': [],
+ # 'subfeatures': [],
+ # 'default': None
+ __all_features = {}
+
+ # All non-subfeatures.
+ __all_top_features = []
+
+ # Maps valus to the corresponding implicit feature
+ __implicit_features = {}
+
+ # A map containing all composite properties. The key is the name of the property. The value is a map with:
+ # 'components': []
+ __composite_properties = {}
+
+ __features_with_attributes = {}
+ for attribute in __all_attributes:
+ __features_with_attributes [attribute] = []
+
+ # Maps a value to the corresponding subfeature name.
+ __subfeature_value_to_name = {}
+
+ # All free features
+ __free_features = []
+
+ __all_subfeatures = []
+
+reset ()
+
+def enumerate ():
+ """ Returns an iterator to the features map.
+ """
+ return __all_features.iteritems ()
+
+# FIXME: prepare-test/finish-test?
+
+def feature (name, values, attributes = []):
+ """ Declares a new feature with the given name, values, and attributes.
+ name: the feature name
+ values: a sequence of the allowable values - may be extended later with feature.extend
+ attributes: a sequence of the feature's attributes (e.g. implicit, free, propagated, ...)
+ """
+ name = add_grist (name)
+
+ __validate_feature_attributes (name, attributes)
+
+ feature = {
+ 'values': [],
+ 'attributes': attributes,
+ 'subfeatures': [],
+ 'default': None
+ }
+ __all_features [name] = feature
+
+ feature ['attributes'] = attributes
+
+ for attribute in attributes:
+ __features_with_attributes [attribute].append (name)
+
+ if 'subfeature' in attributes:
+ __all_subfeatures.append(name)
+ else:
+ __all_top_features.append(name)
+
+ extend (name, values)
+
+ # FIXME: why his is needed.
+ if 'free' in attributes:
+ __free_features.append (name)
+
+def set_default (feature, value):
+ """ Sets the default value of the given feature, overriding any previous default.
+ feature: the name of the feature
+ value: the default value to assign
+ """
+
+ if isinstance(feature, list):
+ feature = feature[0]
+
+ feature = add_grist (feature)
+ f = __all_features [feature]
+
+ if isinstance(value, list):
+ value = value[0]
+
+ values = f['values']
+ if not value in values:
+ raise InvalidValue ("The specified default value, '%s' is invalid.\n" % value + "allowed values are: %s" % values)
+
+ f ['default'] = value
+
+def defaults (features):
+ """ Returns the default property values for the given features.
+ """
+ result = []
+ for f in features:
+ attributes = __all_features [f]['attributes']
+ if not 'free' in attributes and not 'optional' in attributes:
+ defaults = __all_features [f]['default']
+ if defaults:
+ result.append (replace_grist (defaults, f))
+
+ return result
+
+def valid (names):
+ """ Returns true iff all elements of names are valid features.
+ """
+ def valid_one (name): return __all_features.has_key (name)
+
+ if isinstance (names, str):
+ return valid_one (names)
+ else:
+ return [ valid_one (name) for name in names ]
+
+def attributes (feature):
+ """ Returns the attributes of the given feature.
+ """
+ return __all_features [feature]['attributes']
+
+def values (feature):
+ """ Return the values of the given feature.
+ """
+ validate_feature (feature)
+ return __all_features [feature]['values']
+
+def is_implicit_value (value_string):
+ """ Returns true iff 'value_string' is a value_string
+ of an implicit feature.
+ """
+ v = value_string.split('-')
+
+ if not __implicit_features.has_key(v[0]):
+ return False
+
+ feature = __implicit_features[v[0]]
+
+ for subvalue in (v[1:]):
+ if not __find_implied_subfeature(feature, subvalue, v[0]):
+ return False
+
+ return True
+
+def implied_feature (implicit_value):
+ """ Returns the implicit feature associated with the given implicit value.
+ """
+ components = implicit_value.split('-')
+
+ if not __implicit_features.has_key(components[0]):
+ raise InvalidValue ("'%s' is not a value of an implicit feature" % implicit_value)
+
+ return __implicit_features[components[0]]
+
+def __find_implied_subfeature (feature, subvalue, value_string):
+ feature = add_grist (feature)
+ if value_string == None: value_string = ''
+
+ if not __subfeature_value_to_name.has_key (feature) \
+ or not __subfeature_value_to_name [feature].has_key (value_string) \
+ or not __subfeature_value_to_name [feature][value_string].has_key (subvalue):
+ return None
+
+ return __subfeature_value_to_name[feature][value_string][subvalue]
+
+# Given a feature and a value of one of its subfeatures, find the name
+# of the subfeature. If value-string is supplied, looks for implied
+# subfeatures that are specific to that value of feature
+# feature # The main feature name
+# subvalue # The value of one of its subfeatures
+# value-string # The value of the main feature
+
+def implied_subfeature (feature, subvalue, value_string):
+ result = __find_implied_subfeature (feature, subvalue, value_string)
+ if not result:
+ raise InvalidValue ("'%s' is not a known subfeature value of '%s%s'" % (subvalue, feature, value_string))
+
+ return result
+
+def validate_feature (name):
+ """ Checks if all name is a valid feature. Otherwise, raises an exception.
+ """
+ x = valid (name)
+ if not x:
+ raise InvalidFeature ("'%s' is not a valid feature name" % name)
+
+def valid (names):
+ """ Returns true iff all elements of names are valid features.
+ """
+ def valid_one (name): return __all_features.has_key (name)
+
+ if isinstance (names, str):
+ return valid_one (names)
+ else:
+ return [ valid_one (name) for name in names ]
+
+def __expand_subfeatures_aux (feature, value, dont_validate = False):
+ """ Helper for expand_subfeatures.
+ Given a feature and value, or just a value corresponding to an
+ implicit feature, returns a property set consisting of all component
+ subfeatures and their values. For example:
+
+ expand_subfeatures <toolset>gcc-2.95.2-linux-x86
+ -> <toolset>gcc <toolset-version>2.95.2 <toolset-os>linux <toolset-cpu>x86
+ equivalent to:
+ expand_subfeatures gcc-2.95.2-linux-x86
+
+ feature: The name of the feature, or empty if value corresponds to an implicit property
+ value: The value of the feature.
+ dont_validate: If True, no validation of value string will be done.
+ """
+ if not feature:
+ feature = implied_feature(value)
+ else:
+ validate_feature(feature)
+
+ if not dont_validate:
+ validate_value_string(feature, value)
+
+ components = value.split ("-")
+
+ # get the top-level feature's value
+ value = replace_grist(components[0], '')
+
+ result = [ replace_grist(components[0], feature) ]
+
+ subvalues = components[1:]
+
+ while len(subvalues) > 0:
+ subvalue = subvalues [0] # pop the head off of subvalues
+ subvalues = subvalues [1:]
+
+ subfeature = __find_implied_subfeature (feature, subvalue, value)
+
+ # If no subfeature was found, reconstitute the value string and use that
+ if not subfeature:
+ result = '-'.join(components)
+ result = replace_grist (result, feature)
+ return [result]
+
+ f = ungrist (feature)
+ # FIXME: why grist includes '<>'?
+ result.append (replace_grist (subvalue, '<' + f + '-' + subfeature + '>'))
+
+ return result
+
+def expand_subfeatures (properties, dont_validate = False):
+ """
+ Make all elements of properties corresponding to implicit features
+ explicit, and express all subfeature values as separate properties
+ in their own right. For example, the property
+
+ gcc-2.95.2-linux-x86
+
+ might expand to
+
+ <toolset>gcc <toolset-version>2.95.2 <toolset-os>linux <toolset-cpu>x86
+
+ properties: A sequence with elements of the form
+ <feature>value-string or just value-string in the
+ case of implicit features.
+ : dont_validate: If True, no validation of value string will be done.
+ """
+ result = []
+ for p in properties:
+ p_grist = get_grist (p)
+ # Don't expand subfeatures in subfeatures
+ if ':' in p_grist:
+ result.append (p)
+ else:
+ result.extend (__expand_subfeatures_aux (p_grist, replace_grist (p, ''), dont_validate))
+
+ return result
+
+
+
+# rule extend was defined as below:
+ # Can be called three ways:
+ #
+ # 1. extend feature : values *
+ # 2. extend <feature> subfeature : values *
+ # 3. extend <feature>value-string subfeature : values *
+ #
+ # * Form 1 adds the given values to the given feature
+ # * Forms 2 and 3 add subfeature values to the given feature
+ # * Form 3 adds the subfeature values as specific to the given
+ # property value-string.
+ #
+ #rule extend ( feature-or-property subfeature ? : values * )
+#
+# Now, the specific rule must be called, depending on the desired operation:
+# extend_feature
+# extend_subfeature
+
+def extend (name, values):
+ """ Adds the given values to the given feature.
+ """
+ name = add_grist (name)
+ __validate_feature (name)
+ feature = __all_features [name]
+
+ if 'implicit' in feature ['attributes']:
+ for v in values:
+ if __implicit_features.has_key (v):
+ raise BaseException ("'%s' is already associated with the feature '%s'" % (v, __implicit_features [v]))
+
+ __implicit_features[v] = name
+
+ if len (feature ['values']) == 0 and len (values) > 0:
+ # This is the first value specified for this feature,
+ # take it as default value
+ feature ['default'] = values[0]
+
+ feature['values'].extend (values)
+
+def validate_value_string (feature, value_string):
+ """ Checks that value-string is a valid value-string for the given feature.
+ """
+ f = __all_features [feature]
+ if 'free' in f ['attributes'] or value_string in f ['values']:
+ return
+
+ values = [value_string]
+
+ if f['subfeatures']:
+ values = value_string.split('-')
+
+ # An empty value is allowed for optional features
+ if not values[0] in f['values'] and \
+ (values[0] or not 'optional' in f['attributes']):
+ raise InvalidValue ("'%s' is not a known value of feature '%s'\nlegal values: '%s'" % (values [0], feature, f ['values']))
+
+ for v in values [1:]:
+ # this will validate any subfeature values in value-string
+ implied_subfeature(feature, v, values[0])
+
+
+""" Extends the given subfeature with the subvalues. If the optional
+ value-string is provided, the subvalues are only valid for the given
+ value of the feature. Thus, you could say that
+ <target-platform>mingw is specifc to <toolset>gcc-2.95.2 as follows:
+
+ extend-subfeature toolset gcc-2.95.2 : target-platform : mingw ;
+
+ feature: The feature whose subfeature is being extended.
+
+ value-string: If supplied, specifies a specific value of the
+ main feature for which the new subfeature values
+ are valid.
+
+ subfeature: The name of the subfeature.
+
+ subvalues: The additional values of the subfeature being defined.
+"""
+def extend_subfeature (feature, value_string, subfeature, subvalues):
+ feature = add_grist (feature)
+ validate_feature (feature)
+
+ if value_string:
+ validate_value_string (feature, value_string)
+
+ subfeature_name = __get_subfeature_name (subfeature, value_string)
+
+ f = ungrist (feature)
+ extend (f + '-' + subfeature_name, subvalues) ;
+
+ __add_to_subfeature_value_to_name_map (feature, value_string, subfeature_name, subvalues)
+
+def subfeature (feature_name, value_string, subfeature, subvalues, attributes = []):
+ """ Declares a subfeature.
+ feature_name: Root feature that is not a subfeature.
+ value_string: An optional value-string specifying which feature or
+ subfeature values this subfeature is specific to,
+ if any.
+ subfeature: The name of the subfeature being declared.
+ subvalues: The allowed values of this subfeature.
+ attributes: The attributes of the subfeature.
+ """
+ feature_name = add_grist (feature_name)
+ validate_feature (feature_name)
+
+ # Add grist to the subfeature name if a value-string was supplied
+ subfeature_name = __get_subfeature_name (subfeature, value_string)
+
+ if subfeature_name in __all_features [feature_name]['subfeatures']:
+ message = "'%s' already declared as a subfeature of '%s'" % (subfeature, feature_name)
+ message += " specific to '%s'" % value_string
+ raise BaseException (message)
+
+ __all_features [feature_name]['subfeatures'].append (subfeature_name)
+
+ # First declare the subfeature as a feature in its own right
+ f = ungrist (feature_name)
+ feature (f + '-' + subfeature_name, subvalues, attributes + ['subfeature'])
+
+ # Now make sure the subfeature values are known.
+ extend_subfeature (feature_name, value_string, subfeature, subvalues)
+
+def compose (composite_property, component_properties):
+ """ Sets the components of the given composite property.
+ """
+ component_properties = to_seq (component_properties)
+
+ feature = get_grist (composite_property)
+ if not 'composite' in attributes (feature):
+ raise BaseException ("'%s' is not a composite feature" % feature)
+
+ if __composite_properties.has_key (composite_property):
+ raise BaseException ('components of "%s" already set: %s' % (composite_property, str (__composite_properties [composite_property]['components'])))
+
+ if composite_property in component_properties:
+ raise BaseException ('composite property "%s" cannot have itself as a component' % composite_property)
+
+ entry = { 'components': component_properties }
+ __composite_properties [composite_property] = entry
+
+
+def expand_composite (property):
+ result = [ property ]
+ if __composite_properties.has_key (property):
+ for p in __composite_properties [property]['components']:
+ result.extend (expand_composite (p))
+ return result
+
+
+def get_values (feature, properties):
+ """ Returns all values of the given feature specified by the given property set.
+ """
+ result = []
+ for p in properties:
+ if get_grist (p) == feature:
+ result.append (replace_grist (p, ''))
+
+ return result
+
+def free_features ():
+ """ Returns all free features.
+ """
+ return __free_features
+
+def expand_composites (properties):
+ """ Expand all composite properties in the set so that all components
+ are explicitly expressed.
+ """
+ explicit_features = get_grist (properties)
+
+ result = []
+
+ # now expand composite features
+ for p in properties:
+ expanded = expand_composite (p)
+
+ for x in expanded:
+ if not x in result:
+ f = get_grist (x)
+
+ if f in __free_features:
+ result.append (x)
+ elif not x in properties: # x is the result of expansion
+ if not f in explicit_features: # not explicitly-specified
+ if f in get_grist (result):
+ raise FeatureConflict ("expansions of composite features result in "
+ "conflicting values for '%s'\nvalues: '%s'\none contributing composite property was '%s'" % (f,
+ get_values (f, result) + [replace_grist (x, '')], p))
+ else:
+ result.append (x)
+ elif f in get_grist (result):
+ raise FeatureConflict ("explicitly-specified values of non-free feature '%s' conflict\n"
+ "existing values: '%s'\nvalue from expanding '%s': '%s'" % (f,
+ get_values (f, properties), p, replace_grist (x, '')))
+ else:
+ result.append (x)
+
+ return result
+
+def is_subfeature_of (parent_property, f):
+ """ Return true iff f is an ordinary subfeature of the parent_property's
+ feature, or if f is a subfeature of the parent_property's feature
+ specific to the parent_property's value.
+ """
+ if not valid (f) or not 'subfeature' in __all_features [f]['attributes']:
+ return False
+
+ specific_subfeature = __re_split_subfeatures.match (f)
+
+ if specific_subfeature:
+ # The feature has the form
+ # <topfeature-topvalue:subfeature>,
+ # e.g. <toolset-msvc:version>
+ feature_value = split_top_feature(specific_subfeature.group(1))
+ if replace_grist (feature_value [1], '<' + feature_value [0] + '>') == parent_property:
+ return True
+ else:
+ # The feature has the form <topfeature-subfeature>,
+ # e.g. <toolset-version>
+ top_sub = split_top_feature (ungrist (f))
+
+ if top_sub [1] and add_grist (top_sub [0]) == get_grist (parent_property):
+ return True
+
+ return False
+
+def __is_subproperty_of (parent_property, p):
+ """ As is_subfeature_of, for subproperties.
+ """
+ return is_subfeature_of (parent_property, get_grist (p))
+
+
+# Returns true iff the subvalue is valid for the feature. When the
+# optional value-string is provided, returns true iff the subvalues
+# are valid for the given value of the feature.
+def is_subvalue(feature, value_string, subfeature, subvalue):
+
+ if not value_string:
+ value_string = ''
+
+ if not __subfeature_value_to_name.has_key(feature):
+ return False
+
+ if not __subfeature_value_to_name[feature].has_key(value_string):
+ return False
+
+ if not __subfeature_value_to_name[feature][value_string].has_key(subvalue):
+ return False
+
+ if __subfeature_value_to_name[feature][value_string][subvalue]\
+ != subfeature:
+ return False
+
+ return True
+
+
+
+
+def implied_subfeature (feature, subvalue, value_string):
+ result = __find_implied_subfeature (feature, subvalue, value_string)
+ if not result:
+ raise InvalidValue ("'%s' is not a known subfeature value of '%s%s'" % (subvalue, feature, value_string))
+
+ return result
+
+
+def expand (properties):
+ """ Given a property set which may consist of composite and implicit
+ properties and combined subfeature values, returns an expanded,
+ normalized property set with all implicit features expressed
+ explicitly, all subfeature values individually expressed, and all
+ components of composite properties expanded. Non-free features
+ directly expressed in the input properties cause any values of
+ those features due to composite feature expansion to be dropped. If
+ two values of a given non-free feature are directly expressed in the
+ input, an error is issued.
+ """
+ expanded = expand_subfeatures (properties)
+ return expand_composites (expanded)
+
+
+def split_top_feature (feature_plus):
+ """ Given an ungristed string, finds the longest prefix which is a
+ top-level feature name followed by a dash, and return a pair
+ consisting of the parts before and after that dash. More
+ interesting than a simple split because feature names can contain
+ dashes.
+ """
+ e = feature_plus.split ('-')
+ f = e [0]
+
+ v = None
+ while e:
+ if add_grist (f) in __all_top_features:
+ if len (e) > 1:
+ after = '-'.join (e [1:])
+ else:
+ after = ''
+
+ v = (f, after)
+
+ e = e [1:]
+ f = f + '-'
+ if len (e): f += e [0]
+
+ return v
+
+def add_defaults (properties):
+ """ Given a set of properties, add default values for features not
+ represented in the set.
+ Note: if there's there's ordinary feature F1 and composite feature
+ F2, which includes some value for F1, and both feature have default values,
+ then the default value of F1 will be added, not the value in F2. This might
+ not be right idea: consider
+
+ feature variant : debug ... ;
+ <variant>debug : .... <runtime-debugging>on
+ feature <runtime-debugging> : off on ;
+
+ Here, when adding default for an empty property set, we'll get
+
+ <variant>debug <runtime_debugging>off
+
+ and that's kind of strange.
+ """
+ result = [ x for x in properties ]
+
+ for v in replace_grist (properties, ''):
+ if v in properties:
+ raise BaseException ("'add_defaults' requires explicitly specified features, but '%s' appears to be the value of an un-expanded implicit feature" % v)
+
+ # We don't add default for elements with ":" inside. This catches:
+ # 1. Conditional properties --- we don't want <variant>debug:<define>DEBUG
+ # to be takes as specified value for <variant>
+ # 2. Free properties with ":" in values. We don't care, since free properties
+ # don't have defaults.
+ xproperties = [ property for property in properties if __re_no_hyphen.match (property) ]
+ missing_top = set.difference (__all_top_features, get_grist (xproperties))
+ more = defaults (missing_top)
+ result += more
+ xproperties += more
+
+ # Add defaults for subfeatures of features which are present
+ for p in xproperties:
+ gp = get_grist (p)
+ s = []
+ if __all_features.has_key (gp):
+ s = __all_features [gp]['subfeatures']
+ f = ungrist (gp)
+
+ xbase = ['<%s-%s>' % (f, xs) for xs in s]
+
+ missing_subs = set.difference (xbase, get_grist (result))
+ result += defaults (__select_subfeatures (p, missing_subs))
+
+ return result
+
+def minimize (properties):
+ """ Given an expanded property set, eliminate all redundancy: properties
+ which are elements of other (composite) properties in the set will
+ be eliminated. Non-symmetric properties equal to default values will be
+ eliminated, unless the override a value from some composite property.
+ Implicit properties will be expressed without feature
+ grist, and sub-property values will be expressed as elements joined
+ to the corresponding main property.
+ """
+# FXIME: the code below was in the original feature.jam file, however 'p' is not defined.
+# # Precondition checking
+# local implicits = [ set.intersection $(p:G=) : $(p:G) ] ;
+# if $(implicits)
+# {
+# error minimize requires an expanded property set, but \"$(implicits[1])\"
+# appears to be the value of an un-expanded implicit feature ;
+# }
+
+ # remove properties implied by composite features
+ components = []
+ for property in properties:
+ if __composite_properties.has_key (property):
+ components.extend (__composite_properties [property]['components'])
+
+ x = set.difference (properties, components)
+
+ # handle subfeatures and implicit features
+ x = __move_subfeatures_to_the_end (x)
+
+ result = []
+ while x:
+ fullp = x [0]
+ p = fullp
+ f = get_grist (p)
+ v = replace_grist (p, '')
+
+ # eliminate features in implicit properties.
+ if 'implicit' in __all_features [f]['attributes']:
+ p = v
+
+ # locate all subproperties of $(x[1]) in the property set
+ subproperties = __select_subproperties (fullp, x)
+
+ if subproperties:
+ # reconstitute the joined property name
+ subproperties.sort ()
+ joined = p + '-' + '-'.join (replace_grist (subproperties, ''))
+ result.append (joined)
+
+ x = set.difference (x [1:], subproperties)
+
+ else:
+ # eliminate properties whose value is equal to feature's
+ # default and which are not symmetric and which do not
+ # contradict values implied by composite properties.
+
+ # since all component properties of composites in the set
+ # have been eliminated, any remaining property whose
+ # feature is the same as a component of a composite in the
+ # set must have a non-redundant value.
+ if [fullp] != defaults ([f]) or 'symmetric' in attributes (f)\
+ or get_grist (fullp) in get_grist (components):
+ result.append (p)
+
+ x = x [1:]
+
+ return result
+
+
+def split (properties):
+ """ Given a property-set of the form
+ v1/v2/...vN-1/<fN>vN/<fN+1>vN+1/...<fM>vM
+
+ Returns
+ v1 v2 ... vN-1 <fN>vN <fN+1>vN+1 ... <fM>vM
+
+ Note that vN...vM may contain slashes. This is resilient to the
+ substitution of backslashes for slashes, since Jam, unbidden,
+ sometimes swaps slash direction on NT.
+ """
+
+ def split_one (properties):
+ pieces = re.split (__re_slash_or_backslash, properties)
+ result = []
+
+ for x in pieces:
+ if not get_grist (x) and len (result) > 0 and get_grist (result [-1]):
+ result = result [0:-1] + [ result [-1] + '/' + x ]
+ else:
+ result.append (x)
+
+ return result
+
+ if isinstance (properties, str):
+ return split_one (properties)
+
+ result = []
+ for p in properties:
+ result += split_one (p)
+ return result
+
+
+def compress_subproperties (properties):
+ """ Combine all subproperties into their parent properties
+
+ Requires: for every subproperty, there is a parent property. All
+ features are explicitly expressed.
+
+ This rule probably shouldn't be needed, but
+ build-request.expand-no-defaults is being abused for unintended
+ purposes and it needs help
+ """
+ result = []
+ matched_subs = []
+ for p in properties:
+ pg = get_grist (p)
+ if not pg:
+ raise BaseException ("Gristed variable exppected. Got '%s'." % p)
+
+ if not 'subfeature' in __all_features [pg]['attributes']:
+ subs = __select_subproperties (p, properties)
+
+ matched_subs.extend (subs)
+
+ subvalues = '-'.join (get_value (subs))
+ if subvalues: subvalues = '-' + subvalues
+
+ result.append (p + subvalues)
+
+ else:
+ all_subs.append (p)
+
+ # TODO: this variables are used just for debugging. What's the overhead?
+ assert (set.equal (all_subs, matched_subs))
+
+ return result
+
+######################################################################################
+# Private methods
+
+def __select_subproperties (parent_property, properties):
+ return [ x for x in properties if __is_subproperty_of (parent_property, x) ]
+
+def __move_subfeatures_to_the_end (properties):
+ """ Helper for minimize, below - returns the list with
+ the same properties, but where all subfeatures
+ are in the end of the list
+ """
+ x1 = []
+ x2 = []
+ for p in properties:
+ if 'subfeature' in __all_features [get_grist (p)]['attributes']:
+ x2.append (p)
+
+ else:
+ x1.append (p)
+
+ return x1 + x2
+
+def __get_subfeature_name (subfeature, value_string):
+ if value_string == None:
+ prefix = ''
+ else:
+ prefix = value_string + ':'
+
+ return prefix + subfeature
+
+
+def __validate_feature_attributes (name, attributes):
+ for attribute in attributes:
+ if not attribute in __all_attributes:
+ raise InvalidAttribute ("unknown attributes: '%s' in feature declaration: '%s'" % (str (set.difference (attributes, __all_attributes)), name))
+
+ if name in __all_features:
+ raise AlreadyDefined ("feature '%s' already defined" % name)
+ elif 'implicit' in attributes and 'free' in attributes:
+ raise InvalidAttribute ("free features cannot also be implicit (in declaration of feature '%s')" % name)
+ elif 'free' in attributes and 'propagated' in attributes:
+ raise InvalidAttribute ("free features cannot also be propagated (in declaration of feature '%s')" % name)
+
+
+def __validate_feature (feature):
+ """ Generates an error if the feature is unknown.
+ """
+ if not __all_features.has_key (feature):
+ raise BaseException ('unknown feature "%s"' % feature)
+
+def __add_to_subfeature_value_to_name_map (feature, value_string, subfeature_name, subvalues):
+ # provide a way to get from the given feature or property and
+ # subfeature value to the subfeature name.
+ if value_string == None: value_string = ''
+
+ if not __subfeature_value_to_name.has_key (feature):
+ __subfeature_value_to_name [feature] = {}
+
+ if not __subfeature_value_to_name [feature].has_key (value_string):
+ __subfeature_value_to_name [feature][value_string] = {}
+
+ for subvalue in subvalues:
+ __subfeature_value_to_name [feature][value_string][subvalue] = subfeature_name
+
+
+def __select_subfeatures (parent_property, features):
+ """ Given a property, return the subset of features consisting of all
+ ordinary subfeatures of the property's feature, and all specific
+ subfeatures of the property's feature which are conditional on the
+ property's value.
+ """
+ return [f for f in features if is_subfeature_of (parent_property, f)]
+
+# FIXME: copy over tests.

Added: trunk/tools/build/v2/build/generators.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/build/generators.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,967 @@
+# Status: being ported by Vladimir Prus
+# Base revision: 41557
+# TODO: replace the logging with dout
+
+# Copyright Vladimir Prus 2002.
+# Copyright Rene Rivera 2006.
+#
+# Distributed under the Boost Software License, Version 1.0.
+# (See accompanying file LICENSE_1_0.txt or copy at
+# http://www.boost.org/LICENSE_1_0.txt)
+
+# Manages 'generators' --- objects which can do transformation between different
+# target types and contain algorithm for finding transformation from sources
+# to targets.
+#
+# The main entry point to this module is generators.construct rule. It is given
+# a list of source targets, desired target type and a set of properties.
+# It starts by selecting 'viable generators', which have any chances of producing
+# the desired target type with the required properties. Generators are ranked and
+# a set of most specific ones is selected.
+#
+# The most specific generators have their 'run' methods called, with the properties
+# and list of sources. Each one selects target which can be directly consumed, and
+# tries to convert the remaining ones to the types it can consume. This is done
+# by recursively calling 'construct' with all consumable types.
+#
+# If the generator has collected all the targets it needs, it creates targets
+# corresponding to result, and returns it. When all generators have been run,
+# results of one of them are selected and returned as result.
+#
+# It's quite possible that 'construct' returns more targets that it was asked for.
+# For example, it was asked to target type EXE, but the only found generators produces
+# both EXE and TDS (file with debug) information. The extra target will be returned.
+#
+# Likewise, when generator tries to convert sources to consumable types, it can get
+# more targets that it was asked for. The question is what to do with extra targets.
+# Boost.Build attempts to convert them to requested types, and attempts as early as
+# possible. Specifically, this is done after invoking each generator. (Later I'll
+# document the rationale for trying extra target conversion at that point).
+#
+# That early conversion is not always desirable. Suppose a generator got a source of
+# type Y and must consume one target of type X_1 and one target of type X_2.
+# When converting Y to X_1 extra target of type Y_2 is created. We should not try to
+# convert it to type X_1, because if we do so, the generator will get two targets
+# of type X_1, and will be at loss as to which one to use. Because of that, the
+# 'construct' rule has a parameter, telling if multiple targets can be returned. If
+# the parameter is false, conversion of extra targets is not performed.
+
+
+import re
+import cStringIO
+import os.path
+
+from virtual_target import Subvariant
+import virtual_target, type, property_set, property
+from b2.util.logger import *
+from b2.util.utility import *
+from b2.util import set
+from b2.util.sequence import unique
+import b2.util.sequence as sequence
+from b2.manager import get_manager
+
+def reset ():
+ """ Clear the module state. This is mainly for testing purposes.
+ """
+ global __generators, __type_to_generators, __generators_for_toolset, __construct_stack
+ global __overrides, __active_generators
+ global __viable_generators_cache, __viable_source_types_cache
+
+ __generators = {}
+ __type_to_generators = {}
+ __generators_for_toolset = {}
+ __overrides = {}
+
+ # TODO: can these be global?
+ __construct_stack = []
+ __viable_generators_cache = {}
+ __viable_source_types_cache = {}
+ __active_generators = []
+
+reset ()
+
+_re_separate_types_prefix_and_postfix = re.compile ('([^\\(]*)(\\((.*)%(.*)\\))?')
+_re_match_type = re.compile('([^\\(]*)(\\(.*\\))?')
+
+
+__debug = None
+__indent = ""
+
+def debug():
+ global __debug
+ if __debug is None:
+ __debug = "--debug-generators" in bjam.variable("ARGV")
+ return __debug
+
+def increase_indent():
+ global __indent
+ __indent += " "
+
+def decrease_indent():
+ global __indent
+ __indent = __indent[0:-4]
+
+def dout(message):
+ if debug():
+ print __indent + message
+
+def normalize_target_list (targets):
+ """ Takes a vector of 'virtual-target' instances and makes a normalized
+ representation, which is the same for given set of targets,
+ regardless of their order.
+ """
+ return (targets[0], targets[1].sort ())
+
+
+class Generator:
+ """ Creates a generator.
+ manager: the build manager.
+ id: identifies the generator
+
+ rule: the rule which sets up build actions.
+
+ composing: whether generator processes each source target in
+ turn, converting it to required types.
+ Ordinary generators pass all sources together to
+ recusrive generators.construct_types call.
+
+ source_types (optional): types that this generator can handle
+
+ target_types_and_names: types the generator will create and, optionally, names for
+ created targets. Each element should have the form
+ type["(" name-pattern ")"]
+ for example, obj(%_x). Name of generated target will be found
+ by replacing % with the name of source, provided explicit name
+ was not specified.
+
+ requirements (optional)
+
+ NOTE: all subclasses must have a similar signature for clone to work!
+ """
+ def __init__ (self, id, composing, source_types, target_types_and_names, requirements):
+ assert(not isinstance(source_types, str))
+ assert(not isinstance(target_types_and_names, str))
+ self.id_ = id
+ self.composing_ = composing
+ self.source_types_ = source_types
+ self.target_types_and_names_ = target_types_and_names
+ self.requirements_ = requirements
+
+ self.target_types_ = []
+ self.name_prefix_ = []
+ self.name_postfix_ = []
+
+ for e in target_types_and_names:
+ # Create three parallel lists: one with the list of target types,
+ # and two other with prefixes and postfixes to be added to target
+ # name. We use parallel lists for prefix and postfix (as opposed
+ # to mapping), because given target type might occur several times,
+ # for example "H H(%_symbols)".
+ m = _re_separate_types_prefix_and_postfix.match (e)
+
+ if not m:
+ raise BaseException ("Invalid type and name '%s' in declaration of type '%s'" % (e, id))
+
+ target_type = m.group (1)
+ if not target_type: target_type = ''
+ prefix = m.group (3)
+ if not prefix: prefix = ''
+ postfix = m.group (4)
+ if not postfix: postfix = ''
+
+ self.target_types_.append (target_type)
+ self.name_prefix_.append (prefix)
+ self.name_postfix_.append (postfix)
+
+ for x in self.source_types_:
+ type.validate (x)
+
+ for x in self.target_types_:
+ type.validate (x)
+
+ def clone (self, new_id, new_toolset_properties):
+ """ Returns another generator which differers from $(self) in
+ - id
+ - value to <toolset> feature in properties
+ """
+ return self.__class__ (new_id,
+ self.composing_,
+ self.source_types_,
+ self.target_types_and_names_,
+ # Note: this does not remove any subfeatures of <toolset>
+ # which might cause problems
+ property.change (self.requirements_, '<toolset>') + new_toolset_properties)
+
+ def clone_and_change_target_type(self, base, type):
+ """Creates another generator that is the same as $(self), except that
+ if 'base' is in target types of $(self), 'type' will in target types
+ of the new generator."""
+ target_types = []
+ for t in self.target_types_and_names_:
+ m = _re_match_type.match(t)
+ assert m
+
+ if m.group(1) == base:
+ if m.group(2):
+ target_types.append(type + m.group(2))
+ else:
+ target_types.append(type)
+ else:
+ target_types.append(t)
+
+ return self.__class__(self.id_, self.composing_,
+ self.source_types_,
+ target_types,
+ self.requirements_)
+
+
+ def id (self):
+ return self.id_
+
+ def source_types (self):
+ """ Returns the list of target type the generator accepts.
+ """
+ return self.source_types_
+
+ def target_types (self):
+ """ Returns the list of target types that this generator produces.
+ It is assumed to be always the same -- i.e. it cannot change depending
+ list of sources.
+ """
+ return self.target_types_
+
+ def requirements (self):
+ """ Returns the required properties for this generator. Properties
+ in returned set must be present in build properties if this
+ generator is to be used. If result has grist-only element,
+ that build properties must include some value of that feature.
+ """
+ return self.requirements_
+
+ def match_rank (self, property_set_to_match):
+ """ Returns true if the generator can be run with the specified
+ properties.
+ """
+ # See if generator's requirements are satisfied by
+ # 'properties'. Treat a feature name in requirements
+ # (i.e. grist-only element), as matching any value of the
+ # feature.
+ all_requirements = self.requirements ()
+
+ property_requirements = []
+ feature_requirements = []
+ for r in all_requirements:
+ if get_value (r):
+ property_requirements.append (r)
+
+ else:
+ feature_requirements.append (r)
+
+ properties_to_match = property_set_to_match.raw ()
+
+ return set.contains (property_requirements, properties_to_match) \
+ and set.contains (feature_requirements, get_grist (properties_to_match))
+
+ def run (self, project, name, prop_set, sources):
+ """ Tries to invoke this generator on the given sources. Returns a
+ list of generated targets (instances of 'virtual-target').
+
+ project: Project for which the targets are generated.
+
+ name: Determines the name of 'name' attribute for
+ all generated targets. See 'generated_targets' method.
+
+ prop_set: Desired properties for generated targets.
+
+ sources: Source targets.
+ """
+
+ if project.manager ().logger ().on ():
+ project.manager ().logger ().log (__name__, " generator '%s'" % self.id_)
+ project.manager ().logger ().log (__name__, " composing: '%s'" % self.composing_)
+
+ if not self.composing_ and len (sources) > 1 and len (self.source_types_) > 1:
+ raise BaseException ("Unsupported source/source_type combination")
+
+ # We don't run composing generators if no name is specified. The reason
+ # is that composing generator combines several targets, which can have
+ # different names, and it cannot decide which name to give for produced
+ # target. Therefore, the name must be passed.
+ #
+ # This in effect, means that composing generators are runnable only
+ # at top-level of transofrmation graph, or if name is passed explicitly.
+ # Thus, we dissallow composing generators in the middle. For example, the
+ # transofrmation CPP -> OBJ -> STATIC_LIB -> RSP -> EXE won't be allowed
+ # (the OBJ -> STATIC_LIB generator is composing)
+ if not self.composing_ or name:
+ return self.run_really (project, name, prop_set, sources)
+ else:
+ return []
+
+ def run_really (self, project, name, prop_set, sources):
+
+ # consumed: Targets that this generator will consume directly.
+ # bypassed: Targets that can't be consumed and will be returned as-is.
+
+ if self.composing_:
+ (consumed, bypassed) = self.convert_multiple_sources_to_consumable_types (project, prop_set, sources)
+ else:
+ (consumed, bypassed) = self.convert_to_consumable_types (project, name, prop_set, sources)
+
+ result = []
+ if consumed:
+ result = self.construct_result (consumed, project, name, prop_set)
+ result.extend (bypassed)
+
+ if result:
+ if project.manager ().logger ().on ():
+ project.manager ().logger ().log (__name__, " SUCCESS: ", result)
+
+ else:
+ project.manager ().logger ().log (__name__, " FAILURE")
+
+ return result
+
+ def construct_result (self, consumed, project, name, prop_set):
+ """ Constructs the dependency graph that will be returned by this
+ generator.
+ consumed: Already prepared list of consumable targets
+ If generator requires several source files will contain
+ exactly len $(self.source_types_) targets with matching types
+ Otherwise, might contain several targets with the type of
+ self.source_types_ [0]
+ project:
+ name:
+ prop_set: Properties to be used for all actions create here
+ """
+ result = []
+ # If this is 1->1 transformation, apply it to all consumed targets in order.
+ if len (self.source_types_) < 2 and not self.composing_:
+
+ for r in consumed:
+ result.extend (self.generated_targets ([r], prop_set, project, name))
+
+ else:
+
+ if consumed:
+ result.extend (self.generated_targets (consumed, prop_set, project, name))
+
+ return result
+
+ def determine_output_name(self, sources):
+ """Determine the name of the produced target from the
+ names of the sources."""
+
+ # The simple case if when a name
+ # of source has single dot. Then, we take the part before
+ # dot. Several dots can be caused by:
+ # - Using source file like a.host.cpp
+ # - A type which suffix has a dot. Say, we can
+ # type 'host_cpp' with extension 'host.cpp'.
+ # In the first case, we want to take the part till the last
+ # dot. In the second case -- no sure, but for now take
+ # the part till the last dot too.
+ name = os.path.splitext(sources[0].name())[0]
+
+ for s in sources[1:]:
+ n2 = os.path.splitext(s.name())
+ if n2 != name:
+ get_manager().errors()(
+ "%s: source targets have different names: cannot determine target name"
+ % (self.id_))
+
+ # Names of sources might include directory. We should strip it.
+ return os.path.basename(name)
+
+
+ def generated_targets (self, sources, prop_set, project, name):
+ """ Constructs targets that are created after consuming 'sources'.
+ The result will be the list of virtual-target, which the same length
+ as 'target_types' attribute and with corresponding types.
+
+ When 'name' is empty, all source targets must have the same value of
+ the 'name' attribute, which will be used instead of the 'name' argument.
+
+ The value of 'name' attribute for each generated target will be equal to
+ the 'name' parameter if there's no name pattern for this type. Otherwise,
+ the '%' symbol in the name pattern will be replaced with the 'name' parameter
+ to obtain the 'name' attribute.
+
+ For example, if targets types are T1 and T2(with name pattern "%_x"), suffixes
+ for T1 and T2 are .t1 and t2, and source if foo.z, then created files would
+ be "foo.t1" and "foo_x.t2". The 'name' attribute actually determined the
+ basename of a file.
+
+ Note that this pattern mechanism has nothing to do with implicit patterns
+ in make. It's a way to produce target which name is different for name of
+ source.
+ """
+ if not name:
+ name = self.determine_output_name(sources)
+
+ # Assign an action for each target
+ action = self.action_class()
+ a = action (project.manager(), sources, self.id_, prop_set)
+
+ # Create generated target for each target type.
+ targets = []
+ pre = self.name_prefix_
+ post = self.name_postfix_
+ for t in self.target_types_:
+ generated_name = pre[0] + name + post[0]
+ pre = pre[1:]
+ post = post[1:]
+
+ targets.append(virtual_target.FileTarget(generated_name, False, t, project, a))
+
+ return [ project.manager().virtual_targets().register(t) for t in targets ]
+
+ def convert_to_consumable_types (self, project, name, prop_set, sources, only_one=False):
+ """ Attempts to convert 'source' to the types that this generator can
+ handle. The intention is to produce the set of targets can should be
+ used when generator is run.
+ only_one: convert 'source' to only one of source types
+ if there's more that one possibility, report an
+ error.
+
+ Returns a pair:
+ consumed: all targets that can be consumed.
+ bypassed: all targets that cannot be consumed.
+ """
+ consumed = []
+ bypassed = []
+ missing_types = []
+
+ if len (sources) > 1:
+ # Don't know how to handle several sources yet. Just try
+ # to pass the request to other generator
+ missing_types = self.source_types_
+
+ else:
+ (c, m) = self.consume_directly (sources [0])
+ consumed += c
+ missing_types += m
+
+ # No need to search for transformation if
+ # some source type has consumed source and
+ # no more source types are needed.
+ if only_one and consumed:
+ missing_types = []
+
+ #TODO: we should check that only one source type
+ #if create of 'only_one' is true.
+ # TODO: consider if consuned/bypassed separation should
+ # be done by 'construct_types'.
+
+ if missing_types:
+ transformed = construct_types (project, name, missing_types, prop_set, sources)
+
+ # Add targets of right type to 'consumed'. Add others to
+ # 'bypassed'. The 'generators.construct' rule has done
+ # its best to convert everything to the required type.
+ # There's no need to rerun it on targets of different types.
+
+ # NOTE: ignoring usage requirements
+ for t in transformed[1]:
+ if t.type() in missing_types:
+ consumed.append(t)
+
+ else:
+ bypassed.append(t)
+
+ consumed = unique(consumed)
+ bypassed = unique(bypassed)
+
+ # remove elements of 'bypassed' that are in 'consumed'
+
+ # Suppose the target type of current generator, X is produced from
+ # X_1 and X_2, which are produced from Y by one generator.
+ # When creating X_1 from Y, X_2 will be added to 'bypassed'
+ # Likewise, when creating X_2 from Y, X_1 will be added to 'bypassed'
+ # But they are also in 'consumed'. We have to remove them from
+ # bypassed, so that generators up the call stack don't try to convert
+ # them.
+
+ # In this particular case, X_1 instance in 'consumed' and X_1 instance
+ # in 'bypassed' will be the same: because they have the same source and
+ # action name, and 'virtual-target.register' won't allow two different
+ # instances. Therefore, it's OK to use 'set.difference'.
+
+ bypassed = set.difference(bypassed, consumed)
+
+ return (consumed, bypassed)
+
+
+ def convert_multiple_sources_to_consumable_types (self, project, prop_set, sources):
+ """ Converts several files to consumable types.
+ """
+ consumed = []
+ bypassed = []
+
+ # We process each source one-by-one, trying to convert it to
+ # a usable type.
+ for s in sources:
+ # TODO: need to check for failure on each source.
+ (c, b) = self.convert_to_consumable_types (project, None, prop_set, [s], True)
+ if not c:
+ project.manager ().logger ().log (__name__, " failed to convert ", s)
+
+ consumed.extend (c)
+ bypassed.extend (b)
+
+ return (consumed, bypassed)
+
+ def consume_directly (self, source):
+ real_source_type = source.type ()
+
+ # If there are no source types, we can consume anything
+ source_types = self.source_types
+ if not source_types:
+ source_types = [real_source_type]
+
+ consumed = []
+ missing_types = []
+ for st in self.source_types_:
+ # The 'source' if of right type already)
+ if real_source_type == st or type.is_derived (real_source_type, st):
+ consumed.append (source)
+
+ else:
+ missing_types.append (st)
+
+ return (consumed, missing_types)
+
+ def action_class (self):
+ """ Returns the class to be used to actions. Default implementation
+ returns "action".
+ """
+ return virtual_target.Action
+
+
+def find (id):
+ """ Finds the generator with id. Returns None if not found.
+ """
+ return __generators.get (id, None)
+
+def register (g):
+ """ Registers new generator instance 'g'.
+ """
+ id = g.id ()
+
+ __generators [id] = g
+
+ # A generator can produce several targets of the
+ # same type. We want unique occurence of that generator
+ # in .generators.$(t) in that case, otherwise, it will
+ # be tried twice and we'll get false ambiguity.
+ for t in sequence.unique(g.target_types()):
+ __type_to_generators.setdefault(t, []).append(g)
+
+ # Update the set of generators for toolset
+
+ # TODO: should we check that generator with this id
+ # is not already registered. For example, the fop.jam
+ # module intentionally declared two generators with the
+ # same id, so such check will break it.
+
+ # Some generators have multiple periods in their name, so the
+ # normal $(id:S=) won't generate the right toolset name.
+ # e.g. if id = gcc.compile.c++, then
+ # .generators-for-toolset.$(id:S=) will append to
+ # .generators-for-toolset.gcc.compile, which is a separate
+ # value from .generators-for-toolset.gcc. Correcting this
+ # makes generator inheritance work properly.
+ # See also inherit-generators in module toolset
+ base = id.split ('.', 100) [0]
+
+ __generators_for_toolset.setdefault(base, []).append(g)
+
+def register_standard (id, source_types, target_types, requirements = []):
+ """ Creates new instance of the 'generator' class and registers it.
+ Returns the creates instance.
+ Rationale: the instance is returned so that it's possible to first register
+ a generator and then call 'run' method on that generator, bypassing all
+ generator selection.
+ """
+ g = Generator (id, False, source_types, target_types, requirements)
+ register (g)
+ return g
+
+def register_composing (id, source_types, target_types, requirements = []):
+ g = Generator (id, True, source_types, target_types, requirements)
+ register (g)
+ return g
+
+def generators_for_toolset (toolset):
+ """ Returns all generators which belong to 'toolset'.
+ """
+ return __generators_for_toolset.get(toolset, [])
+
+def override (overrider_id, overridee_id):
+ """Make generator 'overrider-id' be preferred to
+ 'overridee-id'. If, when searching for generators
+ that could produce a target of certain type,
+ both those generators are amoung viable generators,
+ the overridden generator is immediately discarded.
+
+ The overridden generators are discarded immediately
+ after computing the list of viable generators, before
+ running any of them."""
+
+ __overrides.get(overrider_id, []).append(overridee_id)
+
+def __viable_source_types_real (target_type):
+ """ Returns a list of source type which can possibly be converted
+ to 'target_type' by some chain of generator invocation.
+
+ More formally, takes all generators for 'target_type' and
+ returns union of source types for those generators and result
+ of calling itself recusrively on source types.
+ """
+ generators = []
+
+ t = type.all_bases (target_type)
+
+ result = []
+ # 't' is the list of types which are not yet processed
+ while t:
+ # Find all generators for current type.
+ # Unlike 'find_viable_generators' we don't care about prop_set.
+ generators = __type_to_generators.get (t [0], [])
+ t = t[1:]
+
+ for g in generators:
+ if not g.source_types():
+ # Empty source types -- everything can be accepted
+ result = "*"
+ # This will terminate outer loop.
+ t = None
+ break
+
+ for source_type in g.source_types ():
+ if not source_type in result:
+ # If generator accepts 'source_type' it
+ # will happily accept any type derived from it
+ all = type.all_derived (source_type)
+ for n in all:
+ if not n in result:
+ t.append (n)
+ result.append (n)
+
+ result = unique (result)
+
+ return result
+
+
+def viable_source_types (target_type):
+ """ Helper rule, caches the result of '__viable_source_types_real'.
+ """
+ if not __viable_source_types_cache.has_key (target_type):
+ __viable_source_types_cache [target_type] = __viable_source_types_real (target_type)
+ return __viable_source_types_cache [target_type]
+
+def viable_source_types_for_generator_real (generator):
+ """ Returns the list of source types, which, when passed to 'run'
+ method of 'generator', has some change of being eventually used
+ (probably after conversion by other generators)
+ """
+ source_types = generator.source_types ()
+
+ if not source_types:
+ # If generator does not specify any source types,
+ # it might be special generator like builtin.lib-generator
+ # which just relays to other generators. Return '*' to
+ # indicate that any source type is possibly OK, since we don't
+ # know for sure.
+ return ['*']
+
+ else:
+ result = []
+ for s in source_types:
+ result += type.all_derived (s) + viable_source_types (s)
+ result = unique (result)
+ if "*" in result:
+ result = ["*"]
+ return result
+
+def viable_source_types_for_generator (generator):
+ """ Caches the result of 'viable_source_types_for_generator'.
+ """
+ key = str (generator)
+ if not __viable_source_types_cache.has_key (key):
+ __viable_source_types_cache [key] = viable_source_types_for_generator_real (generator)
+
+ return __viable_source_types_cache [key]
+
+def try_one_generator_really (project, name, generator, target_type, properties, sources):
+ """ Returns usage requirements + list of created targets.
+ """
+ targets = generator.run (project, name, properties, sources)
+
+ usage_requirements = []
+ success = False
+
+ dout("returned " + str(targets))
+
+ if targets:
+ success = True;
+
+ if isinstance (targets[0], property_set.PropertySet):
+ usage_requirements = targets [0]
+ targets = targets [1]
+
+ else:
+ usage_requirements = property_set.empty ()
+
+ dout( " generator" + generator.id() + " spawned ")
+ # generators.dout [ indent ] " " $(targets) ;
+# if $(usage-requirements)
+# {
+# generators.dout [ indent ] " with usage requirements:" $(x) ;
+# }
+
+ if success:
+ return (usage_requirements, targets)
+ else:
+ return None
+
+def try_one_generator (project, name, generator, target_type, properties, sources):
+ """ Checks if generator invocation can be pruned, because it's guaranteed
+ to fail. If so, quickly returns empty list. Otherwise, calls
+ try_one_generator_really.
+ """
+ source_types = []
+
+ for s in sources:
+ source_types.append (s.type ())
+
+ viable_source_types = viable_source_types_for_generator (generator)
+
+ if source_types and viable_source_types != ['*'] and\
+ not set.intersection (source_types, viable_source_types):
+ if project.manager ().logger ().on ():
+ id = generator.id ()
+ project.manager ().logger ().log (__name__, "generator '%s' pruned" % id)
+ project.manager ().logger ().log (__name__, "source_types" '%s' % source_types)
+ project.manager ().logger ().log (__name__, "viable_source_types '%s'" % viable_source_types)
+
+ return []
+
+ else:
+ return try_one_generator_really (project, name, generator, target_type, properties, sources)
+
+
+def construct_types (project, name, target_types, prop_set, sources):
+
+ result = []
+ usage_requirements = property_set.empty()
+
+ for t in target_types:
+ r = construct (project, name, t, prop_set, sources)
+
+ if r:
+ (ur, targets) = r
+ usage_requirements = usage_requirements.add(ur)
+ result.extend(targets)
+
+ # TODO: have to introduce parameter controlling if
+ # several types can be matched and add appropriate
+ # checks
+
+ # TODO: need to review the documentation for
+ # 'construct' to see if it should return $(source) even
+ # if nothing can be done with it. Currents docs seem to
+ # imply that, contrary to the behaviour.
+ if result:
+ return (usage_requirements, result)
+
+ else:
+ return (usage_requirements, sources)
+
+def __ensure_type (targets):
+ """ Ensures all 'targets' have types. If this is not so, exists with
+ error.
+ """
+ for t in targets:
+ if not t.type ():
+ raise BaseException ("target '%s' has no type" % str (t))
+
+def find_viable_generators_aux (target_type, prop_set):
+ """ Returns generators which can be used to construct target of specified type
+ with specified properties. Uses the following algorithm:
+ - iterates over requested target_type and all it's bases (in the order returned bt
+ type.all-bases.
+ - for each type find all generators that generate that type and which requirements
+ are satisfied by properties.
+ - if the set of generators is not empty, returns that set.
+
+ Note: this algorithm explicitly ignores generators for base classes if there's
+ at least one generator for requested target_type.
+ """
+ # Select generators that can create the required target type.
+ viable_generators = []
+ initial_generators = []
+
+ import type
+
+ # Try all-type generators first. Assume they have
+ # quite specific requirements.
+ all_bases = type.all_bases(target_type)
+
+ for t in all_bases:
+
+ initial_generators = __type_to_generators.get(t, [])
+
+ if initial_generators:
+ dout("there are generators for this type")
+ if t != target_type:
+ # We're here, when no generators for target-type are found,
+ # but there are some generators for a base type.
+ # We'll try to use them, but they will produce targets of
+ # base type, not of 'target-type'. So, we clone the generators
+ # and modify the list of target types.
+ generators2 = []
+ for g in initial_generators[:]:
+ # generators.register adds generator to the list of generators
+ # for toolsets, which is a bit strange, but should work.
+ # That list is only used when inheriting toolset, which
+ # should have being done before generators are run.
+ ng = g.clone_and_change_target_type(t, target_type)
+ generators2.append(ng)
+ register(ng)
+
+ initial_generators = generators2
+ break
+
+ for g in initial_generators:
+ dout("trying generator " + g.id()
+ + "(" + str(g.source_types()) + "->" + str(g.target_types()) + ")")
+
+ m = g.match_rank(prop_set)
+ if m:
+ dout(" is viable")
+ viable_generators.append(g)
+
+ return viable_generators
+
+def find_viable_generators (target_type, prop_set):
+ key = target_type + '.' + str (prop_set)
+
+ l = __viable_generators_cache.get (key, None)
+
+ if not l:
+ l = find_viable_generators_aux (target_type, prop_set)
+
+ __viable_generators_cache [key] = l
+
+ viable_generators = []
+ for g in l:
+ # Avoid trying the same generator twice on different levels.
+ # TODO: is this really used?
+ if not g in __active_generators:
+ viable_generators.append (g)
+
+ # Generators which override 'all'.
+ all_overrides = []
+
+ # Generators which are overriden
+ overriden_ids = []
+
+ for g in viable_generators:
+ id = g.id ()
+
+ this_overrides = __overrides.get (id, [])
+
+ if this_overrides:
+ overriden_ids.extend (this_overrides)
+ if 'all' in this_overrides:
+ all_overrides.append (g)
+
+ if all_overrides:
+ viable_generators = all_overrides
+
+ result = []
+ for g in viable_generators:
+ if not g.id () in overriden_ids:
+ result.append (g)
+
+ return result
+
+def __construct_really (project, name, target_type, prop_set, sources):
+ """ Attempts to construct target by finding viable generators, running them
+ and selecting the dependency graph.
+ """
+ viable_generators = find_viable_generators (target_type, prop_set)
+
+ result = []
+
+ project.manager ().logger ().log (__name__, "*** %d viable generators" % len (viable_generators))
+
+ generators_that_succeeded = []
+
+ for g in viable_generators:
+ __active_generators.append(g)
+ r = try_one_generator (project, name, g, target_type, prop_set, sources)
+ del __active_generators[-1]
+
+ if r:
+ generators_that_succeeded.append(g)
+ if result:
+ output = cStringIO.StringIO()
+ print >>output, "ambiguity found when searching for best transformation"
+ print >>output, "Trying to produce type '%s' from: " % (target_type)
+ for s in sources:
+ print >>output, " - " + s.str()
+ print >>output, "Generators that succeeded:"
+ for g in generators_that_succeeded:
+ print >>output, " - " + g.id()
+ print >>output, "First generator produced: "
+ for t in result[1:]:
+ print >>output, " - " + str(t)
+ print >>output, "Second generator produced:"
+ for t in r[1:]:
+ print >>output, " - " + str(t)
+ get_manager().errors()(output.getvalue())
+ else:
+ result = r;
+
+ return result;
+
+
+def construct (project, name, target_type, prop_set, sources):
+ """ Attempts to create target of 'target-type' with 'properties'
+ from 'sources'. The 'sources' are treated as a collection of
+ *possible* ingridients -- i.e. it is not required to consume
+ them all. If 'multiple' is true, the rule is allowed to return
+ several targets of 'target-type'.
+
+ Returns a list of target. When this invocation is first instance of
+ 'construct' in stack, returns only targets of requested 'target-type',
+ otherwise, returns also unused sources and additionally generated
+ targets.
+ """
+ # TODO: Why is global needed here?
+ global __construct_stack
+ if __construct_stack:
+ __ensure_type (sources)
+
+ __construct_stack.append (1)
+
+ if project.manager().logger().on():
+ increase_indent ()
+
+ dout( "*** construct " + target_type)
+
+ for s in sources:
+ dout(" from " + str(s))
+
+ project.manager().logger().log (__name__, " properties: ", prop_set.raw ())
+
+ result = __construct_really(project, name, target_type, prop_set, sources)
+
+ project.manager().logger().decrease_indent()
+
+ __construct_stack = __construct_stack [1:]
+
+ return result
+

Added: trunk/tools/build/v2/build/project.ann.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/build/project.ann.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,996 @@
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 1) # Status: being ported by Vladimir Prus
+ddc17f01 (vladimir_prus 2007-10-26 14:57:56 +0000 2) # Base revision: 40480
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 3)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 4) # Copyright 2002, 2003 Dave Abrahams
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 5) # Copyright 2002, 2005, 2006 Rene Rivera
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 6) # Copyright 2002, 2003, 2004, 2005, 2006 Vladimir Prus
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 7) # Distributed under the Boost Software License, Version 1.0.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 8) # (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 9)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 10) # Implements project representation and loading.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 11) # Each project is represented by
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 12) # - a module where all the Jamfile content live.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 13) # - an instance of 'project-attributes' class.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 14) # (given module name, can be obtained by 'attributes' rule)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 15) # - an instance of 'project-target' class (from targets.jam)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 16) # (given a module name, can be obtained by 'target' rule)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 17) #
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 18) # Typically, projects are created as result of loading Jamfile, which is
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 19) # do by rules 'load' and 'initialize', below. First, module for Jamfile
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 20) # is loaded and new project-attributes instance is created. Some rules
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 21) # necessary for project are added to the module (see 'project-rules' module)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 22) # at the bottom of this file.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 23) # Default project attributes are set (inheriting attributes of parent project, if
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 24) # it exists). After that, Jamfile is read. It can declare its own attributes,
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 25) # via 'project' rule, which will be combined with already set attributes.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 26) #
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 27) #
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 28) # The 'project' rule can also declare project id, which will be associated with
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 29) # the project module.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 30) #
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 31) # There can also be 'standalone' projects. They are created by calling 'initialize'
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 32) # on arbitrary module, and not specifying location. After the call, the module can
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 33) # call 'project' rule, declare main target and behave as regular projects. However,
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 34) # since it's not associated with any location, it's better declare only prebuilt
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 35) # targets.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 36) #
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 37) # The list of all loaded Jamfile is stored in variable .project-locations. It's possible
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 38) # to obtain module name for a location using 'module-name' rule. The standalone projects
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 39) # are not recorded, the only way to use them is by project id.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 40)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 41) import b2.util.path
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 42) from b2.build import property_set, property
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 43) from b2.build.errors import ExceptionWithUserContext
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 44) import b2.build.targets
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 45)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 46) import bjam
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 47)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 48) import re
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 49) import sys
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 50) import os
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 51) import string
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 52) import imp
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 53) import traceback
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 54)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 55) class ProjectRegistry:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 56)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 57) def __init__(self, manager, global_build_dir):
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 58) self.manager = manager
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 59) self.global_build_dir = None
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 60) self.project_rules_ = ProjectRules(self)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 61)
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 62) # The target corresponding to the project being loaded now
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 63) self.current_project = None
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 64)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 65) # The set of names of loaded project modules
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 66) self.jamfile_modules = {}
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 67)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 68) # Mapping from location to module name
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 69) self.location2module = {}
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 70)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 71) # Mapping from project id to project module
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 72) self.id2module = {}
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 73)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 74) # Map from Jamfile directory to parent Jamfile/Jamroot
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 75) # location.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 76) self.dir2parent_jamfile = {}
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 77)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 78) # Map from directory to the name of Jamfile in
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 79) # that directory (or None).
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 80) self.dir2jamfile = {}
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 81)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 82) # Map from project module to attributes object.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 83) self.module2attributes = {}
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 84)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 85) # Map from project module to target for the project
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 86) self.module2target = {}
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 87)
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 88) # Map from names to Python modules, for modules loaded
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 89) # via 'using' and 'import' rules in Jamfiles.
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 90) self.loaded_tool_modules_ = {}
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 91)
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 92) # Map from project target to the list of
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 93) # (id,location) pairs corresponding to all 'use-project'
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 94) # invocations.
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 95) # TODO: should not have a global map, keep this
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 96) # in ProjectTarget.
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 97) self.used_projects = {}
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 98)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 99) self.saved_current_project = []
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 100)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 101) self.JAMROOT = self.manager.getenv("JAMROOT");
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 102)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 103) # Note the use of character groups, as opposed to listing
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 104) # 'Jamroot' and 'jamroot'. With the latter, we'd get duplicate
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 105) # matches on windows and would have to eliminate duplicates.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 106) if not self.JAMROOT:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 107) self.JAMROOT = ["project-root.jam", "[Jj]amroot", "[Jj]amroot.jam"]
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 108)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 109) # Default patterns to search for the Jamfiles to use for build
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 110) # declarations.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 111) self.JAMFILE = self.manager.getenv("JAMFILE")
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 112)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 113) if not self.JAMFILE:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 114) self.JAMFILE = ["[Bb]uild.jam", "[Jj]amfile.v2", "[Jj]amfile",
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 115) "[Jj]amfile.jam"]
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 116)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 117)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 118) def load (self, jamfile_location):
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 119) """Loads jamfile at the given location. After loading, project global
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 120) file and jamfile needed by the loaded one will be loaded recursively.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 121) If the jamfile at that location is loaded already, does nothing.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 122) Returns the project module for the Jamfile."""
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 123)
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 124) absolute = os.path.join(os.getcwd(), jamfile_location)
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 125) absolute = os.path.normpath(absolute)
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 126) jamfile_location = b2.util.path.relpath(os.getcwd(), absolute)
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 127)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 128) if "--debug-loading" in self.manager.argv():
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 129) print "Loading Jamfile at '%s'" % jamfile_location
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 130)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 131)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 132) mname = self.module_name(jamfile_location)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 133) # If Jamfile is already loaded, don't try again.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 134) if not mname in self.jamfile_modules:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 135)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 136) self.load_jamfile(jamfile_location)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 137)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 138) # We want to make sure that child project are loaded only
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 139) # after parent projects. In particular, because parent projects
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 140) # define attributes whch are inherited by children, and we don't
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 141) # want children to be loaded before parents has defined everything.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 142) #
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 143) # While "build-project" and "use-project" can potentially refer
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 144) # to child projects from parent projects, we don't immediately
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 145) # load child projects when seing those attributes. Instead,
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 146) # we record the minimal information that will be used only later.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 147)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 148) self.load_used_projects(mname)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 149)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 150) return mname
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 151)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 152) def load_used_projects(self, module_name):
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 153) # local used = [ modules.peek $(module-name) : .used-projects ] ;
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 154) used = self.used_projects[module_name]
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 155)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 156) location = self.attribute(module_name, "location")
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 157) for u in used:
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 158) id = u[0]
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 159) where = u[1]
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 160)
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 161) self.use(id, os.path.join(location, where))
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 162)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 163) def load_parent(self, location):
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 164) """Loads parent of Jamfile at 'location'.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 165) Issues an error if nothing is found."""
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 166)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 167) found = b2.util.path.glob_in_parents(
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 168) location, self.JAMROOT + self.JAMFILE)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 169)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 170) if not found:
+1674e2d9 (jhunold 2008-08-08 19:52:05 +0000 171) print "error: Could not find parent for project at '%s'" % location
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 172) print "error: Did not find Jamfile or project-root.jam in any parent directory."
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 173) sys.exit(1)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 174)
+49c03622 (jhunold 2008-07-23 09:57:41 +0000 175) return self.load(os.path.dirname(found[0]))
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 176)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 177) def act_as_jamfile(self, module, location):
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 178) """Makes the specified 'module' act as if it were a regularly loaded Jamfile
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 179) at 'location'. If Jamfile is already located for that location, it's an
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 180) error."""
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 181)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 182) if self.module_name(location) in self.jamfile_modules:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 183) self.manager.errors()(
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 184) "Jamfile was already loaded for '%s'" % location)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 185)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 186) # Set up non-default mapping from location to module.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 187) self.location2module[location] = module
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 188)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 189) # Add the location to the list of project locations
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 190) # so that we don't try to load Jamfile in future
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 191) self.jamfile_modules.append(location)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 192)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 193) self.initialize(module, location)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 194)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 195) def find(self, name, current_location):
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 196) """Given 'name' which can be project-id or plain directory name,
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 197) return project module corresponding to that id or directory.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 198) Returns nothing of project is not found."""
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 199)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 200) project_module = None
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 201)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 202) # Try interpreting name as project id.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 203) if name[0] == '/':
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 204) project_module = self.id2module.get(name)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 205)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 206) if not project_module:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 207) location = os.path.join(current_location, name)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 208) # If no project is registered for the given location, try to
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 209) # load it. First see if we have Jamfile. If not we might have project
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 210) # root, willing to act as Jamfile. In that case, project-root
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 211) # must be placed in the directory referred by id.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 212)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 213) project_module = self.module_name(location)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 214) if not project_module in self.jamfile_modules and \
+49c03622 (jhunold 2008-07-23 09:57:41 +0000 215) b2.util.path.glob([location], self.JAMROOT + self.JAMFILE):
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 216) project_module = self.load(location)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 217)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 218) return project_module
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 219)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 220) def module_name(self, jamfile_location):
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 221) """Returns the name of module corresponding to 'jamfile-location'.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 222) If no module corresponds to location yet, associates default
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 223) module name with that location."""
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 224) module = self.location2module.get(jamfile_location)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 225) if not module:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 226) # Root the path, so that locations are always umbiguious.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 227) # Without this, we can't decide if '../../exe/program1' and '.'
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 228) # are the same paths, or not.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 229) jamfile_location = os.path.realpath(
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 230) os.path.join(os.getcwd(), jamfile_location))
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 231) module = "Jamfile<%s>" % jamfile_location
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 232) self.location2module[jamfile_location] = module
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 233) return module
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 234)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 235) def find_jamfile (self, dir, parent_root=0, no_errors=0):
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 236) """Find the Jamfile at the given location. This returns the
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 237) exact names of all the Jamfiles in the given directory. The optional
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 238) parent-root argument causes this to search not the given directory
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 239) but the ones above it up to the directory given in it."""
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 240)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 241) # Glob for all the possible Jamfiles according to the match pattern.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 242) #
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 243) jamfile_glob = None
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 244) if parent_root:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 245) parent = self.dir2parent_jamfile.get(dir)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 246) if not parent:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 247) parent = b2.util.path.glob_in_parents(dir,
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 248) self.JAMFILE)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 249) self.dir2parent_jamfile[dir] = parent
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 250) jamfile_glob = parent
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 251) else:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 252) jamfile = self.dir2jamfile.get(dir)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 253) if not jamfile:
+49c03622 (jhunold 2008-07-23 09:57:41 +0000 254) jamfile = b2.util.path.glob([dir], self.JAMFILE)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 255) self.dir2jamfile[dir] = jamfile
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 256) jamfile_glob = jamfile
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 257)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 258) if len(jamfile_glob):
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 259) # Multiple Jamfiles found in the same place. Warn about this.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 260) # And ensure we use only one of them.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 261) # As a temporary convenience measure, if there's Jamfile.v2 amount
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 262) # found files, suppress the warning and use it.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 263) #
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 264) pattern = "(.*[Jj]amfile\\.v2)|(.*[Bb]uild\\.jam)"
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 265) v2_jamfiles = [x for x in jamfile_glob if re.match(pattern, x)]
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 266) if len(v2_jamfiles) == 1:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 267) jamfile_glob = v2_jamfiles
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 268) else:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 269) print """warning: Found multiple Jamfiles at '%s'!
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 270) Loading the first one: '%s'.""" % (dir, jamfile_glob[0])
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 271)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 272) # Could not find it, error.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 273) if not no_errors and not jamfile_glob:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 274) self.manager.errors()(
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 275) """Unable to load Jamfile.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 276) Could not find a Jamfile in directory '%s'
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 277) Attempted to find it with pattern '%s'.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 278) Please consult the documentation at 'http://boost.org/b2.'."""
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 279) % (dir, string.join(self.JAMFILE)))
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 280)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 281) return jamfile_glob[0]
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 282)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 283) def load_jamfile(self, dir):
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 284) """Load a Jamfile at the given directory. Returns nothing.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 285) Will attempt to load the file as indicated by the JAMFILE patterns.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 286) Effect of calling this rule twice with the same 'dir' is underfined."""
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 287)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 288) # See if the Jamfile is where it should be.
+49c03622 (jhunold 2008-07-23 09:57:41 +0000 289) jamfile_to_load = b2.util.path.glob([dir], self.JAMROOT)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 290) if not jamfile_to_load:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 291) jamfile_to_load = self.find_jamfile(dir)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 292) else:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 293) jamfile_to_load = jamfile_to_load[0]
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 294)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 295) # The module of the jamfile.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 296) dir = os.path.realpath(os.path.dirname(jamfile_to_load))
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 297)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 298) jamfile_module = self.module_name (dir)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 299)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 300) # Initialize the jamfile module before loading.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 301) #
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 302) self.initialize(jamfile_module, dir, os.path.basename(jamfile_to_load))
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 303)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 304) saved_project = self.current_project
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 305)
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 306) self.used_projects[jamfile_module] = []
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 307)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 308) # Now load the Jamfile in it's own context.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 309) # Initialization might have load parent Jamfiles, which might have
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 310) # loaded the current Jamfile with use-project. Do a final check to make
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 311) # sure it's not loaded already.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 312) if not jamfile_module in self.jamfile_modules:
+49c03622 (jhunold 2008-07-23 09:57:41 +0000 313) self.jamfile_modules[jamfile_module] = True
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 314)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 315) # FIXME:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 316) # mark-as-user $(jamfile-module) ;
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 317)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 318) bjam.call("load", jamfile_module, jamfile_to_load)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 319) basename = os.path.basename(jamfile_to_load)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 320)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 321) # Now do some checks
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 322) if self.current_project != saved_project:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 323) self.manager.errors()(
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 324) """The value of the .current-project variable
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 325) has magically changed after loading a Jamfile.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 326) This means some of the targets might be defined a the wrong project.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 327) after loading %s
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 328) expected value %s
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 329) actual value %s""" % (jamfile_module, saved_project, self.current_project))
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 330)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 331) if self.global_build_dir:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 332) id = self.attribute(jamfile_module, "id")
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 333) project_root = self.attribute(jamfile_module, "project-root")
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 334) location = self.attribute(jamfile_module, "location")
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 335)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 336) if location and project_root == dir:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 337) # This is Jamroot
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 338) if not id:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 339) # FIXME: go via errors module, so that contexts are
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 340) # shown?
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 341) print "warning: the --build-dir option was specified"
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 342) print "warning: but Jamroot at '%s'" % dir
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 343) print "warning: specified no project id"
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 344) print "warning: the --build-dir option will be ignored"
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 345)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 346)
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 347) def load_standalone(self, jamfile_module, file):
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 348) """Loads 'file' as standalone project that has no location
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 349) associated with it. This is mostly useful for user-config.jam,
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 350) which should be able to define targets, but although it has
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 351) some location in filesystem, we don't want any build to
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 352) happen in user's HOME, for example.
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 353)
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 354) The caller is required to never call this method twice on
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 355) the same file.
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 356) """
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 357)
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 358) self.initialize(jamfile_module)
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 359) self.used_projects[jamfile_module] = []
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 360) bjam.call("load", jamfile_module, file)
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 361) self.load_used_projects(jamfile_module)
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 362)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 363) def is_jamroot(self, basename):
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 364) match = [ pat for pat in self.JAMROOT if re.match(pat, basename)]
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 365) if match:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 366) return 1
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 367) else:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 368) return 0
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 369)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 370) def initialize(self, module_name, location=None, basename=None):
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 371) """Initialize the module for a project.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 372)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 373) module-name is the name of the project module.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 374) location is the location (directory) of the project to initialize.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 375) If not specified, stanalone project will be initialized
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 376) """
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 377)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 378) if "--debug-loading" in self.manager.argv():
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 379) print "Initializing project '%s'" % module_name
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 380)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 381) # TODO: need to consider if standalone projects can do anything but defining
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 382) # prebuilt targets. If so, we need to give more sensible "location", so that
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 383) # source paths are correct.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 384) if not location:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 385) location = ""
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 386) else:
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 387) location = b2.util.path.relpath(os.getcwd(), location)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 388)
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 389) attributes = ProjectAttributes(self.manager, location, module_name)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 390) self.module2attributes[module_name] = attributes
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 391)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 392) if location:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 393) attributes.set("source-location", location, exact=1)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 394) else:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 395) attributes.set("source-location", "", exact=1)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 396)
+49c03622 (jhunold 2008-07-23 09:57:41 +0000 397) attributes.set("requirements", property_set.empty(), exact=True)
+49c03622 (jhunold 2008-07-23 09:57:41 +0000 398) attributes.set("usage-requirements", property_set.empty(), exact=True)
+49c03622 (jhunold 2008-07-23 09:57:41 +0000 399) attributes.set("default-build", [], exact=True)
+49c03622 (jhunold 2008-07-23 09:57:41 +0000 400) attributes.set("projects-to-build", [], exact=True)
+49c03622 (jhunold 2008-07-23 09:57:41 +0000 401) attributes.set("project-root", None, exact=True)
+49c03622 (jhunold 2008-07-23 09:57:41 +0000 402) attributes.set("build-dir", None, exact=True)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 403)
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 404) self.project_rules_.init_project(module_name)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 405)
+49c03622 (jhunold 2008-07-23 09:57:41 +0000 406) jamroot = False
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 407)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 408) parent_module = None;
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 409) if module_name == "site-config":
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 410) # No parent
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 411) pass
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 412) elif module_name == "user-config":
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 413) parent_module = "site-config"
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 414) elif location and not self.is_jamroot(basename):
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 415) # We search for parent/project-root only if jamfile was specified
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 416) # --- i.e
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 417) # if the project is not standalone.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 418) parent_module = self.load_parent(location)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 419) else:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 420) # It's either jamroot, or standalone project.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 421) # If it's jamroot, inherit from user-config.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 422) if location:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 423) parent_module = "user-config" ;
+49c03622 (jhunold 2008-07-23 09:57:41 +0000 424) jamroot = True ;
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 425)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 426) if parent_module:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 427) self.inherit_attributes(module_name, parent_module)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 428) attributes.set("parent-module", parent_module, exact=1)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 429)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 430) if jamroot:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 431) attributes.set("project-root", location, exact=1)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 432)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 433) parent = None
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 434) if parent_module:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 435) parent = self.target(parent_module)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 436)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 437) if not self.module2target.has_key(module_name):
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 438) target = b2.build.targets.ProjectTarget(self.manager,
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 439) module_name, module_name, parent,
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 440) self.attribute(module_name,"requirements"),
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 441) # FIXME: why we need to pass this? It's not
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 442) # passed in jam code.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 443) self.attribute(module_name, "default-build"))
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 444) self.module2target[module_name] = target
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 445)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 446) self.current_project = self.target(module_name)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 447)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 448) def inherit_attributes(self, project_module, parent_module):
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 449) """Make 'project-module' inherit attributes of project
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 450) root and parent module."""
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 451)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 452) attributes = self.module2attributes[project_module]
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 453) pattributes = self.module2attributes[parent_module]
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 454)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 455) # Parent module might be locationless user-config.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 456) # FIXME:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 457) #if [ modules.binding $(parent-module) ]
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 458) #{
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 459) # $(attributes).set parent : [ path.parent
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 460) # [ path.make [ modules.binding $(parent-module) ] ] ] ;
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 461) # }
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 462)
+49c03622 (jhunold 2008-07-23 09:57:41 +0000 463) attributes.set("project-root", pattributes.get("project-root"), exact=True)
+49c03622 (jhunold 2008-07-23 09:57:41 +0000 464) attributes.set("default-build", pattributes.get("default-build"), exact=True)
+49c03622 (jhunold 2008-07-23 09:57:41 +0000 465) attributes.set("requirements", pattributes.get("requirements"), exact=True)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 466) attributes.set("usage-requirements",
+cde6f09a (vladimir_prus 2007-10-19 23:12:33 +0000 467) pattributes.get("usage-requirements"), exact=1)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 468)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 469) parent_build_dir = pattributes.get("build-dir")
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 470)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 471) if parent_build_dir:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 472) # Have to compute relative path from parent dir to our dir
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 473) # Convert both paths to absolute, since we cannot
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 474) # find relative path from ".." to "."
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 475)
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 476) location = attributes.get("location")
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 477) parent_location = pattributes.get("location")
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 478)
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 479) our_dir = os.path.join(os.getcwd(), location)
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 480) parent_dir = os.path.join(os.getcwd(), parent_location)
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 481)
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 482) build_dir = os.path.join(parent_build_dir,
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 483) b2.util.path.relpath(parent_dir,
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 484) our_dir))
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 485)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 486) def register_id(self, id, module):
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 487) """Associate the given id with the given project module."""
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 488) self.id2module[id] = module
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 489)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 490) def current(self):
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 491) """Returns the project which is currently being loaded."""
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 492) return self.current_project
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 493)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 494) def push_current(self, project):
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 495) """Temporary changes the current project to 'project'. Should
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 496) be followed by 'pop-current'."""
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 497) self.saved_current_project.append(self.current_project)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 498) self.current_project = project
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 499)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 500) def pop_current(self):
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 501) self.current_project = self.saved_current_project[-1]
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 502) del self.saved_current_project[-1]
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 503)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 504) def attributes(self, project):
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 505) """Returns the project-attribute instance for the
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 506) specified jamfile module."""
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 507) return self.module2attributes[project]
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 508)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 509) def attribute(self, project, attribute):
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 510) """Returns the value of the specified attribute in the
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 511) specified jamfile module."""
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 512) return self.module2attributes[project].get(attribute)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 513)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 514) def target(self, project_module):
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 515) """Returns the project target corresponding to the 'project-module'."""
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 516) if not self.module2target[project_module]:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 517) self.module2target[project_module] = \
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 518) ProjectTarget(project_module, project_module,
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 519) self.attribute(project_module, "requirements"))
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 520)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 521) return self.module2target[project_module]
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 522)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 523) def use(self, id, location):
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 524) # Use/load a project.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 525) saved_project = self.current_project
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 526) project_module = self.load(location)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 527) declared_id = self.attribute(project_module, "id")
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 528)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 529) if not declared_id or declared_id != id:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 530) # The project at 'location' either have no id or
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 531) # that id is not equal to the 'id' parameter.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 532) if self.id2module[id] and self.id2module[id] != project_module:
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 533) self.manager.errors()(
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 534) """Attempt to redeclare already existing project id '%s'""" % id)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 535) self.id2module[id] = project_module
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 536)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 537) self.current_module = saved_project
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 538)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 539) def add_rule(self, name, callable):
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 540) """Makes rule 'name' available to all subsequently loaded Jamfiles.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 541)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 542) Calling that rule wil relay to 'callable'."""
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 543) self.project_rules_.add_rule(name, callable)
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 544)
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 545) def project_rules(self):
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 546) return self.project_rules_
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 547)
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 548) def glob_internal(self, project, wildcards, excludes, rule_name):
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 549) location = project.get("source-location")
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 550)
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 551) result = []
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 552) callable = b2.util.path.__dict__[rule_name]
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 553)
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 554) paths = callable(location, wildcards, excludes)
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 555) has_dir = 0
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 556) for w in wildcards:
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 557) if os.path.dirname(w):
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 558) has_dir = 1
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 559) break
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 560)
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 561) if has_dir or rule_name != "glob":
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 562) # The paths we've found are relative to current directory,
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 563) # but the names specified in sources list are assumed to
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 564) # be relative to source directory of the corresponding
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 565) # prject. So, just make the name absolute.
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 566) result = [os.path.join(os.getcwd(), p) for p in paths]
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 567) else:
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 568) # There were not directory in wildcard, so the files are all
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 569) # in the source directory of the project. Just drop the
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 570) # directory, instead of making paths absolute.
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 571) result = [os.path.basename(p) for p in paths]
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 572)
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 573) return result
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 574)
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 575) def load_module(self, name, extra_path=None):
+53b0faa2 (jhunold 2008-08-10 18:25:50 +0000 576) """Classic Boost.Build 'modules' are in fact global variables.
+53b0faa2 (jhunold 2008-08-10 18:25:50 +0000 577) Therefore, try to find an already loaded Python module called 'name' in sys.modules.
+53b0faa2 (jhunold 2008-08-10 18:25:50 +0000 578) If the module ist not loaded, find it Boost.Build search
+53b0faa2 (jhunold 2008-08-10 18:25:50 +0000 579) path and load it. The new module is not entered in sys.modules.
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 580) The motivation here is to have disjoint namespace of modules
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 581) loaded via 'import/using' in Jamfile, and ordinary Python
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 582) modules. We don't want 'using foo' in Jamfile to load ordinary
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 583) Python module 'foo' which is going to not work. And we
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 584) also don't want 'import foo' in regular Python module to
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 585) accidentally grab module named foo that is internal to
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 586) Boost.Build and intended to provide interface to Jamfiles."""
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 587)
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 588) existing = self.loaded_tool_modules_.get(name)
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 589) if existing:
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 590) return existing
+53b0faa2 (jhunold 2008-08-10 18:25:50 +0000 591)
+53b0faa2 (jhunold 2008-08-10 18:25:50 +0000 592) modules = sys.modules
+53b0faa2 (jhunold 2008-08-10 18:25:50 +0000 593) for class_name in modules:
+53b0faa2 (jhunold 2008-08-10 18:25:50 +0000 594) if name in class_name:
+53b0faa2 (jhunold 2008-08-10 18:25:50 +0000 595) module = modules[class_name]
+53b0faa2 (jhunold 2008-08-10 18:25:50 +0000 596) self.loaded_tool_modules_[name] = module
+53b0faa2 (jhunold 2008-08-10 18:25:50 +0000 597) return module
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 598)
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 599) path = extra_path
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 600) if not path:
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 601) path = []
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 602) path.extend(self.manager.b2.path())
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 603) location = None
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 604) for p in path:
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 605) l = os.path.join(p, name + ".py")
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 606) if os.path.exists(l):
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 607) location = l
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 608) break
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 609)
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 610) if not location:
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 611) self.manager.errors()("Cannot find module '%s'" % name)
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 612)
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 613) mname = "__build_build_temporary__"
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 614) file = open(location)
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 615) try:
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 616) # TODO: this means we'll never make use of .pyc module,
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 617) # which might be a problem, or not.
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 618) module = imp.load_module(mname, file, os.path.basename(location),
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 619) (".py", "r", imp.PY_SOURCE))
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 620) del sys.modules[mname]
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 621) self.loaded_tool_modules_[name] = module
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 622) return module
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 623) finally:
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 624) file.close()
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 625)
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 626)
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 627)
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 628) # FIXME:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 629) # Defines a Boost.Build extension project. Such extensions usually
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 630) # contain library targets and features that can be used by many people.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 631) # Even though extensions are really projects, they can be initialize as
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 632) # a module would be with the "using" (project.project-rules.using)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 633) # mechanism.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 634) #rule extension ( id : options * : * )
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 635) #{
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 636) # # The caller is a standalone module for the extension.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 637) # local mod = [ CALLER_MODULE ] ;
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 638) #
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 639) # # We need to do the rest within the extension module.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 640) # module $(mod)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 641) # {
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 642) # import path ;
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 643) #
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 644) # # Find the root project.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 645) # local root-project = [ project.current ] ;
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 646) # root-project = [ $(root-project).project-module ] ;
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 647) # while
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 648) # [ project.attribute $(root-project) parent-module ] &&
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 649) # [ project.attribute $(root-project) parent-module ] != user-config
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 650) # {
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 651) # root-project = [ project.attribute $(root-project) parent-module ] ;
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 652) # }
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 653) #
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 654) # # Create the project data, and bring in the project rules
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 655) # # into the module.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 656) # project.initialize $(__name__) :
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 657) # [ path.join [ project.attribute $(root-project) location ] ext $(1:L) ] ;
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 658) #
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 659) # # Create the project itself, i.e. the attributes.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 660) # # All extensions are created in the "/ext" project space.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 661) # project /ext/$(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ;
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 662) # local attributes = [ project.attributes $(__name__) ] ;
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 663) #
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 664) # # Inherit from the root project of whomever is defining us.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 665) # project.inherit-attributes $(__name__) : $(root-project) ;
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 666) # $(attributes).set parent-module : $(root-project) : exact ;
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 667) # }
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 668) #}
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 669)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 670)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 671) class ProjectAttributes:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 672) """Class keeping all the attributes of a project.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 673)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 674) The standard attributes are 'id', "location", "project-root", "parent"
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 675) "requirements", "default-build", "source-location" and "projects-to-build".
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 676) """
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 677)
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 678) def __init__(self, manager, location, project_module):
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 679) self.manager = manager
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 680) self.location = location
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 681) self.project_module = project_module
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 682) self.attributes = {}
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 683) self.usage_requirements = None
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 684)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 685) def set(self, attribute, specification, exact):
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 686) """Set the named attribute from the specification given by the user.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 687) The value actually set may be different."""
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 688)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 689) if exact:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 690) self.__dict__[attribute] = specification
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 691)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 692) elif attribute == "requirements":
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 693) self.requirements = property_set.refine_from_user_input(
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 694) self.requirements, specification,
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 695) self.project_module, self.location)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 696)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 697) elif attribute == "usage-requirements":
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 698) unconditional = []
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 699) for p in specification:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 700) split = property.split_conditional(p)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 701) if split:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 702) unconditional.append(split[1])
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 703) else:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 704) unconditional.append(p)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 705)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 706) non_free = property.remove("free", unconditional)
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 707) if non_free:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 708) pass
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 709) # FIXME:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 710) #errors.error "usage-requirements" $(specification) "have non-free properties" $(non-free) ;
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 711)
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 712) t = property.translate_paths(specification, self.location)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 713)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 714) existing = self.__dict__.get("usage-requirements")
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 715) if existing:
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 716) new = property_set.create(existing.raw() + t)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 717) else:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 718) new = property_set.create(t)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 719) self.__dict__["usage-requirements"] = new
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 720)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 721)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 722) elif attribute == "default-build":
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 723) self.__dict__["default-build"] = property_set.create(specification)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 724)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 725) elif attribute == "source-location":
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 726) source_location = []
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 727) for path in specification:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 728) source_location += os.path.join(self.location, path)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 729) self.__dict__["source-location"] = source_location
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 730)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 731) elif attribute == "build-dir":
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 732) self.__dict__["build-dir"] = os.path.join(self.location, specification)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 733)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 734) elif not attribute in ["id", "default-build", "location",
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 735) "source-location", "parent",
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 736) "projects-to-build", "project-root"]:
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 737) self.manager.errors()(
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 738) """Invalid project attribute '%s' specified
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 739) for project at '%s'""" % (attribute, self.location))
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 740) else:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 741) self.__dict__[attribute] = specification
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 742)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 743) def get(self, attribute):
+cde6f09a (vladimir_prus 2007-10-19 23:12:33 +0000 744) return self.__dict__[attribute]
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 745)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 746) def dump(self):
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 747) """Prints the project attributes."""
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 748) id = self.get("id")
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 749) if not id:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 750) id = "(none)"
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 751) else:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 752) id = id[0]
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 753)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 754) parent = self.get("parent")
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 755) if not parent:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 756) parent = "(none)"
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 757) else:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 758) parent = parent[0]
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 759)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 760) print "'%s'" % id
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 761) print "Parent project:%s", parent
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 762) print "Requirements:%s", self.get("requirements")
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 763) print "Default build:%s", string.join(self.get("debuild-build"))
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 764) print "Source location:%s", string.join(self.get("source-location"))
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 765) print "Projects to build:%s", string.join(self.get("projects-to-build").sort());
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 766)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 767) class ProjectRules:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 768) """Class keeping all rules that are made available to Jamfile."""
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 769)
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 770) def __init__(self, registry):
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 771) self.registry = registry
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 772) self.manager_ = registry.manager
+38d984eb (vladimir_prus 2007-10-13 17:52:25 +0000 773) self.rules = {}
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 774) self.local_names = [x for x in self.__class__.__dict__
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 775) if x not in ["__init__", "init_project", "add_rule",
+7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 776) "error_reporting_wrapper", "add_rule_for_type"]]
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 777) self.all_names_ = [x for x in self.local_names]
+7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 778)
+7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 779) def add_rule_for_type(self, type):
+7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 780) rule_name = type.lower();
+7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 781)
+7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 782) def xpto (name, sources, requirements = [], default_build = None, usage_requirements = []):
+7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 783) return self.manager_.targets().create_typed_target(
+7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 784) type, self.registry.current(), name[0], sources,
+7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 785) requirements, default_build, usage_requirements)
+7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 786)
+7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 787) self.add_rule(type.lower(), xpto)
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 788)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 789) def add_rule(self, name, callable):
+38d984eb (vladimir_prus 2007-10-13 17:52:25 +0000 790) self.rules[name] = callable
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 791) self.all_names_.append(name)
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 792)
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 793) def all_names(self):
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 794) return self.all_names_
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 795)
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 796) def call_and_report_errors(self, callable, *args):
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 797) result = None
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 798) try:
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 799) self.manager_.errors().push_jamfile_context()
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 800) result = callable(*args)
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 801) except ExceptionWithUserContext, e:
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 802) e.report()
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 803) except Exception, e:
+7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 804) try:
+7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 805) self.manager_.errors().handle_stray_exception (e)
+7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 806) except ExceptionWithUserContext, e:
+7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 807) e.report()
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 808) finally:
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 809) self.manager_.errors().pop_jamfile_context()
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 810)
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 811) return result
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 812)
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 813) def make_wrapper(self, callable):
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 814) """Given a free-standing function 'callable', return a new
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 815) callable that will call 'callable' and report all exceptins,
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 816) using 'call_and_report_errors'."""
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 817) def wrapper(*args):
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 818) self.call_and_report_errors(callable, *args)
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 819) return wrapper
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 820)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 821) def init_project(self, project_module):
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 822)
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 823) for n in self.local_names:
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 824) # Using 'getattr' here gives us a bound method,
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 825) # while using self.__dict__[r] would give unbound one.
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 826) v = getattr(self, n)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 827) if callable(v):
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 828) if n == "import_":
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 829) n = "import"
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 830) else:
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 831) n = string.replace(n, "_", "-")
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 832)
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 833) bjam.import_rule(project_module, n,
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 834) self.make_wrapper(v))
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 835)
+38d984eb (vladimir_prus 2007-10-13 17:52:25 +0000 836) for n in self.rules:
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 837) bjam.import_rule(project_module, n,
+0317671e (vladimir_prus 2007-10-28 14:02:06 +0000 838) self.make_wrapper(self.rules[n]))
+38d984eb (vladimir_prus 2007-10-13 17:52:25 +0000 839)
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 840) def project(self, *args):
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 841)
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 842) jamfile_module = self.registry.current().project_module()
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 843) attributes = self.registry.attributes(jamfile_module)
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 844)
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 845) id = None
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 846) if args and args[0]:
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 847) id = args[0][0]
+092119e3 (vladimir_prus 2007-10-16 05:45:31 +0000 848) args = args[1:]
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 849)
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 850) if id:
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 851) if id[0] != '/':
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 852) id = '/' + id
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 853) self.registry.register_id (id, jamfile_module)
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 854)
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 855) explicit_build_dir = None
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 856) for a in args:
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 857) if a:
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 858) attributes.set(a[0], a[1:], exact=0)
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 859) if a[0] == "build-dir":
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 860) explicit_build_dir = a[1]
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 861)
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 862) # If '--build-dir' is specified, change the build dir for the project.
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 863) if self.registry.global_build_dir:
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 864)
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 865) location = attributes.get("location")
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 866) # Project with empty location is 'standalone' project, like
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 867) # user-config, or qt. It has no build dir.
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 868) # If we try to set build dir for user-config, we'll then
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 869) # try to inherit it, with either weird, or wrong consequences.
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 870) if location and location == attributes.get("project-root"):
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 871) # This is Jamroot.
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 872) if id:
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 873) if explicit_build_dir and os.path.isabs(explicit_build_dir):
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 874) self.register.manager.errors()(
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 875) """Absolute directory specified via 'build-dir' project attribute
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 876) Don't know how to combine that with the --build-dir option.""")
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 877)
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 878) rid = id
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 879) if rid[0] == '/':
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 880) rid = rid[1:]
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 881)
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 882) p = os.path.join(self.registry.global_build_dir,
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 883) rid, explicit_build_dir)
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 884) attributes.set("build-dir", p, exact=1)
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 885) elif explicit_build_dir:
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 886) self.registry.manager.errors()(
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 887) """When --build-dir is specified, the 'build-project'
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 888) attribute is allowed only for top-level 'project' invocations""")
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 889)
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 890) def constant(self, name, value):
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 891) """Declare and set a project global constant.
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 892) Project global constants are normal variables but should
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 893) not be changed. They are applied to every child Jamfile."""
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 894) m = "Jamfile</home/ghost/Work/Boost/boost-svn/tools/build/v2_python/python/tests/bjam/make>"
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 895) self.registry.current().add_constant(name[0], value)
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 896)
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 897) def path_constant(self, name, value):
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 898) """Declare and set a project global constant, whose value is a path. The
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 899) path is adjusted to be relative to the invocation directory. The given
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 900) value path is taken to be either absolute, or relative to this project
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 901) root."""
+0ed8e16d (vladimir_prus 2007-10-13 21:34:05 +0000 902) self.registry.current().add_constant(name[0], value, path=1)
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 903)
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 904) def use_project(self, id, where):
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 905) # See comment in 'load' for explanation why we record the
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 906) # parameters as opposed to loading the project now.
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 907) m = self.registry.current().project_module();
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 908) self.registry.used_projects[m].append((id, where))
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 909)
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 910) def build_project(self, dir):
+1674e2d9 (jhunold 2008-08-08 19:52:05 +0000 911) assert(isinstance(dir, list))
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 912) jamfile_module = self.registry.current().project_module()
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 913) attributes = self.registry.attributes(jamfile_module)
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 914) now = attributes.get("projects-to-build")
+1674e2d9 (jhunold 2008-08-08 19:52:05 +0000 915) attributes.set("projects-to-build", now + dir, exact=True)
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 916)
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 917) def explicit(self, target_names):
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 918) t = self.registry.current()
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 919) for n in target_names:
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 920) t.mark_target_as_explicit(n)
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 921)
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 922) def glob(self, wildcards, excludes=None):
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 923) return self.registry.glob_internal(self.registry.current(),
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 924) wildcards, excludes, "glob")
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 925)
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 926) def glob_tree(self, wildcards, excludes=None):
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 927) bad = 0
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 928) for p in wildcards:
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 929) if os.path.dirname(p):
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 930) bad = 1
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 931)
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 932) if excludes:
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 933) for p in excludes:
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 934) if os.path.dirname(p):
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 935) bad = 1
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 936)
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 937) if bad:
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 938) self.registry.manager().errors()(
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 939) "The patterns to 'glob-tree' may not include directory")
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 940) return self.registry.glob_internal(self.registry.current(),
+2a36874b (vladimir_prus 2007-10-14 07:20:55 +0000 941) wildcards, excludes, "glob_tree")
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 942)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 943)
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 944) def using(self, toolset, *args):
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 945) # The module referred by 'using' can be placed in
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 946) # the same directory as Jamfile, and the user
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 947) # will expect the module to be found even though
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 948) # the directory is not in BOOST_BUILD_PATH.
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 949) # So temporary change the search path.
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 950) jamfile_module = self.registry.current().project_module()
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 951) attributes = self.registry.attributes(jamfile_module)
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 952) location = attributes.get("location")
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 953)
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 954) m = self.registry.load_module(toolset[0], [location])
+7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 955) if not m.__dict__.has_key("init"):
+7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 956) self.registry.manager.errors()(
+7da7f9c1 (vladimir_prus 2008-05-18 04:29:53 +0000 957) "Tool module '%s' does not define the 'init' method" % toolset[0])
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 958) m.init(*args)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 959)
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 960)
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 961) def import_(self, name, names_to_import=None, local_names=None):
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 962)
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 963) name = name[0]
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 964) jamfile_module = self.registry.current().project_module()
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 965) attributes = self.registry.attributes(jamfile_module)
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 966) location = attributes.get("location")
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 967)
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 968) m = self.registry.load_module(name, [location])
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 969)
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 970) for f in m.__dict__:
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 971) v = m.__dict__[f]
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 972) if callable(v):
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 973) bjam.import_rule(jamfile_module, name + "." + f, v)
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 974)
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 975) if names_to_import:
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 976) if not local_names:
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 977) local_names = names_to_import
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 978)
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 979) if len(names_to_import) != len(local_names):
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 980) self.registry.manager.errors()(
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 981) """The number of names to import and local names do not match.""")
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 982)
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 983) for n, l in zip(names_to_import, local_names):
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 984) bjam.import_rule(jamfile_module, l, m.__dict__[n])
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 985)
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 986) def conditional(self, condition, requirements):
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 987) """Calculates conditional requirements for multiple requirements
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 988) at once. This is a shorthand to be reduce duplication and to
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 989) keep an inline declarative syntax. For example:
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 990)
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 991) lib x : x.cpp : [ conditional <toolset>gcc <variant>debug :
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 992) <define>DEBUG_EXCEPTION <define>DEBUG_TRACE ] ;
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 993) """
+f049766b (vladimir_prus 2007-10-10 09:31:06 +0000 994)
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 995) c = string.join(condition, ",")
+f2aef897 (vladimir_prus 2007-10-14 09:19:52 +0000 996) return [c + ":" + r for r in requirements]

Added: trunk/tools/build/v2/build/project.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/build/project.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,996 @@
+# Status: being ported by Vladimir Prus
+# Base revision: 40480
+
+# Copyright 2002, 2003 Dave Abrahams
+# Copyright 2002, 2005, 2006 Rene Rivera
+# Copyright 2002, 2003, 2004, 2005, 2006 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)
+
+# Implements project representation and loading.
+# Each project is represented by
+# - a module where all the Jamfile content live.
+# - an instance of 'project-attributes' class.
+# (given module name, can be obtained by 'attributes' rule)
+# - an instance of 'project-target' class (from targets.jam)
+# (given a module name, can be obtained by 'target' rule)
+#
+# Typically, projects are created as result of loading Jamfile, which is
+# do by rules 'load' and 'initialize', below. First, module for Jamfile
+# is loaded and new project-attributes instance is created. Some rules
+# necessary for project are added to the module (see 'project-rules' module)
+# at the bottom of this file.
+# Default project attributes are set (inheriting attributes of parent project, if
+# it exists). After that, Jamfile is read. It can declare its own attributes,
+# via 'project' rule, which will be combined with already set attributes.
+#
+#
+# The 'project' rule can also declare project id, which will be associated with
+# the project module.
+#
+# There can also be 'standalone' projects. They are created by calling 'initialize'
+# on arbitrary module, and not specifying location. After the call, the module can
+# call 'project' rule, declare main target and behave as regular projects. However,
+# since it's not associated with any location, it's better declare only prebuilt
+# targets.
+#
+# The list of all loaded Jamfile is stored in variable .project-locations. It's possible
+# to obtain module name for a location using 'module-name' rule. The standalone projects
+# are not recorded, the only way to use them is by project id.
+
+import b2.util.path
+from b2.build import property_set, property
+from b2.build.errors import ExceptionWithUserContext
+import b2.build.targets
+
+import bjam
+
+import re
+import sys
+import os
+import string
+import imp
+import traceback
+
+class ProjectRegistry:
+
+ def __init__(self, manager, global_build_dir):
+ self.manager = manager
+ self.global_build_dir = None
+ self.project_rules_ = ProjectRules(self)
+
+ # The target corresponding to the project being loaded now
+ self.current_project = None
+
+ # The set of names of loaded project modules
+ self.jamfile_modules = {}
+
+ # Mapping from location to module name
+ self.location2module = {}
+
+ # Mapping from project id to project module
+ self.id2module = {}
+
+ # Map from Jamfile directory to parent Jamfile/Jamroot
+ # location.
+ self.dir2parent_jamfile = {}
+
+ # Map from directory to the name of Jamfile in
+ # that directory (or None).
+ self.dir2jamfile = {}
+
+ # Map from project module to attributes object.
+ self.module2attributes = {}
+
+ # Map from project module to target for the project
+ self.module2target = {}
+
+ # Map from names to Python modules, for modules loaded
+ # via 'using' and 'import' rules in Jamfiles.
+ self.loaded_tool_modules_ = {}
+
+ # Map from project target to the list of
+ # (id,location) pairs corresponding to all 'use-project'
+ # invocations.
+ # TODO: should not have a global map, keep this
+ # in ProjectTarget.
+ self.used_projects = {}
+
+ self.saved_current_project = []
+
+ self.JAMROOT = self.manager.getenv("JAMROOT");
+
+ # Note the use of character groups, as opposed to listing
+ # 'Jamroot' and 'jamroot'. With the latter, we'd get duplicate
+ # matches on windows and would have to eliminate duplicates.
+ if not self.JAMROOT:
+ self.JAMROOT = ["project-root.jam", "[Jj]amroot", "[Jj]amroot.jam"]
+
+ # Default patterns to search for the Jamfiles to use for build
+ # declarations.
+ self.JAMFILE = self.manager.getenv("JAMFILE")
+
+ if not self.JAMFILE:
+ self.JAMFILE = ["[Bb]uild.jam", "[Jj]amfile.v2", "[Jj]amfile",
+ "[Jj]amfile.jam"]
+
+
+ def load (self, jamfile_location):
+ """Loads jamfile at the given location. After loading, project global
+ file and jamfile needed by the loaded one will be loaded recursively.
+ If the jamfile at that location is loaded already, does nothing.
+ Returns the project module for the Jamfile."""
+
+ absolute = os.path.join(os.getcwd(), jamfile_location)
+ absolute = os.path.normpath(absolute)
+ jamfile_location = b2.util.path.relpath(os.getcwd(), absolute)
+
+ if "--debug-loading" in self.manager.argv():
+ print "Loading Jamfile at '%s'" % jamfile_location
+
+
+ mname = self.module_name(jamfile_location)
+ # If Jamfile is already loaded, don't try again.
+ if not mname in self.jamfile_modules:
+
+ self.load_jamfile(jamfile_location)
+
+ # We want to make sure that child project are loaded only
+ # after parent projects. In particular, because parent projects
+ # define attributes whch are inherited by children, and we don't
+ # want children to be loaded before parents has defined everything.
+ #
+ # While "build-project" and "use-project" can potentially refer
+ # to child projects from parent projects, we don't immediately
+ # load child projects when seing those attributes. Instead,
+ # we record the minimal information that will be used only later.
+
+ self.load_used_projects(mname)
+
+ return mname
+
+ def load_used_projects(self, module_name):
+ # local used = [ modules.peek $(module-name) : .used-projects ] ;
+ used = self.used_projects[module_name]
+
+ location = self.attribute(module_name, "location")
+ for u in used:
+ id = u[0]
+ where = u[1]
+
+ self.use(id, os.path.join(location, where))
+
+ def load_parent(self, location):
+ """Loads parent of Jamfile at 'location'.
+ Issues an error if nothing is found."""
+
+ found = b2.util.path.glob_in_parents(
+ location, self.JAMROOT + self.JAMFILE)
+
+ if not found:
+ print "error: Could not find parent for project at '%s'" % location
+ print "error: Did not find Jamfile or project-root.jam in any parent directory."
+ sys.exit(1)
+
+ return self.load(os.path.dirname(found[0]))
+
+ def act_as_jamfile(self, module, location):
+ """Makes the specified 'module' act as if it were a regularly loaded Jamfile
+ at 'location'. If Jamfile is already located for that location, it's an
+ error."""
+
+ if self.module_name(location) in self.jamfile_modules:
+ self.manager.errors()(
+ "Jamfile was already loaded for '%s'" % location)
+
+ # Set up non-default mapping from location to module.
+ self.location2module[location] = module
+
+ # Add the location to the list of project locations
+ # so that we don't try to load Jamfile in future
+ self.jamfile_modules.append(location)
+
+ self.initialize(module, location)
+
+ def find(self, name, current_location):
+ """Given 'name' which can be project-id or plain directory name,
+ return project module corresponding to that id or directory.
+ Returns nothing of project is not found."""
+
+ project_module = None
+
+ # Try interpreting name as project id.
+ if name[0] == '/':
+ project_module = self.id2module.get(name)
+
+ if not project_module:
+ location = os.path.join(current_location, name)
+ # If no project is registered for the given location, try to
+ # load it. First see if we have Jamfile. If not we might have project
+ # root, willing to act as Jamfile. In that case, project-root
+ # must be placed in the directory referred by id.
+
+ project_module = self.module_name(location)
+ if not project_module in self.jamfile_modules and \
+ b2.util.path.glob([location], self.JAMROOT + self.JAMFILE):
+ project_module = self.load(location)
+
+ return project_module
+
+ def module_name(self, jamfile_location):
+ """Returns the name of module corresponding to 'jamfile-location'.
+ If no module corresponds to location yet, associates default
+ module name with that location."""
+ module = self.location2module.get(jamfile_location)
+ if not module:
+ # Root the path, so that locations are always umbiguious.
+ # Without this, we can't decide if '../../exe/program1' and '.'
+ # are the same paths, or not.
+ jamfile_location = os.path.realpath(
+ os.path.join(os.getcwd(), jamfile_location))
+ module = "Jamfile<%s>" % jamfile_location
+ self.location2module[jamfile_location] = module
+ return module
+
+ def find_jamfile (self, dir, parent_root=0, no_errors=0):
+ """Find the Jamfile at the given location. This returns the
+ exact names of all the Jamfiles in the given directory. The optional
+ parent-root argument causes this to search not the given directory
+ but the ones above it up to the directory given in it."""
+
+ # Glob for all the possible Jamfiles according to the match pattern.
+ #
+ jamfile_glob = None
+ if parent_root:
+ parent = self.dir2parent_jamfile.get(dir)
+ if not parent:
+ parent = b2.util.path.glob_in_parents(dir,
+ self.JAMFILE)
+ self.dir2parent_jamfile[dir] = parent
+ jamfile_glob = parent
+ else:
+ jamfile = self.dir2jamfile.get(dir)
+ if not jamfile:
+ jamfile = b2.util.path.glob([dir], self.JAMFILE)
+ self.dir2jamfile[dir] = jamfile
+ jamfile_glob = jamfile
+
+ if len(jamfile_glob):
+ # Multiple Jamfiles found in the same place. Warn about this.
+ # And ensure we use only one of them.
+ # As a temporary convenience measure, if there's Jamfile.v2 amount
+ # found files, suppress the warning and use it.
+ #
+ pattern = "(.*[Jj]amfile\\.v2)|(.*[Bb]uild\\.jam)"
+ v2_jamfiles = [x for x in jamfile_glob if re.match(pattern, x)]
+ if len(v2_jamfiles) == 1:
+ jamfile_glob = v2_jamfiles
+ else:
+ print """warning: Found multiple Jamfiles at '%s'!
+Loading the first one: '%s'.""" % (dir, jamfile_glob[0])
+
+ # Could not find it, error.
+ if not no_errors and not jamfile_glob:
+ self.manager.errors()(
+ """Unable to load Jamfile.
+Could not find a Jamfile in directory '%s'
+Attempted to find it with pattern '%s'.
+Please consult the documentation at 'http://boost.org/boost-build2'."""
+ % (dir, string.join(self.JAMFILE)))
+
+ return jamfile_glob[0]
+
+ def load_jamfile(self, dir):
+ """Load a Jamfile at the given directory. Returns nothing.
+ Will attempt to load the file as indicated by the JAMFILE patterns.
+ Effect of calling this rule twice with the same 'dir' is underfined."""
+
+ # See if the Jamfile is where it should be.
+ jamfile_to_load = b2.util.path.glob([dir], self.JAMROOT)
+ if not jamfile_to_load:
+ jamfile_to_load = self.find_jamfile(dir)
+ else:
+ jamfile_to_load = jamfile_to_load[0]
+
+ # The module of the jamfile.
+ dir = os.path.realpath(os.path.dirname(jamfile_to_load))
+
+ jamfile_module = self.module_name (dir)
+
+ # Initialize the jamfile module before loading.
+ #
+ self.initialize(jamfile_module, dir, os.path.basename(jamfile_to_load))
+
+ saved_project = self.current_project
+
+ self.used_projects[jamfile_module] = []
+
+ # Now load the Jamfile in it's own context.
+ # Initialization might have load parent Jamfiles, which might have
+ # loaded the current Jamfile with use-project. Do a final check to make
+ # sure it's not loaded already.
+ if not jamfile_module in self.jamfile_modules:
+ self.jamfile_modules[jamfile_module] = True
+
+ # FIXME:
+ # mark-as-user $(jamfile-module) ;
+
+ bjam.call("load", jamfile_module, jamfile_to_load)
+ basename = os.path.basename(jamfile_to_load)
+
+ # Now do some checks
+ if self.current_project != saved_project:
+ self.manager.errors()(
+"""The value of the .current-project variable
+has magically changed after loading a Jamfile.
+This means some of the targets might be defined a the wrong project.
+after loading %s
+expected value %s
+actual value %s""" % (jamfile_module, saved_project, self.current_project))
+
+ if self.global_build_dir:
+ id = self.attribute(jamfile_module, "id")
+ project_root = self.attribute(jamfile_module, "project-root")
+ location = self.attribute(jamfile_module, "location")
+
+ if location and project_root == dir:
+ # This is Jamroot
+ if not id:
+ # FIXME: go via errors module, so that contexts are
+ # shown?
+ print "warning: the --build-dir option was specified"
+ print "warning: but Jamroot at '%s'" % dir
+ print "warning: specified no project id"
+ print "warning: the --build-dir option will be ignored"
+
+
+ def load_standalone(self, jamfile_module, file):
+ """Loads 'file' as standalone project that has no location
+ associated with it. This is mostly useful for user-config.jam,
+ which should be able to define targets, but although it has
+ some location in filesystem, we don't want any build to
+ happen in user's HOME, for example.
+
+ The caller is required to never call this method twice on
+ the same file.
+ """
+
+ self.initialize(jamfile_module)
+ self.used_projects[jamfile_module] = []
+ bjam.call("load", jamfile_module, file)
+ self.load_used_projects(jamfile_module)
+
+ def is_jamroot(self, basename):
+ match = [ pat for pat in self.JAMROOT if re.match(pat, basename)]
+ if match:
+ return 1
+ else:
+ return 0
+
+ def initialize(self, module_name, location=None, basename=None):
+ """Initialize the module for a project.
+
+ module-name is the name of the project module.
+ location is the location (directory) of the project to initialize.
+ If not specified, stanalone project will be initialized
+ """
+
+ if "--debug-loading" in self.manager.argv():
+ print "Initializing project '%s'" % module_name
+
+ # TODO: need to consider if standalone projects can do anything but defining
+ # prebuilt targets. If so, we need to give more sensible "location", so that
+ # source paths are correct.
+ if not location:
+ location = ""
+ else:
+ location = b2.util.path.relpath(os.getcwd(), location)
+
+ attributes = ProjectAttributes(self.manager, location, module_name)
+ self.module2attributes[module_name] = attributes
+
+ if location:
+ attributes.set("source-location", location, exact=1)
+ else:
+ attributes.set("source-location", "", exact=1)
+
+ attributes.set("requirements", property_set.empty(), exact=True)
+ attributes.set("usage-requirements", property_set.empty(), exact=True)
+ attributes.set("default-build", [], exact=True)
+ attributes.set("projects-to-build", [], exact=True)
+ attributes.set("project-root", None, exact=True)
+ attributes.set("build-dir", None, exact=True)
+
+ self.project_rules_.init_project(module_name)
+
+ jamroot = False
+
+ parent_module = None;
+ if module_name == "site-config":
+ # No parent
+ pass
+ elif module_name == "user-config":
+ parent_module = "site-config"
+ elif location and not self.is_jamroot(basename):
+ # We search for parent/project-root only if jamfile was specified
+ # --- i.e
+ # if the project is not standalone.
+ parent_module = self.load_parent(location)
+ else:
+ # It's either jamroot, or standalone project.
+ # If it's jamroot, inherit from user-config.
+ if location:
+ parent_module = "user-config" ;
+ jamroot = True ;
+
+ if parent_module:
+ self.inherit_attributes(module_name, parent_module)
+ attributes.set("parent-module", parent_module, exact=1)
+
+ if jamroot:
+ attributes.set("project-root", location, exact=1)
+
+ parent = None
+ if parent_module:
+ parent = self.target(parent_module)
+
+ if not self.module2target.has_key(module_name):
+ target = b2.build.targets.ProjectTarget(self.manager,
+ module_name, module_name, parent,
+ self.attribute(module_name,"requirements"),
+ # FIXME: why we need to pass this? It's not
+ # passed in jam code.
+ self.attribute(module_name, "default-build"))
+ self.module2target[module_name] = target
+
+ self.current_project = self.target(module_name)
+
+ def inherit_attributes(self, project_module, parent_module):
+ """Make 'project-module' inherit attributes of project
+ root and parent module."""
+
+ attributes = self.module2attributes[project_module]
+ pattributes = self.module2attributes[parent_module]
+
+ # Parent module might be locationless user-config.
+ # FIXME:
+ #if [ modules.binding $(parent-module) ]
+ #{
+ # $(attributes).set parent : [ path.parent
+ # [ path.make [ modules.binding $(parent-module) ] ] ] ;
+ # }
+
+ attributes.set("project-root", pattributes.get("project-root"), exact=True)
+ attributes.set("default-build", pattributes.get("default-build"), exact=True)
+ attributes.set("requirements", pattributes.get("requirements"), exact=True)
+ attributes.set("usage-requirements",
+ pattributes.get("usage-requirements"), exact=1)
+
+ parent_build_dir = pattributes.get("build-dir")
+
+ if parent_build_dir:
+ # Have to compute relative path from parent dir to our dir
+ # Convert both paths to absolute, since we cannot
+ # find relative path from ".." to "."
+
+ location = attributes.get("location")
+ parent_location = pattributes.get("location")
+
+ our_dir = os.path.join(os.getcwd(), location)
+ parent_dir = os.path.join(os.getcwd(), parent_location)
+
+ build_dir = os.path.join(parent_build_dir,
+ b2.util.path.relpath(parent_dir,
+ our_dir))
+
+ def register_id(self, id, module):
+ """Associate the given id with the given project module."""
+ self.id2module[id] = module
+
+ def current(self):
+ """Returns the project which is currently being loaded."""
+ return self.current_project
+
+ def push_current(self, project):
+ """Temporary changes the current project to 'project'. Should
+ be followed by 'pop-current'."""
+ self.saved_current_project.append(self.current_project)
+ self.current_project = project
+
+ def pop_current(self):
+ self.current_project = self.saved_current_project[-1]
+ del self.saved_current_project[-1]
+
+ def attributes(self, project):
+ """Returns the project-attribute instance for the
+ specified jamfile module."""
+ return self.module2attributes[project]
+
+ def attribute(self, project, attribute):
+ """Returns the value of the specified attribute in the
+ specified jamfile module."""
+ return self.module2attributes[project].get(attribute)
+
+ def target(self, project_module):
+ """Returns the project target corresponding to the 'project-module'."""
+ if not self.module2target[project_module]:
+ self.module2target[project_module] = \
+ ProjectTarget(project_module, project_module,
+ self.attribute(project_module, "requirements"))
+
+ return self.module2target[project_module]
+
+ def use(self, id, location):
+ # Use/load a project.
+ saved_project = self.current_project
+ project_module = self.load(location)
+ declared_id = self.attribute(project_module, "id")
+
+ if not declared_id or declared_id != id:
+ # The project at 'location' either have no id or
+ # that id is not equal to the 'id' parameter.
+ if self.id2module[id] and self.id2module[id] != project_module:
+ self.manager.errors()(
+"""Attempt to redeclare already existing project id '%s'""" % id)
+ self.id2module[id] = project_module
+
+ self.current_module = saved_project
+
+ def add_rule(self, name, callable):
+ """Makes rule 'name' available to all subsequently loaded Jamfiles.
+
+ Calling that rule wil relay to 'callable'."""
+ self.project_rules_.add_rule(name, callable)
+
+ def project_rules(self):
+ return self.project_rules_
+
+ def glob_internal(self, project, wildcards, excludes, rule_name):
+ location = project.get("source-location")
+
+ result = []
+ callable = b2.util.path.__dict__[rule_name]
+
+ paths = callable(location, wildcards, excludes)
+ has_dir = 0
+ for w in wildcards:
+ if os.path.dirname(w):
+ has_dir = 1
+ break
+
+ if has_dir or rule_name != "glob":
+ # The paths we've found are relative to current directory,
+ # but the names specified in sources list are assumed to
+ # be relative to source directory of the corresponding
+ # prject. So, just make the name absolute.
+ result = [os.path.join(os.getcwd(), p) for p in paths]
+ else:
+ # There were not directory in wildcard, so the files are all
+ # in the source directory of the project. Just drop the
+ # directory, instead of making paths absolute.
+ result = [os.path.basename(p) for p in paths]
+
+ return result
+
+ def load_module(self, name, extra_path=None):
+ """Classic Boost.Build 'modules' are in fact global variables.
+ Therefore, try to find an already loaded Python module called 'name' in sys.modules.
+ If the module ist not loaded, find it Boost.Build search
+ path and load it. The new module is not entered in sys.modules.
+ The motivation here is to have disjoint namespace of modules
+ loaded via 'import/using' in Jamfile, and ordinary Python
+ modules. We don't want 'using foo' in Jamfile to load ordinary
+ Python module 'foo' which is going to not work. And we
+ also don't want 'import foo' in regular Python module to
+ accidentally grab module named foo that is internal to
+ Boost.Build and intended to provide interface to Jamfiles."""
+
+ existing = self.loaded_tool_modules_.get(name)
+ if existing:
+ return existing
+
+ modules = sys.modules
+ for class_name in modules:
+ if name is class_name:
+ module = modules[class_name]
+ self.loaded_tool_modules_[name] = module
+ return module
+
+ path = extra_path
+ if not path:
+ path = []
+ path.extend(self.manager.boost_build_path())
+ location = None
+ for p in path:
+ l = os.path.join(p, name + ".py")
+ if os.path.exists(l):
+ location = l
+ break
+
+ if not location:
+ self.manager.errors()("Cannot find module '%s'" % name)
+
+ mname = "__build_build_temporary__"
+ file = open(location)
+ try:
+ # TODO: this means we'll never make use of .pyc module,
+ # which might be a problem, or not.
+ module = imp.load_module(mname, file, os.path.basename(location),
+ (".py", "r", imp.PY_SOURCE))
+ del sys.modules[mname]
+ self.loaded_tool_modules_[name] = module
+ return module
+ finally:
+ file.close()
+
+
+
+# FIXME:
+# Defines a Boost.Build extension project. Such extensions usually
+# contain library targets and features that can be used by many people.
+# Even though extensions are really projects, they can be initialize as
+# a module would be with the "using" (project.project-rules.using)
+# mechanism.
+#rule extension ( id : options * : * )
+#{
+# # The caller is a standalone module for the extension.
+# local mod = [ CALLER_MODULE ] ;
+#
+# # We need to do the rest within the extension module.
+# module $(mod)
+# {
+# import path ;
+#
+# # Find the root project.
+# local root-project = [ project.current ] ;
+# root-project = [ $(root-project).project-module ] ;
+# while
+# [ project.attribute $(root-project) parent-module ] &&
+# [ project.attribute $(root-project) parent-module ] != user-config
+# {
+# root-project = [ project.attribute $(root-project) parent-module ] ;
+# }
+#
+# # Create the project data, and bring in the project rules
+# # into the module.
+# project.initialize $(__name__) :
+# [ path.join [ project.attribute $(root-project) location ] ext $(1:L) ] ;
+#
+# # Create the project itself, i.e. the attributes.
+# # All extensions are created in the "/ext" project space.
+# project /ext/$(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ;
+# local attributes = [ project.attributes $(__name__) ] ;
+#
+# # Inherit from the root project of whomever is defining us.
+# project.inherit-attributes $(__name__) : $(root-project) ;
+# $(attributes).set parent-module : $(root-project) : exact ;
+# }
+#}
+
+
+class ProjectAttributes:
+ """Class keeping all the attributes of a project.
+
+ The standard attributes are 'id', "location", "project-root", "parent"
+ "requirements", "default-build", "source-location" and "projects-to-build".
+ """
+
+ def __init__(self, manager, location, project_module):
+ self.manager = manager
+ self.location = location
+ self.project_module = project_module
+ self.attributes = {}
+ self.usage_requirements = None
+
+ def set(self, attribute, specification, exact):
+ """Set the named attribute from the specification given by the user.
+ The value actually set may be different."""
+
+ if exact:
+ self.__dict__[attribute] = specification
+
+ elif attribute == "requirements":
+ self.requirements = property_set.refine_from_user_input(
+ self.requirements, specification,
+ self.project_module, self.location)
+
+ elif attribute == "usage-requirements":
+ unconditional = []
+ for p in specification:
+ split = property.split_conditional(p)
+ if split:
+ unconditional.append(split[1])
+ else:
+ unconditional.append(p)
+
+ non_free = property.remove("free", unconditional)
+ if non_free:
+ pass
+ # FIXME:
+ #errors.error "usage-requirements" $(specification) "have non-free properties" $(non-free) ;
+
+ t = property.translate_paths(specification, self.location)
+
+ existing = self.__dict__.get("usage-requirements")
+ if existing:
+ new = property_set.create(existing.raw() + t)
+ else:
+ new = property_set.create(t)
+ self.__dict__["usage-requirements"] = new
+
+
+ elif attribute == "default-build":
+ self.__dict__["default-build"] = property_set.create(specification)
+
+ elif attribute == "source-location":
+ source_location = []
+ for path in specification:
+ source_location += os.path.join(self.location, path)
+ self.__dict__["source-location"] = source_location
+
+ elif attribute == "build-dir":
+ self.__dict__["build-dir"] = os.path.join(self.location, specification)
+
+ elif not attribute in ["id", "default-build", "location",
+ "source-location", "parent",
+ "projects-to-build", "project-root"]:
+ self.manager.errors()(
+"""Invalid project attribute '%s' specified
+for project at '%s'""" % (attribute, self.location))
+ else:
+ self.__dict__[attribute] = specification
+
+ def get(self, attribute):
+ return self.__dict__[attribute]
+
+ def dump(self):
+ """Prints the project attributes."""
+ id = self.get("id")
+ if not id:
+ id = "(none)"
+ else:
+ id = id[0]
+
+ parent = self.get("parent")
+ if not parent:
+ parent = "(none)"
+ else:
+ parent = parent[0]
+
+ print "'%s'" % id
+ print "Parent project:%s", parent
+ print "Requirements:%s", self.get("requirements")
+ print "Default build:%s", string.join(self.get("debuild-build"))
+ print "Source location:%s", string.join(self.get("source-location"))
+ print "Projects to build:%s", string.join(self.get("projects-to-build").sort());
+
+class ProjectRules:
+ """Class keeping all rules that are made available to Jamfile."""
+
+ def __init__(self, registry):
+ self.registry = registry
+ self.manager_ = registry.manager
+ self.rules = {}
+ self.local_names = [x for x in self.__class__.__dict__
+ if x not in ["__init__", "init_project", "add_rule",
+ "error_reporting_wrapper", "add_rule_for_type"]]
+ self.all_names_ = [x for x in self.local_names]
+
+ def add_rule_for_type(self, type):
+ rule_name = type.lower();
+
+ def xpto (name, sources, requirements = [], default_build = None, usage_requirements = []):
+ return self.manager_.targets().create_typed_target(
+ type, self.registry.current(), name[0], sources,
+ requirements, default_build, usage_requirements)
+
+ self.add_rule(type.lower(), xpto)
+
+ def add_rule(self, name, callable):
+ self.rules[name] = callable
+ self.all_names_.append(name)
+
+ def all_names(self):
+ return self.all_names_
+
+ def call_and_report_errors(self, callable, *args):
+ result = None
+ try:
+ self.manager_.errors().push_jamfile_context()
+ result = callable(*args)
+ except ExceptionWithUserContext, e:
+ e.report()
+ except Exception, e:
+ try:
+ self.manager_.errors().handle_stray_exception (e)
+ except ExceptionWithUserContext, e:
+ e.report()
+ finally:
+ self.manager_.errors().pop_jamfile_context()
+
+ return result
+
+ def make_wrapper(self, callable):
+ """Given a free-standing function 'callable', return a new
+ callable that will call 'callable' and report all exceptins,
+ using 'call_and_report_errors'."""
+ def wrapper(*args):
+ self.call_and_report_errors(callable, *args)
+ return wrapper
+
+ def init_project(self, project_module):
+
+ for n in self.local_names:
+ # Using 'getattr' here gives us a bound method,
+ # while using self.__dict__[r] would give unbound one.
+ v = getattr(self, n)
+ if callable(v):
+ if n == "import_":
+ n = "import"
+ else:
+ n = string.replace(n, "_", "-")
+
+ bjam.import_rule(project_module, n,
+ self.make_wrapper(v))
+
+ for n in self.rules:
+ bjam.import_rule(project_module, n,
+ self.make_wrapper(self.rules[n]))
+
+ def project(self, *args):
+
+ jamfile_module = self.registry.current().project_module()
+ attributes = self.registry.attributes(jamfile_module)
+
+ id = None
+ if args and args[0]:
+ id = args[0][0]
+ args = args[1:]
+
+ if id:
+ if id[0] != '/':
+ id = '/' + id
+ self.registry.register_id (id, jamfile_module)
+
+ explicit_build_dir = None
+ for a in args:
+ if a:
+ attributes.set(a[0], a[1:], exact=0)
+ if a[0] == "build-dir":
+ explicit_build_dir = a[1]
+
+ # If '--build-dir' is specified, change the build dir for the project.
+ if self.registry.global_build_dir:
+
+ location = attributes.get("location")
+ # Project with empty location is 'standalone' project, like
+ # user-config, or qt. It has no build dir.
+ # If we try to set build dir for user-config, we'll then
+ # try to inherit it, with either weird, or wrong consequences.
+ if location and location == attributes.get("project-root"):
+ # This is Jamroot.
+ if id:
+ if explicit_build_dir and os.path.isabs(explicit_build_dir):
+ self.register.manager.errors()(
+"""Absolute directory specified via 'build-dir' project attribute
+Don't know how to combine that with the --build-dir option.""")
+
+ rid = id
+ if rid[0] == '/':
+ rid = rid[1:]
+
+ p = os.path.join(self.registry.global_build_dir,
+ rid, explicit_build_dir)
+ attributes.set("build-dir", p, exact=1)
+ elif explicit_build_dir:
+ self.registry.manager.errors()(
+"""When --build-dir is specified, the 'build-project'
+attribute is allowed only for top-level 'project' invocations""")
+
+ def constant(self, name, value):
+ """Declare and set a project global constant.
+ Project global constants are normal variables but should
+ not be changed. They are applied to every child Jamfile."""
+ m = "Jamfile</home/ghost/Work/Boost/boost-svn/tools/build/v2_python/python/tests/bjam/make>"
+ self.registry.current().add_constant(name[0], value)
+
+ def path_constant(self, name, value):
+ """Declare and set a project global constant, whose value is a path. The
+ path is adjusted to be relative to the invocation directory. The given
+ value path is taken to be either absolute, or relative to this project
+ root."""
+ self.registry.current().add_constant(name[0], value, path=1)
+
+ def use_project(self, id, where):
+ # See comment in 'load' for explanation why we record the
+ # parameters as opposed to loading the project now.
+ m = self.registry.current().project_module();
+ self.registry.used_projects[m].append((id, where))
+
+ def build_project(self, dir):
+ assert(isinstance(dir, list))
+ jamfile_module = self.registry.current().project_module()
+ attributes = self.registry.attributes(jamfile_module)
+ now = attributes.get("projects-to-build")
+ attributes.set("projects-to-build", now + dir, exact=True)
+
+ def explicit(self, target_names):
+ t = self.registry.current()
+ for n in target_names:
+ t.mark_target_as_explicit(n)
+
+ def glob(self, wildcards, excludes=None):
+ return self.registry.glob_internal(self.registry.current(),
+ wildcards, excludes, "glob")
+
+ def glob_tree(self, wildcards, excludes=None):
+ bad = 0
+ for p in wildcards:
+ if os.path.dirname(p):
+ bad = 1
+
+ if excludes:
+ for p in excludes:
+ if os.path.dirname(p):
+ bad = 1
+
+ if bad:
+ self.registry.manager().errors()(
+"The patterns to 'glob-tree' may not include directory")
+ return self.registry.glob_internal(self.registry.current(),
+ wildcards, excludes, "glob_tree")
+
+
+ def using(self, toolset, *args):
+ # The module referred by 'using' can be placed in
+ # the same directory as Jamfile, and the user
+ # will expect the module to be found even though
+ # the directory is not in BOOST_BUILD_PATH.
+ # So temporary change the search path.
+ jamfile_module = self.registry.current().project_module()
+ attributes = self.registry.attributes(jamfile_module)
+ location = attributes.get("location")
+
+ m = self.registry.load_module(toolset[0], [location])
+ if not m.__dict__.has_key("init"):
+ self.registry.manager.errors()(
+ "Tool module '%s' does not define the 'init' method" % toolset[0])
+ m.init(*args)
+
+
+ def import_(self, name, names_to_import=None, local_names=None):
+
+ name = name[0]
+ jamfile_module = self.registry.current().project_module()
+ attributes = self.registry.attributes(jamfile_module)
+ location = attributes.get("location")
+
+ m = self.registry.load_module(name, [location])
+
+ for f in m.__dict__:
+ v = m.__dict__[f]
+ if callable(v):
+ bjam.import_rule(jamfile_module, name + "." + f, v)
+
+ if names_to_import:
+ if not local_names:
+ local_names = names_to_import
+
+ if len(names_to_import) != len(local_names):
+ self.registry.manager.errors()(
+"""The number of names to import and local names do not match.""")
+
+ for n, l in zip(names_to_import, local_names):
+ bjam.import_rule(jamfile_module, l, m.__dict__[n])
+
+ def conditional(self, condition, requirements):
+ """Calculates conditional requirements for multiple requirements
+ at once. This is a shorthand to be reduce duplication and to
+ keep an inline declarative syntax. For example:
+
+ lib x : x.cpp : [ conditional <toolset>gcc <variant>debug :
+ <define>DEBUG_EXCEPTION <define>DEBUG_TRACE ] ;
+ """
+
+ c = string.join(condition, ",")
+ return [c + ":" + r for r in requirements]

Added: trunk/tools/build/v2/build/property.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/build/property.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,636 @@
+# Status: ported, except for tests and --abbreviate-paths.
+# Base revision: 40480
+#
+# Copyright 2001, 2002, 2003 Dave Abrahams
+# Copyright 2006 Rene Rivera
+# Copyright 2002, 2003, 2004, 2005, 2006 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)
+
+import re
+from b2.util.utility import *
+from b2.build import feature
+from b2.util import sequence, set
+
+__re_two_ampersands = re.compile ('&&')
+__re_comma = re.compile (',')
+__re_split_condition = re.compile ('(.*):(<.*)')
+__re_toolset_feature = re.compile ('^(<toolset>|<toolset->)')
+__re_os_feature = re.compile ('^(<os>)')
+__re_split_conditional = re.compile (r'(.+):<(.+)')
+__re_colon = re.compile (':')
+__re_has_condition = re.compile (r':<')
+__re_separate_condition_and_property = re.compile (r'(.*):(<.*)')
+
+def reset ():
+ """ Clear the module state. This is mainly for testing purposes.
+ """
+ global __results
+
+ # A cache of results from as_path
+ __results = {}
+
+reset ()
+
+
+def path_order (x, y):
+ """ Helper for as_path, below. Orders properties with the implicit ones
+ first, and within the two sections in alphabetical order of feature
+ name.
+ """
+ if x == y:
+ return 0
+
+ xg = get_grist (x)
+ yg = get_grist (y)
+
+ if yg and not xg:
+ return -1
+
+ elif xg and not yg:
+ return 1
+
+ else:
+ if not xg:
+ x = feature.expand_subfeatures([x])
+ y = feature.expand_subfeatures([y])
+
+ if x < y:
+ return -1
+ elif x > y:
+ return 1
+ else:
+ return 0
+
+def abbreviate_dashed(string):
+ # FIXME: string.abbreviate?
+ return [string.abbreviate(part) for part in string.split('-')].join('-')
+
+def identify(string):
+ return string
+
+# FIXME: --abbreviate-paths
+
+def as_path (properties):
+ """ Returns a path which represents the given expanded property set.
+ """
+ key = '-'.join (properties)
+
+ if not __results.has_key (key):
+ # trim redundancy
+ properties = feature.minimize (properties)
+
+ # sort according to path_order
+ properties.sort (path_order)
+
+ components = []
+ for p in properties:
+ pg = get_grist (p)
+ # FIXME: abbrev?
+ if pg:
+ f = ungrist (pg)
+ components.append (f + '-' + replace_grist (p, ''))
+
+ else:
+ components.append (p)
+
+ __results [key] = '/'.join (components)
+
+ return __results [key]
+
+def refine (properties, requirements):
+ """ Refines 'properties' by overriding any non-free properties
+ for which a different value is specified in 'requirements'.
+ Conditional requirements are just added without modification.
+ Returns the resulting list of properties.
+ """
+ # The result has no duplicates, so we store it in a map
+ # TODO: use a set from Python 2.4?
+ result = {}
+
+ # Records all requirements.
+ required = {}
+
+ # All the elements of requirements should be present in the result
+ # Record them so that we can handle 'properties'.
+ for r in requirements:
+ # Don't consider conditional requirements.
+ if not is_conditional (r):
+ # Note: cannot use local here, so take an ugly name
+ required [get_grist (r)] = replace_grist (r, '')
+
+ for p in properties:
+ # Skip conditional properties
+ if is_conditional (p):
+ result [p] = None
+ # No processing for free properties
+ elif 'free' in feature.attributes (get_grist (p)):
+ result [p] = None
+ else:
+ if required.has_key (get_grist (p)):
+ required_value = required [get_grist (p)]
+
+ value = replace_grist (p, '')
+
+ if value != required_value:
+ result [replace_grist (required_value, get_grist (p))] = None
+ else:
+ result [p] = None
+ else:
+ result [p] = None
+
+ return result.keys () + requirements
+
+def translate_paths (properties, path):
+ """ Interpret all path properties in 'properties' as relative to 'path'
+ The property values are assumed to be in system-specific form, and
+ will be translated into normalized form.
+ """
+ result = []
+
+ for p in properties:
+ split = split_conditional (p)
+
+ condition = ''
+
+ if split:
+ condition = split [0]
+ p = split [1]
+
+ if get_grist (p) and 'path' in feature.attributes (get_grist (p)):
+ values = __re_two_ampersands.split (forward_slashes (get_grist (p)))
+
+ t = [os.path.join(path, v) for v in values]
+ t = '&&'.join (t)
+ tp = backslashes_to_slashes (replace_grist (t, get_grist (p)))
+ result.append (condition + tp)
+
+ else:
+ result.append (condition + p)
+
+ return result
+
+def translate_indirect(specification, context_module):
+ """Assumes that all feature values that start with '@' are
+ names of rules, used in 'context-module'. Such rules can be
+ either local to the module or global. Qualified local rules
+ with the name of the module."""
+ result = []
+ for p in specification:
+ if p[0] == '@':
+ m = p[1:]
+ if not '.' in p:
+ # This is unqualified rule name. The user might want
+ # to set flags on this rule name, and toolset.flag
+ # auto-qualifies the rule name. Need to do the same
+ # here so set flag setting work.
+ # We can arrange for toolset.flag to *not* auto-qualify
+ # the argument, but then two rules defined in two Jamfiles
+ # will conflict.
+ m = context_module + "." + m
+
+ result.append(get_grist(p) + "@" + m)
+ else:
+ result.append(p)
+
+ return result
+
+def validate (properties):
+ """ Exit with error if any of the properties is not valid.
+ properties may be a single property or a sequence of properties.
+ """
+
+ if isinstance (properties, str):
+ __validate1 (properties)
+ else:
+ for p in properties:
+ __validate1 (p)
+
+def expand_subfeatures_in_conditions (properties):
+
+ result = []
+ for p in properties:
+ s = __re_split_condition.match (p)
+
+ if not s:
+ result.append (p)
+
+ else:
+ condition = s.group (1)
+
+ # Condition might include several elements
+ condition = __re_comma.split (condition)
+
+ value = s.group (2)
+
+ e = []
+ for c in condition:
+
+ cg = get_grist (c)
+ if __re_toolset_feature.match (cg) or __re_os_feature.match (cg):
+ # It common that condition includes a toolset which
+ # was never defined, or mentiones subfeatures which
+ # were never defined. In that case, validation will
+ # only produce an spirious error, so don't validate.
+ e.append (feature.expand_subfeatures (c, True))
+
+ else:
+ e.append (feature.expand_subfeatures (c))
+
+ if e == condition:
+ result.append (p)
+
+ else:
+ individual_subfeatures = Set.difference (e, condition)
+ result.append (','.join (individual_subfeatures) + ':' + value)
+
+ return result
+
+def make (specification):
+ """ Converts implicit values into full properties.
+ """
+ result = []
+ for e in specification:
+ if get_grist (e):
+ result.append (e)
+
+ elif feature.is_implicit_value (e):
+ f = feature.implied_feature (e)
+ result.append (f + e)
+
+ else:
+ raise InvalidProperty ("'%s' is not a valid for property specification" % e)
+
+ return result
+
+
+def split_conditional (property):
+ """ If 'property' is conditional property, returns
+ condition and the property, e.g
+ <variant>debug,<toolset>gcc:<inlining>full will become
+ <variant>debug,<toolset>gcc <inlining>full.
+ Otherwise, returns empty string.
+ """
+ m = __re_split_conditional.match (property)
+
+ if m:
+ return (m.group (1), '<' + m.group (2))
+
+ return None
+
+def is_conditional (property):
+ """ Returns True if a property is conditional.
+ """
+ if __re_colon.search (replace_grist (property, '')):
+ return True
+ else:
+ return False
+
+def select (features, properties):
+ """ Selects properties which correspond to any of the given features.
+ """
+ result = []
+
+ # add any missing angle brackets
+ features = add_grist (features)
+
+ return [p for p in properties if get_grist(p) in features]
+
+def validate_property_sets (sets):
+ for s in sets:
+ validate(feature.split(s))
+
+
+def evaluate_conditionals_in_context (properties, context):
+ """ Removes all conditional properties which conditions are not met
+ For those with met conditions, removes the condition. Properies
+ in conditions are looked up in 'context'
+ """
+ base = []
+ conditionals = []
+
+ for p in properties:
+ if __re_has_condition.search (p):
+ conditionals.append (p)
+ else:
+ base.append (p)
+
+ result = base
+ for p in conditionals:
+
+ # Separate condition and property
+ s = __re_separate_condition_and_property.match (p)
+
+ # Split condition into individual properties
+ conditions = s.group (1).split (',')
+
+ # Evaluate condition
+ if set.contains (c, context):
+ result.append (s.group (2))
+
+ return result
+
+def expand_subfeatures_in_conditions(properties):
+
+ result = []
+ for p in properties:
+
+ s = __re_separate_condition_and_property.match(p)
+ if not s:
+ result.append(p)
+ else:
+ condition = s.group(1)
+ # Condition might include several elements
+ condition = condition.split(",")
+ value = s.group(2)
+
+ e = []
+ for c in condition:
+ # It common that condition includes a toolset which
+ # was never defined, or mentiones subfeatures which
+ # were never defined. In that case, validation will
+ # only produce an spirious error, so prevent
+ # validation by passing 'true' as second parameter.
+ e.extend(feature.expand_subfeatures(c, dont_validate=True))
+
+ if e == condition:
+ result.append(p)
+ else:
+ individual_subfeatures = set.difference(e, condition)
+ result.append(",".join(individual_subfeatures) + ":" + value)
+
+ return result
+
+def change (properties, feature, value = None):
+ """ Returns a modified version of properties with all values of the
+ given feature replaced by the given value.
+ If 'value' is None the feature will be removed.
+ """
+ result = []
+
+ feature = add_grist (feature)
+
+ for p in properties:
+ if get_grist (p) == feature:
+ if value:
+ result.append (replace_grist (value, feature))
+
+ else:
+ result.append (p)
+
+ return result
+
+
+################################################################
+# Private functions
+
+def __validate1 (property):
+ """ Exit with error if property is not valid.
+ """
+ msg = None
+
+ f = get_grist (property)
+ if f:
+ value = get_value (property)
+
+ if not feature.valid (f):
+ f = ungrist (get_grist (property)) # Ungrist for better error messages
+ msg = "Unknown feature '%s'" % f
+
+ elif value and not 'free' in feature.attributes (f):
+ feature.validate_value_string (f, value)
+
+ elif not value:
+ f = ungrist (get_grist (property)) # Ungrist for better error messages
+ msg = "No value specified for feature '%s'" % f
+
+ else:
+ f = feature.implied_feature (property)
+ feature.validate_value_string (f, property)
+
+ if msg:
+ # FIXME: don't use globals like this. Import here to
+ # break circular dependency.
+ from b2.manager import get_manager
+ get_manager().errors()("Invalid property '%s': %s" % (property, msg))
+
+
+###################################################################
+# Still to port.
+# Original lines are prefixed with "# "
+#
+#
+# import utility : ungrist ;
+# import sequence : unique ;
+# import errors : error ;
+# import feature ;
+# import regex ;
+# import sequence ;
+# import set ;
+# import path ;
+# import assert ;
+#
+#
+
+
+# rule validate-property-sets ( property-sets * )
+# {
+# for local s in $(property-sets)
+# {
+# validate [ feature.split $(s) ] ;
+# }
+# }
+#
+
+def remove(attributes, properties):
+ """Returns a property sets which include all the elements
+ in 'properties' that do not have attributes listed in 'attributes'."""
+
+ result = []
+ for e in properties:
+ attributes_new = feature.attributes(get_grist(e))
+ has_common_features = 0
+ for a in attributes_new:
+ if a in attributes:
+ has_common_features = 1
+ break
+
+ if not has_common_features:
+ result += e
+
+ return result
+
+
+def take(attributes, properties):
+ """Returns a property set which include all
+ properties in 'properties' that have any of 'attributes'."""
+ result = []
+ for e in properties:
+ if set.intersection(attributes, feature.attributes(get_grist(e))):
+ result.append(e)
+ return result
+
+
+class PropertyMap:
+ """ Class which maintains a property set -> string mapping.
+ """
+ def __init__ (self):
+ self.__properties = []
+ self.__values = []
+
+ def insert (self, properties, value):
+ """ Associate value with properties.
+ """
+ self.__properties.append(properties)
+ self.__values.append(value)
+
+ def find (self, properties):
+ """ Return the value associated with properties
+ or any subset of it. If more than one
+ subset has value assigned to it, return the
+ value for the longest subset, if it's unique.
+ """
+ return self.find_replace (properties)
+
+ def find_replace(self, properties, value=None):
+ matches = []
+ match_ranks = []
+
+ for i in range(0, len(self.__properties)):
+ p = self.__properties[i]
+
+ if set.contains (p, properties):
+ matches.append (i)
+ match_ranks.append(len(p))
+
+ best = sequence.select_highest_ranked (matches, match_ranks)
+
+ if not best:
+ return None
+
+ if len (best) > 1:
+ raise NoBestMatchingAlternative ()
+
+ best = best [0]
+
+ original = self.__values[best]
+
+ if value:
+ self.__values[best] = value
+
+ return original
+
+# local rule __test__ ( )
+# {
+# import errors : try catch ;
+# import feature ;
+# import feature : feature subfeature compose ;
+#
+# # local rules must be explicitly re-imported
+# import property : path-order ;
+#
+# feature.prepare-test property-test-temp ;
+#
+# feature toolset : gcc : implicit symmetric ;
+# subfeature toolset gcc : version : 2.95.2 2.95.3 2.95.4
+# 3.0 3.0.1 3.0.2 : optional ;
+# feature define : : free ;
+# feature runtime-link : dynamic static : symmetric link-incompatible ;
+# feature optimization : on off ;
+# feature variant : debug release : implicit composite symmetric ;
+# feature rtti : on off : link-incompatible ;
+#
+# compose <variant>debug : <define>_DEBUG <optimization>off ;
+# compose <variant>release : <define>NDEBUG <optimization>on ;
+#
+# import assert ;
+# import "class" : new ;
+#
+# validate <toolset>gcc <toolset>gcc-3.0.1 : $(test-space) ;
+#
+# assert.result <toolset>gcc <rtti>off <define>FOO
+# : refine <toolset>gcc <rtti>off
+# : <define>FOO
+# : $(test-space)
+# ;
+#
+# assert.result <toolset>gcc <optimization>on
+# : refine <toolset>gcc <optimization>off
+# : <optimization>on
+# : $(test-space)
+# ;
+#
+# assert.result <toolset>gcc <rtti>off
+# : refine <toolset>gcc : <rtti>off : $(test-space)
+# ;
+#
+# assert.result <toolset>gcc <rtti>off <rtti>off:<define>FOO
+# : refine <toolset>gcc : <rtti>off <rtti>off:<define>FOO
+# : $(test-space)
+# ;
+#
+# assert.result <toolset>gcc:<define>foo <toolset>gcc:<define>bar
+# : refine <toolset>gcc:<define>foo : <toolset>gcc:<define>bar
+# : $(test-space)
+# ;
+#
+# assert.result <define>MY_RELEASE
+# : evaluate-conditionals-in-context
+# <variant>release,<rtti>off:<define>MY_RELEASE
+# : <toolset>gcc <variant>release <rtti>off
+#
+# ;
+#
+# try ;
+# validate <feature>value : $(test-space) ;
+# catch "Invalid property '<feature>value': unknown feature 'feature'." ;
+#
+# try ;
+# validate <rtti>default : $(test-space) ;
+# catch \"default\" is not a known value of feature <rtti> ;
+#
+# validate <define>WHATEVER : $(test-space) ;
+#
+# try ;
+# validate <rtti> : $(test-space) ;
+# catch "Invalid property '<rtti>': No value specified for feature 'rtti'." ;
+#
+# try ;
+# validate value : $(test-space) ;
+# catch "value" is not a value of an implicit feature ;
+#
+#
+# assert.result <rtti>on
+# : remove free implicit : <toolset>gcc <define>foo <rtti>on : $(test-space) ;
+#
+# assert.result <include>a
+# : select include : <include>a <toolset>gcc ;
+#
+# assert.result <include>a
+# : select include bar : <include>a <toolset>gcc ;
+#
+# assert.result <include>a <toolset>gcc
+# : select include <bar> <toolset> : <include>a <toolset>gcc ;
+#
+# assert.result <toolset>kylix <include>a
+# : change <toolset>gcc <include>a : <toolset> kylix ;
+#
+# # Test ordinary properties
+# assert.result
+# : split-conditional <toolset>gcc
+# ;
+#
+# # Test properties with ":"
+# assert.result
+# : split-conditional <define>FOO=A::B
+# ;
+#
+# # Test conditional feature
+# assert.result <toolset>gcc,<toolset-gcc:version>3.0 <define>FOO
+# : split-conditional <toolset>gcc,<toolset-gcc:version>3.0:<define>FOO
+# ;
+#
+# feature.finish-test property-test-temp ;
+# }
+#
+

Added: trunk/tools/build/v2/build/property_set.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/build/property_set.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,368 @@
+# Status: ported.
+# Base revision: 40480
+
+# Copyright (C) Vladimir Prus 2002. Permission to copy, use, modify, sell and
+# distribute this software is granted provided this copyright notice appears in
+# all copies. This software is provided "as is" without express or implied
+# warranty, and with no claim as to its suitability for any purpose.
+
+from b2.util.utility import *
+import property, feature, string
+from b2.exceptions import *
+from b2.util.sequence import unique
+from b2.util.set import difference
+
+def reset ():
+ """ Clear the module state. This is mainly for testing purposes.
+ """
+ global __cache
+
+ # A cache of property sets
+ # TODO: use a map of weak refs?
+ __cache = {}
+
+reset ()
+
+
+def create (raw_properties = []):
+ """ Creates a new 'PropertySet' instance for the given raw properties,
+ or returns an already existing one.
+ """
+ raw_properties.sort ()
+ raw_properties = unique (raw_properties)
+
+ key = '-'.join (raw_properties)
+
+ if not __cache.has_key (key):
+ __cache [key] = PropertySet (raw_properties)
+
+ return __cache [key]
+
+def create_with_validation (raw_properties):
+ """ Creates new 'PropertySet' instances after checking
+ that all properties are valid and converting incidental
+ properties into gristed form.
+ """
+ property.validate (raw_properties)
+
+ return create (property.make (raw_properties))
+
+def empty ():
+ """ Returns PropertySet with empty set of properties.
+ """
+ return create ()
+
+def create_from_user_input(raw_properties, jamfile_module, location):
+ """Creates a property-set from the input given by the user, in the
+ context of 'jamfile-module' at 'location'"""
+
+ property.validate(raw_properties)
+
+ specification = property.translate_paths(raw_properties, location)
+ specification = property.translate_indirect(specification, jamfile_module)
+ specification = property.expand_subfeatures_in_conditions(specification)
+ specification = property.make(specification)
+ return create(specification)
+
+
+def refine_from_user_input(parent_requirements, specification, jamfile_module,
+ location):
+ """Refines requirements with requirements provided by the user.
+ Specially handles "-<property>value" syntax in specification
+ to remove given requirements.
+ - parent-requirements -- property-set object with requirements
+ to refine
+ - specification -- string list of requirements provided by the use
+ - project-module -- the module to which context indirect features
+ will be bound.
+ - location -- the path to which path features are relative."""
+
+
+ if not specification:
+ return parent_requirements
+
+
+ add_requirements = []
+ remove_requirements = []
+
+ for r in specification:
+ if r[0] == '-':
+ remove_requirements.append(r[1:])
+ else:
+ add_requirements.append(r)
+
+ if remove_requirements:
+ # Need to create property set, so that path features
+ # and indirect features are translated just like they
+ # are in project requirements.
+ ps = create_from_user_input(remove_requirements,
+ jamfile_module, location)
+
+ parent_requirements = create(difference(parent_requirements.raw(),
+ ps.raw()))
+ specification = add_requirements
+
+ requirements = create_from_user_input(specification,
+ jamfile_module, location)
+
+ return parent_requirements.refine(requirements)
+
+class PropertySet:
+ """ Class for storing a set of properties.
+ - there's 1<->1 correspondence between identity and value. No
+ two instances of the class are equal. To maintain this property,
+ the 'PropertySet.create' rule should be used to create new instances.
+ Instances are immutable.
+
+ - each property is classified with regard to it's effect on build
+ results. Incidental properties have no effect on build results, from
+ Boost.Build point of view. Others are either free, or non-free, which we
+ call 'base'. Each property belong to exactly one of those categories and
+ it's possible to get list of properties in each category.
+
+ In addition, it's possible to get list of properties with specific
+ attribute.
+
+ - several operations, like and refine and as_path are provided. They all use
+ caching whenever possible.
+ """
+ def __init__ (self, raw_properties = []):
+
+ self.raw_ = raw_properties
+
+ self.incidental_ = []
+ self.free_ = []
+ self.base_ = []
+ self.dependency_ = []
+ self.non_dependency_ = []
+ self.conditional_ = []
+ self.non_conditional_ = []
+ self.propagated_ = []
+ self.link_incompatible = []
+
+ # A cache of refined properties.
+ self.refined_ = {}
+
+ # A cache of property sets created by adding properties to this one.
+ self.added_ = {}
+
+ # Cache for the default properties.
+ self.defaults_ = None
+
+ # Cache for the expanded properties.
+ self.expanded_ = None
+
+ # Cache for the expanded composite properties
+ self.composites_ = None
+
+ # Cache for the property set containing propagated properties.
+ self.propagated_ps_ = None
+
+ # A map of features to its values.
+ self.feature_map_ = None
+
+ # A tuple (target path, is relative to build directory)
+ self.target_path_ = None
+
+ self.as_path_ = None
+
+ # A cache for already evaluated sets.
+ self.evaluated_ = {}
+
+ for p in raw_properties:
+ if not get_grist (p):
+ raise BaseException ("Invalid property: '%s'" % p)
+
+ att = feature.attributes (get_grist (p))
+
+ # A feature can be both incidental and free,
+ # in which case we add it to incidental.
+ if 'incidental' in att:
+ self.incidental_.append (p)
+ elif 'free' in att:
+ self.free_.append (p)
+ else:
+ self.base_.append (p)
+
+ if 'dependency' in att:
+ self.dependency_.append (p)
+ else:
+ self.non_dependency_.append (p)
+
+ if property.is_conditional (p):
+ self.conditional_.append (p)
+ else:
+ self.non_conditional_.append (p)
+
+ if 'propagated' in att:
+ self.propagated_.append (p)
+
+ if 'link_incompatible' in att:
+ self.link_incompatible.append (p)
+
+ def raw (self):
+ """ Returns the list of stored properties.
+ """
+ return self.raw_
+
+ def __str__(self):
+ return string.join(self.raw_)
+
+ def base (self):
+ """ Returns properties that are neither incidental nor free.
+ """
+ return self.base_
+
+ def free (self):
+ """ Returns free properties which are not dependency properties.
+ """
+ return self.free_
+
+ def dependency (self):
+ """ Returns dependency properties.
+ """
+ return self.dependency_
+
+ def non_dependency (self):
+ """ Returns properties that are not dependencies.
+ """
+ return self.non_dependency_
+
+ def conditional (self):
+ """ Returns conditional properties.
+ """
+ return self.conditional_
+
+ def non_conditional (self):
+ """ Returns properties that are not conditional.
+ """
+ return self.non_conditional_
+
+ def incidental (self):
+ """ Returns incidental properties.
+ """
+ return self.incidental_
+
+ def refine (self, requirements):
+ """ Refines this set's properties using the requirements passed as an argument.
+ """
+ str_req = str (requirements)
+ if not self.refined_.has_key (str_req):
+ r = property.refine (self.raw (), requirements.raw ())
+
+ self.refined_ [str_req] = create (r)
+
+ return self.refined_ [str_req]
+
+ def expand (self):
+ if not self.expanded_:
+ expanded = feature.expand (self.raw_)
+ self.expanded_ = create (expanded)
+ return self.expanded_
+
+ def expand_componsite(self):
+ if not self.componsites_:
+ self.composites_ = create(feature.expand_composires(self.raw_))
+ return self.composites_
+
+ def evaluate_conditionals(self, context=None):
+ if not context:
+ context = self
+
+ if not self.evaluated_.has_key(context):
+ self.evaluated_[context] = create(
+ property.evaluate_conditionals_in_context(self.raw_,
+ context.raw()))
+
+ return self.evaluated_[context]
+
+ def propagated (self):
+ if not self.propagated_ps_:
+ self.propagated_ps_ = create (self.propagated_)
+ return self.propagated_ps_
+
+ def add_defaults (self):
+ if not self.defaults_:
+ expanded = feature.add_defaults(self.raw_)
+ self.defaults_ = create(expanded)
+ return self.defaults_
+
+ def as_path (self):
+ if not self.as_path_:
+ self.as_path_ = property.as_path(self.base_)
+
+ return self.as_path_
+
+ def target_path (self):
+ """ Computes the target path that should be used for
+ target with these properties.
+ Returns a tuple of
+ - the computed path
+ - if the path is relative to build directory, a value of
+ 'true'.
+ """
+ if not self.target_path_:
+ # The <location> feature can be used to explicitly
+ # change the location of generated targets
+ l = self.get ('<location>')
+ if l:
+ computed = l
+ is_relative = False
+
+ else:
+ p = self.as_path ()
+
+ # Really, an ugly hack. Boost regression test system requires
+ # specific target paths, and it seems that changing it to handle
+ # other directory layout is really hard. For that reason,
+ # we teach V2 to do the things regression system requires.
+ # The value o '<location-prefix>' is predended to the path.
+ prefix = self.get ('<location-prefix>')
+
+ if prefix:
+ if len (prefix) > 1:
+ raise AlreadyDefined ("Two <location-prefix> properties specified: '%s'" % prefix)
+
+ computed = os.path.join(prefix[0], p)
+
+ else:
+ computed = p
+
+ if not computed:
+ computed = "."
+
+ is_relative = True
+
+ self.target_path_ = (computed, is_relative)
+
+ return self.target_path_
+
+ def add (self, ps):
+ """ Creates a new property set containing the properties in this one,
+ plus the ones of the property set passed as argument.
+ """
+ if not self.added_.has_key (str (ps)):
+ self.added_ [str (ps)] = create (self.raw_ + ps.raw ())
+ return self.added_ [str (ps)]
+
+ def add_raw (self, properties):
+ """ Creates a new property set containing the properties in this one,
+ plus the ones passed as argument.
+ """
+ return self.add (create (properties))
+
+
+ def get (self, feature):
+ """ Returns all values of 'feature'.
+ """
+ if not self.feature_map_:
+ self.feature_map_ = {}
+
+ for v in self.raw_:
+ key = get_grist (v)
+ if not self.feature_map_.has_key (key):
+ self.feature_map_ [key] = []
+ self.feature_map_ [get_grist (v)].append (replace_grist (v, ''))
+
+ return self.feature_map_.get (feature, [])
+

Added: trunk/tools/build/v2/build/scanner.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/build/scanner.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,157 @@
+# Status: ported.
+# Base revision: 45462
+#
+# Copyright 2003 Dave Abrahams
+# Copyright 2002, 2003, 2004, 2005 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)
+
+# Implements scanners: objects that compute implicit dependencies for
+# files, such as includes in C++.
+#
+# Scanner has a regular expression used to find dependencies, some
+# data needed to interpret those dependencies (for example, include
+# paths), and a code which actually established needed relationship
+# between actual jam targets.
+#
+# Scanner objects are created by actions, when they try to actualize
+# virtual targets, passed to 'virtual-target.actualize' method and are
+# then associated with actual targets. It is possible to use
+# several scanners for a virtual-target. For example, a single source
+# might be used by to compile actions, with different include paths.
+# In this case, two different actual targets will be created, each
+# having scanner of its own.
+#
+# Typically, scanners are created from target type and action's
+# properties, using the rule 'get' in this module. Directly creating
+# scanners is not recommended, because it might create many equvivalent
+# but different instances, and lead in unneeded duplication of
+# actual targets. However, actions can also create scanners in a special
+# way, instead of relying on just target type.
+
+import property
+import bjam
+from b2.exceptions import *
+from b2.manager import get_manager
+
+def reset ():
+ """ Clear the module state. This is mainly for testing purposes.
+ """
+ global __scanners, __rv_cache, __scanner_cache
+
+ # Maps registered scanner classes to relevant properties
+ __scanners = {}
+
+ # A cache of scanners.
+ # The key is: class_name.properties_tag, where properties_tag is the concatenation
+ # of all relevant properties, separated by '-'
+ __scanner_cache = {}
+
+reset ()
+
+
+def register(scanner_class, relevant_properties):
+ """ Registers a new generator class, specifying a set of
+ properties relevant to this scanner. Ctor for that class
+ should have one parameter: list of properties.
+ """
+ __scanners[str(scanner_class)] = relevant_properties
+
+def registered(scanner_class):
+ """ Returns true iff a scanner of that class is registered
+ """
+ return __scanners.has_key(str(scanner_class))
+
+def get(scanner_class, properties):
+ """ Returns an instance of previously registered scanner
+ with the specified properties.
+ """
+ scanner_name = str(scanner_class)
+
+ if not registered(scanner_name):
+ raise BaseException ("attempt to get unregisted scanner: %s" % scanner_name)
+
+ relevant_properties = __scanners[scanner_name]
+ r = property.select(relevant_properties, properties)
+
+ scanner_id = scanner_name + '.' + '-'.join(r)
+
+ if not __scanner_cache.has_key(scanner_name):
+ __scanner_cache[scanner_name] = scanner_class(r)
+
+ return __scanner_cache[scanner_name]
+
+class Scanner:
+ """ Base scanner class.
+ """
+ def __init__ (self):
+ pass
+
+ def pattern (self):
+ """ Returns a pattern to use for scanning.
+ """
+ raise BaseException ("method must be overriden")
+
+ def process (self, target, matches):
+ """ Establish necessary relationship between targets,
+ given actual target beeing scanned, and a list of
+ pattern matches in that file.
+ """
+ raise BaseException ("method must be overriden")
+
+
+# Common scanner class, which can be used when there's only one
+# kind of includes (unlike C, where "" and <> includes have different
+# search paths).
+def CommonScanner(Scanner):
+
+ def __init__ (self, includes):
+ Scanner.__init__(self)
+ self.includes = includes
+
+ def process(self, target, matches, binding):
+
+ target_path = os.path.normpath(os.path.dirname(binding[0]))
+ bjam.call("mark-included", target, matches)
+
+ engine.set_target_variable(matches, "SEARCH",
+ [target_path] + self.includes_)
+ get_manager().scanners().propagate(self, matches)
+
+class ScannerRegistry:
+
+ def __init__ (self, manager):
+ self.manager_ = manager
+ self.count_ = 0
+ self.exported_scanners_ = {}
+
+ def install (self, scanner, target, vtarget):
+ """ Installs the specified scanner on actual target 'target'.
+ vtarget: virtual target from which 'target' was actualized.
+ """
+ engine = self.manager_.engine()
+ engine.set_target_variable(target, "HDRSCAN", scanner.pattern())
+ if not self.exported_scanners_.has_key(scanner):
+ exported_name = "scanner_" + str(self.count_)
+ self.count_ = self.count_ + 1
+ self.exported_scanners_[scanner] = exported_name
+ bjam.import_rule("", exported_name, scanner.process)
+ else:
+ exported_name = self.exported_scanners_[scanner]
+
+ engine.set_target_variable(target, "HDRRULE", exported_name)
+
+ # scanner reflects difference in properties affecting
+ # binding of 'target', which will be known when processing
+ # includes for it, will give information on how to
+ # interpret quoted includes.
+ engine.set_target_variable(target, "HDRGRIST", str(id(scanner)))
+ pass
+
+ def propagate(self, scanner, targets):
+ engine = self.manager_.engine()
+ engine.set_target_variable(targets, "HDRSCAN", scanner.pattern())
+ engine.set_target_variable(targets, "HDRRULE",
+ self.exported_scanners_[scanner])
+ engine.set_target_variable(targets, "HDRGRIST", str(id(scanner)))
+

Added: trunk/tools/build/v2/build/targets.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/build/targets.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,1264 @@
+# Status: being ported by Vladimir Prus
+# Still to do: call toolset.requirements when those are ported.
+# Remember the location of target.
+# Base revision: 40480
+
+# Copyright Vladimir Prus 2002-2007.
+# Copyright Rene Rivera 2006.
+#
+# Distributed under the Boost Software License, Version 1.0.
+# (See accompanying file LICENSE_1_0.txt or copy at
+# http://www.boost.org/LICENSE_1_0.txt)
+
+# Supports 'abstract' targets, which are targets explicitly defined in Jamfile.
+#
+# Abstract targets are represented by classes derived from 'AbstractTarget' class.
+# The first abstract target is 'project_target', which is created for each
+# Jamfile, and can be obtained by the 'target' rule in the Jamfile's module.
+# (see project.jam).
+#
+# Project targets keep a list of 'MainTarget' instances.
+# A main target is what the user explicitly defines in a Jamfile. It is
+# possible to have several definitions for a main target, for example to have
+# different lists of sources for different platforms. So, main targets
+# keep a list of alternatives.
+#
+# Each alternative is an instance of 'AbstractTarget'. When a main target
+# subvariant is defined by some rule, that rule will decide what class to
+# use, create an instance of that class and add it to the list of alternatives
+# for the main target.
+#
+# Rules supplied by the build system will use only targets derived
+# from 'BasicTarget' class, which will provide some default behaviour.
+# There will be two classes derived from it, 'make-target', created by the
+# 'make' rule, and 'TypedTarget', created by rules such as 'exe' and 'dll'.
+
+#
+# +------------------------+
+# |AbstractTarget |
+# +========================+
+# |name |
+# |project |
+# | |
+# |generate(properties) = 0|
+# +-----------+------------+
+# |
+# ^
+# / \
+# +-+-+
+# |
+# |
+# +------------------------+------+------------------------------+
+# | | |
+# | | |
+# +----------+-----------+ +------+------+ +------+-------+
+# | project_target | | MainTarget | | BasicTarget |
+# +======================+ 1 * +=============+ alternatives +==============+
+# | generate(properties) |o-----------+ generate |<>------------->| generate |
+# | main-target | +-------------+ | construct = 0|
+# +----------------------+ +--------------+
+# |
+# ^
+# / \
+# +-+-+
+# |
+# |
+# ...--+----------------+------------------+----------------+---+
+# | | | |
+# | | | |
+# ... ---+-----+ +------+-------+ +------+------+ +--------+-----+
+# | | TypedTarget | | make-target | | stage-target |
+# . +==============+ +=============+ +==============+
+# . | construct | | construct | | construct |
+# +--------------+ +-------------+ +--------------+
+
+import re
+import os.path
+import sys
+
+from b2.util.utility import *
+import property, project, virtual_target, property_set, feature, generators
+from virtual_target import Subvariant
+from b2.exceptions import *
+from b2.util.sequence import unique
+from b2.util import set, path
+from b2.build.errors import user_error_checkpoint
+
+_re_separate_target_from_properties = re.compile (r'^([^<]*)(/(<.*))?$')
+
+class TargetRegistry:
+
+ def __init__ (self):
+ # All targets that are currently being built.
+ # Only the key is id (target), the value is the actual object.
+ self.targets_being_built_ = {}
+
+ # Current indent for debugging messages
+ self.indent_ = ""
+
+ self.debug_building_ = "--debug-building" in bjam.variable("ARGV")
+
+ def main_target_alternative (self, target):
+ """ Registers the specified target as a main target alternatives.
+ Returns 'target'.
+ """
+ target.project ().add_alternative (target)
+ return target
+
+ def main_target_sources (self, sources, main_target_name, no_remaning=0):
+ """Return the list of sources to use, if main target rule is invoked
+ with 'sources'. If there are any objects in 'sources', they are treated
+ as main target instances, and the name of such targets are adjusted to
+ be '<name_of_this_target>__<name_of_source_target>'. Such renaming
+ is disabled is non-empty value is passed for 'no-renaming' parameter."""
+ result = []
+
+ for t in sources:
+ if isinstance (t, AbstractTarget):
+ name = t.name ()
+
+ if not no_renaming:
+ new_name = main_target_name + '__' + name
+ t.rename (new_name)
+
+ # Inline targets are not built by default.
+ p = t.project()
+ p.mark_target_as_explicit(name)
+ result.append (new_name)
+
+ else:
+ result.append (t)
+
+ return result
+
+
+ def main_target_requirements(self, specification, project):
+ """Returns the requirement to use when declaring a main target,
+ which are obtained by
+ - translating all specified property paths, and
+ - refining project requirements with the one specified for the target
+
+ 'specification' are the properties xplicitly specified for a
+ main target
+ 'project' is the project where the main taret is to be declared."""
+
+ # FIXME: revive after toolset.requirements are ported.
+ #specification.append(toolset.requirements)
+
+ requirements = property_set.refine_from_user_input(
+ project.get("requirements"), specification,
+ project.project_module, project.get("location"))
+
+ return requirements
+
+ def main_target_usage_requirements (self, specification, project):
+ """ Returns the use requirement to use when declaraing a main target,
+ which are obtained by
+ - translating all specified property paths, and
+ - adding project's usage requirements
+ specification: Use-properties explicitly specified for a main target
+ project: Project where the main target is to be declared
+ """
+ project_usage_requirements = project.get ('usage-requirements')
+
+ # We don't use 'refine-from-user-input' because I'm not sure if:
+ # - removing of parent's usage requirements makes sense
+ # - refining of usage requirements is not needed, since usage requirements
+ # are always free.
+ usage_requirements = property_set.create_from_user_input(
+ specification, project.project_module(), project.get("location"))
+
+ return project_usage_requirements.add (usage_requirements)
+
+ def main_target_default_build (self, specification, project):
+ """ Return the default build value to use when declaring a main target,
+ which is obtained by using specified value if not empty and parent's
+ default build attribute otherwise.
+ specification: Default build explicitly specified for a main target
+ project: Project where the main target is to be declared
+ """
+ if specification:
+ result = specification
+
+ else:
+ result = project.get ('default-build')
+
+ return property_set.create_with_validation (result)
+
+ def start_building (self, main_target_instance):
+ """ Helper rules to detect cycles in main target references.
+ """
+ if self.targets_being_built_.has_key(id(main_target_instance)):
+ names = []
+ for t in self.targets_being_built_.values():
+ names.append (t.full_name())
+
+ raise Recursion ("Recursion in main target references"
+ "the following target are being built currently: '%s'" % names)
+
+ self.targets_being_built_[id(main_target_instance)] = main_target_instance
+
+ def end_building (self, main_target_instance):
+ assert (self.targets_being_built_.has_key (id (main_target_instance)))
+ del self.targets_being_built_ [id (main_target_instance)]
+
+ def create_typed_target (self, type, project, name, sources, requirements, default_build, usage_requirements):
+ """ Creates a TypedTarget with the specified properties.
+ The 'name', 'sources', 'requirements', 'default_build' and
+ 'usage_requirements' are assumed to be in the form specified
+ by the user in Jamfile corresponding to 'project'.
+ """
+ return self.main_target_alternative (TypedTarget (name, project, type,
+ self.main_target_sources (sources, name),
+ self.main_target_requirements (requirements, project),
+ self.main_target_default_build (default_build, project),
+ self.main_target_usage_requirements (usage_requirements, project)))
+
+ def increase_indent(self):
+ self.indent_ += " "
+
+ def decrease_indent(self):
+ self.indent_ = self.indent_[0:-4]
+
+ def logging(self):
+ return self.debug_building_
+
+ def log(self, message):
+ if self.debug_building_:
+ print self.indent_ + message
+
+class GenerateResult:
+
+ def __init__ (self, ur=None, targets=None):
+ if not targets:
+ targets = []
+
+ self.__usage_requirements = ur
+ self.__targets = targets
+
+ if not self.__usage_requirements:
+ self.__usage_requirements = property_set.empty ()
+
+ def usage_requirements (self):
+ return self.__usage_requirements
+
+ def targets (self):
+ return self.__targets
+
+ def extend (self, other):
+ assert (isinstance (other, GenerateResult))
+
+ self.__usage_requirements = self.__usage_requirements.add (other.usage_requirements ())
+ self.__targets.extend (other.targets ())
+
+class AbstractTarget:
+ """ Base class for all abstract targets.
+ """
+ def __init__ (self, name, project, manager = None):
+ """ manager: the Manager object
+ name: name of the target
+ project: the project target to which this one belongs
+ manager:the manager object. If none, uses project.manager ()
+ """
+ assert (isinstance (project, ProjectTarget))
+ # Note: it might seem that we don't need either name or project at all.
+ # However, there are places where we really need it. One example is error
+ # messages which should name problematic targets. Another is setting correct
+ # paths for sources and generated files.
+
+ # Why allow manager to be specified? Because otherwise project target could not derive
+ # from this class.
+ if manager:
+ self.manager_ = manager
+ else:
+ self.manager_ = project.manager ()
+
+ self.name_ = name
+ self.project_ = project
+
+ def manager (self):
+ return self.manager_
+
+ def name (self):
+ """ Returns the name of this target.
+ """
+ return self.name_
+
+ def project (self):
+ """ Returns the project for this target.
+ """
+ return self.project_
+
+ def location (self):
+ """ Return the location where the target was declared.
+ """
+ return self.location_
+
+ def full_name (self):
+ """ Returns a user-readable name for this target.
+ """
+ location = self.project ().get ('location')
+ return location + '/' + self.name_
+
+ def generate (self, property_set):
+ """ Takes a property set. Generates virtual targets for this abstract
+ target, using the specified properties, unless a different value of some
+ feature is required by the target.
+ On success, returns a GenerateResult instance with:
+ - a property_set with the usage requirements to be
+ applied to dependents
+ - a list of produced virtual targets, which may be
+ empty.
+ If 'property_set' is empty, performs default build of this
+ target, in a way specific to derived class.
+ """
+ raise BaseException ("method should be defined in derived classes")
+
+ def rename (self, new_name):
+ self.name_ = new_name
+
+class ProjectTarget (AbstractTarget):
+ """ Project target class (derived from 'AbstractTarget')
+
+ This class these responsibilities:
+ - maintaining a list of main target in this project and
+ building it
+
+ Main targets are constructed in two stages:
+ - When Jamfile is read, a number of calls to 'add_alternative' is made.
+ At that time, alternatives can also be renamed to account for inline
+ targets.
+ - The first time 'main-target' or 'has-main-target' rule is called,
+ all alternatives are enumerated an main targets are created.
+ """
+ def __init__ (self, manager, name, project_module, parent_project, requirements, default_build):
+ AbstractTarget.__init__ (self, name, self, manager)
+
+ self.project_module_ = project_module
+ self.location_ = manager.projects().attribute (project_module, 'location')
+ self.requirements_ = requirements
+ self.default_build_ = default_build
+
+ self.build_dir_ = None
+
+ if parent_project:
+ self.inherit (parent_project)
+
+ # A cache of IDs
+ self.ids_cache_ = {}
+
+ # True is main targets have already been built.
+ self.built_main_targets_ = False
+
+ # A list of the registered alternatives for this project.
+ self.alternatives_ = []
+
+ # A map from main target name to the target corresponding
+ # to it.
+ self.main_target_ = {}
+
+ # Targets marked as explicit.
+ self.explicit_targets_ = []
+
+ # The constants defined for this project.
+ self.constants_ = {}
+
+ # Whether targets for all main target are already created.
+ self.built_main_targets_ = 0
+
+ # 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):
+ return self.project_module_
+
+ def get (self, attribute):
+ return self.manager().projects().attribute(
+ self.project_module_, attribute)
+
+ def build_dir (self):
+ if not self.build_dir_:
+ self.build_dir_ = self.get ('build-dir')
+ if not self.build_dir_:
+ self.build_dir_ = os.path.join (os.path.dirname(
+ self.project_.get ('location')), 'bin')
+
+ return self.build_dir_
+
+ def generate (self, ps):
+ """ Generates all possible targets contained in this project.
+ """
+ self.manager_.targets().log(
+ "Building project '%s' with '%s'" % (self.name (), ps.raw ()))
+ self.manager_.targets().increase_indent ()
+
+ result = GenerateResult ()
+
+ for t in self.targets_to_build ():
+ g = t.generate (ps)
+ result.extend (g)
+
+ self.manager_.targets().decrease_indent ()
+ return result
+
+ def targets_to_build (self):
+ """ Computes and returns a list of AbstractTarget instances which
+ must be built when this project is built.
+ """
+ result = []
+
+ if not self.built_main_targets_:
+ self.build_main_targets ()
+
+ # Collect all main targets here, except for "explicit" ones.
+ for n, t in self.main_target_.iteritems ():
+ if not t.name () in self.explicit_targets_:
+ result.append (t)
+
+ # Collect all projects referenced via "projects-to-build" attribute.
+ self_location = self.get ('location')
+ for pn in self.get ('projects-to-build'):
+ result.append (self.find(pn))
+
+ return result
+
+ def mark_target_as_explicit (self, target_name):
+ """Add 'target' to the list of targets in this project
+ that should be build only by explicit request."""
+
+ # Record the name of the target, not instance, since this
+ # rule is called before main target instaces are created.
+ self.explicit_.append(target_name)
+
+ def add_alternative (self, target_instance):
+ """ Add new target alternative.
+ """
+ if self.built_main_targets_:
+ raise IllegalOperation ("add-alternative called when main targets are already created for project '%s'" % self.full_name ())
+
+ self.alternatives_.append (target_instance)
+
+ def main_target (self, name):
+ if not self.built_main_targets_:
+ self.build_main_targets()
+
+ return self.main_target_[name]
+
+ def has_main_target (self, name):
+ """Tells if a main target with the specified name exists."""
+ if not self.built_main_targets_:
+ self.build_main_targets()
+
+ return self.main_target_.has_key(name)
+
+ def create_main_target (self, name):
+ """ Returns a 'MainTarget' class instance corresponding to the 'name'.
+ """
+ if not self.built_main_targets_:
+ self.build_main_targets ()
+
+ return self.main_targets_.get (name, None)
+
+
+ def find_really(self, id):
+ """ Find and return the target with the specified id, treated
+ relative to self.
+ """
+ result = None
+ current_location = self.get ('location')
+
+ __re_split_project_target = re.compile (r'(.*)//(.*)')
+ split = __re_split_project_target.match (id)
+
+ project_part = None
+ target_part = None
+
+ if split:
+ project_part = split.group (1)
+ target_part = split.group (2)
+
+ project_registry = self.project_.manager ().projects ()
+
+ extra_error_message = ''
+ if project_part:
+ # There's explicit project part in id. Looks up the
+ # project and pass the request to it.
+ pm = project_registry.find (project_part, current_location)
+
+ if pm:
+ project_target = project_registry.target (pm)
+ result = project_target.find (target_part, no_error=1)
+
+ else:
+ extra_error_message = "error: could not find project '$(project_part)'"
+
+ else:
+ # Interpret target-name as name of main target
+ # Need to do this before checking for file. Consider this:
+ #
+ # exe test : test.cpp ;
+ # install s : test : <location>. ;
+ #
+ # After first build we'll have target 'test' in Jamfile and file
+ # 'test' on the disk. We need target to override the file.
+
+ result = None
+ if self.has_main_target(id):
+ result = self.main_target(id)
+
+ if not result:
+ result = FileReference (self.manager_, id, self.project_)
+ if not result.exists ():
+ # File actually does not exist.
+ # Reset 'target' so that an error is issued.
+ result = None
+
+
+ if not result:
+ # Interpret id as project-id
+ project_module = project_registry.find (id, current_location)
+ if project_module:
+ result = project_registry.target (project_module)
+
+ return result
+
+ def find (self, id, no_error = False):
+ v = self.ids_cache_.get (id, None)
+
+ if not v:
+ v = self.find_really (id)
+ self.ids_cache_ [id] = v
+
+ if v or no_error:
+ return v
+
+ raise BaseException ("Unable to find file or target named '%s'\nreferred from project at '%s'" % (id, self.get ('location')))
+
+
+ def build_main_targets (self):
+ self.built_main_targets_ = True
+
+ for a in self.alternatives_:
+ name = a.name ()
+ if not self.main_target_.has_key (name):
+ t = MainTarget (name, self.project_)
+ self.main_target_ [name] = t
+
+ self.main_target_ [name].add_alternative (a)
+
+ def add_constant(self, name, value, path=0):
+ """Adds a new constant for this project.
+
+ The constant will be available for use in Jamfile
+ module for this project. If 'path' is true,
+ the constant will be interpreted relatively
+ to the location of project.
+ """
+
+ if path:
+ 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)
+
+ def inherit(self, parent_project):
+ 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])
+
+ # Import rules from parent
+ this_module = self.project_module()
+ parent_module = parent_project.project_module()
+
+ rules = bjam.call("RULENAMES", parent_module)
+ if not rules:
+ rules = []
+ user_rules = [x for x in rules
+ if x not in self.manager().projects().project_rules()]
+ if user_rules:
+ bjam.call("import-rules-from-parent", parent_module, this_module, user_rules)
+
+class MainTarget (AbstractTarget):
+ """ A named top-level target in Jamfile.
+ """
+ def __init__ (self, name, project):
+ AbstractTarget.__init__ (self, name, project)
+ self.alternatives_ = []
+ self.default_build_ = property_set.empty ()
+
+ def add_alternative (self, target):
+ """ Add a new alternative for this target.
+ """
+ d = target.default_build ()
+
+ if self.alternatives_ and self.default_build_ != d:
+ raise BaseException ("Default build must be identical in all alternatives\n"
+ "main target is '%s'\n"
+ "with '%s'\n"
+ "differing from previous default build: '%s'" % (full_name (), d.raw (), self.default_build_.raw ()))
+
+ else:
+ self.default_build_ = d
+
+ self.alternatives_.append (target)
+
+ def __select_alternatives (self, property_set, debug):
+ """ Returns the best viable alternative for this property_set
+ See the documentation for selection rules.
+ # TODO: shouldn't this be 'alternative' (singular)?
+ """
+ # When selecting alternatives we have to consider defaults,
+ # for example:
+ # lib l : l.cpp : <variant>debug ;
+ # lib l : l_opt.cpp : <variant>release ;
+ # won't work unless we add default value <variant>debug.
+ property_set = property_set.add_defaults ()
+
+ # The algorithm: we keep the current best viable alternative.
+ # When we've got new best viable alternative, we compare it
+ # with the current one.
+ best = None
+ best_properties = None
+
+ if len (self.alternatives_) == 0:
+ return None
+
+ if len (self.alternatives_) == 1:
+ return self.alternatives_ [0]
+
+ for v in self.alternatives_:
+ properties = v.match (property_set, debug)
+
+ if properties:
+ if not best:
+ best = v
+ best_properties = properties
+
+ else:
+ if set.equal (properties, best_properties):
+ return None
+
+ elif set.contains (properties, best_properties):
+ # Do nothing, this alternative is worse
+ pass
+
+ elif set.contains (best_properties, properties):
+ best = v
+ best_properties = properties
+
+ else:
+ return None
+
+ return best
+
+ def apply_default_build (self, property_set):
+ # 1. First, see what properties from default_build
+ # are already present in property_set.
+
+ raw = property_set.raw ()
+ specified_features = get_grist (raw)
+
+ defaults_to_apply = []
+ for d in self.default_build_.raw ():
+ if not get_grist (d) in specified_features:
+ defaults_to_apply.append (d)
+
+ # 2. If there's any defaults to be applied, form the new
+ # build request. Pass it throw 'expand-no-defaults', since
+ # default_build might contain "release debug", which will
+ # result in two property_sets.
+ result = []
+ if defaults_to_apply:
+
+ # We have to compress subproperties here to prevent
+ # property lists like:
+ #
+ # <toolset>msvc <toolset-msvc:version>7.1 <threading>multi
+ #
+ # from being expanded into:
+ #
+ # <toolset-msvc:version>7.1/<threading>multi
+ # <toolset>msvc/<toolset-msvc:version>7.1/<threading>multi
+ #
+ # due to cross-product property combination. That may
+ # be an indication that
+ # build_request.expand-no-defaults is the wrong rule
+ # to use here.
+ compressed = feature.compress-subproperties (raw)
+
+ properties = build_request.expand_no_defaults (compressed, defaults_to_apply)
+
+ if properties:
+ for p in properties:
+ result.append (property_set.create (feature.expand (feature.split (p))))
+
+ else:
+ result .append (property_set.empty ())
+
+ else:
+ result.append (property_set)
+
+ return result
+
+ def generate (self, ps):
+ """ Select an alternative for this main target, by finding all alternatives
+ which requirements are satisfied by 'properties' and picking the one with
+ longest requirements set.
+ Returns the result of calling 'generate' on that alternative.
+ """
+ self.manager_.targets ().start_building (self)
+
+ # We want composite properties in build request act as if
+ # all the properties it expands too are explicitly specified.
+ ps = ps.expand ()
+
+ all_property_sets = self.apply_default_build (ps)
+
+ result = GenerateResult ()
+
+ for p in all_property_sets:
+ result.extend (self.__generate_really (p))
+
+ self.manager_.targets ().end_building (self)
+
+ return result
+
+ def __generate_really (self, prop_set):
+ """ Generates the main target with the given property set
+ and returns a list which first element is property_set object
+ containing usage_requirements of generated target and with
+ generated virtual target in other elements. It's possible
+ that no targets are generated.
+ """
+ best_alternative = self.__select_alternatives (prop_set, debug=0)
+
+ if not best_alternative:
+ self.__select_alternatives(prop_set, debug=1)
+ raise NoBestMatchingAlternative (
+ "Failed to build '%s'\n"
+ "with properties '%s'\n"
+ "because no best-matching alternative could be found."
+ % (full_name, prop_set.raw ()))
+
+ result = best_alternative.generate (prop_set)
+
+ # Now return virtual targets for the only alternative
+ return result
+
+ def rename(self, new_name):
+ AbstractTarget.rename(self, new_name)
+ for a in self.alternatives_:
+ a.rename(new_name)
+
+class FileReference (AbstractTarget):
+ """ Abstract target which refers to a source file.
+ This is artificial creature; it's usefull so that sources to
+ a target can be represented as list of abstract target instances.
+ """
+ def __init__ (self, manager, file, project):
+ AbstractTarget.__init__ (self, file, project)
+ self.file_location_ = None
+
+ def generate (self, properties):
+ return GenerateResult (None, [
+ self.manager_.virtual_targets ().from_file (
+ self.name_, self.location(), self.project_) ])
+
+ def exists (self):
+ """ Returns true if the referred file really exists.
+ """
+ if self.location ():
+ return True
+ else:
+ return False
+
+ def location (self):
+ # Returns the location of target. Needed by 'testing.jam'
+ if not self.file_location_:
+ source_location = self.project_.get ('source-location')
+
+ for src_dir in source_location:
+ location = os.path.join(src_dir, self.name())
+ if os.path.isfile(location):
+ self.file_location_ = src_dir
+ self.file_path = location
+ break
+
+ return self.file_location_
+
+class BasicTarget (AbstractTarget):
+ """ Implements the most standard way of constructing main target
+ alternative from sources. Allows sources to be either file or
+ other main target and handles generation of those dependency
+ targets.
+ """
+ def __init__ (self, name, project, sources, requirements = None, default_build = None, usage_requirements = None):
+ AbstractTarget.__init__ (self, name, project)
+
+ for s in sources:
+ if get_grist (s):
+ raise InvalidSource ("property '%s' found in the 'sources' parameter for '%s'" (s, name))
+
+ self.sources_ = sources
+
+ if not requirements: requirements = property_set.empty ()
+ self.requirements_ = requirements
+
+ if not default_build: default_build = property_set.empty ()
+ self.default_build_ = default_build
+
+ if not usage_requirements: usage_requirements = property_set.empty ()
+ self.usage_requirements_ = usage_requirements
+
+ # A cache for resolved references
+ self.source_targets_ = None
+
+ # A cache for generated targets
+ self.generated_ = {}
+
+ # A cache for build requests
+ self.request_cache = {}
+
+ self.user_context_ = self.manager_.errors().capture_user_context()
+
+ def sources (self):
+ """ Returns the list of AbstractTargets which are used as sources.
+ The extra properties specified for sources are not represented.
+ The only used of this rule at the moment is the '--dump-test'
+ feature of the test system.
+ """
+ if self.source_targets_ == None:
+ self.source_targets_ = []
+ for s in self.sources_:
+ self.source_targets_.append (self.resolve_reference (s, self.project_))
+
+ return self.source_targets_
+
+ def requirements (self):
+ return self.requirements_
+
+ def default_build (self):
+ return self.default_build_
+
+ def resolve_reference (self, target_reference, project):
+ """ Given a target_reference, made in context of 'project',
+ returns the AbstractTarget instance that is referred to, as well
+ as properties explicitly specified for this reference.
+ """
+ # Separate target name from properties override
+ split = _re_separate_target_from_properties.match (target_reference)
+ if not split:
+ raise BaseException ("Invalid reference: '%s'" % target_reference)
+
+ id = split.group (1)
+
+ sproperties = []
+
+ if split.group (3):
+ sproperties = property.make (feature.split (split.group (3)))
+ sproperties = self.manager.features ().expand_composites (sproperties)
+
+ # Find the target
+ target = project.find (id)
+
+ return (target, property_set.create (sproperties))
+
+ def common_properties (self, build_request, requirements):
+ """ Given build request and requirements, return properties
+ common to dependency build request and target build
+ properties.
+ """
+ # For optimization, we add free requirements directly,
+ # without using complex algorithsm.
+ # This gives the complex algorithm better chance of caching results.
+ free = requirements.free ()
+ non_free = property_set.create (requirements.base () + requirements.incidental ())
+
+ key = str (build_request) + '-' + str (non_free)
+ if not self.request_cache.has_key (key):
+ self.request_cache [key] = self.__common_properties2 (build_request, non_free)
+
+ return self.request_cache [key].add_raw (free)
+
+ # Given 'context' -- a set of already present properties, and 'requirements',
+ # decide which extra properties should be applied to 'context'.
+ # For conditional requirements, this means evaluating condition. For
+ # indirect conditional requirements, this means calling a rule. Ordinary
+ # requirements are always applied.
+ #
+ # Handles situation where evaluating one conditional requirements affects
+ # condition of another conditional requirements, for example:
+ #
+ # <toolset>gcc:<variant>release <variant>release:<define>RELEASE
+ #
+ # If 'what' is 'refined' returns context refined with new requirements.
+ # If 'what' is 'added' returns just the requirements that must be applied.
+ def evaluate_requirements(self, requirements, context, what):
+ # Apply non-conditional requirements.
+ # It's possible that that further conditional requirement change
+ # a value set by non-conditional requirements. For example:
+ #
+ # exe a : a.cpp : <threading>single <toolset>foo:<threading>multi ;
+ #
+ # I'm not sure if this should be an error, or not, especially given that
+ #
+ # <threading>single
+ #
+ # might come from project's requirements.
+
+ unconditional = feature.expand(requirements.non_conditional())
+
+ raw = context.raw()
+ raw = property.refine(raw, unconditional)
+
+ # We've collected properties that surely must be present in common
+ # properties. We now try to figure out what other properties
+ # should be added in order to satisfy rules (4)-(6) from the docs.
+
+ conditionals = requirements.conditional()
+
+ # It's supposed that #conditionals iterations
+ # should be enough for properties to propagate along conditions in any
+ # direction.
+ max_iterations = len(conditionals) +\
+ len(requirements.get("<conditional>")) + 1
+
+ added_requirements = []
+ current = raw
+
+ # It's assumed that ordinary conditional requirements can't add
+ # <indirect-conditional> properties, and that rules referred
+ # by <indirect-conditional> properties can't add new
+ # <indirect-conditional> properties. So the list of indirect conditionals
+ # does not change.
+ indirect = requirements.get("<conditional>")
+ indirect = [s[1:] for s in indirect]
+
+ ok = 0
+ for i in range(0, max_iterations):
+
+ e = property.evaluate_conditionals_in_context(conditionals, current)
+
+ # Evaluate indirect conditionals.
+ for i in indirect:
+ e.extend(bjam.call(i, current))
+
+ if e == added_requirements:
+ # If we got the same result, we've found final properties.
+ ok = 1
+ break
+ else:
+ # Oops, results of evaluation of conditionals has changed.
+ # Also 'current' contains leftover from previous evaluation.
+ # Recompute 'current' using initial properties and conditional
+ # requirements.
+ added_requirements = e
+ current = property.refine(raw, feature.expand(e))
+
+ if not ok:
+ self.manager().errors()("Can't evaluate conditional properties "
+ + str(conditionals))
+
+
+ if what == "added":
+ return property_set.create(unconditional + added_requirements)
+ elif what == "refined":
+ return property_set.create(current)
+ else:
+ self.manager().errors("Invalid value of the 'what' parameter")
+
+ def __common_properties2(self, build_request, requirements):
+ # This guarantees that default properties are present
+ # in result, unless they are overrided by some requirement.
+ # TODO: There is possibility that we've added <foo>bar, which is composite
+ # and expands to <foo2>bar2, but default value of <foo2> is not bar2,
+ # in which case it's not clear what to do.
+ #
+ build_request = build_request.add_defaults()
+ # Featured added by 'add-default' can be composite and expand
+ # to features without default values -- so they are not added yet.
+ # It could be clearer/faster to expand only newly added properties
+ # but that's not critical.
+ build_request = build_request.expand()
+
+ return self.evaluate_requirements(requirements, build_request,
+ "refined")
+
+ def match (self, property_set, debug):
+ """ Returns the alternative condition for this alternative, if
+ the condition is satisfied by 'property_set'.
+ """
+ # The condition is composed of all base non-conditional properties.
+ # It's not clear if we should expand 'self.requirements_' or not.
+ # For one thing, it would be nice to be able to put
+ # <toolset>msvc-6.0
+ # in requirements.
+ # On the other hand, if we have <variant>release in condition it
+ # does not make sense to require <optimization>full to be in
+ # build request just to select this variant.
+ bcondition = self.requirements_.base ()
+ ccondition = self.requirements_.conditional ()
+ condition = set.difference (bcondition, ccondition)
+
+ if debug:
+ print " next alternative: required properties:", str(condition)
+
+ if set.contains (condition, property_set.raw ()):
+
+ if debug:
+ print " matched"
+
+ return condition
+
+ else:
+ return None
+
+ def generate_dependencies (self, dependencies, property_set):
+ """ Takes a target reference, which might be either target id
+ or a dependency property, and generates that target using
+ 'property_set' as build request.
+
+ Returns a tuple (result, usage_requirements).
+ """
+ result_var = []
+ usage_requirements = []
+ for dependency in dependencies:
+ grist = get_grist (dependency)
+ id = replace_grist (dependency, '')
+
+ result = self.generate_from_reference (id, self.project_, property_set)
+
+ # FIXME:
+ # TODO: this is a problem: the grist must be kept and the value
+ # is the object itself. This won't work in python.
+ targets = [ self.manager_.register_object (x) for x in result.targets () ]
+
+ result_var += replace_grist (targets, grist)
+ usage_requirements += result.usage_requirements ().raw ()
+
+ return (result_var, usage_requirements)
+
+ @user_error_checkpoint
+ def generate (self, ps):
+ """ Determines final build properties, generates sources,
+ and calls 'construct'. This method should not be
+ overridden.
+ """
+ self.manager_.errors().push_user_context(
+ "Generating target " + self.full_name(), self.user_context_)
+
+ if self.manager().targets().logging():
+ self.manager().targets().log(
+ "Building target '%s'" % self.name_)
+ self.manager().targets().increase_indent ()
+ self.manager().targets().log(
+ "Build request: '%s'" % str (ps.raw ()))
+ cf = self.manager().command_line_free_features()
+ self.manager().targets().log(
+ "Command line free features: '%s'" % str (cf.raw ()))
+ self.manager().targets().log(
+ "Target requirements: %s'" % str (self.requirements().raw ()))
+
+ if not self.generated_.has_key (str (ps)):
+
+ # Apply free features form the command line. If user
+ # said
+ # define=FOO
+ # he most likely want this define to be set for all compiles.
+ ps = ps.refine(self.manager().command_line_free_features())
+ rproperties = self.common_properties (ps, self.requirements_)
+
+ self.manager().targets().log(
+ "Common properties are '%s'" % str (rproperties.raw ()))
+
+ if rproperties.get("<build>") != "no":
+
+ result = GenerateResult ()
+
+ properties = rproperties.non_dependency ()
+
+ (p, u) = self.generate_dependencies (rproperties.dependency (), rproperties)
+ properties += p
+ usage_requirements = u
+
+ (source_targets, u) = self.generate_dependencies (self.sources_, rproperties)
+ usage_requirements += u
+
+ self.manager_.targets().log(
+ "Usage requirements for '%s' are '%s'" % (self.name_, usage_requirements))
+
+ rproperties = property_set.create (properties + usage_requirements)
+ usage_requirements = property_set.create (usage_requirements)
+
+ self.manager_.targets().log(
+ "Build properties: '%s'" % str(rproperties.raw()))
+
+ extra = rproperties.get ('<source>')
+ source_targets += replace_grist (extra, '')
+ source_targets = replace_references_by_objects (self.manager (), source_targets)
+
+ # We might get duplicate sources, for example if
+ # we link to two library which have the same <library> in
+ # usage requirements.
+ source_targets = unique (source_targets)
+
+ result = self.construct (self.name_, source_targets, rproperties)
+ if result:
+ assert len(result) == 2
+ gur = result [0]
+ result = result [1]
+
+ s = self.create_subvariant (
+ result,
+ self.manager().virtual_targets().recent_targets(), ps,
+ source_targets, rproperties, usage_requirements)
+ self.manager().virtual_targets().clear_recent_targets()
+
+ ur = self.compute_usage_requirements (s)
+ ur = ur.add (gur)
+ s.set_usage_requirements (ur)
+
+ self.manager_.targets().log (
+ "Usage requirements from '%s' are '%s'" %
+ (self.name, str(rproperties.raw())))
+
+ self.generated_ [str (ps)] = GenerateResult (ur, result)
+ else:
+ self.generated_ [str (ps)] = GenerateResult (property_set.empty(), [])
+ else:
+ self.manager().targets().log(
+ "Skipping build: <build>no in common properties")
+
+ # We're here either because there's error computing
+ # properties, or there's <build>no in properties.
+ # In the latter case we don't want any diagnostic.
+ # In the former case, we need diagnostics. TODOo
+ self.generated_ [str (ps)] = GenerateResult (rproperties, [])
+ else:
+ self.manager().targets().log ("Already built")
+
+ self.manager().targets().decrease_indent()
+
+ return self.generated_ [str (ps)]
+
+ def generate_from_reference (self, target_reference, project, property_set):
+ """ Attempts to generate the target given by target reference, which
+ can refer both to a main target or to a file.
+ Returns a list consisting of
+ - usage requirements
+ - generated virtual targets, if any
+ target_reference: Target reference
+ project: Project where the reference is made
+ property_set: Properties of the main target that makes the reference
+ """
+ target, sproperties = self.resolve_reference (target_reference, project)
+
+ # Take properties which should be propagated and refine them
+ # with source-specific requirements.
+ propagated = property_set.propagated ()
+ rproperties = propagated.refine (sproperties)
+
+ return target.generate (rproperties)
+
+ def compute_usage_requirements (self, subvariant):
+ """ Given the set of generated targets, and refined build
+ properties, determines and sets appripriate usage requirements
+ on those targets.
+ """
+ rproperties = subvariant.build_properties ()
+ xusage_requirements =self.evaluate_requirements(
+ self.usage_requirements_, rproperties, "added")
+
+ # We generate all dependency properties and add them,
+ # as well as their usage requirements, to result.
+ (r1, r2) = self.generate_dependencies (xusage_requirements.dependency (), rproperties)
+ extra = r1 + r2
+
+ result = property_set.create (xusage_requirements.non_dependency () + extra)
+
+ # Propagate usage requirements we've got from sources, except
+ # for the <pch-header> and <pch-file> features.
+ #
+ # That feature specifies which pch file to use, and should apply
+ # only to direct dependents. Consider:
+ #
+ # pch pch1 : ...
+ # lib lib1 : ..... pch1 ;
+ # pch pch2 :
+ # lib lib2 : pch2 lib1 ;
+ #
+ # Here, lib2 should not get <pch-header> property from pch1.
+ #
+ # Essentially, when those two features are in usage requirements,
+ # they are propagated only to direct dependents. We might need
+ # a more general mechanism, but for now, only those two
+ # features are special.
+ raw = subvariant.sources_usage_requirements().raw()
+ raw = property.change(raw, "<pch-header>", None);
+ raw = property.change(raw, "<pch-file>", None);
+ result = result.add(property_set.create(raw))
+
+ return result
+
+ def create_subvariant (self, root_targets, all_targets,
+ build_request, sources,
+ rproperties, usage_requirements):
+ """Creates a new subvariant-dg instances for 'targets'
+ - 'root-targets' the virtual targets will be returned to dependents
+ - 'all-targets' all virtual
+ targets created while building this main target
+ - 'build-request' is property-set instance with
+ requested build properties"""
+
+ for e in root_targets:
+ e.root (True)
+
+ s = Subvariant (self, build_request, sources,
+ rproperties, usage_requirements, all_targets)
+
+ for v in all_targets:
+ if not v.creating_subvariant():
+ v.creating_subvariant(s)
+
+ return s
+
+ def construct (self, name, source_targets, properties):
+ """ Constructs the virtual targets for this abstract targets and
+ the dependecy graph. Returns a tuple consisting of the properties and the list of virtual targets.
+ Should be overrided in derived classes.
+ """
+ raise BaseException ("method should be defined in derived classes")
+
+
+class TypedTarget (BasicTarget):
+ import generators
+
+ def __init__ (self, name, project, type, sources, requirements, default_build, usage_requirements):
+ BasicTarget.__init__ (self, name, project, sources, requirements, default_build, usage_requirements)
+ self.type_ = type
+
+ def type (self):
+ return self.type_
+
+ def construct (self, name, source_targets, prop_set):
+ r = generators.construct (self.project_, name, self.type_,
+ property_set.create (prop_set.raw () + ['<main-target-type>' + self.type_]),
+ source_targets)
+
+ if not r:
+ print "warning: Unable to construct '%s'" % self.full_name ()
+
+ # Are there any top-level generators for this type/property set.
+ if not generators.find_viable_generators (self.type_, prop_set):
+ print "error: no generators were found for type '$(self.type)'"
+ print "error: and the requested properties"
+ print "error: make sure you've configured the needed tools"
+ print "See http://boost.org/boost-build2/doc/html/bbv2/advanced/configuration.html"
+
+ print "To debug this problem, try the --debug-generators option."
+ sys.exit(1)
+
+ return r
+

Added: trunk/tools/build/v2/build/toolset.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/build/toolset.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,402 @@
+# Status: being ported by Vladimir Prus
+# Base revision: 40958
+#
+# Copyright 2003 Dave Abrahams
+# Copyright 2005 Rene Rivera
+# Copyright 2002, 2003, 2004, 2005, 2006 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)
+
+""" Support for toolset definition.
+"""
+
+import feature, property, generators
+from b2.util.utility import *
+from b2.util import set
+
+__re_split_last_segment = re.compile (r'^(.+)\.([^\.])*')
+__re_two_ampersands = re.compile ('(&&)')
+__re_first_segment = re.compile ('([^.]*).*')
+__re_first_group = re.compile (r'[^.]*\.(.*)')
+
+# Flag is a mechanism to set a value
+# A single toolset flag. Specifies that when certain
+# properties are in build property set, certain values
+# should be appended to some variable.
+#
+# A flag applies to a specific action in specific module.
+# The list of all flags for a module is stored, and each
+# flag further contains the name of the rule it applies
+# for,
+class Flag:
+
+ def __init__(self, variable_name, values, condition, rule = None):
+ self.variable_name = variable_name
+ self.values = values
+ self.condition = condition
+ self.rule = rule
+
+ def __str__(self):
+ return("Flag(" + str(self.variable_name) + ", " + str(self.values) +\
+ ", " + str(self.condition) + ", " + str(self.rule) + ")")
+
+def reset ():
+ """ Clear the module state. This is mainly for testing purposes.
+ """
+ global __module_flags, __flags, __stv
+
+ # Mapping from module name to a list of all flags that apply
+ # to either that module directly, or to any rule in that module.
+ # Each element of the list is Flag instance.
+ # So, for module named xxx this might contain flags for 'xxx',
+ # for 'xxx.compile', for 'xxx.compile.c++', etc.
+ __module_flags = {}
+
+ # Mapping from specific rule or module name to a list of Flag instances
+ # that apply to that name.
+ # Say, it might contain flags for 'xxx.compile.c++'. If there are
+ # entries for module name 'xxx', they are flags for 'xxx' itself,
+ # not including any rules in that module.
+ __flags = {}
+
+ # A cache for varaible settings. The key is generated from the rule name and the properties.
+ __stv = {}
+
+reset ()
+
+# FIXME: --ignore-toolset-requirements
+# FIXME: using
+
+def normalize_condition (property_sets):
+ """ Expands subfeatures in each property set.
+ e.g
+ <toolset>gcc-3.2
+ will be converted to
+ <toolset>gcc/<toolset-version>3.2
+
+ TODO: does this one belong here or in feature?
+ """
+ result = []
+ for p in property_sets:
+ split = feature.split (p)
+ expanded = feature.expand_subfeatures (split)
+ result.append ('/'.join (expanded))
+
+ return result
+
+# FIXME push-checking-for-flags-module ....
+# FIXME: investigate existing uses of 'hack-hack' parameter
+# in jam code.
+
+def flags (rule_or_module, variable_name, condition, values = []):
+ """ Specifies the flags (variables) that must be set on targets under certain
+ conditions, described by arguments.
+ rule_or_module: If contains dot, should be a rule name.
+ The flags will be applied when that rule is
+ used to set up build actions.
+
+ If does not contain dot, should be a module name.
+ The flags will be applied for all rules in that
+ module.
+ If module for rule is different from the calling
+ module, an error is issued.
+
+ variable_name: Variable that should be set on target
+
+ condition A condition when this flag should be applied.
+ Should be set of property sets. If one of
+ those property sets is contained in build
+ properties, the flag will be used.
+ Implied values are not allowed:
+ "<toolset>gcc" should be used, not just
+ "gcc". Subfeatures, like in "<toolset>gcc-3.2"
+ are allowed. If left empty, the flag will
+ always used.
+
+ Propery sets may use value-less properties
+ ('<a>' vs. '<a>value') to match absent
+ properties. This allows to separately match
+
+ <architecture>/<address-model>64
+ <architecture>ia64/<address-model>
+
+ Where both features are optional. Without this
+ syntax we'd be forced to define "default" value.
+
+ values: The value to add to variable. If <feature>
+ is specified, then the value of 'feature'
+ will be added.
+ """
+ if condition and not replace_grist (condition, ''):
+ # We have condition in the form '<feature>', that is, without
+ # value. That's a previous syntax:
+ #
+ # flags gcc.link RPATH <dll-path> ;
+ # for compatibility, convert it to
+ # flags gcc.link RPATH : <dll-path> ;
+ values = [ condition ]
+ condition = None
+
+ if condition:
+ property.validate_property_sets (condition)
+ condition = normalize_condition ([condition])
+
+ __add_flag (rule_or_module, variable_name, condition, values)
+
+def set_target_variables (manager, rule_or_module, targets, properties):
+ """
+ """
+ key = rule_or_module + '.' + str (properties)
+ settings = __stv.get (key, None)
+ if not settings:
+ settings = __set_target_variables_aux (manager, rule_or_module, properties)
+
+ __stv [key] = settings
+
+ if settings:
+ for s in settings:
+ for target in targets:
+ manager.engine ().set_target_variable (target, s [0], s[1], True)
+
+def find_property_subset (property_sets, properties):
+ """Returns the first element of 'property-sets' which is a subset of
+ 'properties', or an empty list if no such element exists."""
+
+ prop_keys = get_grist(properties)
+
+ for s in property_sets:
+ # Handle value-less properties like '<architecture>' (compare with
+ # '<architecture>x86').
+
+ set = feature.split(s)
+
+ # Find the set of features that
+ # - have no property specified in required property set
+ # - are omitted in build property set
+ default_props = []
+ for i in set:
+ # If $(i) is a value-less property it should match default
+ # value of an optional property. See the first line in the
+ # example below:
+ #
+ # property set properties result
+ # <a> <b>foo <b>foo match
+ # <a> <b>foo <a>foo <b>foo no match
+ # <a>foo <b>foo <b>foo no match
+ # <a>foo <b>foo <a>foo <b>foo match
+ if not (get_value(i) or get_grist(i) in prop_keys):
+ default_props.append(i)
+
+ # FIXME: can this be expressed in a more pythonic way?
+ has_all = 1
+ for i in set:
+ if i not in (properties + default_props):
+ has_all = 0
+ break
+ if has_all:
+ return s
+
+ return None
+
+
+def register (toolset):
+ """ Registers a new toolset.
+ """
+ feature.extend('toolset', [toolset])
+
+def inherit_generators (toolset, properties, base, generators_to_ignore = []):
+ if not properties:
+ properties = [replace_grist (toolset, '<toolset>')]
+
+ base_generators = generators.generators_for_toolset(base)
+
+ for g in base_generators:
+ id = g.id()
+
+ if not id in generators_to_ignore:
+ # Some generator names have multiple periods in their name, so
+ # $(id:B=$(toolset)) doesn't generate the right new_id name.
+ # e.g. if id = gcc.compile.c++, $(id:B=darwin) = darwin.c++,
+ # which is not what we want. Manually parse the base and suffix
+ # (if there's a better way to do this, I'd love to see it.)
+ # See also register in module generators.
+ (base, suffix) = split_action_id(id)
+
+ new_id = toolset + '.' + suffix
+
+ generators.register(g.clone(new_id, properties))
+
+def inherit_flags(toolset, base, prohibited_properties = []):
+ """Brings all flag definitions from the 'base' toolset into the 'toolset'
+ toolset. Flag definitions whose conditions make use of properties in
+ 'prohibited-properties' are ignored. Don't confuse property and feature, for
+ example <debug-symbols>on and <debug-symbols>off, so blocking one of them does
+ not block the other one.
+
+ The flag conditions are not altered at all, so if a condition includes a name,
+ or version of a base toolset, it won't ever match the inheriting toolset. When
+ such flag settings must be inherited, define a rule in base toolset module and
+ call it as needed."""
+ for f in __module_flags.get(base, []):
+
+ if not f.condition or set.difference(f.condition, prohibited_properties):
+ match = __re_first_group.match(f.rule)
+ rule_ = None
+ if match:
+ rule_ = match.group(1)
+
+ new_rule_or_module = ''
+
+ if rule_:
+ new_rule_or_module = toolset + '.' + rule_
+ else:
+ new_rule_or_module = toolset
+
+ __add_flag (new_rule_or_module, f.variable_name, f.condition, f.values)
+
+def inherit_rules (toolset, base):
+ pass
+ # FIXME: do something about this.
+# base_generators = generators.generators_for_toolset (base)
+
+# import action
+
+# ids = []
+# for g in base_generators:
+# (old_toolset, id) = split_action_id (g.id ())
+# ids.append (id) ;
+
+# new_actions = []
+
+# engine = get_manager().engine()
+ # FIXME: do this!
+# for action in engine.action.values():
+# pass
+# (old_toolset, id) = split_action_id(action.action_name)
+#
+# if old_toolset == base:
+# new_actions.append ((id, value [0], value [1]))
+#
+# for a in new_actions:
+# action.register (toolset + '.' + a [0], a [1], a [2])
+
+ # TODO: how to deal with this?
+# IMPORT $(base) : $(rules) : $(toolset) : $(rules) : localized ;
+# # Import the rules to the global scope
+# IMPORT $(toolset) : $(rules) : : $(toolset).$(rules) ;
+# }
+#
+
+######################################################################################
+# Private functions
+
+def __set_target_variables_aux (manager, rule_or_module, properties):
+ """ Given a rule name and a property set, returns a list of tuples of
+ variables names and values, which must be set on targets for that
+ rule/properties combination.
+ """
+ result = []
+
+ for f in __flags.get(rule_or_module, []):
+
+ if not f.condition or find_property_subset (f.condition, properties):
+ processed = []
+ for v in f.values:
+ # The value might be <feature-name> so needs special
+ # treatment.
+ processed += __handle_flag_value (manager, v, properties)
+
+ for r in processed:
+ result.append ((f.variable_name, r))
+
+ # strip away last dot separated part and recurse.
+ next = __re_split_last_segment.match(rule_or_module)
+
+ if next:
+ result.extend(__set_target_variables_aux(
+ manager, next.group(1), properties))
+
+ return result
+
+def __handle_flag_value (manager, value, properties):
+ result = []
+
+ if get_grist (value):
+ matches = property.select (value, properties)
+ for p in matches:
+ att = feature.attributes (get_grist (p))
+
+ ungristed = replace_grist (p, '')
+
+ if 'dependency' in att:
+ # the value of a dependency feature is a target
+ # and must be actualized
+ # FIXME: verify that 'find' actually works, ick!
+ result.append (manager.targets ().find (ungristed).actualize ())
+
+ elif 'path' in att or 'free' in att:
+ values = []
+
+ # Treat features with && in the value
+ # specially -- each &&-separated element is considered
+ # separate value. This is needed to handle searched
+ # libraries, which must be in specific order.
+ if not __re_two_ampersands.search (ungristed):
+ values.append (ungristed)
+
+ else:
+ values.extend(value.split ('&&'))
+
+ result.extend(values)
+ else:
+ result.append (ungristed)
+ else:
+ result.append (value)
+
+ return result
+
+def __add_flag (rule_or_module, variable_name, condition, values):
+ """ Adds a new flag setting with the specified values.
+ Does no checking.
+ """
+ f = Flag(variable_name, values, condition, rule_or_module)
+
+ # Grab the name of the module
+ m = __re_first_segment.match (rule_or_module)
+ assert m
+ module = m.group(1)
+
+ __module_flags.setdefault(m, []).append(f)
+ __flags.setdefault(rule_or_module, []).append(f)
+
+def requirements():
+ """Return the list of global 'toolset requirements'.
+ Those requirements will be automatically added to the requirements of any main target."""
+ return __requirements
+
+def add_requirements(requirements):
+ """Adds elements to the list of global 'toolset requirements'. The requirements
+ will be automatically added to the requirements for all main targets, as if
+ they were specified literally. For best results, all requirements added should
+ be conditional or indirect conditional."""
+
+ # FIXME:
+ #if ! $(.ignore-requirements)
+ #{
+ __requirements.extend(requirements)
+ #}
+
+# Make toolset 'toolset', defined in a module of the same name,
+# inherit from 'base'
+# 1. The 'init' rule from 'base' is imported into 'toolset' with full
+# name. Another 'init' is called, which forwards to the base one.
+# 2. All generators from 'base' are cloned. The ids are adjusted and
+# <toolset> property in requires is adjusted too
+# 3. All flags are inherited
+# 4. All rules are imported.
+def inherit(toolset, base):
+ get_manager().projects().load_module(base, []);
+
+ inherit_generators(toolset, [], base)
+ inherit_flags(toolset, base)
+ inherit_rules(toolset, base)

Added: trunk/tools/build/v2/build/type.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/build/type.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,292 @@
+# Status: ported.
+# Base revision: 45462.
+
+# Copyright (C) Vladimir Prus 2002. Permission to copy, use, modify, sell and
+# distribute this software is granted provided this copyright notice appears in
+# all copies. This software is provided "as is" without express or implied
+# warranty, and with no claim as to its suitability for any purpose.
+
+
+
+import re
+import os
+import os.path
+from b2.util.utility import replace_grist, os_name
+from b2.exceptions import *
+from b2.build import feature, property, scanner
+
+__re_hyphen = re.compile ('-')
+
+def __register_features ():
+ """ Register features need by this module.
+ """
+ # The feature is optional so that it is never implicitly added.
+ # It's used only for internal purposes, and in all cases we
+ # want to explicitly use it.
+ feature.feature ('target-type', [], ['composite', 'optional'])
+ feature.feature ('main-target-type', [], ['optional', 'incidental'])
+ feature.feature ('base-target-type', [], ['composite', 'optional', 'free'])
+
+def reset ():
+ """ Clear the module state. This is mainly for testing purposes.
+ Note that this must be called _after_ resetting the module 'feature'.
+ """
+ global __prefixes_suffixes, __suffixes_to_types, __types, __rule_names_to_types, __target_suffixes_cache
+
+ __register_features ()
+
+ # Stores suffixes for generated targets.
+ __prefixes_suffixes = [property.PropertyMap(), property.PropertyMap()]
+
+ # Maps suffixes to types
+ __suffixes_to_types = {}
+
+ # A map with all the registered types, indexed by the type name
+ # Each entry is a dictionary with following values:
+ # 'base': the name of base type or None if type has no base
+ # 'derived': a list of names of type which derive from this one
+ # 'scanner': the scanner class registered for this type, if any
+ __types = {}
+
+ # Caches suffixes for targets with certain properties.
+ __target_suffixes_cache = {}
+
+reset ()
+
+
+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'.
+ Also, the first element gives the suffix to be used when constructing and object of
+ 'type'.
+ type: a string
+ suffixes: None or a sequence of strings
+ base_type: None or a string
+ """
+ # Type names cannot contain hyphens, because when used as
+ # feature-values they will be interpreted as composite features
+ # which need to be decomposed.
+ if __re_hyphen.search (type):
+ raise BaseException ('type name "%s" contains a hyphen' % type)
+
+ if __types.has_key (type):
+ raise BaseException ('Type "%s" is already registered.' % type)
+
+ entry = {}
+ entry ['base'] = base_type
+ entry ['derived'] = []
+ entry ['scanner'] = None
+ __types [type] = entry
+
+ if base_type:
+ __types [base_type]['derived'].append (type)
+
+ if len (suffixes) > 0:
+ # Generated targets of 'type' will use the first of 'suffixes'
+ # (this may be overriden)
+ set_generated_target_suffix (type, [], suffixes [0])
+
+ # Specify mapping from suffixes to type
+ register_suffixes (suffixes, type)
+
+ feature.extend('target-type', [type])
+ feature.extend('main-target-type', [type])
+ feature.extend('base-target-type', [type])
+
+ if base_type:
+ feature.compose ('<target-type>' + type, replace_grist (base_type, '<base-target-type>'))
+ feature.compose ('<base-target-type>' + type, '<base-target-type>' + base_type)
+
+ # FIXME: resolving recursive dependency.
+ from b2.manager import get_manager
+ get_manager().projects().project_rules().add_rule_for_type(type)
+
+def register_suffixes (suffixes, type):
+ """ Specifies that targets with suffix from 'suffixes' have the type 'type'.
+ If a different type is already specified for any of syffixes, issues an error.
+ """
+ for s in suffixes:
+ if __suffixes_to_types.has_key (s):
+ old_type = __suffixes_to_types [s]
+ if old_type != type:
+ raise BaseException ('Attempting to specify type for suffix "%s"\nOld type: "%s", New type "%s"' % (s, old_type, type))
+ else:
+ __suffixes_to_types [s] = type
+
+def registered (type):
+ """ Returns true iff type has been registered.
+ """
+ return __types.has_key (type)
+
+def validate (type):
+ """ Issues an error if 'type' is unknown.
+ """
+ if not registered (type):
+ raise BaseException ("Unknown target type '%s'" % type)
+
+def set_scanner (type, scanner):
+ """ Sets a scanner class that will be used for this 'type'.
+ """
+ validate (type)
+ __types [type]['scanner'] = scanner
+
+def get_scanner (type, prop_set):
+ """ Returns a scanner instance appropriate to 'type' and 'property_set'.
+ """
+ if registered (type):
+ scanner_type = __types [type]['scanner']
+ if scanner_type:
+ return scanner.get (scanner_type, prop_set.raw ())
+ pass
+
+ return None
+
+def all_bases (type):
+ """ Returns type and all of its bases, in the order of their distance from type.
+ """
+ result = []
+ while type:
+ result.append (type)
+ type = __types [type]['base']
+
+ return result
+
+def all_derived (type):
+ """ Returns type and all classes that derive from it, in the order of their distance from type.
+ """
+ result = [type]
+ for d in __types [type]['derived']:
+ result.extend (all_derived (d))
+
+ return result
+
+def is_derived (type, base):
+ """ Returns true if 'type' is 'base' or has 'base' as its direct or indirect base.
+ """
+ # TODO: this isn't very efficient, especially for bases close to type
+ if base in all_bases (type):
+ return True
+ else:
+ return False
+
+def is_subtype (type, base):
+ """ Same as is_derived. Should be removed.
+ """
+ # TODO: remove this method
+ return is_derived (type, base)
+
+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
+ empty properties if no suffix for 'type' was specified yet.
+ This does not automatically specify that files 'suffix' have
+ 'type' --- two different types can use the same suffix for
+ generating, but only one type should be auto-detected for
+ a file with that suffix. User should explicitly specify which
+ one.
+
+ The 'suffix' parameter can be empty string ("") to indicate that
+ no suffix should be used.
+ """
+ set_generated_target_ps(1, type, properties, suffix)
+
+
+
+def change_generated_target_suffix (type, properties, suffix):
+ """ Change the suffix previously registered for this type/properties
+ combination. If suffix is not yet specified, sets it.
+ """
+ change_generated_target_ps(1, type, properties, suffix)
+
+def generated_target_suffix(type, properties):
+ return generated_target_ps(1, type, properties)
+
+# Sets a target prefix that should be used when generating targets of 'type'
+# with the specified properties. Can be called with empty properties if no
+# prefix for 'type' has been specified yet.
+#
+# The 'prefix' parameter can be empty string ("") to indicate that no prefix
+# should be used.
+#
+# Usage example: library names use the "lib" prefix on unix.
+def set_generated_target_prefix(type, properties, prefix):
+ set_generated_target_ps(0, type, properties, prefix)
+
+# Change the prefix previously registered for this type/properties combination.
+# If prefix is not yet specified, sets it.
+def change_generated_target_prefix(type, properties, prefix):
+ change_generated_target_ps(0, type, properties, prefix)
+
+def generated_target_prefix(type, properties):
+ return generated_target_ps(0, type, properties)
+
+def set_generated_target_ps(is_suffix, type, properties, val):
+ properties.append ('<target-type>' + type)
+ __prefixes_suffixes[is_suffix].insert (properties, val)
+
+def change_generated_target_ps(is_suffix, type, properties, val):
+ properties.append ('<target-type>' + type)
+ prev = __prefixes_suffixes[is_suffix].find_replace(properties, val)
+ if not prev:
+ set_generated_target_ps(is_suffix, type, properties, val)
+
+# Returns either prefix or suffix (as indicated by 'is_suffix') that should be used
+# when generating a target of 'type' with the specified properties.
+# If no prefix/suffix is specified for 'type', returns prefix/suffix for
+# base type, if any.
+def generated_target_ps_real(is_suffix, type, properties):
+
+ result = ''
+ found = False
+ while type and not found:
+ result = __prefixes_suffixes[is_suffix].find (['<target-type>' + type] + properties)
+
+ # Note that if the string is empty (""), but not null, we consider
+ # suffix found. Setting prefix or suffix to empty string is fine.
+ if result:
+ found = True
+
+ type = __types [type]['base']
+
+ if not result:
+ result = ''
+ return result
+
+def generated_target_ps(is_suffix, type, prop_set):
+ """ Returns suffix that should be used when generating target of 'type',
+ with the specified properties. If not suffix were specified for
+ 'type', returns suffix for base type, if any.
+ """
+ key = str(is_suffix) + type + str(prop_set)
+ v = __target_suffixes_cache.get (key, None)
+
+ if not v:
+ v = generated_target_ps_real(is_suffix, type, prop_set.raw())
+ __target_suffixes_cache [key] = v
+
+ return v
+
+def type(filename):
+ """ Returns file type given it's name. If there are several dots in filename,
+ tries each suffix. E.g. for name of "file.so.1.2" suffixes "2", "1", and
+ "so" will be tried.
+ """
+ while 1:
+ filename, suffix = os.path.splitext (filename)
+ if not suffix: return None
+ suffix = suffix[1:]
+
+ if __suffixes_to_types.has_key(suffix):
+ return __suffixes_to_types[suffix]
+
+# NOTE: moved from tools/types/register
+def register_type (type, suffixes, base_type = None, os = []):
+ """ Register the given type on the specified OSes, or on remaining OSes
+ if os is not specified. This rule is injected into each of the type
+ modules for the sake of convenience.
+ """
+ if registered (type):
+ return
+
+ if not os or os_name () in os:
+ register (type, suffixes, base_type)

Added: trunk/tools/build/v2/build/virtual_target.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/build/virtual_target.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,1051 @@
+# Status: being ported by Vladimir Prus
+# Essentially ported, minor fixme remain.
+# Base revision: 40480
+#
+# Copyright (C) Vladimir Prus 2002. Permission to copy, use, modify, sell and
+# distribute this software is granted provided this copyright notice appears in
+# all copies. This software is provided "as is" without express or implied
+# warranty, and with no claim as to its suitability for any purpose.
+
+# Implements virtual targets, which correspond to actual files created during
+# build, but are not yet targets in Jam sense. They are needed, for example,
+# when searching for possible transormation sequences, when it's not known
+# if particular target should be created at all.
+#
+#
+# +--------------------------+
+# | VirtualTarget |
+# +==========================+
+# | actualize |
+# +--------------------------+
+# | actualize_action() = 0 |
+# | actualize_location() = 0 |
+# +----------------+---------+
+# |
+# ^
+# / \
+# +-+-+
+# |
+# +---------------------+ +-------+--------------+
+# | Action | | AbstractFileTarget |
+# +=====================| * +======================+
+# | action_name | +--+ action |
+# | properties | | +----------------------+
+# +---------------------+--+ | actualize_action() |
+# | actualize() |0..1 +-----------+----------+
+# | path() | |
+# | adjust_properties() | sources |
+# | actualize_sources() | targets |
+# +------+--------------+ ^
+# | / \
+# ^ +-+-+
+# / \ |
+# +-+-+ +-------------+-------------+
+# | | |
+# | +------+---------------+ +--------+-------------+
+# | | FileTarget | | SearchedLibTarget |
+# | +======================+ +======================+
+# | | actualize-location() | | actualize-location() |
+# | +----------------------+ +----------------------+
+# |
+# +-+------------------------------+
+# | |
+# +----+----------------+ +---------+-----------+
+# | CompileAction | | LinkAction |
+# +=====================+ +=====================+
+# | adjust_properties() | | adjust_properties() |
+# +---------------------+ | actualize_sources() |
+# +---------------------+
+#
+# The 'CompileAction' and 'LinkAction' classes are defined not here,
+# but in builtin.jam modules. They are shown in the diagram to give
+# the big picture.
+
+import re
+import os.path
+import string
+
+from b2.util import path, utility, set
+from b2.util.utility import add_grist, get_grist, ungrist, replace_grist, get_value
+from b2.util.sequence import unique
+from b2.tools import common
+from b2.exceptions import *
+import b2.build.type
+import type
+
+__re_starts_with_at = re.compile ('^@(.*)')
+
+class VirtualTargetRegistry:
+ def __init__ (self, manager):
+ self.manager_ = manager
+
+ # A cache for FileTargets
+ self.files_ = {}
+
+ # A cache for targets.
+ self.cache_ = {}
+
+ # A map of actual names to virtual targets.
+ # Used to make sure we don't associate same
+ # actual target to two virtual targets.
+ self.actual_ = {}
+
+ self.recent_targets_ = []
+
+ # All targets ever registed
+ self.all_targets_ = []
+
+ self.next_id_ = 0
+
+ def register (self, target):
+ """ Registers a new virtual target. Checks if there's already registered target, with the same
+ name, type, project and subvariant properties, and also with the same sources
+ and equal action. If such target is found it is retured and 'target' is not registered.
+ Otherwise, 'target' is registered and returned.
+ """
+ signature = target.path() + "-" + target.name()
+
+ result = None
+ if not self.cache_.has_key (signature):
+ self.cache_ [signature] = []
+
+ for t in self.cache_ [signature]:
+ a1 = t.action ()
+ a2 = target.action ()
+
+ # TODO: why are we checking for not result?
+ if not result:
+ if not a1 and not a2:
+ result = t
+ else:
+ if a1 and a2 and a1.action_name () == a2.action_name () and a1.sources () == a2.sources ():
+ ps1 = a1.properties ()
+ ps2 = a2.properties ()
+ p1 = ps1.base () + ps1.free () + ps1.dependency ()
+ p2 = ps2.base () + ps2.free () + ps2.dependency ()
+ if p1 == p2:
+ result = t
+
+ if not result:
+ self.cache_ [signature].append (target)
+ result = target
+
+ # TODO: Don't append if we found pre-existing target?
+ self.recent_targets_.append(result)
+ self.all_targets_.append(result)
+
+ result.set_id(self.next_id_)
+ self.next_id_ = self.next_id_+1
+
+ return result
+
+ def from_file (self, file, file_location, project):
+ """ Creates a virtual target with appropriate name and type from 'file'.
+ If a target with that name in that project was already created, returns that already
+ created target.
+ TODO: more correct way would be to compute path to the file, based on name and source location
+ for the project, and use that path to determine if the target was already created.
+ TODO: passing project with all virtual targets starts to be annoying.
+ """
+ # Check if we've created a target corresponding to this file.
+ path = os.path.join(os.getcwd(), file_location, file)
+ path = os.path.normpath(path)
+
+ if self.files_.has_key (path):
+ return self.files_ [path]
+
+ file_type = type.type (file)
+
+ result = FileTarget (file, False, file_type, project,
+ None, file_location)
+ self.files_ [path] = result
+
+ result.set_id(self.next_id_)
+ self.next_id_ = self.next_id_+1
+
+ return result
+
+ def recent_targets(self):
+ """Each target returned by 'register' is added to a list of
+ 'recent-target', returned by this function. So, this allows
+ us to find all targets created when building a given main
+ target, even if the target."""
+
+ return self.recent_targets_
+
+ def clear_recent_targets(self):
+ self.recent_targets_ = []
+
+ def all_targets(self):
+ # Returns all virtual targets ever created
+ return self.all_targets_
+
+ # 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)]
+
+ def register_actual_name (self, actual_name, virtual_target):
+ if self.actual_.has_key (actual_name):
+ cs1 = self.actual_ [actual_name].creating_subvariant ()
+ cs2 = virtual_target.creating_subvariant ()
+ cmt1 = cs1.main_target ()
+ cmt2 = cs2.main_target ()
+
+ action1 = self.actual_ [actual_name].action ()
+ action2 = virtual_target.action ()
+
+ properties_added = []
+ properties_removed = []
+ if action1 and action2:
+ p1 = action1.properties ()
+ p1 = p1.raw ()
+ p2 = action2.properties ()
+ p2 = p2.raw ()
+
+ properties_removed = set.difference (p1, p2)
+ if not properties_removed: properties_removed = "none"
+
+ properties_added = set.difference (p2, p1)
+ if not properties_added: properties_added = "none"
+
+ # FIXME: Revive printing of real location.
+ raise BaseException ("Duplicate name of actual target: '%s'\n"
+ "previous virtual target '%s'\n"
+ "created from '%s'\n"
+ "another virtual target '%s'\n"
+ "created from '%s'\n"
+ "added properties: '%s'\n"
+ "removed properties: '%s'\n" % (actual_name,
+ self.actual_ [actual_name], "loc", #cmt1.location (),
+ virtual_target,
+ "loc", #cmt2.location (),
+ properties_added, properties_removed))
+
+ else:
+ self.actual_ [actual_name] = virtual_target
+
+
+ def add_suffix (self, specified_name, file_type, prop_set):
+ """ 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)
+
+ if suffix:
+ return specified_name + '.' + suffix
+
+ else:
+ return specified_name
+
+class VirtualTarget:
+ """ Potential target. It can be converted into jam target and used in
+ building, if needed. However, it can be also dropped, which allows
+ to search for different transformation and select only one.
+ name: name of this target.
+ project: project to which this target belongs.
+ """
+ def __init__ (self, name, project):
+ self.name_ = name
+ self.project_ = project
+ self.dependencies_ = []
+
+ # Caches if dapendencies for scanners have already been set.
+ self.made_ = {}
+
+ def manager(self):
+ return self.project_.manager()
+
+ def virtual_targets(self):
+ return self.manager().virtual_targets()
+
+ def name (self):
+ """ Name of this target.
+ """
+ return self.name_
+
+ def project (self):
+ """ Project of this target.
+ """
+ return self.project_
+
+ def set_id(self, id):
+ self.id_ = id
+
+ def __hash__(self):
+ return self.id_
+
+ def __cmp__(self, other):
+ return self.id_ - other.id_
+
+ def depends (self, d):
+ """ Adds additional instances of 'VirtualTarget' that this
+ one depends on.
+ """
+ self.dependencies_ = unique (self.dependencies_ + d).sort ()
+
+ def dependencies (self):
+ return self.dependencies_
+
+ def actualize (self, scanner = None):
+ """ Generates all the actual targets and sets up build actions for
+ this target.
+
+ If 'scanner' is specified, creates an additional target
+ with the same location as actual target, which will depend on the
+ actual target and be associated with 'scanner'. That additional
+ target is returned. See the docs (#dependency_scanning) for rationale.
+ Target must correspond to a file if 'scanner' is specified.
+
+ If scanner is not specified, then actual target is returned.
+ """
+ actual_name = self.actualize_no_scanner ()
+
+ if not scanner:
+ return actual_name
+
+ else:
+ # Add the scanner instance to the grist for name.
+ g = '-'.join ([ungrist(get_grist(actual_name)), str(id(scanner))])
+
+ name = replace_grist (actual_name, '<' + g + '>')
+
+ if not self.made_.has_key (name):
+ self.made_ [name] = True
+
+ self.project_.manager ().engine ().add_dependency (name, actual_name)
+
+ self.actualize_location (name)
+
+ self.project_.manager ().scanners ().install (scanner, name, str (self))
+
+ return name
+
+# private: (overridables)
+
+ def actualize_action (self, target):
+ """ Sets up build actions for 'target'. Should call appropriate rules
+ and set target variables.
+ """
+ raise BaseException ("method should be defined in derived classes")
+
+ def actualize_location (self, target):
+ """ Sets up variables on 'target' which specify its location.
+ """
+ raise BaseException ("method should be defined in derived classes")
+
+ def path (self):
+ """ If the target is generated one, returns the path where it will be
+ generated. Otherwise, returns empty list.
+ """
+ raise BaseException ("method should be defined in derived classes")
+
+ def actual_name (self):
+ """ Return that actual target name that should be used
+ (for the case where no scanner is involved)
+ """
+ raise BaseException ("method should be defined in derived classes")
+
+
+class AbstractFileTarget (VirtualTarget):
+ """ Target which correspond to a file. The exact mapping for file
+ is not yet specified in this class. (TODO: Actually, the class name
+ could be better...)
+
+ May be a source file (when no action is specified), or
+ derived file (otherwise).
+
+ The target's grist is concatenation of project's location,
+ properties of action (for derived files), and, optionally,
+ value identifying the main target.
+
+ exact: If non-empty, the name is exactly the name
+ created file should have. Otherwise, the '__init__'
+ method will add suffix obtained from 'type' by
+ calling 'type.generated-target-suffix'.
+
+ type: optional type of this target.
+ """
+ def __init__ (self, name, exact, type, project, action = None):
+ VirtualTarget.__init__ (self, name, project)
+
+ self.type_ = type
+
+ self.action_ = action
+ self.exact_ = exact
+
+ if action:
+ action.add_targets ([self])
+
+ if self.type and not exact:
+ self.__adjust_name (name)
+
+
+ self.actual_name_ = None
+ self.path_ = None
+ self.intermediate_ = False
+ self.creating_subvariant_ = None
+
+ # True if this is a root target.
+ self.root_ = False
+
+ def type (self):
+ return self.type_
+
+ def set_path (self, path):
+ """ Sets the path. When generating target name, it will override any path
+ computation from properties.
+ """
+ self.path_ = path
+
+ def action (self):
+ """ Returns the action.
+ """
+ return self.action_
+
+ def root (self, set = None):
+ """ Sets/gets the 'root' flag. Target is root is it directly correspods to some
+ variant of a main target.
+ """
+ if set:
+ self.root_ = True
+ return self.root_
+
+ def creating_subvariant (self, s = None):
+ """ Gets or sets the subvariant which created this target. Subvariant
+ is set when target is brought into existance, and is never changed
+ after that. In particual, if target is shared by subvariant, only
+ the first is stored.
+ s: If specified, specified the value to set,
+ which should be instance of 'subvariant' class.
+ """
+ if s and not self.creating_subvariant ():
+ if self.creating_subvariant ():
+ raise BaseException ("Attempt to change 'dg'")
+
+ else:
+ self.creating_subvariant_ = s
+
+ return self.creating_subvariant_
+
+ def actualize_action (self, target):
+ if self.action_:
+ self.action_.actualize ()
+
+ # Return a human-readable representation of this target
+ #
+ # If this target has an action, that's:
+ #
+ # { <action-name>-<self.name>.<self.type> <action-sources>... }
+ #
+ # otherwise, it's:
+ #
+ # { <self.name>.<self.type> }
+ #
+ def str(self):
+ a = self.action()
+
+ name_dot_type = self.name_ + "." + self.type_
+
+ if a:
+ action_name = a.action_name()
+ ss = [ s.str() for s in a.sources()]
+
+ return "{ %s-%s %s}" % (action_name, name_dot_type, str(ss))
+ else:
+ return "{ " + name_dot_type + " }"
+
+# private:
+
+ def actual_name (self):
+ if not self.actual_name_:
+ self.actual_name_ = '<' + self.grist() + '>' + self.name_
+
+ return self.actual_name_
+
+ def grist (self):
+ """Helper to 'actual_name', above. Compute unique prefix used to distinguish
+ this target from other targets with the same name which create different
+ file.
+ """
+ # Depending on target, there may be different approaches to generating
+ # unique prefixes. We'll generate prefixes in the form
+ # <one letter approach code> <the actual prefix>
+ path = self.path ()
+
+ if path:
+ # The target will be generated to a known path. Just use the path
+ # for identification, since path is as unique as it can get.
+ return 'p' + path
+
+ else:
+ # File is either source, which will be searched for, or is not a file at
+ # all. Use the location of project for distinguishing.
+ project_location = self.project_.get ('location')
+ path_components = b2.util.path.split(project_location)
+ location_grist = '!'.join (path_components)
+
+ if self.action_:
+ ps = self.action_.properties ()
+ property_grist = ps.as_path ()
+ # 'property_grist' can be empty when 'ps' is an empty
+ # property set.
+ if property_grist:
+ location_grist = location_grist + '/' + property_grist
+
+ return 'l' + location_grist
+
+ def __adjust_name(self, specified_name):
+ """Given the target name specified in constructor, returns the
+ name which should be really used, by looking at the <tag> properties.
+ The tag properties come in two flavour:
+ - <tag>value,
+ - <tag>@rule-name
+ In the first case, value is just added to name
+ In the second case, the specified rule is called with specified name,
+ target type and properties and should return the new name.
+ If not <tag> property is specified, or the rule specified by
+ <tag> returns nothing, returns the result of calling
+ virtual-target.add-suffix"""
+
+ if self.action_:
+ ps = self.action_.properties()
+ else:
+ ps = property_set.empty()
+
+ # FIXME: I'm not sure how this is used, need to check with
+ # Rene to figure out how to implement
+ #~ We add ourselves to the properties so that any tag rule can get
+ #~ more direct information about the target than just that available
+ #~ through the properties. This is useful in implementing
+ #~ name changes based on the sources of the target. For example to
+ #~ make unique names of object files based on the source file.
+ #~ --grafik
+ #ps = property_set.create(ps.raw() + ["<target>%s" % "XXXX"])
+ #ps = [ property-set.create [ $(ps).raw ] <target>$(__name__) ] ;
+
+ tag = ps.get("<tag>")
+
+ if tag:
+
+ rule_names = [t[:1] for t in tag if t[0] == '@']
+ if rule_names:
+ if len(tag) > 1:
+ self.manager_.errors()(
+"""<tag>@rulename is present but is not the only <tag> feature""")
+
+ self.name_ = bjam.call(rule_names[0], specified_name, self.type_, ps)
+ else:
+ self.manager_.errors()(
+"""The value of the <tag> feature must be '@rule-nane'""")
+
+ # If there's no tag or the tag rule returned nothing.
+ if not tag or not self.name_:
+ self.name_ = add_prefix_and_suffix(specified_name, self.type_, ps)
+
+ def actualize_no_scanner(self):
+ name = self.actual_name()
+
+ # Do anything only on the first invocation
+ if not self.made_:
+ self.made_[name] = True
+
+ if self.action_:
+ # For non-derived target, we don't care if there
+ # are several virtual targets that refer to the same name.
+ # One case when this is unavoidable is when file name is
+ # main.cpp and two targets have types CPP (for compiling)
+ # and MOCCABLE_CPP (for convertion to H via Qt tools).
+ self.virtual_targets().register_actual_name(name, self)
+
+ for i in self.dependencies_:
+ self.manager_.engine().add_dependency(name, i.actualize())
+
+ self.actualize_location(name)
+ self.actualize_action(name)
+
+ return name
+
+def add_prefix_and_suffix(specified_name, type, property_set):
+ """Appends the suffix appropriate to 'type/property-set' combination
+ to the specified name and returns the result."""
+
+ suffix = b2.build.type.generated_target_suffix(type, property_set)
+
+ # Handle suffixes for which no leading dot is desired. Those are
+ # specified by enclosing them in <...>. Needed by python so it
+ # can create "_d.so" extensions, for example.
+ if get_grist(suffix):
+ suffix = ungrist(suffix)
+ elif suffix:
+ suffix = "." + suffix
+
+ prefix = b2.build.type.generated_target_prefix(type, property_set)
+
+ if specified_name.startswith(prefix):
+ prefix = ""
+
+ if not prefix:
+ prefix = ""
+ if not suffix:
+ suffix = ""
+ return prefix + specified_name + suffix
+
+
+class FileTarget (AbstractFileTarget):
+ """ File target with explicitly known location.
+
+ The file path is determined as
+ - value passed to the 'set_path' method, if any
+ - for derived files, project's build dir, joined with components
+ that describe action's properties. If the free properties
+ are not equal to the project's reference properties
+ an element with name of main target is added.
+ - for source files, project's source dir
+
+ The file suffix is
+ - the value passed to the 'suffix' method, if any, or
+ - the suffix which correspond to the target's type.
+ """
+ def __init__ (self, name, exact, type, project, action = None, path=None):
+ AbstractFileTarget.__init__ (self, name, exact, type, project, action)
+
+ self.path_ = path
+
+ def clone_with_different_type(self, new_type):
+ return FileTarget(self.name_, 1, new_type, self.project_,
+ self.action_, self.path_)
+
+ def actualize_location (self, target):
+ engine = self.project_.manager_.engine ()
+
+ if self.action_:
+ # This is a derived file.
+ path = self.path ()
+ engine.set_target_variable (target, 'LOCATE', path)
+
+ # Make sure the path exists.
+ engine.add_dependency (target, path)
+ common.mkdir(engine, path)
+
+ # It's possible that the target name includes a directory
+ # too, for example when installing headers. Create that
+ # directory.
+ d = os.path.dirname(get_value(target))
+ if d:
+ d = os.path.join(path, d)
+ engine.add_dependency(target, d)
+ common.mkdir(engine, d)
+
+ # For real file target, we create a fake target that
+ # depends on the real target. This allows to run
+ #
+ # bjam hello.o
+ #
+ # without trying to guess the name of the real target.
+ # Note the that target has no directory name, and a special
+ # grist <e>.
+ #
+ # First, that means that "bjam hello.o" will build all
+ # known hello.o targets.
+ # Second, the <e> grist makes sure this target won't be confused
+ # with other targets, for example, if we have subdir 'test'
+ # with target 'test' in it that includes 'test.o' file,
+ # then the target for directory will be just 'test' the target
+ # for test.o will be <ptest/bin/gcc/debug>test.o and the target
+ # we create below will be <e>test.o
+ engine.add_dependency("<e>%s" % get_value(target), target)
+
+ else:
+ # This is a source file.
+ engine.set_target_variable (target, 'SEARCH', self.project_.get ('source-location'))
+
+
+ def path (self):
+ """ Returns the directory for this target.
+ """
+ if not self.path_:
+ if self.action_:
+ p = self.action_.properties ()
+ target_path = p.target_path ()
+
+ if target_path [1] == True:
+ # Indicates that the path is relative to
+ # build dir.
+ target_path = os.path.join (self.project_.build_dir (), target_path [0])
+
+ # Store the computed path, so that it's not recomputed
+ # any more
+ self.path_ = target_path
+
+ return self.path_
+
+
+class NotFileTarget(AbstractFileTarget):
+
+ def __init__(self, name, project):
+ AbstractFileTarget.__init__(name, project)
+
+ def path(self):
+ """Returns nothing, to indicate that target path is not known."""
+ return None
+
+ def actualize_location(self, target):
+ bjam.call("NOTFILE", target)
+ bjam.call("ALWAYS", taget)
+
+
+class Action:
+ """ Class which represents an action.
+ Both 'targets' and 'sources' should list instances of 'VirtualTarget'.
+ Action name should name a rule with this prototype
+ rule action_name ( targets + : sources * : properties * )
+ Targets and sources are passed as actual jam targets. The rule may
+ not establish dependency relationship, but should do everything else.
+ """
+ def __init__ (self, manager, sources, action_name, prop_set):
+ self.sources_ = sources
+ self.action_name_ = action_name
+ if not prop_set:
+ prop_set = property_set.empty()
+ self.properties_ = prop_set
+
+ self.manager_ = manager
+ self.engine_ = self.manager_.engine ()
+ self.targets_ = []
+
+ # Indicates whether this has been actualized or not.
+ self.actualized_ = False
+
+ self.dependency_only_sources_ = []
+ self.actual_sources_ = []
+
+
+ def add_targets (self, targets):
+ self.targets_ += targets
+
+ def targets (self):
+ return self.targets_
+
+ def sources (self):
+ return self.sources_
+
+ def action_name (self):
+ return self.action_name_
+
+ def properties (self):
+ return self.properties_
+
+ def actualize (self):
+ """ Generates actual build instructions.
+ """
+ if self.actualized_:
+ return
+
+ self.actualized_ = True
+
+ ps = self.properties ()
+ properties = self.adjust_properties (ps)
+ actual_targets = []
+
+ for i in self.targets ():
+ actual_targets.append (i.actualize ())
+
+ self.actualize_sources (self.sources (), properties)
+
+ self.engine_.add_dependency (actual_targets, self.actual_sources_ + self.dependency_only_sources_)
+
+ raw_properties = properties.raw ()
+
+ # FIXME: check the comment below. Was self.action_name_ [1]
+ # Action name can include additional argument to rule, which should not
+ # be passed to 'set-target-variables'
+ # FIXME: breaking circular dependency
+ import toolset
+ toolset.set_target_variables (self.manager_, self.action_name_, actual_targets, raw_properties)
+
+ engine = self.manager_.engine ()
+
+ self.manager_.engine ().set_update_action (self.action_name_, actual_targets, self.actual_sources_,
+ properties)
+
+ # Since we set up creating action here, we also set up
+ # action for cleaning up
+ self.manager_.engine ().set_update_action ('common.Clean', 'clean-all',
+ actual_targets, None)
+
+ return actual_targets
+
+ def actualize_source_type (self, sources, prop_set):
+ """ Helper for 'actualize_sources'.
+ For each passed source, actualizes it with the appropriate scanner.
+ Returns the actualized virtual targets.
+ """
+ result = []
+ for i in sources:
+ scanner = None
+
+# FIXME: what's this?
+# if isinstance (i, str):
+# i = self.manager_.get_object (i)
+
+ if i.type ():
+ scanner = type.get_scanner (i.type (), prop_set)
+
+ result.append (i.actualize (scanner))
+
+ return result
+
+ def actualize_sources (self, sources, prop_set):
+ """ Creates actual jam targets for sources. Initializes two member
+ variables:
+ 'self.actual_sources_' -- sources which are passed to updating action
+ 'self.dependency_only_sources_' -- sources which are made dependencies, but
+ are not used otherwise.
+
+ New values will be *appended* to the variables. They may be non-empty,
+ if caller wants it.
+ """
+ dependencies = self.properties_.get ('<dependency>')
+
+ self.dependency_only_sources_ += self.actualize_source_type (dependencies, prop_set)
+ self.actual_sources_ += self.actualize_source_type (sources, prop_set)
+
+ # This is used to help bjam find dependencies in generated headers
+ # in other main targets.
+ # Say:
+ #
+ # make a.h : ....... ;
+ # exe hello : hello.cpp : <implicit-dependency>a.h ;
+ #
+ # However, for bjam to find the dependency the generated target must
+ # be actualized (i.e. have the jam target). In the above case,
+ # if we're building just hello ("bjam hello"), 'a.h' won't be
+ # actualized unless we do it here.
+ implicit = self.properties_.get("<implicit-dependency>")
+ for i in implicit:
+ i.actualize()
+
+ def adjust_properties (self, prop_set):
+ """ Determines real properties when trying building with 'properties'.
+ This is last chance to fix properties, for example to adjust includes
+ to get generated headers correctly. Default implementation returns
+ its argument.
+ """
+ return prop_set
+
+
+class NullAction (Action):
+ """ Action class which does nothing --- it produces the targets with
+ specific properties out of nowhere. It's needed to distinguish virtual
+ targets with different properties that are known to exist, and have no
+ actions which create them.
+ """
+ def __init__ (self, manager, prop_set):
+ Action.__init__ (self, manager, None, None, prop_set)
+
+ def actualize (self):
+ if not self.actualized_:
+ self.actualized_ = True
+
+ for i in self.targets ():
+ i.actualize ()
+
+class NonScanningAction(Action):
+ """Class which acts exactly like 'action', except that the sources
+ are not scanned for dependencies."""
+
+ def __init__(self, sources, action_name, property_set):
+ #FIXME: should the manager parameter of Action.__init__
+ #be removed? -- Steven Watanabe
+ Action.__init__(b2.manager.get_manager(), sources, action_name, property_set)
+
+ def actualize_source_type(self, sources, property_set):
+
+ return [x for source in sources for x in i.actualize()]
+
+def traverse (target, include_roots = False, include_sources = False):
+ """ Traverses the dependency graph of 'target' and return all targets that will
+ be created before this one is created. If root of some dependency graph is
+ found during traversal, it's either included or not, dependencing of the
+ value of 'include_roots'. In either case, sources of root are not traversed.
+ """
+ result = []
+
+ if target.action ():
+ action = target.action ()
+
+ # This includes 'target' as well
+ result += action.targets ()
+
+ for t in action.sources ():
+
+ # FIXME:
+ # TODO: see comment in Manager.register_object ()
+ #if not isinstance (t, VirtualTarget):
+ # t = target.project_.manager_.get_object (t)
+
+ if not t.root ():
+ result += traverse (t, include_roots, include_sources)
+
+ elif include_roots:
+ result.append (t)
+
+ elif include_sources:
+ result.append (target)
+
+ return result
+
+def clone_action (action, new_project, new_action_name, new_properties):
+ """Takes an 'action' instances and creates new instance of it
+ and all produced target. The rule-name and properties are set
+ to 'new-rule-name' and 'new-properties', if those are specified.
+ Returns the cloned action."""
+
+ if not new_action_name:
+ new_action_name = action.action_name()
+
+ if not new_properties:
+ new_properties = action.properties()
+
+ closed_action = action.__class__(action.sources(), new_action_name,
+ new_properties)
+
+ cloned_targets = []
+ for target in action.targets():
+
+ n = target.name()
+ # Don't modify the name of the produced targets. Strip the directory f
+ cloned_target = FileTarget(n, 1, target.type(), new_project,
+ cloned_action)
+
+ d = target.dependencies()
+ if d:
+ cloned_target.depends(d)
+ cloned_target.root(target.root())
+ cloned_target.creating_subvariant(target.creating_subvariant())
+
+ cloned_targets.append(cloned_target)
+
+ return cloned_action
+
+class Subvariant:
+
+ def __init__ (self, main_target, prop_set, sources, build_properties, sources_usage_requirements, created_targets):
+ """
+ main_target: The instance of MainTarget class
+ prop_set: Properties requested for this target
+ sources:
+ build_properties: Actually used properties
+ sources_usage_requirements: Properties propagated from sources
+ created_targets: Top-level created targets
+ """
+ self.main_target_ = main_target
+ self.properties_ = prop_set
+ self.sources_ = sources
+ self.build_properties_ = build_properties
+ self.sources_usage_requirements_ = sources_usage_requirements
+ self.created_targets_ = created_targets
+
+ self.usage_requirements_ = None
+
+ # Pre-compose the list of other dependency graphs, on which this one
+ # depends
+ deps = build_properties.get ('<implicit-dependency>')
+
+ self.other_dg_ = []
+ for d in deps:
+ # FIXME: the property must have the actual object here, not a string.
+ value = replace_grist (d, '')
+ self.other_dg_.append (value.creating_subvariant ())
+
+ self.other_dg_ = unique (self.other_dg_)
+
+ self.implicit_includes_cache_ = {}
+ self.target_directories_ = None
+
+ def main_target (self):
+ return self.main_target_
+
+ def created_targets (self):
+ return self.created_targets_
+
+ def requested_properties (self):
+ return self.properties_
+
+ def build_properties (self):
+ return self.build_properties_
+
+ def sources_usage_requirements (self):
+ return self.sources_usage_requirements_
+
+ def set_usage_requirements (self, usage_requirements):
+ self.usage_requirements_ = usage_requirements
+
+ def usage_requirements (self):
+ return self.usage_requirements_
+
+ def all_referenced_targets(self):
+ """Returns all targets referenced by this subvariant,
+ 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:
+ r.append(t.creating_subvariant)
+ r = unique(r)
+
+ for s in r:
+ if s != self:
+ all_targets.extend(s.all_referenced_targets())
+
+ return all_targets
+
+ def implicit_includes (self, feature, target_type):
+ """ Returns the properties which specify implicit include paths to
+ generated headers. This traverses all targets in this subvariant,
+ and subvariants referred by <implcit-dependecy>properties.
+ For all targets which are of type 'target-type' (or for all targets,
+ if 'target_type' is not specified), the result will contain
+ <$(feature)>path-to-that-target.
+ """
+
+ if not target_type:
+ key = feature
+ else:
+ key = feature + "-" + target_type
+
+
+ result = self.implicit_includes_cache_.get(key)
+ if not result:
+ target_paths = self.all_target_directories(target_type)
+ target_paths = unique(target_paths)
+ result = ["<%s>%s" % (feature, p) for p in target_paths]
+ self.implicit_includes_cache_[key] = result
+
+ return result
+
+ def all_target_directories(self, target_type = None):
+ # TODO: does not appear to use target_type in deciding
+ # if we've computed this already.
+ if not self.target_directories_:
+ self.target_directories_ = self.compute_target_directories(target_type)
+ return self.target_directories_
+
+ 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):
+ result.append(t.path())
+
+ for d in self.other_dg_:
+ result.extend(d.all_target_directories(target_type))
+
+ result = unique(result)
+ return result

Added: trunk/tools/build/v2/build_system.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/build_system.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,437 @@
+# Status: being ported by Vladimir Prus.
+
+# Copyright 2003, 2005 Dave Abrahams
+# Copyright 2006 Rene Rivera
+# Copyright 2003, 2004, 2005, 2006, 2007 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)
+
+from b2.build.engine import Engine
+from b2.manager import Manager
+from b2.util.path import glob
+from b2.build import feature, property_set
+import b2.build.virtual_target
+from b2.build.targets import ProjectTarget
+from b2.util.sequence import unique
+import b2.build.build_request
+from b2.build.errors import ExceptionWithUserContext
+import b2.tools.common
+
+import bjam
+
+import os
+import sys
+
+# FIXME:
+# Returns the location of the build system. The primary use case
+# is building Boost, where it's sometimes needed to get location
+# of other components (like BoostBook files), and it's convenient
+# to use location relatively to Boost.Build path.
+#rule location ( )
+#{
+# local r = [ modules.binding build-system ] ;
+# return $(r:P) ;
+#}
+
+# FIXME:
+
+def get_boolean_option(name):
+ match = "--" + name
+ if match in argv:
+ return 1
+ else:
+ return 0
+
+def get_string_option(name):
+ match = "--" + name + "="
+
+ for arg in argv:
+ if arg.startswith(match):
+ return arg[len(match):]
+ return None
+
+def home_directories():
+ if os.name == "nt":
+ result = set()
+ try:
+ result.add(os.environ['HOMEDRIVE'] + os.environ['HOMEPATH'])
+ result.add(os.environ['HOME'])
+ result.add(os.environ['USERPROFILE'])
+ except KeyError:
+ pass
+ return list(result)
+ else:
+ return [os.environ['HOME']]
+
+ignore_config = 0
+debug_config = 0
+
+def load_config(manager, basename, path):
+ """Unless ignore-config is set, search configuration
+ basename.jam in path and loads it. The jamfile module
+ for that file will be loaded 'basename'."""
+
+ if not ignore_config:
+ found = glob(path, [basename + ".jam"])
+ if found:
+ found = found[0]
+ if debug_config:
+ print "notice: searching '%s' for '%s.jam'" % (path, basename)
+ if found:
+ print "notice: loading %s.jam from %s" % (basename, found)
+
+ manager.projects().load_standalone(basename, found)
+
+def main():
+
+ global argv
+ argv = bjam.variable("ARGV")
+
+ # FIXME: document this option.
+ if "--profiling" in argv:
+ import cProfile
+ import pstats
+ cProfile.runctx('main_real()', globals(), locals(), "stones.prof")
+
+ stats = pstats.Stats("stones.prof")
+ stats.strip_dirs()
+ stats.sort_stats('time', 'calls')
+ stats.print_callers(20)
+ else:
+ main_real()
+
+def main_real():
+
+ global ignore_config
+ global debug_config
+
+ boost_build_path = bjam.variable("BOOST_BUILD_PATH")
+
+ engine = Engine()
+
+ global_build_dir = get_string_option("build-dir")
+ debug_config = get_boolean_option("debug-configuration")
+
+ manager = Manager(engine, global_build_dir)
+
+ # This module defines types and generator and what not,
+ # and depends on manager's existence
+ import b2.tools.builtin
+
+
+ # Check if we can load 'test-config.jam'. If we can, load it and
+ # ignore user configs.
+
+ test_config = glob(boost_build_path, ["test-config.jam"])
+ if test_config:
+ test_config = test_config[0]
+
+ if test_config:
+ if debug_config:
+ print "notice: loading testing-config.jam from '%s'" % test_config
+ print "notice: user-config.jam and site-config.jam will be ignored"
+
+ manager.projects().load_standalone("test-config", test_config)
+
+
+ ignore_config = test_config or get_boolean_option("ignore-config")
+ user_path = home_directories() + boost_build_path
+
+ site_path = ["/etc"] + user_path
+ if bjam.variable("OS") in ["NT", "CYGWIN"]:
+ site_path = [os.environ("SystemRoot")] + user_path
+
+ load_config(manager, "site-config", site_path)
+
+ user_config_path = get_string_option("user-config")
+ if not user_config_path:
+ user_config_path = os.environ.get("BOOST_BUILD_USER_CONFIG")
+
+ if user_config_path:
+ if debug_config:
+ print "Loading explicitly specifier user configuration file:"
+ print " %s" % user_config_path
+
+ manager.projects().load_standalone("user-config", user_config_path)
+
+ else:
+ load_config(manager, "user-config", user_path)
+
+
+# FIXME:
+## #
+## # Autoconfigure toolsets based on any instances of --toolset=xx,yy,...zz or
+## # toolset=xx,yy,...zz in the command line
+## #
+## local option-toolsets = [ regex.split-list [ MATCH ^--toolset=(.*) : $(argv) ] : "," ] ;
+## local feature-toolsets = [ regex.split-list [ MATCH ^toolset=(.*) : $(argv) ] : "," ] ;
+
+## # if the user specified --toolset=..., we need to add toolset=... to
+## # the build request
+## local extra-build-request ;
+
+ extra_build_request = []
+
+## if ! $(ignore-config)
+## {
+## for local t in $(option-toolsets) $(feature-toolsets)
+## {
+## # Parse toolset-version/properties
+## local (t-v,t,v) = [ MATCH (([^-/]+)-?([^/]+)?)/?.* : $(t) ] ;
+## local toolset-version = $((t-v,t,v)[1]) ;
+## local toolset = $((t-v,t,v)[2]) ;
+## local version = $((t-v,t,v)[3]) ;
+
+## if $(debug-config)
+## {
+## ECHO notice: [cmdline-cfg] Detected command-line request for
+## $(toolset-version): toolset= \"$(toolset)\" "version= \""$(version)\" ;
+## }
+
+## local known ;
+
+## # if the toolset isn't known, configure it now.
+## if $(toolset) in [ feature.values <toolset> ]
+## {
+## known = true ;
+## }
+
+## if $(known) && $(version)
+## && ! [ feature.is-subvalue toolset : $(toolset) : version : $(version) ]
+## {
+## known = ;
+## }
+
+## if ! $(known)
+## {
+## if $(debug-config)
+## {
+## ECHO notice: [cmdline-cfg] toolset $(toolset-version)
+## not previously configured; configuring now ;
+## }
+## toolset.using $(toolset) : $(version) ;
+## }
+## else
+## {
+## if $(debug-config)
+## {
+## ECHO notice: [cmdline-cfg] toolset $(toolset-version) already configured ;
+## }
+## }
+
+## # make sure we get an appropriate property into the build request in
+## # case the user used the "--toolset=..." form
+## if ! $(t) in $(argv)
+## && ! $(t) in $(feature-toolsets)
+## {
+## if $(debug-config)
+## {
+## ECHO notice: [cmdline-cfg] adding toolset=$(t) "to build request." ;
+## }
+## extra-build-request += toolset=$(t) ;
+## }
+## }
+## }
+
+
+# FIXME:
+## if USER_MODULE in [ RULENAMES ]
+## {
+## USER_MODULE site-config user-config ;
+## }
+
+ if get_boolean_option("version"):
+ # FIXME: Move to a separate module. Include bjam
+ # verision.
+ print "Boost.Build M15 (Python port in development)"
+ sys.exit(0)
+
+ b2.tools.common.init(manager)
+
+ # We always load project in "." so that 'use-project' directives has
+ # any chance of been seen. Otherwise, we won't be able to refer to
+ # subprojects using target ids.
+
+ current_project = None
+ projects = manager.projects()
+ if projects.find(".", "."):
+ current_project = projects.target(projects.load("."))
+
+ # FIXME: revive this logic, when loading of gcc works
+ if not feature.values("<toolset>") and not ignore_config and 0:
+ default_toolset = "gcc" ;
+ if bjam.variable("OS") == "NT":
+ default_toolset = "msvc"
+
+ print "warning: No toolsets are configured." ;
+ print "warning: Configuring default toolset '%s'" % default_toolset
+ print "warning: If the default is wrong, you may not be able to build C++ programs."
+ print "warning: Use the \"--toolset=xxxxx\" option to override our guess."
+ print "warning: For more configuration options, please consult"
+ print "warning: http://boost.org/boost-build2/doc/html/bbv2/advanced/configuration.html"
+
+ projects.project_rules().using([default_toolset])
+
+ (target_ids, properties) = b2.build.build_request.from_command_line(
+ argv[1:] + extra_build_request)
+
+ if properties:
+ expanded = b2.build.build_request.expand_no_defaults(properties)
+ xexpanded = []
+ for e in expanded:
+ xexpanded.append(property_set.create(feature.split(e)))
+ expanded = xexpanded
+ else:
+ expanded = [property_set.empty()]
+
+ targets = []
+
+ clean = get_boolean_option("clean")
+ clean_all = get_boolean_option("clean-all")
+
+
+ bjam_targets = []
+
+ # Given a target id, try to find and return corresponding target.
+ # This is only invoked when there's no Jamfile in "."
+ # This code somewhat duplicates code in project-target.find but we can't reuse
+ # that code without project-targets instance.
+ def find_target (target_id):
+ split = target_id.split("//")
+ pm = None
+ if len(split) > 1:
+ pm = projects.find(split[0], ".")
+ else:
+ pm = projects.find(target_id, ".")
+
+ result = None
+ if pm:
+ result = projects.target(pm)
+
+ if len(split) > 1:
+ result = result.find(split[1])
+
+ if not current_project and not target_ids:
+ print "error: no Jamfile in current directory found, and no target references specified."
+ sys.exit(1)
+
+ for id in target_ids:
+ if id == "clean":
+ clean = 1
+ else:
+ t = None
+ if current_project:
+ t = current_project.find(id, no_error=1)
+ else:
+ t = find_target(id)
+
+ if not t:
+ print "notice: could not find main target '%s'" % id
+ print "notice: assuming it's a name of file to create " ;
+ bjam_targets.append(id)
+ else:
+ targets.append(t)
+
+ if not targets:
+ targets = [projects.target(projects.module_name("."))]
+
+ virtual_targets = []
+
+ # Virtual targets obtained when building main targets references on
+ # the command line. When running
+ #
+ # bjam --clean main_target
+ #
+ # we want to clean the files that belong only to that main target,
+ # so we need to record which targets are produced.
+ results_of_main_targets = []
+
+ for p in expanded:
+ manager.set_command_line_free_features(property_set.create(p.free()))
+
+ for t in targets:
+ try:
+ g = t.generate(p)
+ if not isinstance(t, ProjectTarget):
+ results_of_main_targets.extend(g.targets())
+ virtual_targets.extend(g.targets())
+ except ExceptionWithUserContext, e:
+ e.report()
+ except Exception:
+ raise
+
+ # The cleaning is tricky. Say, if
+ # user says:
+ #
+ # bjam --clean foo
+ #
+ # where 'foo' is a directory, then we want to clean targets
+ # which are in 'foo' or in any children Jamfiles, but not in any
+ # unrelated Jamfiles. So, we collect the list of project under which
+ # cleaning is allowed.
+ #
+ projects_to_clean = []
+ targets_to_clean = []
+ if clean or clean_all:
+ for t in targets:
+ if isinstance(t, ProjectTarget):
+ projects_to_clean.append(t.project_module())
+
+ for t in results_of_main_targets:
+ # Don't include roots or sources.
+ targets_to_clean += b2.build.virtual_target.traverse(t)
+
+ targets_to_clean = unique(targets_to_clean)
+
+ is_child_cache_ = {}
+
+ # Returns 'true' if 'project' is a child of 'current-project',
+ # possibly indirect, or is equal to 'project'.
+ # Returns 'false' otherwise.
+ def is_child (project):
+
+ r = is_child_cache_.get(project, None)
+ if not r:
+ if project in projects_to_clean:
+ r = 1
+ else:
+ parent = manager.projects().attribute(project, "parent-module")
+ if parent and parent != "user-config":
+ r = is_child(parent)
+ else:
+ r = 0
+
+ is_child_cache_[project] = r
+
+ return r
+
+ actual_targets = []
+ for t in virtual_targets:
+ actual_targets.append(t.actualize())
+
+
+ bjam.call("NOTFILE", "all")
+ bjam.call("DEPENDS", "all", actual_targets)
+
+ if bjam_targets:
+ bjam.call("UPDATE", ["<e>%s" % x for x in bjam_targets])
+ elif clean_all:
+ bjam.call("UPDATE", "clean-all")
+ elif clean:
+ to_clean = []
+ for t in manager.virtual_targets().all_targets():
+ p = t.project()
+
+ # Remove only derived targets.
+ if t.action() and \
+ (t in targets_to_clean or is_child(p.project_module())):
+ to_clean.append(t)
+
+ to_clean_actual = [t.actualize() for t in to_clean]
+ manager.engine().set_update_action('common.Clean', 'clean',
+ to_clean_actual, None)
+
+ bjam.call("UPDATE", "clean")
+
+ else:
+ bjam.call("UPDATE", "all")

Added: trunk/tools/build/v2/exceptions.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/exceptions.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,44 @@
+# Copyright Pedro Ferreira 2005. Distributed under the Boost
+# Software License, Version 1.0. (See accompanying
+# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+# TODO: add more exception types?
+
+class BaseException (Exception):
+ def __init__ (self, message = ''): Exception.__init__ (self, message)
+
+class UserError (BaseException):
+ def __init__ (self, message = ''): BaseException.__init__ (self, message)
+
+class FeatureConflict (BaseException):
+ def __init__ (self, message = ''): BaseException.__init__ (self, message)
+
+class InvalidSource (BaseException):
+ def __init__ (self, message = ''): BaseException.__init__ (self, message)
+
+class InvalidFeature (BaseException):
+ def __init__ (self, message = ''): BaseException.__init__ (self, message)
+
+class InvalidProperty (BaseException):
+ def __init__ (self, message = ''): BaseException.__init__ (self, message)
+
+class InvalidValue (BaseException):
+ def __init__ (self, message = ''): BaseException.__init__ (self, message)
+
+class InvalidAttribute (BaseException):
+ def __init__ (self, message = ''): BaseException.__init__ (self, message)
+
+class AlreadyDefined (BaseException):
+ def __init__ (self, message = ''): BaseException.__init__ (self, message)
+
+class IllegalOperation (BaseException):
+ def __init__ (self, message = ''): BaseException.__init__ (self, message)
+
+class Recursion (BaseException):
+ def __init__ (self, message = ''): BaseException.__init__ (self, message)
+
+class NoBestMatchingAlternative (BaseException):
+ def __init__ (self, message = ''): BaseException.__init__ (self, message)
+
+class NoAction (BaseException):
+ def __init__ (self, message = ''): BaseException.__init__ (self, message)

Modified: trunk/tools/build/v2/kernel/bootstrap.jam
==============================================================================
--- trunk/tools/build/v2/kernel/bootstrap.jam (original)
+++ trunk/tools/build/v2/kernel/bootstrap.jam 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -111,6 +111,8 @@
     BOOST_BUILD_PATH += $(whereami:D)/$(subdirs) ;
 
     modules.poke .ENVIRON : BOOST_BUILD_PATH : $(BOOST_BUILD_PATH) ;
+
+ modules.poke : EXTRA_PYTHONPATH : $(whereami) ;
 }
 
 # Reload the modules, to clean up things. The modules module can tolerate
@@ -129,11 +131,110 @@
 #
 if ! $(dont-build)
 {
- # Allow users to override the build system file from the
- # command-line (mostly for testing)
- local build-system = [ MATCH --build-system=(.*) : $(ARGV) ] ;
- build-system ?= build-system ;
+ if ! [ MATCH (--python) : $(ARGV) ]
+ {
+ # Allow users to override the build system file from the
+ # command-line (mostly for testing)
+ local build-system = [ MATCH --build-system=(.*) : $(ARGV) ] ;
+ build-system ?= build-system ;
 
- # Use last element in case of multiple command-line options
- import $(build-system[-1]) ;
+ # Use last element in case of multiple command-line options
+ import $(build-system[-1]) ;
+ }
+ else
+ {
+ ECHO "Boost.Build V2 Python port (experimental)" ;
+
+ # Define additional interface that is exposed to Python code. Python code will
+ # also have access to select bjam builtins in the 'bjam' module, but some
+ # things are easier to define outside C.
+ module python_interface
+ {
+ rule load ( module-name : location )
+ {
+ USER_MODULE $(module-name) ;
+ # Make all rules in the loaded module available in
+ # the global namespace, so that we don't have
+ # to bother specifying "right" module when calling
+ # from Python.
+ module $(module-name)
+ {
+ __name__ = $(1) ;
+ include $(2) ;
+ local rules = [ RULENAMES $(1) ] ;
+ IMPORT $(1) : $(rules) : $(1) : $(1).$(rules) ;
+ }
+ }
+
+ rule peek ( module-name ? : variables + )
+ {
+ module $(<)
+ {
+ return $($(>)) ;
+ }
+ }
+
+ rule set-variable ( module-name : name : value * )
+ {
+ module $(<)
+ {
+ $(>) = $(3) ;
+ }
+ }
+
+ rule set-top-level-targets ( targets * )
+ {
+ DEPENDS all : $(targets) ;
+ }
+
+ rule set-update-action ( action : targets * : sources * : properties * )
+ {
+ $(action) $(targets) : $(sources) : $(properties) ;
+ }
+
+ rule set-target-variable ( targets + : variable : value * : append ? )
+ {
+ if $(append)
+ {
+ $(variable) on $(targets) += $(value) ;
+ }
+ else
+ {
+ $(variable) on $(targets) = $(value) ;
+ }
+ }
+
+ rule get-target-variable ( target : variable )
+ {
+ return [ on $(target) return $($(variable)) ] ;
+ }
+
+ rule import-rules-from-parent ( parent-module : this-module : user-rules )
+ {
+ IMPORT $(parent-module) : $(user-rules) : $(this-module) : $(user-rules) ;
+ EXPORT $(this-module) : $(user-rules) ;
+ }
+
+ rule mark-included ( targets * : includes * ) {
+ INCLUDES $(targets) : $(INCLUDES) ;
+ }
+ }
+
+ PYTHON_IMPORT_RULE bootstrap : bootstrap : PyBB : bootstrap ;
+ modules.poke PyBB : root : [ NORMALIZE_PATH $(.bootstrap-file:DT)/.. ] ;
+
+ module PyBB
+ {
+ bootstrap $(root) ;
+ }
+
+
+ #PYTHON_IMPORT_RULE boost.build.build_system : main : PyBB : main ;
+
+ #module PyBB
+ #{
+ # main ;
+ #}
+
+ }
 }

Added: trunk/tools/build/v2/manager.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/manager.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,132 @@
+# Copyright Pedro Ferreira 2005. Distributed under the Boost
+# Software License, Version 1.0. (See accompanying
+# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+import bjam
+
+# To simplify implementation of tools level, we'll
+# have a global variable keeping the current manager.
+the_manager = None
+def get_manager():
+ return the_manager
+
+class Manager:
+ """ This class is a facade to the Boost.Build system.
+ It serves as the root to access all data structures in use.
+ """
+
+ def __init__ (self, engine, global_build_dir):
+ """ Constructor.
+ engine: the build engine that will actually construct the targets.
+ """
+ from build.virtual_target import VirtualTargetRegistry
+ from build.targets import TargetRegistry
+ from build.project import ProjectRegistry
+ from build.scanner import ScannerRegistry
+ from build.errors import Errors
+ from b2.util.logger import NullLogger
+ from build import build_request, property_set, feature
+
+ self.engine_ = engine
+ self.virtual_targets_ = VirtualTargetRegistry (self)
+ self.projects_ = ProjectRegistry (self, global_build_dir)
+ self.targets_ = TargetRegistry ()
+ self.logger_ = NullLogger ()
+ self.scanners_ = ScannerRegistry (self)
+ self.argv_ = bjam.variable("ARGV")
+ self.boost_build_path_ = bjam.variable("BOOST_BUILD_PATH")
+ self.errors_ = Errors()
+ self.command_line_free_features_ = property_set.empty()
+
+ # Object Map.
+ # TODO: This is a kludge: maps object names to the actual instances.
+ # Sometimes, objects are stored in properties, along with some grist.
+ # This map is used to store the value and return an id, which can be later on used to retriev it back.
+ self.object_map_ = {}
+
+ global the_manager
+ the_manager = self
+
+ def scanners (self):
+ return self.scanners_
+
+ def engine (self):
+ return self.engine_
+
+ def virtual_targets (self):
+ return self.virtual_targets_
+
+ def targets (self):
+ return self.targets_
+
+ def projects (self):
+ return self.projects_
+
+ def argv (self):
+ return self.argv_
+
+ def logger (self):
+ return self.logger_
+
+ def set_logger (self, logger):
+ self.logger_ = logger
+
+ def errors (self):
+ return self.errors_
+
+ def getenv(self, name):
+ return bjam.variable(name)
+
+ def boost_build_path(self):
+ return self.boost_build_path_
+
+ def command_line_free_features(self):
+ return self.command_line_free_features_
+
+ def set_command_line_free_features(self, v):
+ self.command_line_free_features_ = v
+
+ def register_object (self, value):
+ """ Stores an object in a map and returns a key that can be used to retrieve it.
+ """
+ key = 'object_registry_' + str (value)
+ self.object_map_ [key] = value
+ return key
+
+ def get_object (self, key):
+ """ Returns a previously registered object.
+ """
+ if not isinstance (key, str):
+ # Probably it's the object itself.
+ return key
+
+ return self.object_map_ [key]
+
+ def construct (self, properties = [], targets = []):
+ """ Constructs the dependency graph.
+ properties: the build properties.
+ targets: the targets to consider. If none is specified, uses all.
+ """
+ if not targets:
+ for name, project in self.projects ().projects ():
+ targets.append (project.target ())
+
+ property_groups = build_request.expand_no_defaults (properties)
+
+ virtual_targets = []
+ build_prop_sets = []
+ for p in property_groups:
+ build_prop_sets.append (property_set.create (feature.split (p)))
+
+ if not build_prop_sets:
+ build_prop_sets = [property_set.empty ()]
+
+ for build_properties in build_prop_sets:
+ for target in targets:
+ result = target.generate (build_properties)
+ virtual_targets.extend (result.targets ())
+
+ actual_targets = []
+ for virtual_target in virtual_targets:
+ actual_targets.extend (virtual_target.actualize ())
+

Added: trunk/tools/build/v2/tools/__init__.py
==============================================================================

Added: trunk/tools/build/v2/tools/builtin.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/tools/builtin.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,722 @@
+# Status: minor updates by Steven Watanabe to make gcc work
+#
+# Copyright (C) Vladimir Prus 2002. Permission to copy, use, modify, sell and
+# distribute this software is granted provided this copyright notice appears in
+# all copies. This software is provided "as is" without express or implied
+# warranty, and with no claim as to its suitability for any purpose.
+
+""" Defines standard features and rules.
+"""
+
+import sys
+from b2.build import feature, property, virtual_target, generators, type, property_set, scanner
+from b2.util.utility import *
+from b2.util import path, regex
+import b2.tools.types
+from b2.manager import get_manager
+
+# Records explicit properties for a variant.
+# The key is the variant name.
+__variant_explicit_properties = {}
+
+def reset ():
+ """ Clear the module state. This is mainly for testing purposes.
+ """
+ global __variant_explicit_properties
+
+ __variant_explicit_properties = {}
+
+def variant (name, parents_or_properties, explicit_properties = []):
+ """ Declares a new variant.
+ First determines explicit properties for this variant, by
+ refining parents' explicit properties with the passed explicit
+ properties. The result is remembered and will be used if
+ this variant is used as parent.
+
+ Second, determines the full property set for this variant by
+ adding to the explicit properties default values for all properties
+ which neither present nor are symmetric.
+
+ Lastly, makes appropriate value of 'variant' property expand
+ to the full property set.
+ name: Name of the variant
+ parents_or_properties: Specifies parent variants, if
+ 'explicit_properties' are given,
+ and explicit_properties otherwise.
+ explicit_properties: Explicit properties.
+ """
+ parents = []
+ if not explicit_properties:
+ if get_grist (parents_or_properties [0]):
+ explicit_properties = parents_or_properties
+
+ else:
+ parents = parents_or_properties
+
+ else:
+ parents = parents_or_properties
+
+ # The problem is that we have to check for conflicts
+ # between base variants.
+ if len (parents) > 1:
+ raise BaseException ("Multiple base variants are not yet supported")
+
+ inherited = []
+ # Add explicitly specified properties for parents
+ for p in parents:
+ # TODO: the check may be stricter
+ if not feature.is_implicit_value (p):
+ raise BaseException ("Invalid base varaint '%s'" % p)
+
+ inherited += __variant_explicit_properties [p]
+
+ property.validate (explicit_properties)
+ explicit_properties = property.refine (inherited, explicit_properties)
+
+ # Record explicitly specified properties for this variant
+ # We do this after inheriting parents' properties, so that
+ # they affect other variants, derived from this one.
+ __variant_explicit_properties [name] = explicit_properties
+
+ feature.extend('variant', [name])
+ feature.compose (replace_grist (name, '<variant>'), explicit_properties)
+
+__os_names = """
+ amiga aix bsd cygwin darwin dos emx freebsd hpux iphone linux netbsd
+ openbsd osf qnx qnxnto sgi solaris sun sunos svr4 sysv ultrix unix unixware
+ vms windows
+""".split()
+
+# Translates from bjam current OS to the os tags used in host-os and target-os,
+# i.e. returns the running host-os.
+#
+def default_host_os():
+ host_os = os_name()
+ if host_os not in (x.upper() for x in __os_names):
+ if host_os == 'NT': host_os = 'windows'
+ elif host_os == 'AS400': host_os = 'unix'
+ elif host_os == 'MINGW': host_os = 'windows'
+ elif host_os == 'BSDI': host_os = 'bsd'
+ elif host_os == 'COHERENT': host_os = 'unix'
+ elif host_os == 'DRAGONFLYBSD': host_os = 'bsd'
+ elif host_os == 'IRIX': host_os = 'sgi'
+ elif host_os == 'MACOSX': host_os = 'darwin'
+ elif host_os == 'KFREEBSD': host_os = 'freebsd'
+ elif host_os == 'LINUX': host_os = 'linux'
+ else: host_os = 'unix'
+ return host_os.lower()
+
+def register_globals ():
+ """ Registers all features and variants declared by this module.
+ """
+
+ # This feature is used to determine which OS we're on.
+ # In future, this may become <target-os> and <host-os>
+ # TODO: check this. Compatibility with bjam names? Subfeature for version?
+ os = sys.platform
+ feature.feature ('os', [os], ['propagated', 'link-incompatible'])
+
+
+ # The two OS features define a known set of abstract OS names. The host-os is
+ # the OS under which bjam is running. Even though this should really be a fixed
+ # property we need to list all the values to prevent unknown value errors. Both
+ # set the default value to the current OS to account for the default use case of
+ # building on the target OS.
+ feature.feature('host-os', __os_names)
+ feature.set_default('host-os', default_host_os())
+
+ feature.feature('target-os', __os_names, ['propagated', 'link-incompatible'])
+ feature.set_default('target-os', default_host_os())
+
+ feature.feature ('toolset', [], ['implicit', 'propagated' ,'symmetric'])
+
+ feature.feature ('stdlib', ['native'], ['propagated', 'composite'])
+
+ feature.feature ('link', ['shared', 'static'], ['propagated'])
+ feature.feature ('runtime-link', ['shared', 'static'], ['propagated'])
+ feature.feature ('runtime-debugging', ['on', 'off'], ['propagated'])
+
+
+ feature.feature ('optimization', ['off', 'speed', 'space'], ['propagated'])
+ feature.feature ('profiling', ['off', 'on'], ['propagated'])
+ feature.feature ('inlining', ['off', 'on', 'full'], ['propagated'])
+
+ feature.feature ('threading', ['single', 'multi'], ['propagated'])
+ feature.feature ('rtti', ['on', 'off'], ['propagated'])
+ feature.feature ('exception-handling', ['on', 'off'], ['propagated'])
+ feature.feature ('debug-symbols', ['on', 'off'], ['propagated'])
+ feature.feature ('define', [], ['free'])
+ feature.feature ('include', [], ['free', 'path']) #order-sensitive
+ feature.feature ('cflags', [], ['free'])
+ feature.feature ('cxxflags', [], ['free'])
+ feature.feature ('linkflags', [], ['free'])
+ feature.feature ('archiveflags', [], ['free'])
+ feature.feature ('version', [], ['free'])
+
+ feature.feature ('location-prefix', [], ['free'])
+
+ feature.feature ('action', [], ['free'])
+
+
+ # The following features are incidental, since
+ # in themself they have no effect on build products.
+ # Not making them incidental will result in problems in corner
+ # cases, for example:
+ #
+ # unit-test a : a.cpp : <use>b ;
+ # lib b : a.cpp b ;
+ #
+ # Here, if <use> is not incidental, we'll decide we have two
+ # targets for a.obj with different properties, and will complain.
+ #
+ # Note that making feature incidental does not mean it's ignored. It may
+ # be ignored when creating the virtual target, but the rest of build process
+ # will use them.
+ feature.feature ('use', [], ['free', 'dependency', 'incidental'])
+ feature.feature ('dependency', [], ['free', 'dependency', 'incidental'])
+ feature.feature ('implicit-dependency', [], ['free', 'dependency', 'incidental'])
+
+ feature.feature('warnings', [
+ 'on', # Enable default/"reasonable" warning level for the tool.
+ 'all', # Enable all possible warnings issued by the tool.
+ 'off'], # Disable all warnings issued by the tool.
+ ['incidental', 'propagated'])
+
+ feature.feature('warnings-as-errors', [
+ 'off', # Do not fail the compilation if there are warnings.
+ 'on'], # Fail the compilation if there are warnings.
+ ['incidental', 'propagated'])
+
+ feature.feature ('source', [], ['free', 'dependency', 'incidental'])
+ feature.feature ('library', [], ['free', 'dependency', 'incidental'])
+ feature.feature ('file', [], ['free', 'dependency', 'incidental'])
+ feature.feature ('find-shared-library', [], ['free']) #order-sensitive ;
+ feature.feature ('find-static-library', [], ['free']) #order-sensitive ;
+ feature.feature ('library-path', [], ['free', 'path']) #order-sensitive ;
+ # Internal feature.
+ feature.feature ('library-file', [], ['free', 'dependency'])
+
+ feature.feature ('name', [], ['free'])
+ feature.feature ('tag', [], ['free'])
+ feature.feature ('search', [], ['free', 'path']) #order-sensitive ;
+ feature.feature ('location', [], ['free', 'path'])
+
+ feature.feature ('dll-path', [], ['free', 'path'])
+ feature.feature ('hardcode-dll-paths', ['true', 'false'], ['incidental'])
+
+
+ # This is internal feature which holds the paths of all dependency
+ # dynamic libraries. On Windows, it's needed so that we can all
+ # those paths to PATH, when running applications.
+ # On Linux, it's needed to add proper -rpath-link command line options.
+ feature.feature ('xdll-path', [], ['free', 'path'])
+
+ #provides means to specify def-file for windows dlls.
+ feature.feature ('def-file', [], ['free', 'dependency'])
+
+ # This feature is used to allow specific generators to run.
+ # For example, QT tools can only be invoked when QT library
+ # is used. In that case, <allow>qt will be in usage requirement
+ # of the library.
+ feature.feature ('allow', [], ['free'])
+
+ # The addressing model to generate code for. Currently a limited set only
+ # specifying the bit size of pointers.
+ feature.feature('address-model', ['16', '32', '64'], ['propagated', 'optional'])
+
+ # Type of CPU architecture to compile for.
+ feature.feature('architecture', [
+ # x86 and x86-64
+ 'x86',
+
+ # ia64
+ 'ia64',
+
+ # Sparc
+ 'sparc',
+
+ # RS/6000 & PowerPC
+ 'power',
+
+ # MIPS/SGI
+ 'mips1', 'mips2', 'mips3', 'mips4', 'mips32', 'mips32r2', 'mips64',
+
+ # HP/PA-RISC
+ 'parisc',
+
+ # Advanced RISC Machines
+ 'arm',
+
+ # Combined architectures for platforms/toolsets that support building for
+ # multiple architectures at once. "combined" would be the default multi-arch
+ # for the toolset.
+ 'combined',
+ 'combined-x86-power'],
+
+ ['propagated', 'optional'])
+
+ # The specific instruction set in an architecture to compile.
+ feature.feature('instruction-set', [
+ # x86 and x86-64
+ 'i386', 'i486', 'i586', 'i686', 'pentium', 'pentium-mmx', 'pentiumpro', 'pentium2', 'pentium3',
+ 'pentium3m', 'pentium-m', 'pentium4', 'pentium4m', 'prescott', 'nocona', 'conroe', 'conroe-xe',
+ 'conroe-l', 'allendale', 'mermon', 'mermon-xe', 'kentsfield', 'kentsfield-xe', 'penryn', 'wolfdale',
+ 'yorksfield', 'nehalem', 'k6', 'k6-2', 'k6-3', 'athlon', 'athlon-tbird', 'athlon-4', 'athlon-xp',
+ 'athlon-mp', 'k8', 'opteron', 'athlon64', 'athlon-fx', 'winchip-c6', 'winchip2', 'c3', 'c3-2',
+
+ # ia64
+ 'itanium', 'itanium1', 'merced', 'itanium2', 'mckinley',
+
+ # Sparc
+ 'v7', 'cypress', 'v8', 'supersparc', 'sparclite', 'hypersparc', 'sparclite86x', 'f930', 'f934',
+ 'sparclet', 'tsc701', 'v9', 'ultrasparc', 'ultrasparc3',
+
+ # RS/6000 & PowerPC
+ '401', '403', '405', '405fp', '440', '440fp', '505', '601', '602',
+ '603', '603e', '604', '604e', '620', '630', '740', '7400',
+ '7450', '750', '801', '821', '823', '860', '970', '8540',
+ 'power-common', 'ec603e', 'g3', 'g4', 'g5', 'power', 'power2',
+ 'power3', 'power4', 'power5', 'powerpc', 'powerpc64', 'rios',
+ 'rios1', 'rsc', 'rios2', 'rs64a',
+
+ # MIPS
+ '4kc', '4kp', '5kc', '20kc', 'm4k', 'r2000', 'r3000', 'r3900', 'r4000',
+ 'r4100', 'r4300', 'r4400', 'r4600', 'r4650',
+ 'r6000', 'r8000', 'rm7000', 'rm9000', 'orion', 'sb1', 'vr4100',
+ 'vr4111', 'vr4120', 'vr4130', 'vr4300',
+ 'vr5000', 'vr5400', 'vr5500',
+
+ # HP/PA-RISC
+ '700', '7100', '7100lc', '7200', '7300', '8000',
+
+ # Advanced RISC Machines
+ 'armv2', 'armv2a', 'armv3', 'armv3m', 'armv4', 'armv4t', 'armv5',
+ 'armv5t', 'armv5te', 'armv6', 'armv6j', 'iwmmxt', 'ep9312'],
+
+ ['propagated', 'optional'])
+
+ # Windows-specific features
+ feature.feature ('user-interface', ['console', 'gui', 'wince', 'native', 'auto'], [])
+ feature.feature ('variant', [], ['implicit', 'composite', 'propagated', 'symmetric'])
+
+
+ variant ('debug', ['<optimization>off', '<debug-symbols>on', '<inlining>off', '<runtime-debugging>on'])
+ variant ('release', ['<optimization>speed', '<debug-symbols>off', '<inlining>full',
+ '<runtime-debugging>off', '<define>NDEBUG'])
+ variant ('profile', ['release'], ['<profiling>on', '<debug-symbols>on'])
+
+ type.register ('H', ['h'])
+ type.register ('HPP', ['hpp'], 'H')
+ type.register ('C', ['c'])
+
+
+reset ()
+register_globals ()
+
+class SearchedLibTarget (virtual_target.AbstractFileTarget):
+ def __init__ (self, name, project, shared, real_name, search, action):
+ virtual_target.AbstractFileTarget.__init__ (self, name, False, 'SEARCHED_LIB', project, action)
+
+ self.shared_ = shared
+ self.real_name_ = real_name
+ if not self.real_name_:
+ self.real_name_ = name
+ self.search_ = search
+
+ def shared (self):
+ return self.shared_
+
+ def real_name (self):
+ return self.real_name_
+
+ def search (self):
+ return self.search_
+
+ def actualize_location (self, target):
+ project.manager ().engine ().add_not_file_target (target)
+
+ def path (self):
+ #FIXME: several functions rely on this not being None
+ return ""
+
+
+class CScanner (scanner.Scanner):
+ def __init__ (self, includes):
+ scanner.Scanner.__init__ (self)
+
+ self.includes_ = includes
+
+ def pattern (self):
+ return r'#[ \t]*include[ ]*(<(.*)>|"(.*)")'
+
+ def process (self, target, matches, binding):
+
+ angle = regex.transform (matches, "<(.*)>")
+ quoted = regex.transform (matches, '"(.*)"')
+
+ g = str(id(self))
+ b = os.path.normpath(os.path.dirname(binding[0]))
+
+ # Attach binding of including file to included targets.
+ # When target is directly created from virtual target
+ # this extra information is unnecessary. But in other
+ # cases, it allows to distinguish between two headers of the
+ # same name included from different places.
+ # We don't need this extra information for angle includes,
+ # since they should not depend on including file (we can't
+ # get literal "." in include path).
+ g2 = g + "#" + b
+
+ g = "<" + g + ">"
+ g2 = "<" + g2 + ">"
+ angle = [g + x for x in angle]
+ quoted = [g2 + x for x in quoted]
+
+ all = angle + quoted
+ bjam.call("mark-included", target, all)
+
+ engine = get_manager().engine()
+ engine.set_target_variable(angle, "SEARCH", self.includes_)
+ engine.set_target_variable(quoted, "SEARCH", self.includes_)
+
+ # Just propagate current scanner to includes, in a hope
+ # that includes do not change scanners.
+ get_manager().scanners().propagate(self, angle + quoted)
+
+scanner.register (CScanner, 'include')
+type.set_scanner ('CPP', CScanner)
+
+# Ported to trunk_at_47077
+class LibGenerator (generators.Generator):
+ """ The generator class for libraries (target type LIB). Depending on properties it will
+ request building of the approapriate specific type -- SHARED_LIB, STATIC_LIB or
+ SHARED_LIB.
+ """
+
+ def __init__(self, id = 'LibGenerator', composing = True, source_types = [], target_types_and_names = ['LIB'], requirements = []):
+ generators.Generator.__init__(self, id, composing, source_types, target_types_and_names, requirements)
+
+ def run(self, project, name, prop_set, sources):
+ # The lib generator is composing, and can be only invoked with
+ # explicit name. This check is present in generator.run (and so in
+ # builtin.LinkingGenerator), but duplicate it here to avoid doing
+ # extra work.
+ if name:
+ properties = prop_set.raw()
+ # Determine the needed target type
+ actual_type = None
+ properties_grist = get_grist(properties)
+ if '<source>' not in properties_grist and \
+ ('<search>' in properties_grist or '<name>' in properties_grist):
+ actual_type = 'SEARCHED_LIB'
+ elif '<file>' in properties_grist:
+ # The generator for
+ actual_type = 'LIB'
+ elif '<link>shared' in properties:
+ actual_type = 'SHARED_LIB'
+ else:
+ actual_type = 'STATIC_LIB'
+
+ prop_set = prop_set.add_raw(['<main-target-type>LIB'])
+
+ # Construct the target.
+ return generators.construct(project, name, actual_type, prop_set, sources)
+
+ def viable_source_types(self):
+ return ['*']
+
+generators.register(LibGenerator())
+
+### # The implementation of the 'lib' rule. Beyond standard syntax that rule allows
+### # simplified:
+### # lib a b c ;
+### # so we need to write code to handle that syntax.
+### rule lib ( names + : sources * : requirements * : default-build *
+### : usage-requirements * )
+### {
+### local project = [ project.current ] ;
+###
+### # This is a circular module dependency, so it must be imported here
+### import targets ;
+###
+### local result ;
+### if ! $(sources) && ! $(requirements)
+### && ! $(default-build) && ! $(usage-requirements)
+### {
+### for local name in $(names)
+### {
+### result += [
+### targets.main-target-alternative
+### [ new typed-target $(name) : $(project) : LIB
+### :
+### : [ targets.main-target-requirements $(requirements) <name>$(name) :
+### $(project) ]
+### : [ targets.main-target-default-build $(default-build) : $(project) ]
+### : [ targets.main-target-usage-requirements $(usage-requirements) : $(project) ]
+### ] ] ;
+### }
+### }
+### else
+### {
+### if $(names[2])
+### {
+### errors.user-error "When several names are given to the 'lib' rule" :
+### "it's not allowed to specify sources or requirements. " ;
+### }
+###
+### local name = $(names[1]) ;
+### result = [ targets.main-target-alternative
+### [ new typed-target $(name) : $(project) : LIB
+### : [ targets.main-target-sources $(sources) : $(name) ]
+### : [ targets.main-target-requirements $(requirements) : $(project) ]
+### : [ targets.main-target-default-build $(default-build) : $(project) ]
+### : [ targets.main-target-usage-requirements $(usage-requirements) : $(project) ]
+### ] ] ;
+### }
+### return $(result) ;
+### }
+### IMPORT $(__name__) : lib : : lib ;
+
+# Updated to trunk_at_47077
+class SearchedLibGenerator (generators.Generator):
+ def __init__ (self, id = 'SearchedLibGenerator', composing = False, source_types = [], target_types_and_names = ['SEARCHED_LIB'], requirements = []):
+ # TODO: the comment below looks strange. There are no requirements!
+ # The requirements cause the generators to be tried *only* when we're building
+ # lib target and there's 'search' feature. This seems ugly --- all we want
+ # is make sure SearchedLibGenerator is not invoked deep in transformation
+ # search.
+ generators.Generator.__init__ (self, id, composing, source_types, target_types_and_names, requirements)
+
+ def run(self, project, name, prop_set, sources):
+ if not name:
+ return None
+
+ # If name is empty, it means we're called not from top-level.
+ # In this case, we just fail immediately, because SearchedLibGenerator
+ # cannot be used to produce intermediate targets.
+
+ properties = prop_set.raw ()
+ shared = '<link>shared' in properties
+
+ a = virtual_target.NullAction (project.manager(), prop_set)
+
+ real_name = feature.get_values ('<name>', properties)
+ if real_name:
+ real_name = real_name[0]
+ else:
+ real_nake = name
+ search = feature.get_values('<search>', properties)
+ usage_requirements = property_set.create(['<xdll-path>' + p for p in search])
+ t = SearchedLibTarget(name, project, shared, real_name, search, a)
+
+ # We return sources for a simple reason. If there's
+ # lib png : z : <name>png ;
+ # the 'z' target should be returned, so that apps linking to
+ # 'png' will link to 'z', too.
+ return(usage_requirements, [b2.manager.get_manager().virtual_targets().register(t)] + sources)
+
+generators.register (SearchedLibGenerator ())
+
+### class prebuilt-lib-generator : generator
+### {
+### rule __init__ ( * : * )
+### {
+### generator.__init__ $(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ;
+### }
+###
+### rule run ( project name ? : prop_set : sources * : multiple ? )
+### {
+### local f = [ $(prop_set).get <file> ] ;
+### return $(f) $(sources) ;
+### }
+### }
+###
+### generators.register
+### [ new prebuilt-lib-generator builtin.prebuilt : : LIB : <file> ] ;
+
+
+class CompileAction (virtual_target.Action):
+ def __init__ (self, manager, sources, action_name, prop_set):
+ virtual_target.Action.__init__ (self, manager, sources, action_name, prop_set)
+
+ def adjust_properties (self, prop_set):
+ """ For all virtual targets for the same dependency graph as self,
+ i.e. which belong to the same main target, add their directories
+ to include path.
+ """
+ s = self.targets () [0].creating_subvariant ()
+
+ return prop_set.add_raw (s.implicit_includes ('include', 'H'))
+
+class CCompilingGenerator (generators.Generator):
+ """ Declare a special compiler generator.
+ The only thing it does is changing the type used to represent
+ 'action' in the constructed dependency graph to 'CompileAction'.
+ That class in turn adds additional include paths to handle a case
+ when a source file includes headers which are generated themselfs.
+ """
+ def __init__ (self, id, composing, source_types, target_types_and_names, requirements):
+ # TODO: (PF) What to do with optional_properties? It seemed that, in the bjam version, the arguments are wrong.
+ generators.Generator.__init__ (self, id, composing, source_types, target_types_and_names, requirements)
+
+ def action_class (self):
+ return CompileAction
+
+def register_c_compiler (id, source_types, target_types, requirements, optional_properties = []):
+ g = CCompilingGenerator (id, False, source_types, target_types, requirements + optional_properties)
+ return generators.register (g)
+
+
+class LinkingGenerator (generators.Generator):
+ """ The generator class for handling EXE and SHARED_LIB creation.
+ """
+ def __init__ (self, id, composing, source_types, target_types_and_names, requirements):
+ generators.Generator.__init__ (self, id, composing, source_types, target_types_and_names, requirements)
+
+ def run (self, project, name, prop_set, sources):
+ lib_sources = prop_set.get('<library>')
+ [ sources.append (project.manager().get_object(x)) for x in lib_sources ]
+
+ # Add <library-path> properties for all searched libraries
+ extra = []
+ for s in sources:
+ if s.type () == 'SEARCHED_LIB':
+ search = s.search()
+ extra.append(replace_grist(search, '<library-path>'))
+
+ orig_xdll_path = []
+
+ if prop_set.get('<hardcode-dll-paths>') == ['true'] and type.is_derived(self.target_types_ [0], 'EXE'):
+ xdll_path = prop_set.get('<xdll-path>')
+ orig_xdll_path = [ replace_grist(x, '<dll-path>') for x in xdll_path ]
+ # It's possible that we have libraries in sources which did not came
+ # from 'lib' target. For example, libraries which are specified
+ # just as filenames as sources. We don't have xdll-path properties
+ # for such target, but still need to add proper dll-path properties.
+ for s in sources:
+ if type.is_derived (s.type (), 'SHARED_LIB') and not s.action ():
+ # Unfortunately, we don't have a good way to find the path
+ # to a file, so use this nasty approach.
+ p = s.project()
+ location = path.root(s.name(), p.get('source-location'))
+ xdll_path.append(path.parent(location))
+
+ extra += [ replace_grist(x, '<dll-path>') for x in xdll_path ]
+
+ if extra:
+ prop_set = prop_set.add_raw (extra)
+
+ result = generators.Generator.run(self, project, name, prop_set, sources)
+
+ if result:
+ ur = self.extra_usage_requirements(result, prop_set)
+ ur = ur.add(property_set.create(orig_xdll_path))
+ else:
+ return None
+
+ return(ur, result)
+
+ def extra_usage_requirements (self, created_targets, prop_set):
+
+ result = property_set.empty ()
+ extra = []
+
+ # Add appropriate <xdll-path> usage requirements.
+ raw = prop_set.raw ()
+ if '<link>shared' in raw:
+ paths = []
+
+ # TODO: is it safe to use the current directory? I think we should use
+ # another mechanism to allow this to be run from anywhere.
+ pwd = os.getcwd()
+
+ for t in created_targets:
+ if type.is_derived(t.type(), 'SHARED_LIB'):
+ paths.append(path.root(path.make(t.path()), pwd))
+
+ extra += replace_grist(paths, '<xdll-path>')
+
+ # We need to pass <xdll-path> features that we've got from sources,
+ # because if shared library is built, exe which uses it must know paths
+ # to other shared libraries this one depends on, to be able to find them
+ # all at runtime.
+
+ # Just pass all features in property_set, it's theorically possible
+ # that we'll propagate <xdll-path> features explicitly specified by
+ # the user, but then the user's to blaim for using internal feature.
+ values = prop_set.get('<xdll-path>')
+ extra += replace_grist(values, '<xdll-path>')
+
+ if extra:
+ result = property_set.create(extra)
+
+ return result
+
+ def generated_targets (self, sources, prop_set, project, name):
+
+ # sources to pass to inherited rule
+ sources2 = []
+ # properties to pass to inherited rule
+ properties2 = []
+ # sources which are libraries
+ libraries = []
+
+ # Searched libraries are not passed as argument to linker
+ # but via some option. So, we pass them to the action
+ # via property.
+ properties2 = prop_set.raw()
+ fsa = []
+ fst = []
+ for s in sources:
+ if type.is_derived(s.type(), 'SEARCHED_LIB'):
+ name = s.real_name()
+ if s.shared():
+ fsa.append(name)
+
+ else:
+ fst.append(name)
+
+ else:
+ sources2.append(s)
+
+ if fsa:
+ properties2 += [replace_grist('&&'.join(fsa), '<find-shared-library>')]
+ if fst:
+ properties2 += [replace_grist('&&'.join(fst), '<find-static-library>')]
+
+ spawn = generators.Generator.generated_targets(self, sources2, property_set.create(properties2), project, name)
+
+ return spawn
+
+
+def register_linker(id, source_types, target_types, requirements):
+ g = LinkingGenerator(id, True, source_types, target_types, requirements)
+ generators.register(g)
+
+class ArchiveGenerator (generators.Generator):
+ """ The generator class for handling STATIC_LIB creation.
+ """
+ def __init__ (self, id, composing, source_types, target_types_and_names, requirements):
+ generators.Generator.__init__ (self, id, composing, source_types, target_types_and_names, requirements)
+
+ def run (self, project, name, prop_set, sources):
+ sources += prop_set.get ('<library>')
+
+ result = generators.Generator.run (self, project, name, prop_set, sources)
+
+ return result
+
+### rule register-archiver ( id composing ? : source_types + : target_types + :
+### requirements * )
+### {
+### local g = [ new ArchiveGenerator $(id) $(composing) : $(source_types)
+### : $(target_types) : $(requirements) ] ;
+### generators.register $(g) ;
+### }
+###
+###
+### IMPORT $(__name__) : register-linker register-archiver
+### : : generators.register-linker generators.register-archiver ;
+###
+###
+###

Added: trunk/tools/build/v2/tools/common.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/tools/common.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,817 @@
+# Status: being ported by Steven Watanabe
+# Base revision: 47174
+#
+# Copyright (C) Vladimir Prus 2002. Permission to copy, use, modify, sell and
+# distribute this software is granted provided this copyright notice appears in
+# all copies. This software is provided "as is" without express or implied
+# warranty, and with no claim as to its suitability for any purpose.
+
+""" Provides actions common to all toolsets, such as creating directories and
+ removing files.
+"""
+
+import re
+import bjam
+import os
+import os.path
+
+from b2.build import feature
+from b2.util.utility import *
+from b2.util import path
+
+__re__before_first_dash = re.compile ('([^-]*)-')
+
+def reset ():
+ """ Clear the module state. This is mainly for testing purposes.
+ Note that this must be called _after_ resetting the module 'feature'.
+ """
+ global __had_unspecified_value, __had_value, __declared_subfeature
+ global __init_loc
+ global __all_signatures, __debug_configuration, __show_configuration
+
+ # Stores toolsets without specified initialization values.
+ __had_unspecified_value = {}
+
+ # Stores toolsets with specified initialization values.
+ __had_value = {}
+
+ # Stores toolsets with declared subfeatures.
+ __declared_subfeature = {}
+
+ # Stores all signatures of the toolsets.
+ __all_signatures = {}
+
+ # Stores the initialization locations of each toolset
+ __init_loc = {}
+
+ __debug_configuration = '--debug-configuration' in bjam.variable('ARGV')
+ __show_configuration = '--show-configuration' in bjam.variable('ARGV')
+
+reset()
+
+# ported from trunk_at_47174
+class Configurations(object):
+ """
+ This class helps to manage toolset configurations. Each configuration
+ has a unique ID and one or more parameters. A typical example of a unique ID
+ is a condition generated by 'common.check-init-parameters' rule. Other kinds
+ of IDs can be used. Parameters may include any details about the configuration
+ like 'command', 'path', etc.
+
+ A toolset configuration may be in one of the following states:
+
+ - registered
+ Configuration has been registered (e.g. by autodetection code) but has
+ not yet been marked as used, i.e. 'toolset.using' rule has not yet been
+ called for it.
+ - used
+ Once called 'toolset.using' rule marks the configuration as 'used'.
+
+ The main difference between the states above is that while a configuration is
+ 'registered' its options can be freely changed. This is useful in particular
+ for autodetection code - all detected configurations may be safely overwritten
+ by user code.
+ """
+
+ def __init__(self):
+ self.used_ = set()
+ self.all_ = set()
+ self.params = {}
+
+ def register(self, id):
+ """
+ Registers a configuration.
+
+ Returns True if the configuration has been added and False if
+ it already exists. Reports an error if the configuration is 'used'.
+ """
+ if id in self.used_:
+ #FIXME
+ errors.error("common: the configuration '$(id)' is in use")
+
+ if id not in self.all_:
+ self.all_ += [id]
+
+ # Indicate that a new configuration has been added.
+ return True
+ else:
+ return False
+
+ def use(self, id):
+ """
+ Mark a configuration as 'used'.
+
+ Returns True if the state of the configuration has been changed to
+ 'used' and False if it the state wasn't changed. Reports an error
+ if the configuration isn't known.
+ """
+ if id not in self.all_:
+ #FIXME:
+ errors.error("common: the configuration '$(id)' is not known")
+
+ if id not in self.used_:
+ self.used_ += [id]
+
+ # indicate that the configuration has been marked as 'used'
+ return True
+ else:
+ return False
+
+ def all(self):
+ """ Return all registered configurations. """
+ return self.all_
+
+ def used(self):
+ """ Return all used configurations. """
+ return self.used_
+
+ def get(self, id, param):
+ """ Returns the value of a configuration parameter. """
+ self.params_.getdefault(param, {}).getdefault(id, None)
+
+ def set (self, id, param, value):
+ """ Sets the value of a configuration parameter. """
+ self.params_.setdefault(param, {})[id] = value
+
+# Ported from trunk_at_47174
+def check_init_parameters(toolset, requirement, *args):
+ """ The rule for checking toolset parameters. Trailing parameters should all be
+ parameter name/value pairs. The rule will check that each parameter either has
+ a value in each invocation or has no value in each invocation. Also, the rule
+ will check that the combination of all parameter values is unique in all
+ invocations.
+
+ Each parameter name corresponds to a subfeature. This rule will declare a
+ subfeature the first time a non-empty parameter value is passed and will
+ extend it with all the values.
+
+ The return value from this rule is a condition to be used for flags settings.
+ """
+ # The type checking here is my best guess about
+ # what the types should be.
+ assert(isinstance(toolset, str))
+ assert(isinstance(requirement, str) or requirement is None)
+ sig = toolset
+ condition = replace_grist(toolset, '<toolset>')
+ subcondition = []
+
+ for arg in args:
+ assert(isinstance(arg, tuple))
+ assert(len(arg) == 2)
+ name = arg[0]
+ value = arg[1]
+ assert(isinstance(name, str))
+ assert(isinstance(value, str) or value is None)
+
+ str_toolset_name = str((toolset, name))
+
+ # FIXME: is this the correct translation?
+ ### if $(value)-is-not-empty
+ if value is not None:
+ condition = condition + '-' + value
+ if __had_unspecified_value.has_key(str_toolset_name):
+ raise BaseException("'%s' initialization: parameter '%s' inconsistent\n" \
+ "no value was specified in earlier initialization\n" \
+ "an explicit value is specified now" % (toolset, name))
+
+ # The logic below is for intel compiler. It calls this rule
+ # with 'intel-linux' and 'intel-win' as toolset, so we need to
+ # get the base part of toolset name.
+ # We can't pass 'intel' as toolset, because it that case it will
+ # be impossible to register versionles intel-linux and
+ # intel-win of specific version.
+ t = toolset
+ m = __re__before_first_dash.match(toolset)
+ if m:
+ t = m.group(1)
+
+ if not __had_value.has_key(str_toolset_name):
+ if not __declared_subfeature.has_key(str((t, name))):
+ feature.subfeature('toolset', t, name, [], ['propagated'])
+ __declared_subfeature[str((t, name))] = True
+
+ __had_value[str_toolset_name] = True
+
+ feature.extend_subfeature('toolset', t, name, [value])
+ subcondition += ['<toolset-' + t + ':' + name + '>' + value ]
+
+ else:
+ if __had_value.has_key(str_toolset_name):
+ raise BaseException ("'%s' initialization: parameter '%s' inconsistent\n" \
+ "an explicit value was specified in an earlier initialization\n" \
+ "no value is specified now" % (toolset, name))
+
+ __had_unspecified_value[str_toolset_name] = True
+
+ if value == None: value = ''
+
+ sig = sig + value + '-'
+
+ if __all_signatures.has_key(sig):
+ message = "duplicate initialization of '%s' with the following parameters: " % toolset
+
+ for arg in args:
+ name = arg[0]
+ value = arg[1]
+ if value == None: value = '<unspecified>'
+
+ message += "'%s' = '%s'\n" % (name, value)
+
+ raise BaseException(message)
+
+ __all_signatures[sig] = True
+ # FIXME
+ __init_loc[sig] = "User location unknown" #[ errors.nearest-user-location ] ;
+
+ # If we have a requirement, this version should only be applied under that
+ # condition. To accomplish this we add a toolset requirement that imposes
+ # the toolset subcondition, which encodes the version.
+ if requirement:
+ r = ['<toolset>' + toolset, requirement]
+ r = ','.join(r)
+ toolset.add_requirements([r + ':' + c for c in subcondition])
+
+ # We add the requirements, if any, to the condition to scope the toolset
+ # variables and options to this specific version.
+ condition = [condition]
+ if requirement:
+ condition += [requirement]
+
+ if __show_configuration:
+ print "notice:", condition
+ return ['/'.join(condition)]
+
+# Ported from trunk_at_47077
+def get_invocation_command_nodefault(
+ toolset, tool, user_provided_command=[], additional_paths=[], path_last=False):
+ """
+ A helper rule to get the command to invoke some tool. If
+ 'user-provided-command' is not given, tries to find binary named 'tool' in
+ PATH and in the passed 'additional-path'. Otherwise, verifies that the first
+ element of 'user-provided-command' is an existing program.
+
+ This rule returns the command to be used when invoking the tool. If we can't
+ find the tool, a warning is issued. If 'path-last' is specified, PATH is
+ checked after 'additional-paths' when searching for 'tool'.
+ """
+ assert(isinstance(toolset, str))
+ assert(isinstance(tool, str))
+ assert(isinstance(user_provided_command, list))
+ if additional_paths is not None:
+ assert(isinstance(additional_paths, list))
+ assert(all([isinstance(path, str) for path in additional_paths]))
+ assert(all(isinstance(path, str) for path in additional_paths))
+ assert(isinstance(path_last, bool))
+
+ if not user_provided_command:
+ command = find_tool(tool, additional_paths, path_last)
+ if not command and __debug_configuration:
+ print "warning: toolset", toolset, "initialization: can't find tool, tool"
+ #FIXME
+ #print "warning: initialized from" [ errors.nearest-user-location ] ;
+ else:
+ command = check_tool(user_provided_command)
+ if not command and __debug_configuration:
+ print "warning: toolset", toolset, "initialization:"
+ print "warning: can't find user-provided command", user_provided_command
+ #FIXME
+ #ECHO "warning: initialized from" [ errors.nearest-user-location ]
+
+ assert(isinstance(command, str))
+
+ return command
+
+# ported from trunk_at_47174
+def get_invocation_command(toolset, tool, user_provided_command = [],
+ additional_paths = [], path_last = False):
+ """ Same as get_invocation_command_nodefault, except that if no tool is found,
+ returns either the user-provided-command, if present, or the 'tool' parameter.
+ """
+
+ assert(isinstance(toolset, str))
+ assert(isinstance(tool, str))
+ assert(isinstance(user_provided_command, list))
+ if additional_paths is not None:
+ assert(isinstance(additional_paths, list))
+ assert(all([isinstance(path, str) for path in additional_paths]))
+ assert(isinstance(path_last, bool))
+
+ result = get_invocation_command_nodefault(toolset, tool,
+ user_provided_command,
+ additional_paths,
+ path_last)
+
+ if not result:
+ if user_provided_command:
+ result = user_provided_command[0]
+ else:
+ result = tool
+
+ assert(isinstance(result, str))
+
+ return result
+
+# ported from trunk_at_47281
+def get_absolute_tool_path(command):
+ """
+ Given an invocation command,
+ return the absolute path to the command. This works even if commnad
+ has not path element and is present in PATH.
+ """
+ if os.path.dirname(command):
+ return os.path.dirname(command)
+ else:
+ programs = path.programs_path()
+ m = path.glob(programs, [command, command + '.exe' ])
+ if not len(m):
+ print "Could not find:", command, "in", programs
+ return os.path.dirname(m[0])
+
+# ported from trunk_at_47174
+def find_tool(name, additional_paths = [], path_last = False):
+ """ Attempts to find tool (binary) named 'name' in PATH and in
+ 'additional-paths'. If found in path, returns 'name'. If
+ found in additional paths, returns full name. If the tool
+ is found in several directories, returns the first path found.
+ Otherwise, returns the empty string. If 'path_last' is specified,
+ path is checked after 'additional_paths'.
+ """
+ assert(isinstance(name, str))
+ assert(isinstance(additional_paths, list))
+ assert(isinstance(path_last, bool))
+
+ programs = path.programs_path()
+ match = path.glob(programs, [name, name + '.exe'])
+ additional_match = path.glob(additional_paths, [name, name + '.exe'])
+
+ result = []
+ if path_last:
+ result = additional_match
+ if not result and match:
+ result = match
+
+ else:
+ if match:
+ result = match
+
+ elif additional_match:
+ result = additional_match
+
+ if result:
+ return path.native(result[0])
+ else:
+ return ''
+
+#ported from trunk_at_47281
+def check_tool_aux(command):
+ """ Checks if 'command' can be found either in path
+ or is a full name to an existing file.
+ """
+ assert(isinstance(command, str))
+ dirname = os.path.dirname(command)
+ if dirname:
+ if os.path.exists(command):
+ return command
+ # Both NT and Cygwin will run .exe files by their unqualified names.
+ elif on_windows() and os.path.exists(command + '.exe'):
+ return command
+ # Only NT will run .bat files by their unqualified names.
+ elif os_name() == 'NT' and os.path.exists(command + '.bat'):
+ return command
+ else:
+ paths = path.programs_path()
+ if path.glob(paths, [command]):
+ return command
+
+# ported from trunk_at_47281
+def check_tool(command):
+ """ Checks that a tool can be invoked by 'command'.
+ If command is not an absolute path, checks if it can be found in 'path'.
+ If comand is absolute path, check that it exists. Returns 'command'
+ if ok and empty string otherwise.
+ """
+ assert(isinstance(command, list))
+ assert(all(isinstance(c, str) for c in command))
+ #FIXME: why do we check the first and last elements????
+ if check_tool_aux(command[0]) or check_tool_aux(command[-1]):
+ return command
+
+# ported from trunk_at_47281
+def handle_options(tool, condition, command, options):
+ """ Handle common options for toolset, specifically sets the following
+ flag variables:
+ - CONFIG_COMMAND to 'command'
+ - OPTIOns for compile to the value of <compileflags> in options
+ - OPTIONS for compile.c to the value of <cflags> in options
+ - OPTIONS for compile.c++ to the value of <cxxflags> in options
+ - OPTIONS for compile.fortran to the value of <fflags> in options
+ - OPTIONs for link to the value of <linkflags> in options
+ """
+ from b2.build import toolset
+
+ assert(isinstance(tool, str))
+ assert(isinstance(condition, list))
+ assert(isinstance(command, str))
+ assert(isinstance(options, list))
+ assert(command)
+ toolset.flags(tool, 'CONFIG_COMMAND', condition, [command])
+ toolset.flags(tool + '.compile', 'OPTIONS', condition, feature.get_values('<compileflags>', options))
+ toolset.flags(tool + '.compile.c', 'OPTIONS', condition, feature.get_values('<cflags>', options))
+ toolset.flags(tool + '.compile.c++', 'OPTIONS', condition, feature.get_values('<cxxflags>', options))
+ toolset.flags(tool + '.compile.fortran', 'OPTIONS', condition, feature.get_values('<fflags>', options))
+ toolset.flags(tool + '.link', 'OPTIONS', condition, feature.get_values('<linkflags>', options))
+
+# ported from trunk_at_47281
+def get_program_files_dir():
+ """ returns the location of the "program files" directory on a windows
+ platform
+ """
+ ProgramFiles = bjam.variable("ProgramFiles")
+ if ProgramFiles:
+ ProgramFiles = ' '.join(ProgramFiles)
+ else:
+ ProgramFiles = "c:\\Program Files"
+ return ProgramFiles
+
+# ported from trunk_at_47281
+def rm_command():
+ return __RM
+
+# ported from trunk_at_47281
+def copy_command():
+ return __CP
+
+# ported from trunk_at_47281
+def variable_setting_command(variable, value):
+ """
+ Returns the command needed to set an environment variable on the current
+ platform. The variable setting persists through all following commands and is
+ visible in the environment seen by subsequently executed commands. In other
+ words, on Unix systems, the variable is exported, which is consistent with the
+ only possible behavior on Windows systems.
+ """
+ assert(isinstance(variable, str))
+ assert(isinstance(value, str))
+
+ if os_name() == 'NT':
+ return "set " + variable + "=" + value + os.linesep
+ else:
+ # (todo)
+ # The following does not work on CYGWIN and needs to be fixed. On
+ # CYGWIN the $(nl) variable holds a Windows new-line \r\n sequence that
+ # messes up the executed export command which then reports that the
+ # passed variable name is incorrect. This is most likely due to the
+ # extra \r character getting interpreted as a part of the variable name.
+ #
+ # Several ideas pop to mind on how to fix this:
+ # * One way would be to separate the commands using the ; shell
+ # command separator. This seems like the quickest possible
+ # solution but I do not know whether this would break code on any
+ # platforms I I have no access to.
+ # * Another would be to not use the terminating $(nl) but that would
+ # require updating all the using code so it does not simply
+ # prepend this variable to its own commands.
+ # * I guess the cleanest solution would be to update Boost Jam to
+ # allow explicitly specifying \n & \r characters in its scripts
+ # instead of always relying only on the 'current OS native newline
+ # sequence'.
+ #
+ # Some code found to depend on this behaviour:
+ # * This Boost Build module.
+ # * __test__ rule.
+ # * path-variable-setting-command rule.
+ # * python.jam toolset.
+ # * xsltproc.jam toolset.
+ # * fop.jam toolset.
+ # (todo) (07.07.2008.) (Jurko)
+ #
+ # I think that this works correctly in python -- Steven Watanabe
+ return variable + "=" + value + os.linesep + "export " + variable + os.linesep
+
+def path_variable_setting_command(variable, paths):
+ """
+ Returns a command to sets a named shell path variable to the given NATIVE
+ paths on the current platform.
+ """
+ assert(isinstance(variable, str))
+ assert(isinstance(paths, list))
+ sep = os.path.pathsep
+ return variable_setting_command(variable, sep.join(paths))
+
+def prepend_path_variable_command(variable, paths):
+ """
+ Returns a command that prepends the given paths to the named path variable on
+ the current platform.
+ """
+ return path_variable_setting_command(variable,
+ paths + os.environ(variable).split(os.pathsep))
+
+def file_creation_command():
+ """
+ Return a command which can create a file. If 'r' is result of invocation, then
+ 'r foobar' will create foobar with unspecified content. What happens if file
+ already exists is unspecified.
+ """
+ if os_name() == 'NT':
+ return "echo. > "
+ else:
+ return "touch "
+
+#FIXME: global variable
+__mkdir_set = set()
+__re_windows_drive = re.compile(r'^.*:\$')
+
+def mkdir(engine, target):
+ # If dir exists, do not update it. Do this even for $(DOT).
+ bjam.call('NOUPDATE', target)
+
+ global __mkdir_set
+
+ # FIXME: Where is DOT defined?
+ #if $(<) != $(DOT) && ! $($(<)-mkdir):
+ if target != '.' and target not in __mkdir_set:
+ # Cheesy gate to prevent multiple invocations on same dir.
+ __mkdir_set.add(target)
+
+ # Schedule the mkdir build action.
+ if os_name() == 'NT':
+ engine.set_update_action("common.MkDir1-quick-fix-for-windows", target, [], None)
+ else:
+ engine.set_update_action("common.MkDir1-quick-fix-for-unix", target, [], None)
+
+ # Prepare a Jam 'dirs' target that can be used to make the build only
+ # construct all the target directories.
+ engine.add_dependency('dirs', target)
+
+ # Recursively create parent directories. $(<:P) = $(<)'s parent & we
+ # recurse until root.
+
+ s = os.path.dirname(target)
+ if os_name() == 'NT':
+ if(__re_windows_drive.match(s)):
+ s = ''
+
+ if s:
+ if s != target:
+ engine.add_dependency(target, s)
+ mkdir(engine, s)
+ else:
+ bjam.call('NOTFILE', s)
+
+__re_version = re.compile(r'^([^.]+)[.]([^.]+)[.]?([^.]*)')
+
+def format_name(format, name, target_type, prop_set):
+ """ Given a target, as given to a custom tag rule, returns a string formatted
+ according to the passed format. Format is a list of properties that is
+ represented in the result. For each element of format the corresponding target
+ information is obtained and added to the result string. For all, but the
+ literal, the format value is taken as the as string to prepend to the output
+ to join the item to the rest of the result. If not given "-" is used as a
+ joiner.
+
+ The format options can be:
+
+ <base>[joiner]
+ :: The basename of the target name.
+ <toolset>[joiner]
+ :: The abbreviated toolset tag being used to build the target.
+ <threading>[joiner]
+ :: Indication of a multi-threaded build.
+ <runtime>[joiner]
+ :: Collective tag of the build runtime.
+ <version:/version-feature | X.Y[.Z]/>[joiner]
+ :: Short version tag taken from the given "version-feature"
+ in the build properties. Or if not present, the literal
+ value as the version number.
+ <property:/property-name/>[joiner]
+ :: Direct lookup of the given property-name value in the
+ build properties. /property-name/ is a regular expression.
+ e.g. <property:toolset-.*:flavor> will match every toolset.
+ /otherwise/
+ :: The literal value of the format argument.
+
+ For example this format:
+
+ boost_ <base> <toolset> <threading> <runtime> <version:boost-version>
+
+ Might return:
+
+ boost_thread-vc80-mt-gd-1_33.dll, or
+ boost_regex-vc80-gd-1_33.dll
+
+ The returned name also has the target type specific prefix and suffix which
+ puts it in a ready form to use as the value from a custom tag rule.
+ """
+ assert(isinstance(format, list))
+ assert(isinstance(name, str))
+ assert(isinstance(target_type, str) or not type)
+ # assert(isinstance(prop_set, property_set.PropertySet))
+ if type.is_derived(target_type, 'LIB'):
+ result = "" ;
+ for f in format:
+ grist = get_grist(f)
+ if grist == '<base>':
+ result += os.path.basename(name)
+ elif grist == '<toolset>':
+ result += join_tag(ungrist(f),
+ toolset_tag(name, target_type, prop_set))
+ elif grist == '<threading>':
+ result += join_tag(ungrist(f),
+ threading_tag(name, target_type, prop_set))
+ elif grist == '<runtime>':
+ result += join_tag(ungrist(f),
+ runtime_tag(name, target_type, prop_set))
+ elif grist.startswith('<version:'):
+ key = grist[len('<version:'):-1]
+ version = prop_set.get('<' + key + '>')
+ if not version:
+ version = key
+ version = __re_version.match(version)
+ result += join_tag(ungrist(f), version[1] + '_' + version[2])
+ elif grist.startswith('<property:'):
+ key = grist[len('<property:'):-1]
+ property_re = re.compile('<(' + key + ')>')
+ p0 = None
+ for prop in prop_set.raw():
+ match = property_re.match(prop)
+ if match:
+ p0 = match[1]
+ break
+ if p0:
+ p = prop_set.get('<' + p0 + '>')
+ if p:
+ assert(len(p) == 1)
+ result += join_tag(ungrist(f), p)
+ else:
+ result += ungrist(f)
+
+ result = virtual_target.add_prefix_and_suffix(
+ ''.join(result), target_type, prop_set)
+ return result
+
+def join_tag(joiner, tag):
+ if not joiner: joiner = '-'
+ return joiner + tag
+
+__re_toolset_version = re.compile(r"<toolset.*version>(\d+)[.](\d*)")
+
+def toolset_tag(name, target_type, prop_set):
+ tag = ''
+
+ properties = prop_set.raw()
+ tools = prop_set.get('<toolset>')
+ assert(len(tools) == 0)
+ tools = tools[0]
+ if tools.startswith('borland'): tag += 'bcb'
+ elif tools.startswith('como'): tag += 'como'
+ elif tools.startswith('cw'): tag += 'cw'
+ elif tools.startswith('darwin'): tag += 'xgcc'
+ elif tools.startswith('edg'): tag += edg
+ elif tools.startswith('gcc'):
+ flavor = prop_set.get('<toolset-gcc:flavor>')
+ ''.find
+ if flavor.find('mingw') != -1:
+ tag += 'mgw'
+ else:
+ tag += 'gcc'
+ elif tools == 'intel':
+ if prop_set.get('<toolset-intel:platform>') == ['win']:
+ tag += 'iw'
+ else:
+ tag += 'il'
+ elif tools.startswith('kcc'): tag += 'kcc'
+ elif tools.startswith('kylix'): tag += 'bck'
+ #case metrowerks* : tag += cw ;
+ #case mingw* : tag += mgw ;
+ elif tools.startswith('mipspro'): tag += 'mp'
+ elif tools.startswith('msvc'): tag += 'vc'
+ elif tools.startswith('sun'): tag += 'sw'
+ elif tools.startswith('tru64cxx'): tag += 'tru'
+ elif tools.startswith('vacpp'): tag += 'xlc'
+
+ for prop in properties:
+ match = __re_toolset_version.match(prop)
+ if(match):
+ version = match
+ break
+ version_string = None
+ # For historical reasons, vc6.0 and vc7.0 use different naming.
+ if tag == 'vc':
+ if version.group(1) == '6':
+ # Cancel minor version.
+ version_string = '6'
+ elif version.group(1) == '7' and version.group(2) == '0':
+ version_string = '7'
+
+ # On intel, version is not added, because it does not matter and it's the
+ # version of vc used as backend that matters. Ideally, we'd encode the
+ # backend version but that would break compatibility with V1.
+ elif tag == 'iw':
+ version_string = ''
+
+ # On borland, version is not added for compatibility with V1.
+ elif tag == 'bcb':
+ version_string = ''
+
+ if version_string is None:
+ version = version.group(1) + version.group(2)
+
+ tag += version
+
+ return tag
+
+
+def threading_tag(name, target_type, prop_set):
+ tag = ''
+ properties = prop_set.raw()
+ if '<threading>multi' in properties: tag = 'mt'
+
+ return tag
+
+
+def runtime_tag(name, target_type, prop_set ):
+ tag = ''
+
+ properties = prop_set.raw()
+ if '<runtime-link>static' in properties: tag += 's'
+
+ # This is an ugly thing. In V1, there's a code to automatically detect which
+ # properties affect a target. So, if <runtime-debugging> does not affect gcc
+ # toolset, the tag rules won't even see <runtime-debugging>. Similar
+ # functionality in V2 is not implemented yet, so we just check for toolsets
+ # which are known to care about runtime debug.
+ if '<toolset>msvc' in properties \
+ or '<stdlib>stlport' in properties \
+ or '<toolset-intel:platform>win' in properties:
+ if '<runtime-debugging>on' in properties: tag += 'g'
+
+ if '<python-debugging>on' in properties: tag += 'y'
+ if '<variant>debug' in properties: tag += 'd'
+ if '<stdlib>stlport' in properties: tag += 'p'
+ if '<stdlib-stlport:iostream>hostios' in properties: tag += 'n'
+
+ return tag
+
+
+## TODO:
+##rule __test__ ( )
+##{
+## import assert ;
+##
+## local nl = "
+##" ;
+##
+## local save-os = [ modules.peek os : .name ] ;
+##
+## modules.poke os : .name : LINUX ;
+##
+## assert.result "PATH=foo:bar:baz$(nl)export PATH$(nl)"
+## : path-variable-setting-command PATH : foo bar baz ;
+##
+## assert.result "PATH=foo:bar:$PATH$(nl)export PATH$(nl)"
+## : prepend-path-variable-command PATH : foo bar ;
+##
+## modules.poke os : .name : NT ;
+##
+## assert.result "set PATH=foo;bar;baz$(nl)"
+## : path-variable-setting-command PATH : foo bar baz ;
+##
+## assert.result "set PATH=foo;bar;%PATH%$(nl)"
+## : prepend-path-variable-command PATH : foo bar ;
+##
+## modules.poke os : .name : $(save-os) ;
+##}
+
+def init(manager):
+ engine = manager.engine()
+
+ engine.register_action("common.MkDir1-quick-fix-for-unix", 'mkdir -p "$(<)"')
+ engine.register_action("common.MkDir1-quick-fix-for-windows", 'if not exist "$(<)\\" mkdir "$(<)"')
+
+ import b2.tools.make
+ import b2.build.alias
+
+ global __RM, __CP, __IGNORE, __LN
+ # ported from trunk_at_47281
+ if os_name() == 'NT':
+ __RM = 'del /f /q'
+ __CP = 'copy'
+ __IGNORE = '2>nul >nul & setlocal'
+ __LN = __CP
+ #if not __LN:
+ # __LN = CP
+ else:
+ __RM = 'rm -f'
+ __CP = 'cp'
+ __IGNORE = ''
+ __LN = 'ln'
+
+ engine.register_action("common.Clean", __RM + ' "$(>)"',
+ flags=['piecemeal', 'together', 'existing'])
+ engine.register_action("common.copy", __CP + ' "$(>)" "$(<)"')
+ engine.register_action("common.RmTemps", __RM + ' "$(>)" ' + __IGNORE,
+ flags=['quietly', 'updated', 'piecemeal', 'together'])
+
+ engine.register_action("common.hard-link",
+ __RM + ' "$(<)" 2$(NULL_OUT) $(NULL_OUT)' + os.linesep +
+ __LN + ' "$(>)" "$(<)" $(NULL_OUT)')

Added: trunk/tools/build/v2/tools/darwin.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/tools/darwin.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,57 @@
+# Copyright (C) Christopher Currie 2003. Permission to copy, use,
+# modify, sell and distribute this software is granted provided this
+# copyright notice appears in all copies. This software is provided
+# "as is" without express or implied warranty, and with no claim as to
+# its suitability for any purpose.
+
+# Please see http://article.gmane.org/gmane.comp.lib.boost.build/3389/
+# for explanation why it's a separate toolset.
+
+import common, gcc, builtin
+from b2.build import feature, toolset, type, action, generators
+from b2.util.utility import *
+
+toolset.register ('darwin')
+
+toolset.inherit_generators ('darwin', [], 'gcc')
+toolset.inherit_flags ('darwin', 'gcc')
+toolset.inherit_rules ('darwin', 'gcc')
+
+def init (version = None, command = None, options = None):
+ options = to_seq (options)
+
+ condition = common.check_init_parameters ('darwin', None, ('version', version))
+
+ command = common.get_invocation_command ('darwin', 'g++', command)
+
+ common.handle_options ('darwin', condition, command, options)
+
+ gcc.init_link_flags ('darwin', 'darwin', condition)
+
+# Darwin has a different shared library suffix
+type.set_generated_target_suffix ('SHARED_LIB', ['<toolset>darwin'], 'dylib')
+
+# we need to be able to tell the type of .dylib files
+type.register_suffixes ('dylib', 'SHARED_LIB')
+
+feature.feature ('framework', [], ['free'])
+
+toolset.flags ('darwin.compile', 'OPTIONS', '<link>shared', ['-dynamic'])
+toolset.flags ('darwin.compile', 'OPTIONS', None, ['-Wno-long-double', '-no-cpp-precomp'])
+toolset.flags ('darwin.compile.c++', 'OPTIONS', None, ['-fcoalesce-templates'])
+
+toolset.flags ('darwin.link', 'FRAMEWORK', '<framework>')
+
+# This is flag is useful for debugging the link step
+# uncomment to see what libtool is doing under the hood
+# toolset.flags ('darwin.link.dll', 'OPTIONS', None, '[-Wl,-v'])
+
+action.register ('darwin.compile.cpp', None, ['$(CONFIG_COMMAND) $(ST_OPTIONS) -L"$(LINKPATH)" -o "$(<)" "$(>)" "$(LIBRARIES)" -l$(FINDLIBS-SA) -l$(FINDLIBS-ST) -framework$(_)$(FRAMEWORK) $(OPTIONS)'])
+
+# TODO: how to set 'bind LIBRARIES'?
+action.register ('darwin.link.dll', None, ['$(CONFIG_COMMAND) -dynamiclib -L"$(LINKPATH)" -o "$(<)" "$(>)" "$(LIBRARIES)" -l$(FINDLIBS-SA) -l$(FINDLIBS-ST) -framework$(_)$(FRAMEWORK) $(OPTIONS)'])
+
+def darwin_archive (manager, targets, sources, properties):
+ pass
+
+action.register ('darwin.archive', darwin_archive, ['ar -c -r -s $(ARFLAGS) "$(<:T)" "$(>:T)"'])

Added: trunk/tools/build/v2/tools/gcc.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/tools/gcc.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,796 @@
+# Status: being ported by Steven Watanabe
+# Base revision: 47077
+# TODO: common.jam needs to be ported
+# TODO: generators.jam needs to have register_c_compiler.
+#
+# Copyright 2001 David Abrahams.
+# Copyright 2002-2006 Rene Rivera.
+# Copyright 2002-2003 Vladimir Prus.
+# Copyright (c) 2005 Reece H. Dunn.
+# Copyright 2006 Ilya Sokolov.
+# Copyright 2007 Roland Schwarz
+# Copyright 2007 Boris Gubenko.
+# Copyright 2008 Steven Watanabe
+#
+# Distributed under the Boost Software License, Version 1.0.
+# (See accompanying file LICENSE_1_0.txt or copy at
+# http://www.boost.org/LICENSE_1_0.txt)
+
+import os
+import subprocess
+import re
+
+import bjam
+
+from b2.tools import unix, common, rc, pch, builtin
+from b2.build import feature, type, toolset, generators
+from b2.util.utility import os_name, on_windows
+from b2.manager import get_manager
+from b2.build.generators import Generator
+from b2.build.toolset import flags
+from b2.util.utility import to_seq
+
+__debug = None
+
+def debug():
+ global __debug
+ if __debug is None:
+ __debug = "--debug-configuration" in bjam.variable("ARGV")
+ return __debug
+
+feature.extend('toolset', ['gcc'])
+
+
+toolset.inherit_generators('gcc', [], 'unix', ['unix.link', 'unix.link.dll'])
+toolset.inherit_flags('gcc', 'unix')
+toolset.inherit_rules('gcc', 'unix')
+
+generators.override('gcc.prebuilt', 'builtin.prebuilt')
+generators.override('gcc.searched-lib-generator', 'searched-lib-generator')
+
+# Target naming is determined by types/lib.jam and the settings below this
+# comment.
+#
+# On *nix:
+# libxxx.a static library
+# libxxx.so shared library
+#
+# On windows (mingw):
+# libxxx.lib static library
+# xxx.dll DLL
+# xxx.lib import library
+#
+# On windows (cygwin) i.e. <target-os>cygwin
+# libxxx.a static library
+# xxx.dll DLL
+# libxxx.dll.a import library
+#
+# Note: user can always override by using the <tag>@rule
+# This settings have been choosen, so that mingw
+# is in line with msvc naming conventions. For
+# cygwin the cygwin naming convention has been choosen.
+
+# Make the "o" suffix used for gcc toolset on all
+# platforms
+type.set_generated_target_suffix('OBJ', ['<toolset>gcc'], 'o')
+type.set_generated_target_suffix('STATIC_LIB', ['<toolset>gcc', '<target-os>cygwin'], 'a')
+
+type.set_generated_target_suffix('IMPORT_LIB', ['<toolset>gcc', '<target-os>cygwin'], 'dll.a')
+type.set_generated_target_prefix('IMPORT_LIB', ['<toolset>gcc', '<target-os>cygwin'], 'lib')
+
+__machine_match = re.compile('^([^ ]+)')
+__version_match = re.compile('^([0-9.]+)')
+
+def init(version = None, command = None, options = None):
+ """
+ Initializes the gcc toolset for the given version. If necessary, command may
+ be used to specify where the compiler is located. The parameter 'options' is a
+ space-delimited list of options, each one specified as
+ <option-name>option-value. Valid option names are: cxxflags, linkflags and
+ linker-type. Accepted linker-type values are gnu, darwin, osf, hpux or sun
+ and the default value will be selected based on the current OS.
+ Example:
+ using gcc : 3.4 : : <cxxflags>foo <linkflags>bar <linker-type>sun ;
+ """
+
+ options = to_seq(options)
+ command = to_seq(command)
+
+ # Information about the gcc command...
+ # The command.
+ command = to_seq(common.get_invocation_command('gcc', 'g++', command))
+ # The root directory of the tool install.
+ root = feature.get_values('<root>', options) ;
+ # The bin directory where to find the command to execute.
+ bin = None
+ # The flavor of compiler.
+ flavor = feature.get_values('<flavor>', options)
+ # Autodetect the root and bin dir if not given.
+ if command:
+ if not bin:
+ bin = common.get_absolute_tool_path(command[-1])
+ if not root:
+ root = os.path.dirname(bin)
+ # Autodetect the version and flavor if not given.
+ if command:
+ machine_info = subprocess.Popen(command + ['-dumpmachine'], stdout=subprocess.PIPE).communicate()[0]
+ machine = __machine_match.search(machine_info).group(1)
+
+ version_info = subprocess.Popen(command + ['-dumpversion'], stdout=subprocess.PIPE).communicate()[0]
+ version = __version_match.search(version_info).group(1)
+ if not flavor and machine.find('mingw') != -1:
+ flavor = 'mingw'
+
+ condition = None
+ if flavor:
+ condition = common.check_init_parameters('gcc', None,
+ ('version', version),
+ ('flavor', flavor))
+ else:
+ condition = common.check_init_parameters('gcc', None,
+ ('version', version))
+
+ if command:
+ command = command[0]
+
+ common.handle_options('gcc', condition, command, options)
+
+ linker = feature.get_values('<linker-type>', options)
+ if not linker:
+ if os_name() == 'OSF':
+ linker = 'osf'
+ elif os_name() == 'HPUX':
+ linker = 'hpux' ;
+ else:
+ linker = 'gnu'
+
+ init_link_flags('gcc', linker, condition)
+
+ # If gcc is installed in non-standard location, we'd need to add
+ # LD_LIBRARY_PATH when running programs created with it (for unit-test/run
+ # rules).
+ if command:
+ # On multilib 64-bit boxes, there are both 32-bit and 64-bit libraries
+ # and all must be added to LD_LIBRARY_PATH. The linker will pick the
+ # right onces. Note that we don't provide a clean way to build 32-bit
+ # binary with 64-bit compiler, but user can always pass -m32 manually.
+ lib_path = [os.path.join(root, 'bin'),
+ os.path.join(root, 'lib'),
+ os.path.join(root, 'lib32'),
+ os.path.join(root, 'lib64')]
+ if debug():
+ print 'notice: using gcc libraries ::', condition, '::', lib_path
+ toolset.flags('gcc.link', 'RUN_PATH', condition, lib_path)
+
+ # If it's not a system gcc install we should adjust the various programs as
+ # needed to prefer using the install specific versions. This is essential
+ # for correct use of MinGW and for cross-compiling.
+
+ # - The archive builder.
+ archiver = common.get_invocation_command('gcc',
+ 'ar', feature.get_values('<archiver>', options), [bin], path_last=True)
+ toolset.flags('gcc.archive', '.AR', condition, [archiver])
+ if debug():
+ print 'notice: using gcc archiver ::', condition, '::', archiver
+
+ # - The resource compiler.
+ rc_command = common.get_invocation_command_nodefault('gcc',
+ 'windres', feature.get_values('<rc>', options), [bin], path_last=True)
+ rc_type = feature.get_values('<rc-type>', options)
+
+ if not rc_type:
+ rc_type = 'windres'
+
+ if not rc_command:
+ # If we can't find an RC compiler we fallback to a null RC compiler that
+ # creates empty object files. This allows the same Jamfiles to work
+ # across the board. The null RC uses the assembler to create the empty
+ # objects, so configure that.
+ rc_command = common.get_invocation_command('gcc', 'as', [], [bin], path_last=True)
+ rc_type = 'null'
+ rc.configure(rc_command, condition, '<rc-type>' + rc_type)
+
+###if [ os.name ] = NT
+###{
+### # This causes single-line command invocation to not go through .bat files,
+### # thus avoiding command-line length limitations.
+### JAMSHELL = % ;
+###}
+
+#FIXME: when register_c_compiler is moved to
+# generators, these should be updated
+builtin.register_c_compiler('gcc.compile.c++', ['CPP'], ['OBJ'], ['<toolset>gcc'])
+builtin.register_c_compiler('gcc.compile.c', ['C'], ['OBJ'], ['<toolset>gcc'])
+builtin.register_c_compiler('gcc.compile.asm', ['ASM'], ['OBJ'], ['<toolset>gcc'])
+
+# pch support
+
+# The compiler looks for a precompiled header in each directory just before it
+# looks for the include file in that directory. The name searched for is the
+# name specified in the #include directive with ".gch" suffix appended. The
+# logic in gcc-pch-generator will make sure that BASE_PCH suffix is appended to
+# full name of the header.
+
+type.set_generated_target_suffix('PCH', ['<toolset>gcc'], 'gch')
+
+# GCC-specific pch generator.
+class GccPchGenerator(pch.PchGenerator):
+
+ # Inherit the __init__ method
+
+ def run_pch(self, project, name, prop_set, sources):
+ # Find the header in sources. Ignore any CPP sources.
+ header = None
+ for s in sources:
+ if type.is_derived(s.type, 'H'):
+ header = s
+
+ # Error handling: Base header file name should be the same as the base
+ # precompiled header name.
+ header_name = header.name
+ header_basename = os.path.basename(header_name).rsplit('.', 1)[0]
+ if header_basename != name:
+ location = project.project_module
+ ###FIXME:
+ raise Exception()
+ ### errors.user-error "in" $(location)": pch target name `"$(name)"' should be the same as the base name of header file `"$(header-name)"'" ;
+
+ pch_file = Generator.run(self, project, name, prop_set, [header])
+
+ # return result of base class and pch-file property as usage-requirements
+ # FIXME: what about multiple results from generator.run?
+ return (property_set.create('<pch-file>' + pch_file[0], '<cflags>-Winvalid-pch'),
+ pch_file)
+
+ # Calls the base version specifying source's name as the name of the created
+ # target. As result, the PCH will be named whatever.hpp.gch, and not
+ # whatever.gch.
+ def generated_targets(self, sources, prop_set, project, name = None):
+ name = sources[0].name
+ return Generator.generated_targets(self, sources,
+ prop_set, project, name)
+
+# Note: the 'H' source type will catch both '.h' header and '.hpp' header. The
+# latter have HPP type, but HPP type is derived from H. The type of compilation
+# is determined entirely by the destination type.
+generators.register(GccPchGenerator('gcc.compile.c.pch', False, ['H'], ['C_PCH'], ['<pch>on', '<toolset>gcc' ]))
+generators.register(GccPchGenerator('gcc.compile.c++.pch', False, ['H'], ['CPP_PCH'], ['<pch>on', '<toolset>gcc' ]))
+
+# Override default do-nothing generators.
+generators.override('gcc.compile.c.pch', 'pch.default-c-pch-generator')
+generators.override('gcc.compile.c++.pch', 'pch.default-cpp-pch-generator')
+
+flags('gcc.compile', 'PCH_FILE', ['<pch>on'], ['<pch-file>'])
+
+# Declare flags and action for compilation
+flags('gcc.compile', 'OPTIONS', ['<optimization>off'], ['-O0'])
+flags('gcc.compile', 'OPTIONS', ['<optimization>speed'], ['-O3'])
+flags('gcc.compile', 'OPTIONS', ['<optimization>space'], ['-Os'])
+
+flags('gcc.compile', 'OPTIONS', ['<inlining>off'], ['-fno-inline'])
+flags('gcc.compile', 'OPTIONS', ['<inlining>on'], ['-Wno-inline'])
+flags('gcc.compile', 'OPTIONS', ['<inlining>full'], ['-finline-functions', '-Wno-inline'])
+
+flags('gcc.compile', 'OPTIONS', ['<warnings>off'], ['-w'])
+flags('gcc.compile', 'OPTIONS', ['<warnings>on'], ['-Wall'])
+flags('gcc.compile', 'OPTIONS', ['<warnings>all'], ['-Wall', '-pedantic'])
+flags('gcc.compile', 'OPTIONS', ['<warnings-as-errors>on'], ['-Werror'])
+
+flags('gcc.compile', 'OPTIONS', ['<debug-symbols>on'], ['-g'])
+flags('gcc.compile', 'OPTIONS', ['<profiling>on'], ['-pg'])
+flags('gcc.compile', 'OPTIONS', ['<rtti>off'], ['-fno-rtti'])
+
+# On cygwin and mingw, gcc generates position independent code by default, and
+# warns if -fPIC is specified. This might not be the right way of checking if
+# we're using cygwin. For example, it's possible to run cygwin gcc from NT
+# shell, or using crosscompiling. But we'll solve that problem when it's time.
+# In that case we'll just add another parameter to 'init' and move this login
+# inside 'init'.
+if not os_name () in ['CYGWIN', 'NT']:
+ print "osname:", os_name()
+ # This logic will add -fPIC for all compilations:
+ #
+ # lib a : a.cpp b ;
+ # obj b : b.cpp ;
+ # exe c : c.cpp a d ;
+ # obj d : d.cpp ;
+ #
+ # This all is fine, except that 'd' will be compiled with -fPIC even though
+ # it's not needed, as 'd' is used only in exe. However, it's hard to detect
+ # where a target is going to be used. Alternative, we can set -fPIC only
+ # when main target type is LIB but than 'b' will be compiled without -fPIC.
+ # In x86-64 that will lead to link errors. So, compile everything with
+ # -fPIC.
+ #
+ # Yet another alternative would be to create propagated <sharedable>
+ # feature, and set it when building shared libraries, but that's hard to
+ # implement and will increase target path length even more.
+ flags('gcc.compile', 'OPTIONS', ['<link>shared'], ['-fPIC'])
+
+if os_name() != 'NT' and os_name() != 'OSF' and os_name() != 'HPUX':
+ # OSF does have an option called -soname but it doesn't seem to work as
+ # expected, therefore it has been disabled.
+ HAVE_SONAME = ''
+ SONAME_OPTION = '-h'
+
+
+flags('gcc.compile', 'USER_OPTIONS', [], ['<cflags>'])
+flags('gcc.compile.c++', 'USER_OPTIONS',[], ['<cxxflags>'])
+flags('gcc.compile', 'DEFINES', [], ['<define>'])
+flags('gcc.compile', 'INCLUDES', [], ['<include>'])
+
+engine = get_manager().engine()
+
+engine.register_action('gcc.compile.c++.pch',
+ '"$(CONFIG_COMMAND)" -x c++-header $(OPTIONS) -D$(DEFINES) -I"$(INCLUDES)" -c -o "$(<)" "$(>)"')
+
+engine.register_action('gcc.compile.c.pch',
+ '"$(CONFIG_COMMAND)" -x c-header $(OPTIONS) -D$(DEFINES) -I"$(INCLUDES)" -c -o "$(<)" "$(>)"')
+
+
+def gcc_compile_cpp(targets, sources, properties):
+ # Some extensions are compiled as C++ by default. For others, we need to
+ # pass -x c++. We could always pass -x c++ but distcc does not work with it.
+ extension = os.path.splitext (sources [0]) [1]
+ lang = ''
+ if not extension in ['.cc', '.cp', '.cxx', '.cpp', '.c++', '.C']:
+ lang = '-x c++'
+ get_manager().engine().set_target_variable (targets, 'LANG', lang)
+ engine.add_dependency(targets, bjam.call('get-target-variable', targets, 'PCH_FILE'))
+
+def gcc_compile_c(targets, sources, properties):
+ engine = get_manager().engine()
+ # If we use the name g++ then default file suffix -> language mapping does
+ # not work. So have to pass -x option. Maybe, we can work around this by
+ # allowing the user to specify both C and C++ compiler names.
+ #if $(>:S) != .c
+ #{
+ engine.set_target_variable (targets, 'LANG', '-x c')
+ #}
+ engine.add_dependency(targets, bjam.call('get-target-variable', targets, 'PCH_FILE'))
+
+engine.register_action(
+ 'gcc.compile.c++',
+ '"$(CONFIG_COMMAND)" $(LANG) -ftemplate-depth-128 $(OPTIONS) ' +
+ '$(USER_OPTIONS) -D$(DEFINES) -I"$(PCH_FILE:D)" -I"$(INCLUDES)" ' +
+ '-c -o "$(<:W)" "$(>:W)"',
+ function=gcc_compile_cpp,
+ bound_list=['PCH_FILE'])
+
+engine.register_action(
+ 'gcc.compile.c',
+ '"$(CONFIG_COMMAND)" $(LANG) $(OPTIONS) $(USER_OPTIONS) -D$(DEFINES) ' +
+ '-I"$(PCH_FILE:D)" -I"$(INCLUDES)" -c -o "$(<)" "$(>)"',
+ function=gcc_compile_c,
+ bound_list=['PCH_FILE'])
+
+def gcc_compile_asm(targets, sources, properties):
+ get_manager().engine().set_target_variable(targets, 'LANG', '-x assembler-with-cpp')
+
+engine.register_action(
+ 'gcc.compile.asm',
+ '"$(CONFIG_COMMAND)" $(LANG) $(OPTIONS) -D$(DEFINES) -I"$(INCLUDES)" -c -o "$(<)" "$(>)"',
+ function=gcc_compile_asm)
+
+
+class GccLinkingGenerator(unix.UnixLinkingGenerator):
+ """
+ The class which check that we don't try to use the <runtime-link>static
+ property while creating or using shared library, since it's not supported by
+ gcc/libc.
+ """
+ def run(self, project, name, prop_set, sources):
+ # TODO: Replace this with the use of a target-os property.
+
+ no_static_link = False
+ if bjam.variable('UNIX'):
+ no_static_link = True;
+ ##FIXME: what does this mean?
+## {
+## switch [ modules.peek : JAMUNAME ]
+## {
+## case * : no-static-link = true ;
+## }
+## }
+
+ properties = prop_set.raw()
+ reason = None
+ if no_static_link and '<runtime-link>static' in properties:
+ if '<link>shared' in properties:
+ reason = "On gcc, DLL can't be build with '<runtime-link>static'."
+ elif type.is_derived(self.target_types[0], 'EXE'):
+ for s in sources:
+ source_type = s.type()
+ if source_type and type.is_derived(source_type, 'SHARED_LIB'):
+ reason = "On gcc, using DLLS together with the " +\
+ "<runtime-link>static options is not possible "
+ if reason:
+ print 'warning:', reason
+ print 'warning:',\
+ "It is suggested to use '<runtime-link>static' together",\
+ "with '<link>static'." ;
+ return
+ else:
+ generated_targets = unix.UnixLinkingGenerator.run(self, project,
+ name, prop_set, sources)
+ return generated_targets
+
+if on_windows():
+ flags('gcc.link.dll', '.IMPLIB-COMMAND', [], ['-Wl,--out-implib,'])
+ generators.register(
+ GccLinkingGenerator('gcc.link', True,
+ ['OBJ', 'SEARCHED_LIB', 'STATIC_LIB', 'IMPORT_LIB'],
+ [ 'EXE' ],
+ [ '<toolset>gcc' ]))
+ generators.register(
+ GccLinkingGenerator('gcc.link.dll', True,
+ ['OBJ', 'SEARCHED_LIB', 'STATIC_LIB', 'IMPORT_LIB'],
+ ['IMPORT_LIB', 'SHARED_LIB'],
+ ['<toolset>gcc']))
+else:
+ generators.register(
+ GccLinkingGenerator('gcc.link', True,
+ ['LIB', 'OBJ'],
+ ['EXE'],
+ ['<toolset>gcc']))
+ generators.register(
+ GccLinkingGenerator('gcc.link.dll', True,
+ ['LIB', 'OBJ'],
+ ['SHARED_LIB'],
+ ['<toolset>gcc']))
+
+# Declare flags for linking.
+# First, the common flags.
+flags('gcc.link', 'OPTIONS', ['<debug-symbols>on'], ['-g'])
+flags('gcc.link', 'OPTIONS', ['<profiling>on'], ['-pg'])
+flags('gcc.link', 'USER_OPTIONS', [], ['<linkflags>'])
+flags('gcc.link', 'LINKPATH', [], ['<library-path>'])
+flags('gcc.link', 'FINDLIBS-ST', [], ['<find-static-library>'])
+flags('gcc.link', 'FINDLIBS-SA', [], ['<find-shared-library>'])
+flags('gcc.link', 'LIBRARIES', [], ['<library-file>'])
+
+# For <runtime-link>static we made sure there are no dynamic libraries in the
+# link. On HP-UX not all system libraries exist as archived libraries (for
+# example, there is no libunwind.a), so, on this platform, the -static option
+# cannot be specified.
+if os_name() != 'HPUX':
+ flags('gcc.link', 'OPTIONS', ['<runtime-link>static'], ['-static'])
+
+# Now, the vendor specific flags.
+# The parameter linker can be either gnu, darwin, osf, hpux or sun.
+def init_link_flags(toolset, linker, condition):
+ """
+ Now, the vendor specific flags.
+ The parameter linker can be either gnu, darwin, osf, hpux or sun.
+ """
+ toolset_link = toolset + '.link'
+ if linker == 'gnu':
+ # Strip the binary when no debugging is needed. We use --strip-all flag
+ # as opposed to -s since icc (intel's compiler) is generally
+ # option-compatible with and inherits from the gcc toolset, but does not
+ # support -s.
+
+ # FIXME: what does unchecked translate to?
+ flags(toolset_link, 'OPTIONS', map(lambda x: x + '/<debug-symbols>off', condition), ['-Wl,--strip-all']) # : unchecked ;
+ flags(toolset_link, 'RPATH', condition, ['<dll-path>']) # : unchecked ;
+ flags(toolset_link, 'RPATH_LINK', condition, ['<xdll-path>']) # : unchecked ;
+ flags(toolset_link, 'START-GROUP', condition, ['-Wl,--start-group'])# : unchecked ;
+ flags(toolset_link, 'END-GROUP', condition, ['-Wl,--end-group']) # : unchecked ;
+
+ # gnu ld has the ability to change the search behaviour for libraries
+ # referenced by -l switch. These modifiers are -Bstatic and -Bdynamic
+ # and change search for -l switches that follow them. The following list
+ # shows the tried variants.
+ # The search stops at the first variant that has a match.
+ # *nix: -Bstatic -lxxx
+ # libxxx.a
+ #
+ # *nix: -Bdynamic -lxxx
+ # libxxx.so
+ # libxxx.a
+ #
+ # windows (mingw,cygwin) -Bstatic -lxxx
+ # libxxx.a
+ # xxx.lib
+ #
+ # windows (mingw,cygwin) -Bdynamic -lxxx
+ # libxxx.dll.a
+ # xxx.dll.a
+ # libxxx.a
+ # xxx.lib
+ # cygxxx.dll (*)
+ # libxxx.dll
+ # xxx.dll
+ # libxxx.a
+ #
+ # (*) This is for cygwin
+ # Please note that -Bstatic and -Bdynamic are not a guarantee that a
+ # static or dynamic lib indeed gets linked in. The switches only change
+ # search patterns!
+
+ # On *nix mixing shared libs with static runtime is not a good idea.
+ flags(toolset_link, 'FINDLIBS-ST-PFX',
+ map(lambda x: x + '/<runtime-link>shared', condition),
+ ['-Wl,-Bstatic']) # : unchecked ;
+ flags(toolset_link, 'FINDLIBS-SA-PFX',
+ map(lambda x: x + '/<runtime-link>shared', condition),
+ ['-Wl,-Bdynamic']) # : unchecked ;
+
+ # On windows allow mixing of static and dynamic libs with static
+ # runtime.
+ flags(toolset_link, 'FINDLIBS-ST-PFX',
+ map(lambda x: x + '/<runtime-link>static/<target-os>windows', condition),
+ ['-Wl,-Bstatic']) # : unchecked ;
+ flags(toolset_link, 'FINDLIBS-SA-PFX',
+ map(lambda x: x + '/<runtime-link>static/<target-os>windows', condition),
+ ['-Wl,-Bdynamic']) # : unchecked ;
+ flags(toolset_link, 'OPTIONS',
+ map(lambda x: x + '/<runtime-link>static/<target-os>windows', condition),
+ ['-Wl,-Bstatic']) # : unchecked ;
+
+ elif linker == 'darwin':
+ # On Darwin, the -s option to ld does not work unless we pass -static,
+ # and passing -static unconditionally is a bad idea. So, don't pass -s.
+ # at all, darwin.jam will use separate 'strip' invocation.
+ flags(toolset_link, 'RPATH', condition, ['<dll-path>']) # : unchecked ;
+ flags(toolset_link, 'RPATH_LINK', condition, ['<xdll-path>']) # : unchecked ;
+
+ elif linker == 'osf':
+ # No --strip-all, just -s.
+ flags(toolset_link, 'OPTIONS', map(lambda x: x + '/<debug-symbols>off', condition), ['-Wl,-s'])
+ # : unchecked ;
+ flags(toolset_link, 'RPATH', condition, ['<dll-path>']) # : unchecked ;
+ # This does not supports -R.
+ flags(toolset_link, 'RPATH_OPTION', condition, ['-rpath']) # : unchecked ;
+ # -rpath-link is not supported at all.
+
+ elif linker == 'sun':
+ flags(toolset_link, 'OPTIONS', map(lambda x: x + '/<debug-symbols>off', condition), ['-Wl,-s'])
+ # : unchecked ;
+ flags(toolset_link, 'RPATH', condition, ['<dll-path>']) # : unchecked ;
+ # Solaris linker does not have a separate -rpath-link, but allows to use
+ # -L for the same purpose.
+ flags(toolset_link, 'LINKPATH', condition, ['<xdll-path>']) # : unchecked ;
+
+ # This permits shared libraries with non-PIC code on Solaris.
+ # VP, 2004/09/07: Now that we have -fPIC hardcode in link.dll, the
+ # following is not needed. Whether -fPIC should be hardcoded, is a
+ # separate question.
+ # AH, 2004/10/16: it is still necessary because some tests link against
+ # static libraries that were compiled without PIC.
+ flags(toolset_link, 'OPTIONS', map(lambda x: x + '/<link>shared', condition), ['-mimpure-text'])
+ # : unchecked ;
+
+ elif linker == 'hpux':
+ flags(toolset_link, 'OPTIONS', map(lambda x: x + '/<debug-symbols>off', condition),
+ ['-Wl,-s']) # : unchecked ;
+ flags(toolset_link, 'OPTIONS', map(lambda x: x + '/<link>shared', condition),
+ ['-fPIC']) # : unchecked ;
+
+ else:
+ # FIXME:
+ errors.user_error(
+ "$(toolset) initialization: invalid linker '$(linker)' " +
+ "The value '$(linker)' specified for <linker> is not recognized. " +
+ "Possible values are 'gnu', 'darwin', 'osf', 'hpux' or 'sun'")
+
+# Declare actions for linking.
+def gcc_link(targets, sources, properties):
+ engine = get_manager().engine()
+ engine.set_target_variable(targets, 'SPACE', ' ')
+ # Serialize execution of the 'link' action, since running N links in
+ # parallel is just slower. For now, serialize only gcc links, it might be a
+ # good idea to serialize all links.
+ engine.set_target_variable(targets, 'JAM_SEMAPHORE', '<s>gcc-link-semaphore')
+
+engine.register_action(
+ 'gcc.link',
+ '"$(CONFIG_COMMAND)" -L"$(LINKPATH)" ' +
+ '-Wl,$(RPATH_OPTION:E=-R)$(SPACE)-Wl,"$(RPATH)" ' +
+ '-Wl,-rpath-link$(SPACE)-Wl,"$(RPATH_LINK)" -o "$(<)" ' +
+ '$(START-GROUP) "$(>)" "$(LIBRARIES)" $(FINDLIBS-ST-PFX) ' +
+ '-l$(FINDLIBS-ST) $(FINDLIBS-SA-PFX) -l$(FINDLIBS-SA) $(END-GROUP) ' +
+ '$(OPTIONS) $(USER_OPTIONS)',
+ function=gcc_link,
+ bound_list=['LIBRARIES'])
+
+# Default value. Mostly for the sake of intel-linux that inherits from gcc, but
+# does not have the same logic to set the .AR variable. We can put the same
+# logic in intel-linux, but that's hardly worth the trouble as on Linux, 'ar' is
+# always available.
+__AR = 'ar'
+
+flags('gcc.archive', 'AROPTIONS', [], ['<archiveflags>'])
+
+def gcc_archive(targets, sources, properties):
+ # Always remove archive and start again. Here's rationale from
+ #
+ # Andre Hentz:
+ #
+ # I had a file, say a1.c, that was included into liba.a. I moved a1.c to
+ # a2.c, updated my Jamfiles and rebuilt. My program was crashing with absurd
+ # errors. After some debugging I traced it back to the fact that a1.o was
+ # *still* in liba.a
+ #
+ # Rene Rivera:
+ #
+ # Originally removing the archive was done by splicing an RM onto the
+ # archive action. That makes archives fail to build on NT when they have
+ # many files because it will no longer execute the action directly and blow
+ # the line length limit. Instead we remove the file in a different action,
+ # just before building the archive.
+ clean = targets[0] + '(clean)'
+ bjam.call('TEMPORARY', clean)
+ bjam.call('NOCARE', clean)
+ engine = get_manager().engine()
+ engine.set_target_variable('LOCATE', clean, bjam.call('get-target-variable', targets, 'LOCATE'))
+ engine.add_dependency(clean, sources)
+ engine.add_dependency(targets, clean)
+ engine.set_update_action('common.RmTemps', clean, targets, None)
+
+# Declare action for creating static libraries.
+# The letter 'r' means to add files to the archive with replacement. Since we
+# remove archive, we don't care about replacement, but there's no option "add
+# without replacement".
+# The letter 'c' suppresses the warning in case the archive does not exists yet.
+# That warning is produced only on some platforms, for whatever reasons.
+engine.register_action('gcc.archive',
+ '"$(.AR)" $(AROPTIONS) rc "$(<)" "$(>)"',
+ function=gcc_archive,
+ flags=['piecemeal'])
+
+def gcc_link_dll(targets, sources, properties):
+ engine = get_manager().engine()
+ engine.set_target_variable(targets, 'SPACE', ' ')
+ engine.set_target_variable(targets, 'JAM_SEMAPHORE', '<s>gcc-link-semaphore')
+
+engine.register_action(
+ 'gcc.link.dll',
+ # Differ from 'link' above only by -shared.
+ '"$(CONFIG_COMMAND)" -L"$(LINKPATH)" ' +
+ '-Wl,$(RPATH_OPTION:E=-R)$(SPACE)-Wl,"$(RPATH)" ' +
+ '"$(.IMPLIB-COMMAND)$(<[1])" -o "$(<[-1])" ' +
+ '$(HAVE_SONAME)-Wl,$(SONAME_OPTION)$(SPACE)-Wl,$(<[-1]:D=) ' +
+ '-shared $(START-GROUP) "$(>)" "$(LIBRARIES)" $(FINDLIBS-ST-PFX) ' +
+ '-l$(FINDLIBS-ST) $(FINDLIBS-SA-PFX) -l$(FINDLIBS-SA) $(END-GROUP) ' +
+ '$(OPTIONS) $(USER_OPTIONS)',
+ function = gcc_link_dll,
+ bound_list=['LIBRARIES'])
+
+# Set up threading support. It's somewhat contrived, so perform it at the end,
+# to avoid cluttering other code.
+
+if on_windows():
+ flags('gcc', 'OPTIONS', ['<threading>multi'], ['-mthreads'])
+elif bjam.variable('UNIX'):
+ jamuname = bjam.variable('JAMUNAME')
+ host_os_name = jamuname[0]
+ if host_os_name.startswith('SunOS'):
+ flags('gcc', 'OPTIONS', ['<threading>multi'], ['-pthreads'])
+ flags('gcc', 'FINDLIBS-SA', [], ['rt'])
+ elif host_os_name == 'BeOS':
+ # BeOS has no threading options, don't set anything here.
+ pass
+ elif host_os_name.endswith('BSD'):
+ flags('gcc', 'OPTIONS', ['<threading>multi'], ['-pthread'])
+ # there is no -lrt on BSD
+ elif host_os_name == 'DragonFly':
+ flags('gcc', 'OPTIONS', ['<threading>multi'], ['-pthread'])
+ # there is no -lrt on BSD - DragonFly is a FreeBSD variant,
+ # which anoyingly doesn't say it's a *BSD.
+ elif host_os_name == 'IRIX':
+ # gcc on IRIX does not support multi-threading, don't set anything here.
+ pass
+ elif host_os_name == 'Darwin':
+ # Darwin has no threading options, don't set anything here.
+ pass
+ else:
+ flags('gcc', 'OPTIONS', ['<threading>multi'], ['-pthread'])
+ flags('gcc', 'FINDLIBS-SA', [], ['rt'])
+
+def cpu_flags(toolset, variable, architecture, instruction_set, values, default=None):
+ #FIXME: for some reason this fails. Probably out of date feature code
+## if default:
+## flags(toolset, variable,
+## ['<architecture>' + architecture + '/<instruction-set>'],
+## values)
+ flags(toolset, variable,
+ #FIXME: same as above
+ [##'<architecture>/<instruction-set>' + instruction_set,
+ '<architecture>' + architecture + '/<instruction-set>' + instruction_set],
+ values)
+
+# Set architecture/instruction-set options.
+#
+# x86 and compatible
+flags('gcc', 'OPTIONS', ['<architecture>x86/<address-model>32'], ['-m32'])
+flags('gcc', 'OPTIONS', ['<architecture>x86/<address-model>64'], ['-m64'])
+cpu_flags('gcc', 'OPTIONS', 'x86', 'i386', ['-march=i386'], default=True)
+cpu_flags('gcc', 'OPTIONS', 'x86', 'i486', ['-march=i486'])
+cpu_flags('gcc', 'OPTIONS', 'x86', 'i586', ['-march=i586'])
+cpu_flags('gcc', 'OPTIONS', 'x86', 'i686', ['-march=i686'])
+cpu_flags('gcc', 'OPTIONS', 'x86', 'pentium', ['-march=pentium'])
+cpu_flags('gcc', 'OPTIONS', 'x86', 'pentium-mmx', ['-march=pentium-mmx'])
+cpu_flags('gcc', 'OPTIONS', 'x86', 'pentiumpro', ['-march=pentiumpro'])
+cpu_flags('gcc', 'OPTIONS', 'x86', 'pentium2', ['-march=pentium2'])
+cpu_flags('gcc', 'OPTIONS', 'x86', 'pentium3', ['-march=pentium3'])
+cpu_flags('gcc', 'OPTIONS', 'x86', 'pentium3m', ['-march=pentium3m'])
+cpu_flags('gcc', 'OPTIONS', 'x86', 'pentium-m', ['-march=pentium-m'])
+cpu_flags('gcc', 'OPTIONS', 'x86', 'pentium4', ['-march=pentium4'])
+cpu_flags('gcc', 'OPTIONS', 'x86', 'pentium4m', ['-march=pentium4m'])
+cpu_flags('gcc', 'OPTIONS', 'x86', 'prescott', ['-march=prescott'])
+cpu_flags('gcc', 'OPTIONS', 'x86', 'nocona', ['-march=nocona'])
+cpu_flags('gcc', 'OPTIONS', 'x86', 'k6', ['-march=k6'])
+cpu_flags('gcc', 'OPTIONS', 'x86', 'k6-2', ['-march=k6-2'])
+cpu_flags('gcc', 'OPTIONS', 'x86', 'k6-3', ['-march=k6-3'])
+cpu_flags('gcc', 'OPTIONS', 'x86', 'athlon', ['-march=athlon'])
+cpu_flags('gcc', 'OPTIONS', 'x86', 'athlon-tbird', ['-march=athlon-tbird'])
+cpu_flags('gcc', 'OPTIONS', 'x86', 'athlon-4', ['-march=athlon-4'])
+cpu_flags('gcc', 'OPTIONS', 'x86', 'athlon-xp', ['-march=athlon-xp'])
+cpu_flags('gcc', 'OPTIONS', 'x86', 'athlon-mp', ['-march=athlon-mp'])
+##
+cpu_flags('gcc', 'OPTIONS', 'x86', 'k8', ['-march=k8'])
+cpu_flags('gcc', 'OPTIONS', 'x86', 'opteron', ['-march=opteron'])
+cpu_flags('gcc', 'OPTIONS', 'x86', 'athlon64', ['-march=athlon64'])
+cpu_flags('gcc', 'OPTIONS', 'x86', 'athlon-fx', ['-march=athlon-fx'])
+cpu_flags('gcc', 'OPTIONS', 'x86', 'winchip-c6', ['-march=winchip-c6'])
+cpu_flags('gcc', 'OPTIONS', 'x86', 'winchip2', ['-march=winchip2'])
+cpu_flags('gcc', 'OPTIONS', 'x86', 'c3', ['-march=c3'])
+cpu_flags('gcc', 'OPTIONS', 'x86', 'c3-2', ['-march=c3-2'])
+# Sparc
+flags('gcc', 'OPTIONS', ['<architecture>sparc/<address-model>32'], ['-m32'])
+flags('gcc', 'OPTIONS', ['<architecture>sparc/<address-model>64'], ['-m64'])
+cpu_flags('gcc', 'OPTIONS', 'sparc', 'c3', ['-mcpu=c3'], default=True)
+cpu_flags('gcc', 'OPTIONS', 'sparc', 'v7', ['-mcpu=v7'])
+cpu_flags('gcc', 'OPTIONS', 'sparc', 'cypress', ['-mcpu=cypress'])
+cpu_flags('gcc', 'OPTIONS', 'sparc', 'v8', ['-mcpu=v8'])
+cpu_flags('gcc', 'OPTIONS', 'sparc', 'supersparc', ['-mcpu=supersparc'])
+cpu_flags('gcc', 'OPTIONS', 'sparc', 'sparclite', ['-mcpu=sparclite'])
+cpu_flags('gcc', 'OPTIONS', 'sparc', 'hypersparc', ['-mcpu=hypersparc'])
+cpu_flags('gcc', 'OPTIONS', 'sparc', 'sparclite86x', ['-mcpu=sparclite86x'])
+cpu_flags('gcc', 'OPTIONS', 'sparc', 'f930', ['-mcpu=f930'])
+cpu_flags('gcc', 'OPTIONS', 'sparc', 'f934', ['-mcpu=f934'])
+cpu_flags('gcc', 'OPTIONS', 'sparc', 'sparclet', ['-mcpu=sparclet'])
+cpu_flags('gcc', 'OPTIONS', 'sparc', 'tsc701', ['-mcpu=tsc701'])
+cpu_flags('gcc', 'OPTIONS', 'sparc', 'v9', ['-mcpu=v9'])
+cpu_flags('gcc', 'OPTIONS', 'sparc', 'ultrasparc', ['-mcpu=ultrasparc'])
+cpu_flags('gcc', 'OPTIONS', 'sparc', 'ultrasparc3', ['-mcpu=ultrasparc3'])
+# RS/6000 & PowerPC
+flags('gcc', 'OPTIONS', ['<architecture>power/<address-model>32'], ['-m32'])
+flags('gcc', 'OPTIONS', ['<architecture>power/<address-model>64'], ['-m64'])
+cpu_flags('gcc', 'OPTIONS', 'power', '403', ['-mcpu=403'])
+cpu_flags('gcc', 'OPTIONS', 'power', '505', ['-mcpu=505'])
+cpu_flags('gcc', 'OPTIONS', 'power', '601', ['-mcpu=601'])
+cpu_flags('gcc', 'OPTIONS', 'power', '602', ['-mcpu=602'])
+cpu_flags('gcc', 'OPTIONS', 'power', '603', ['-mcpu=603'])
+cpu_flags('gcc', 'OPTIONS', 'power', '603e', ['-mcpu=603e'])
+cpu_flags('gcc', 'OPTIONS', 'power', '604', ['-mcpu=604'])
+cpu_flags('gcc', 'OPTIONS', 'power', '604e', ['-mcpu=604e'])
+cpu_flags('gcc', 'OPTIONS', 'power', '620', ['-mcpu=620'])
+cpu_flags('gcc', 'OPTIONS', 'power', '630', ['-mcpu=630'])
+cpu_flags('gcc', 'OPTIONS', 'power', '740', ['-mcpu=740'])
+cpu_flags('gcc', 'OPTIONS', 'power', '7400', ['-mcpu=7400'])
+cpu_flags('gcc', 'OPTIONS', 'power', '7450', ['-mcpu=7450'])
+cpu_flags('gcc', 'OPTIONS', 'power', '750', ['-mcpu=750'])
+cpu_flags('gcc', 'OPTIONS', 'power', '801', ['-mcpu=801'])
+cpu_flags('gcc', 'OPTIONS', 'power', '821', ['-mcpu=821'])
+cpu_flags('gcc', 'OPTIONS', 'power', '823', ['-mcpu=823'])
+cpu_flags('gcc', 'OPTIONS', 'power', '860', ['-mcpu=860'])
+cpu_flags('gcc', 'OPTIONS', 'power', '970', ['-mcpu=970'])
+cpu_flags('gcc', 'OPTIONS', 'power', '8540', ['-mcpu=8540'])
+cpu_flags('gcc', 'OPTIONS', 'power', 'power', ['-mcpu=power'])
+cpu_flags('gcc', 'OPTIONS', 'power', 'power2', ['-mcpu=power2'])
+cpu_flags('gcc', 'OPTIONS', 'power', 'power3', ['-mcpu=power3'])
+cpu_flags('gcc', 'OPTIONS', 'power', 'power4', ['-mcpu=power4'])
+cpu_flags('gcc', 'OPTIONS', 'power', 'power5', ['-mcpu=power5'])
+cpu_flags('gcc', 'OPTIONS', 'power', 'powerpc', ['-mcpu=powerpc'])
+cpu_flags('gcc', 'OPTIONS', 'power', 'powerpc64', ['-mcpu=powerpc64'])
+cpu_flags('gcc', 'OPTIONS', 'power', 'rios', ['-mcpu=rios'])
+cpu_flags('gcc', 'OPTIONS', 'power', 'rios1', ['-mcpu=rios1'])
+cpu_flags('gcc', 'OPTIONS', 'power', 'rios2', ['-mcpu=rios2'])
+cpu_flags('gcc', 'OPTIONS', 'power', 'rsc', ['-mcpu=rsc'])
+cpu_flags('gcc', 'OPTIONS', 'power', 'rs64a', ['-mcpu=rs64'])
+# AIX variant of RS/6000 & PowerPC
+flags('gcc', 'OPTIONS', ['<architecture>power/<address-model>32/<target-os>aix'], ['-maix32'])
+flags('gcc', 'OPTIONS', ['<architecture>power/<address-model>64/<target-os>aix'], ['-maix64'])
+flags('gcc', 'AROPTIONS', ['<architecture>power/<address-model>64/<target-os>aix'], ['-X 64'])

Added: trunk/tools/build/v2/tools/make.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/tools/make.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,55 @@
+# Status: being ported by Vladimir Prus
+
+# Copyright 2003 Dave Abrahams
+# Copyright 2003 Douglas Gregor
+# Copyright 2006 Rene Rivera
+# Copyright 2002, 2003, 2004, 2005, 2006 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 'make' main target rule.
+
+from b2.build.targets import BasicTarget
+from b2.build.virtual_target import Action, FileTarget
+from b2.build import type
+from b2.manager import get_manager
+import b2.build.property_set
+
+class MakeTarget(BasicTarget):
+
+ def construct(self, name, source_targets, property_set):
+
+ action_name = property_set.get("<action>")[0]
+
+ action = Action(get_manager(), source_targets, action_name, property_set)
+ # FIXME: type.type uses global data.
+ target = FileTarget(self.name(), 1, type.type(self.name()),
+ self.project(), action)
+ return [ b2.build.property_set.empty(),
+ [self.project().manager().virtual_targets().register(target)]]
+
+def make (target_name, sources, generating_rule,
+ requirements=None, usage_requirements=None):
+
+ target_name = target_name[0]
+ generating_rule = generating_rule[0]
+
+ if not requirements:
+ requirements = []
+
+ requirements.append("<action>%s" % generating_rule)
+ m = get_manager()
+ targets = m.targets()
+ project = m.projects().current()
+ engine = m.engine()
+ engine.register_bjam_action(generating_rule)
+
+ targets.main_target_alternative(MakeTarget(
+ target_name, project,
+ targets.main_target_sources(sources, target_name),
+ targets.main_target_requirements(requirements, project),
+ targets.main_target_default_build([], project),
+ targets.main_target_usage_requirements(usage_requirements or [], project)))
+
+get_manager().projects().add_rule("make", make)
+

Added: trunk/tools/build/v2/tools/pch.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/tools/pch.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,83 @@
+# Status: Being ported by Steven Watanabe
+# Base revision: 47077
+#
+# Copyright (c) 2005 Reece H. Dunn.
+# Copyright 2006 Ilya Sokolov
+# Copyright (c) 2008 Steven Watanabe
+#
+# Use, modification and distribution is subject to the Boost Software
+# License Version 1.0. (See accompanying file LICENSE_1_0.txt or
+# http://www.boost.org/LICENSE_1_0.txt)
+
+##### Using Precompiled Headers (Quick Guide) #####
+#
+# Make precompiled mypch.hpp:
+#
+# import pch ;
+#
+# cpp-pch mypch
+# : # sources
+# mypch.hpp
+# : # requiremnts
+# <toolset>msvc:<source>mypch.cpp
+# ;
+#
+# Add cpp-pch to sources:
+#
+# exe hello
+# : main.cpp hello.cpp mypch
+# ;
+
+from b2.build import type, feature, generators
+
+type.register('PCH', ['pch'])
+type.register('C_PCH', [], 'PCH')
+type.register('CPP_PCH', [], 'PCH')
+
+# Control precompiled header (PCH) generation.
+feature.feature('pch',
+ ['on', 'off'],
+ ['propagated'])
+
+feature.feature('pch-header', [], ['free', 'dependency'])
+feature.feature('pch-file', [], ['free', 'dependency'])
+
+class PchGenerator(generators.Generator):
+ """
+ Base PCH generator. The 'run' method has the logic to prevent this generator
+ from being run unless it's being used for a top-level PCH target.
+ """
+ def action_class(self):
+ return 'compile-action'
+
+ def run(self, project, name, prop_set, sources):
+ if not name:
+ # Unless this generator is invoked as the top-most generator for a
+ # main target, fail. This allows using 'H' type as input type for
+ # this generator, while preventing Boost.Build to try this generator
+ # when not explicitly asked for.
+ #
+ # One bad example is msvc, where pch generator produces both PCH
+ # target and OBJ target, so if there's any header generated (like by
+ # bison, or by msidl), we'd try to use pch generator to get OBJ from
+ # that H, which is completely wrong. By restricting this generator
+ # only to pch main target, such problem is solved.
+ pass
+ else:
+ r = self.run_pch(project, name,
+ prop_set.add_raw('<define>BOOST_BUILD_PCH_ENABLED'),
+ sources)
+ return generators.add_usage_requirements(
+ r, ['<define>BOOST_BUILD_PCH_ENABLED'])
+
+ # This rule must be overridden by the derived classes.
+ def run_pch(self, project, name, prop_set, sources):
+ pass
+
+#FIXME: dummy-generator in builtins.jam needs to be ported.
+# NOTE: requirements are empty, default pch generator can be applied when
+# pch=off.
+###generators.register(
+### [ new dummy-generator pch.default-c-pch-generator : : C_PCH ] ;
+###generators.register
+### [ new dummy-generator pch.default-cpp-pch-generator : : CPP_PCH ] ;

Added: trunk/tools/build/v2/tools/rc.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/tools/rc.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,189 @@
+# Status: being ported by Steven Watanabe
+# Base revision: 47077
+#
+# Copyright (C) Andre Hentz 2003. Permission to copy, use, modify, sell and
+# distribute this software is granted provided this copyright notice appears in
+# all copies. This software is provided "as is" without express or implied
+# warranty, and with no claim as to its suitability for any purpose.
+#
+# Copyright (c) 2006 Rene Rivera.
+#
+# Copyright (c) 2008 Steven Watanabe
+#
+# Use, modification and distribution is subject to the Boost Software
+# License Version 1.0. (See accompanying file LICENSE_1_0.txt or
+# http://www.boost.org/LICENSE_1_0.txt)
+
+##import type ;
+##import generators ;
+##import feature ;
+##import errors ;
+##import scanner ;
+##import toolset : flags ;
+
+from b2.build import type, toolset, generators, scanner, feature
+from b2.tools import builtin
+from b2.util import regex
+from b2.build.toolset import flags
+from b2.manager import get_manager
+
+__debug = None
+
+def debug():
+ global __debug
+ if __debug is None:
+ __debug = "--debug-configuration" in bjam.variable("ARGV")
+ return __debug
+
+type.register('RC', ['rc'])
+
+def init():
+ pass
+
+def configure (command = None, condition = None, options = None):
+ """
+ Configures a new resource compilation command specific to a condition,
+ usually a toolset selection condition. The possible options are:
+
+ * <rc-type>(rc|windres) - Indicates the type of options the command
+ accepts.
+
+ Even though the arguments are all optional, only when a command, condition,
+ and at minimum the rc-type option are given will the command be configured.
+ This is so that callers don't have to check auto-configuration values
+ before calling this. And still get the functionality of build failures when
+ the resource compiler can't be found.
+ """
+ rc_type = feature.get_values('<rc-type>', options)
+ if rc_type:
+ assert(len(rc_type) == 1)
+ rc_type = rc_type[0]
+
+ if command and condition and rc_type:
+ flags('rc.compile.resource', '.RC', condition, command)
+ flags('rc.compile.resource', '.RC_TYPE', condition, rc_type.lower())
+ flags('rc.compile.resource', 'DEFINES', [], ['<define>'])
+ flags('rc.compile.resource', 'INCLUDES', [], ['<include>'])
+ if debug():
+ print 'notice: using rc compiler ::', condition, '::', command
+
+engine = get_manager().engine()
+
+class RCAction:
+ """Class representing bjam action defined from Python.
+ The function must register the action to execute."""
+
+ def __init__(self, action_name, function):
+ self.action_name = action_name
+ self.function = function
+
+ def __call__(self, targets, sources, property_set):
+ if self.function:
+ self.function(targets, sources, property_set)
+
+# FIXME: What is the proper way to dispatch actions?
+def rc_register_action(action_name, function = None):
+ global engine
+ if engine.actions.has_key(action_name):
+ raise "Bjam action %s is already defined" % action_name
+ engine.actions[action_name] = RCAction(action_name, function)
+
+def rc_compile_resource(targets, sources, properties):
+ rc_type = bjam.call('get-target-variable', targets, '.RC_TYPE')
+ global engine
+ engine.set_update_action('rc.compile.resource.' + rc_type, targets, sources, properties)
+
+rc_register_action('rc.compile.resource', rc_compile_resource)
+
+
+engine.register_action(
+ 'rc.compile.resource.rc',
+ '"$(.RC)" -l 0x409 "-U$(UNDEFS)" "-D$(DEFINES)" -I"$(>:D)" -I"$(<:D)" -I"$(INCLUDES)" -fo "$(<)" "$(>)"')
+
+engine.register_action(
+ 'rc.compile.resource.windres',
+ '"$(.RC)" "-U$(UNDEFS)" "-D$(DEFINES)" -I"$(>:D)" -I"$(<:D)" -I"$(INCLUDES)" -o "$(<)" -i "$(>)"')
+
+# FIXME: this was originally declared quietly
+engine.register_action(
+ 'compile.resource.null',
+ 'as /dev/null -o "$(<)"')
+
+# Since it's a common practice to write
+# exe hello : hello.cpp hello.rc
+# we change the name of object created from RC file, to
+# avoid conflict with hello.cpp.
+# The reason we generate OBJ and not RES, is that gcc does not
+# seem to like RES files, but works OK with OBJ.
+# See http://article.gmane.org/gmane.comp.lib.boost.build/5643/
+#
+# Using 'register-c-compiler' adds the build directory to INCLUDES
+# FIXME: switch to generators
+builtin.register_c_compiler('rc.compile.resource', ['RC'], ['OBJ(%_res)'], [])
+
+__angle_include_re = "#include[ ]*<([^<]+)>"
+
+# Register scanner for resources
+class ResScanner(scanner.Scanner):
+
+ def __init__(self, includes):
+ scanner.__init__ ;
+ self.includes = includes
+
+ def pattern(self):
+ return "(([^ ]+[ ]+(BITMAP|CURSOR|FONT|ICON|MESSAGETABLE|RT_MANIFEST)" +\
+ "[ ]+([^ \"]+|\"[^\"]+\"))|(#include[ ]*(<[^<]+>|\"[^\"]+\")))" ;
+
+ def process(self, target, matches, binding):
+
+ angle = regex.transform(matches, "#include[ ]*<([^<]+)>")
+ quoted = regex.transform(matches, "#include[ ]*\"([^\"]+)\"")
+ res = regex.transform(matches,
+ "[^ ]+[ ]+(BITMAP|CURSOR|FONT|ICON|MESSAGETABLE|RT_MANIFEST)" +\
+ "[ ]+(([^ \"]+)|\"([^\"]+)\")", [3, 4])
+
+ # Icons and other includes may referenced as
+ #
+ # IDR_MAINFRAME ICON "res\\icon.ico"
+ #
+ # so we have to replace double backslashes to single ones.
+ res = [ re.sub(r'\\\\', '/', match) for match in res ]
+
+ # CONSIDER: the new scoping rule seem to defeat "on target" variables.
+ g = bjam.call('get-target-variable', target, 'HDRGRIST')
+ b = os.path.normalize_path(os.path.dirname(binding))
+
+ # Attach binding of including file to included targets.
+ # When target is directly created from virtual target
+ # this extra information is unnecessary. But in other
+ # cases, it allows to distinguish between two headers of the
+ # same name included from different places.
+ # We don't need this extra information for angle includes,
+ # since they should not depend on including file (we can't
+ # get literal "." in include path).
+ g2 = g + "#" + b
+
+ g = "<" + g + ">"
+ g2 = "<" + g2 + ">"
+ angle = [g + x for x in angle]
+ quoted = [g2 + x for x in quoted]
+ res = [g2 + x for x in res]
+
+ all = angle + quoted
+
+ bjam.call('mark-included', target, all)
+
+ engine = get_manager().engine()
+
+ engine.add_dependency(target, res)
+ bjam.call('NOCARE', all + res)
+ engine.set_target_variable(angle, 'SEARCH', ungrist(self.includes))
+ engine.set_target_variable(quoted, 'SEARCH', b + ungrist(self.includes))
+ engine.set_target_variable(res, 'SEARCH', b + ungrist(self.includes)) ;
+
+ # Just propagate current scanner to includes, in a hope
+ # that includes do not change scanners.
+ get_manager().scanners().propagate(self, angle + quoted)
+
+scanner.register(ResScanner, 'include')
+type.set_scanner('RC', ResScanner)

Added: trunk/tools/build/v2/tools/types/__init__.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/tools/types/__init__.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,18 @@
+__all__ = [
+ 'asm',
+ 'cpp',
+ 'exe',
+ 'html',
+ 'lib',
+ 'obj',
+ 'rsp',
+]
+
+def register_all ():
+ for i in __all__:
+ m = __import__ (__name__ + '.' + i)
+ reg = i + '.register ()'
+ #exec (reg)
+
+# TODO: (PF) I thought these would be imported automatically. Anyone knows why they aren't?
+register_all ()

Added: trunk/tools/build/v2/tools/types/asm.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/tools/types/asm.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,13 @@
+# Copyright Craig Rodrigues 2005.
+# Copyright (c) 2008 Steven Watanabe
+#
+# Distributed under the Boost
+# Software License, Version 1.0. (See accompanying
+# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+from b2.build import type
+
+def register():
+ type.register_type('ASM', ['s', 'S', 'asm'])
+
+register()

Added: trunk/tools/build/v2/tools/types/cpp.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/tools/types/cpp.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,10 @@
+# Copyright David Abrahams 2004. Distributed under the Boost
+# Software License, Version 1.0. (See accompanying
+# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+from b2.build import type
+
+def register ():
+ type.register_type ('CPP', ['cpp', 'cxx', 'cc'])
+
+register ()

Added: trunk/tools/build/v2/tools/types/exe.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/tools/types/exe.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,11 @@
+# Copyright David Abrahams 2004. Distributed under the Boost
+# Software License, Version 1.0. (See accompanying
+# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+from b2.build import type
+
+def register ():
+ type.register_type ('EXE', ['exe'], None, ['NT', 'CYGWIN'])
+ type.register_type ('EXE', [], None, [])
+
+register ()

Added: trunk/tools/build/v2/tools/types/html.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/tools/types/html.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,10 @@
+# Copyright David Abrahams 2004. Distributed under the Boost
+# Software License, Version 1.0. (See accompanying
+# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+from b2.build import type
+
+def register ():
+ type.register_type ('HTML', ['html'])
+
+register ()

Added: trunk/tools/build/v2/tools/types/lib.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/tools/types/lib.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,23 @@
+# Copyright David Abrahams 2004. Distributed under the Boost
+# Software License, Version 1.0. (See accompanying
+# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+from b2.build import type
+
+def register ():
+
+ if not type.registered ('LIB'):
+ type.register ('LIB')
+
+ type.register_type ('STATIC_LIB', ['lib', 'a'], 'LIB', ['NT', 'CYGWIN'])
+ type.register_type ('STATIC_LIB', ['a'], 'LIB')
+
+ type.register_type ('IMPORT_LIB', [], 'STATIC_LIB')
+ type.set_generated_target_suffix ('IMPORT_LIB', [], 'lib')
+
+ type.register_type ('SHARED_LIB', ['dll'], 'LIB', ['NT', 'CYGWIN'])
+ type.register_type ('SHARED_LIB', ['so'], 'LIB')
+
+ type.register_type ('SEARCHED_LIB', [], 'LIB')
+
+register ()

Added: trunk/tools/build/v2/tools/types/obj.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/tools/types/obj.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,11 @@
+# Copyright David Abrahams 2004. Distributed under the Boost
+# Software License, Version 1.0. (See accompanying
+# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+from b2.build import type
+
+def register ():
+ type.register_type ('OBJ', ['obj'], None, ['NT', 'CYGWIN'])
+ type.register_type ('OBJ', ['o'])
+
+register ()

Added: trunk/tools/build/v2/tools/types/rsp.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/tools/types/rsp.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,10 @@
+# Copyright David Abrahams 2004. Distributed under the Boost
+# Software License, Version 1.0. (See accompanying
+# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+from b2.build import type
+
+def register ():
+ type.register_type ('RSP', ['rsp'])
+
+register ()

Added: trunk/tools/build/v2/tools/unix.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/tools/unix.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,150 @@
+# Copyright (c) 2004 Vladimir Prus.
+#
+# Use, modification and distribution is subject to the Boost Software
+# License Version 1.0. (See accompanying file LICENSE_1_0.txt or
+# http://www.boost.org/LICENSE_1_0.txt)
+
+""" This file implements linking semantics common to all unixes. On unix, static
+ libraries must be specified in a fixed order on the linker command line. Generators
+ declared there store information about the order and use it properly.
+"""
+
+import builtin
+from b2.build import generators, type
+from b2.util.utility import *
+from b2.util import set, sequence
+
+class UnixLinkingGenerator (builtin.LinkingGenerator):
+
+ def __init__ (self, id, composing, source_types, target_types, requirements):
+ builtin.LinkingGenerator.__init__ (self, id, composing, source_types, target_types, requirements)
+
+ def run (self, project, name, prop_set, sources):
+ result = builtin.LinkingGenerator.run (self, project, name, prop_set, sources)
+ if result:
+ set_library_order (project.manager (), sources, prop_set, result [1])
+
+ return result
+
+ def generated_targets (self, sources, prop_set, project, name):
+ sources2 = []
+ libraries = []
+ for l in sources:
+ if type.is_derived (l.type (), 'LIB'):
+ libraries.append (l)
+
+ else:
+ sources2.append (l)
+
+ sources = sources2 + order_libraries (libraries)
+
+ return builtin.LinkingGenerator.generated_targets (self, sources, prop_set, project, name)
+
+
+class UnixArchiveGenerator (builtin.ArchiveGenerator):
+ def __init__ (self, id, composing, source_types, target_types_and_names, requirements):
+ builtin.ArchiveGenerator.__init__ (self, id, composing, source_types, target_types_and_names, requirements)
+
+ def run (self, project, name, prop_set, sources):
+ result = builtin.ArchiveGenerator.run(self, project, name, prop_set, sources)
+ set_library_order(project.manager(), sources, prop_set, result)
+ return result
+
+class UnixSearchedLibGenerator (builtin.SearchedLibGenerator):
+
+ def __init__ (self):
+ builtin.SearchedLibGenerator.__init__ (self)
+
+ def optional_properties (self):
+ return self.requirements ()
+
+ def run (self, project, name, prop_set, sources, multiple):
+ result = SearchedLibGenerator.run (project, name, prop_set, sources, multiple)
+
+ set_library_order (sources, prop_set, result)
+
+ return result
+
+class UnixPrebuiltLibGenerator (generators.Generator):
+ def __init__ (self, id, composing, source_types, target_types_and_names, requirements):
+ generators.Generator.__init__ (self, id, composing, source_types, target_types_and_names, requirements)
+
+ def run (self, project, name, prop_set, sources, multiple):
+ f = prop_set.get ('<file>')
+ set_library_order_aux (f, sources)
+ return (f, sources)
+
+### # The derived toolset must specify their own rules and actions.
+# FIXME: restore?
+# action.register ('unix.prebuilt', None, None)
+
+
+generators.register (UnixPrebuiltLibGenerator ('unix.prebuilt', False, [], ['LIB'], ['<file>', '<toolset>unix']))
+
+
+
+
+
+### # Declare generators
+### generators.register [ new UnixLinkingGenerator unix.link : LIB OBJ : EXE
+### : <toolset>unix ] ;
+generators.register (UnixArchiveGenerator ('unix.archive', True, ['OBJ'], ['STATIC_LIB'], ['<toolset>unix']))
+
+### generators.register [ new UnixLinkingGenerator unix.link.dll : LIB OBJ : SHARED_LIB
+### : <toolset>unix ] ;
+###
+### generators.register [ new UnixSearchedLibGenerator
+### unix.SearchedLibGenerator : : SEARCHED_LIB : <toolset>unix ] ;
+###
+###
+### # The derived toolset must specify their own actions.
+### actions link {
+### }
+###
+### actions link.dll {
+### }
+
+def unix_archive (manager, targets, sources, properties):
+ pass
+
+# FIXME: restore?
+#action.register ('unix.archive', unix_archive, [''])
+
+### actions searched-lib-generator {
+### }
+###
+### actions prebuilt {
+### }
+
+
+from b2.util.order import Order
+__order = Order ()
+
+def set_library_order_aux (from_libs, to_libs):
+ for f in from_libs:
+ for t in to_libs:
+ if f != t:
+ __order.add_pair (f, t)
+
+def set_library_order (manager, sources, prop_set, result):
+ used_libraries = []
+ deps = prop_set.dependency ()
+
+ [ sources.append (manager.get_object (get_value (x))) for x in deps ]
+ sources = sequence.unique (sources)
+
+ for l in sources:
+ if l.type () and type.is_derived (l.type (), 'LIB'):
+ used_libraries.append (l)
+
+ created_libraries = []
+ for l in result:
+ if l.type () and type.is_derived (l.type (), 'LIB'):
+ created_libraries.append (l)
+
+ created_libraries = set.difference (created_libraries, used_libraries)
+ set_library_order_aux (created_libraries, used_libraries)
+
+def order_libraries (libraries):
+ return __order.order (libraries)
+

Added: trunk/tools/build/v2/util/__init__.py
==============================================================================

Added: trunk/tools/build/v2/util/logger.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/util/logger.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,46 @@
+# Copyright Pedro Ferreira 2005. Distributed under the Boost
+# Software License, Version 1.0. (See accompanying
+# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+import sys
+
+class NullLogger:
+ def __init__ (self):
+ self.indent_ = ''
+
+ def log (self, source_name, *args):
+ if self.on () and self.interesting (source_name):
+ self.do_log (self.indent_)
+ for i in args:
+ self.do_log (i)
+ self.do_log ('\n')
+
+ def increase_indent (self):
+ if self.on ():
+ self.indent_ += ' '
+
+ def decrease_indent (self):
+ if self.on () and len (self.indent_) > 4:
+ self.indent_ = self.indent_ [-4:]
+
+ def do_log (self, *args):
+ pass
+
+ def interesting (self, source_name):
+ return False
+
+ def on (self):
+ return False
+
+class TextLogger (NullLogger):
+ def __init__ (self):
+ NullLogger.__init__ (self)
+
+ def do_log (self, arg):
+ sys.stdout.write (str (arg))
+
+ def interesting (self, source_name):
+ return True
+
+ def on (self):
+ return True

Added: trunk/tools/build/v2/util/order.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/util/order.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,121 @@
+# Copyright (C) 2003 Vladimir Prus
+# Use, modification, and distribution is subject to the Boost Software
+# License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy
+# at http://www.boost.org/LICENSE_1_0.txt)
+
+class Order:
+ """Allows ordering arbitrary objects with regard to arbitrary binary relation.
+
+ The primary use case is the gcc toolset, which is sensitive to
+ library order: if library 'a' uses symbols from library 'b',
+ then 'a' must be present before 'b' on the linker's command line.
+
+ This requirement can be lifted for gcc with GNU ld, but for gcc with
+ Solaris LD (and for Solaris toolset as well), the order always matters.
+
+ So, we need to store order requirements and then order libraries
+ according to them. It it not possible to use dependency graph as
+ order requirements. What we need is "use symbols" relationship
+ while dependency graph provides "needs to be updated" relationship.
+
+ For example::
+ lib a : a.cpp b;
+ lib b ;
+
+ For static linking, the 'a' library need not depend on 'b'. However, it
+ still should come before 'b' on the command line.
+ """
+
+ def __init__ (self):
+ self.constraints_ = []
+
+ def add_pair (self, first, second):
+ """ Adds the constraint that 'first' should precede 'second'.
+ """
+ self.constraints_.append ((first, second))
+
+ def order (self, objects):
+ """ Given a list of objects, reorder them so that the constains specified
+ by 'add_pair' are satisfied.
+
+ The algorithm was adopted from an awk script by Nikita Youshchenko
+ (yoush at cs dot msu dot su)
+ """
+ # The algorithm used is the same is standard transitive closure,
+ # except that we're not keeping in-degree for all vertices, but
+ # rather removing edges.
+ result = []
+
+ if not objects:
+ return result
+
+ constraints = self.__eliminate_unused_constraits (objects)
+
+ # Find some library that nobody depends upon and add it to
+ # the 'result' array.
+ obj = None
+ while objects:
+ new_objects = []
+ while objects:
+ obj = objects [0]
+
+ if self.__has_no_dependents (obj, constraints):
+ # Emulate break ;
+ new_objects.extend (objects [1:])
+ objects = []
+
+ else:
+ new_objects.append (obj)
+ obj = None
+ objects = objects [1:]
+
+ if not obj:
+ raise BaseException ("Circular order dependencies")
+
+ # No problem with placing first.
+ result.append (obj)
+
+ # Remove all containts where 'obj' comes first,
+ # since they are already satisfied.
+ constraints = self.__remove_satisfied (constraints, obj)
+
+ # Add the remaining objects for further processing
+ # on the next iteration
+ objects = new_objects
+
+ return result
+
+ def __eliminate_unused_constraits (self, objects):
+ """ Eliminate constraints which mention objects not in 'objects'.
+ In graph-theory terms, this is finding subgraph induced by
+ ordered vertices.
+ """
+ result = []
+ for c in self.constraints_:
+ if c [0] in objects and c [1] in objects:
+ result.append (c)
+
+ return result
+
+ def __has_no_dependents (self, obj, constraints):
+ """ Returns true if there's no constraint in 'constraints' where
+ 'obj' comes second.
+ """
+ failed = False
+ while constraints and not failed:
+ c = constraints [0]
+
+ if c [1] == obj:
+ failed = True
+
+ constraints = constraints [1:]
+
+ return not failed
+
+ def __remove_satisfied (self, constraints, obj):
+ result = []
+ for c in constraints:
+ if c [0] != obj:
+ result.append (c)
+
+ return result

Added: trunk/tools/build/v2/util/path.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/util/path.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,922 @@
+# Status: this module is ported on demand by however needs something
+# from it. Functionality that is not needed by Python port will
+# be dropped.
+
+# Copyright (C) Vladimir Prus 2002. Permission to copy, use, modify, sell and
+# distribute this software is granted provided this copyright notice appears in
+# all copies. This software is provided "as is" without express or implied
+# warranty, and with no claim as to its suitability for any purpose.
+
+# Performs various path manipulations. Path are always in a 'normilized'
+# representation. In it, a path may be either:
+#
+# - '.', or
+#
+# - ['/'] [ ( '..' '/' )* (token '/')* token ]
+#
+# In plain english, path can be rooted, '..' elements are allowed only
+# at the beginning, and it never ends in slash, except for path consisting
+# of slash only.
+
+import os.path
+from utility import to_seq
+from glob import glob as builtin_glob
+
+def root (path, root):
+ """ If 'path' is relative, it is rooted at 'root'. Otherwise, it's unchanged.
+ """
+ if os.path.isabs (path):
+ return path
+ else:
+ return os.path.join (root, path)
+
+def make (native):
+ """ Converts the native path into normalized form.
+ """
+ # TODO: make os selection here.
+ return make_UNIX (native)
+
+def make_UNIX (native):
+
+ # VP: I have no idea now 'native' can be empty here! But it can!
+ assert (native)
+
+ return os.path.normpath (native)
+
+def native (path):
+ """ Builds a native representation of the path.
+ """
+ # TODO: make os selection here.
+ return native_UNIX (path)
+
+def native_UNIX (path):
+ return path
+
+
+def pwd ():
+ """ Returns the current working directory.
+ # TODO: is it a good idea to use the current dir? Some use-cases
+ may not allow us to depend on the current dir.
+ """
+ return make (os.getcwd ())
+
+def is_rooted (path):
+ """ Tests if a path is rooted.
+ """
+ return path and path [0] == '/'
+
+
+###################################################################
+# Still to port.
+# Original lines are prefixed with "# "
+#
+# # Copyright (C) Vladimir Prus 2002. Permission to copy, use, modify, sell and
+# # distribute this software is granted provided this copyright notice appears in
+# # all copies. This software is provided "as is" without express or implied
+# # warranty, and with no claim as to its suitability for any purpose.
+#
+# # Performs various path manipulations. Path are always in a 'normilized'
+# # representation. In it, a path may be either:
+# #
+# # - '.', or
+# #
+# # - ['/'] [ ( '..' '/' )* (token '/')* token ]
+# #
+# # In plain english, path can be rooted, '..' elements are allowed only
+# # at the beginning, and it never ends in slash, except for path consisting
+# # of slash only.
+#
+# import modules ;
+# import sequence ;
+# import regex ;
+# import errors : error ;
+#
+#
+# os = [ modules.peek : OS ] ;
+# if [ modules.peek : UNIX ]
+# {
+# local uname = [ modules.peek : JAMUNAME ] ;
+# switch $(uname)
+# {
+# case CYGWIN* :
+# os = CYGWIN ;
+#
+# case * :
+# os = UNIX ;
+# }
+# }
+#
+# #
+# # Tests if a path is rooted.
+# #
+# rule is-rooted ( path )
+# {
+# return [ MATCH "^(/)" : $(path) ] ;
+# }
+#
+# #
+# # Tests if a path has a parent.
+# #
+# rule has-parent ( path )
+# {
+# if $(path) != / {
+# return 1 ;
+# } else {
+# return ;
+# }
+# }
+#
+# #
+# # Returns the path without any directory components.
+# #
+# rule basename ( path )
+# {
+# return [ MATCH "([^/]+)$" : $(path) ] ;
+# }
+#
+# #
+# # Returns parent directory of the path. If no parent exists, error is issued.
+# #
+# rule parent ( path )
+# {
+# if [ has-parent $(path) ] {
+#
+# if $(path) = . {
+# return .. ;
+# } else {
+#
+# # Strip everything at the end of path up to and including
+# # the last slash
+# local result = [ regex.match "((.*)/)?([^/]+)" : $(path) : 2 3 ] ;
+#
+# # Did we strip what we shouldn't?
+# if $(result[2]) = ".." {
+# return $(path)/.. ;
+# } else {
+# if ! $(result[1]) {
+# if [ is-rooted $(path) ] {
+# result = / ;
+# } else {
+# result = . ;
+# }
+# }
+# return $(result[1]) ;
+# }
+# }
+# } else {
+# error "Path '$(path)' has no parent" ;
+# }
+# }
+#
+# #
+# # Returns path2 such that "[ join path path2 ] = .".
+# # The path may not contain ".." element or be rooted.
+# #
+# rule reverse ( path )
+# {
+# if $(path) = .
+# {
+# return $(path) ;
+# }
+# else
+# {
+# local tokens = [ regex.split $(path) "/" ] ;
+# local tokens2 ;
+# for local i in $(tokens) {
+# tokens2 += .. ;
+# }
+# return [ sequence.join $(tokens2) : "/" ] ;
+# }
+# }
+#
+# #
+# # Auxillary rule: does all the semantic of 'join', except for error cheching.
+# # The error checking is separated because this rule is recursive, and I don't
+# # like the idea of checking the same input over and over.
+# #
+# local rule join-imp ( elements + )
+# {
+# return [ NORMALIZE_PATH $(elements:J="/") ] ;
+# }
+#
+# #
+# # Contanenates the passed path elements. Generates an error if
+# # any element other than the first one is rooted.
+# #
+# rule join ( elements + )
+# {
+# if ! $(elements[2])
+# {
+# return $(elements[1]) ;
+# }
+# else
+# {
+# for local e in $(elements[2-])
+# {
+# if [ is-rooted $(e) ]
+# {
+# error only first element may be rooted ;
+# }
+# }
+# return [ join-imp $(elements) ] ;
+# }
+# }
+
+
+def glob (dirs, patterns):
+ """ Returns the list of files matching the given pattern in the
+ specified directory. Both directories and patterns are
+ supplied as portable paths. Each pattern should be non-absolute
+ path, and can't contain "." or ".." elements. Each slash separated
+ element of pattern can contain the following special characters:
+ - '?', which match any character
+ - '*', which matches arbitrary number of characters.
+ A file $(d)/e1/e2/e3 (where 'd' is in $(dirs)) matches pattern p1/p2/p3
+ if and only if e1 matches p1, e2 matches p2 and so on.
+
+ For example:
+ [ glob . : *.cpp ]
+ [ glob . : */build/Jamfile ]
+ """
+# {
+# local result ;
+# if $(patterns:D)
+# {
+# # When a pattern has a directory element, we first glob for
+# # directory, and then glob for file name is the found directories.
+# for local p in $(patterns)
+# {
+# # First glob for directory part.
+# local globbed-dirs = [ glob $(dirs) : $(p:D) ] ;
+# result += [ glob $(globbed-dirs) : $(p:D="") ] ;
+# }
+# }
+# else
+# {
+# # When a pattern has not directory, we glob directly.
+# # Take care of special ".." value. The "GLOB" rule simply ignores
+# # the ".." element (and ".") element in directory listings. This is
+# # needed so that
+# #
+# # [ glob libs/*/Jamfile ]
+# #
+# # don't return
+# #
+# # libs/../Jamfile (which is the same as ./Jamfile)
+# #
+# # On the other hand, when ".." is explicitly present in the pattern
+# # we need to return it.
+# #
+# for local dir in $(dirs)
+# {
+# for local p in $(patterns)
+# {
+# if $(p) != ".."
+# {
+# result += [ sequence.transform make
+# : [ GLOB [ native $(dir) ] : $(p) ] ] ;
+# }
+# else
+# {
+# result += [ path.join $(dir) .. ] ;
+# }
+# }
+# }
+# }
+# return $(result) ;
+# }
+#
+
+# TODO: (PF) I replaced the code above by this. I think it should work but needs to be tested.
+ result = []
+ dirs = to_seq (dirs)
+ patterns = to_seq (patterns)
+
+ splitdirs = []
+ for dir in dirs:
+ splitdirs += dir.split (os.pathsep)
+
+ for dir in splitdirs:
+ for pattern in patterns:
+ p = os.path.join (dir, pattern)
+ import glob
+ result.extend (glob.glob (p))
+ return result
+
+# #
+# # Returns true is the specified file exists.
+# #
+# rule exists ( file )
+# {
+# return [ path.glob $(file:D) : $(file:D=) ] ;
+# }
+# NATIVE_RULE path : exists ;
+#
+#
+#
+# #
+# # Find out the absolute name of path and returns the list of all the parents,
+# # starting with the immediate one. Parents are returned as relative names.
+# # If 'upper_limit' is specified, directories above it will be pruned.
+# #
+# rule all-parents ( path : upper_limit ? : cwd ? )
+# {
+# cwd ?= [ pwd ] ;
+# local path_ele = [ regex.split [ root $(path) $(cwd) ] "/" ] ;
+#
+# if ! $(upper_limit) {
+# upper_limit = / ;
+# }
+# local upper_ele = [ regex.split [ root $(upper_limit) $(cwd) ] "/" ] ;
+#
+# # Leave only elements in 'path_ele' below 'upper_ele'
+# while $(path_ele) && $(upper_ele[1]) = $(path_ele[1]) {
+# upper_ele = $(upper_ele[2-]) ;
+# path_ele = $(path_ele[2-]) ;
+# }
+#
+# # All upper elements removed ?
+# if ! $(upper_ele) {
+# # Create the relative paths to parents, number of elements in 'path_ele'
+# local result ;
+# for local i in $(path_ele) {
+# path = [ parent $(path) ] ;
+# result += $(path) ;
+# }
+# return $(result) ;
+# }
+# else {
+# error "$(upper_limit) is not prefix of $(path)" ;
+# }
+# }
+#
+#
+# #
+# # Search for 'pattern' in parent directories of 'dir', up till and including
+# # 'upper_limit', if it is specified, or till the filesystem root otherwise.
+# #
+# rule glob-in-parents ( dir : patterns + : upper-limit ? )
+# {
+# local result ;
+# local parent-dirs = [ all-parents $(dir) : $(upper-limit) ] ;
+#
+# while $(parent-dirs) && ! $(result)
+# {
+# result = [ glob $(parent-dirs[1]) : $(patterns) ] ;
+# parent-dirs = $(parent-dirs[2-]) ;
+# }
+# return $(result) ;
+# }
+#
+# #
+# # Assuming 'child' is a subdirectory of 'parent', return the relative
+# # path from 'parent' to 'child'
+# #
+# rule relative ( child parent )
+# {
+# if $(parent) = "."
+# {
+# return $(child) ;
+# }
+# else
+# {
+# local split1 = [ regex.split $(parent) / ] ;
+# local split2 = [ regex.split $(child) / ] ;
+#
+# while $(split1)
+# {
+# if $(split1[1]) = $(split2[1])
+# {
+# split1 = $(split1[2-]) ;
+# split2 = $(split2[2-]) ;
+# }
+# else
+# {
+# errors.error $(child) is not a subdir of $(parent) ;
+# }
+# }
+# return [ join $(split2) ] ;
+# }
+# }
+#
+# # Returns the minimal path to path2 that is relative path1.
+# #
+# rule relative-to ( path1 path2 )
+# {
+# local root_1 = [ regex.split [ reverse $(path1) ] / ] ;
+# local split1 = [ regex.split $(path1) / ] ;
+# local split2 = [ regex.split $(path2) / ] ;
+#
+# while $(split1) && $(root_1)
+# {
+# if $(split1[1]) = $(split2[1])
+# {
+# root_1 = $(root_1[2-]) ;
+# split1 = $(split1[2-]) ;
+# split2 = $(split2[2-]) ;
+# }
+# else
+# {
+# split1 = ;
+# }
+# }
+# return [ join . $(root_1) $(split2) ] ;
+# }
+
+# Returns the list of paths which are used by the operating system
+# for looking up programs
+def programs_path ():
+ raw = []
+ names = ['PATH', 'Path', 'path']
+
+ for name in names:
+ raw.append(os.environ.get (name, ''))
+
+ result = []
+ for elem in raw:
+ if elem:
+ for p in elem.split(os.path.pathsep):
+ result.append(make(p))
+
+ return result
+
+# rule make-NT ( native )
+# {
+# local tokens = [ regex.split $(native) "[/\\]" ] ;
+# local result ;
+#
+# # Handle paths ending with slashes
+# if $(tokens[-1]) = ""
+# {
+# tokens = $(tokens[1--2]) ; # discard the empty element
+# }
+#
+# result = [ path.join $(tokens) ] ;
+#
+# if [ regex.match "(^.:)" : $(native) ]
+# {
+# result = /$(result) ;
+# }
+#
+# if $(native) = ""
+# {
+# result = "." ;
+# }
+#
+# return $(result) ;
+# }
+#
+# rule native-NT ( path )
+# {
+# local result = [ MATCH "^/?(.*)" : $(path) ] ;
+# result = [ sequence.join [ regex.split $(result) "/" ] : "\\" ] ;
+# return $(result) ;
+# }
+#
+# rule make-CYGWIN ( path )
+# {
+# return [ make-NT $(path) ] ;
+# }
+#
+# rule native-CYGWIN ( path )
+# {
+# local result = $(path) ;
+# if [ regex.match "(^/.:)" : $(path) ] # win absolute
+# {
+# result = [ MATCH "^/?(.*)" : $(path) ] ; # remove leading '/'
+# }
+# return [ native-UNIX $(result) ] ;
+# }
+#
+# #
+# # split-VMS: splits input native path into
+# # device dir file (each part is optional),
+# # example:
+# #
+# # dev:[dir]file.c => dev: [dir] file.c
+# #
+# rule split-path-VMS ( native )
+# {
+# local matches = [ MATCH ([a-zA-Z0-9_-]+:)?(\\[[^\]]*\\])?(.*)?$ : $(native) ] ;
+# local device = $(matches[1]) ;
+# local dir = $(matches[2]) ;
+# local file = $(matches[3]) ;
+#
+# return $(device) $(dir) $(file) ;
+# }
+#
+# #
+# # Converts a native VMS path into a portable path spec.
+# #
+# # Does not handle current-device absolute paths such
+# # as "[dir]File.c" as it is not clear how to represent
+# # them in the portable path notation.
+# #
+# # Adds a trailing dot (".") to the file part if no extension
+# # is present (helps when converting it back into native path).
+# #
+# rule make-VMS ( native )
+# {
+# if [ MATCH ^(\\[[a-zA-Z0-9]) : $(native) ]
+# {
+# errors.error "Can't handle default-device absolute paths: " $(native) ;
+# }
+#
+# local parts = [ split-path-VMS $(native) ] ;
+# local device = $(parts[1]) ;
+# local dir = $(parts[2]) ;
+# local file = $(parts[3]) ;
+# local elems ;
+#
+# if $(device)
+# {
+# #
+# # rooted
+# #
+# elems = /$(device) ;
+# }
+#
+# if $(dir) = "[]"
+# {
+# #
+# # Special case: current directory
+# #
+# elems = $(elems) "." ;
+# }
+# else if $(dir)
+# {
+# dir = [ regex.replace $(dir) "\\[|\\]" "" ] ;
+# local dir_parts = [ regex.split $(dir) \\. ] ;
+#
+# if $(dir_parts[1]) = ""
+# {
+# #
+# # Relative path
+# #
+# dir_parts = $(dir_parts[2--1]) ;
+# }
+#
+# #
+# # replace "parent-directory" parts (- => ..)
+# #
+# dir_parts = [ regex.replace-list $(dir_parts) : - : .. ] ;
+#
+# elems = $(elems) $(dir_parts) ;
+# }
+#
+# if $(file)
+# {
+# if ! [ MATCH (\\.) : $(file) ]
+# {
+# #
+# # Always add "." to end of non-extension file
+# #
+# file = $(file). ;
+# }
+# elems = $(elems) $(file) ;
+# }
+#
+# local portable = [ path.join $(elems) ] ;
+#
+# return $(portable) ;
+# }
+#
+# #
+# # Converts a portable path spec into a native VMS path.
+# #
+# # Relies on having at least one dot (".") included in the file
+# # name to be able to differentiate it ftom the directory part.
+# #
+# rule native-VMS ( path )
+# {
+# local device = "" ;
+# local dir = $(path) ;
+# local file = "" ;
+# local native ;
+# local split ;
+#
+# #
+# # Has device ?
+# #
+# if [ is-rooted $(dir) ]
+# {
+# split = [ MATCH ^/([^:]+:)/?(.*) : $(dir) ] ;
+# device = $(split[1]) ;
+# dir = $(split[2]) ;
+# }
+#
+# #
+# # Has file ?
+# #
+# # This is no exact science, just guess work:
+# #
+# # If the last part of the current path spec
+# # includes some chars, followed by a dot,
+# # optionally followed by more chars -
+# # then it is a file (keep your fingers crossed).
+# #
+# split = [ regex.split $(dir) / ] ;
+# local maybe_file = $(split[-1]) ;
+#
+# if [ MATCH ^([^.]+\\..*) : $(maybe_file) ]
+# {
+# file = $(maybe_file) ;
+# dir = [ sequence.join $(split[1--2]) : / ] ;
+# }
+#
+# #
+# # Has dir spec ?
+# #
+# if $(dir) = "."
+# {
+# dir = "[]" ;
+# }
+# else if $(dir)
+# {
+# dir = [ regex.replace $(dir) \\.\\. - ] ;
+# dir = [ regex.replace $(dir) / . ] ;
+#
+# if $(device) = ""
+# {
+# #
+# # Relative directory
+# #
+# dir = "."$(dir) ;
+# }
+# dir = "["$(dir)"]" ;
+# }
+#
+# native = [ sequence.join $(device) $(dir) $(file) ] ;
+#
+# return $(native) ;
+# }
+#
+#
+# rule __test__ ( ) {
+#
+# import assert ;
+# import errors : try catch ;
+#
+# assert.true is-rooted "/" ;
+# assert.true is-rooted "/foo" ;
+# assert.true is-rooted "/foo/bar" ;
+# assert.result : is-rooted "." ;
+# assert.result : is-rooted "foo" ;
+# assert.result : is-rooted "foo/bar" ;
+#
+# assert.true has-parent "foo" ;
+# assert.true has-parent "foo/bar" ;
+# assert.true has-parent "." ;
+# assert.result : has-parent "/" ;
+#
+# assert.result "." : basename "." ;
+# assert.result ".." : basename ".." ;
+# assert.result "foo" : basename "foo" ;
+# assert.result "foo" : basename "bar/foo" ;
+# assert.result "foo" : basename "gaz/bar/foo" ;
+# assert.result "foo" : basename "/gaz/bar/foo" ;
+#
+# assert.result "." : parent "foo" ;
+# assert.result "/" : parent "/foo" ;
+# assert.result "foo/bar" : parent "foo/bar/giz" ;
+# assert.result ".." : parent "." ;
+# assert.result ".." : parent "../foo" ;
+# assert.result "../../foo" : parent "../../foo/bar" ;
+#
+#
+# assert.result "." : reverse "." ;
+# assert.result ".." : reverse "foo" ;
+# assert.result "../../.." : reverse "foo/bar/giz" ;
+#
+# assert.result "foo" : join "foo" ;
+# assert.result "/foo" : join "/" "foo" ;
+# assert.result "foo/bar" : join "foo" "bar" ;
+# assert.result "foo/bar" : join "foo/giz" "../bar" ;
+# assert.result "foo/giz" : join "foo/bar/baz" "../../giz" ;
+# assert.result ".." : join "." ".." ;
+# assert.result ".." : join "foo" "../.." ;
+# assert.result "../.." : join "../foo" "../.." ;
+# assert.result "/foo" : join "/bar" "../foo" ;
+# assert.result "foo/giz" : join "foo/giz" "." ;
+# assert.result "." : join lib2 ".." ;
+# assert.result "/" : join "/a" ".." ;
+#
+# assert.result /a/b : join /a/b/c .. ;
+#
+# assert.result "foo/bar/giz" : join "foo" "bar" "giz" ;
+# assert.result "giz" : join "foo" ".." "giz" ;
+# assert.result "foo/giz" : join "foo" "." "giz" ;
+#
+# try ;
+# {
+# join "a" "/b" ;
+# }
+# catch only first element may be rooted ;
+#
+# local CWD = "/home/ghost/build" ;
+# assert.result : all-parents . : . : $(CWD) ;
+# assert.result . .. ../.. ../../.. : all-parents "Jamfile" : "" : $(CWD) ;
+# assert.result foo . .. ../.. ../../.. : all-parents "foo/Jamfile" : "" : $(CWD) ;
+# assert.result ../Work .. ../.. ../../.. : all-parents "../Work/Jamfile" : "" : $(CWD) ;
+#
+# local CWD = "/home/ghost" ;
+# assert.result . .. : all-parents "Jamfile" : "/home" : $(CWD) ;
+# assert.result . : all-parents "Jamfile" : "/home/ghost" : $(CWD) ;
+#
+# assert.result "c/d" : relative "a/b/c/d" "a/b" ;
+# assert.result "foo" : relative "foo" "." ;
+#
+# local save-os = [ modules.peek path : os ] ;
+# modules.poke path : os : NT ;
+#
+# assert.result "foo/bar/giz" : make "foo/bar/giz" ;
+# assert.result "foo/bar/giz" : make "foo\\bar\\giz" ;
+# assert.result "foo" : make "foo/." ;
+# assert.result "foo" : make "foo/bar/.." ;
+# assert.result "/D:/My Documents" : make "D:\\My Documents" ;
+# assert.result "/c:/boost/tools/build/new/project.jam" : make "c:\\boost\\tools\\build\\test\\..\\new\\project.jam" ;
+#
+# assert.result "foo\\bar\\giz" : native "foo/bar/giz" ;
+# assert.result "foo" : native "foo" ;
+# assert.result "D:\\My Documents\\Work" : native "/D:/My Documents/Work" ;
+#
+# modules.poke path : os : UNIX ;
+#
+# assert.result "foo/bar/giz" : make "foo/bar/giz" ;
+# assert.result "/sub1" : make "/sub1/." ;
+# assert.result "/sub1" : make "/sub1/sub2/.." ;
+# assert.result "sub1" : make "sub1/." ;
+# assert.result "sub1" : make "sub1/sub2/.." ;
+# assert.result "/foo/bar" : native "/foo/bar" ;
+#
+# modules.poke path : os : VMS ;
+#
+# #
+# # Don't really need to poke os before these
+# #
+# assert.result "disk:" "[dir]" "file" : split-path-VMS "disk:[dir]file" ;
+# assert.result "disk:" "[dir]" "" : split-path-VMS "disk:[dir]" ;
+# assert.result "disk:" "" "" : split-path-VMS "disk:" ;
+# assert.result "disk:" "" "file" : split-path-VMS "disk:file" ;
+# assert.result "" "[dir]" "file" : split-path-VMS "[dir]file" ;
+# assert.result "" "[dir]" "" : split-path-VMS "[dir]" ;
+# assert.result "" "" "file" : split-path-VMS "file" ;
+# assert.result "" "" "" : split-path-VMS "" ;
+#
+# #
+# # Special case: current directory
+# #
+# assert.result "" "[]" "" : split-path-VMS "[]" ;
+# assert.result "disk:" "[]" "" : split-path-VMS "disk:[]" ;
+# assert.result "" "[]" "file" : split-path-VMS "[]file" ;
+# assert.result "disk:" "[]" "file" : split-path-VMS "disk:[]file" ;
+#
+# #
+# # Make portable paths
+# #
+# assert.result "/disk:" : make "disk:" ;
+# assert.result "foo/bar/giz" : make "[.foo.bar.giz]" ;
+# assert.result "foo" : make "[.foo]" ;
+# assert.result "foo" : make "[.foo.bar.-]" ;
+# assert.result ".." : make "[.-]" ;
+# assert.result ".." : make "[-]" ;
+# assert.result "." : make "[]" ;
+# assert.result "giz.h" : make "giz.h" ;
+# assert.result "foo/bar/giz.h" : make "[.foo.bar]giz.h" ;
+# assert.result "/disk:/my_docs" : make "disk:[my_docs]" ;
+# assert.result "/disk:/boost/tools/build/new/project.jam" : make "disk:[boost.tools.build.test.-.new]project.jam" ;
+#
+# #
+# # Special case (adds '.' to end of file w/o extension to
+# # disambiguate from directory in portable path spec).
+# #
+# assert.result "Jamfile." : make "Jamfile" ;
+# assert.result "dir/Jamfile." : make "[.dir]Jamfile" ;
+# assert.result "/disk:/dir/Jamfile." : make "disk:[dir]Jamfile" ;
+#
+# #
+# # Make native paths
+# #
+# assert.result "disk:" : native "/disk:" ;
+# assert.result "[.foo.bar.giz]" : native "foo/bar/giz" ;
+# assert.result "[.foo]" : native "foo" ;
+# assert.result "[.-]" : native ".." ;
+# assert.result "[.foo.-]" : native "foo/.." ;
+# assert.result "[]" : native "." ;
+# assert.result "disk:[my_docs.work]" : native "/disk:/my_docs/work" ;
+# assert.result "giz.h" : native "giz.h" ;
+# assert.result "disk:Jamfile." : native "/disk:Jamfile." ;
+# assert.result "disk:[my_docs.work]Jamfile." : native "/disk:/my_docs/work/Jamfile." ;
+#
+# modules.poke path : os : $(save-os) ;
+#
+# }
+
+#
+
+
+#def glob(dir, patterns):
+# result = []
+# for pattern in patterns:
+# result.extend(builtin_glob(os.path.join(dir, pattern)))
+# return result
+
+def glob(dirs, patterns, exclude_patterns=None):
+ """Returns the list of files matching the given pattern in the
+ specified directory. Both directories and patterns are
+ supplied as portable paths. Each pattern should be non-absolute
+ path, and can't contain '.' or '..' elements. Each slash separated
+ element of pattern can contain the following special characters:
+ - '?', which match any character
+ - '*', which matches arbitrary number of characters.
+ A file $(d)/e1/e2/e3 (where 'd' is in $(dirs)) matches pattern p1/p2/p3
+ if and only if e1 matches p1, e2 matches p2 and so on.
+ For example:
+ [ glob . : *.cpp ]
+ [ glob . : */build/Jamfile ]
+ """
+
+ assert(isinstance(patterns, list))
+ assert(isinstance(dirs, list))
+
+ if not exclude_patterns:
+ exclude_patterns = []
+ else:
+ assert(isinstance(exclude_patterns, list))
+
+ real_patterns = [os.path.join(d, p) for p in patterns for d in dirs]
+ real_exclude_patterns = [os.path.join(d, p) for p in exclude_patterns
+ for d in dirs]
+
+ inc = [os.path.normpath(name) for p in real_patterns
+ for name in builtin_glob(p)]
+ exc = [os.path.normpath(name) for p in real_exclude_patterns
+ for name in builtin_glob(p)]
+ return [x for x in inc if x not in exc]
+
+def glob_tree(roots, patterns, exclude_patterns=None):
+ """Recursive version of GLOB. Builds the glob of files while
+ also searching in the subdirectories of the given roots. An
+ optional set of exclusion patterns will filter out the
+ matching entries from the result. The exclusions also apply
+ to the subdirectory scanning, such that directories that
+ match the exclusion patterns will not be searched."""
+
+ if not exclude_patterns:
+ exclude_patterns = []
+
+ result = glob(roots, patterns, exclude_patterns)
+ subdirs = [s for s in result if s != "." and s != ".." and os.path.isdir(s)]
+ if subdirs:
+ result.extend(glob_tree(subdirs, patterns, exclude_patterns))
+
+ return result
+
+def glob_in_parents(dir, patterns, upper_limit=None):
+ """Recursive version of GLOB which glob sall parent directories
+ of dir until the first match is found. Returns an empty result if no match
+ is found"""
+
+ assert(isinstance(dir, str))
+ assert(isinstance(patterns, list))
+
+ result = []
+
+ absolute_dir = os.path.join(os.getcwd(), dir)
+ absolute_dir = os.path.normpath(absolute_dir)
+ while absolute_dir:
+ new_dir = os.path.split(absolute_dir)[0]
+ if new_dir == absolute_dir:
+ break
+ result = glob([new_dir], patterns)
+ if result:
+ break
+ absolute_dir = new_dir
+
+ return result
+
+
+# The relpath functionality is written by
+# Cimarron Taylor
+def split(p, rest=[]):
+ (h,t) = os.path.split(p)
+ if len(h) < 1: return [t]+rest
+ if len(t) < 1: return [h]+rest
+ return split(h,[t]+rest)
+
+def commonpath(l1, l2, common=[]):
+ if len(l1) < 1: return (common, l1, l2)
+ if len(l2) < 1: return (common, l1, l2)
+ if l1[0] != l2[0]: return (common, l1, l2)
+ return commonpath(l1[1:], l2[1:], common+[l1[0]])
+
+def relpath(p1, p2):
+ (common,l1,l2) = commonpath(split(p1), split(p2))
+ p = []
+ if len(l1) > 0:
+ p = [ '../' * len(l1) ]
+ p = p + l2
+ if p:
+ return os.path.join( *p )
+ else:
+ return "."

Added: trunk/tools/build/v2/util/regex.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/util/regex.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,25 @@
+# (C) Copyright David Abrahams 2001. Permission to copy, use, modify, sell and
+# distribute this software is granted provided this copyright notice appears in
+# all copies. This software is provided "as is" without express or implied
+# warranty, and with no claim as to its suitability for any purpose.
+
+import re
+
+def transform (list, pattern, indices = [1]):
+ """ Matches all elements of 'list' agains the 'pattern'
+ and returns a list of the elements indicated by indices of
+ all successfull matches. If 'indices' is omitted returns
+ a list of first paranthethised groups of all successfull
+ matches.
+ """
+ result = []
+
+ for e in list:
+ m = re.match (pattern, e)
+
+ if m:
+ for i in indices:
+ result.append (m.group (i))
+
+ return result
+

Added: trunk/tools/build/v2/util/sequence.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/util/sequence.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,52 @@
+# (C) Copyright David Abrahams 2002. Permission to copy, use, modify, sell and
+# distribute this software is granted provided this copyright notice appears in
+# all copies. This software is provided "as is" without express or implied
+# warranty, and with no claim as to its suitability for any purpose.
+
+import operator
+
+def unique (values):
+ # TODO: is this the most efficient way?
+ # consider using a set from Python 2.4.
+ return list(set(values))
+# cache = {}
+# result = []
+# for v in values:
+# if not cache.has_key(v):
+# cache[v] = None
+# result.append(v)
+# return result
+
+
+
+def max_element (elements, ordered = None):
+ """ Returns the maximum number in 'elements'. Uses 'ordered' for comparisons,
+ or '<' is none is provided.
+ """
+ if not ordered: ordered = operator.lt
+
+ max = elements [0]
+ for e in elements [1:]:
+ if ordered (max, e):
+ max = e
+
+ return max
+
+def select_highest_ranked (elements, ranks):
+ """ Returns all of 'elements' for which corresponding element in parallel
+ list 'rank' is equal to the maximum value in 'rank'.
+ """
+ if not elements:
+ return []
+
+ max_rank = max_element (ranks)
+
+ result = []
+ while elements:
+ if ranks [0] == max_rank:
+ result.append (elements [0])
+
+ elements = elements [1:]
+ ranks = ranks [1:]
+
+ return result

Added: trunk/tools/build/v2/util/set.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/util/set.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,42 @@
+# (C) Copyright David Abrahams 2001. Permission to copy, use, modify, sell and
+# distribute this software is granted provided this copyright notice appears in
+# all copies. This software is provided "as is" without express or implied
+# warranty, and with no claim as to its suitability for any purpose.
+
+from utility import to_seq
+
+def difference (b, a):
+ """ Returns the elements of B that are not in A.
+ """
+ result = []
+ for element in b:
+ if not element in a:
+ result.append (element)
+
+ return result
+
+def intersection (set1, set2):
+ """ Removes from set1 any items which don't appear in set2 and returns the result.
+ """
+ result = []
+ for v in set1:
+ if v in set2:
+ result.append (v)
+ return result
+
+def contains (small, large):
+ """ Returns true iff all elements of 'small' exist in 'large'.
+ """
+ small = to_seq (small)
+ large = to_seq (large)
+
+ for s in small:
+ if not s in large:
+ return False
+ return True
+
+def equal (a, b):
+ """ Returns True iff 'a' contains the same elements as 'b', irrespective of their order.
+ # TODO: Python 2.4 has a proper set class.
+ """
+ return contains (a, b) and contains (b, a)

Added: trunk/tools/build/v2/util/utility.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/util/utility.py 2009-07-27 04:52:41 EDT (Mon, 27 Jul 2009)
@@ -0,0 +1,155 @@
+# (C) Copyright David Abrahams 2001. Permission to copy, use, modify, sell and
+# distribute this software is granted provided this copyright notice appears in
+# all copies. This software is provided "as is" without express or implied
+# warranty, and with no claim as to its suitability for any purpose.
+
+""" Utility functions to add/remove/get grists.
+ Grists are string enclosed in angle brackets (<>) that are used as prefixes. See Jam for more information.
+"""
+
+import re
+import os
+import bjam
+from b2.exceptions import *
+
+__re_grist_and_value = re.compile (r'(<[^>]*>)(.*)')
+__re_grist_content = re.compile ('^<(.*)>$')
+__re_backslash = re.compile (r'\\')
+
+def to_seq (value):
+ """ If value is a sequence, returns it.
+ If it is a string, returns a sequence with value as its sole element.
+ """
+ if not value:
+ return []
+
+ if isinstance (value, str):
+ return [value]
+
+ else:
+ return value
+
+def replace_references_by_objects (manager, refs):
+ objs = []
+ for r in refs:
+ objs.append (manager.get_object (r))
+ return objs
+
+def add_grist (features):
+ """ Transform a string by bracketing it with "<>". If already bracketed, does nothing.
+ features: one string or a sequence of strings
+ return: the gristed string, if features is a string, or a sequence of gristed strings, if features is a sequence
+ """
+
+ def grist_one (feature):
+ if feature [0] != '<' and feature [len (feature) - 1] != '>':
+ return '<' + feature + '>'
+ else:
+ return feature
+
+ if isinstance (features, str):
+ return grist_one (features)
+ else:
+ return [ grist_one (feature) for feature in features ]
+
+def replace_grist (features, new_grist):
+ """ Replaces the grist of a string by a new one.
+ Returns the string with the new grist.
+ """
+ def replace_grist_one (name, new_grist):
+ split = __re_grist_and_value.match (name)
+ if not split:
+ return new_grist + name
+ else:
+ return new_grist + split.group (2)
+
+ if isinstance (features, str):
+ return replace_grist_one (features, new_grist)
+ else:
+ return [ replace_grist_one (feature, new_grist) for feature in features ]
+
+def get_value (property):
+ """ Gets the value of a property, that is, the part following the grist, if any.
+ """
+ return replace_grist (property, '')
+
+def get_grist (value):
+ """ Returns the grist of a string.
+ If value is a sequence, does it for every value and returns the result as a sequence.
+ """
+ def get_grist_one (name):
+ split = __re_grist_and_value.match (name)
+ if not split:
+ return ''
+ else:
+ return split.group (1)
+
+ if isinstance (value, str):
+ return get_grist_one (value)
+ else:
+ return [ get_grist_one (v) for v in value ]
+
+def ungrist (value):
+ """ Returns the value without grist.
+ If value is a sequence, does it for every value and returns the result as a sequence.
+ """
+ def ungrist_one (value):
+ stripped = __re_grist_content.match (value)
+ if not stripped:
+ raise BaseException ("in ungrist: '%s' is not of the form <.*>" % value)
+
+ return stripped.group (1)
+
+ if isinstance (value, str):
+ return ungrist_one (value)
+ else:
+ return [ ungrist_one (v) for v in value ]
+
+def replace_suffix (name, new_suffix):
+ """ Replaces the suffix of name by new_suffix.
+ If no suffix exists, the new one is added.
+ """
+ split = os.path.splitext (name)
+ return split [0] + new_suffix
+
+def forward_slashes (s):
+ """ Converts all backslashes to forward slashes.
+ """
+ return __re_backslash.sub ('/', s)
+
+
+def split_action_id (id):
+ """ Splits an id in the toolset and specific rule parts. E.g.
+ 'gcc.compile.c++' returns ('gcc', 'compile.c++')
+ """
+ split = id.split ('.', 1)
+ toolset = split [0]
+ name = ''
+ if len (split) > 1:
+ name = split [1]
+ return (toolset, name)
+
+def os_name ():
+ result = bjam.variable("OS")
+ assert(len(result) == 1)
+ return result[0]
+
+def platform ():
+ return bjam.variable("OSPLAT")
+
+def os_version ():
+ return bjam.variable("OSVER")
+
+def on_windows ():
+ """ Returns true if running on windows, whether in cygwin or not.
+ """
+ if bjam.variable("NT"):
+ return True
+
+ elif bjam.variable("UNIX"):
+
+ uname = bjam.variable("JAMUNAME")
+ if uname and uname[0].startswith("CYGWIN"):
+ return True
+
+ return False


Boost-Commit list run by bdawes at acm.org, david.abrahams at rcn.com, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk