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