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