• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2003 Dave Abrahams
2# Copyright 2003, 2004, 2005, 2006 Vladimir Prus
3# Distributed under the Boost Software License, Version 1.0.
4# (See accompanying file LICENSE_1_0.txt or copy at
5# http://www.boost.org/LICENSE_1_0.txt)
6
7import "class" : new ;
8import feature ;
9import path ;
10import project ;
11import property ;
12import sequence ;
13import set ;
14import option ;
15
16# Class for storing a set of properties.
17#
18#   There is 1<->1 correspondence between identity and value. No two instances
19# of the class are equal. To maintain this property, the 'property-set.create'
20# rule should be used to create new instances. Instances are immutable.
21#
22#   Each property is classified with regard to its effect on build results.
23# Incidental properties have no effect on build results, from B2's
24# point of view. Others are either free, or non-free and we refer to non-free
25# ones as 'base'. Each property belongs to exactly one of those categories.
26#
27#   It is possible to get a list of properties belonging to each category as
28# well as a list of properties with a specific attribute.
29#
30#   Several operations, like and refine and as-path are provided. They all use
31# caching whenever possible.
32#
33class property-set
34{
35    import errors ;
36    import feature ;
37    import modules ;
38    import path ;
39    import property ;
40    import property-set ;
41    import set ;
42
43    rule __init__ ( raw-properties * )
44    {
45        self.raw = $(raw-properties) ;
46
47        for local p in $(raw-properties)
48        {
49            if ! $(p:G)
50            {
51                errors.error "Invalid property: '$(p)'" ;
52            }
53        }
54    }
55
56    # Returns Jam list of stored properties.
57    #
58    rule raw ( )
59    {
60        return $(self.raw) ;
61    }
62
63    rule str ( )
64    {
65        return "[" $(self.raw) "]" ;
66    }
67
68    # Returns properties that are neither incidental nor free.
69    #
70    rule base ( )
71    {
72        if ! $(self.base-initialized)
73        {
74            init-base ;
75        }
76        return $(self.base) ;
77    }
78
79    # Returns free properties which are not incidental.
80    #
81    rule free ( )
82    {
83        if ! $(self.base-initialized)
84        {
85            init-base ;
86        }
87        return $(self.free) ;
88    }
89
90    # Returns relevant base properties.  This is used for computing
91    # target paths, so it must return the expanded set of relevant
92    # properties.
93    #
94    rule base-relevant ( )
95    {
96        if ! $(self.relevant-initialized)
97        {
98            init-relevant ;
99        }
100        return $(self.base-relevant) ;
101    }
102
103    # Returns all properties marked as relevant by features-ps
104    # Does not attempt to expand features-ps in any way, as
105    # this matches what virtual-target.register needs.
106    #
107    rule relevant ( features-ps )
108    {
109        if ! $(self.relevant.$(features-ps))
110        {
111            local result ;
112            local features = [ $(features-ps).get <relevant> ] ;
113            features = <$(features)> ;
114            local ignore-relevance = [ modules.peek
115                property-set : .ignore-relevance ] ;
116            for local p in $(self.raw)
117            {
118                if $(ignore-relevance) || $(p:G) in $(features)
119                {
120                    local att = [ feature.attributes $(p:G) ] ;
121                    if ! ( incidental in $(att) )
122                    {
123                        result += $(p) ;
124                    }
125                }
126            }
127            self.relevant.$(features-ps) = [ property-set.create $(result) ] ;
128        }
129        return $(self.relevant.$(features-ps)) ;
130    }
131
132    # Returns dependency properties.
133    #
134    rule dependency ( )
135    {
136        if ! $(self.dependency-initialized)
137        {
138            init-dependency ;
139        }
140        return $(self.dependency) ;
141    }
142
143    rule non-dependency ( )
144    {
145        if ! $(self.dependency-initialized)
146        {
147            init-dependency ;
148        }
149        return $(self.non-dependency) ;
150    }
151
152    rule conditional ( )
153    {
154        if ! $(self.conditional-initialized)
155        {
156            init-conditional ;
157        }
158        return $(self.conditional) ;
159    }
160
161    rule non-conditional ( )
162    {
163        if ! $(self.conditional-initialized)
164        {
165            init-conditional ;
166        }
167        return $(self.non-conditional) ;
168    }
169
170    # Returns incidental properties.
171    #
172    rule incidental ( )
173    {
174        if ! $(self.base-initialized)
175        {
176            init-base ;
177        }
178        return $(self.incidental) ;
179    }
180
181    rule refine ( ps )
182    {
183        if ! $(self.refined.$(ps))
184        {
185            local r = [ property.refine $(self.raw) : [ $(ps).raw ] ] ;
186            if $(r[1]) != "@error"
187            {
188                self.refined.$(ps) = [ property-set.create $(r) ] ;
189            }
190            else
191            {
192                self.refined.$(ps) = $(r) ;
193            }
194        }
195        return $(self.refined.$(ps)) ;
196    }
197
198    rule expand ( )
199    {
200        if ! $(self.expanded)
201        {
202            self.expanded = [ property-set.create [ feature.expand $(self.raw) ]
203                ] ;
204        }
205        return $(self.expanded) ;
206    }
207
208    rule expand-composites ( )
209    {
210        if ! $(self.composites)
211        {
212            self.composites = [ property-set.create
213                [ feature.expand-composites $(self.raw) ] ] ;
214        }
215        return $(self.composites) ;
216    }
217
218    rule evaluate-conditionals ( context ? )
219    {
220        context ?= $(__name__) ;
221        if ! $(self.evaluated.$(context))
222        {
223            self.evaluated.$(context) = [ property-set.create
224                [ property.evaluate-conditionals-in-context $(self.raw) : [
225                $(context).raw ] ] ] ;
226        }
227        return $(self.evaluated.$(context)) ;
228    }
229
230    rule propagated ( )
231    {
232        if ! $(self.propagated-ps)
233        {
234            local result ;
235            for local p in $(self.raw)
236            {
237                if propagated in [ feature.attributes $(p:G) ]
238                {
239                    result += $(p) ;
240                }
241            }
242            self.propagated-ps = [ property-set.create $(result) ] ;
243        }
244        return $(self.propagated-ps) ;
245    }
246
247    rule add-defaults ( )
248    {
249        if ! $(self.defaults)
250        {
251            self.defaults = [ property-set.create
252                [ feature.add-defaults $(self.raw) ] ] ;
253        }
254        return $(self.defaults) ;
255    }
256
257    rule as-path ( )
258    {
259        if ! $(self.as-path)
260        {
261            self.as-path = [ property.as-path [ base-relevant ] ] ;
262        }
263        return $(self.as-path) ;
264    }
265
266    # Computes the path to be used for a target with the given properties.
267    # Returns a list of
268    #   - the computed path
269    #   - if the path is relative to the build directory, a value of 'true'.
270    #
271    rule target-path ( )
272    {
273        if ! $(self.target-path)
274        {
275            # The <location> feature can be used to explicitly change the
276            # location of generated targets.
277            local l = [ get <location> ] ;
278            if $(l)
279            {
280                self.target-path = $(l) ;
281            }
282            else
283            {
284                local p = [ property-set.hash-maybe [ as-path ] ] ;
285
286                # A real ugly hack. Boost regression test system requires
287                # specific target paths, and it seems that changing it to handle
288                # other directory layout is really hard. For that reason, we
289                # teach V2 to do the things regression system requires. The
290                # value of '<location-prefix>' is prepended to the path.
291                local prefix = [ get <location-prefix> ] ;
292                if $(prefix)
293                {
294                    self.target-path = [ path.join $(prefix) $(p) ] ;
295                }
296                else
297                {
298                    self.target-path = $(p) ;
299                }
300                if ! $(self.target-path)
301                {
302                    self.target-path = . ;
303                }
304                # The path is relative to build dir.
305                self.target-path += true ;
306            }
307        }
308        return $(self.target-path) ;
309    }
310
311    rule add ( ps )
312    {
313        if ! $(self.added.$(ps))
314        {
315            self.added.$(ps) = [ property-set.create $(self.raw) [ $(ps).raw ] ]
316                ;
317        }
318        return $(self.added.$(ps)) ;
319    }
320
321    rule add-raw ( properties * )
322    {
323        return [ add [ property-set.create $(properties) ] ] ;
324    }
325
326    # Returns all values of 'feature'.
327    #
328    rule get ( feature )
329    {
330        if ! $(self.map-built)
331        {
332            # For each feature, create a member var and assign all values to it.
333            # Since all regular member vars start with 'self', there will be no
334            # conflicts between names.
335            self.map-built = true ;
336            for local v in $(self.raw)
337            {
338                $(v:G) += $(v:G=) ;
339            }
340        }
341        return $($(feature)) ;
342    }
343
344    # Returns true if the property-set contains all the
345    # specified properties.
346    #
347    rule contains-raw ( properties * )
348    {
349        if $(properties) in $(self.raw)
350        {
351            return true ;
352        }
353    }
354
355    # Returns true if the property-set has values for
356    # all the specified features
357    #
358    rule contains-features ( features * )
359    {
360        if $(features) in $(self.raw:G)
361        {
362            return true ;
363        }
364    }
365
366    # private
367
368    rule init-base ( )
369    {
370        for local p in $(self.raw)
371        {
372            local att = [ feature.attributes $(p:G) ] ;
373            # A feature can be both incidental and free, in which case we add it
374            # to incidental.
375            if incidental in $(att)
376            {
377                self.incidental += $(p) ;
378            }
379            else if free in $(att)
380            {
381                self.free += $(p) ;
382            }
383            else
384            {
385                self.base += $(p) ;
386            }
387        }
388        self.base-initialized = true ;
389    }
390
391    rule init-relevant ( )
392    {
393        local relevant-features = [ get <relevant> ] ;
394        relevant-features = [ feature.expand-relevant $(relevant-features) ] ;
395        relevant-features = <$(relevant-features)> ;
396        ignore-relevance = [ modules.peek property-set : .ignore-relevance ] ;
397        for local p in $(self.raw)
398        {
399            if $(ignore-relevance) || $(p:G) in $(relevant-features)
400            {
401                local att = [ feature.attributes $(p:G) ] ;
402                if ! ( incidental in $(att) )
403                {
404                    self.relevant += $(p) ;
405                    if ! ( free in $(att) )
406                    {
407                        self.base-relevant += $(p) ;
408                    }
409                }
410            }
411        }
412        self.relevant-initialized = true ;
413    }
414
415    rule init-dependency ( )
416    {
417        for local p in $(self.raw)
418        {
419            if dependency in [ feature.attributes $(p:G) ]
420            {
421                self.dependency += $(p) ;
422            }
423            else
424            {
425                self.non-dependency += $(p) ;
426            }
427        }
428        self.dependency-initialized = true ;
429    }
430
431    rule init-conditional ( )
432    {
433        for local p in $(self.raw)
434        {
435            # TODO: Note that non-conditional properties may contain colon (':')
436            # characters as well, e.g. free or indirect properties. Indirect
437            # properties for example contain a full Jamfile path in their value
438            # which on Windows file systems contains ':' as the drive separator.
439            if ( [ MATCH "(:)" : $(p:G=) ] && ! ( free in [ feature.attributes $(p:G) ] ) ) || $(p:G) = <conditional>
440            {
441                self.conditional += $(p) ;
442            }
443            else
444            {
445                self.non-conditional += $(p) ;
446            }
447        }
448        self.conditional-initialized = true ;
449    }
450}
451
452# This is a temporary measure to help users work around
453# any problems.  Remove it once we've verified that
454# everything works.
455if --ignore-relevance in [ modules.peek : ARGV ]
456{
457    .ignore-relevance = true ;
458}
459
460# Creates a new 'property-set' instance for the given raw properties or returns
461# an already existing ones.
462#
463rule create ( raw-properties * )
464{
465    raw-properties = [ sequence.unique
466        [ sequence.insertion-sort $(raw-properties) ] ] ;
467
468    local key = $(raw-properties:J=-:E=) ;
469
470    if ! $(.ps.$(key))
471    {
472        .ps.$(key) = [ new property-set $(raw-properties) ] ;
473    }
474    return $(.ps.$(key)) ;
475}
476NATIVE_RULE property-set : create ;
477
478if [ HAS_NATIVE_RULE class@property-set : get : 1 ]
479{
480    NATIVE_RULE class@property-set : get ;
481}
482
483if [ HAS_NATIVE_RULE class@property-set : contains-features : 1 ]
484{
485    NATIVE_RULE class@property-set : contains-features ;
486}
487
488# Creates a new 'property-set' instance after checking that all properties are
489# valid and converting implicit properties into gristed form.
490#
491rule create-with-validation ( raw-properties * )
492{
493    property.validate $(raw-properties) ;
494    return [ create [ property.make $(raw-properties) ] ] ;
495}
496
497
498# Creates a property-set from the input given by the user, in the context of
499# 'jamfile-module' at 'location'.
500#
501rule create-from-user-input ( raw-properties * : jamfile-module location )
502{
503    local project-id = [ project.attribute $(jamfile-module) id ] ;
504    project-id ?= [ path.root $(location) [ path.pwd ] ] ;
505    return [ property-set.create [ property.translate $(raw-properties)
506        : $(project-id) : $(location) : $(jamfile-module) ] ] ;
507}
508
509
510# Refines requirements with requirements provided by the user. Specially handles
511# "-<property>value" syntax in specification to remove given requirements.
512# - parent-requirements -- property-set object with requirements to refine.
513# - specification       -- string list of requirements provided by the user.
514# - project-module      -- module to which context indirect features will be
515#                          bound.
516# - location            -- path to which path features are relative.
517#
518rule refine-from-user-input ( parent-requirements : specification * :
519    project-module : location )
520{
521    if ! $(specification)
522    {
523        return $(parent-requirements) ;
524    }
525    else
526    {
527        local add-requirements ;
528        local remove-requirements ;
529
530        for local r in $(specification)
531        {
532            local m = [ MATCH "^-(.*)" : $(r) ] ;
533            if $(m)
534            {
535                remove-requirements += $(m) ;
536            }
537            else
538            {
539                add-requirements += $(r) ;
540            }
541        }
542
543        if $(remove-requirements)
544        {
545            # Need to create a property set, so that path features and indirect
546            # features are translated just like they are in project
547            # requirements.
548            local ps = [ property-set.create-from-user-input
549                $(remove-requirements) : $(project-module) $(location) ] ;
550
551            parent-requirements = [ property-set.create
552                [ set.difference [ $(parent-requirements).raw ]
553                : [ $(ps).raw ] ] ] ;
554            specification = $(add-requirements) ;
555        }
556
557        local requirements = [ property-set.create-from-user-input
558            $(specification) : $(project-module) $(location) ] ;
559
560        return [ $(parent-requirements).refine $(requirements) ] ;
561    }
562}
563
564
565# Returns a property-set with an empty set of properties.
566#
567rule empty ( )
568{
569    if ! $(.empty)
570    {
571        .empty = [ create ] ;
572    }
573    return $(.empty) ;
574}
575
576
577if [ option.get hash : : yes ] = yes
578{
579    rule hash-maybe ( path ? )
580    {
581        path ?= "" ;
582        return [ MD5 $(path) ] ;
583    }
584}
585else
586{
587    rule hash-maybe ( path ? )
588    {
589        return $(path) ;
590    }
591}
592
593rule __test__ ( )
594{
595    import errors : try catch ;
596
597    try ;
598        create invalid-property ;
599    catch "Invalid property: 'invalid-property'" ;
600}
601