]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | # Copyright 2003 Dave Abrahams |
2 | # Copyright 2003, 2004, 2005, 2006 Vladimir Prus | |
3 | # Distributed under the Boost Software License, Version 1.0. | |
1e59de90 TL |
4 | # (See accompanying file LICENSE.txt or copy at |
5 | # https://www.bfgroup.xyz/b2/LICENSE.txt) | |
7c673cae FG |
6 | |
7 | import "class" : new ; | |
8 | import feature ; | |
1e59de90 | 9 | import indirect ; |
7c673cae FG |
10 | import path ; |
11 | import project ; | |
12 | import property ; | |
13 | import sequence ; | |
14 | import set ; | |
15 | import option ; | |
16 | ||
17 | # Class for storing a set of properties. | |
18 | # | |
19 | # There is 1<->1 correspondence between identity and value. No two instances | |
20 | # of the class are equal. To maintain this property, the 'property-set.create' | |
21 | # rule should be used to create new instances. Instances are immutable. | |
22 | # | |
23 | # Each property is classified with regard to its effect on build results. | |
f67539c2 | 24 | # Incidental properties have no effect on build results, from B2's |
7c673cae FG |
25 | # point of view. Others are either free, or non-free and we refer to non-free |
26 | # ones as 'base'. Each property belongs to exactly one of those categories. | |
27 | # | |
28 | # It is possible to get a list of properties belonging to each category as | |
29 | # well as a list of properties with a specific attribute. | |
30 | # | |
31 | # Several operations, like and refine and as-path are provided. They all use | |
32 | # caching whenever possible. | |
33 | # | |
34 | class property-set | |
35 | { | |
36 | import errors ; | |
37 | import feature ; | |
11fdf7f2 | 38 | import modules ; |
7c673cae FG |
39 | import path ; |
40 | import property ; | |
41 | import property-set ; | |
42 | import set ; | |
43 | ||
44 | rule __init__ ( raw-properties * ) | |
45 | { | |
46 | self.raw = $(raw-properties) ; | |
47 | ||
48 | for local p in $(raw-properties) | |
49 | { | |
50 | if ! $(p:G) | |
51 | { | |
52 | errors.error "Invalid property: '$(p)'" ; | |
53 | } | |
54 | } | |
55 | } | |
56 | ||
57 | # Returns Jam list of stored properties. | |
58 | # | |
59 | rule raw ( ) | |
60 | { | |
61 | return $(self.raw) ; | |
62 | } | |
63 | ||
64 | rule str ( ) | |
65 | { | |
66 | return "[" $(self.raw) "]" ; | |
67 | } | |
68 | ||
69 | # Returns properties that are neither incidental nor free. | |
70 | # | |
71 | rule base ( ) | |
72 | { | |
73 | if ! $(self.base-initialized) | |
74 | { | |
75 | init-base ; | |
76 | } | |
77 | return $(self.base) ; | |
78 | } | |
79 | ||
80 | # Returns free properties which are not incidental. | |
81 | # | |
82 | rule free ( ) | |
83 | { | |
84 | if ! $(self.base-initialized) | |
85 | { | |
86 | init-base ; | |
87 | } | |
88 | return $(self.free) ; | |
89 | } | |
90 | ||
11fdf7f2 TL |
91 | # Returns relevant base properties. This is used for computing |
92 | # target paths, so it must return the expanded set of relevant | |
93 | # properties. | |
94 | # | |
95 | rule base-relevant ( ) | |
96 | { | |
97 | if ! $(self.relevant-initialized) | |
98 | { | |
99 | init-relevant ; | |
100 | } | |
101 | return $(self.base-relevant) ; | |
102 | } | |
103 | ||
104 | # Returns all properties marked as relevant by features-ps | |
105 | # Does not attempt to expand features-ps in any way, as | |
106 | # this matches what virtual-target.register needs. | |
107 | # | |
108 | rule relevant ( features-ps ) | |
109 | { | |
110 | if ! $(self.relevant.$(features-ps)) | |
111 | { | |
112 | local result ; | |
113 | local features = [ $(features-ps).get <relevant> ] ; | |
114 | features = <$(features)> ; | |
115 | local ignore-relevance = [ modules.peek | |
116 | property-set : .ignore-relevance ] ; | |
117 | for local p in $(self.raw) | |
118 | { | |
119 | if $(ignore-relevance) || $(p:G) in $(features) | |
120 | { | |
121 | local att = [ feature.attributes $(p:G) ] ; | |
122 | if ! ( incidental in $(att) ) | |
123 | { | |
124 | result += $(p) ; | |
125 | } | |
126 | } | |
127 | } | |
128 | self.relevant.$(features-ps) = [ property-set.create $(result) ] ; | |
129 | } | |
130 | return $(self.relevant.$(features-ps)) ; | |
131 | } | |
132 | ||
7c673cae FG |
133 | # Returns dependency properties. |
134 | # | |
135 | rule dependency ( ) | |
136 | { | |
137 | if ! $(self.dependency-initialized) | |
138 | { | |
139 | init-dependency ; | |
140 | } | |
141 | return $(self.dependency) ; | |
142 | } | |
143 | ||
144 | rule non-dependency ( ) | |
145 | { | |
146 | if ! $(self.dependency-initialized) | |
147 | { | |
148 | init-dependency ; | |
149 | } | |
150 | return $(self.non-dependency) ; | |
151 | } | |
152 | ||
153 | rule conditional ( ) | |
154 | { | |
155 | if ! $(self.conditional-initialized) | |
156 | { | |
157 | init-conditional ; | |
158 | } | |
159 | return $(self.conditional) ; | |
160 | } | |
161 | ||
162 | rule non-conditional ( ) | |
163 | { | |
164 | if ! $(self.conditional-initialized) | |
165 | { | |
166 | init-conditional ; | |
167 | } | |
168 | return $(self.non-conditional) ; | |
169 | } | |
170 | ||
171 | # Returns incidental properties. | |
172 | # | |
173 | rule incidental ( ) | |
174 | { | |
175 | if ! $(self.base-initialized) | |
176 | { | |
177 | init-base ; | |
178 | } | |
179 | return $(self.incidental) ; | |
180 | } | |
181 | ||
182 | rule refine ( ps ) | |
183 | { | |
184 | if ! $(self.refined.$(ps)) | |
185 | { | |
186 | local r = [ property.refine $(self.raw) : [ $(ps).raw ] ] ; | |
187 | if $(r[1]) != "@error" | |
188 | { | |
189 | self.refined.$(ps) = [ property-set.create $(r) ] ; | |
190 | } | |
191 | else | |
192 | { | |
193 | self.refined.$(ps) = $(r) ; | |
194 | } | |
195 | } | |
196 | return $(self.refined.$(ps)) ; | |
197 | } | |
198 | ||
199 | rule expand ( ) | |
200 | { | |
201 | if ! $(self.expanded) | |
202 | { | |
203 | self.expanded = [ property-set.create [ feature.expand $(self.raw) ] | |
204 | ] ; | |
205 | } | |
206 | return $(self.expanded) ; | |
207 | } | |
208 | ||
209 | rule expand-composites ( ) | |
210 | { | |
211 | if ! $(self.composites) | |
212 | { | |
213 | self.composites = [ property-set.create | |
214 | [ feature.expand-composites $(self.raw) ] ] ; | |
215 | } | |
216 | return $(self.composites) ; | |
217 | } | |
218 | ||
219 | rule evaluate-conditionals ( context ? ) | |
220 | { | |
221 | context ?= $(__name__) ; | |
222 | if ! $(self.evaluated.$(context)) | |
223 | { | |
224 | self.evaluated.$(context) = [ property-set.create | |
225 | [ property.evaluate-conditionals-in-context $(self.raw) : [ | |
226 | $(context).raw ] ] ] ; | |
227 | } | |
228 | return $(self.evaluated.$(context)) ; | |
229 | } | |
230 | ||
231 | rule propagated ( ) | |
232 | { | |
233 | if ! $(self.propagated-ps) | |
234 | { | |
235 | local result ; | |
236 | for local p in $(self.raw) | |
237 | { | |
238 | if propagated in [ feature.attributes $(p:G) ] | |
239 | { | |
240 | result += $(p) ; | |
241 | } | |
242 | } | |
243 | self.propagated-ps = [ property-set.create $(result) ] ; | |
244 | } | |
245 | return $(self.propagated-ps) ; | |
246 | } | |
247 | ||
248 | rule add-defaults ( ) | |
249 | { | |
250 | if ! $(self.defaults) | |
251 | { | |
252 | self.defaults = [ property-set.create | |
253 | [ feature.add-defaults $(self.raw) ] ] ; | |
254 | } | |
255 | return $(self.defaults) ; | |
256 | } | |
257 | ||
258 | rule as-path ( ) | |
259 | { | |
260 | if ! $(self.as-path) | |
261 | { | |
11fdf7f2 | 262 | self.as-path = [ property.as-path [ base-relevant ] ] ; |
7c673cae FG |
263 | } |
264 | return $(self.as-path) ; | |
265 | } | |
266 | ||
267 | # Computes the path to be used for a target with the given properties. | |
268 | # Returns a list of | |
269 | # - the computed path | |
270 | # - if the path is relative to the build directory, a value of 'true'. | |
271 | # | |
272 | rule target-path ( ) | |
273 | { | |
274 | if ! $(self.target-path) | |
275 | { | |
276 | # The <location> feature can be used to explicitly change the | |
277 | # location of generated targets. | |
278 | local l = [ get <location> ] ; | |
279 | if $(l) | |
280 | { | |
281 | self.target-path = $(l) ; | |
282 | } | |
283 | else | |
284 | { | |
285 | local p = [ property-set.hash-maybe [ as-path ] ] ; | |
286 | ||
287 | # A real ugly hack. Boost regression test system requires | |
288 | # specific target paths, and it seems that changing it to handle | |
289 | # other directory layout is really hard. For that reason, we | |
290 | # teach V2 to do the things regression system requires. The | |
291 | # value of '<location-prefix>' is prepended to the path. | |
292 | local prefix = [ get <location-prefix> ] ; | |
293 | if $(prefix) | |
294 | { | |
295 | self.target-path = [ path.join $(prefix) $(p) ] ; | |
296 | } | |
297 | else | |
298 | { | |
299 | self.target-path = $(p) ; | |
300 | } | |
301 | if ! $(self.target-path) | |
302 | { | |
303 | self.target-path = . ; | |
304 | } | |
305 | # The path is relative to build dir. | |
306 | self.target-path += true ; | |
307 | } | |
308 | } | |
309 | return $(self.target-path) ; | |
310 | } | |
311 | ||
312 | rule add ( ps ) | |
313 | { | |
314 | if ! $(self.added.$(ps)) | |
315 | { | |
316 | self.added.$(ps) = [ property-set.create $(self.raw) [ $(ps).raw ] ] | |
317 | ; | |
318 | } | |
319 | return $(self.added.$(ps)) ; | |
320 | } | |
321 | ||
322 | rule add-raw ( properties * ) | |
323 | { | |
324 | return [ add [ property-set.create $(properties) ] ] ; | |
325 | } | |
326 | ||
327 | # Returns all values of 'feature'. | |
328 | # | |
329 | rule get ( feature ) | |
330 | { | |
331 | if ! $(self.map-built) | |
332 | { | |
333 | # For each feature, create a member var and assign all values to it. | |
334 | # Since all regular member vars start with 'self', there will be no | |
335 | # conflicts between names. | |
336 | self.map-built = true ; | |
337 | for local v in $(self.raw) | |
338 | { | |
339 | $(v:G) += $(v:G=) ; | |
340 | } | |
341 | } | |
342 | return $($(feature)) ; | |
343 | } | |
344 | ||
345 | # Returns true if the property-set contains all the | |
346 | # specified properties. | |
347 | # | |
348 | rule contains-raw ( properties * ) | |
349 | { | |
350 | if $(properties) in $(self.raw) | |
351 | { | |
352 | return true ; | |
353 | } | |
354 | } | |
355 | ||
356 | # Returns true if the property-set has values for | |
357 | # all the specified features | |
358 | # | |
359 | rule contains-features ( features * ) | |
360 | { | |
361 | if $(features) in $(self.raw:G) | |
362 | { | |
363 | return true ; | |
364 | } | |
365 | } | |
366 | ||
367 | # private | |
368 | ||
369 | rule init-base ( ) | |
370 | { | |
371 | for local p in $(self.raw) | |
372 | { | |
373 | local att = [ feature.attributes $(p:G) ] ; | |
374 | # A feature can be both incidental and free, in which case we add it | |
375 | # to incidental. | |
376 | if incidental in $(att) | |
377 | { | |
378 | self.incidental += $(p) ; | |
379 | } | |
380 | else if free in $(att) | |
381 | { | |
382 | self.free += $(p) ; | |
383 | } | |
384 | else | |
385 | { | |
386 | self.base += $(p) ; | |
387 | } | |
388 | } | |
389 | self.base-initialized = true ; | |
390 | } | |
391 | ||
11fdf7f2 TL |
392 | rule init-relevant ( ) |
393 | { | |
394 | local relevant-features = [ get <relevant> ] ; | |
395 | relevant-features = [ feature.expand-relevant $(relevant-features) ] ; | |
396 | relevant-features = <$(relevant-features)> ; | |
397 | ignore-relevance = [ modules.peek property-set : .ignore-relevance ] ; | |
398 | for local p in $(self.raw) | |
399 | { | |
400 | if $(ignore-relevance) || $(p:G) in $(relevant-features) | |
401 | { | |
402 | local att = [ feature.attributes $(p:G) ] ; | |
f67539c2 | 403 | if ! ( incidental in $(att) ) |
11fdf7f2 TL |
404 | { |
405 | self.relevant += $(p) ; | |
406 | if ! ( free in $(att) ) | |
407 | { | |
408 | self.base-relevant += $(p) ; | |
409 | } | |
410 | } | |
411 | } | |
412 | } | |
413 | self.relevant-initialized = true ; | |
414 | } | |
415 | ||
7c673cae FG |
416 | rule init-dependency ( ) |
417 | { | |
418 | for local p in $(self.raw) | |
419 | { | |
420 | if dependency in [ feature.attributes $(p:G) ] | |
421 | { | |
422 | self.dependency += $(p) ; | |
423 | } | |
424 | else | |
425 | { | |
426 | self.non-dependency += $(p) ; | |
427 | } | |
428 | } | |
429 | self.dependency-initialized = true ; | |
430 | } | |
431 | ||
432 | rule init-conditional ( ) | |
433 | { | |
434 | for local p in $(self.raw) | |
435 | { | |
436 | # TODO: Note that non-conditional properties may contain colon (':') | |
437 | # characters as well, e.g. free or indirect properties. Indirect | |
438 | # properties for example contain a full Jamfile path in their value | |
439 | # which on Windows file systems contains ':' as the drive separator. | |
11fdf7f2 | 440 | if ( [ MATCH "(:)" : $(p:G=) ] && ! ( free in [ feature.attributes $(p:G) ] ) ) || $(p:G) = <conditional> |
7c673cae FG |
441 | { |
442 | self.conditional += $(p) ; | |
443 | } | |
444 | else | |
445 | { | |
446 | self.non-conditional += $(p) ; | |
447 | } | |
448 | } | |
449 | self.conditional-initialized = true ; | |
450 | } | |
451 | } | |
452 | ||
11fdf7f2 TL |
453 | # This is a temporary measure to help users work around |
454 | # any problems. Remove it once we've verified that | |
455 | # everything works. | |
456 | if --ignore-relevance in [ modules.peek : ARGV ] | |
457 | { | |
458 | .ignore-relevance = true ; | |
459 | } | |
7c673cae FG |
460 | |
461 | # Creates a new 'property-set' instance for the given raw properties or returns | |
462 | # an already existing ones. | |
463 | # | |
464 | rule create ( raw-properties * ) | |
465 | { | |
466 | raw-properties = [ sequence.unique | |
467 | [ sequence.insertion-sort $(raw-properties) ] ] ; | |
468 | ||
469 | local key = $(raw-properties:J=-:E=) ; | |
470 | ||
471 | if ! $(.ps.$(key)) | |
472 | { | |
473 | .ps.$(key) = [ new property-set $(raw-properties) ] ; | |
474 | } | |
475 | return $(.ps.$(key)) ; | |
476 | } | |
477 | NATIVE_RULE property-set : create ; | |
478 | ||
479 | if [ HAS_NATIVE_RULE class@property-set : get : 1 ] | |
480 | { | |
481 | NATIVE_RULE class@property-set : get ; | |
482 | } | |
483 | ||
484 | if [ HAS_NATIVE_RULE class@property-set : contains-features : 1 ] | |
485 | { | |
486 | NATIVE_RULE class@property-set : contains-features ; | |
487 | } | |
488 | ||
489 | # Creates a new 'property-set' instance after checking that all properties are | |
490 | # valid and converting implicit properties into gristed form. | |
491 | # | |
492 | rule create-with-validation ( raw-properties * ) | |
493 | { | |
494 | property.validate $(raw-properties) ; | |
495 | return [ create [ property.make $(raw-properties) ] ] ; | |
496 | } | |
497 | ||
498 | ||
499 | # Creates a property-set from the input given by the user, in the context of | |
500 | # 'jamfile-module' at 'location'. | |
501 | # | |
502 | rule create-from-user-input ( raw-properties * : jamfile-module location ) | |
503 | { | |
504 | local project-id = [ project.attribute $(jamfile-module) id ] ; | |
505 | project-id ?= [ path.root $(location) [ path.pwd ] ] ; | |
506 | return [ property-set.create [ property.translate $(raw-properties) | |
507 | : $(project-id) : $(location) : $(jamfile-module) ] ] ; | |
508 | } | |
509 | ||
510 | ||
511 | # Refines requirements with requirements provided by the user. Specially handles | |
512 | # "-<property>value" syntax in specification to remove given requirements. | |
513 | # - parent-requirements -- property-set object with requirements to refine. | |
514 | # - specification -- string list of requirements provided by the user. | |
515 | # - project-module -- module to which context indirect features will be | |
516 | # bound. | |
517 | # - location -- path to which path features are relative. | |
518 | # | |
519 | rule refine-from-user-input ( parent-requirements : specification * : | |
520 | project-module : location ) | |
521 | { | |
522 | if ! $(specification) | |
523 | { | |
524 | return $(parent-requirements) ; | |
525 | } | |
526 | else | |
527 | { | |
528 | local add-requirements ; | |
529 | local remove-requirements ; | |
530 | ||
531 | for local r in $(specification) | |
532 | { | |
533 | local m = [ MATCH "^-(.*)" : $(r) ] ; | |
534 | if $(m) | |
535 | { | |
536 | remove-requirements += $(m) ; | |
537 | } | |
538 | else | |
539 | { | |
540 | add-requirements += $(r) ; | |
541 | } | |
542 | } | |
543 | ||
544 | if $(remove-requirements) | |
545 | { | |
546 | # Need to create a property set, so that path features and indirect | |
547 | # features are translated just like they are in project | |
548 | # requirements. | |
549 | local ps = [ property-set.create-from-user-input | |
550 | $(remove-requirements) : $(project-module) $(location) ] ; | |
551 | ||
552 | parent-requirements = [ property-set.create | |
1e59de90 TL |
553 | [ set.difference |
554 | [ indirect.difference | |
555 | [ $(parent-requirements).raw ] : [ $(ps).raw ] ] | |
556 | : [ $(ps).raw ] | |
557 | ] ] ; | |
7c673cae FG |
558 | specification = $(add-requirements) ; |
559 | } | |
560 | ||
561 | local requirements = [ property-set.create-from-user-input | |
562 | $(specification) : $(project-module) $(location) ] ; | |
563 | ||
564 | return [ $(parent-requirements).refine $(requirements) ] ; | |
565 | } | |
566 | } | |
567 | ||
568 | ||
569 | # Returns a property-set with an empty set of properties. | |
570 | # | |
571 | rule empty ( ) | |
572 | { | |
573 | if ! $(.empty) | |
574 | { | |
575 | .empty = [ create ] ; | |
576 | } | |
577 | return $(.empty) ; | |
578 | } | |
579 | ||
580 | ||
581 | if [ option.get hash : : yes ] = yes | |
582 | { | |
583 | rule hash-maybe ( path ? ) | |
584 | { | |
585 | path ?= "" ; | |
586 | return [ MD5 $(path) ] ; | |
587 | } | |
588 | } | |
589 | else | |
590 | { | |
591 | rule hash-maybe ( path ? ) | |
592 | { | |
593 | return $(path) ; | |
594 | } | |
595 | } | |
20effc67 TL |
596 | |
597 | rule __test__ ( ) | |
598 | { | |
599 | import errors : try catch ; | |
600 | ||
601 | try ; | |
602 | create invalid-property ; | |
603 | catch "Invalid property: 'invalid-property'" ; | |
604 | } |