]>
Commit | Line | Data |
---|---|---|
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 ! 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 | # | |
260 | local 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 | ||
298 | rule validate ( properties * ) | |
299 | { | |
300 | for local p in $(properties) | |
301 | { | |
302 | validate1 $(p) ; | |
303 | } | |
304 | } | |
305 | ||
306 | ||
307 | rule 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 | # | |
319 | rule 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 | # | |
346 | rule 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 | # | |
363 | rule 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 | # | |
379 | rule 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 | # | |
401 | rule 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 | # | |
424 | rule split-conditional ( property ) | |
425 | { | |
426 | return [ MATCH "^(.+):(<.+)" : $(property) ] ; | |
427 | } | |
428 | ||
429 | ||
430 | rule 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 | ||
440 | rule 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 | ||
459 | rule 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 | # | |
492 | rule 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 | # | |
574 | rule 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 | # | |
613 | rule 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 | # | |
659 | rule 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 | # | |
700 | class 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 | ||
781 | rule __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 | } |