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