2
0
mirror of https://github.com/boostorg/build.git synced 2026-02-15 00:52:16 +00:00

Port build/configure.jam.

Also allow to expose Python class to Jam, which fixes
tag.py and inline.py testcases.


[SVN r64610]
This commit is contained in:
Vladimir Prus
2010-08-05 06:22:58 +00:00
parent 5255209e97
commit add5bdf7e0
15 changed files with 368 additions and 125 deletions

View File

@@ -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)))

157
v2/build/configure.py Normal file
View File

@@ -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 "<conditional>" + value
get_manager().projects().add_rule("check-target-builds", check_target_builds)

View File

@@ -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):

View File

@@ -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

View File

@@ -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

View File

@@ -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_

View File

@@ -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("<conditional>")) + 1
added_requirements = []
current = raw
current = context
# It's assumed that ordinary conditional requirements can't add
# <indirect-conditional> properties, and that rules referred
@@ -933,25 +955,24 @@ class BasicTarget (AbstractTarget):
# <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)
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_

View File

@@ -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()(
"""<tag>@rulename is present but is not the only <tag> 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 <tag> feature must be '@rule-nane'""")
"""<tag>@rulename is present but is not the only <tag> 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 <tag> 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)

View File

@@ -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 = []

View File

@@ -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 );

View File

@@ -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) ;

View File

@@ -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.

View File

@@ -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

View File

@@ -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:

View File

@@ -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: