diff --git a/v2/build/alias.py b/v2/build/alias.py index 5fdc1b892..f83a25bef 100755 --- a/v2/build/alias.py +++ b/v2/build/alias.py @@ -55,7 +55,7 @@ def alias(name, sources, requirements=None, default_build=None, usage_requiremen targets.main_target_alternative(AliasTarget( name, project, - targets.main_target_sources(sources, name), + 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))) diff --git a/v2/build/configure.py b/v2/build/configure.py new file mode 100644 index 000000000..34bc9acd3 --- /dev/null +++ b/v2/build/configure.py @@ -0,0 +1,157 @@ +# Status: ported. +# Base revison: 64488 +# +# Copyright (c) 2010 Vladimir Prus. +# +# Use, modification and distribution is subject to the Boost Software +# License Version 1.0. (See accompanying file LICENSE_1_0.txt or +# http://www.boost.org/LICENSE_1_0.txt) + +# This module defines function to help with two main tasks: +# +# - Discovering build-time configuration for the purposes of adjusting +# build process. +# - Reporting what is built, and how it is configured. + +import b2.build.property as property +import b2.build.property_set as property_set + +import b2.build.targets + +from b2.manager import get_manager +from b2.util.sequence import unique +from b2.util import bjam_signature, value_to_jam + +import bjam +import os + +__width = 30 + +def set_width(width): + global __width + __width = 30 + +__components = [] +__built_components = [] +__component_logs = {} +__announced_checks = False + +__log_file = None +__log_fd = -1 + +def register_components(components): + """Declare that the components specified by the parameter exist.""" + __components.extend(components) + +def components_building(components): + """Declare that the components specified by the parameters will be build.""" + __built_components.extend(components) + +def log_component_configuration(component, message): + """Report something about component configuration that the user should better know.""" + __component_logs.setdefault(component, []).append(message) + +def log_check_result(result): + global __announced_checks + if not __announced_checks: + print "Performing configuration checks" + __announced_checks = True + + print result + +def log_library_search_result(library, result): + log_check_result((" - %(library)s : %(result)s" % locals()).rjust(width)) + + +def print_component_configuration(): + + print "\nComponent configuration:" + for c in __components: + if c in __built_components: + s = "building" + else: + s = "not building" + message = " - %s)" % c + message = message.rjust(__width) + message += " : " + s + for m in __component_logs.get(c, []): + print " -" + m + print "" + +__builds_cache = {} + +def builds(metatarget_reference, project, ps, what): + # Attempt to build a metatarget named by 'metatarget-reference' + # in context of 'project' with properties 'ps'. + # Returns non-empty value if build is OK. + + result = [] + + existing = __builds_cache.get((what, ps), None) + if existing is None: + + result = False + __builds_cache[(what, ps)] = False + + targets = b2.build.targets.generate_from_reference( + metatarget_reference, project, ps).targets() + jam_targets = [] + for t in targets: + jam_targets.append(t.actualize()) + + x = (" - %s" % what).rjust(__width) + if bjam.call("UPDATE_NOW", jam_targets, str(__log_fd), "ignore-minus-n"): + __builds_cache[(what, ps)] = True + result = True + log_check_result("%s: yes" % x) + else: + log_check_result("%s: no" % x) + + return result + else: + return existing + +def set_log_file(log_file_name): + # Called by Boost.Build startup code to specify name of a file + # that will receive results of configure checks. This + # should never be called by users. + global __log_file, __log_fd + dirname = os.path.dirname(log_file_name) + if not os.path.exists(dirname): + os.makedirs(dirname) + # Make sure to keep the file around, so that it's not + # garbage-collected and closed + __log_file = open(log_file_name, "w") + __log_fd = __log_file.fileno() + +# Frontend rules + +class CheckTargetBuildsWorker: + + def __init__(self, target, true_properties, false_properties): + self.target = target + self.true_properties = property.create_from_strings(true_properties) + self.false_properties = property.create_from_strings(false_properties) + + def check(self, ps): + + # FIXME: this should not be hardcoded. Other checks might + # want to consider different set of features as relevant. + toolset = ps.get_properties('toolset')[0] + ps = property_set.create([toolset]) + t = get_manager().targets().current() + p = t.project() + if builds(self.target, p, ps, "%s builds" % self.target): + return self.true_properties + else: + return self.false_properties + +@bjam_signature((["target"], ["true_properties", "*"], ["false_properties", "*"])) +def check_target_builds(target, true_properties, false_properties): + worker = CheckTargetBuildsWorker(target, true_properties, false_properties) + value = value_to_jam(worker.check) + return "" + value + +get_manager().projects().add_rule("check-target-builds", check_target_builds) + + diff --git a/v2/build/engine.py b/v2/build/engine.py index fe63c5760..23fd6e58c 100644 --- a/v2/build/engine.py +++ b/v2/build/engine.py @@ -50,7 +50,7 @@ class BjamNativeAction: if property_set: p = property_set.raw() - b2.util.call_jam_function(self.action_name, targets, sources, p) + b2.util.set_jam_action(self.action_name, targets, sources, p) action_modifiers = {"updated": 0x01, "together": 0x02, @@ -144,6 +144,10 @@ class Engine: # Rule is already in indirect format return action_name else: + ix = action_name.find('.') + if ix != -1 and action_name[:ix] == context_module: + return context_module + '%' + action_name[ix+1:] + return context_module + '%' + action_name def register_bjam_action (self, action_name, function=None): diff --git a/v2/build/project.py b/v2/build/project.py index 4120a5e9a..d3d978e19 100644 --- a/v2/build/project.py +++ b/v2/build/project.py @@ -650,7 +650,8 @@ actual value %s""" % (jamfile_module, saved_project, self.current_project)) modules = sys.modules for class_name in modules: parts = class_name.split('.') - if name is class_name or parts[0] == "b2" and parts[-1] == name: + if name is class_name or parts[0] == "b2" \ + and parts[-1] == name.replace("-", "_"): module = modules[class_name] self.loaded_tool_modules_[name] = module return module diff --git a/v2/build/property.py b/v2/build/property.py index d3ed168d4..fece8c6d8 100644 --- a/v2/build/property.py +++ b/v2/build/property.py @@ -273,14 +273,6 @@ def split_conditional (property): return None -# FIXME: this should go -def is_conditional (property): - """ Returns True if a property is conditional. - """ - if __re_colon.search (replace_grist (property, '')): - return True - else: - return False def select (features, properties): """ Selects properties which correspond to any of the given features. @@ -310,7 +302,7 @@ def evaluate_conditionals_in_context (properties, context): else: base.append (p) - result = base + result = base[:] for p in conditional: # Evaluate condition diff --git a/v2/build/property_set.py b/v2/build/property_set.py index 589382423..a8f8597ea 100644 --- a/v2/build/property_set.py +++ b/v2/build/property_set.py @@ -303,9 +303,9 @@ class PropertySet: context = self if not self.evaluated_.has_key(context): + # FIXME: figure why the call messes up first parameter self.evaluated_[context] = create( - property.evaluate_conditionals_in_context(self.all_raw_, - context.raw())) + property.evaluate_conditionals_in_context(self.all(), context)) return self.evaluated_[context] @@ -412,6 +412,8 @@ class PropertySet: def get (self, feature): """ Returns all values of 'feature'. """ + if type(feature) == type([]): + feature = feature[0] if not isinstance(feature, b2.build.feature.Feature): feature = b2.build.feature.get(feature) @@ -439,9 +441,5 @@ class PropertySet: return result def __contains__(self, item): - for p in self.all_set_: - if p.feature().name() == "toolset": - print "EXISTING", hash(p), hash(p._feature), hash(p._value), "--", hash(item._feature), has(item._value) - print self.all_set_ return item in self.all_set_ diff --git a/v2/build/targets.py b/v2/build/targets.py index 9483b36e8..3d3668822 100644 --- a/v2/build/targets.py +++ b/v2/build/targets.py @@ -87,7 +87,6 @@ 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: @@ -111,7 +110,7 @@ class TargetRegistry: target.project ().add_alternative (target) return target - def main_target_sources (self, sources, main_target_name, no_remaning=0): + 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 @@ -120,17 +119,20 @@ class TargetRegistry: result = [] for t in sources: + + t = b2.util.jam_to_value_maybe(t) + if isinstance (t, AbstractTarget): name = t.name () if not no_renaming: - new_name = main_target_name + '__' + name - t.rename (new_name) + name = main_target_name + '__' + name + t.rename (name) # Inline targets are not built by default. p = t.project() p.mark_target_as_explicit(name) - result.append (new_name) + result.append(name) else: result.append (t) @@ -776,6 +778,50 @@ class FileReference (AbstractTarget): 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 @@ -835,29 +881,6 @@ class BasicTarget (AbstractTarget): 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.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 common_properties (self, build_request, requirements): """ Given build request and requirements, return properties common to dependency build request and target build @@ -908,24 +931,23 @@ class BasicTarget (AbstractTarget): # # might come from project's requirements. unconditional = feature.expand(requirements.non_conditional()) - - raw = context.all() - raw = property.refine(raw, unconditional) + + 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 = requirements.conditional() + 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) +\ + max_iterations = len(conditionals.all()) +\ len(requirements.get("")) + 1 added_requirements = [] - current = raw + current = context # It's assumed that ordinary conditional requirements can't add # properties, and that rules referred @@ -933,25 +955,24 @@ class BasicTarget (AbstractTarget): # properties. So the list of indirect conditionals # does not change. indirect = requirements.get("") - indirect = [s[1:] for s in indirect] ok = 0 for i in range(0, max_iterations): - e = property.evaluate_conditionals_in_context(conditionals, current) + 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(bjam.call(i, current)) + 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, [str(p) for p in current]) + 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. @@ -963,7 +984,7 @@ class BasicTarget (AbstractTarget): # Recompute 'current' using initial properties and conditional # requirements. added_requirements = e - current = property.refine(raw, feature.expand(e)) + current = context.refine(property_set.create(feature.expand(e))) if not ok: self.manager().errors()("Can't evaluate conditional properties " @@ -973,7 +994,7 @@ class BasicTarget (AbstractTarget): if what == "added": return property_set.create(unconditional + added_requirements) elif what == "refined": - return property_set.create(current) + return current else: self.manager().errors("Invalid value of the 'what' parameter") @@ -1029,7 +1050,7 @@ class BasicTarget (AbstractTarget): usage_requirements = [] for id in target_ids: - result = self.generate_from_reference(id, self.project_, property_set) + result = generate_from_reference(id, self.project_, property_set) targets += result.targets() usage_requirements += result.usage_requirements().all() @@ -1046,7 +1067,7 @@ class BasicTarget (AbstractTarget): usage_requirements = [] for p in properties: - result = self.generate_from_reference(p.value(), self.project_, ps) + result = generate_from_reference(p.value(), self.project_, ps) for t in result.targets(): result_properties.append(property.Property(p.feature(), t)) @@ -1180,25 +1201,6 @@ class BasicTarget (AbstractTarget): self.manager().targets().decrease_indent() return self.generated_[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 @@ -1276,6 +1278,9 @@ class TypedTarget (BasicTarget): 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_ diff --git a/v2/build/virtual_target.py b/v2/build/virtual_target.py index 2e67a53f3..6d9c65ebf 100644 --- a/v2/build/virtual_target.py +++ b/v2/build/virtual_target.py @@ -78,6 +78,7 @@ import b2.build.property_set as property_set import b2.build.property as property from b2.manager import get_manager +from b2.util import bjam_signature __re_starts_with_at = re.compile ('^@(.*)') @@ -533,16 +534,22 @@ class AbstractFileTarget (VirtualTarget): if tag: - rule_names = [t[:1] for t in tag if t[0] == '@'] - if rule_names: - if len(tag) > 1: - self.manager_.errors()( -"""@rulename is present but is not the only feature""") - - self.name_ = bjam.call(rule_names[0], specified_name, self.type_, ps) - else: + if len(tag) > 1: self.manager_.errors()( -"""The value of the feature must be '@rule-nane'""") + """@rulename is present but is not the only feature""") + + tag = tag[0] + if callable(tag): + self.name_ = tag(specified_name, self.type_, ps) + else: + if not tag[0] == '@': + self.manager_.errors()("""The value of the feature must be '@rule-nane'""") + + exported_ps = b2.util.value_to_jam(ps, methods=True) + self.name_ = b2.util.call_jam_function( + tag[1:], specified_name, self.type_, exported_ps) + if self.name_: + self.name_ = self.name_[0] # If there's no tag or the tag rule returned nothing. if not tag or not self.name_: @@ -571,10 +578,13 @@ class AbstractFileTarget (VirtualTarget): return name +@bjam_signature((["specified_name"], ["type"], ["property_set"])) def add_prefix_and_suffix(specified_name, type, property_set): """Appends the suffix appropriate to 'type/property-set' combination to the specified name and returns the result.""" + property_set = b2.util.jam_to_value_maybe(property_set) + suffix = "" if type: suffix = b2.build.type.generated_target_suffix(type, property_set) diff --git a/v2/build_system.py b/v2/build_system.py index f779ffb83..f6bb89f6f 100644 --- a/v2/build_system.py +++ b/v2/build_system.py @@ -460,6 +460,8 @@ def main_real(): global_build_dir = option.get("build-dir") manager = Manager(engine, global_build_dir) + import b2.build.configure as configure + if "--version" in sys.argv: version.report() @@ -587,17 +589,18 @@ def main_real(): ## { ## generators.dump ; ## } + - ## # We wish to put config.log in the build directory corresponding - ## # to Jamroot, so that the location does not differ depending on - ## # directory where we do build. The amount of indirection necessary - ## # here is scary. - ## local first-project = [ $(targets[0]).project ] ; - ## local first-project-root-location = [ $(first-project).get project-root ] ; - ## local first-project-root-module = [ project.load $(first-project-root-location) ] ; - ## local first-project-root = [ project.target $(first-project-root-module) ] ; - ## local first-build-build-dir = [ $(first-project-root).build-dir ] ; - ## configure.set-log-file $(first-build-build-dir)/config.log ; + # We wish to put config.log in the build directory corresponding + # to Jamroot, so that the location does not differ depending on + # directory where we do build. The amount of indirection necessary + # here is scary. + first_project = targets[0].project() + first_project_root_location = first_project.get('project-root') + first_project_root_module = manager.projects().load(first_project_root_location) + first_project_root = manager.projects().target(first_project_root_module) + first_build_build_dir = first_project_root.build_dir() + configure.set_log_file(os.path.join(first_build_build_dir, "config.log")) virtual_targets = [] diff --git a/v2/engine/src/compile.c b/v2/engine/src/compile.c index a4106bf39..c79c8febf 100644 --- a/v2/engine/src/compile.c +++ b/v2/engine/src/compile.c @@ -856,9 +856,31 @@ call_python_function(RULE* r, FRAME* frame) } else { - /* Do nothing. There are cases, e.g. feature.feature function that - should return value for the benefit of Python code and which - also can be called by Jam code. */ + /* See if this is an instance that defines special __jam_repr__ + method. */ + if (PyInstance_Check(py_result) + && PyObject_HasAttrString(py_result, "__jam_repr__")) + { + PyObject* repr = PyObject_GetAttrString(py_result, "__jam_repr__"); + if (repr) + { + PyObject* arguments2 = PyTuple_New(0); + PyObject* py_result2 = PyObject_Call(repr, arguments2, 0); + Py_DECREF(repr); + Py_DECREF(arguments2); + if (PyString_Check(py_result2)) + { + result = list_new(0, newstr(PyString_AsString(py_result2))); + } + Py_DECREF(py_result2); + } + } + + /* If 'result' is still empty, do nothing. There are cases, e.g. + feature.feature function that should return value for the benefit + of Python code and which also can be called by Jam code, where + no sensible value can be returned. We cannot even emit a warning, + since there will be a pile of them. */ } Py_DECREF( py_result ); diff --git a/v2/kernel/bootstrap.jam b/v2/kernel/bootstrap.jam index 004784a49..c345ba705 100644 --- a/v2/kernel/bootstrap.jam +++ b/v2/kernel/bootstrap.jam @@ -187,6 +187,15 @@ if ! $(dont-build) DEPENDS all : $(targets) ; } + rule call-in-module ( m : rulename : * ) + { + module $(m) + { + return [ $(2) $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ] ; + } + } + + rule set-update-action ( action : targets * : sources * : properties * ) { $(action) $(targets) : $(sources) : $(properties) ; diff --git a/v2/manager.py b/v2/manager.py index 7067dc82c..473857fc7 100644 --- a/v2/manager.py +++ b/v2/manager.py @@ -38,12 +38,6 @@ class Manager: self.errors_ = Errors() self.command_line_free_features_ = property_set.empty() - # Object Map. - # TODO: This is a kludge: maps object names to the actual instances. - # Sometimes, objects are stored in properties, along with some grist. - # This map is used to store the value and return an id, which can be later on used to retriev it back. - self.object_map_ = {} - global the_manager the_manager = self @@ -86,22 +80,6 @@ class Manager: def set_command_line_free_features(self, v): self.command_line_free_features_ = v - def register_object (self, value): - """ Stores an object in a map and returns a key that can be used to retrieve it. - """ - key = 'object_registry_' + str (value) - self.object_map_ [key] = value - return key - - def get_object (self, key): - """ Returns a previously registered object. - """ - if not isinstance (key, str): - # Probably it's the object itself. - return key - - return self.object_map_ [key] - def construct (self, properties = [], targets = []): """ Constructs the dependency graph. properties: the build properties. diff --git a/v2/test/BoostBuild.py b/v2/test/BoostBuild.py index e9756342e..7c01b6102 100644 --- a/v2/test/BoostBuild.py +++ b/v2/test/BoostBuild.py @@ -46,6 +46,9 @@ def flush_annotations(xml=0): print_annotation(ann[0], ann[1], xml) annotations = [] +def clear_annotations(): + global annotations + annotations = [] defer_annotations = 0 diff --git a/v2/test/test_all.py b/v2/test/test_all.py index fad67fab6..59476bd74 100644 --- a/v2/test/test_all.py +++ b/v2/test/test_all.py @@ -64,6 +64,10 @@ def run_tests(critical_tests, other_tests): print "PASSED" else: print "FAILED" + + if i == "regression": + BoostBuild.flush_annotations() + BoostBuild.clear_annotations() else: rs = "succeed" if not passed: diff --git a/v2/util/__init__.py b/v2/util/__init__.py index 044a8429a..99d34cdd0 100644 --- a/v2/util/__init__.py +++ b/v2/util/__init__.py @@ -1,6 +1,7 @@ import bjam import re +import types # Decorator the specifies bjam-side prototype for a Python function def bjam_signature(s): @@ -39,7 +40,7 @@ def unquote(s): _extract_jamfile_and_rule = re.compile("(Jamfile<.*>)%(.*)") -def call_jam_function(name, *args): +def set_jam_action(name, *args): m = _extract_jamfile_and_rule.match(name) if m: @@ -49,6 +50,62 @@ def call_jam_function(name, *args): return bjam.call(*args) + +def call_jam_function(name, *args): + + m = _extract_jamfile_and_rule.match(name) + if m: + args = ("call-in-module", m.group(1), m.group(2)) + args + return bjam.call(*args) + else: + return bjam.call(*((name,) + args)) + +__value_id = 0 +__python_to_jam = {} +__jam_to_python = {} + +def value_to_jam(value, methods=False): + """Makes a token to refer to a Python value inside Jam language code. + + The token is merely a string that can be passed around in Jam code and + eventually passed back. For example, we might want to pass PropertySet + instance to a tag function and it might eventually call back + to virtual_target.add_suffix_and_prefix, passing the same instance. + + For values that are classes, we'll also make class methods callable + from Jam. + + Note that this is necessary to make a bit more of existing Jamfiles work. + This trick should not be used to much, or else the performance benefits of + Python port will be eaten. + """ + + global __value_id + + r = __python_to_jam.get(value, None) + if r: + return r + + exported_name = '###_' + str(__value_id) + __value_id = __value_id + 1 + __python_to_jam[value] = exported_name + __jam_to_python[exported_name] = value + + if methods and type(value) == types.InstanceType: + for field_name in dir(value): + field = getattr(value, field_name) + if callable(field) and not field_name.startswith("__"): + bjam.import_rule("", exported_name + "." + field_name, field) + + return exported_name + +def jam_to_value_maybe(jam_value): + + if type(jam_value) == type("") and jam_value.startswith("###"): + return __jam_to_python[jam_value] + else: + return jam_value + def stem(filename): i = filename.find('.') if i != -1: