]> git.proxmox.com Git - ceph.git/blob - ceph/src/boost/tools/build/src/build/property.py
import new upstream nautilus stable release 14.2.8
[ceph.git] / ceph / src / boost / tools / build / src / build / property.py
1 # Status: ported, except for tests.
2 # Base revision: 64070
3 #
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)
9
10 import re
11 import sys
12 from functools import total_ordering
13
14 from b2.util.utility import *
15 from b2.build import feature
16 from b2.util import sequence, qualify_jam_action, is_iterable_typed
17 import b2.util.set
18 from b2.manager import get_manager
19
20
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'(.*):(<.*)')
28
29 _not_applicable_feature='not-applicable-in-this-context'
30 feature.feature(_not_applicable_feature, [], ['free'])
31
32 __abbreviated_paths = False
33
34
35 class PropertyMeta(type):
36 """
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
42 the Property class.
43
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.
47 """
48 _registry = {}
49 current_id = 1
50
51 def __call__(mcs, f, value, condition=None):
52 """
53 This intercepts the call to the Property() constructor.
54
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.
57 """
58 from b2.build.feature import Feature
59 if not isinstance(f, Feature):
60 f = feature.get(f)
61 if condition is None:
62 condition = []
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]
68
69 @staticmethod
70 def check(obj):
71 return (hasattr(obj, 'feature') and
72 hasattr(obj, 'value') and
73 hasattr(obj, 'condition'))
74
75 def __instancecheck__(self, instance):
76 return self.check(instance)
77
78 def __subclasscheck__(self, subclass):
79 return self.check(subclass)
80
81
82 @total_ordering
83 class Property(object):
84
85 __slots__ = ('feature', 'value', 'condition', '_to_raw', '_hash', 'id')
86 __metaclass__ = PropertyMeta
87
88 def __init__(self, f, value, condition=None):
89 assert(f.free or ':' not in value)
90 if condition is None:
91 condition = []
92
93 self.feature = f
94 self.value = 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
104
105 condition_str = ''
106 if condition:
107 condition_str = ",".join(str(p) for p in self.condition) + ':'
108
109 self._to_raw = '{}<{}>{}'.format(condition_str, f.name, value)
110
111 def to_raw(self):
112 return self._to_raw
113
114 def __str__(self):
115
116 return self._to_raw
117
118 def __hash__(self):
119 return self._hash
120
121 def __eq__(self, other):
122 return self._hash == other._hash
123
124 def __lt__(self, other):
125 return (self.feature.name, self.value) < (other.feature.name, other.value)
126
127
128 @total_ordering
129 class LazyProperty(object):
130 def __init__(self, feature_name, value, condition=None):
131 if condition is None:
132 condition = []
133
134 self.__property = Property(
135 feature.get(_not_applicable_feature), feature_name + value, condition=condition)
136 self.__name = feature_name
137 self.__value = value
138 self.__condition = condition
139 self.__feature = None
140
141 def __getattr__(self, item):
142 if self.__feature is None:
143 try:
144 self.__feature = feature.get(self.__name)
145 self.__property = Property(self.__feature, self.__value, self.__condition)
146 except KeyError:
147 pass
148 return getattr(self.__property, item)
149
150 def __hash__(self):
151 return hash(self.__property)
152
153 def __str__(self):
154 return self.__property._to_raw
155
156 def __eq__(self, other):
157 return self.__property == other
158
159 def __lt__(self, other):
160 return (self.feature.name, self.value) < (other.feature.name, other.value)
161
162
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)
167 condition = []
168 import types
169 if not isinstance(s, types.StringType):
170 print type(s)
171 if __re_has_condition.search(s):
172
173 if not allow_condition:
174 raise BaseException("Conditional property is not allowed in this context")
175
176 m = __re_separate_condition_and_property.match(s)
177 condition = m.group(1)
178 s = m.group(2)
179
180 # FIXME: break dependency cycle
181 from b2.manager import get_manager
182
183 if condition:
184 condition = [create_from_string(x) for x in condition.split(',')]
185
186 feature_name = get_grist(s)
187 if not feature_name:
188 if feature.is_implicit_value(s):
189 f = feature.implied_feature(s)
190 value = s
191 p = Property(f, value, condition=condition)
192 else:
193 raise get_manager().errors()("Invalid property '%s' -- unknown feature" % s)
194 else:
195 value = get_value(s)
196 if not value and not allow_missing_value:
197 get_manager().errors()("Invalid property '%s' -- no value specified" % s)
198
199 if feature.valid(feature_name):
200 p = Property(feature.get(feature_name), value, condition=condition)
201 else:
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.
205 #
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.
209 #
210 # The underlying cause for this problem is that python port Property
211 # is more strict than its Jam counterpart and must always reference
212 # a valid feature.
213 p = LazyProperty(feature_name, value, condition=condition)
214
215 return p
216
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]
220
221 def reset ():
222 """ Clear the module state. This is mainly for testing purposes.
223 """
224 global __results
225
226 # A cache of results from as_path
227 __results = {}
228
229 reset ()
230
231
232 def set_abbreviated_paths(on=True):
233 global __abbreviated_paths
234 if on == 'off':
235 on = False
236 on = bool(on)
237 __abbreviated_paths = on
238
239
240 def get_abbreviated_paths():
241 return __abbreviated_paths or '--abbreviated-paths' in sys.argv
242
243
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
247 name.
248 """
249 if x == y:
250 return 0
251
252 xg = get_grist (x)
253 yg = get_grist (y)
254
255 if yg and not xg:
256 return -1
257
258 elif xg and not yg:
259 return 1
260
261 else:
262 if not xg:
263 x = feature.expand_subfeatures([x])
264 y = feature.expand_subfeatures([y])
265
266 if x < y:
267 return -1
268 elif x > y:
269 return 1
270 else:
271 return 0
272
273 def identify(string):
274 return string
275
276 # Uses Property
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.
282 """
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
286 result = set()
287
288 # Records all requirements.
289 required = {}
290
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.
295 if not r.condition:
296 required[r.feature] = r
297
298 for p in properties:
299 # Skip conditional properties
300 if p.condition:
301 result.add(p)
302 # No processing for free properties
303 elif p.feature.free:
304 result.add(p)
305 else:
306 if p.feature in required:
307 result.add(required[p.feature])
308 else:
309 result.add(p)
310
311 return sequence.unique(list(result) + requirements)
312
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.
317 """
318 assert is_iterable_typed(properties, Property)
319 result = []
320
321 for p in properties:
322
323 if p.feature.path:
324 values = __re_two_ampersands.split(p.value)
325
326 new_value = "&&".join(os.path.normpath(os.path.join(path, v)) for v in values)
327
328 if new_value != p.value:
329 result.append(Property(p.feature, new_value, p.condition))
330 else:
331 result.append(p)
332
333 else:
334 result.append (p)
335
336 return result
337
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)
345 result = []
346 for p in properties:
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))
351 else:
352 result.append(p)
353
354 return result
355
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.
359 """
360 if isinstance(properties, Property):
361 properties = [properties]
362 assert is_iterable_typed(properties, Property)
363 for p in properties:
364 __validate1(p)
365
366 def expand_subfeatures_in_conditions (properties):
367 assert is_iterable_typed(properties, Property)
368 result = []
369 for p in properties:
370
371 if not p.condition:
372 result.append(p)
373 else:
374 expanded = []
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))
381
382 # we need to keep LazyProperties lazy
383 if isinstance(p, LazyProperty):
384 value = p.value
385 feature_name = get_grist(value)
386 value = value.replace(feature_name, '')
387 result.append(LazyProperty(feature_name, value, condition=expanded))
388 else:
389 result.append(Property(p.feature, p.value, expanded))
390
391 return result
392
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.
400 """
401 assert isinstance(property, basestring)
402 m = __re_split_conditional.match (property)
403
404 if m:
405 return (m.group (1), '<' + m.group (2))
406
407 return None
408
409
410 def select (features, properties):
411 """ Selects properties which correspond to any of the given features.
412 """
413 assert is_iterable_typed(properties, basestring)
414 result = []
415
416 # add any missing angle brackets
417 features = add_grist (features)
418
419 return [p for p in properties if get_grist(p) in features]
420
421 def validate_property_sets (sets):
422 if __debug__:
423 from .property_set import PropertySet
424 assert is_iterable_typed(sets, PropertySet)
425 for s in sets:
426 validate(s.all())
427
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'
432 """
433 if __debug__:
434 from .property_set import PropertySet
435 assert is_iterable_typed(properties, Property)
436 assert isinstance(context, PropertySet)
437 base = []
438 conditional = []
439
440 for p in properties:
441 if p.condition:
442 conditional.append (p)
443 else:
444 base.append (p)
445
446 result = base[:]
447 for p in conditional:
448
449 # Evaluate condition
450 # FIXME: probably inefficient
451 if all(x in context for x in p.condition):
452 result.append(Property(p.feature, p.value))
453
454 return result
455
456
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.
461 """
462 assert is_iterable_typed(properties, basestring)
463 assert isinstance(feature, basestring)
464 assert isinstance(value, (basestring, type(None)))
465 result = []
466
467 feature = add_grist (feature)
468
469 for p in properties:
470 if get_grist (p) == feature:
471 if value:
472 result.append (replace_grist (value, feature))
473
474 else:
475 result.append (p)
476
477 return result
478
479
480 ################################################################
481 # Private functions
482
483 def __validate1 (property):
484 """ Exit with error if property is not valid.
485 """
486 assert isinstance(property, Property)
487 msg = None
488
489 if not property.feature.free:
490 feature.validate_value_string (property.feature, property.value)
491
492
493 ###################################################################
494 # Still to port.
495 # Original lines are prefixed with "# "
496 #
497 #
498 # import utility : ungrist ;
499 # import sequence : unique ;
500 # import errors : error ;
501 # import feature ;
502 # import regex ;
503 # import sequence ;
504 # import set ;
505 # import path ;
506 # import assert ;
507 #
508 #
509
510
511 # rule validate-property-sets ( property-sets * )
512 # {
513 # for local s in $(property-sets)
514 # {
515 # validate [ feature.split $(s) ] ;
516 # }
517 # }
518 #
519
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)
527 result = []
528 for e in properties:
529 attributes_new = feature.attributes(get_grist(e))
530 has_common_features = 0
531 for a in attributes_new:
532 if a in attributes:
533 has_common_features = 1
534 break
535
536 if not has_common_features:
537 result += e
538
539 return result
540
541
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)
547 result = []
548 for e in properties:
549 if b2.util.set.intersection(attributes, feature.attributes(get_grist(e))):
550 result.append(e)
551 return result
552
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)
557 result = []
558 for p in properties:
559
560 if not p.feature.dependency:
561 result.append(p)
562 else:
563 v = p.value
564 m = re.match("(.*)//(.*)", v)
565 if m:
566 rooted = m.group(1)
567 if rooted[0] == '/':
568 # Either project id or absolute Linux path, do nothing.
569 pass
570 else:
571 rooted = os.path.join(os.getcwd(), location, rooted)
572
573 result.append(Property(p.feature, rooted + "//" + m.group(2), p.condition))
574
575 elif os.path.isabs(v):
576 result.append(p)
577 else:
578 result.append(Property(p.feature, project_id + "//" + v, p.condition))
579
580 return result
581
582
583 class PropertyMap:
584 """ Class which maintains a property set -> string mapping.
585 """
586 def __init__ (self):
587 self.__properties = []
588 self.__values = []
589
590 def insert (self, properties, value):
591 """ Associate value with properties.
592 """
593 assert is_iterable_typed(properties, basestring)
594 assert isinstance(value, basestring)
595 self.__properties.append(properties)
596 self.__values.append(value)
597
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.
603 """
604 assert is_iterable_typed(properties, basestring)
605 return self.find_replace (properties)
606
607 def find_replace(self, properties, value=None):
608 assert is_iterable_typed(properties, basestring)
609 assert isinstance(value, (basestring, type(None)))
610 matches = []
611 match_ranks = []
612
613 for i in range(0, len(self.__properties)):
614 p = self.__properties[i]
615
616 if b2.util.set.contains (p, properties):
617 matches.append (i)
618 match_ranks.append(len(p))
619
620 best = sequence.select_highest_ranked (matches, match_ranks)
621
622 if not best:
623 return None
624
625 if len (best) > 1:
626 raise NoBestMatchingAlternative ()
627
628 best = best [0]
629
630 original = self.__values[best]
631
632 if value:
633 self.__values[best] = value
634
635 return original
636
637 # local rule __test__ ( )
638 # {
639 # import errors : try catch ;
640 # import feature ;
641 # import feature : feature subfeature compose ;
642 #
643 # # local rules must be explicitly re-imported
644 # import property : path-order ;
645 #
646 # feature.prepare-test property-test-temp ;
647 #
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 ;
656 #
657 # compose <variant>debug : <define>_DEBUG <optimization>off ;
658 # compose <variant>release : <define>NDEBUG <optimization>on ;
659 #
660 # import assert ;
661 # import "class" : new ;
662 #
663 # validate <toolset>gcc <toolset>gcc-3.0.1 : $(test-space) ;
664 #
665 # assert.result <toolset>gcc <rtti>off <define>FOO
666 # : refine <toolset>gcc <rtti>off
667 # : <define>FOO
668 # : $(test-space)
669 # ;
670 #
671 # assert.result <toolset>gcc <optimization>on
672 # : refine <toolset>gcc <optimization>off
673 # : <optimization>on
674 # : $(test-space)
675 # ;
676 #
677 # assert.result <toolset>gcc <rtti>off
678 # : refine <toolset>gcc : <rtti>off : $(test-space)
679 # ;
680 #
681 # assert.result <toolset>gcc <rtti>off <rtti>off:<define>FOO
682 # : refine <toolset>gcc : <rtti>off <rtti>off:<define>FOO
683 # : $(test-space)
684 # ;
685 #
686 # assert.result <toolset>gcc:<define>foo <toolset>gcc:<define>bar
687 # : refine <toolset>gcc:<define>foo : <toolset>gcc:<define>bar
688 # : $(test-space)
689 # ;
690 #
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
695 #
696 # ;
697 #
698 # try ;
699 # validate <feature>value : $(test-space) ;
700 # catch "Invalid property '<feature>value': unknown feature 'feature'." ;
701 #
702 # try ;
703 # validate <rtti>default : $(test-space) ;
704 # catch \"default\" is not a known value of feature <rtti> ;
705 #
706 # validate <define>WHATEVER : $(test-space) ;
707 #
708 # try ;
709 # validate <rtti> : $(test-space) ;
710 # catch "Invalid property '<rtti>': No value specified for feature 'rtti'." ;
711 #
712 # try ;
713 # validate value : $(test-space) ;
714 # catch "value" is not a value of an implicit feature ;
715 #
716 #
717 # assert.result <rtti>on
718 # : remove free implicit : <toolset>gcc <define>foo <rtti>on : $(test-space) ;
719 #
720 # assert.result <include>a
721 # : select include : <include>a <toolset>gcc ;
722 #
723 # assert.result <include>a
724 # : select include bar : <include>a <toolset>gcc ;
725 #
726 # assert.result <include>a <toolset>gcc
727 # : select include <bar> <toolset> : <include>a <toolset>gcc ;
728 #
729 # assert.result <toolset>kylix <include>a
730 # : change <toolset>gcc <include>a : <toolset> kylix ;
731 #
732 # # Test ordinary properties
733 # assert.result
734 # : split-conditional <toolset>gcc
735 # ;
736 #
737 # # Test properties with ":"
738 # assert.result
739 # : split-conditional <define>FOO=A::B
740 # ;
741 #
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
745 # ;
746 #
747 # feature.finish-test property-test-temp ;
748 # }
749 #
750