Boost logo

Boost-Commit :

Subject: [Boost-commit] svn:boost r64351 - in trunk/tools/build/v2: . build test util
From: ghost_at_[hidden]
Date: 2010-07-26 04:28:13


Author: vladimir_prus
Date: 2010-07-26 04:28:12 EDT (Mon, 26 Jul 2010)
New Revision: 64351
URL: http://svn.boost.org/trac/boost/changeset/64351

Log:
Major update of top level 'build_system.py' module.
Added:
   trunk/tools/build/v2/util/option.py (contents, props changed)
Text files modified:
   trunk/tools/build/v2/build/build_request.py | 6
   trunk/tools/build/v2/build/targets.py | 20
   trunk/tools/build/v2/build_system.py | 1021 ++++++++++++++++++++++++++++-----------
   trunk/tools/build/v2/test/BoostBuild.py | 2
   trunk/tools/build/v2/test/dll_path.py | 4
   trunk/tools/build/v2/test/explicit.py | 4
   trunk/tools/build/v2/util/path.py | 105 +--
   7 files changed, 785 insertions(+), 377 deletions(-)

Modified: trunk/tools/build/v2/build/build_request.py
==============================================================================
--- trunk/tools/build/v2/build/build_request.py (original)
+++ trunk/tools/build/v2/build/build_request.py 2010-07-26 04:28:12 EDT (Mon, 26 Jul 2010)
@@ -7,7 +7,9 @@
 # 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
+import b2.build.feature
+feature = b2.build.feature
+
 from b2.util.utility import *
 import b2.build.property_set as property_set
 
@@ -145,7 +147,7 @@
         else:
             result = [e1 + "/" + e2 for e1 in result for e2 in lresult]
 
- return result
+ return [property_set.create(b2.build.feature.split(r)) for r in result]
 
 ###
 ### rule __test__ ( )

Modified: trunk/tools/build/v2/build/targets.py
==============================================================================
--- trunk/tools/build/v2/build/targets.py (original)
+++ trunk/tools/build/v2/build/targets.py 2010-07-26 04:28:12 EDT (Mon, 26 Jul 2010)
@@ -81,9 +81,11 @@
 from virtual_target import Subvariant
 from b2.exceptions import *
 from b2.util.sequence import unique
-from b2.util import set, path, bjam_signature
+from b2.util import path, bjam_signature
 from b2.build.errors import user_error_checkpoint
 
+import b2.util.set
+
 _re_separate_target_from_properties = re.compile (r'^([^<]*)(/(<.*))?$')
 
 class TargetRegistry:
@@ -357,7 +359,7 @@
         self.main_target_ = {}
         
         # Targets marked as explicit.
- self.explicit_targets_ = []
+ self.explicit_targets_ = set()
 
         # The constants defined for this project.
         self.constants_ = {}
@@ -426,7 +428,7 @@
         
         # Record the name of the target, not instance, since this
         # rule is called before main target instaces are created.
- self.explicit_.append(target_name)
+ self.explicit_targets_.add(target_name)
     
     def add_alternative (self, target_instance):
         """ Add new target alternative.
@@ -575,7 +577,7 @@
         if not rules:
             rules = []
         user_rules = [x for x in rules
- if x not in self.manager().projects().project_rules()]
+ if x not in self.manager().projects().project_rules().all_names()]
         if user_rules:
             bjam.call("import-rules-from-parent", parent_module, this_module, user_rules)
         
@@ -636,14 +638,14 @@
                     best_properties = properties
 
                 else:
- if set.equal (properties, best_properties):
+ if b2.util.set.equal (properties, best_properties):
                         return None
 
- elif set.contains (properties, best_properties):
+ elif b2.util.set.contains (properties, best_properties):
                         # Do nothing, this alternative is worse
                         pass
 
- elif set.contains (best_properties, properties):
+ elif b2.util.set.contains (best_properties, properties):
                         best = v
                         best_properties = properties
 
@@ -1006,12 +1008,12 @@
         # build request just to select this variant.
         bcondition = self.requirements_.base ()
         ccondition = self.requirements_.conditional ()
- condition = set.difference (bcondition, ccondition)
+ condition = b2.util.set.difference (bcondition, ccondition)
 
         if debug:
             print " next alternative: required properties:", str(condition)
         
- if set.contains (condition, property_set.raw ()):
+ if b2.util.set.contains (condition, property_set.raw ()):
 
             if debug:
                 print " matched"

Modified: trunk/tools/build/v2/build_system.py
==============================================================================
--- trunk/tools/build/v2/build_system.py (original)
+++ trunk/tools/build/v2/build_system.py 2010-07-26 04:28:12 EDT (Mon, 26 Jul 2010)
@@ -17,302 +17,542 @@
 from b2.build.errors import ExceptionWithUserContext
 import b2.tools.common
 
+import b2.build.project as project
+import b2.build.virtual_target as virtual_target
+import b2.build.build_request as build_request
+
+import b2.util.regex
+
+from b2.manager import get_manager
+from b2.util import cached
+from b2.util import option
+
+
 import bjam
 
 import os
 import sys
+import re
 
-# 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
+################################################################################
+#
+# Module global data.
+#
+################################################################################
+
+# Flag indicating we should display additional debugging information related to
+# locating and loading Boost Build configuration files.
+debug_config = False
+
+# Legacy option doing too many things, some of which are not even documented.
+# Should be phased out.
+# * Disables loading site and user configuration files.
+# * Disables auto-configuration for toolsets specified explicitly on the
+# command-line.
+# * Causes --toolset command-line options to be ignored.
+# * Prevents the default toolset from being used even if no toolset has been
+# configured at all.
+legacy_ignore_config = False
+
+# 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' as well as those
+# in any children Jamfiles under foo but not in any unrelated Jamfiles. To
+# achieve this we collect a list of projects under which cleaning is allowed.
+project_targets = []
+
+# Virtual targets obtained when building main targets references on the command
+# line. When running 'bjam --clean main_target' we want to clean only files
+# belonging to that main target so we need to record which targets are produced
+# for it.
+results_of_main_targets = []
+
+# Was an XML dump requested?
+out_xml = False
+
+# Default toolset & version to be used in case no other toolset has been used
+# explicitly by either the loaded configuration files, the loaded project build
+# scripts or an explicit toolset request on the command line. If not specified,
+# an arbitrary default will be used based on the current host OS. This value,
+# while not strictly necessary, has been added to allow testing Boost-Build's
+# default toolset usage functionality.
+default_toolset = None
+default_toolset_version = None
+
+################################################################################
+#
+# Public rules.
+#
+################################################################################
+
+# Returns the property set with the free features from the currently processed
+# build request.
+#
+def command_line_free_features():
+ return command_line_free_features
+
+# Sets the default toolset & version to be used in case no other toolset has
+# been used explicitly by either the loaded configuration files, the loaded
+# project build scripts or an explicit toolset request on the command line. For
+# more detailed information see the comment related to used global variables.
+#
+def set_default_toolset(toolset, version=None):
+ default_toolset = toolset
+ default_toolset_version = version
 
-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']]
+pre_build_hook = None
 
-ignore_config = 0
-debug_config = 0
+def set_pre_build_hook(callable):
+ pre_build_hook = callable
 
-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)
+post_build_hook = None
 
- manager.projects().load_standalone(basename, found)
+def set_post_build_hook(callable):
+ post_build_hook = callable
 
-def main():
+################################################################################
+#
+# Local rules.
+#
+################################################################################
 
- global argv
- argv = bjam.variable("ARGV")
+# Returns actual Jam targets to be used for executing a clean request.
+#
+def actual_clean_targets(targets):
 
- # 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)
+ # Construct a list of projects explicitly detected as targets on this build
+ # system run. These are the projects under which cleaning is allowed.
+ for t in targets:
+ if isinstance(t, b2.build.targets.ProjectTarget):
+ project_targets.append(t.project_module())
+
+
+ # Construct a list of targets explicitly detected on this build system run
+ # as a result of building main targets.
+ targets_to_clean = set()
+ for t in results_of_main_targets:
+ # Do not include roots or sources.
+ targets_to_clean.update(virtual_target.traverse(t))
+
+ to_clean = []
+ for t in get_manager().virtual_targets().all_targets():
+
+ # Remove only derived targets.
+ if t.action():
+ p = t.project()
+ if t in targets_to_clean or should_clean_project(p.project_module()):
+ to_clean.append(t)
+
+ return [t.actualize() for t in to_clean]
+
+_target_id_split = re.compile("(.*)//(.*)")
+
+# Given a target id, try to find and return the corresponding target. This is
+# only invoked when there is no Jamfile in ".". This code somewhat duplicates
+# code in project-target.find but we can not reuse that code without a
+# project-targets instance.
+#
+def _find_target(target_id):
+
+ m = _target_id_split(target_id)
+ if m:
+ pm = project.find(m.group(1), ".")
     else:
- main_real()
+ pm = project.find(target_id, ".")
 
-def main_real():
+ if pm:
+ result = project.target(pm)
 
- global ignore_config
- global debug_config
-
- boost_build_path = bjam.variable("BOOST_BUILD_PATH")
+ if m:
+ result = result.find(m.group(2))
 
- engine = Engine()
+ return result
 
- global_build_dir = get_string_option("build-dir")
- debug_config = get_boolean_option("debug-configuration")
-
- manager = Manager(engine, global_build_dir)
+def initialize_config_module(module_name):
 
- # This module defines types and generator and what not,
- # and depends on manager's existence
- import b2.tools.builtin
+ get_manager().projects().initialize(module_name)
 
+# Helper rule used to load configuration files. Loads the first configuration
+# file with the given 'filename' at 'path' into module with name 'module-name'.
+# Not finding the requested file may or may not be treated as an error depending
+# on the must-find parameter. Returns a normalized path to the loaded
+# configuration file or nothing if no file was loaded.
+#
+def load_config(module_name, filename, paths, must_find=False):
 
- # 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 debug_config:
+ print "notice: Searching '%s' for '%s' configuration file '%s." \
+ % (paths, module_name, filename)
 
- if test_config:
+ where = None
+ for path in paths:
+ t = os.path.join(path, filename)
+ if os.path.exists(t):
+ where = t
+ break
+
+ if where:
+ where = os.path.realpath(where)
+
         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"
+ print "notice: Loading '%s' configuration file '%s' from '%s'." \
+ % (module_name, filename, where)
 
- manager.projects().load_standalone("test-config", test_config)
+ # Set source location so that path-constant in config files
+ # with relative paths work. This is of most importance
+ # for project-config.jam, but may be used in other
+ # config files as well.
+ attributes = get_manager().projects().attributes(module_name) ;
+ attributes.set('source-location', os.path.dirname(where), True)
+ get_manager().projects().load_standalone(module_name, where)
+
+ else:
+ msg = "Configuration file '%s' not found in '%s'." % (filename, path)
+ if must_find:
+ get_manager().errors()(msg)
+
+ elif debug_config:
+ print msg
+
+ return where
+
+# Loads all the configuration files used by Boost Build in the following order:
+#
+# -- test-config --
+# Loaded only if specified on the command-line using the --test-config
+# command-line parameter. It is ok for this file not to exist even if specified.
+# If this configuration file is loaded, regular site and user configuration
+# files will not be. If a relative path is specified, file is searched for in
+# the current folder.
+#
+# -- site-config --
+# Always named site-config.jam. Will only be found if located on the system
+# root path (Windows), /etc (non-Windows), user's home folder or the Boost Build
+# path, in that order. Not loaded in case the test-config configuration file is
+# loaded or either the --ignore-site-config or the --ignore-config command-line
+# option is specified.
+#
+# -- user-config --
+# Named user-config.jam by default or may be named explicitly using the
+# --user-config command-line option or the BOOST_BUILD_USER_CONFIG environment
+# variable. If named explicitly the file is looked for from the current working
+# directory and if the default one is used then it is searched for in the
+# user's home directory and the Boost Build path, in that order. Not loaded in
+# case either the test-config configuration file is loaded, --ignore-config
+# command-line option is specified or an empty file name is explicitly
+# specified. If the file name has been given explicitly then the file must
+# exist.
+#
+# Test configurations have been added primarily for use by Boost Build's
+# internal unit testing system but may be used freely in other places as well.
+#
+def load_configuration_files():
+
+ # Flag indicating that site configuration should not be loaded.
+ ignore_site_config = "--ignore-site-config" in sys.argv
 
+ if legacy_ignore_config and debug_config:
+ print "notice: Regular site and user configuration files will be ignored"
+ print "notice: due to the --ignore-config command-line option."
+
+ initialize_config_module("test-config")
+ test_config = None
+ for a in sys.argv:
+ m = re.match("--test-config=(.*)$", a)
+ if m:
+ test_config = b2.util.unquote(m.group(1))
+ break
 
- ignore_config = test_config or get_boolean_option("ignore-config")
- user_path = home_directories() + boost_build_path
+ if test_config:
+ where = load_config("test-config", os.path.basename(test_config), [os.path.dirname(test_config)])
+ if where:
+ if debug_config and not legacy_ignore_config:
+ print "notice: Regular site and user configuration files will"
+ print "notice: be ignored due to the test configuration being loaded."
 
+ user_path = [os.path.expanduser("~")] + os.getenv("BOOST_BUILD_PATH").split(os.pathsep)
     site_path = ["/etc"] + user_path
- if bjam.variable("OS") in ["NT", "CYGWIN"]:
- site_path = [os.environ("SystemRoot")] + user_path
+ if os.name in ["nt"]:
+ site_path = [os.getenv("SystemRoot")] + user_path
 
- load_config(manager, "site-config", site_path)
+ if ignore_site_config and not legacy_ignore_config:
+ print "notice: Site configuration files will be ignored due to the"
+ print "notice: --ignore-site-config command-line option."
+
+ initialize_config_module("site-config")
+ if not test_config and not ignore_site_config and not legacy_ignore_config:
+ load_config('site-config', 'site-config.jam', site_path)
+
+ initialize_config_module('user-config')
+ if not test_config and not legacy_ignore_config:
+
+ user_config = None
+ for a in sys.argv:
+ m = re.match("--user-config=(.*)$", a)
+ if m:
+ user_config = m.group(1)
+ break
 
- user_config_path = get_string_option("user-config")
- if not user_config_path:
- user_config_path = os.environ.get("BOOST_BUILD_USER_CONFIG")
+ if not user_config:
+ user_config = os.getenv("BOOST_BUILD_USER_CONFIG")
+
+ # Special handling for the case when the OS does not strip the quotes
+ # around the file name, as is the case when using Cygwin bash.
+ user_config = b2.util.unquote(user_config)
+ explicitly_requested = user_config
+ if not user_config:
+ user_config = "user-config.jam"
 
- if user_config_path:
- if debug_config:
- print "Loading explicitly specifier user configuration file:"
- print " %s" % user_config_path
+ if explicitly_requested:
+
+ user_config = os.path.abspath(user_config)
             
- manager.projects().load_standalone("user-config", user_config_path)
+ if debug_config:
+ print "notice: Loading explicitly specified user configuration file:"
+ print " " + user_config
+
+ load_config('user-config', os.path.basename(user_config), [os.path.dirname(user_config)], True)
+ else:
+ load_config('user-config', os.path.basename(user_config), user_path)
 
- else:
- load_config(manager, "user-config", user_path)
+ elif debug_config:
+ print "notice: User configuration file loading explicitly disabled." ;
         
+ # We look for project-config.jam from "." upward.
+ # I am not sure this is 100% right decision, we might as well check for
+ # it only alonside the Jamroot file. However:
+ #
+ # - We need to load project-root.jam before Jamroot
+ # - We probably would need to load project-root.jam even if there's no
+ # Jamroot - e.g. to implement automake-style out-of-tree builds.
+ if os.path.exists("project-config.jam"):
+ file = ["project-config.jam"]
+ else:
+ file = b2.util.path.glob_in_parents(".", ["project-config.jam"])
 
-# 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 file:
+ initialize_config_module('project-config')
+ load_config('project-config', "project-config.jam", [os.path.dirname(file[0])], True)
 
-## if $(debug-config)
-## {
-## ECHO notice: [cmdline-cfg] Detected command-line request for
-## $(toolset-version): toolset= \"$(toolset)\" "version= \""$(version)\" ;
-## }
 
-## local known ;
+# Autoconfigure toolsets based on any instances of --toolset=xx,yy,...zz or
+# toolset=xx,yy,...zz in the command line. May return additional properties to
+# be processed as if they had been specified by the user.
+#
+def process_explicit_toolset_requests():
 
-## # if the toolset isn't known, configure it now.
-## if $(toolset) in [ feature.values <toolset> ]
-## {
-## known = true ;
-## }
+ extra_properties = []
 
-## if $(known) && $(version)
-## && ! [ feature.is-subvalue toolset : $(toolset) : version : $(version) ]
-## {
-## known = ;
-## }
+ option_toolsets = [e for option in b2.util.regex.transform(sys.argv, "^--toolset=(.*)$")
+ for e in option.split(',')]
+ feature_toolsets = [e for option in b2.util.regex.transform(sys.argv, "^toolset=(.*)$")
+ for e in option.split(',')]
 
-## 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 ;
-## }
-## }
+ for t in option_toolsets + feature_toolsets:
+
+ # Parse toolset-version/properties.
+ (toolset_version, toolset, version) = re.match("(([^-/]+)-?([^/]+)?)/?.*", t).groups()
 
-## # 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) ;
-## }
-## }
-## }
+ if debug_config:
+ print "notice: [cmdline-cfg] Detected command-line request for '%s': toolset= %s version=%s" \
+ % (toolset_version, toolset, version)
 
+ # If the toolset is not known, configure it now.
+ known = False
+ if toolset in feature.values("toolset"):
+ known = True
+
+ if known and version and not feature.is_subvalue("toolset", toolset, "version", version):
+ known = False
+ # TODO: we should do 'using $(toolset)' in case no version has been
+ # specified and there are no versions defined for the given toolset to
+ # allow the toolset to configure its default version. For this we need
+ # to know how to detect whether a given toolset has any versions
+ # defined. An alternative would be to do this whenever version is not
+ # specified but that would require that toolsets correctly handle the
+ # case when their default version is configured multiple times which
+ # should be checked for all existing toolsets first.
+
+ if not known:
+
+ if debug_config:
+ print "notice: [cmdline-cfg] toolset '%s' not previously configured; attempting to auto-configure now" % toolset_version
+ toolset.using(toolset, version)
 
-# FIXME:
-## if USER_MODULE in [ RULENAMES ]
-## {
-## USER_MODULE site-config user-config ;
-## }
+ else:
 
- 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)
+ if debug_config:
 
- b2.tools.common.init(manager)
+ print "notice: [cmdline-cfg] toolset '%s' already configured" % toolset_version
 
- # 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.
+ # Make sure we get an appropriate property into the build request in
+ # case toolset has been specified using the "--toolset=..." command-line
+ # option form.
+ if not t in sys.argv and not t in feature_toolsets:
 
- current_project = None
- projects = manager.projects()
- if projects.find(".", "."):
- current_project = projects.target(projects.load("."))
+ if debug_config:
+ print "notice: [cmdline-cfg] adding toolset=%s) to the build request." % t ;
+ extra_properties += "toolset=%s" % t
 
- # 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"
+ return extra_properties
 
- projects.project_rules().using([default_toolset])
 
- (target_ids, properties) = b2.build.build_request.from_command_line(
- argv[1:] + extra_build_request)
 
- properties = [property_set.create(feature.split(ps)) for ps in properties]
+# Returns 'true' if the given 'project' is equal to or is a (possibly indirect)
+# child to any of the projects requested to be cleaned in this build system run.
+# Returns 'false' otherwise. Expects the .project-targets list to have already
+# been constructed.
+#
+@cached
+def should_clean_project(project):
 
- if properties:
- expanded = b2.build.build_request.expand_no_defaults(properties)
+ if project in project_targets:
+ return True
     else:
- expanded = [property_set.empty()]
 
- targets = []
-
- clean = get_boolean_option("clean")
- clean_all = get_boolean_option("clean-all")
+ parent = get_manager().projects().attribute(project, "parent-module")
+ if parent and parent != "user-config":
+ return should_clean_project(parent)
+ else:
+ return False
+
+################################################################################
+#
+# main()
+# ------
+#
+################################################################################
+
+def main():
+
+ sys.argv = bjam.variable("ARGV")
+
+ # FIXME: document this option.
+ if "--profiling" in sys.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 debug_config, legacy_ignore_config, out_xml
+
+ debug_config = "--debug-configuration" in sys.argv
+ legacy_ignore_config = "--ignore_config" in sys.argv
+ out_xml = any(re.match("^--out-xml=(.*)$", a) for a in sys.argv)
+
+ engine = Engine()
+
+ global_build_dir = option.get("build-dir")
     
+ manager = Manager(engine, global_build_dir)
+
+ if "--version" in sys.argv:
+
+ version.report()
+ return
+
+ # This module defines types and generator and what not,
+ # and depends on manager's existence
+ import b2.tools.builtin
 
- bjam_targets = []
+ b2.tools.common.init(manager)
 
- # 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], ".")
+ load_configuration_files()
+
+ extra_properties = []
+ # Note that this causes --toolset options to be ignored if --ignore-config
+ # is specified.
+ if not legacy_ignore_config:
+ extra_properties = process_explicit_toolset_requests()
+
+ # We always load project in "." so that 'use-project' directives have any
+ # chance of being seen. Otherwise, we would not be able to refer to
+ # subprojects using target ids.
+ current_project = None
+ projects = get_manager().projects()
+ if projects.find(".", "."):
+ current_project = projects.target(projects.load("."))
+
+ # In case there are no toolsets currently defined makes the build run using
+ # the default toolset.
+ if not legacy_ignore_config and not feature.values("toolset"):
+
+ dt = default_toolset
+ dtv = None
+ if default_toolset:
+ dtv = default_toolset_version
         else:
- pm = projects.find(target_id, ".")
+ dt = "gcc"
+ if os.name == 'nt':
+ dt = "msvc"
+ # FIXME:
+ #else if [ os.name ] = MACOSX
+ #{
+ # default-toolset = darwin ;
+ #}
+
+ print "warning: No toolsets are configured."
+ print "warning: Configuring default toolset '%s'." % dt
+ print "warning: If the default is wrong, your build may not work correctly."
+ 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"
 
- result = None
- if pm:
- result = projects.target(pm)
+ toolset.using(dt, dtv)
 
- if len(split) > 1:
- result = result.find(split[1])
+ # Parse command line for targets and properties. Note that this requires
+ # that all project files already be loaded.
+ (target_ids, properties) = build_request.from_command_line(sys.argv[1:] + extra_properties)
+
+ # Expand properties specified on the command line into multiple property
+ # sets consisting of all legal property combinations. Each expanded property
+ # set will be used for a single build run. E.g. if multiple toolsets are
+ # specified then requested targets will be built with each of them.
+ if properties:
+ expanded = build_request.expand_no_defaults(properties)
+ else:
+ expanded = [property_set.empty()]
 
+ # Check that we actually found something to build.
     if not current_project and not target_ids:
- print "error: no Jamfile in current directory found, and no target references specified."
- sys.exit(1)
+ get_manager().errors()("no Jamfile in current directory found, and no target references specified.")
+ # FIXME:
+ # EXIT
+
+ # Flags indicating that this build system run has been started in order to
+ # clean existing instead of create new targets. Note that these are not the
+ # final flag values as they may get changed later on due to some special
+ # targets being specified on the command line.
+ clean = "--clean" in sys.argv
+ cleanall = "--clean-all" in sys.argv
+
+ # List of explicitly requested files to build. Any target references read
+ # from the command line parameter not recognized as one of the targets
+ # defined in the loaded Jamfiles will be interpreted as an explicitly
+ # requested file to build. If any such files are explicitly requested then
+ # only those files and the targets they depend on will be built and they
+ # will be searched for among targets that would have been built had there
+ # been no explicitly requested files.
+ explicitly_requested_files = []
 
+ # List of Boost Build meta-targets, virtual-targets and actual Jam targets
+ # constructed in this build system run.
+ targets = []
+ virtual_targets = []
+ actual_targets = []
+
+ # Process each target specified on the command-line and convert it into
+ # internal Boost Build target objects. Detect special clean target. If no
+ # main Boost Build targets were explictly requested use the current project
+ # as the target.
     for id in target_ids:
         if id == "clean":
             clean = 1
@@ -332,7 +572,25 @@
 
     if not targets:
         targets = [projects.target(projects.module_name("."))]
+
+ # FIXME: put this BACK.
     
+ ## if [ option.get dump-generators : : true ]
+ ## {
+ ## generators.dump ;
+ ## }
+
+ ## # We wish to put config.log in the build directory corresponding
+ ## # to Jamroot, so that the location does not differ depending on
+ ## # directory where we do build. The amount of indirection necessary
+ ## # here is scary.
+ ## local first-project = [ $(targets[0]).project ] ;
+ ## local first-project-root-location = [ $(first-project).get project-root ] ;
+ ## local first-project-root-module = [ project.load $(first-project-root-location) ] ;
+ ## local first-project-root = [ project.target $(first-project-root-module) ] ;
+ ## local first-build-build-dir = [ $(first-project-root).build-dir ] ;
+ ## configure.set-log-file $(first-build-build-dir)/config.log ;
+
     virtual_targets = []
 
     # Virtual targets obtained when building main targets references on
@@ -344,6 +602,11 @@
     # so we need to record which targets are produced.
     results_of_main_targets = []
 
+
+ # Now that we have a set of targets to build and a set of property sets to
+ # build the targets with, we can start the main build process by using each
+ # property set to generate virtual targets from all of our listed targets
+ # and any of their dependants.
     for p in expanded:
         manager.set_command_line_free_features(property_set.create(p.free()))
         
@@ -358,78 +621,242 @@
             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
+ # Convert collected virtual targets into actual raw Jam targets.
+ for t in virtual_targets:
+ actual_targets.append(t.actualize())
 
- is_child_cache_[project] = r
 
- return r
+ # FIXME: restore
+## # If XML data output has been requested prepare additional rules and targets
+## # so we can hook into Jam to collect build data while its building and have
+## # it trigger the final XML report generation after all the planned targets
+## # have been built.
+## if $(.out-xml)
+## {
+## # Get a qualified virtual target name.
+## rule full-target-name ( target )
+## {
+## local name = [ $(target).name ] ;
+## local project = [ $(target).project ] ;
+## local project-path = [ $(project).get location ] ;
+## return $(project-path)//$(name) ;
+## }
 
- actual_targets = []
- for t in virtual_targets:
- actual_targets.append(t.actualize())
+## # Generate an XML file containing build statistics for each constituent.
+## #
+## rule out-xml ( xml-file : constituents * )
+## {
+## # Prepare valid XML header and footer with some basic info.
+## local nl = "
+## " ;
+## local jam = [ version.jam ] ;
+## local os = [ modules.peek : OS OSPLAT JAMUNAME ] "" ;
+## local timestamp = [ modules.peek : JAMDATE ] ;
+## local cwd = [ PWD ] ;
+## local command = $(.sys.argv) ;
+## local bb-version = [ version.boost-build ] ;
+## .header on $(xml-file) =
+## "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+## "$(nl)<build format=\"1.0\" version=\"$(bb-version)\">"
+## "$(nl) <jam version=\"$(jam:J=.)\" />"
+## "$(nl) <os name=\"$(os[1])\" platform=\"$(os[2])\"><![CDATA[$(os[3-]:J= )]]></os>"
+## "$(nl) <timestamp><![CDATA[$(timestamp)]]></timestamp>"
+## "$(nl) <directory><![CDATA[$(cwd)]]></directory>"
+## "$(nl) <command><![CDATA[\"$(command:J=\" \")\"]]></command>"
+## ;
+## .footer on $(xml-file) =
+## "$(nl)</build>" ;
+
+## # Generate the target dependency graph.
+## .contents on $(xml-file) +=
+## "$(nl) <targets>" ;
+## for local t in [ virtual-target.all-targets ]
+## {
+## local action = [ $(t).action ] ;
+## if $(action)
+## # If a target has no action, it has no dependencies.
+## {
+## local name = [ full-target-name $(t) ] ;
+## local sources = [ $(action).sources ] ;
+## local dependencies ;
+## for local s in $(sources)
+## {
+## dependencies += [ full-target-name $(s) ] ;
+## }
+
+## local path = [ $(t).path ] ;
+## local jam-target = [ $(t).actual-name ] ;
+
+## .contents on $(xml-file) +=
+## "$(nl) <target>"
+## "$(nl) <name><![CDATA[$(name)]]></name>"
+## "$(nl) <dependencies>"
+## "$(nl) <dependency><![CDATA[$(dependencies)]]></dependency>"
+## "$(nl) </dependencies>"
+## "$(nl) <path><![CDATA[$(path)]]></path>"
+## "$(nl) <jam-target><![CDATA[$(jam-target)]]></jam-target>"
+## "$(nl) </target>"
+## ;
+## }
+## }
+## .contents on $(xml-file) +=
+## "$(nl) </targets>" ;
 
+## # Build $(xml-file) after $(constituents). Do so even if a
+## # constituent action fails and regenerate the xml on every bjam run.
+## INCLUDES $(xml-file) : $(constituents) ;
+## ALWAYS $(xml-file) ;
+## __ACTION_RULE__ on $(xml-file) = build-system.out-xml.generate-action ;
+## out-xml.generate $(xml-file) ;
+## }
 
- bjam.call("NOTFILE", "all")
- bjam.call("DEPENDS", "all", actual_targets)
+## # The actual build actions are here; if we did this work in the actions
+## # clause we would have to form a valid command line containing the
+## # result of @(...) below (the name of the XML file).
+## #
+## rule out-xml.generate-action ( args * : xml-file
+## : command status start end user system : output ? )
+## {
+## local contents =
+## [ on $(xml-file) return $(.header) $(.contents) $(.footer) ] ;
+## local f = @($(xml-file):E=$(contents)) ;
+## }
 
- 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()
+## # Nothing to do here; the *real* actions happen in
+## # out-xml.generate-action.
+## actions quietly out-xml.generate { }
+
+## # Define the out-xml file target, which depends on all the targets so
+## # that it runs the collection after the targets have run.
+## out-xml $(.out-xml) : $(actual-targets) ;
+
+## # Set up a global __ACTION_RULE__ that records all the available
+## # statistics about each actual target in a variable "on" the --out-xml
+## # target.
+## #
+## rule out-xml.collect ( xml-file : target : command status start end user
+## system : output ? )
+## {
+## local nl = "
+## " ;
+## # Open the action with some basic info.
+## .contents on $(xml-file) +=
+## "$(nl) <action status=\"$(status)\" start=\"$(start)\" end=\"$(end)\" user=\"$(user)\" system=\"$(system)\">" ;
+
+## # If we have an action object we can print out more detailed info.
+## local action = [ on $(target) return $(.action) ] ;
+## if $(action)
+## {
+## local action-name = [ $(action).action-name ] ;
+## local action-sources = [ $(action).sources ] ;
+## local action-props = [ $(action).properties ] ;
+
+## # The qualified name of the action which we created the target.
+## .contents on $(xml-file) +=
+## "$(nl) <name><![CDATA[$(action-name)]]></name>" ;
+
+## # The sources that made up the target.
+## .contents on $(xml-file) +=
+## "$(nl) <sources>" ;
+## for local source in $(action-sources)
+## {
+## local source-actual = [ $(source).actual-name ] ;
+## .contents on $(xml-file) +=
+## "$(nl) <source><![CDATA[$(source-actual)]]></source>" ;
+## }
+## .contents on $(xml-file) +=
+## "$(nl) </sources>" ;
+
+## # The properties that define the conditions under which the
+## # target was built.
+## .contents on $(xml-file) +=
+## "$(nl) <properties>" ;
+## for local prop in [ $(action-props).raw ]
+## {
+## local prop-name = [ MATCH ^<(.*)>$ : $(prop:G) ] ;
+## .contents on $(xml-file) +=
+## "$(nl) <property name=\"$(prop-name)\"><![CDATA[$(prop:G=)]]></property>" ;
+## }
+## .contents on $(xml-file) +=
+## "$(nl) </properties>" ;
+## }
 
- # Remove only derived targets.
- if t.action() and \
- (t in targets_to_clean or is_child(p.project_module())):
- to_clean.append(t)
+## local locate = [ on $(target) return $(LOCATE) ] ;
+## locate ?= "" ;
+## .contents on $(xml-file) +=
+## "$(nl) <jam-target><![CDATA[$(target)]]></jam-target>"
+## "$(nl) <path><![CDATA[$(target:G=:R=$(locate))]]></path>"
+## "$(nl) <command><![CDATA[$(command)]]></command>"
+## "$(nl) <output><![CDATA[$(output)]]></output>" ;
+## .contents on $(xml-file) +=
+## "$(nl) </action>" ;
+## }
+
+## # When no __ACTION_RULE__ is set "on" a target, the search falls back to
+## # the global module.
+## module
+## {
+## __ACTION_RULE__ = build-system.out-xml.collect
+## [ modules.peek build-system : .out-xml ] ;
+## }
+
+## IMPORT
+## build-system :
+## out-xml.collect
+## out-xml.generate-action
+## : :
+## build-system.out-xml.collect
+## build-system.out-xml.generate-action
+## ;
+## }
 
- to_clean_actual = [t.actualize() for t in to_clean]
- manager.engine().set_update_action('common.Clean', 'clean',
- to_clean_actual, None)
+ j = option.get("jobs")
+ if j:
+ bjam.call("set-variable", PARALLELISM, j)
+
+ k = option.get("keep-going", "true", "true")
+ if k in ["on", "yes", "true"]:
+ bjam.call("set-variable", "KEEP_GOING", "1")
+ elif k in ["off", "no", "false"]:
+ bjam.call("set-variable", "KEEP_GOING", "0")
+ else:
+ print "error: Invalid value for the --keep-going option"
+ sys.exit()
+
+ # The 'all' pseudo target is not strictly needed expect in the case when we
+ # use it below but people often assume they always have this target
+ # available and do not declare it themselves before use which may cause
+ # build failures with an error message about not being able to build the
+ # 'all' target.
+ bjam.call("NOTFILE", "all")
 
+ # And now that all the actual raw Jam targets and all the dependencies
+ # between them have been prepared all that is left is to tell Jam to update
+ # those targets.
+ if explicitly_requested_files:
+ # Note that this case can not be joined with the regular one when only
+ # exact Boost Build targets are requested as here we do not build those
+ # requested targets but only use them to construct the dependency tree
+ # needed to build the explicitly requested files.
+ # FIXME: add $(.out-xml)
+ bjam.call("UPDATE", ["<e>%s" % x for x in explicitly_requested_files])
+ elif cleanall:
+ bjam.call("UPDATE", "clean-all")
+ elif clean:
+ manager.engine().set_update_action("common.Clean", "clean",
+ actual_clean_targets(targets), None)
         bjam.call("UPDATE", "clean")
-
     else:
- bjam.call("UPDATE", "all")
+ # FIXME:
+ #configure.print-configure-checks-summary ;
+
+ if pre_build_hook:
+ pre_build_hook()
+
+ bjam.call("DEPENDS", "all", actual_targets)
+ ok = bjam.call("UPDATE_NOW", "all") # FIXME: add out-xml
+ if post_build_hook:
+ post_build_hook(ok)
+ # Prevent automatic update of the 'all' target, now that
+ # we have explicitly updated what we wanted.
+ bjam.call("UPDATE")

Modified: trunk/tools/build/v2/test/BoostBuild.py
==============================================================================
--- trunk/tools/build/v2/test/BoostBuild.py (original)
+++ trunk/tools/build/v2/test/BoostBuild.py 2010-07-26 04:28:12 EDT (Mon, 26 Jul 2010)
@@ -668,6 +668,8 @@
 
         self.ignore("bin/config.log")
 
+ self.ignore("*.pyc")
+
         if not self.unexpected_difference.empty():
             annotation('failure', 'Unexpected changes found')
             output = StringIO.StringIO()

Modified: trunk/tools/build/v2/test/dll_path.py
==============================================================================
--- trunk/tools/build/v2/test/dll_path.py (original)
+++ trunk/tools/build/v2/test/dll_path.py 2010-07-26 04:28:12 EDT (Mon, 26 Jul 2010)
@@ -29,10 +29,10 @@
 """)
 
 t.write("jamroot.jam", """
-using dll-paths ;
+using dll_paths ;
 """)
 
