$include_dir="/home/hyper-archives/boost-commit/include"; include("$include_dir/msg-header.inc") ?>
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