1# Copyright 2001, 2002, 2003 Dave Abrahams
2# Copyright 2006 Rene Rivera
3# Copyright 2002, 2003, 2004, 2005, 2006 Vladimir Prus
4# Distributed under the Boost Software License, Version 1.0.
5# (See accompanying file LICENSE_1_0.txt or copy at
6# http://www.boost.org/LICENSE_1_0.txt)
7
8import feature ;
9import indirect ;
10import path ;
11import regex ;
12import string ;
13import sequence ;
14import set ;
15import utility ;
16
17
18# Refines 'properties' by overriding any non-free and non-conditional properties
19# for which a different value is specified in 'requirements'. Returns the
20# resulting list of properties.
21#
22rule refine ( properties * : requirements * )
23{
24    local result ;
25    local unset ;
26
27    # Collect all non-free features in requirements
28    for local r in $(requirements)
29    {
30        # Do not consider conditional requirements.
31        if ! [ MATCH "(:<)" : $(r:G=) ] && ! free in [ feature.attributes $(r:G) ]
32        {
33            if ! $(r) in $(properties)
34            {
35                # Kill subfeatures of properties that we're changing
36                local sub = [ modules.peek feature : $(r:G).subfeatures ] ;
37                if $(sub)
38                {
39                    # non-specific subfeatures are still valid
40                    sub = [ MATCH "(.*:.*)" : $(sub) ] ;
41                    local name = [ utility.ungrist $(r:G) ] ;
42                    unset += <$(name)-$(sub)> ;
43                }
44            }
45            unset += $(r:G) ;
46        }
47    }
48
49    # Remove properties that are overridden by requirements
50    for local p in $(properties)
51    {
52        if [ MATCH "(:<)" : $(p:G=) ] || ! $(p:G) in $(unset)
53        {
54            result += $(p) ;
55        }
56    }
57
58    return [ sequence.unique $(result) $(requirements) ] ;
59}
60
61
62# Removes all conditional properties whose conditions are not met. For those
63# with met conditions, removes the condition. Properties in conditions are
64# looked up in 'context'.
65#
66rule evaluate-conditionals-in-context ( properties * : context * )
67{
68    local base ;
69    local conditionals ;
70    local indirect ;
71    for local p in $(properties)
72    {
73        if [ MATCH "(:<)" : $(p) ] && ! free in [ feature.attributes $(p:G) ]
74        {
75            conditionals += $(p) ;
76        }
77        else if $(p:G) = <conditional>
78        {
79            indirect += $(p) ;
80        }
81        else
82        {
83            base += $(p) ;
84        }
85    }
86
87    local result = $(base) ;
88    for local p in $(conditionals)
89    {
90        # Separate condition and property.
91        local s = [ MATCH "^(.*):(<.*)" : $(p) ] ;
92        # Split condition into individual properties.
93        local condition = [ regex.split $(s[1]) "," ] ;
94        # Evaluate condition.
95        if ! [ MATCH ^(!).* : $(condition:G=) ]
96        {
97            # Only positive checks
98            if $(condition) in $(context)
99            {
100                result += $(s[2]) ;
101            }
102        }
103        else
104        {
105            # Have negative checks
106            local fail ;
107            while $(condition)
108            {
109                local c = $(condition[1]) ;
110                local m = [ MATCH ^!(.*) : $(c) ] ;
111                if $(m)
112                {
113                    local p = $(m:G=$(c:G)) ;
114                    if $(p) in $(context)
115                    {
116                        fail = true ;
117                        c = ;
118                    }
119                }
120                else
121                {
122                    if ! $(c) in $(context)
123                    {
124                        fail = true ;
125                        c = ;
126                    }
127                }
128                condition = $(condition[2-]) ;
129            }
130            if ! $(fail)
131            {
132                result += $(s[2]) ;
133            }
134        }
135    }
136    # Import here to avoid cyclic dependency
137    import project ;
138    for local i in [ MATCH "^@(.*)" : $(indirect:G=) ]
139    {
140        # If the rule was set in a project module, translate paths
141        # relative to that project's location.
142        local m = [ indirect.get-module $(i) ] ;
143        local p = [ project.target $(m) : allow-missing ] ;
144        local new = [ indirect.call $(i) $(context) ] ;
145        if $(p) && [ $(p).location ]
146        {
147            local location = [ $(p).location ] ;
148            local project-id = [ project.attribute $(m) id ] ;
149            project-id ?= [ path.root $(location) [ path.pwd ] ] ;
150            result +=
151                [ translate $(new) : $(project-id) : $(location) : $(m) ] ;
152        }
153        else
154        {
155            result += $(new) ;
156        }
157    }
158    return $(result) ;
159}
160
161
162# Returns <relevant> properties indicating how the conditionals in
163# properties affect feature relevance.  If the optional argument cond
164# is passed, it is treated as extra conditions for all properties.
165#
166rule evaluate-conditional-relevance ( properties * :  cond * )
167{
168    cond = [ sequence.transform utility.ungrist : $(cond:G) ] ;
169    local result ;
170    for local p in $(properties)
171    {
172        # Separate condition and property.
173        local s = [ MATCH "^(.*):(<.*)" : $(p) ] ;
174        if ! $(s) || free in [ feature.attributes $(p:G) ]
175        {
176            local value = [ utility.ungrist $(p:G) ] ;
177            result += <relevant>$(value):<relevant>$(cond) ;
178        }
179        else
180        {
181            local condition = [ regex.split $(s[1]) "," ] ;
182            condition = [ MATCH "^!?(.*)" : $(condition) ] ;
183            condition = [ sequence.transform utility.ungrist : $(condition:G) ] $(cond) ;
184            local value = [ utility.ungrist $(s[2]:G) ] ;
185            result += <relevant>$(value):<relevant>$(condition) ;
186        }
187    }
188    return [ sequence.unique $(result) ] ;
189}
190
191
192rule expand-subfeatures-in-conditions ( properties * )
193{
194    local result ;
195    for local p in $(properties)
196    {
197        local s = [ MATCH "^(.*):(<.*)" : $(p) ] ;
198        if ! $(s)
199        {
200            result += $(p) ;
201        }
202        else
203        {
204            local condition = $(s[1]) ;
205            local value     = $(s[2]) ;
206            # Condition might include several elements.
207            condition = [ regex.split $(condition) "," ] ;
208            local e ;
209            for local c in $(condition)
210            {
211                # It is common for a condition to include a toolset or
212                # subfeatures that have not been defined. In that case we want
213                # the condition to simply 'never be satisfied' and validation
214                # would only produce a spurious error so we prevent it by
215                # passing 'true' as the second parameter.
216                e += [ feature.expand-subfeatures $(c) : true ] ;
217            }
218            if $(e) = $(condition)
219            {
220                # (todo)
221                #   This is just an optimization and possibly a premature one at
222                # that.
223                #                             (todo) (12.07.2008.) (Jurko)
224                result += $(p) ;
225            }
226            else
227            {
228                result += "$(e:J=,):$(value)" ;
229            }
230        }
231    }
232    return $(result) ;
233}
234
235
236# Helper for as-path, below. Orders properties with the implicit ones first, and
237# within the two sections in alphabetical order of feature name.
238#
239local rule path-order ( x y )
240{
241    if $(y:G) && ! $(x:G)
242    {
243        return true ;
244    }
245    else if $(x:G) && ! $(y:G)
246    {
247        return ;
248    }
249    else
250    {
251        if ! $(x:G)
252        {
253            x = [ feature.expand-subfeatures $(x) ] ;
254            y = [ feature.expand-subfeatures $(y) ] ;
255        }
256
257        if $(x[1]) < $(y[1])
258        {
259            return true ;
260        }
261    }
262}
263
264
265local rule abbreviate-dashed ( string )
266{
267    local r ;
268    for local part in [ regex.split $(string) - ]
269    {
270        r += [ string.abbreviate $(part) ] ;
271    }
272    return $(r:J=-) ;
273}
274
275
276local rule identity ( string )
277{
278    return $(string) ;
279}
280
281
282if --abbreviate-paths in [ modules.peek : ARGV ]
283{
284    .abbrev = abbreviate-dashed ;
285}
286else
287{
288    .abbrev = identity ;
289}
290
291
292# Returns a path representing the given expanded property set.
293#
294rule as-path ( properties * )
295{
296    local entry = .result.$(properties:J=-) ;
297
298    if ! $($(entry))
299    {
300        # Trim redundancy.
301        properties = [ feature.minimize $(properties) ] ;
302
303        # Sort according to path-order.
304        properties = [ sequence.insertion-sort $(properties) : path-order ] ;
305
306        local components ;
307        for local p in $(properties)
308        {
309            if ! hidden in [ feature.attributes $(p:G) ]
310            {
311                if $(p:G)
312                {
313                    local f = [ utility.ungrist $(p:G) ] ;
314                    p = $(f)-$(p:G=) ;
315                 }
316                 components += [ $(.abbrev) $(p) ] ;
317            }
318        }
319
320        $(entry) = $(components:J=/) ;
321    }
322
323    return $($(entry)) ;
324}
325
326
327# Exit with error if property is not valid.
328#
329local rule validate1 ( property )
330{
331    local msg ;
332    if $(property:G)
333    {
334        local feature = $(property:G) ;
335        local value = $(property:G=) ;
336
337        if ! [ feature.valid $(feature) ]
338        {
339            # Ungrist for better error messages.
340            feature = [ utility.ungrist $(property:G) ] ;
341            msg = "unknown feature '$(feature)'" ;
342        }
343        else if $(value) && ! free in [ feature.attributes $(feature) ]
344        {
345            feature.validate-value-string $(feature) $(value) ;
346        }
347        else if ! ( $(value) || ( optional in [ feature.attributes $(feature) ] ) )
348        {
349            # Ungrist for better error messages.
350            feature = [ utility.ungrist $(property:G) ] ;
351            msg = "No value specified for feature '$(feature)'" ;
352        }
353    }
354    else
355    {
356        local feature = [ feature.implied-feature $(property) ] ;
357        feature.validate-value-string $(feature) $(property) ;
358    }
359    if $(msg)
360    {
361        import errors ;
362        errors.error "Invalid property "'$(property:J=" ")'": "$(msg:J=" "). ;
363    }
364}
365
366
367rule validate ( properties * )
368{
369    for local p in $(properties)
370    {
371        validate1 $(p) ;
372    }
373}
374
375
376rule validate-property-sets ( property-sets * )
377{
378    for local s in $(property-sets)
379    {
380        validate [ feature.split $(s) ] ;
381    }
382}
383
384
385# Expands any implicit property values in the given property 'specification' so
386# they explicitly state their feature.
387#
388rule make ( specification * )
389{
390    local result ;
391    for local e in $(specification)
392    {
393        if $(e:G)
394        {
395            result += $(e) ;
396        }
397        else if [ feature.is-implicit-value $(e) ]
398        {
399            local feature = [ feature.implied-feature $(e) ] ;
400            result += $(feature)$(e) ;
401        }
402        else
403        {
404            import errors ;
405            errors.error "'$(e)' is not a valid property specification" ;
406        }
407    }
408    return $(result) ;
409}
410
411
412# Returns a property set containing all the elements in 'properties' that do not
413# have their attributes listed in 'attributes'.
414#
415rule remove ( attributes + : properties * )
416{
417    local result ;
418    for local e in $(properties)
419    {
420        if ! [ set.intersection $(attributes) : [ feature.attributes $(e:G) ] ]
421        {
422            result += $(e) ;
423        }
424    }
425    return $(result) ;
426}
427
428
429# Returns a property set containing all the elements in 'properties' that have
430# their attributes listed in 'attributes'.
431#
432rule take ( attributes + : properties * )
433{
434    local result ;
435    for local e in $(properties)
436    {
437        if [ set.intersection $(attributes) : [ feature.attributes $(e:G) ] ]
438        {
439            result += $(e) ;
440        }
441    }
442    return $(result) ;
443}
444
445
446# Selects properties corresponding to any of the given features.
447#
448rule select ( features * : properties * )
449{
450    local result ;
451
452    # Add any missing angle brackets.
453    local empty = "" ;
454    features = $(empty:G=$(features)) ;
455
456    for local p in $(properties)
457    {
458        if $(p:G) in $(features)
459        {
460            result += $(p) ;
461        }
462    }
463    return $(result) ;
464}
465
466
467# Returns a modified version of properties with all values of the given feature
468# replaced by the given value. If 'value' is empty the feature will be removed.
469#
470rule change ( properties * : feature value ? )
471{
472    local result ;
473    for local p in $(properties)
474    {
475        if $(p:G) = $(feature)
476        {
477            result += $(value:G=$(feature)) ;
478        }
479        else
480        {
481            result += $(p) ;
482        }
483    }
484    return $(result) ;
485}
486
487
488# If 'property' is a conditional property, returns the condition and the
489# property. E.g. <variant>debug,<toolset>gcc:<inlining>full will become
490# <variant>debug,<toolset>gcc <inlining>full. Otherwise, returns an empty
491# string.
492#
493rule split-conditional ( property )
494{
495    return [ MATCH "^(.+):(<.+)" : $(property) ] ;
496}
497
498
499rule translate-path-value ( value : path )
500{
501    local t ;
502    for local v in [ regex.split $(value) "&&" ]
503    {
504        t += [ path.root [ path.make $(v) ] $(path) ] ;
505    }
506    return $(t:TJ="&&") ;
507}
508
509rule translate-dependency-value ( value : project-id : project-location )
510{
511    local split-target = [ regex.match ^(.*)//(.*) : $(value) ] ;
512    if $(split-target)
513    {
514        local rooted = [ path.root [ path.make $(split-target[1]) ]
515                [ path.root $(project-location) [ path.pwd ] ] ] ;
516        return $(rooted)//$(split-target[2]) ;
517    }
518    else if [ path.is-rooted $(value) ]
519    {
520        return $(value) ;
521    }
522    else
523    {
524        return $(project-id)//$(value) ;
525    }
526}
527
528rule translate-indirect-value ( rulename : context-module )
529{
530    if [ MATCH "^([^%]*)%([^%]+)$" : $(rulename) ]
531    {
532        # Rule is already in the 'indirect-rule' format.
533        return @$(rulename) ;
534    }
535    else
536    {
537        local v ;
538        if ! [ MATCH "([.])" : $(rulename) ]
539        {
540            # This is an unqualified rule name. The user might want to
541            # set flags on this rule name and toolset.flag
542            # auto-qualifies it. Need to do the same here so flag
543            # setting works. We can arrange for toolset.flag to *not*
544            # auto-qualify the argument but then two rules defined in
545            # two Jamfiles would conflict.
546            rulename = $(context-module).$(rulename) ;
547        }
548        v = [ indirect.make $(rulename) : $(context-module) ] ;
549        return @$(v) ;
550    }
551
552}
553
554# Equivalent to a calling all of:
555#   translate-path
556#   translate-indirect
557#   translate-dependency
558#   expand-subfeatures-in-conditions
559#   make
560#
561rule translate ( properties * : project-id : project-location : context-module )
562{
563    local result ;
564    for local p in $(properties)
565    {
566        local split = [ split-conditional $(p) ] ;
567        local condition property ;
568
569        if $(split)
570        {
571            condition = $(split[1]) ;
572            property = $(split[2]) ;
573
574            local e ;
575            for local c in [ regex.split $(condition) "," ]
576            {
577                e += [ feature.expand-subfeatures $(c) : true ] ;
578            }
579
580            condition = "$(e:J=,):" ;
581        }
582        else
583        {
584            property = $(p) ;
585        }
586
587        local feature = $(property:G) ;
588        if ! $(feature)
589        {
590            if [ feature.is-implicit-value $(property) ]
591            {
592                feature = [ feature.implied-feature $(property) ] ;
593                result += $(condition:E=)$(feature)$(property) ;
594            }
595            else
596            {
597                import errors ;
598                errors.error "'$(property)' is not a valid property specification" ;
599            }
600        } else {
601            local attributes = [ feature.attributes $(feature) ] ;
602            local value ;
603            # Only free features should be translated
604            if free in $(attributes)
605            {
606                if path in $(attributes)
607                {
608                    value = [ translate-path-value $(property:G=) : $(project-location) ] ;
609                    result += $(condition:E=)$(feature)$(value) ;
610                }
611                else if dependency in $(attributes)
612                {
613                    value = [ translate-dependency-value $(property:G=) : $(project-id) : $(project-location) ] ;
614                    result += $(condition:E=)$(feature)$(value) ;
615                }
616                else
617                {
618                    local m = [ MATCH ^@(.+) : $(property:G=) ] ;
619                    if $(m)
620                    {
621                        value = [ translate-indirect-value $(m) : $(context-module) ] ;
622                        result += $(condition:E=)$(feature)$(value) ;
623                    }
624                    else
625                    {
626                        result += $(condition:E=)$(property) ;
627                    }
628                }
629            }
630            else
631            {
632                result += $(condition:E=)$(property) ;
633            }
634        }
635    }
636    return $(result) ;
637}
638
639# Interpret all path properties in 'properties' as relative to 'path'. The
640# property values are assumed to be in system-specific form, and will be
641# translated into normalized form.
642#
643rule translate-paths ( properties * : path )
644{
645    local result ;
646    for local p in $(properties)
647    {
648        local split = [ split-conditional $(p) ] ;
649        local condition = "" ;
650        if $(split)
651        {
652            condition = "$(split[1]):" ;
653            p = $(split[2]) ;
654        }
655
656        if path in [ feature.attributes $(p:G) ]
657        {
658            local values = [ regex.split $(p:TG=) "&&" ] ;
659            local t ;
660            for local v in $(values)
661            {
662                t += [ path.root [ path.make $(v) ] $(path) ] ;
663            }
664            t = $(t:J="&&") ;
665            result += $(condition)$(t:TG=$(p:G)) ;
666        }
667        else
668        {
669            result += $(condition)$(p) ;
670        }
671    }
672    return $(result) ;
673}
674
675
676# Assumes that all feature values that start with '@' are names of rules, used
677# in 'context-module'. Such rules can be either local to the module or global.
678# Converts such values into 'indirect-rule' format (see indirect.jam), so they
679# can be called from other modules. Does nothing for such values that are
680# already in the 'indirect-rule' format.
681#
682rule translate-indirect ( specification * : context-module )
683{
684    local result ;
685    for local p in $(specification)
686    {
687        local m = [ MATCH ^@(.+) : $(p:G=) ] ;
688        if $(m)
689        {
690            local v ;
691            if [ MATCH "^([^%]*)%([^%]+)$" : $(m) ]
692            {
693                # Rule is already in the 'indirect-rule' format.
694                v = $(m) ;
695            }
696            else
697            {
698                if ! [ MATCH "([.])" : $(m) ]
699                {
700                    # This is an unqualified rule name. The user might want to
701                    # set flags on this rule name and toolset.flag
702                    # auto-qualifies it. Need to do the same here so flag
703                    # setting works. We can arrange for toolset.flag to *not*
704                    # auto-qualify the argument but then two rules defined in
705                    # two Jamfiles would conflict.
706                    m = $(context-module).$(m) ;
707                }
708                v = [ indirect.make $(m) : $(context-module) ] ;
709            }
710
711            v = @$(v) ;
712            result += $(v:G=$(p:G)) ;
713        }
714        else
715        {
716            result += $(p) ;
717        }
718    }
719    return $(result) ;
720}
721
722
723# Binds all dependency properties in a list relative to the given project.
724# Targets with absolute paths will be left unchanged and targets which have a
725# project specified will have the path to the project interpreted relative to
726# the specified location.
727#
728rule translate-dependencies ( specification * : project-id : location )
729{
730    local result ;
731    for local p in $(specification)
732    {
733        local split = [ split-conditional $(p) ] ;
734        local condition = "" ;
735        if $(split)
736        {
737            condition = "$(split[1]):" ;
738            p = $(split[2]) ;
739        }
740        if dependency in [ feature.attributes $(p:G) ]
741        {
742            local split-target = [ regex.match ^(.*)//(.*) : $(p:G=) ] ;
743            if $(split-target)
744            {
745                local rooted = [ path.root [ path.make $(split-target[1]) ]
746                     [ path.root $(location) [ path.pwd ] ] ] ;
747                result += $(condition)$(p:G)$(rooted)//$(split-target[2]) ;
748            }
749            else if [ path.is-rooted $(p:G=) ]
750            {
751                result += $(condition)$(p) ;
752            }
753            else
754            {
755                result += $(condition)$(p:G)$(project-id)//$(p:G=) ;
756            }
757        }
758        else
759        {
760            result += $(condition)$(p) ;
761        }
762    }
763    return $(result) ;
764}
765
766
767# Class maintaining a property set -> string mapping.
768#
769class property-map
770{
771    import numbers ;
772    import sequence ;
773
774    rule __init__ ( )
775    {
776        self.next-flag = 1 ;
777    }
778
779    # Associate 'value' with 'properties'.
780    #
781    rule insert ( properties * : value )
782    {
783        self.all-flags += self.$(self.next-flag) ;
784        self.$(self.next-flag) = $(value) $(properties) ;
785
786        self.next-flag = [ numbers.increment $(self.next-flag) ] ;
787    }
788
789    # Returns the value associated with 'properties' or any subset of it. If
790    # more than one subset has a value assigned to it, returns the value for the
791    # longest subset, if it is unique.
792    #
793    rule find ( property-set )
794    {
795        # First find all matches.
796        local matches ;
797        local match-ranks ;
798        for local i in $(self.all-flags)
799        {
800            local list = $($(i)) ;
801            if [ $(property-set).contains-raw $(list[2-]) ]
802            {
803                matches += $(list[1]) ;
804                match-ranks += [ sequence.length $(list) ] ;
805            }
806        }
807        local best = [ sequence.select-highest-ranked $(matches)
808            : $(match-ranks) ] ;
809        if $(best[2])
810        {
811            import errors : error : errors.error ;
812            errors.error "Ambiguous key $(properties:J= :E=)" ;
813        }
814        return $(best) ;
815    }
816
817    # Returns the value associated with 'properties'. If 'value' parameter is
818    # given, replaces the found value.
819    #
820    rule find-replace ( properties * : value ? )
821    {
822        # First find all matches.
823        local matches ;
824        local match-ranks ;
825        for local i in $(self.all-flags)
826        {
827            if $($(i)[2-]) in $(properties)
828            {
829                matches += $(i) ;
830                match-ranks += [ sequence.length $($(i)) ] ;
831            }
832        }
833        local best = [ sequence.select-highest-ranked $(matches)
834            : $(match-ranks) ] ;
835        if $(best[2])
836        {
837            import errors : error : errors.error ;
838            errors.error "Ambiguous key $(properties:J= :E=)" ;
839        }
840        local original = $($(best)[1]) ;
841        if $(value)-is-set
842        {
843            $(best) = $(value) $($(best)[2-]) ;
844        }
845        return $(original) ;
846    }
847}
848
849
850rule __test__ ( )
851{
852    import assert ;
853    import "class" : new ;
854    import errors : try catch ;
855    import feature ;
856
857    # Local rules must be explicitly re-imported.
858    import property : path-order abbreviate-dashed ;
859
860    feature.prepare-test property-test-temp ;
861
862    feature.feature toolset : gcc : implicit symmetric ;
863    feature.subfeature toolset gcc : version : 2.95.2 2.95.3 2.95.4 3.0 3.0.1
864        3.0.2 : optional ;
865    feature.feature define : : free ;
866    feature.feature runtime-link : dynamic static : symmetric link-incompatible ;
867    feature.feature optimization : on off ;
868    feature.feature variant : debug release : implicit composite symmetric ;
869    feature.feature rtti : on off : link-incompatible ;
870
871    feature.compose <variant>debug : <define>_DEBUG <optimization>off ;
872    feature.compose <variant>release : <define>NDEBUG <optimization>on ;
873
874    validate <toolset>gcc <toolset>gcc-3.0.1 : $(test-space) ;
875
876    assert.true  path-order $(test-space) debug <define>foo ;
877    assert.false path-order $(test-space) <define>foo debug ;
878    assert.true  path-order $(test-space) gcc debug ;
879    assert.false path-order $(test-space) debug gcc ;
880    assert.true  path-order $(test-space) <optimization>on <rtti>on ;
881    assert.false path-order $(test-space) <rtti>on <optimization>on ;
882
883    assert.result-set-equal <toolset>gcc <rtti>off <define>FOO
884        : refine <toolset>gcc <rtti>off
885        : <define>FOO
886        : $(test-space) ;
887
888    assert.result-set-equal <toolset>gcc <optimization>on
889        : refine <toolset>gcc <optimization>off
890        : <optimization>on
891        : $(test-space) ;
892
893    assert.result-set-equal <toolset>gcc <rtti>off
894        : refine <toolset>gcc : <rtti>off : $(test-space) ;
895
896    assert.result-set-equal <toolset>gcc <rtti>off <rtti>off:<define>FOO
897        : refine <toolset>gcc : <rtti>off <rtti>off:<define>FOO
898        : $(test-space) ;
899
900    assert.result-set-equal <toolset>gcc:<define>foo <toolset>gcc:<define>bar
901        : refine <toolset>gcc:<define>foo : <toolset>gcc:<define>bar
902        : $(test-space) ;
903
904    assert.result <define>MY_RELEASE
905        : evaluate-conditionals-in-context
906          <variant>release,<rtti>off:<define>MY_RELEASE
907        : <toolset>gcc <variant>release <rtti>off ;
908
909    assert.result debug
910        : as-path <optimization>off <variant>debug
911        : $(test-space) ;
912
913    assert.result gcc/debug/rtti-off
914        : as-path <toolset>gcc <optimization>off <rtti>off <variant>debug
915        : $(test-space) ;
916
917    assert.result optmz-off : abbreviate-dashed optimization-off ;
918    assert.result rntm-lnk-sttc : abbreviate-dashed runtime-link-static ;
919
920    try ;
921        validate <feature>value : $(test-space) ;
922    catch "Invalid property '<feature>value': unknown feature 'feature'." ;
923
924    try ;
925        validate <rtti>default : $(test-space) ;
926    catch \"default\" is not a known value of feature <rtti> ;
927
928    validate <define>WHATEVER : $(test-space) ;
929
930    try ;
931        validate <rtti> : $(test-space) ;
932    catch "Invalid property '<rtti>': No value specified for feature 'rtti'." ;
933
934    try ;
935        validate value : $(test-space) ;
936    catch \"value\" is not an implicit feature value ;
937
938    assert.result-set-equal <rtti>on
939        : remove free implicit : <toolset>gcc <define>foo <rtti>on : $(test-space) ;
940
941    assert.result-set-equal <include>a
942        : select include : <include>a <toolset>gcc ;
943
944    assert.result-set-equal <include>a
945        : select include bar : <include>a <toolset>gcc ;
946
947    assert.result-set-equal <include>a <toolset>gcc
948        : select include <bar> <toolset> : <include>a <toolset>gcc ;
949
950    assert.result-set-equal <toolset>kylix <include>a
951        : change <toolset>gcc <include>a : <toolset> kylix ;
952
953    pm = [ new property-map ] ;
954    $(pm).insert <toolset>gcc : o ;
955    $(pm).insert <toolset>gcc <os>NT : obj ;
956    $(pm).insert <toolset>gcc <os>CYGWIN : obj ;
957
958    assert.equal o : [ $(pm).find-replace <toolset>gcc ] ;
959
960    assert.equal obj : [ $(pm).find-replace <toolset>gcc <os>NT ] ;
961
962    try ;
963        $(pm).find-replace <toolset>gcc <os>NT <os>CYGWIN ;
964    catch "Ambiguous key <toolset>gcc <os>NT <os>CYGWIN" ;
965
966    # Test ordinary properties.
967    assert.result : split-conditional <toolset>gcc ;
968
969    # Test properties with ":".
970    assert.result : split-conditional <define>"FOO=A::B" ;
971
972    # Test conditional feature.
973    assert.result-set-equal <toolset>gcc,<toolset-gcc:version>3.0 <define>FOO
974        : split-conditional <toolset>gcc,<toolset-gcc:version>3.0:<define>FOO ;
975
976    feature.finish-test property-test-temp ;
977}
978