-t.write("dll-paths.jam", """
+t.write("dll_paths.jam", """
 import type ;
 import generators ;
 import feature ;

Modified: trunk/tools/build/v2/test/explicit.py
==============================================================================
--- trunk/tools/build/v2/test/explicit.py (original)
+++ trunk/tools/build/v2/test/explicit.py 2010-07-26 04:28:12 EDT (Mon, 26 Jul 2010)
@@ -8,9 +8,7 @@
 
 t = BoostBuild.Tester()
 
-t.write("jamroot.jam", "")
-
-t.write("jamfile.jam", """
+t.write("jamroot.jam", """
 exe hello : hello.cpp ;
 exe hello2 : hello.cpp ;
 explicit hello2 ;

Added: trunk/tools/build/v2/util/option.py
==============================================================================
--- (empty file)
+++ trunk/tools/build/v2/util/option.py 2010-07-26 04:28:12 EDT (Mon, 26 Jul 2010)
@@ -0,0 +1,34 @@
+# Copyright (c) 2005-2010 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)
+
+import sys
+import b2.util.regex
+
+options = {}
+
+# Set a value for a named option, to be used when not overridden on the command
+# line.
+def set(name, value=None):
+
+ global options
+
+ options[name] = value
+
+def get(name, default_value=None, implied_value=None):
+
+ global options
+
+ matches = b2.util.regex.transform(sys.argv, "--$(name)=(.*)")
+ if matches:
+ return matches[-1]
+ else:
+ m = b2.util.regex.transform(sys.argv, "--$(name)")
+ if m and implied_value:
+ return implied_value
+ elif options.has_key(name):
+ return options[name]
+ else:
+ return default_value

Modified: trunk/tools/build/v2/util/path.py
==============================================================================
--- trunk/tools/build/v2/util/path.py (original)
+++ trunk/tools/build/v2/util/path.py 2010-07-26 04:28:12 EDT (Mon, 26 Jul 2010)
@@ -303,70 +303,47 @@
             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) ;
-# }
+#
+# 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.
+#
+def all_parents(path, upper_limit=None, cwd=None):
+
+ if not cwd:
+ cwd = os.getcwd()
+
+ path_abs = os.path.join(cwd, path)
+
+ if upper_limit:
+ upper_limit = os.path.join(cwd, upper_limit)
+
+ result = []
+ while path_abs and path_abs != upper_limit:
+ (head, tail) = os.path.split(path)
+ path = os.path.join(path, "..")
+ result.append(path)
+ path_abs = head
+
+ if upper_limit and path_abs != upper_limit:
+ raise BaseException("'%s' is not a prefix of '%s'" % (upper_limit, path))
+
+ return result
+
+# Search for 'pattern' in parent directories of 'dir', up till and including
+# 'upper_limit', if it is specified, or till the filesystem root otherwise.
+#
+def glob_in_parents(dir, patterns, upper_limit=None):
+
+ result = []
+ parent_dirs = all_parents(dir, upper_limit)
+
+ for p in parent_dirs:
+ result = glob(p, patterns)
+ if result: break
+
+ return result
+
 #
 # #
 # # Assuming 'child' is a subdirectory of 'parent', return the relative


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