• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Status: being ported by Vladimir Prus
2# Base revision:  40958
3#
4# Copyright 2003 Dave Abrahams
5# Copyright 2005 Rene Rivera
6# Copyright 2002, 2003, 2004, 2005, 2006 Vladimir Prus
7# Distributed under the Boost Software License, Version 1.0.
8# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
9
10""" Support for toolset definition.
11"""
12import sys
13
14import feature, property, generators, property_set
15import b2.util.set
16import bjam
17
18from b2.util import cached, qualify_jam_action, is_iterable_typed, is_iterable
19from b2.util.utility import *
20from b2.util import bjam_signature, sequence
21from b2.manager import get_manager
22
23__re_split_last_segment = re.compile (r'^(.+)\.([^\.])*')
24__re_two_ampersands = re.compile ('(&&)')
25__re_first_segment = re.compile ('([^.]*).*')
26__re_first_group = re.compile (r'[^.]*\.(.*)')
27_ignore_toolset_requirements = '--ignore-toolset-requirements' not in sys.argv
28
29# Flag is a mechanism to set a value
30# A single toolset flag. Specifies that when certain
31# properties are in build property set, certain values
32# should be appended to some variable.
33#
34# A flag applies to a specific action in specific module.
35# The list of all flags for a module is stored, and each
36# flag further contains the name of the rule it applies
37# for,
38class Flag:
39
40    def __init__(self, variable_name, values, condition, rule = None):
41        assert isinstance(variable_name, basestring)
42        assert is_iterable(values) and all(
43            isinstance(v, (basestring, type(None))) for v in values)
44        assert is_iterable_typed(condition, property_set.PropertySet)
45        assert isinstance(rule, (basestring, type(None)))
46        self.variable_name = variable_name
47        self.values = values
48        self.condition = condition
49        self.rule = rule
50
51    def __str__(self):
52        return("Flag(" + str(self.variable_name) + ", " + str(self.values) +\
53               ", " + str(self.condition) + ", " + str(self.rule) + ")")
54
55def reset ():
56    """ Clear the module state. This is mainly for testing purposes.
57    """
58    global __module_flags, __flags, __stv
59
60    # Mapping from module name to a list of all flags that apply
61    # to either that module directly, or to any rule in that module.
62    # Each element of the list is Flag instance.
63    # So, for module named xxx this might contain flags for 'xxx',
64    # for 'xxx.compile', for 'xxx.compile.c++', etc.
65    __module_flags = {}
66
67    # Mapping from specific rule or module name to a list of Flag instances
68    # that apply to that name.
69    # Say, it might contain flags for 'xxx.compile.c++'. If there are
70    # entries for module name 'xxx', they are flags for 'xxx' itself,
71    # not including any rules in that module.
72    __flags = {}
73
74    # A cache for variable settings. The key is generated from the rule name and the properties.
75    __stv = {}
76
77reset ()
78
79# FIXME: --ignore-toolset-requirements
80def using(toolset_module, *args):
81    if isinstance(toolset_module, (list, tuple)):
82        toolset_module = toolset_module[0]
83    loaded_toolset_module= get_manager().projects().load_module(toolset_module, [os.getcwd()]);
84    loaded_toolset_module.init(*args)
85
86# FIXME push-checking-for-flags-module ....
87# FIXME: investigate existing uses of 'hack-hack' parameter
88# in jam code.
89
90@bjam_signature((["rule_or_module", "variable_name", "condition", "*"],
91                 ["values", "*"]))
92def flags(rule_or_module, variable_name, condition, values = []):
93    """ Specifies the flags (variables) that must be set on targets under certain
94        conditions, described by arguments.
95        rule_or_module:   If contains dot, should be a rule name.
96                          The flags will be applied when that rule is
97                          used to set up build actions.
98
99                          If does not contain dot, should be a module name.
100                          The flags will be applied for all rules in that
101                          module.
102                          If module for rule is different from the calling
103                          module, an error is issued.
104
105         variable_name:   Variable that should be set on target
106
107         condition        A condition when this flag should be applied.
108                          Should be set of property sets. If one of
109                          those property sets is contained in build
110                          properties, the flag will be used.
111                          Implied values are not allowed:
112                          "<toolset>gcc" should be used, not just
113                          "gcc". Subfeatures, like in "<toolset>gcc-3.2"
114                          are allowed. If left empty, the flag will
115                          always used.
116
117                          Property sets may use value-less properties
118                          ('<a>'  vs. '<a>value') to match absent
119                          properties. This allows to separately match
120
121                             <architecture>/<address-model>64
122                             <architecture>ia64/<address-model>
123
124                          Where both features are optional. Without this
125                          syntax we'd be forced to define "default" value.
126
127         values:          The value to add to variable. If <feature>
128                          is specified, then the value of 'feature'
129                          will be added.
130    """
131    assert isinstance(rule_or_module, basestring)
132    assert isinstance(variable_name, basestring)
133    assert is_iterable_typed(condition, basestring)
134    assert is_iterable(values) and all(isinstance(v, (basestring, type(None))) for v in values)
135    caller = bjam.caller()
136    if not '.' in rule_or_module and caller and caller[:-1].startswith("Jamfile"):
137        # Unqualified rule name, used inside Jamfile. Most likely used with
138        # 'make' or 'notfile' rules. This prevents setting flags on the entire
139        # Jamfile module (this will be considered as rule), but who cares?
140        # Probably, 'flags' rule should be split into 'flags' and
141        # 'flags-on-module'.
142        rule_or_module = qualify_jam_action(rule_or_module, caller)
143    else:
144        # FIXME: revive checking that we don't set flags for a different
145        # module unintentionally
146        pass
147
148    if condition and not replace_grist (condition, ''):
149        # We have condition in the form '<feature>', that is, without
150        # value. That's a previous syntax:
151        #
152        #   flags gcc.link RPATH <dll-path> ;
153        # for compatibility, convert it to
154        #   flags gcc.link RPATH : <dll-path> ;
155        values = [ condition ]
156        condition = None
157
158    if condition:
159        transformed = []
160        for c in condition:
161            # FIXME: 'split' might be a too raw tool here.
162            pl = [property.create_from_string(s,False,True) for s in c.split('/')]
163            pl = feature.expand_subfeatures(pl);
164            transformed.append(property_set.create(pl))
165        condition = transformed
166
167        property.validate_property_sets(condition)
168
169    __add_flag (rule_or_module, variable_name, condition, values)
170
171def set_target_variables (manager, rule_or_module, targets, ps):
172    """
173    """
174    assert isinstance(rule_or_module, basestring)
175    assert is_iterable_typed(targets, basestring)
176    assert isinstance(ps, property_set.PropertySet)
177    settings = __set_target_variables_aux(manager, rule_or_module, ps)
178
179    if settings:
180        for s in settings:
181            for target in targets:
182                manager.engine ().set_target_variable (target, s [0], s[1], True)
183
184def find_satisfied_condition(conditions, ps):
185    """Returns the first element of 'property-sets' which is a subset of
186    'properties', or an empty list if no such element exists."""
187    assert is_iterable_typed(conditions, property_set.PropertySet)
188    assert isinstance(ps, property_set.PropertySet)
189
190    for condition in conditions:
191
192        found_all = True
193        for i in condition.all():
194
195            if i.value:
196                found = i.value in ps.get(i.feature)
197            else:
198                # Handle value-less properties like '<architecture>' (compare with
199                # '<architecture>x86').
200                # If $(i) is a value-less property it should match default
201                # value of an optional property. See the first line in the
202                # example below:
203                #
204                #  property set     properties     result
205                # <a> <b>foo      <b>foo           match
206                # <a> <b>foo      <a>foo <b>foo    no match
207                # <a>foo <b>foo   <b>foo           no match
208                # <a>foo <b>foo   <a>foo <b>foo    match
209                found = not ps.get(i.feature)
210
211            found_all = found_all and found
212
213        if found_all:
214            return condition
215
216    return None
217
218
219def register (toolset):
220    """ Registers a new toolset.
221    """
222    assert isinstance(toolset, basestring)
223    feature.extend('toolset', [toolset])
224
225def inherit_generators (toolset, properties, base, generators_to_ignore = []):
226    assert isinstance(toolset, basestring)
227    assert is_iterable_typed(properties, basestring)
228    assert isinstance(base, basestring)
229    assert is_iterable_typed(generators_to_ignore, basestring)
230    if not properties:
231        properties = [replace_grist (toolset, '<toolset>')]
232
233    base_generators = generators.generators_for_toolset(base)
234
235    for g in base_generators:
236        id = g.id()
237
238        if not id in generators_to_ignore:
239            # Some generator names have multiple periods in their name, so
240            # $(id:B=$(toolset)) doesn't generate the right new_id name.
241            # e.g. if id = gcc.compile.c++, $(id:B=darwin) = darwin.c++,
242            # which is not what we want. Manually parse the base and suffix
243            # (if there's a better way to do this, I'd love to see it.)
244            # See also register in module generators.
245            (base, suffix) = split_action_id(id)
246
247            new_id = toolset + '.' + suffix
248
249            generators.register(g.clone(new_id, properties))
250
251def inherit_flags(toolset, base, prohibited_properties = []):
252    """Brings all flag definitions from the 'base' toolset into the 'toolset'
253    toolset. Flag definitions whose conditions make use of properties in
254    'prohibited-properties' are ignored. Don't confuse property and feature, for
255    example <debug-symbols>on and <debug-symbols>off, so blocking one of them does
256    not block the other one.
257
258    The flag conditions are not altered at all, so if a condition includes a name,
259    or version of a base toolset, it won't ever match the inheriting toolset. When
260    such flag settings must be inherited, define a rule in base toolset module and
261    call it as needed."""
262    assert isinstance(toolset, basestring)
263    assert isinstance(base, basestring)
264    assert is_iterable_typed(prohibited_properties, basestring)
265    for f in __module_flags.get(base, []):
266
267        if not f.condition or b2.util.set.difference(f.condition, prohibited_properties):
268            match = __re_first_group.match(f.rule)
269            rule_ = None
270            if match:
271                rule_ = match.group(1)
272
273            new_rule_or_module = ''
274
275            if rule_:
276                new_rule_or_module = toolset + '.' + rule_
277            else:
278                new_rule_or_module = toolset
279
280            __add_flag (new_rule_or_module, f.variable_name, f.condition, f.values)
281
282
283def inherit_rules(toolset, base):
284    engine = get_manager().engine()
285    new_actions = {}
286    for action_name, action in engine.actions.iteritems():
287        module, id = split_action_id(action_name)
288        if module == base:
289            new_action_name = toolset + '.' + id
290            # make sure not to override any existing actions
291            # that may have been declared already
292            if new_action_name not in engine.actions:
293                new_actions[new_action_name] = action
294
295    engine.actions.update(new_actions)
296
297######################################################################################
298# Private functions
299
300@cached
301def __set_target_variables_aux (manager, rule_or_module, ps):
302    """ Given a rule name and a property set, returns a list of tuples of
303        variables names and values, which must be set on targets for that
304        rule/properties combination.
305    """
306    assert isinstance(rule_or_module, basestring)
307    assert isinstance(ps, property_set.PropertySet)
308    result = []
309
310    for f in __flags.get(rule_or_module, []):
311
312        if not f.condition or find_satisfied_condition (f.condition, ps):
313            processed = []
314            for v in f.values:
315                # The value might be <feature-name> so needs special
316                # treatment.
317                processed += __handle_flag_value (manager, v, ps)
318
319            for r in processed:
320                result.append ((f.variable_name, r))
321
322    # strip away last dot separated part and recurse.
323    next = __re_split_last_segment.match(rule_or_module)
324
325    if next:
326        result.extend(__set_target_variables_aux(
327            manager, next.group(1), ps))
328
329    return result
330
331def __handle_flag_value (manager, value, ps):
332    assert isinstance(value, basestring)
333    assert isinstance(ps, property_set.PropertySet)
334    result = []
335
336    if get_grist (value):
337        f = feature.get(value)
338        values = ps.get(f)
339
340        for value in values:
341
342            if f.dependency:
343                # the value of a dependency feature is a target
344                # and must be actualized
345                result.append(value.actualize())
346
347            elif f.path or f.free:
348
349                # Treat features with && in the value
350                # specially -- each &&-separated element is considered
351                # separate value. This is needed to handle searched
352                # libraries, which must be in specific order.
353                if not __re_two_ampersands.search(value):
354                    result.append(value)
355
356                else:
357                    result.extend(value.split ('&&'))
358            else:
359                result.append (value)
360    else:
361        result.append (value)
362
363    return sequence.unique(result, stable=True)
364
365def __add_flag (rule_or_module, variable_name, condition, values):
366    """ Adds a new flag setting with the specified values.
367        Does no checking.
368    """
369    assert isinstance(rule_or_module, basestring)
370    assert isinstance(variable_name, basestring)
371    assert is_iterable_typed(condition, property_set.PropertySet)
372    assert is_iterable(values) and all(
373        isinstance(v, (basestring, type(None))) for v in values)
374    f = Flag(variable_name, values, condition, rule_or_module)
375
376    # Grab the name of the module
377    m = __re_first_segment.match (rule_or_module)
378    assert m
379    module = m.group(1)
380
381    __module_flags.setdefault(module, []).append(f)
382    __flags.setdefault(rule_or_module, []).append(f)
383
384__requirements = []
385
386def requirements():
387    """Return the list of global 'toolset requirements'.
388    Those requirements will be automatically added to the requirements of any main target."""
389    return __requirements
390
391def add_requirements(requirements):
392    """Adds elements to the list of global 'toolset requirements'. The requirements
393    will be automatically added to the requirements for all main targets, as if
394    they were specified literally. For best results, all requirements added should
395    be conditional or indirect conditional."""
396    assert is_iterable_typed(requirements, basestring)
397
398    if _ignore_toolset_requirements:
399        __requirements.extend(requirements)
400
401
402# Make toolset 'toolset', defined in a module of the same name,
403# inherit from 'base'
404# 1. The 'init' rule from 'base' is imported into 'toolset' with full
405#    name. Another 'init' is called, which forwards to the base one.
406# 2. All generators from 'base' are cloned. The ids are adjusted and
407#    <toolset> property in requires is adjusted too
408# 3. All flags are inherited
409# 4. All rules are imported.
410def inherit(toolset, base):
411    assert isinstance(toolset, basestring)
412    assert isinstance(base, basestring)
413    get_manager().projects().load_module(base, ['.']);
414
415    inherit_generators(toolset, [], base)
416    inherit_flags(toolset, base)
417    inherit_rules(toolset, base)
418