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