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