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