def make_test(target_type, sources, requirements, target_name=None): assert isinstance(target_type, basestring) assert is_iterable_typed(sources, basestring) assert is_iterable_typed(requirements, basestring) assert isinstance(target_type, basestring) or target_type is None if not target_name: target_name = stem(os.path.basename(sources[0])) # Having periods (".") in the target name is problematic because the typed # generator will strip the suffix and use the bare name for the file # targets. Even though the location-prefix averts problems most times it # does not prevent ambiguity issues when referring to the test targets. For # example when using the XML log output. So we rename the target to remove # the periods, and provide an alias for users. real_name = target_name.replace(".", "~") project = get_manager().projects().current() # The <location-prefix> forces the build system for generate paths in the # form '$build_dir/array1.test/gcc/debug'. This is necessary to allow # post-processing tools to work. t = get_manager().targets().create_typed_target( type.type_from_rule_name(target_type), project, real_name, sources, requirements + ["<location-prefix>" + real_name + ".test"], [], []) # The alias to the real target, per period replacement above. if real_name != target_name: get_manager().projects().project_rules().all_names_["alias"]( target_name, [t]) # Remember the test (for --dump-tests). A good way would be to collect all # given a project. This has some technical problems: e.g. we can not call # this dump from a Jamfile since projects referred by 'build-project' are # not available until the whole Jamfile has been loaded. __all_tests.append(t) return t
def use_project(self, id, where): # See comment in 'load' for explanation why we record the # parameters as opposed to loading the project now. assert is_iterable_typed(id, basestring) assert is_iterable_typed(where, basestring) m = self.registry.current().project_module() self.registry.used_projects[m].append((id[0], where[0]))
def refine (properties, requirements): """ Refines 'properties' by overriding any non-free properties for which a different value is specified in 'requirements'. Conditional requirements are just added without modification. Returns the resulting list of properties. """ assert is_iterable_typed(properties, Property) assert is_iterable_typed(requirements, Property) # The result has no duplicates, so we store it in a set result = set() # Records all requirements. required = {} # All the elements of requirements should be present in the result # Record them so that we can handle 'properties'. for r in requirements: # Don't consider conditional requirements. if not r.condition: required[r.feature] = r for p in properties: # Skip conditional properties if p.condition: result.add(p) # No processing for free properties elif p.feature.free: result.add(p) else: if p.feature in required: result.add(required[p.feature]) else: result.add(p) return sequence.unique(list(result) + requirements)
def inherit_generators (toolset, properties, base, generators_to_ignore = []): assert isinstance(toolset, basestring) assert is_iterable_typed(properties, basestring) assert isinstance(base, basestring) assert is_iterable_typed(generators_to_ignore, basestring) if not properties: properties = [replace_grist (toolset, '<toolset>')] base_generators = generators.generators_for_toolset(base) for g in base_generators: id = g.id() if not id in generators_to_ignore: # Some generator names have multiple periods in their name, so # $(id:B=$(toolset)) doesn't generate the right new_id name. # e.g. if id = gcc.compile.c++, $(id:B=darwin) = darwin.c++, # which is not what we want. Manually parse the base and suffix # (if there's a better way to do this, I'd love to see it.) # See also register in module generators. (base, suffix) = split_action_id(id) new_id = toolset + '.' + suffix generators.register(g.clone(new_id, properties))
def construct_types (project, name, target_types, prop_set, sources): if __debug__: from .targets import ProjectTarget assert isinstance(project, ProjectTarget) assert isinstance(name, basestring) or name is None assert is_iterable_typed(target_types, basestring) assert isinstance(prop_set, property_set.PropertySet) assert is_iterable_typed(sources, virtual_target.VirtualTarget) result = [] usage_requirements = property_set.empty() for t in target_types: r = construct (project, name, t, prop_set, sources) if r: (ur, targets) = r usage_requirements = usage_requirements.add(ur) result.extend(targets) # TODO: have to introduce parameter controlling if # several types can be matched and add appropriate # checks # TODO: need to review the documentation for # 'construct' to see if it should return $(source) even # if nothing can be done with it. Currents docs seem to # imply that, contrary to the behaviour. if result: return (usage_requirements, result) else: return (usage_requirements, sources)
def convert_multiple_sources_to_consumable_types (self, project, prop_set, sources): """ Converts several files to consumable types. """ consumed = [] bypassed = [] if __debug__: from .targets import ProjectTarget assert isinstance(project, ProjectTarget) assert isinstance(prop_set, property_set.PropertySet) assert is_iterable_typed(sources, virtual_target.VirtualTarget) assert isinstance(project, ProjectTarget) assert isinstance(prop_set, property_set.PropertySet) assert is_iterable_typed(sources, virtual_target.VirtualTarget) # We process each source one-by-one, trying to convert it to # a usable type. for s in sources: # TODO: need to check for failure on each source. (c, b) = self.convert_to_consumable_types (project, None, prop_set, [s], True) if not c: project.manager ().logger ().log (__name__, " failed to convert ", s) consumed.extend (c) bypassed.extend (b) return (consumed, bypassed)
def create (raw_properties = []): """ Creates a new 'PropertySet' instance for the given raw properties, or returns an already existing one. """ assert (is_iterable_typed(raw_properties, property.Property) or is_iterable_typed(raw_properties, basestring)) # FIXME: propagate to callers. if len(raw_properties) > 0 and isinstance(raw_properties[0], property.Property): x = raw_properties else: x = [property.create_from_string(ps) for ps in raw_properties] # These two lines of code are optimized to the current state # of the Property class. Since this function acts as the caching # frontend to the PropertySet class modifying these two lines # could have a severe performance penalty. Be careful. # It would be faster to sort by p.id, but some projects may rely # on the fact that the properties are ordered alphabetically. So, # we maintain alphabetical sorting so as to maintain backward compatibility. x = sorted(set(x), key=lambda p: (p.feature.name, p.value, p.condition)) key = tuple(p.id for p in x) if key not in __cache: __cache [key] = PropertySet(x) return __cache [key]
def lib(names, sources=[], requirements=[], default_build=[], usage_requirements=[]): """The implementation of the 'lib' rule. Beyond standard syntax that rule allows simplified: 'lib a b c ;'.""" assert is_iterable_typed(names, basestring) assert is_iterable_typed(sources, basestring) assert is_iterable_typed(requirements, basestring) assert is_iterable_typed(default_build, basestring) assert is_iterable_typed(usage_requirements, basestring) if len(names) > 1: if any(r.startswith("<name>") for r in requirements): get_manager().errors()( "When several names are given to the 'lib' rule\n" + "it is not allowed to specify the <name> feature." ) if sources: get_manager().errors()( "When several names are given to the 'lib' rule\n" + "it is not allowed to specify sources." ) project = get_manager().projects().current() result = [] for name in names: r = requirements[:] # Support " lib a ; " and " lib a b c ; " syntax. if ( not sources and not any(r.startswith("<name>") for r in requirements) and not any(r.startswith("<file") for r in requirements) ): r.append("<name>" + name) result.append(targets.create_typed_metatarget(name, "LIB", sources, r, default_build, usage_requirements)) return result
def get_invocation_command(toolset, tool, user_provided_command = [], additional_paths = [], path_last = False): """ Same as get_invocation_command_nodefault, except that if no tool is found, returns either the user-provided-command, if present, or the 'tool' parameter. """ assert isinstance(toolset, basestring) assert isinstance(tool, basestring) assert is_iterable_typed(user_provided_command, basestring) assert is_iterable_typed(additional_paths, basestring) or additional_paths is None assert isinstance(path_last, (int, bool)) result = get_invocation_command_nodefault(toolset, tool, user_provided_command, additional_paths, path_last) if not result: if user_provided_command: result = user_provided_command[0] else: result = tool assert(isinstance(result, str)) return result
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.""" assert is_iterable_typed(name, basestring) assert is_iterable_typed(value, basestring) self.registry.current().add_constant(name[0], value)
def take(attributes, properties): """Returns a property set which include all properties in 'properties' that have any of 'attributes'.""" assert is_iterable_typed(attributes, basestring) assert is_iterable_typed(properties, basestring) result = [] for e in properties: if b2.util.set.intersection(attributes, feature.attributes(get_grist(e))): result.append(e) return result
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.""" assert is_iterable_typed(name, basestring) assert is_iterable_typed(value, basestring) 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 capture_output_setup(target, sources, ps): if __debug__: from ..build.property_set import PropertySet assert is_iterable_typed(target, basestring) assert is_iterable_typed(sources, basestring) assert isinstance(ps, PropertySet) run_path_setup(target[0], sources, ps) if ps.get('preserve-test-targets') == ['off']: bjam.call("set-target-variable", target, "REMOVE_TEST_TARGETS", "1")
def __init__(self, name, values, attributes): assert isinstance(name, basestring) assert is_iterable_typed(values, basestring) assert is_iterable_typed(attributes, basestring) self._name = name self._values = values self._default = None self._attributes = 0 for a in attributes: self._attributes = self._attributes | Feature._attribute_name_to_integer[a] self._attributes_string_list = attributes self._subfeatures = [] self._parent = None
def register_type (type, suffixes, base_type = None, os = []): """ Register the given type on the specified OSes, or on remaining OSes if os is not specified. This rule is injected into each of the type modules for the sake of convenience. """ assert isinstance(type, basestring) assert is_iterable_typed(suffixes, basestring) assert isinstance(base_type, basestring) or base_type is None assert is_iterable_typed(os, basestring) if registered (type): return if not os or os_name () in os: register (type, suffixes, base_type)
def alias(name, sources=[], requirements=[], default_build=[], usage_requirements=[]): assert isinstance(name, basestring) assert is_iterable_typed(sources, basestring) assert is_iterable_typed(requirements, basestring) assert is_iterable_typed(default_build, basestring) assert is_iterable_typed(usage_requirements, basestring) project = get_manager().projects().current() targets = get_manager().targets() targets.main_target_alternative(AliasTarget( name, project, targets.main_target_sources(sources, name, no_renaming=True), targets.main_target_requirements(requirements or [], project), targets.main_target_default_build(default_build, project), targets.main_target_usage_requirements(usage_requirements or [], project)))
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 ] ; """ assert is_iterable_typed(condition, basestring) assert is_iterable_typed(requirements, basestring) c = string.join(condition, ",") if c.find(":") != -1: return [c + r for r in requirements] else: return [c + ":" + r for r in requirements]
def generated_targets(self, sources, prop_set, project, name): assert is_iterable_typed(sources, virtual_target.VirtualTarget) assert isinstance(prop_set, property_set.PropertySet) assert isinstance(project, targets.ProjectTarget) assert isinstance(name, basestring) # sources to pass to inherited rule sources2 = [] # sources which are libraries libraries = [] # Searched libraries are not passed as argument to linker # but via some option. So, we pass them to the action # via property. fsa = [] fst = [] for s in sources: if type.is_derived(s.type(), "SEARCHED_LIB"): n = s.name() if s.shared(): fsa.append(n) else: fst.append(n) else: sources2.append(s) add = [] if fsa: add.append("<find-shared-library>" + "&&".join(fsa)) if fst: add.append("<find-static-library>" + "&&".join(fst)) spawn = generators.Generator.generated_targets(self, sources2, prop_set.add_raw(add), project, name) return spawn
def translate_dependencies(properties, project_id, location): assert is_iterable_typed(properties, Property) assert isinstance(project_id, basestring) assert isinstance(location, basestring) result = [] for p in properties: if not p.feature.dependency: result.append(p) else: v = p.value m = re.match("(.*)//(.*)", v) if m: rooted = m.group(1) if rooted[0] == '/': # Either project id or absolute Linux path, do nothing. pass else: rooted = os.path.join(os.getcwd(), location, rooted) result.append(Property(p.feature, rooted + "//" + m.group(2), p.condition)) elif os.path.isabs(v): result.append(p) else: result.append(Property(p.feature, project_id + "//" + v, p.condition)) return result
def construct_result (self, consumed, project, name, prop_set): """ Constructs the dependency graph that will be returned by this generator. consumed: Already prepared list of consumable targets If generator requires several source files will contain exactly len $(self.source_types_) targets with matching types Otherwise, might contain several targets with the type of self.source_types_ [0] project: name: prop_set: Properties to be used for all actions create here """ if __debug__: from .targets import ProjectTarget assert is_iterable_typed(consumed, virtual_target.VirtualTarget) assert isinstance(project, ProjectTarget) assert isinstance(name, basestring) or name is None assert isinstance(prop_set, property_set.PropertySet) result = [] # If this is 1->1 transformation, apply it to all consumed targets in order. if len (self.source_types_) < 2 and not self.composing_: for r in consumed: result.extend (self.generated_targets ([r], prop_set, project, name)) else: if consumed: result.extend (self.generated_targets (consumed, prop_set, project, name)) return result
def run_really (self, project, name, prop_set, sources): if __debug__: from .targets import ProjectTarget assert isinstance(project, ProjectTarget) # intermediary targets don't have names, so None is possible assert isinstance(name, basestring) or name is None assert isinstance(prop_set, property_set.PropertySet) assert is_iterable_typed(sources, virtual_target.VirtualTarget) # consumed: Targets that this generator will consume directly. # bypassed: Targets that can't be consumed and will be returned as-is. if self.composing_: (consumed, bypassed) = self.convert_multiple_sources_to_consumable_types (project, prop_set, sources) else: (consumed, bypassed) = self.convert_to_consumable_types (project, name, prop_set, sources) result = [] if consumed: result = self.construct_result (consumed, project, name, prop_set) result.extend (bypassed) if result: if project.manager ().logger ().on (): project.manager ().logger ().log (__name__, " SUCCESS: ", result) else: project.manager ().logger ().log (__name__, " FAILURE") return result
def find_replace(self, properties, value=None): assert is_iterable_typed(properties, basestring) assert isinstance(value, (basestring, type(None))) matches = [] match_ranks = [] for i in range(0, len(self.__properties)): p = self.__properties[i] if b2.util.set.contains (p, properties): matches.append (i) match_ranks.append(len(p)) best = sequence.select_highest_ranked (matches, match_ranks) if not best: return None if len (best) > 1: raise NoBestMatchingAlternative () best = best [0] original = self.__values[best] if value: self.__values[best] = value return original
def try_one_generator (project, name, generator, target_type, properties, sources): """ Checks if generator invocation can be pruned, because it's guaranteed to fail. If so, quickly returns empty list. Otherwise, calls try_one_generator_really. """ if __debug__: from .targets import ProjectTarget assert isinstance(project, ProjectTarget) assert isinstance(name, basestring) or name is None assert isinstance(generator, Generator) assert isinstance(target_type, basestring) assert isinstance(properties, property_set.PropertySet) assert is_iterable_typed(sources, virtual_target.VirtualTarget) source_types = [] for s in sources: source_types.append (s.type ()) viable_source_types = viable_source_types_for_generator (generator) if source_types and viable_source_types != ['*'] and\ not set_.intersection (source_types, viable_source_types): if project.manager ().logger ().on (): id = generator.id () project.manager ().logger ().log (__name__, "generator '%s' pruned" % id) project.manager ().logger ().log (__name__, "source_types" '%s' % source_types) project.manager ().logger ().log (__name__, "viable_source_types '%s'" % viable_source_types) return [] else: return try_one_generator_really (project, name, generator, target_type, properties, sources)
def run(self, project, name, prop_set, sources): assert isinstance(project, targets.ProjectTarget) assert isinstance(name, basestring) or name is None assert isinstance(prop_set, property_set.PropertySet) assert is_iterable_typed(sources, virtual_target.VirtualTarget) # The lib generator is composing, and can be only invoked with # explicit name. This check is present in generator.run (and so in # builtin.LinkingGenerator), but duplicate it here to avoid doing # extra work. if name: properties = prop_set.raw() # Determine the needed target type actual_type = None properties_grist = get_grist(properties) if "<source>" not in properties_grist and ("<search>" in properties_grist or "<name>" in properties_grist): actual_type = "SEARCHED_LIB" elif "<file>" in properties_grist: # The generator for actual_type = "LIB" elif "<link>shared" in properties: actual_type = "SHARED_LIB" else: actual_type = "STATIC_LIB" prop_set = prop_set.add_raw(["<main-target-type>LIB"]) # Construct the target. return generators.construct(project, name, actual_type, prop_set, sources)
def insert (self, properties, value): """ Associate value with properties. """ assert is_iterable_typed(properties, basestring) assert isinstance(value, basestring) self.__properties.append(properties) self.__values.append(value)
def determine_output_name(self, sources): """Determine the name of the produced target from the names of the sources.""" assert is_iterable_typed(sources, virtual_target.VirtualTarget) # The simple case if when a name # of source has single dot. Then, we take the part before # dot. Several dots can be caused by: # - Using source file like a.host.cpp # - A type which suffix has a dot. Say, we can # type 'host_cpp' with extension 'host.cpp'. # In the first case, we want to take the part till the last # dot. In the second case -- no sure, but for now take # the part till the last dot too. name = os.path.splitext(sources[0].name())[0] for s in sources[1:]: n2 = os.path.splitext(s.name()) if n2 != name: get_manager().errors()( "%s: source targets have different names: cannot determine target name" % (self.id_)) # Names of sources might include directory. We should strip it. return self.determine_target_name(sources[0].name())
def expand_subfeatures_in_conditions (properties): assert is_iterable_typed(properties, Property) result = [] for p in properties: if not p.condition: result.append(p) else: expanded = [] for c in p.condition: # It common that condition includes a toolset which # was never defined, or mentiones subfeatures which # were never defined. In that case, validation will # only produce an spirious error, so don't validate. expanded.extend(feature.expand_subfeatures ([c], True)) # we need to keep LazyProperties lazy if isinstance(p, LazyProperty): value = p.value feature_name = get_grist(value) value = value.replace(feature_name, '') result.append(LazyProperty(feature_name, value, condition=expanded)) else: result.append(Property(p.feature, p.value, expanded)) return result
def evaluate_conditionals_in_context (properties, context): """ Removes all conditional properties which conditions are not met For those with met conditions, removes the condition. Properties in conditions are looked up in 'context' """ if __debug__: from .property_set import PropertySet assert is_iterable_typed(properties, Property) assert isinstance(context, PropertySet) base = [] conditional = [] for p in properties: if p.condition: conditional.append (p) else: base.append (p) result = base[:] for p in conditional: # Evaluate condition # FIXME: probably inefficient if all(x in context for x in p.condition): result.append(Property(p.feature, p.value)) return result
def construct(self, name, source_targets, properties): if __debug__: from .virtual_target import VirtualTarget assert isinstance(name, basestring) assert is_iterable_typed(source_targets, VirtualTarget) assert isinstance(properties, property_set.PropertySet) return [property_set.empty(), source_targets]
def run(self, project, name, prop_set, sources): assert isinstance(project, targets.ProjectTarget) assert isinstance(name, basestring) or name is None assert isinstance(prop_set, property_set.PropertySet) assert is_iterable_typed(sources, virtual_target.VirtualTarget) if not name: return None # If name is empty, it means we're called not from top-level. # In this case, we just fail immediately, because SearchedLibGenerator # cannot be used to produce intermediate targets. properties = prop_set.raw() shared = "<link>shared" in properties a = virtual_target.NullAction(project.manager(), prop_set) real_name = feature.get_values("<name>", properties) if real_name: real_name = real_name[0] else: real_name = name search = feature.get_values("<search>", properties) usage_requirements = property_set.create(["<xdll-path>" + p for p in search]) t = SearchedLibTarget(real_name, project, shared, search, a) # We return sources for a simple reason. If there's # lib png : z : <name>png ; # the 'z' target should be returned, so that apps linking to # 'png' will link to 'z', too. return (usage_requirements, [b2.manager.get_manager().virtual_targets().register(t)] + sources)
def validate(properties): """ Exit with error if any of the properties is not valid. properties may be a single property or a sequence of properties. """ if isinstance(properties, Property): properties = [properties] assert is_iterable_typed(properties, Property) for p in properties: __validate1(p)
def add_requirements(requirements): """Adds elements to the list of global 'toolset requirements'. The requirements will be automatically added to the requirements for all main targets, as if they were specified literally. For best results, all requirements added should be conditional or indirect conditional.""" assert is_iterable_typed(requirements, basestring) if _ignore_toolset_requirements: __requirements.extend(requirements)
def path_variable_setting_command(variable, paths): """ Returns a command to sets a named shell path variable to the given NATIVE paths on the current platform. """ assert isinstance(variable, basestring) assert is_iterable_typed(paths, basestring) sep = os.path.pathsep return variable_setting_command(variable, sep.join(paths))
def prepend_path_variable_command(variable, paths): """ Returns a command that prepends the given paths to the named path variable on the current platform. """ assert isinstance(variable, basestring) assert is_iterable_typed(paths, basestring) return path_variable_setting_command(variable, paths + [expand_variable(variable)])
def run_path_setup(target, sources, ps): if __debug__: from ..build.property_set import PropertySet assert is_iterable_typed(target, basestring) or isinstance(target, basestring) assert is_iterable_typed(sources, basestring) assert isinstance(ps, PropertySet) # For testing, we need to make sure that all dynamic libraries needed by the # test are found. So, we collect all paths from dependency libraries (via # xdll-path property) and add whatever explicit dll-path user has specified. # The resulting paths are added to the environment on each test invocation. dll_paths = ps.get('dll-path') dll_paths.extend(ps.get('xdll-path')) dll_paths.extend(bjam.call("get-target-variable", sources, "RUN_PATH")) dll_paths = unique(dll_paths) if dll_paths: bjam.call("set-target-variable", target, "PATH_SETUP", common.prepend_path_variable_command( common.shared_library_path_variable(), dll_paths))
def change_generated_target_ps(is_suffix, type, properties, val): assert isinstance(is_suffix, (int, bool)) assert isinstance(type, basestring) assert is_iterable_typed(properties, basestring) assert isinstance(val, basestring) properties.append ('<target-type>' + type) prev = __prefixes_suffixes[is_suffix].find_replace(properties, val) if not prev: set_generated_target_ps(is_suffix, type, properties, val)
def propagate(self, scanner, targets): assert isinstance(scanner, Scanner) assert is_iterable_typed(targets, basestring) or isinstance( targets, basestring) engine = self.manager_.engine() engine.set_target_variable(targets, "HDRSCAN", scanner.pattern()) engine.set_target_variable(targets, "HDRRULE", self.exported_scanners_[scanner]) engine.set_target_variable(targets, "HDRGRIST", str(id(scanner)))
def __x_product_aux(property_sets, seen_features): """Returns non-conflicting combinations of property sets. property_sets is a list of PropertySet instances. seen_features is a set of Property instances. Returns a tuple of: - list of lists of Property instances, such that within each list, no two Property instance have the same feature, and no Property is for feature in seen_features. - set of features we saw in property_sets """ assert is_iterable_typed(property_sets, property_set.PropertySet) assert isinstance(seen_features, set) if not property_sets: return ([], set()) properties = property_sets[0].all() these_features = set() for p in property_sets[0].non_free(): these_features.add(p.feature()) # Note: the algorithm as implemented here, as in original Jam code, appears to # detect conflicts based on features, not properties. For example, if command # line build request say: # # <a>1/<b>1 c<1>/<b>1 # # It will decide that those two property sets conflict, because they both specify # a value for 'b' and will not try building "<a>1 <c1> <b1>", but rather two # different property sets. This is a topic for future fixing, maybe. if these_features & seen_features: (inner_result, inner_seen) = __x_product_aux(property_sets[1:], seen_features) return (inner_result, inner_seen | these_features) else: result = [] (inner_result, inner_seen) = __x_product_aux(property_sets[1:], seen_features | these_features) if inner_result: for inner in inner_result: result.append(properties + inner) else: result.append(properties) if inner_seen & these_features: # Some of elements in property_sets[1:] conflict with elements of property_sets[0], # Try again, this time omitting elements of property_sets[0] (inner_result2, inner_seen2) = __x_product_aux(property_sets[1:], seen_features) result.extend(inner_result2) return (result, inner_seen | these_features)
def run(self, project, name, prop_set, sources): assert isinstance(project, targets.ProjectTarget) assert isinstance(name, basestring) or name is None assert isinstance(prop_set, property_set.PropertySet) assert is_iterable_typed(sources, virtual_target.VirtualTarget) # create a copy since sources is being modified sources = list(sources) sources.extend(prop_set.get('<library>')) # Add <library-path> properties for all searched libraries extra = [] for s in sources: if s.type() == 'SEARCHED_LIB': search = s.search() extra.extend( property.Property('<library-path>', sp) for sp in search) # It's possible that we have libraries in sources which did not came # from 'lib' target. For example, libraries which are specified # just as filenames as sources. We don't have xdll-path properties # for such target, but still need to add proper dll-path properties. extra_xdll_path = [] for s in sources: if type.is_derived(s.type(), 'SHARED_LIB') and not s.action(): # Unfortunately, we don't have a good way to find the path # to a file, so use this nasty approach. p = s.project() location = path.root(s.name(), p.get('source-location')[0]) extra_xdll_path.append(os.path.dirname(location)) # Hardcode DLL paths only when linking executables. # Pros: do not need to relink libraries when installing. # Cons: "standalone" libraries (plugins, python extensions) can not # hardcode paths to dependent libraries. if prop_set.get('<hardcode-dll-paths>') == ['true'] \ and type.is_derived(self.target_types_ [0], 'EXE'): xdll_path = prop_set.get('<xdll-path>') extra.extend(property.Property('<dll-path>', sp) \ for sp in extra_xdll_path) extra.extend(property.Property('<dll-path>', sp) \ for sp in xdll_path) if extra: prop_set = prop_set.add_raw(extra) result = generators.Generator.run(self, project, name, prop_set, sources) if result: ur = self.extra_usage_requirements(result, prop_set) ur = ur.add( property_set.create( ['<xdll-path>' + p for p in extra_xdll_path])) else: return None return (ur, result)
def construct (project, name, target_type, prop_set, sources, top_level=False): """ Attempts to create target of 'target-type' with 'properties' from 'sources'. The 'sources' are treated as a collection of *possible* ingridients -- i.e. it is not required to consume them all. If 'multiple' is true, the rule is allowed to return several targets of 'target-type'. Returns a list of target. When this invocation is first instance of 'construct' in stack, returns only targets of requested 'target-type', otherwise, returns also unused sources and additionally generated targets. If 'top-level' is set, does not suppress generators that are already used in the stack. This may be useful in cases where a generator has to build a metatarget -- for example a target corresponding to built tool. """ if __debug__: from .targets import ProjectTarget assert isinstance(project, ProjectTarget) assert isinstance(name, basestring) or name is None assert isinstance(target_type, basestring) assert isinstance(prop_set, property_set.PropertySet) assert is_iterable_typed(sources, virtual_target.VirtualTarget) assert isinstance(top_level, bool) global __active_generators if top_level: saved_active = __active_generators __active_generators = [] global __construct_stack if not __construct_stack: __ensure_type (sources) __construct_stack.append (1) increase_indent () if project.manager().logger().on(): dout( "*** construct " + target_type) for s in sources: dout(" from " + str(s)) project.manager().logger().log (__name__, " properties: ", prop_set.raw ()) result = __construct_really(project, name, target_type, prop_set, sources) decrease_indent() __construct_stack = __construct_stack [1:] if top_level: __active_generators = saved_active return result
def prepend_path_variable_command(variable, paths): """ Returns a command that prepends the given paths to the named path variable on the current platform. """ assert isinstance(variable, basestring) assert is_iterable_typed(paths, basestring) return path_variable_setting_command( variable, paths + os.environ.get(variable, "").split(os.pathsep))
def check_tool(command): """ Checks that a tool can be invoked by 'command'. If command is not an absolute path, checks if it can be found in 'path'. If comand is absolute path, check that it exists. Returns 'command' if ok and empty string otherwise. """ assert is_iterable_typed(command, basestring) #FIXME: why do we check the first and last elements???? if check_tool_aux(command[0]) or check_tool_aux(command[-1]): return command
def create_with_validation (raw_properties): """ Creates new 'PropertySet' instances after checking that all properties are valid and converting implicit properties into gristed form. """ assert is_iterable_typed(raw_properties, basestring) properties = [property.create_from_string(s) for s in raw_properties] property.validate(properties) return create(properties)
def __init__ (self, id, composing, source_types, target_types_and_names, requirements = []): assert isinstance(id, basestring) assert isinstance(composing, bool) assert is_iterable_typed(source_types, basestring) assert is_iterable_typed(target_types_and_names, basestring) assert is_iterable_typed(requirements, basestring) self.id_ = id self.composing_ = composing self.source_types_ = source_types self.target_types_and_names_ = target_types_and_names self.requirements_ = requirements self.target_types_ = [] self.name_prefix_ = [] self.name_postfix_ = [] for e in target_types_and_names: # Create three parallel lists: one with the list of target types, # and two other with prefixes and postfixes to be added to target # name. We use parallel lists for prefix and postfix (as opposed # to mapping), because given target type might occur several times, # for example "H H(%_symbols)". m = _re_separate_types_prefix_and_postfix.match (e) if not m: raise BaseException ("Invalid type and name '%s' in declaration of type '%s'" % (e, id)) target_type = m.group (1) if not target_type: target_type = '' prefix = m.group (3) if not prefix: prefix = '' postfix = m.group (4) if not postfix: postfix = '' self.target_types_.append (target_type) self.name_prefix_.append (prefix) self.name_postfix_.append (postfix) for x in self.source_types_: type.validate (x) for x in self.target_types_: type.validate (x)
def __init__(self, variable_name, values, condition, rule=None): assert isinstance(variable_name, basestring) assert is_iterable(values) and all( isinstance(v, (basestring, type(None))) for v in values) assert is_iterable_typed(condition, property_set.PropertySet) assert isinstance(rule, (basestring, type(None))) self.variable_name = variable_name self.values = values self.condition = condition self.rule = rule
def select(features, properties): """ Selects properties which correspond to any of the given features. """ assert is_iterable_typed(properties, basestring) result = [] # add any missing angle brackets features = add_grist(features) return [p for p in properties if get_grist(p) in features]
def glob_internal(self, project, wildcards, excludes, rule_name): if __debug__: from .targets import ProjectTarget assert isinstance(project, ProjectTarget) assert is_iterable_typed(wildcards, basestring) assert is_iterable_typed(excludes, basestring) or excludes is None assert isinstance(rule_name, basestring) 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 remove(attributes, properties): """Returns a property sets which include all the elements in 'properties' that do not have attributes listed in 'attributes'.""" if isinstance(attributes, basestring): attributes = [attributes] assert is_iterable_typed(attributes, basestring) assert is_iterable_typed(properties, basestring) result = [] for e in properties: attributes_new = feature.attributes(get_grist(e)) has_common_features = 0 for a in attributes_new: if a in attributes: has_common_features = 1 break if not has_common_features: result += e return result
def __select_subfeatures(parent_property, features): """ Given a property, return the subset of features consisting of all ordinary subfeatures of the property's feature, and all specific subfeatures of the property's feature which are conditional on the property's value. """ if __debug__: from .property import Property assert isinstance(parent_property, Property) assert is_iterable_typed(features, Feature) return [f for f in features if is_subfeature_of(parent_property, f)]
def get_invocation_command_nodefault(toolset, tool, user_provided_command=[], additional_paths=[], path_last=False): """ A helper rule to get the command to invoke some tool. If 'user-provided-command' is not given, tries to find binary named 'tool' in PATH and in the passed 'additional-path'. Otherwise, verifies that the first element of 'user-provided-command' is an existing program. This rule returns the command to be used when invoking the tool. If we can't find the tool, a warning is issued. If 'path-last' is specified, PATH is checked after 'additional-paths' when searching for 'tool'. """ assert isinstance(toolset, basestring) assert isinstance(tool, basestring) assert is_iterable_typed(user_provided_command, basestring) assert is_iterable_typed(additional_paths, basestring) or additional_paths is None assert isinstance(path_last, (int, bool)) if not user_provided_command: command = find_tool(tool, additional_paths, path_last) if not command and __debug_configuration: print "warning: toolset", toolset, "initialization: can't find tool, tool" #FIXME #print "warning: initialized from" [ errors.nearest-user-location ] ; else: command = check_tool(user_provided_command) if not command and __debug_configuration: print "warning: toolset", toolset, "initialization:" print "warning: can't find user-provided command", user_provided_command #FIXME #ECHO "warning: initialized from" [ errors.nearest-user-location ] command = [] command = ' '.join(command) assert (isinstance(command, str)) return command
def expand_no_defaults(property_sets): """ Expand the given build request by combining all property_sets which don't specify conflicting non-free features. """ assert is_iterable_typed(property_sets, property_set.PropertySet) # First make all features and subfeatures explicit expanded_property_sets = [ps.expand_subfeatures() for ps in property_sets] # Now combine all of the expanded property_sets product = __x_product(expanded_property_sets) return [property_set.create(p) for p in product]
def set_target_variables(manager, rule_or_module, targets, ps): """ """ assert isinstance(rule_or_module, basestring) assert is_iterable_typed(targets, basestring) assert isinstance(ps, property_set.PropertySet) settings = __set_target_variables_aux(manager, rule_or_module, ps) if settings: for s in settings: for target in targets: manager.engine().set_target_variable(target, s[0], s[1], True)
def alias(name, sources=[], requirements=[], default_build=[], usage_requirements=[]): assert isinstance(name, basestring) assert is_iterable_typed(sources, basestring) assert is_iterable_typed(requirements, basestring) assert is_iterable_typed(default_build, basestring) assert is_iterable_typed(usage_requirements, basestring) project = get_manager().projects().current() targets = get_manager().targets() targets.main_target_alternative( AliasTarget( name, project, targets.main_target_sources(sources, name, no_renaming=True), targets.main_target_requirements(requirements or [], project), targets.main_target_default_build(default_build, project), targets.main_target_usage_requirements(usage_requirements or [], project)))
def generated_targets(self, sources, prop_set, project, name): """ Constructs targets that are created after consuming 'sources'. The result will be the list of virtual-target, which the same length as 'target_types' attribute and with corresponding types. When 'name' is empty, all source targets must have the same value of the 'name' attribute, which will be used instead of the 'name' argument. The value of 'name' attribute for each generated target will be equal to the 'name' parameter if there's no name pattern for this type. Otherwise, the '%' symbol in the name pattern will be replaced with the 'name' parameter to obtain the 'name' attribute. For example, if targets types are T1 and T2(with name pattern "%_x"), suffixes for T1 and T2 are .t1 and t2, and source if foo.z, then created files would be "foo.t1" and "foo_x.t2". The 'name' attribute actually determined the basename of a file. Note that this pattern mechanism has nothing to do with implicit patterns in make. It's a way to produce target which name is different for name of source. """ if __debug__: from .targets import ProjectTarget assert is_iterable_typed(sources, virtual_target.VirtualTarget) assert isinstance(prop_set, property_set.PropertySet) assert isinstance(project, ProjectTarget) assert isinstance(name, basestring) or name is None if not name: name = self.determine_output_name(sources) # Assign an action for each target action = self.action_class() a = action(project.manager(), sources, self.id_, prop_set) # Create generated target for each target type. targets = [] pre = self.name_prefix_ post = self.name_postfix_ for t in self.target_types_: basename = os.path.basename(name) generated_name = pre[0] + basename + post[0] generated_name = os.path.join(os.path.dirname(name), generated_name) pre = pre[1:] post = post[1:] targets.append( virtual_target.FileTarget(generated_name, t, project, a)) return [ project.manager().virtual_targets().register(t) for t in targets ]
def __init__(self, main_target, prop_set, sources, build_properties, sources_usage_requirements, created_targets): """ main_target: The instance of MainTarget class prop_set: Properties requested for this target sources: build_properties: Actually used properties sources_usage_requirements: Properties propagated from sources created_targets: Top-level created targets """ if __debug__: from .targets import AbstractTarget assert isinstance(main_target, AbstractTarget) assert isinstance(prop_set, property_set.PropertySet) assert is_iterable_typed(sources, VirtualTarget) assert isinstance(build_properties, property_set.PropertySet) assert isinstance(sources_usage_requirements, property_set.PropertySet) assert is_iterable_typed(created_targets, VirtualTarget) self.main_target_ = main_target self.properties_ = prop_set self.sources_ = sources self.build_properties_ = build_properties self.sources_usage_requirements_ = sources_usage_requirements self.created_targets_ = created_targets self.usage_requirements_ = None # Pre-compose the list of other dependency graphs, on which this one # depends deps = build_properties.get('<implicit-dependency>') self.other_dg_ = [] for d in deps: self.other_dg_.append(d.creating_subvariant()) self.other_dg_ = unique(self.other_dg_) self.implicit_includes_cache_ = {} self.target_directories_ = None
def handle_options(tool, condition, command, options): """ Handle common options for toolset, specifically sets the following flag variables: - CONFIG_COMMAND to 'command' - OPTIOns for compile to the value of <compileflags> in options - OPTIONS for compile.c to the value of <cflags> in options - OPTIONS for compile.c++ to the value of <cxxflags> in options - OPTIONS for compile.fortran to the value of <fflags> in options - OPTIONs for link to the value of <linkflags> in options """ from b2.build import toolset assert isinstance(tool, basestring) assert is_iterable_typed(condition, basestring) assert command and isinstance(command, basestring) assert is_iterable_typed(options, basestring) toolset.flags(tool, 'CONFIG_COMMAND', condition, [command]) toolset.flags(tool + '.compile', 'OPTIONS', condition, feature.get_values('<compileflags>', options)) toolset.flags(tool + '.compile.c', 'OPTIONS', condition, feature.get_values('<cflags>', options)) toolset.flags(tool + '.compile.c++', 'OPTIONS', condition, feature.get_values('<cxxflags>', options)) toolset.flags(tool + '.compile.fortran', 'OPTIONS', condition, feature.get_values('<fflags>', options)) toolset.flags(tool + '.link', 'OPTIONS', condition, feature.get_values('<linkflags>', options))
def defaults(features): """ Returns the default property values for the given features. """ assert is_iterable_typed(features, Feature) # FIXME: should merge feature and property modules. from . import property result = [] for f in features: if not f.free and not f.optional and f.default: result.append(property.Property(f, f.default)) return result
def __validate_feature_attributes (name, attributes): assert isinstance(name, basestring) assert is_iterable_typed(attributes, basestring) for attribute in attributes: if attribute not in VALID_ATTRIBUTES: raise InvalidAttribute ("unknown attributes: '%s' in feature declaration: '%s'" % (str (b2.util.set.difference (attributes, __all_attributes)), name)) if name in __all_features: raise AlreadyDefined ("feature '%s' already defined" % name) elif 'implicit' in attributes and 'free' in attributes: raise InvalidAttribute ("free features cannot also be implicit (in declaration of feature '%s')" % name) elif 'free' in attributes and 'propagated' in attributes: raise InvalidAttribute ("free features cannot also be propagated (in declaration of feature '%s')" % name)
def register_suffixes (suffixes, type): """ Specifies that targets with suffix from 'suffixes' have the type 'type'. If a different type is already specified for any of syffixes, issues an error. """ assert is_iterable_typed(suffixes, basestring) assert isinstance(type, basestring) for s in suffixes: if s in __suffixes_to_types: old_type = __suffixes_to_types [s] if old_type != type: raise BaseException ('Attempting to specify type for suffix "%s"\nOld type: "%s", New type "%s"' % (s, old_type, type)) else: __suffixes_to_types [s] = type
def create (raw_properties = []): """ Creates a new 'PropertySet' instance for the given raw properties, or returns an already existing one. """ assert (is_iterable_typed(raw_properties, property.Property) or is_iterable_typed(raw_properties, basestring)) # FIXME: propagate to callers. if len(raw_properties) > 0 and isinstance(raw_properties[0], property.Property): x = raw_properties else: x = [property.create_from_string(ps) for ps in raw_properties] x.sort() x = unique(x, stable=True) # FIXME: can we do better, e.g. by directly computing # hash value of the list? key = tuple(x) if not __cache.has_key (key): __cache [key] = PropertySet(x) return __cache [key]