• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Status: ported.
2# Base revision: 40480
3
4#  Copyright (C) Vladimir Prus 2002. Permission to copy, use, modify, sell and
5#  distribute this software is granted provided this copyright notice appears in
6#  all copies. This software is provided "as is" without express or implied
7#  warranty, and with no claim as to its suitability for any purpose.
8
9import hashlib
10
11import bjam
12from b2.util.utility import *
13import property, feature
14import b2.build.feature
15from b2.exceptions import *
16from b2.build.property import get_abbreviated_paths
17from b2.util.sequence import unique
18from b2.util.set import difference
19from b2.util import cached, abbreviate_dashed, is_iterable_typed
20
21from b2.manager import get_manager
22
23
24def reset ():
25    """ Clear the module state. This is mainly for testing purposes.
26    """
27    global __cache
28
29    # A cache of property sets
30    # TODO: use a map of weak refs?
31    __cache = {}
32
33reset ()
34
35
36def create (raw_properties = []):
37    """ Creates a new 'PropertySet' instance for the given raw properties,
38        or returns an already existing one.
39    """
40    assert (is_iterable_typed(raw_properties, property.Property)
41            or is_iterable_typed(raw_properties, basestring))
42    # FIXME: propagate to callers.
43    if len(raw_properties) > 0 and isinstance(raw_properties[0], property.Property):
44        x = raw_properties
45    else:
46        x = [property.create_from_string(ps) for ps in raw_properties]
47
48    # These two lines of code are optimized to the current state
49    # of the Property class. Since this function acts as the caching
50    # frontend to the PropertySet class modifying these two lines
51    # could have a severe performance penalty. Be careful.
52    # It would be faster to sort by p.id, but some projects may rely
53    # on the fact that the properties are ordered alphabetically. So,
54    # we maintain alphabetical sorting so as to maintain backward compatibility.
55    x = sorted(set(x), key=lambda p: (p.feature.name, p.value, p.condition))
56    key = tuple(p.id for p in x)
57
58    if key not in __cache:
59        __cache [key] = PropertySet(x)
60
61    return __cache [key]
62
63def create_with_validation (raw_properties):
64    """ Creates new 'PropertySet' instances after checking
65        that all properties are valid and converting implicit
66        properties into gristed form.
67    """
68    assert is_iterable_typed(raw_properties, basestring)
69    properties = [property.create_from_string(s) for s in raw_properties]
70    property.validate(properties)
71
72    return create(properties)
73
74def empty ():
75    """ Returns PropertySet with empty set of properties.
76    """
77    return create ()
78
79def create_from_user_input(raw_properties, jamfile_module, location):
80    """Creates a property-set from the input given by the user, in the
81    context of 'jamfile-module' at 'location'"""
82    assert is_iterable_typed(raw_properties, basestring)
83    assert isinstance(jamfile_module, basestring)
84    assert isinstance(location, basestring)
85    properties = property.create_from_strings(raw_properties, True)
86    properties = property.translate_paths(properties, location)
87    properties = property.translate_indirect(properties, jamfile_module)
88
89    project_id = get_manager().projects().attributeDefault(jamfile_module, 'id', None)
90    if not project_id:
91        project_id = os.path.abspath(location)
92    properties = property.translate_dependencies(properties, project_id, location)
93    properties = property.expand_subfeatures_in_conditions(properties)
94    return create(properties)
95
96
97def refine_from_user_input(parent_requirements, specification, jamfile_module,
98                           location):
99    """Refines requirements with requirements provided by the user.
100    Specially handles "-<property>value" syntax in specification
101     to remove given requirements.
102     - parent-requirements -- property-set object with requirements
103       to refine
104     - specification -- string list of requirements provided by the use
105     - project-module -- the module to which context indirect features
106       will be bound.
107     - location -- the path to which path features are relative."""
108    assert isinstance(parent_requirements, PropertySet)
109    assert is_iterable_typed(specification, basestring)
110    assert isinstance(jamfile_module, basestring)
111    assert isinstance(location, basestring)
112
113    if not specification:
114        return parent_requirements
115
116
117    add_requirements = []
118    remove_requirements = []
119
120    for r in specification:
121        if r[0] == '-':
122            remove_requirements.append(r[1:])
123        else:
124            add_requirements.append(r)
125
126    if remove_requirements:
127        # Need to create property set, so that path features
128        # and indirect features are translated just like they
129        # are in project requirements.
130        ps = create_from_user_input(remove_requirements,
131                                    jamfile_module, location)
132
133        parent_requirements = create(difference(parent_requirements.all(),
134                                                ps.all()))
135        specification = add_requirements
136
137    requirements = create_from_user_input(specification,
138                                          jamfile_module, location)
139
140    return parent_requirements.refine(requirements)
141
142class PropertySet:
143    """ Class for storing a set of properties.
144        - there's 1<->1 correspondence between identity and value. No
145          two instances of the class are equal. To maintain this property,
146          the 'PropertySet.create' rule should be used to create new instances.
147          Instances are immutable.
148
149        - each property is classified with regard to it's effect on build
150          results. Incidental properties have no effect on build results, from
151          Boost.Build point of view. Others are either free, or non-free, which we
152          call 'base'. Each property belong to exactly one of those categories and
153          it's possible to get list of properties in each category.
154
155          In addition, it's possible to get list of properties with specific
156          attribute.
157
158        - several operations, like and refine and as_path are provided. They all use
159          caching whenever possible.
160    """
161    def __init__ (self, properties=None):
162        if properties is None:
163            properties = []
164        assert is_iterable_typed(properties, property.Property)
165
166        self.all_ = properties
167        self._all_set = {p.id for p in properties}
168
169        self.incidental_ = []
170        self.free_ = []
171        self.base_ = []
172        self.dependency_ = []
173        self.non_dependency_ = []
174        self.conditional_ = []
175        self.non_conditional_ = []
176        self.propagated_ = []
177        self.link_incompatible = []
178
179        # A cache of refined properties.
180        self.refined_ = {}
181
182        # A cache of property sets created by adding properties to this one.
183        self.added_ = {}
184
185        # Cache for the default properties.
186        self.defaults_ = None
187
188        # Cache for the expanded properties.
189        self.expanded_ = None
190
191        # Cache for the expanded composite properties
192        self.composites_ = None
193
194        # Cache for property set with expanded subfeatures
195        self.subfeatures_ = None
196
197        # Cache for the property set containing propagated properties.
198        self.propagated_ps_ = None
199
200        # A map of features to its values.
201        self.feature_map_ = None
202
203        # A tuple (target path, is relative to build directory)
204        self.target_path_ = None
205
206        self.as_path_ = None
207
208        # A cache for already evaluated sets.
209        self.evaluated_ = {}
210
211        # stores the list of LazyProperty instances.
212        # these are being kept separate from the normal
213        # Property instances so that when this PropertySet
214        # tries to return one of its attributes, it
215        # will then try to evaluate the LazyProperty instances
216        # first before returning.
217        self.lazy_properties = []
218
219        for p in properties:
220            f = p.feature
221            if isinstance(p, property.LazyProperty):
222                self.lazy_properties.append(p)
223            # A feature can be both incidental and free,
224            # in which case we add it to incidental.
225            elif f.incidental:
226                self.incidental_.append(p)
227            elif f.free:
228                self.free_.append(p)
229            else:
230                self.base_.append(p)
231
232            if p.condition:
233                self.conditional_.append(p)
234            else:
235                self.non_conditional_.append(p)
236
237            if f.dependency:
238                self.dependency_.append (p)
239            elif not isinstance(p, property.LazyProperty):
240                self.non_dependency_.append (p)
241
242            if f.propagated:
243                self.propagated_.append(p)
244            if f.link_incompatible:
245                self.link_incompatible.append(p)
246
247
248    def all(self):
249        return self.all_
250
251    def raw (self):
252        """ Returns the list of stored properties.
253        """
254        # create a new list due to the LazyProperties.
255        # this gives them a chance to evaluate to their
256        # true Property(). This approach is being
257        # taken since calculations should not be using
258        # PropertySet.raw()
259        return [p._to_raw for p in self.all_]
260
261    def __str__(self):
262        return ' '.join(p._to_raw for p in self.all_)
263
264    def base (self):
265        """ Returns properties that are neither incidental nor free.
266        """
267        result = [p for p in self.lazy_properties
268                  if not(p.feature.incidental or p.feature.free)]
269        result.extend(self.base_)
270        return result
271
272    def free (self):
273        """ Returns free properties which are not dependency properties.
274        """
275        result = [p for p in self.lazy_properties
276                  if not p.feature.incidental and p.feature.free]
277        result.extend(self.free_)
278        return result
279
280    def non_free(self):
281        return self.base() + self.incidental()
282
283    def dependency (self):
284        """ Returns dependency properties.
285        """
286        result = [p for p in self.lazy_properties if p.feature.dependency]
287        result.extend(self.dependency_)
288        return self.dependency_
289
290    def non_dependency (self):
291        """ Returns properties that are not dependencies.
292        """
293        result = [p for p in self.lazy_properties if not p.feature.dependency]
294        result.extend(self.non_dependency_)
295        return result
296
297    def conditional (self):
298        """ Returns conditional properties.
299        """
300        return self.conditional_
301
302    def non_conditional (self):
303        """ Returns properties that are not conditional.
304        """
305        return self.non_conditional_
306
307    def incidental (self):
308        """ Returns incidental properties.
309        """
310        result = [p for p in self.lazy_properties if p.feature.incidental]
311        result.extend(self.incidental_)
312        return result
313
314    def refine (self, requirements):
315        """ Refines this set's properties using the requirements passed as an argument.
316        """
317        assert isinstance(requirements, PropertySet)
318        if requirements not in self.refined_:
319            r = property.refine(self.all_, requirements.all_)
320
321            self.refined_[requirements] = create(r)
322
323        return self.refined_[requirements]
324
325    def expand (self):
326        if not self.expanded_:
327            expanded = feature.expand(self.all_)
328            self.expanded_ = create(expanded)
329        return self.expanded_
330
331    def expand_subfeatures(self):
332        if not self.subfeatures_:
333            self.subfeatures_ = create(feature.expand_subfeatures(self.all_))
334        return self.subfeatures_
335
336    def evaluate_conditionals(self, context=None):
337        assert isinstance(context, (PropertySet, type(None)))
338        if not context:
339            context = self
340
341        if context not in self.evaluated_:
342            # FIXME: figure why the call messes up first parameter
343            self.evaluated_[context] = create(
344                property.evaluate_conditionals_in_context(self.all(), context))
345
346        return self.evaluated_[context]
347
348    def propagated (self):
349        if not self.propagated_ps_:
350            self.propagated_ps_ = create (self.propagated_)
351        return self.propagated_ps_
352
353    def add_defaults (self):
354        # FIXME: this caching is invalidated when new features
355        # are declare inside non-root Jamfiles.
356        if not self.defaults_:
357            expanded = feature.add_defaults(self.all_)
358            self.defaults_ = create(expanded)
359        return self.defaults_
360
361    def as_path (self):
362        if not self.as_path_:
363
364            def path_order (p1, p2):
365
366                i1 = p1.feature.implicit
367                i2 = p2.feature.implicit
368
369                if i1 != i2:
370                    return i2 - i1
371                else:
372                    return cmp(p1.feature.name, p2.feature.name)
373
374            # trim redundancy
375            properties = feature.minimize(self.base_)
376
377            # sort according to path_order
378            properties.sort (path_order)
379
380            components = []
381            for p in properties:
382                f = p.feature
383                if f.implicit:
384                    components.append(p.value)
385                else:
386                    value = f.name.replace(':', '-') + "-" + p.value
387                    if property.get_abbreviated_paths():
388                        value = abbreviate_dashed(value)
389                    components.append(value)
390
391            self.as_path_ = '/'.join(components)
392
393        return self.as_path_
394
395    def target_path (self):
396        """ Computes the target path that should be used for
397            target with these properties.
398            Returns a tuple of
399              - the computed path
400              - if the path is relative to build directory, a value of
401                'true'.
402        """
403        if not self.target_path_:
404            # The <location> feature can be used to explicitly
405            # change the location of generated targets
406            l = self.get ('<location>')
407            if l:
408                computed = l[0]
409                is_relative = False
410
411            else:
412                p = self.as_path()
413                if hash_maybe:
414                    p = hash_maybe(p)
415
416                # Really, an ugly hack. Boost regression test system requires
417                # specific target paths, and it seems that changing it to handle
418                # other directory layout is really hard. For that reason,
419                # we teach V2 to do the things regression system requires.
420                # The value o '<location-prefix>' is predended to the path.
421                prefix = self.get ('<location-prefix>')
422
423                if prefix:
424                    if len (prefix) > 1:
425                        raise AlreadyDefined ("Two <location-prefix> properties specified: '%s'" % prefix)
426
427                    computed = os.path.join(prefix[0], p)
428
429                else:
430                    computed = p
431
432                if not computed:
433                    computed = "."
434
435                is_relative = True
436
437            self.target_path_ = (computed, is_relative)
438
439        return self.target_path_
440
441    def add (self, ps):
442        """ Creates a new property set containing the properties in this one,
443            plus the ones of the property set passed as argument.
444        """
445        assert isinstance(ps, PropertySet)
446        if ps not in self.added_:
447            self.added_[ps] = create(self.all_ + ps.all())
448        return self.added_[ps]
449
450    def add_raw (self, properties):
451        """ Creates a new property set containing the properties in this one,
452            plus the ones passed as argument.
453        """
454        return self.add (create (properties))
455
456
457    def get (self, feature):
458        """ Returns all values of 'feature'.
459        """
460        if type(feature) == type([]):
461            feature = feature[0]
462        if not isinstance(feature, b2.build.feature.Feature):
463            feature = b2.build.feature.get(feature)
464        assert isinstance(feature, b2.build.feature.Feature)
465
466        if self.feature_map_ is None:
467            self.feature_map_ = {}
468
469            for v in self.all_:
470                if v.feature not in self.feature_map_:
471                    self.feature_map_[v.feature] = []
472                self.feature_map_[v.feature].append(v.value)
473
474        return self.feature_map_.get(feature, [])
475
476    @cached
477    def get_properties(self, feature):
478        """Returns all contained properties associated with 'feature'"""
479        if not isinstance(feature, b2.build.feature.Feature):
480            feature = b2.build.feature.get(feature)
481        assert isinstance(feature, b2.build.feature.Feature)
482
483        result = []
484        for p in self.all_:
485            if p.feature == feature:
486                result.append(p)
487        return result
488
489    def __contains__(self, item):
490        return item.id in self._all_set
491
492def hash(p):
493    m = hashlib.md5()
494    m.update(p)
495    return m.hexdigest()
496
497hash_maybe = hash if "--hash" in bjam.variable("ARGV") else None
498
499