1 # Status: ported, except for tests.
4 # Copyright 2001, 2002, 2003 Dave Abrahams
5 # Copyright 2006 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)
12 from functools
import total_ordering
14 from b2
.util
.utility
import *
15 from b2
.build
import feature
16 from b2
.util
import sequence
, qualify_jam_action
, is_iterable_typed
18 from b2
.manager
import get_manager
21 __re_two_ampersands
= re
.compile ('&&')
22 __re_comma
= re
.compile (',')
23 __re_split_condition
= re
.compile ('(.*):(<.*)')
24 __re_split_conditional
= re
.compile (r
'(.+):<(.+)')
25 __re_colon
= re
.compile (':')
26 __re_has_condition
= re
.compile (r
':<')
27 __re_separate_condition_and_property
= re
.compile (r
'(.*):(<.*)')
29 _not_applicable_feature
='not-applicable-in-this-context'
30 feature
.feature(_not_applicable_feature
, [], ['free'])
32 __abbreviated_paths
= False
35 class PropertyMeta(type):
37 This class exists to implement the isinstance() and issubclass()
38 hooks for the Property class. Since we've introduce the concept of
39 a LazyProperty, isinstance(p, Property) will fail when p is a LazyProperty.
40 Implementing both __instancecheck__ and __subclasscheck__ will allow
41 LazyProperty instances to pass the isinstance() and issubclass check for
44 Additionally, the __call__ method intercepts the call to the Property
45 constructor to ensure that calling Property with the same arguments
46 will always return the same Property instance.
51 def __call__(mcs
, f
, value
, condition
=None):
53 This intercepts the call to the Property() constructor.
55 This exists so that the same arguments will always return the same Property
56 instance. This allows us to give each instance a unique ID.
58 from b2
.build
.feature
import Feature
59 if not isinstance(f
, Feature
):
63 key
= (f
, value
) + tuple(sorted(condition
))
64 if key
not in mcs
._registry
:
65 instance
= super(PropertyMeta
, mcs
).__call
__(f
, value
, condition
)
66 mcs
._registry
[key
] = instance
67 return mcs
._registry
[key
]
71 return (hasattr(obj
, 'feature') and
72 hasattr(obj
, 'value') and
73 hasattr(obj
, 'condition'))
75 def __instancecheck__(self
, instance
):
76 return self
.check(instance
)
78 def __subclasscheck__(self
, subclass
):
79 return self
.check(subclass
)
83 class Property(object):
85 __slots__
= ('feature', 'value', 'condition', '_to_raw', '_hash', 'id')
86 __metaclass__
= PropertyMeta
88 def __init__(self
, f
, value
, condition
=None):
89 assert(f
.free
or ':' not in value
)
95 self
.condition
= condition
96 self
._hash
= hash((self
.feature
, self
.value
) + tuple(sorted(self
.condition
)))
97 self
.id = PropertyMeta
.current_id
98 # increment the id counter.
99 # this allows us to take a list of Property
100 # instances and use their unique integer ID
101 # to create a key for PropertySet caching. This is
102 # much faster than string comparison.
103 PropertyMeta
.current_id
+= 1
107 condition_str
= ",".join(str(p
) for p
in self
.condition
) + ':'
109 self
._to
_raw
= '{}<{}>{}'.format(condition_str
, f
.name
, value
)
121 def __eq__(self
, other
):
122 return self
._hash
== other
._hash
124 def __lt__(self
, other
):
125 return (self
.feature
.name
, self
.value
) < (other
.feature
.name
, other
.value
)
129 class LazyProperty(object):
130 def __init__(self
, feature_name
, value
, condition
=None):
131 if condition
is None:
134 self
.__property
= Property(
135 feature
.get(_not_applicable_feature
), feature_name
+ value
, condition
=condition
)
136 self
.__name
= feature_name
138 self
.__condition
= condition
139 self
.__feature
= None
141 def __getattr__(self
, item
):
142 if self
.__feature
is None:
144 self
.__feature
= feature
.get(self
.__name
)
145 self
.__property
= Property(self
.__feature
, self
.__value
, self
.__condition
)
148 return getattr(self
.__property
, item
)
151 return hash(self
.__property
)
154 return self
.__property
._to
_raw
156 def __eq__(self
, other
):
157 return self
.__property
== other
159 def __lt__(self
, other
):
160 return (self
.feature
.name
, self
.value
) < (other
.feature
.name
, other
.value
)
163 def create_from_string(s
, allow_condition
=False,allow_missing_value
=False):
164 assert isinstance(s
, basestring
)
165 assert isinstance(allow_condition
, bool)
166 assert isinstance(allow_missing_value
, bool)
169 if not isinstance(s
, types
.StringType
):
171 if __re_has_condition
.search(s
):
173 if not allow_condition
:
174 raise BaseException("Conditional property is not allowed in this context")
176 m
= __re_separate_condition_and_property
.match(s
)
177 condition
= m
.group(1)
180 # FIXME: break dependency cycle
181 from b2
.manager
import get_manager
184 condition
= [create_from_string(x
) for x
in condition
.split(',')]
186 feature_name
= get_grist(s
)
188 if feature
.is_implicit_value(s
):
189 f
= feature
.implied_feature(s
)
191 p
= Property(f
, value
, condition
=condition
)
193 raise get_manager().errors()("Invalid property '%s' -- unknown feature" % s
)
196 if not value
and not allow_missing_value
:
197 get_manager().errors()("Invalid property '%s' -- no value specified" % s
)
199 if feature
.valid(feature_name
):
200 p
= Property(feature
.get(feature_name
), value
, condition
=condition
)
202 # In case feature name is not known, it is wrong to do a hard error.
203 # Feature sets change depending on the toolset. So e.g.
204 # <toolset-X:version> is an unknown feature when using toolset Y.
206 # Ideally we would like to ignore this value, but most of
207 # Boost.Build code expects that we return a valid Property. For this
208 # reason we use a sentinel <not-applicable-in-this-context> feature.
210 # The underlying cause for this problem is that python port Property
211 # is more strict than its Jam counterpart and must always reference
213 p
= LazyProperty(feature_name
, value
, condition
=condition
)
217 def create_from_strings(string_list
, allow_condition
=False):
218 assert is_iterable_typed(string_list
, basestring
)
219 return [create_from_string(s
, allow_condition
) for s
in string_list
]
222 """ Clear the module state. This is mainly for testing purposes.
226 # A cache of results from as_path
232 def set_abbreviated_paths(on
=True):
233 global __abbreviated_paths
237 __abbreviated_paths
= on
240 def get_abbreviated_paths():
241 return __abbreviated_paths
or '--abbreviated-paths' in sys
.argv
244 def path_order (x
, y
):
245 """ Helper for as_path, below. Orders properties with the implicit ones
246 first, and within the two sections in alphabetical order of feature
263 x
= feature
.expand_subfeatures([x
])
264 y
= feature
.expand_subfeatures([y
])
273 def identify(string
):
277 def refine (properties
, requirements
):
278 """ Refines 'properties' by overriding any non-free properties
279 for which a different value is specified in 'requirements'.
280 Conditional requirements are just added without modification.
281 Returns the resulting list of properties.
283 assert is_iterable_typed(properties
, Property
)
284 assert is_iterable_typed(requirements
, Property
)
285 # The result has no duplicates, so we store it in a set
288 # Records all requirements.
291 # All the elements of requirements should be present in the result
292 # Record them so that we can handle 'properties'.
293 for r
in requirements
:
294 # Don't consider conditional requirements.
296 required
[r
.feature
] = r
299 # Skip conditional properties
302 # No processing for free properties
306 if p
.feature
in required
:
307 result
.add(required
[p
.feature
])
311 return sequence
.unique(list(result
) + requirements
)
313 def translate_paths (properties
, path
):
314 """ Interpret all path properties in 'properties' as relative to 'path'
315 The property values are assumed to be in system-specific form, and
316 will be translated into normalized form.
318 assert is_iterable_typed(properties
, Property
)
324 values
= __re_two_ampersands
.split(p
.value
)
326 new_value
= "&&".join(os
.path
.normpath(os
.path
.join(path
, v
)) for v
in values
)
328 if new_value
!= p
.value
:
329 result
.append(Property(p
.feature
, new_value
, p
.condition
))
338 def translate_indirect(properties
, context_module
):
339 """Assumes that all feature values that start with '@' are
340 names of rules, used in 'context-module'. Such rules can be
341 either local to the module or global. Qualified local rules
342 with the name of the module."""
343 assert is_iterable_typed(properties
, Property
)
344 assert isinstance(context_module
, basestring
)
347 if p
.value
[0] == '@':
348 q
= qualify_jam_action(p
.value
[1:], context_module
)
349 get_manager().engine().register_bjam_action(q
)
350 result
.append(Property(p
.feature
, '@' + q
, p
.condition
))
356 def validate (properties
):
357 """ Exit with error if any of the properties is not valid.
358 properties may be a single property or a sequence of properties.
360 if isinstance(properties
, Property
):
361 properties
= [properties
]
362 assert is_iterable_typed(properties
, Property
)
366 def expand_subfeatures_in_conditions (properties
):
367 assert is_iterable_typed(properties
, Property
)
375 for c
in p
.condition
:
376 # It common that condition includes a toolset which
377 # was never defined, or mentiones subfeatures which
378 # were never defined. In that case, validation will
379 # only produce an spirious error, so don't validate.
380 expanded
.extend(feature
.expand_subfeatures ([c
], True))
382 # we need to keep LazyProperties lazy
383 if isinstance(p
, LazyProperty
):
385 feature_name
= get_grist(value
)
386 value
= value
.replace(feature_name
, '')
387 result
.append(LazyProperty(feature_name
, value
, condition
=expanded
))
389 result
.append(Property(p
.feature
, p
.value
, expanded
))
393 # FIXME: this should go
394 def split_conditional (property):
395 """ If 'property' is conditional property, returns
396 condition and the property, e.g
397 <variant>debug,<toolset>gcc:<inlining>full will become
398 <variant>debug,<toolset>gcc <inlining>full.
399 Otherwise, returns empty string.
401 assert isinstance(property, basestring
)
402 m
= __re_split_conditional
.match (property)
405 return (m
.group (1), '<' + m
.group (2))
410 def select (features
, properties
):
411 """ Selects properties which correspond to any of the given features.
413 assert is_iterable_typed(properties
, basestring
)
416 # add any missing angle brackets
417 features
= add_grist (features
)
419 return [p
for p
in properties
if get_grist(p
) in features
]
421 def validate_property_sets (sets
):
423 from .property_set
import PropertySet
424 assert is_iterable_typed(sets
, PropertySet
)
428 def evaluate_conditionals_in_context (properties
, context
):
429 """ Removes all conditional properties which conditions are not met
430 For those with met conditions, removes the condition. Properties
431 in conditions are looked up in 'context'
434 from .property_set
import PropertySet
435 assert is_iterable_typed(properties
, Property
)
436 assert isinstance(context
, PropertySet
)
442 conditional
.append (p
)
447 for p
in conditional
:
450 # FIXME: probably inefficient
451 if all(x
in context
for x
in p
.condition
):
452 result
.append(Property(p
.feature
, p
.value
))
457 def change (properties
, feature
, value
= None):
458 """ Returns a modified version of properties with all values of the
459 given feature replaced by the given value.
460 If 'value' is None the feature will be removed.
462 assert is_iterable_typed(properties
, basestring
)
463 assert isinstance(feature
, basestring
)
464 assert isinstance(value
, (basestring
, type(None)))
467 feature
= add_grist (feature
)
470 if get_grist (p
) == feature
:
472 result
.append (replace_grist (value
, feature
))
480 ################################################################
483 def __validate1 (property):
484 """ Exit with error if property is not valid.
486 assert isinstance(property, Property
)
489 if not property.feature
.free
:
490 feature
.validate_value_string (property.feature
, property.value
)
493 ###################################################################
495 # Original lines are prefixed with "# "
498 # import utility : ungrist ;
499 # import sequence : unique ;
500 # import errors : error ;
511 # rule validate-property-sets ( property-sets * )
513 # for local s in $(property-sets)
515 # validate [ feature.split $(s) ] ;
520 def remove(attributes
, properties
):
521 """Returns a property sets which include all the elements
522 in 'properties' that do not have attributes listed in 'attributes'."""
523 if isinstance(attributes
, basestring
):
524 attributes
= [attributes
]
525 assert is_iterable_typed(attributes
, basestring
)
526 assert is_iterable_typed(properties
, basestring
)
529 attributes_new
= feature
.attributes(get_grist(e
))
530 has_common_features
= 0
531 for a
in attributes_new
:
533 has_common_features
= 1
536 if not has_common_features
:
542 def take(attributes
, properties
):
543 """Returns a property set which include all
544 properties in 'properties' that have any of 'attributes'."""
545 assert is_iterable_typed(attributes
, basestring
)
546 assert is_iterable_typed(properties
, basestring
)
549 if b2
.util
.set.intersection(attributes
, feature
.attributes(get_grist(e
))):
553 def translate_dependencies(properties
, project_id
, location
):
554 assert is_iterable_typed(properties
, Property
)
555 assert isinstance(project_id
, basestring
)
556 assert isinstance(location
, basestring
)
560 if not p
.feature
.dependency
:
564 m
= re
.match("(.*)//(.*)", v
)
568 # Either project id or absolute Linux path, do nothing.
571 rooted
= os
.path
.join(os
.getcwd(), location
, rooted
)
573 result
.append(Property(p
.feature
, rooted
+ "//" + m
.group(2), p
.condition
))
575 elif os
.path
.isabs(v
):
578 result
.append(Property(p
.feature
, project_id
+ "//" + v
, p
.condition
))
584 """ Class which maintains a property set -> string mapping.
587 self
.__properties
= []
590 def insert (self
, properties
, value
):
591 """ Associate value with properties.
593 assert is_iterable_typed(properties
, basestring
)
594 assert isinstance(value
, basestring
)
595 self
.__properties
.append(properties
)
596 self
.__values
.append(value
)
598 def find (self
, properties
):
599 """ Return the value associated with properties
600 or any subset of it. If more than one
601 subset has value assigned to it, return the
602 value for the longest subset, if it's unique.
604 assert is_iterable_typed(properties
, basestring
)
605 return self
.find_replace (properties
)
607 def find_replace(self
, properties
, value
=None):
608 assert is_iterable_typed(properties
, basestring
)
609 assert isinstance(value
, (basestring
, type(None)))
613 for i
in range(0, len(self
.__properties
)):
614 p
= self
.__properties
[i
]
616 if b2
.util
.set.contains (p
, properties
):
618 match_ranks
.append(len(p
))
620 best
= sequence
.select_highest_ranked (matches
, match_ranks
)
626 raise NoBestMatchingAlternative ()
630 original
= self
.__values
[best
]
633 self
.__values
[best
] = value
637 # local rule __test__ ( )
639 # import errors : try catch ;
641 # import feature : feature subfeature compose ;
643 # # local rules must be explicitly re-imported
644 # import property : path-order ;
646 # feature.prepare-test property-test-temp ;
648 # feature toolset : gcc : implicit symmetric ;
649 # subfeature toolset gcc : version : 2.95.2 2.95.3 2.95.4
650 # 3.0 3.0.1 3.0.2 : optional ;
651 # feature define : : free ;
652 # feature runtime-link : dynamic static : symmetric link-incompatible ;
653 # feature optimization : on off ;
654 # feature variant : debug release : implicit composite symmetric ;
655 # feature rtti : on off : link-incompatible ;
657 # compose <variant>debug : <define>_DEBUG <optimization>off ;
658 # compose <variant>release : <define>NDEBUG <optimization>on ;
661 # import "class" : new ;
663 # validate <toolset>gcc <toolset>gcc-3.0.1 : $(test-space) ;
665 # assert.result <toolset>gcc <rtti>off <define>FOO
666 # : refine <toolset>gcc <rtti>off
671 # assert.result <toolset>gcc <optimization>on
672 # : refine <toolset>gcc <optimization>off
677 # assert.result <toolset>gcc <rtti>off
678 # : refine <toolset>gcc : <rtti>off : $(test-space)
681 # assert.result <toolset>gcc <rtti>off <rtti>off:<define>FOO
682 # : refine <toolset>gcc : <rtti>off <rtti>off:<define>FOO
686 # assert.result <toolset>gcc:<define>foo <toolset>gcc:<define>bar
687 # : refine <toolset>gcc:<define>foo : <toolset>gcc:<define>bar
691 # assert.result <define>MY_RELEASE
692 # : evaluate-conditionals-in-context
693 # <variant>release,<rtti>off:<define>MY_RELEASE
694 # : <toolset>gcc <variant>release <rtti>off
699 # validate <feature>value : $(test-space) ;
700 # catch "Invalid property '<feature>value': unknown feature 'feature'." ;
703 # validate <rtti>default : $(test-space) ;
704 # catch \"default\" is not a known value of feature <rtti> ;
706 # validate <define>WHATEVER : $(test-space) ;
709 # validate <rtti> : $(test-space) ;
710 # catch "Invalid property '<rtti>': No value specified for feature 'rtti'." ;
713 # validate value : $(test-space) ;
714 # catch "value" is not a value of an implicit feature ;
717 # assert.result <rtti>on
718 # : remove free implicit : <toolset>gcc <define>foo <rtti>on : $(test-space) ;
720 # assert.result <include>a
721 # : select include : <include>a <toolset>gcc ;
723 # assert.result <include>a
724 # : select include bar : <include>a <toolset>gcc ;
726 # assert.result <include>a <toolset>gcc
727 # : select include <bar> <toolset> : <include>a <toolset>gcc ;
729 # assert.result <toolset>kylix <include>a
730 # : change <toolset>gcc <include>a : <toolset> kylix ;
732 # # Test ordinary properties
734 # : split-conditional <toolset>gcc
737 # # Test properties with ":"
739 # : split-conditional <define>FOO=A::B
742 # # Test conditional feature
743 # assert.result <toolset>gcc,<toolset-gcc:version>3.0 <define>FOO
744 # : split-conditional <toolset>gcc,<toolset-gcc:version>3.0:<define>FOO
747 # feature.finish-test property-test-temp ;