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