Imported Upstream version 1.57.0
[platform/upstream/boost.git] / tools / build / src / build / property.jam
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
8 import feature ;
9 import indirect ;
10 import path ;
11 import regex ;
12 import string ;
13 import sequence ;
14 import set ;
15 import 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 #
22 rule 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 #
54 rule 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
123 rule 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 #
170 local 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
196 local 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
207 local rule identity ( string )
208 {
209     return $(string) ;
210 }
211
212
213 if --abbreviate-paths in [ modules.peek : ARGV ]
214 {
215     .abbrev = abbreviate-dashed ;
216 }
217 else
218 {
219     .abbrev = identity ;
220 }
221
222
223 # Returns a path representing the given expanded property set.
224 #
225 rule 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 $(p:G)
241             {
242                 local f = [ utility.ungrist $(p:G) ] ;
243                 p = $(f)-$(p:G=) ;
244             }
245             components += [ $(.abbrev) $(p) ] ;
246         }
247
248         $(entry) = $(components:J=/) ;
249     }
250
251     return $($(entry)) ;
252 }
253
254
255 # Exit with error if property is not valid.
256 #
257 local rule validate1 ( property )
258 {
259     local msg ;
260     if $(property:G)
261     {
262         local feature = $(property:G) ;
263         local value = $(property:G=) ;
264
265         if ! [ feature.valid $(feature) ]
266         {
267             # Ungrist for better error messages.
268             feature = [ utility.ungrist $(property:G) ] ;
269             msg = "unknown feature '$(feature)'" ;
270         }
271         else if $(value) && ! free in [ feature.attributes $(feature) ]
272         {
273             feature.validate-value-string $(feature) $(value) ;
274         }
275         else if ! ( $(value) || ( optional in [ feature.attributes $(feature) ] ) )
276         {
277             # Ungrist for better error messages.
278             feature = [ utility.ungrist $(property:G) ] ;
279             msg = "No value specified for feature '$(feature)'" ;
280         }
281     }
282     else
283     {
284         local feature = [ feature.implied-feature $(property) ] ;
285         feature.validate-value-string $(feature) $(property) ;
286     }
287     if $(msg)
288     {
289         import errors ;
290         errors.error "Invalid property "'$(property:J=" ")'": "$(msg:J=" "). ;
291     }
292 }
293
294
295 rule validate ( properties * )
296 {
297     for local p in $(properties)
298     {
299         validate1 $(p) ;
300     }
301 }
302
303
304 rule validate-property-sets ( property-sets * )
305 {
306     for local s in $(property-sets)
307     {
308         validate [ feature.split $(s) ] ;
309     }
310 }
311
312
313 # Expands any implicit property values in the given property 'specification' so
314 # they explicitly state their feature.
315 #
316 rule make ( specification * )
317 {
318     local result ;
319     for local e in $(specification)
320     {
321         if $(e:G)
322         {
323             result += $(e) ;
324         }
325         else if [ feature.is-implicit-value $(e) ]
326         {
327             local feature = [ feature.implied-feature $(e) ] ;
328             result += $(feature)$(e) ;
329         }
330         else
331         {
332             import errors ;
333             errors.error "'$(e)' is not a valid property specification" ;
334         }
335     }
336     return $(result) ;
337 }
338
339
340 # Returns a property set containing all the elements in 'properties' that do not
341 # have their attributes listed in 'attributes'.
342 #
343 rule remove ( attributes + : properties * )
344 {
345     local result ;
346     for local e in $(properties)
347     {
348         if ! [ set.intersection $(attributes) : [ feature.attributes $(e:G) ] ]
349         {
350             result += $(e) ;
351         }
352     }
353     return $(result) ;
354 }
355
356
357 # Returns a property set containing all the elements in 'properties' that have
358 # their attributes listed in 'attributes'.
359 #
360 rule take ( attributes + : properties * )
361 {
362     local result ;
363     for local e in $(properties)
364     {
365         if [ set.intersection $(attributes) : [ feature.attributes $(e:G) ] ]
366         {
367             result += $(e) ;
368         }
369     }
370     return $(result) ;
371 }
372
373
374 # Selects properties corresponding to any of the given features.
375 #
376 rule select ( features * : properties * )
377 {
378     local result ;
379
380     # Add any missing angle brackets.
381     local empty = "" ;
382     features = $(empty:G=$(features)) ;
383
384     for local p in $(properties)
385     {
386         if $(p:G) in $(features)
387         {
388             result += $(p) ;
389         }
390     }
391     return $(result) ;
392 }
393
394
395 # Returns a modified version of properties with all values of the given feature
396 # replaced by the given value. If 'value' is empty the feature will be removed.
397 #
398 rule change ( properties * : feature value ? )
399 {
400     local result ;
401     for local p in $(properties)
402     {
403         if $(p:G) = $(feature)
404         {
405             result += $(value:G=$(feature)) ;
406         }
407         else
408         {
409             result += $(p) ;
410         }
411     }
412     return $(result) ;
413 }
414
415
416 # If 'property' is a conditional property, returns the condition and the
417 # property. E.g. <variant>debug,<toolset>gcc:<inlining>full will become
418 # <variant>debug,<toolset>gcc <inlining>full. Otherwise, returns an empty
419 # string.
420 #
421 rule split-conditional ( property )
422 {
423     return [ MATCH "^(.+):(<.+)" : $(property) ] ;
424 }
425
426
427 rule translate-path-value ( value : path )
428 {
429     local t ;
430     for local v in [ regex.split $(value) "&&" ]
431     {
432         t += [ path.root [ path.make $(v) ] $(path) ] ;
433     }
434     return $(t:TJ="&&") ;
435 }
436
437 rule translate-dependency-value ( value : project-id : project-location )
438 {
439     local split-target = [ regex.match ^(.*)//(.*) : $(value) ] ;
440     if $(split-target)
441     {
442         local rooted = [ path.root [ path.make $(split-target[1]) ]
443                 [ path.root $(project-location) [ path.pwd ] ] ] ;
444         return $(rooted)//$(split-target[2]) ;
445     }
446     else if [ path.is-rooted $(value) ]
447     {
448         return $(value) ;
449     }
450     else
451     {
452         return $(project-id)//$(value) ;
453     }
454 }
455
456 rule translate-indirect-value ( rulename : context-module )
457 {
458     if [ MATCH "^([^%]*)%([^%]+)$" : $(rulename) ]
459     {
460         # Rule is already in the 'indirect-rule' format.
461         return @$(rulename) ;
462     }
463     else
464     {
465         local v ;
466         if ! [ MATCH "([.])" : $(rulename) ]
467         {
468             # This is an unqualified rule name. The user might want to
469             # set flags on this rule name and toolset.flag
470             # auto-qualifies it. Need to do the same here so flag
471             # setting works. We can arrange for toolset.flag to *not*
472             # auto-qualify the argument but then two rules defined in
473             # two Jamfiles would conflict.
474             rulename = $(context-module).$(rulename) ;
475         }
476         v = [ indirect.make $(rulename) : $(context-module) ] ;
477         return @$(v) ;
478     }
479
480 }
481
482 # Equivalent to a calling all of:
483 #   translate-path
484 #   translate-indirect
485 #   translate-dependency
486 #   expand-subfeatures-in-conditions
487 #   make
488 #
489 rule translate ( properties * : project-id : project-location : context-module )
490 {
491     local result ;
492     for local p in $(properties)
493     {
494         local split = [ split-conditional $(p) ] ;
495         local condition property ;
496
497         if $(split)
498         {
499             condition = $(split[1]) ;
500             property = $(split[2]) ;
501             
502             local e ;
503             for local c in [ regex.split $(condition) "," ]
504             {
505                 e += [ feature.expand-subfeatures $(c) : true ] ;
506             }
507
508             condition = $(e:J=,): ;
509         }
510         else
511         {
512             property = $(p) ;
513         }
514
515         local feature = $(property:G) ;
516         if ! $(feature)
517         {
518             if [ feature.is-implicit-value $(property) ]
519             {
520                 feature = [ feature.implied-feature $(property) ] ;
521                 result += $(condition:E=)$(feature)$(property) ;
522             }
523             else
524             {
525                 import errors ;
526                 errors.error "'$(property)' is not a valid property specification" ;
527             }
528         } else {
529             local attributes = [ feature.attributes $(feature) ] ;
530             local value ;
531             # Only free features should be translated
532             if free in $(attributes)
533             {
534                 if path in $(attributes)
535                 {
536                     value = [ translate-path-value $(property:G=) : $(project-location) ] ;
537                     result += $(condition:E=)$(feature)$(value) ;
538                 }
539                 else if dependency in $(attributes)
540                 {
541                     value = [ translate-dependency-value $(property:G=) : $(project-id) : $(project-location) ] ;
542                     result += $(condition:E=)$(feature)$(value) ;
543                 }
544                 else
545                 {
546                     local m = [ MATCH ^@(.+) : $(property:G=) ] ;
547                     if $(m)
548                     {
549                         value = [ translate-indirect-value $(m) : $(context-module) ] ;
550                         result += $(condition:E=)$(feature)$(value) ;
551                     }
552                     else
553                     {
554                         result += $(condition:E=)$(property) ;
555                     }
556                 }
557             }
558             else
559             {
560                 result += $(condition:E=)$(property) ;
561             }
562         }
563     }
564     return $(result) ;
565 }
566
567 # Interpret all path properties in 'properties' as relative to 'path'. The
568 # property values are assumed to be in system-specific form, and will be
569 # translated into normalized form.
570 #
571 rule translate-paths ( properties * : path )
572 {
573     local result ;
574     for local p in $(properties)
575     {
576         local split = [ split-conditional $(p) ] ;
577         local condition = "" ;
578         if $(split)
579         {
580             condition = $(split[1]): ;
581             p = $(split[2]) ;
582         }
583
584         if path in [ feature.attributes $(p:G) ]
585         {
586             local values = [ regex.split $(p:TG=) "&&" ] ;
587             local t ;
588             for local v in $(values)
589             {
590                 t += [ path.root [ path.make $(v) ] $(path) ] ;
591             }
592             t = $(t:J="&&") ;
593             result += $(condition)$(t:TG=$(p:G)) ;
594         }
595         else
596         {
597             result += $(condition)$(p) ;
598         }
599     }
600     return $(result) ;
601 }
602
603
604 # Assumes that all feature values that start with '@' are names of rules, used
605 # in 'context-module'. Such rules can be either local to the module or global.
606 # Converts such values into 'indirect-rule' format (see indirect.jam), so they
607 # can be called from other modules. Does nothing for such values that are
608 # already in the 'indirect-rule' format.
609 #
610 rule translate-indirect ( specification * : context-module )
611 {
612     local result ;
613     for local p in $(specification)
614     {
615         local m = [ MATCH ^@(.+) : $(p:G=) ] ;
616         if $(m)
617         {
618             local v ;
619             if [ MATCH "^([^%]*)%([^%]+)$" : $(m) ]
620             {
621                 # Rule is already in the 'indirect-rule' format.
622                 v = $(m) ;
623             }
624             else
625             {
626                 if ! [ MATCH "([.])" : $(m) ]
627                 {
628                     # This is an unqualified rule name. The user might want to
629                     # set flags on this rule name and toolset.flag
630                     # auto-qualifies it. Need to do the same here so flag
631                     # setting works. We can arrange for toolset.flag to *not*
632                     # auto-qualify the argument but then two rules defined in
633                     # two Jamfiles would conflict.
634                     m = $(context-module).$(m) ;
635                 }
636                 v = [ indirect.make $(m) : $(context-module) ] ;
637             }
638
639             v = @$(v) ;
640             result += $(v:G=$(p:G)) ;
641         }
642         else
643         {
644             result += $(p) ;
645         }
646     }
647     return $(result) ;
648 }
649
650
651 # Binds all dependency properties in a list relative to the given project.
652 # Targets with absolute paths will be left unchanged and targets which have a
653 # project specified will have the path to the project interpreted relative to
654 # the specified location.
655 #
656 rule translate-dependencies ( specification * : project-id : location )
657 {
658     local result ;
659     for local p in $(specification)
660     {
661         local split = [ split-conditional $(p) ] ;
662         local condition = "" ;
663         if $(split)
664         {
665             condition = $(split[1]): ;
666             p = $(split[2]) ;
667         }
668         if dependency in [ feature.attributes $(p:G) ]
669         {
670             local split-target = [ regex.match ^(.*)//(.*) : $(p:G=) ] ;
671             if $(split-target)
672             {
673                 local rooted = [ path.root [ path.make $(split-target[1]) ]
674                      [ path.root $(location) [ path.pwd ] ] ] ;
675                 result += $(condition)$(p:G)$(rooted)//$(split-target[2]) ;
676             }
677             else if [ path.is-rooted $(p:G=) ]
678             {
679                 result += $(condition)$(p) ;
680             }
681             else
682             {
683                 result += $(condition)$(p:G)$(project-id)//$(p:G=) ;
684             }
685         }
686         else
687         {
688             result += $(condition)$(p) ;
689         }
690     }
691     return $(result) ;
692 }
693
694
695 # Class maintaining a property set -> string mapping.
696 #
697 class property-map
698 {
699     import numbers ;
700     import sequence ;
701
702     rule __init__ ( )
703     {
704         self.next-flag = 1 ;
705     }
706
707     # Associate 'value' with 'properties'.
708     #
709     rule insert ( properties * : value )
710     {
711         self.all-flags += self.$(self.next-flag) ;
712         self.$(self.next-flag) = $(value) $(properties) ;
713
714         self.next-flag = [ numbers.increment $(self.next-flag) ] ;
715     }
716
717     # Returns the value associated with 'properties' or any subset of it. If
718     # more than one subset has a value assigned to it, returns the value for the
719     # longest subset, if it is unique.
720     #
721     rule find ( property-set )
722     {
723         # First find all matches.
724         local matches ;
725         local match-ranks ;
726         for local i in $(self.all-flags)
727         {
728             local list = $($(i)) ;
729             if [ $(property-set).contains-raw $(list[2-]) ] 
730             {
731                 matches += $(list[1]) ;
732                 match-ranks += [ sequence.length $(list) ] ;
733             }
734         }
735         local best = [ sequence.select-highest-ranked $(matches)
736             : $(match-ranks) ] ;
737         if $(best[2])
738         {
739             import errors : error : errors.error ;
740             errors.error "Ambiguous key $(properties:J= :E=)" ;
741         }
742         return $(best) ;
743     }
744
745     # Returns the value associated with 'properties'. If 'value' parameter is
746     # given, replaces the found value.
747     #
748     rule find-replace ( properties * : value ? )
749     {
750         # First find all matches.
751         local matches ;
752         local match-ranks ;
753         for local i in $(self.all-flags)
754         {
755             if $($(i)[2-]) in $(properties)
756             {
757                 matches += $(i) ;
758                 match-ranks += [ sequence.length $($(i)) ] ;
759             }
760         }
761         local best = [ sequence.select-highest-ranked $(matches)
762             : $(match-ranks) ] ;
763         if $(best[2])
764         {
765             import errors : error : errors.error ;
766             errors.error "Ambiguous key $(properties:J= :E=)" ;
767         }
768         local original = $($(best)[1]) ;
769         if $(value)-is-set
770         {
771             $(best) = $(value) $($(best)[2-]) ;
772         }
773         return $(original) ;
774     }
775 }
776
777
778 rule __test__ ( )
779 {
780     import assert ;
781     import "class" : new ;
782     import errors : try catch ;
783     import feature ;
784
785     # Local rules must be explicitly re-imported.
786     import property : path-order abbreviate-dashed ;
787
788     feature.prepare-test property-test-temp ;
789
790     feature.feature toolset : gcc : implicit symmetric ;
791     feature.subfeature toolset gcc : version : 2.95.2 2.95.3 2.95.4 3.0 3.0.1
792         3.0.2 : optional ;
793     feature.feature define : : free ;
794     feature.feature runtime-link : dynamic static : symmetric link-incompatible ;
795     feature.feature optimization : on off ;
796     feature.feature variant : debug release : implicit composite symmetric ;
797     feature.feature rtti : on off : link-incompatible ;
798
799     feature.compose <variant>debug : <define>_DEBUG <optimization>off ;
800     feature.compose <variant>release : <define>NDEBUG <optimization>on ;
801
802     validate <toolset>gcc <toolset>gcc-3.0.1 : $(test-space) ;
803
804     assert.true  path-order $(test-space) debug <define>foo ;
805     assert.false path-order $(test-space) <define>foo debug ;
806     assert.true  path-order $(test-space) gcc debug ;
807     assert.false path-order $(test-space) debug gcc ;
808     assert.true  path-order $(test-space) <optimization>on <rtti>on ;
809     assert.false path-order $(test-space) <rtti>on <optimization>on ;
810
811     assert.result-set-equal <toolset>gcc <rtti>off <define>FOO
812         : refine <toolset>gcc <rtti>off
813         : <define>FOO
814         : $(test-space) ;
815
816     assert.result-set-equal <toolset>gcc <optimization>on
817         : refine <toolset>gcc <optimization>off
818         : <optimization>on
819         : $(test-space) ;
820
821     assert.result-set-equal <toolset>gcc <rtti>off
822         : refine <toolset>gcc : <rtti>off : $(test-space) ;
823
824     assert.result-set-equal <toolset>gcc <rtti>off <rtti>off:<define>FOO
825         : refine <toolset>gcc : <rtti>off <rtti>off:<define>FOO
826         : $(test-space) ;
827
828     assert.result-set-equal <toolset>gcc:<define>foo <toolset>gcc:<define>bar
829         : refine <toolset>gcc:<define>foo : <toolset>gcc:<define>bar
830         : $(test-space) ;
831
832     assert.result <define>MY_RELEASE
833         : evaluate-conditionals-in-context
834           <variant>release,<rtti>off:<define>MY_RELEASE
835         : <toolset>gcc <variant>release <rtti>off ;
836
837     assert.result debug
838         : as-path <optimization>off <variant>debug
839         : $(test-space) ;
840
841     assert.result gcc/debug/rtti-off
842         : as-path <toolset>gcc <optimization>off <rtti>off <variant>debug
843         : $(test-space) ;
844
845     assert.result optmz-off : abbreviate-dashed optimization-off ;
846     assert.result rntm-lnk-sttc : abbreviate-dashed runtime-link-static ;
847
848     try ;
849         validate <feature>value : $(test-space) ;
850     catch "Invalid property '<feature>value': unknown feature 'feature'." ;
851
852     try ;
853         validate <rtti>default : $(test-space) ;
854     catch \"default\" is not a known value of feature <rtti> ;
855
856     validate <define>WHATEVER : $(test-space) ;
857
858     try ;
859         validate <rtti> : $(test-space) ;
860     catch "Invalid property '<rtti>': No value specified for feature 'rtti'." ;
861
862     try ;
863         validate value : $(test-space) ;
864     catch \"value\" is not an implicit feature value ;
865
866     assert.result-set-equal <rtti>on
867         : remove free implicit : <toolset>gcc <define>foo <rtti>on : $(test-space) ;
868
869     assert.result-set-equal <include>a
870         : select include : <include>a <toolset>gcc ;
871
872     assert.result-set-equal <include>a
873         : select include bar : <include>a <toolset>gcc ;
874
875     assert.result-set-equal <include>a <toolset>gcc
876         : select include <bar> <toolset> : <include>a <toolset>gcc ;
877
878     assert.result-set-equal <toolset>kylix <include>a
879         : change <toolset>gcc <include>a : <toolset> kylix ;
880
881     pm = [ new property-map ] ;
882     $(pm).insert <toolset>gcc : o ;
883     $(pm).insert <toolset>gcc <os>NT : obj ;
884     $(pm).insert <toolset>gcc <os>CYGWIN : obj ;
885
886     assert.equal o : [ $(pm).find-replace <toolset>gcc ] ;
887
888     assert.equal obj : [ $(pm).find-replace <toolset>gcc <os>NT ] ;
889
890     try ;
891         $(pm).find-replace <toolset>gcc <os>NT <os>CYGWIN ;
892     catch "Ambiguous key <toolset>gcc <os>NT <os>CYGWIN" ;
893
894     # Test ordinary properties.
895     assert.result : split-conditional <toolset>gcc ;
896
897     # Test properties with ":".
898     assert.result : split-conditional <define>FOO=A::B ;
899
900     # Test conditional feature.
901     assert.result-set-equal <toolset>gcc,<toolset-gcc:version>3.0 <define>FOO
902         : split-conditional <toolset>gcc,<toolset-gcc:version>3.0:<define>FOO ;
903
904     feature.finish-test property-test-temp ;
905 }