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