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