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 Boost.Build'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 path ;
38    import property ;
39    import property-set ;
40    import set ;
41
42    rule __init__ ( raw-properties * )
43    {
44        self.raw = $(raw-properties) ;
45
46        for local p in $(raw-properties)
47        {
48            if ! $(p:G)
49            {
50                errors.error "Invalid property: '$(p)'" ;
51            }
52        }
53    }
54
55    # Returns Jam list of stored properties.
56    #
57    rule raw ( )
58    {
59        return $(self.raw) ;
60    }
61
62    rule str ( )
63    {
64        return "[" $(self.raw) "]" ;
65    }
66
67    # Returns properties that are neither incidental nor free.
68    #
69    rule base ( )
70    {
71        if ! $(self.base-initialized)
72        {
73            init-base ;
74        }
75        return $(self.base) ;
76    }
77
78    # Returns free properties which are not incidental.
79    #
80    rule free ( )
81    {
82        if ! $(self.base-initialized)
83        {
84            init-base ;
85        }
86        return $(self.free) ;
87    }
88
89    # Returns dependency properties.
90    #
91    rule dependency ( )
92    {
93        if ! $(self.dependency-initialized)
94        {
95            init-dependency ;
96        }
97        return $(self.dependency) ;
98    }
99
100    rule non-dependency ( )
101    {
102        if ! $(self.dependency-initialized)
103        {
104            init-dependency ;
105        }
106        return $(self.non-dependency) ;
107    }
108
109    rule conditional ( )
110    {
111        if ! $(self.conditional-initialized)
112        {
113            init-conditional ;
114        }
115        return $(self.conditional) ;
116    }
117
118    rule non-conditional ( )
119    {
120        if ! $(self.conditional-initialized)
121        {
122            init-conditional ;
123        }
124        return $(self.non-conditional) ;
125    }
126
127    # Returns incidental properties.
128    #
129    rule incidental ( )
130    {
131        if ! $(self.base-initialized)
132        {
133            init-base ;
134        }
135        return $(self.incidental) ;
136    }
137
138    rule refine ( ps )
139    {
140        if ! $(self.refined.$(ps))
141        {
142            local r = [ property.refine $(self.raw) : [ $(ps).raw ] ] ;
143            if $(r[1]) != "@error"
144            {
145                self.refined.$(ps) = [ property-set.create $(r) ] ;
146            }
147            else
148            {
149                self.refined.$(ps) = $(r) ;
150            }
151        }
152        return $(self.refined.$(ps)) ;
153    }
154
155    rule expand ( )
156    {
157        if ! $(self.expanded)
158        {
159            self.expanded = [ property-set.create [ feature.expand $(self.raw) ]
160                ] ;
161        }
162        return $(self.expanded) ;
163    }
164
165    rule expand-composites ( )
166    {
167        if ! $(self.composites)
168        {
169            self.composites = [ property-set.create
170                [ feature.expand-composites $(self.raw) ] ] ;
171        }
172        return $(self.composites) ;
173    }
174
175    rule evaluate-conditionals ( context ? )
176    {
177        context ?= $(__name__) ;
178        if ! $(self.evaluated.$(context))
179        {
180            self.evaluated.$(context) = [ property-set.create
181                [ property.evaluate-conditionals-in-context $(self.raw) : [
182                $(context).raw ] ] ] ;
183        }
184        return $(self.evaluated.$(context)) ;
185    }
186
187    rule propagated ( )
188    {
189        if ! $(self.propagated-ps)
190        {
191            local result ;
192            for local p in $(self.raw)
193            {
194                if propagated in [ feature.attributes $(p:G) ]
195                {
196                    result += $(p) ;
197                }
198            }
199            self.propagated-ps = [ property-set.create $(result) ] ;
200        }
201        return $(self.propagated-ps) ;
202    }
203
204    rule add-defaults ( )
205    {
206        if ! $(self.defaults)
207        {
208            self.defaults = [ property-set.create
209                [ feature.add-defaults $(self.raw) ] ] ;
210        }
211        return $(self.defaults) ;
212    }
213
214    rule as-path ( )
215    {
216        if ! $(self.as-path)
217        {
218            self.as-path = [ property.as-path [ base ] ] ;
219        }
220        return $(self.as-path) ;
221    }
222
223    # Computes the path to be used for a target with the given properties.
224    # Returns a list of
225    #   - the computed path
226    #   - if the path is relative to the build directory, a value of 'true'.
227    #
228    rule target-path ( )
229    {
230        if ! $(self.target-path)
231        {
232            # The <location> feature can be used to explicitly change the
233            # location of generated targets.
234            local l = [ get <location> ] ;
235            if $(l)
236            {
237                self.target-path = $(l) ;
238            }
239            else
240            {
241                local p = [ property-set.hash-maybe [ as-path ] ] ;
242
243                # A real ugly hack. Boost regression test system requires
244                # specific target paths, and it seems that changing it to handle
245                # other directory layout is really hard. For that reason, we
246                # teach V2 to do the things regression system requires. The
247                # value of '<location-prefix>' is prepended to the path.
248                local prefix = [ get <location-prefix> ] ;
249                if $(prefix)
250                {
251                    self.target-path = [ path.join $(prefix) $(p) ] ;
252                }
253                else
254                {
255                    self.target-path = $(p) ;
256                }
257                if ! $(self.target-path)
258                {
259                    self.target-path = . ;
260                }
261                # The path is relative to build dir.
262                self.target-path += true ;
263            }
264        }
265        return $(self.target-path) ;
266    }
267
268    rule add ( ps )
269    {
270        if ! $(self.added.$(ps))
271        {
272            self.added.$(ps) = [ property-set.create $(self.raw) [ $(ps).raw ] ]
273                ;
274        }
275        return $(self.added.$(ps)) ;
276    }
277
278    rule add-raw ( properties * )
279    {
280        return [ add [ property-set.create $(properties) ] ] ;
281    }
282
283    # Returns all values of 'feature'.
284    #
285    rule get ( feature )
286    {
287        if ! $(self.map-built)
288        {
289            # For each feature, create a member var and assign all values to it.
290            # Since all regular member vars start with 'self', there will be no
291            # conflicts between names.
292            self.map-built = true ;
293            for local v in $(self.raw)
294            {
295                $(v:G) += $(v:G=) ;
296            }
297        }
298        return $($(feature)) ;
299    }
300
301    # Returns true if the property-set contains all the
302    # specified properties.
303    #
304    rule contains-raw ( properties * )
305    {
306        if $(properties) in $(self.raw)
307        {
308            return true ;
309        }
310    }
311
312    # Returns true if the property-set has values for
313    # all the specified features
314    #
315    rule contains-features ( features * )
316    {
317        if $(features) in $(self.raw:G)
318        {
319            return true ;
320        }
321    }
322
323    # private
324
325    rule init-base ( )
326    {
327        for local p in $(self.raw)
328        {
329            local att = [ feature.attributes $(p:G) ] ;
330            # A feature can be both incidental and free, in which case we add it
331            # to incidental.
332            if incidental in $(att)
333            {
334                self.incidental += $(p) ;
335            }
336            else if free in $(att)
337            {
338                self.free += $(p) ;
339            }
340            else
341            {
342                self.base += $(p) ;
343            }
344        }
345        self.base-initialized = true ;
346    }
347
348    rule init-dependency ( )
349    {
350        for local p in $(self.raw)
351        {
352            if dependency in [ feature.attributes $(p:G) ]
353            {
354                self.dependency += $(p) ;
355            }
356            else
357            {
358                self.non-dependency += $(p) ;
359            }
360        }
361        self.dependency-initialized = true ;
362    }
363
364    rule init-conditional ( )
365    {
366        for local p in $(self.raw)
367        {
368            # TODO: Note that non-conditional properties may contain colon (':')
369            # characters as well, e.g. free or indirect properties. Indirect
370            # properties for example contain a full Jamfile path in their value
371            # which on Windows file systems contains ':' as the drive separator.
372            if [ MATCH (:) : $(p:G=) ]
373            {
374                self.conditional += $(p) ;
375            }
376            else
377            {
378                self.non-conditional += $(p) ;
379            }
380        }
381        self.conditional-initialized = true ;
382    }
383}
384
385
386# Creates a new 'property-set' instance for the given raw properties or returns
387# an already existing ones.
388#
389rule create ( raw-properties * )
390{
391    raw-properties = [ sequence.unique
392        [ sequence.insertion-sort $(raw-properties) ] ] ;
393
394    local key = $(raw-properties:J=-:E=) ;
395
396    if ! $(.ps.$(key))
397    {
398        .ps.$(key) = [ new property-set $(raw-properties) ] ;
399    }
400    return $(.ps.$(key)) ;
401}
402NATIVE_RULE property-set : create ;
403
404if [ HAS_NATIVE_RULE class@property-set : get : 1 ]
405{
406    NATIVE_RULE class@property-set : get ;
407}
408
409if [ HAS_NATIVE_RULE class@property-set : contains-features : 1 ]
410{
411    NATIVE_RULE class@property-set : contains-features ;
412}
413
414# Creates a new 'property-set' instance after checking that all properties are
415# valid and converting implicit properties into gristed form.
416#
417rule create-with-validation ( raw-properties * )
418{
419    property.validate $(raw-properties) ;
420    return [ create [ property.make $(raw-properties) ] ] ;
421}
422
423
424# Creates a property-set from the input given by the user, in the context of
425# 'jamfile-module' at 'location'.
426#
427rule create-from-user-input ( raw-properties * : jamfile-module location )
428{
429    local project-id = [ project.attribute $(jamfile-module) id ] ;
430    project-id ?= [ path.root $(location) [ path.pwd ] ] ;
431    return [ property-set.create [ property.translate $(raw-properties)
432        : $(project-id) : $(location) : $(jamfile-module) ] ] ;
433}
434
435
436# Refines requirements with requirements provided by the user. Specially handles
437# "-<property>value" syntax in specification to remove given requirements.
438# - parent-requirements -- property-set object with requirements to refine.
439# - specification       -- string list of requirements provided by the user.
440# - project-module      -- module to which context indirect features will be
441#                          bound.
442# - location            -- path to which path features are relative.
443#
444rule refine-from-user-input ( parent-requirements : specification * :
445    project-module : location )
446{
447    if ! $(specification)
448    {
449        return $(parent-requirements) ;
450    }
451    else
452    {
453        local add-requirements ;
454        local remove-requirements ;
455
456        for local r in $(specification)
457        {
458            local m = [ MATCH "^-(.*)" : $(r) ] ;
459            if $(m)
460            {
461                remove-requirements += $(m) ;
462            }
463            else
464            {
465                add-requirements += $(r) ;
466            }
467        }
468
469        if $(remove-requirements)
470        {
471            # Need to create a property set, so that path features and indirect
472            # features are translated just like they are in project
473            # requirements.
474            local ps = [ property-set.create-from-user-input
475                $(remove-requirements) : $(project-module) $(location) ] ;
476
477            parent-requirements = [ property-set.create
478                [ set.difference [ $(parent-requirements).raw ]
479                : [ $(ps).raw ] ] ] ;
480            specification = $(add-requirements) ;
481        }
482
483        local requirements = [ property-set.create-from-user-input
484            $(specification) : $(project-module) $(location) ] ;
485
486        return [ $(parent-requirements).refine $(requirements) ] ;
487    }
488}
489
490
491# Returns a property-set with an empty set of properties.
492#
493rule empty ( )
494{
495    if ! $(.empty)
496    {
497        .empty = [ create ] ;
498    }
499    return $(.empty) ;
500}
501
502
503if [ option.get hash : : yes ] = yes
504{
505    rule hash-maybe ( path ? )
506    {
507        path ?= "" ;
508        return [ MD5 $(path) ] ;
509    }
510}
511else
512{
513    rule hash-maybe ( path ? )
514    {
515        return $(path) ;
516    }
517}
518