1# Status: ported, except for unit tests. 2# Base revision: 64488 3# 4# Copyright 2001, 2002, 2003 Dave Abrahams 5# Copyright 2002, 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 10import re 11 12from b2.util import utility, bjam_signature 13import b2.util.set 14from b2.util.utility import add_grist, get_grist, ungrist, replace_grist, to_seq 15from b2.exceptions import * 16 17__re_split_subfeatures = re.compile ('<(.*):(.*)>') 18__re_no_hyphen = re.compile ('^([^:]+)$') 19__re_slash_or_backslash = re.compile (r'[\\/]') 20 21class Feature(object): 22 23 # Map from string attribute names to integers bit flags. 24 # This will be initialized after declaration of the class. 25 _attribute_name_to_integer = {} 26 27 def __init__(self, name, values, attributes): 28 self._name = name 29 self._values = values 30 self._default = None 31 self._attributes = 0 32 for a in attributes: 33 self._attributes = self._attributes | Feature._attribute_name_to_integer[a] 34 self._attributes_string_list = attributes 35 self._subfeatures = [] 36 self._parent = None 37 38 def name(self): 39 return self._name 40 41 def values(self): 42 return self._values 43 44 def add_values(self, values): 45 self._values.extend(values) 46 47 def attributes(self): 48 return self._attributes 49 50 def set_default(self, value): 51 self._default = value 52 53 def default(self): 54 return self._default 55 56 # FIXME: remove when we fully move to using classes for features/properties 57 def attributes_string_list(self): 58 return self._attributes_string_list 59 60 def subfeatures(self): 61 return self._subfeatures 62 63 def add_subfeature(self, name): 64 self._subfeatures.append(name) 65 66 def parent(self): 67 """For subfeatures, return pair of (parent_feature, value). 68 69 Value may be None if this subfeature is not specific to any 70 value of the parent feature. 71 """ 72 return self._parent 73 74 def set_parent(self, feature, value): 75 self._parent = (feature, value) 76 77 def __str__(self): 78 return self._name 79 80 81def reset (): 82 """ Clear the module state. This is mainly for testing purposes. 83 """ 84 global __all_attributes, __all_features, __implicit_features, __composite_properties 85 global __features_with_attributes, __subfeature_from_value, __all_top_features, __free_features 86 global __all_subfeatures 87 88 # The list with all attribute names. 89 __all_attributes = [ 'implicit', 90 'composite', 91 'optional', 92 'symmetric', 93 'free', 94 'incidental', 95 'path', 96 'dependency', 97 'propagated', 98 'link-incompatible', 99 'subfeature', 100 'order-sensitive' 101 ] 102 i = 1 103 for a in __all_attributes: 104 setattr(Feature, a.upper(), i) 105 Feature._attribute_name_to_integer[a] = i 106 def probe(self, flag=i): 107 return getattr(self, "_attributes") & flag 108 setattr(Feature, a.replace("-", "_"), probe) 109 i = i << 1 110 111 # A map containing all features. The key is the feature name. 112 # The value is an instance of Feature class. 113 __all_features = {} 114 115 # All non-subfeatures. 116 __all_top_features = [] 117 118 # Maps valus to the corresponding implicit feature 119 __implicit_features = {} 120 121 # A map containing all composite properties. The key is a Property instance, 122 # and the value is a list of Property instances 123 __composite_properties = {} 124 125 __features_with_attributes = {} 126 for attribute in __all_attributes: 127 __features_with_attributes [attribute] = [] 128 129 # Maps a value to the corresponding subfeature name. 130 __subfeature_from_value = {} 131 132 # All free features 133 __free_features = [] 134 135 __all_subfeatures = [] 136 137reset () 138 139def enumerate (): 140 """ Returns an iterator to the features map. 141 """ 142 return __all_features.iteritems () 143 144def get(name): 145 """Return the Feature instance for the specified name. 146 147 Throws if no feature by such name exists 148 """ 149 return __all_features[name] 150 151# FIXME: prepare-test/finish-test? 152 153@bjam_signature((["name"], ["values", "*"], ["attributes", "*"])) 154def feature (name, values, attributes = []): 155 """ Declares a new feature with the given name, values, and attributes. 156 name: the feature name 157 values: a sequence of the allowable values - may be extended later with feature.extend 158 attributes: a sequence of the feature's attributes (e.g. implicit, free, propagated, ...) 159 """ 160 __validate_feature_attributes (name, attributes) 161 162 feature = Feature(name, [], attributes) 163 __all_features[name] = feature 164 # Temporary measure while we have not fully moved from 'gristed strings' 165 __all_features["<" + name + ">"] = feature 166 167 for attribute in attributes: 168 __features_with_attributes [attribute].append (name) 169 170 name = add_grist(name) 171 172 if 'subfeature' in attributes: 173 __all_subfeatures.append(name) 174 else: 175 __all_top_features.append(feature) 176 177 extend (name, values) 178 179 # FIXME: why his is needed. 180 if 'free' in attributes: 181 __free_features.append (name) 182 183 return feature 184 185@bjam_signature((["feature"], ["value"])) 186def set_default (feature, value): 187 """ Sets the default value of the given feature, overriding any previous default. 188 feature: the name of the feature 189 value: the default value to assign 190 """ 191 f = __all_features[feature] 192 attributes = f.attributes() 193 bad_attribute = None 194 195 if attributes & Feature.FREE: 196 bad_attribute = "free" 197 elif attributes & Feature.OPTIONAL: 198 bad_attribute = "optional" 199 200 if bad_attribute: 201 raise InvalidValue ("%s property %s cannot have a default" % (bad_attribute, feature.name())) 202 203 if not value in f.values(): 204 raise InvalidValue ("The specified default value, '%s' is invalid.\n" % value + "allowed values are: %s" % f.values()) 205 206 f.set_default(value) 207 208def defaults(features): 209 """ Returns the default property values for the given features. 210 """ 211 # FIXME: should merge feature and property modules. 212 import property 213 214 result = [] 215 for f in features: 216 if not f.free() and not f.optional() and f.default(): 217 result.append(property.Property(f, f.default())) 218 219 return result 220 221def valid (names): 222 """ Returns true iff all elements of names are valid features. 223 """ 224 def valid_one (name): return __all_features.has_key (name) 225 226 if isinstance (names, str): 227 return valid_one (names) 228 else: 229 return all([ valid_one (name) for name in names ]) 230 231def attributes (feature): 232 """ Returns the attributes of the given feature. 233 """ 234 return __all_features[feature].attributes_string_list() 235 236def values (feature): 237 """ Return the values of the given feature. 238 """ 239 validate_feature (feature) 240 return __all_features[feature].values() 241 242def is_implicit_value (value_string): 243 """ Returns true iff 'value_string' is a value_string 244 of an implicit feature. 245 """ 246 247 if __implicit_features.has_key(value_string): 248 return __implicit_features[value_string] 249 250 v = value_string.split('-') 251 252 if not __implicit_features.has_key(v[0]): 253 return False 254 255 feature = __implicit_features[v[0]] 256 257 for subvalue in (v[1:]): 258 if not __find_implied_subfeature(feature, subvalue, v[0]): 259 return False 260 261 return True 262 263def implied_feature (implicit_value): 264 """ Returns the implicit feature associated with the given implicit value. 265 """ 266 components = implicit_value.split('-') 267 268 if not __implicit_features.has_key(components[0]): 269 raise InvalidValue ("'%s' is not a value of an implicit feature" % implicit_value) 270 271 return __implicit_features[components[0]] 272 273def __find_implied_subfeature (feature, subvalue, value_string): 274 275 #if value_string == None: value_string = '' 276 277 if not __subfeature_from_value.has_key(feature) \ 278 or not __subfeature_from_value[feature].has_key(value_string) \ 279 or not __subfeature_from_value[feature][value_string].has_key (subvalue): 280 return None 281 282 return __subfeature_from_value[feature][value_string][subvalue] 283 284# Given a feature and a value of one of its subfeatures, find the name 285# of the subfeature. If value-string is supplied, looks for implied 286# subfeatures that are specific to that value of feature 287# feature # The main feature name 288# subvalue # The value of one of its subfeatures 289# value-string # The value of the main feature 290 291def implied_subfeature (feature, subvalue, value_string): 292 result = __find_implied_subfeature (feature, subvalue, value_string) 293 if not result: 294 raise InvalidValue ("'%s' is not a known subfeature value of '%s%s'" % (subvalue, feature, value_string)) 295 296 return result 297 298def validate_feature (name): 299 """ Checks if all name is a valid feature. Otherwise, raises an exception. 300 """ 301 if not __all_features.has_key(name): 302 raise InvalidFeature ("'%s' is not a valid feature name" % name) 303 else: 304 return __all_features[name] 305 306def valid (names): 307 """ Returns true iff all elements of names are valid features. 308 """ 309 def valid_one (name): return __all_features.has_key (name) 310 311 if isinstance (names, str): 312 return valid_one (names) 313 else: 314 return [ valid_one (name) for name in names ] 315 316# Uses Property 317def __expand_subfeatures_aux (property, dont_validate = False): 318 """ Helper for expand_subfeatures. 319 Given a feature and value, or just a value corresponding to an 320 implicit feature, returns a property set consisting of all component 321 subfeatures and their values. For example: 322 323 expand_subfeatures <toolset>gcc-2.95.2-linux-x86 324 -> <toolset>gcc <toolset-version>2.95.2 <toolset-os>linux <toolset-cpu>x86 325 equivalent to: 326 expand_subfeatures gcc-2.95.2-linux-x86 327 328 feature: The name of the feature, or empty if value corresponds to an implicit property 329 value: The value of the feature. 330 dont_validate: If True, no validation of value string will be done. 331 """ 332 f = property.feature() 333 v = property.value() 334 if not dont_validate: 335 validate_value_string(f, v) 336 337 components = v.split ("-") 338 339 v = components[0] 340 341 import property 342 343 result = [property.Property(f, components[0])] 344 345 subvalues = components[1:] 346 347 while len(subvalues) > 0: 348 subvalue = subvalues [0] # pop the head off of subvalues 349 subvalues = subvalues [1:] 350 351 subfeature = __find_implied_subfeature (f, subvalue, v) 352 353 # If no subfeature was found, reconstitute the value string and use that 354 if not subfeature: 355 return [property.Property(f, '-'.join(components))] 356 357 result.append(property.Property(subfeature, subvalue)) 358 359 return result 360 361def expand_subfeatures(properties, dont_validate = False): 362 """ 363 Make all elements of properties corresponding to implicit features 364 explicit, and express all subfeature values as separate properties 365 in their own right. For example, the property 366 367 gcc-2.95.2-linux-x86 368 369 might expand to 370 371 <toolset>gcc <toolset-version>2.95.2 <toolset-os>linux <toolset-cpu>x86 372 373 properties: A sequence with elements of the form 374 <feature>value-string or just value-string in the 375 case of implicit features. 376 : dont_validate: If True, no validation of value string will be done. 377 """ 378 result = [] 379 for p in properties: 380 # Don't expand subfeatures in subfeatures 381 if p.feature().subfeature(): 382 result.append (p) 383 else: 384 result.extend(__expand_subfeatures_aux (p, dont_validate)) 385 386 return result 387 388 389 390# rule extend was defined as below: 391 # Can be called three ways: 392 # 393 # 1. extend feature : values * 394 # 2. extend <feature> subfeature : values * 395 # 3. extend <feature>value-string subfeature : values * 396 # 397 # * Form 1 adds the given values to the given feature 398 # * Forms 2 and 3 add subfeature values to the given feature 399 # * Form 3 adds the subfeature values as specific to the given 400 # property value-string. 401 # 402 #rule extend ( feature-or-property subfeature ? : values * ) 403# 404# Now, the specific rule must be called, depending on the desired operation: 405# extend_feature 406# extend_subfeature 407 408def extend (name, values): 409 """ Adds the given values to the given feature. 410 """ 411 name = add_grist (name) 412 __validate_feature (name) 413 feature = __all_features [name] 414 415 if feature.implicit(): 416 for v in values: 417 if __implicit_features.has_key(v): 418 raise BaseException ("'%s' is already associated with the feature '%s'" % (v, __implicit_features [v])) 419 420 __implicit_features[v] = feature 421 422 if len (feature.values()) == 0 and len (values) > 0: 423 # This is the first value specified for this feature, 424 # take it as default value 425 feature.set_default(values[0]) 426 427 feature.add_values(values) 428 429def validate_value_string (f, value_string): 430 """ Checks that value-string is a valid value-string for the given feature. 431 """ 432 if f.free() or value_string in f.values(): 433 return 434 435 values = [value_string] 436 437 if f.subfeatures(): 438 if not value_string in f.values() and \ 439 not value_string in f.subfeatures(): 440 values = value_string.split('-') 441 442 # An empty value is allowed for optional features 443 if not values[0] in f.values() and \ 444 (values[0] or not f.optional()): 445 raise InvalidValue ("'%s' is not a known value of feature '%s'\nlegal values: '%s'" % (values [0], f.name(), f.values())) 446 447 for v in values [1:]: 448 # this will validate any subfeature values in value-string 449 implied_subfeature(f, v, values[0]) 450 451 452""" Extends the given subfeature with the subvalues. If the optional 453 value-string is provided, the subvalues are only valid for the given 454 value of the feature. Thus, you could say that 455 <target-platform>mingw is specifc to <toolset>gcc-2.95.2 as follows: 456 457 extend-subfeature toolset gcc-2.95.2 : target-platform : mingw ; 458 459 feature: The feature whose subfeature is being extended. 460 461 value-string: If supplied, specifies a specific value of the 462 main feature for which the new subfeature values 463 are valid. 464 465 subfeature: The name of the subfeature. 466 467 subvalues: The additional values of the subfeature being defined. 468""" 469def extend_subfeature (feature_name, value_string, subfeature_name, subvalues): 470 471 feature = validate_feature(feature_name) 472 473 if value_string: 474 validate_value_string(feature, value_string) 475 476 subfeature_name = feature_name + '-' + __get_subfeature_name (subfeature_name, value_string) 477 478 extend(subfeature_name, subvalues) ; 479 subfeature = __all_features[subfeature_name] 480 481 if value_string == None: value_string = '' 482 483 if not __subfeature_from_value.has_key(feature): 484 __subfeature_from_value [feature] = {} 485 486 if not __subfeature_from_value[feature].has_key(value_string): 487 __subfeature_from_value [feature][value_string] = {} 488 489 for subvalue in subvalues: 490 __subfeature_from_value [feature][value_string][subvalue] = subfeature 491 492@bjam_signature((["feature_name", "value_string", "?"], ["subfeature"], 493 ["subvalues", "*"], ["attributes", "*"])) 494def subfeature (feature_name, value_string, subfeature, subvalues, attributes = []): 495 """ Declares a subfeature. 496 feature_name: Root feature that is not a subfeature. 497 value_string: An optional value-string specifying which feature or 498 subfeature values this subfeature is specific to, 499 if any. 500 subfeature: The name of the subfeature being declared. 501 subvalues: The allowed values of this subfeature. 502 attributes: The attributes of the subfeature. 503 """ 504 parent_feature = validate_feature (feature_name) 505 506 # Add grist to the subfeature name if a value-string was supplied 507 subfeature_name = __get_subfeature_name (subfeature, value_string) 508 509 if subfeature_name in __all_features[feature_name].subfeatures(): 510 message = "'%s' already declared as a subfeature of '%s'" % (subfeature, feature_name) 511 message += " specific to '%s'" % value_string 512 raise BaseException (message) 513 514 # First declare the subfeature as a feature in its own right 515 f = feature (feature_name + '-' + subfeature_name, subvalues, attributes + ['subfeature']) 516 f.set_parent(parent_feature, value_string) 517 518 parent_feature.add_subfeature(f) 519 520 # Now make sure the subfeature values are known. 521 extend_subfeature (feature_name, value_string, subfeature, subvalues) 522 523 524@bjam_signature((["composite_property_s"], ["component_properties_s", "*"])) 525def compose (composite_property_s, component_properties_s): 526 """ Sets the components of the given composite property. 527 528 All parameters are <feature>value strings 529 """ 530 import property 531 532 component_properties_s = to_seq (component_properties_s) 533 composite_property = property.create_from_string(composite_property_s) 534 f = composite_property.feature() 535 536 if len(component_properties_s) > 0 and isinstance(component_properties_s[0], property.Property): 537 component_properties = component_properties_s 538 else: 539 component_properties = [property.create_from_string(p) for p in component_properties_s] 540 541 if not f.composite(): 542 raise BaseException ("'%s' is not a composite feature" % f) 543 544 if __composite_properties.has_key(property): 545 raise BaseException ('components of "%s" already set: %s' % (composite_property, str (__composite_properties[composite_property]))) 546 547 if composite_property in component_properties: 548 raise BaseException ('composite property "%s" cannot have itself as a component' % composite_property) 549 550 __composite_properties[composite_property] = component_properties 551 552 553def expand_composite(property): 554 result = [ property ] 555 if __composite_properties.has_key(property): 556 for p in __composite_properties[property]: 557 result.extend(expand_composite(p)) 558 return result 559 560@bjam_signature((['feature'], ['properties', '*'])) 561def get_values (feature, properties): 562 """ Returns all values of the given feature specified by the given property set. 563 """ 564 if feature[0] != '<': 565 feature = '<' + feature + '>' 566 result = [] 567 for p in properties: 568 if get_grist (p) == feature: 569 result.append (replace_grist (p, '')) 570 571 return result 572 573def free_features (): 574 """ Returns all free features. 575 """ 576 return __free_features 577 578def expand_composites (properties): 579 """ Expand all composite properties in the set so that all components 580 are explicitly expressed. 581 """ 582 explicit_features = set(p.feature() for p in properties) 583 584 result = [] 585 586 # now expand composite features 587 for p in properties: 588 expanded = expand_composite(p) 589 590 for x in expanded: 591 if not x in result: 592 f = x.feature() 593 594 if f.free(): 595 result.append (x) 596 elif not x in properties: # x is the result of expansion 597 if not f in explicit_features: # not explicitly-specified 598 if any(r.feature() == f for r in result): 599 raise FeatureConflict( 600 "expansions of composite features result in " 601 "conflicting values for '%s'\nvalues: '%s'\none contributing composite property was '%s'" % 602 (f.name(), [r.value() for r in result if r.feature() == f] + [x.value()], p)) 603 else: 604 result.append (x) 605 elif any(r.feature() == f for r in result): 606 raise FeatureConflict ("explicitly-specified values of non-free feature '%s' conflict\n" 607 "existing values: '%s'\nvalue from expanding '%s': '%s'" % (f, 608 [r.value() for r in result if r.feature() == f], p, x.value())) 609 else: 610 result.append (x) 611 612 return result 613 614# Uses Property 615def is_subfeature_of (parent_property, f): 616 """ Return true iff f is an ordinary subfeature of the parent_property's 617 feature, or if f is a subfeature of the parent_property's feature 618 specific to the parent_property's value. 619 """ 620 if not f.subfeature(): 621 return False 622 623 p = f.parent() 624 if not p: 625 return False 626 627 parent_feature = p[0] 628 parent_value = p[1] 629 630 if parent_feature != parent_property.feature(): 631 return False 632 633 if parent_value and parent_value != parent_property.value(): 634 return False 635 636 return True 637 638def __is_subproperty_of (parent_property, p): 639 """ As is_subfeature_of, for subproperties. 640 """ 641 return is_subfeature_of (parent_property, p.feature()) 642 643 644# Returns true iff the subvalue is valid for the feature. When the 645# optional value-string is provided, returns true iff the subvalues 646# are valid for the given value of the feature. 647def is_subvalue(feature, value_string, subfeature, subvalue): 648 649 if not value_string: 650 value_string = '' 651 652 if not __subfeature_from_value.has_key(feature): 653 return False 654 655 if not __subfeature_from_value[feature].has_key(value_string): 656 return False 657 658 if not __subfeature_from_value[feature][value_string].has_key(subvalue): 659 return False 660 661 if __subfeature_from_value[feature][value_string][subvalue]\ 662 != subfeature: 663 return False 664 665 return True 666 667def implied_subfeature (feature, subvalue, value_string): 668 result = __find_implied_subfeature (feature, subvalue, value_string) 669 if not result: 670 raise InvalidValue ("'%s' is not a known subfeature value of '%s%s'" % (subvalue, feature, value_string)) 671 672 return result 673 674 675# Uses Property 676def expand (properties): 677 """ Given a property set which may consist of composite and implicit 678 properties and combined subfeature values, returns an expanded, 679 normalized property set with all implicit features expressed 680 explicitly, all subfeature values individually expressed, and all 681 components of composite properties expanded. Non-free features 682 directly expressed in the input properties cause any values of 683 those features due to composite feature expansion to be dropped. If 684 two values of a given non-free feature are directly expressed in the 685 input, an error is issued. 686 """ 687 expanded = expand_subfeatures(properties) 688 return expand_composites (expanded) 689 690# Accepts list of Property objects 691def add_defaults (properties): 692 """ Given a set of properties, add default values for features not 693 represented in the set. 694 Note: if there's there's ordinary feature F1 and composite feature 695 F2, which includes some value for F1, and both feature have default values, 696 then the default value of F1 will be added, not the value in F2. This might 697 not be right idea: consider 698 699 feature variant : debug ... ; 700 <variant>debug : .... <runtime-debugging>on 701 feature <runtime-debugging> : off on ; 702 703 Here, when adding default for an empty property set, we'll get 704 705 <variant>debug <runtime_debugging>off 706 707 and that's kind of strange. 708 """ 709 result = [x for x in properties] 710 711 handled_features = set() 712 for p in properties: 713 # We don't add default for conditional properties. We don't want 714 # <variant>debug:<define>DEBUG to be takes as specified value for <variant> 715 if not p.condition(): 716 handled_features.add(p.feature()) 717 718 missing_top = [f for f in __all_top_features if not f in handled_features] 719 more = defaults(missing_top) 720 result.extend(more) 721 for p in more: 722 handled_features.add(p.feature()) 723 724 # Add defaults for subfeatures of features which are present 725 for p in result[:]: 726 s = p.feature().subfeatures() 727 more = defaults([s for s in p.feature().subfeatures() if not s in handled_features]) 728 for p in more: 729 handled_features.add(p.feature()) 730 result.extend(more) 731 732 return result 733 734def minimize (properties): 735 """ Given an expanded property set, eliminate all redundancy: properties 736 which are elements of other (composite) properties in the set will 737 be eliminated. Non-symmetric properties equal to default values will be 738 eliminated, unless the override a value from some composite property. 739 Implicit properties will be expressed without feature 740 grist, and sub-property values will be expressed as elements joined 741 to the corresponding main property. 742 """ 743 744 # remove properties implied by composite features 745 components = [] 746 for property in properties: 747 if __composite_properties.has_key (property): 748 components.extend(__composite_properties[property]) 749 properties = b2.util.set.difference (properties, components) 750 751 # handle subfeatures and implicit features 752 753 # move subfeatures to the end of the list 754 properties = [p for p in properties if not p.feature().subfeature()] +\ 755 [p for p in properties if p.feature().subfeature()] 756 757 result = [] 758 while properties: 759 p = properties[0] 760 f = p.feature() 761 762 # locate all subproperties of $(x[1]) in the property set 763 subproperties = __select_subproperties (p, properties) 764 765 if subproperties: 766 # reconstitute the joined property name 767 subproperties.sort () 768 joined = b2.build.property.Property(p.feature(), p.value() + '-' + '-'.join ([sp.value() for sp in subproperties])) 769 result.append(joined) 770 771 properties = b2.util.set.difference(properties[1:], subproperties) 772 773 else: 774 # eliminate properties whose value is equal to feature's 775 # default and which are not symmetric and which do not 776 # contradict values implied by composite properties. 777 778 # since all component properties of composites in the set 779 # have been eliminated, any remaining property whose 780 # feature is the same as a component of a composite in the 781 # set must have a non-redundant value. 782 if p.value() != f.default() or f.symmetric(): 783 result.append (p) 784 #\ 785 #or get_grist (fullp) in get_grist (components): 786 # FIXME: restore above 787 788 789 properties = properties[1:] 790 791 return result 792 793 794def split (properties): 795 """ Given a property-set of the form 796 v1/v2/...vN-1/<fN>vN/<fN+1>vN+1/...<fM>vM 797 798 Returns 799 v1 v2 ... vN-1 <fN>vN <fN+1>vN+1 ... <fM>vM 800 801 Note that vN...vM may contain slashes. This is resilient to the 802 substitution of backslashes for slashes, since Jam, unbidden, 803 sometimes swaps slash direction on NT. 804 """ 805 806 def split_one (properties): 807 pieces = re.split (__re_slash_or_backslash, properties) 808 result = [] 809 810 for x in pieces: 811 if not get_grist (x) and len (result) > 0 and get_grist (result [-1]): 812 result = result [0:-1] + [ result [-1] + '/' + x ] 813 else: 814 result.append (x) 815 816 return result 817 818 if isinstance (properties, str): 819 return split_one (properties) 820 821 result = [] 822 for p in properties: 823 result += split_one (p) 824 return result 825 826 827def compress_subproperties (properties): 828 """ Combine all subproperties into their parent properties 829 830 Requires: for every subproperty, there is a parent property. All 831 features are explicitly expressed. 832 833 This rule probably shouldn't be needed, but 834 build-request.expand-no-defaults is being abused for unintended 835 purposes and it needs help 836 """ 837 result = [] 838 matched_subs = set() 839 all_subs = set() 840 for p in properties: 841 f = p.feature() 842 843 if not f.subfeature(): 844 subs = __select_subproperties (p, properties) 845 if subs: 846 847 matched_subs.update(subs) 848 849 subvalues = '-'.join (sub.value() for sub in subs) 850 result.append(b2.build.property.Property( 851 p.feature(), p.value() + '-' + subvalues, 852 p.condition())) 853 else: 854 result.append(p) 855 856 else: 857 all_subs.add(p) 858 859 # TODO: this variables are used just for debugging. What's the overhead? 860 assert all_subs == matched_subs 861 862 return result 863 864###################################################################################### 865# Private methods 866 867def __select_subproperties (parent_property, properties): 868 return [ x for x in properties if __is_subproperty_of (parent_property, x) ] 869 870def __get_subfeature_name (subfeature, value_string): 871 if value_string == None: 872 prefix = '' 873 else: 874 prefix = value_string + ':' 875 876 return prefix + subfeature 877 878 879def __validate_feature_attributes (name, attributes): 880 for attribute in attributes: 881 if not attribute in __all_attributes: 882 raise InvalidAttribute ("unknown attributes: '%s' in feature declaration: '%s'" % (str (b2.util.set.difference (attributes, __all_attributes)), name)) 883 884 if name in __all_features: 885 raise AlreadyDefined ("feature '%s' already defined" % name) 886 elif 'implicit' in attributes and 'free' in attributes: 887 raise InvalidAttribute ("free features cannot also be implicit (in declaration of feature '%s')" % name) 888 elif 'free' in attributes and 'propagated' in attributes: 889 raise InvalidAttribute ("free features cannot also be propagated (in declaration of feature '%s')" % name) 890 891 892def __validate_feature (feature): 893 """ Generates an error if the feature is unknown. 894 """ 895 if not __all_features.has_key (feature): 896 raise BaseException ('unknown feature "%s"' % feature) 897 898 899def __select_subfeatures (parent_property, features): 900 """ Given a property, return the subset of features consisting of all 901 ordinary subfeatures of the property's feature, and all specific 902 subfeatures of the property's feature which are conditional on the 903 property's value. 904 """ 905 return [f for f in features if is_subfeature_of (parent_property, f)] 906 907# FIXME: copy over tests. 908