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 Boost.Build'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 path ; 38 import property ; 39 import property-set ; 40 import set ; 41 42 rule __init__ ( raw-properties * ) 43 { 44 self.raw = $(raw-properties) ; 45 46 for local p in $(raw-properties) 47 { 48 if ! $(p:G) 49 { 50 errors.error "Invalid property: '$(p)'" ; 51 } 52 } 53 } 54 55 # Returns Jam list of stored properties. 56 # 57 rule raw ( ) 58 { 59 return $(self.raw) ; 60 } 61 62 rule str ( ) 63 { 64 return "[" $(self.raw) "]" ; 65 } 66 67 # Returns properties that are neither incidental nor free. 68 # 69 rule base ( ) 70 { 71 if ! $(self.base-initialized) 72 { 73 init-base ; 74 } 75 return $(self.base) ; 76 } 77 78 # Returns free properties which are not incidental. 79 # 80 rule free ( ) 81 { 82 if ! $(self.base-initialized) 83 { 84 init-base ; 85 } 86 return $(self.free) ; 87 } 88 89 # Returns dependency properties. 90 # 91 rule dependency ( ) 92 { 93 if ! $(self.dependency-initialized) 94 { 95 init-dependency ; 96 } 97 return $(self.dependency) ; 98 } 99 100 rule non-dependency ( ) 101 { 102 if ! $(self.dependency-initialized) 103 { 104 init-dependency ; 105 } 106 return $(self.non-dependency) ; 107 } 108 109 rule conditional ( ) 110 { 111 if ! $(self.conditional-initialized) 112 { 113 init-conditional ; 114 } 115 return $(self.conditional) ; 116 } 117 118 rule non-conditional ( ) 119 { 120 if ! $(self.conditional-initialized) 121 { 122 init-conditional ; 123 } 124 return $(self.non-conditional) ; 125 } 126 127 # Returns incidental properties. 128 # 129 rule incidental ( ) 130 { 131 if ! $(self.base-initialized) 132 { 133 init-base ; 134 } 135 return $(self.incidental) ; 136 } 137 138 rule refine ( ps ) 139 { 140 if ! $(self.refined.$(ps)) 141 { 142 local r = [ property.refine $(self.raw) : [ $(ps).raw ] ] ; 143 if $(r[1]) != "@error" 144 { 145 self.refined.$(ps) = [ property-set.create $(r) ] ; 146 } 147 else 148 { 149 self.refined.$(ps) = $(r) ; 150 } 151 } 152 return $(self.refined.$(ps)) ; 153 } 154 155 rule expand ( ) 156 { 157 if ! $(self.expanded) 158 { 159 self.expanded = [ property-set.create [ feature.expand $(self.raw) ] 160 ] ; 161 } 162 return $(self.expanded) ; 163 } 164 165 rule expand-composites ( ) 166 { 167 if ! $(self.composites) 168 { 169 self.composites = [ property-set.create 170 [ feature.expand-composites $(self.raw) ] ] ; 171 } 172 return $(self.composites) ; 173 } 174 175 rule evaluate-conditionals ( context ? ) 176 { 177 context ?= $(__name__) ; 178 if ! $(self.evaluated.$(context)) 179 { 180 self.evaluated.$(context) = [ property-set.create 181 [ property.evaluate-conditionals-in-context $(self.raw) : [ 182 $(context).raw ] ] ] ; 183 } 184 return $(self.evaluated.$(context)) ; 185 } 186 187 rule propagated ( ) 188 { 189 if ! $(self.propagated-ps) 190 { 191 local result ; 192 for local p in $(self.raw) 193 { 194 if propagated in [ feature.attributes $(p:G) ] 195 { 196 result += $(p) ; 197 } 198 } 199 self.propagated-ps = [ property-set.create $(result) ] ; 200 } 201 return $(self.propagated-ps) ; 202 } 203 204 rule add-defaults ( ) 205 { 206 if ! $(self.defaults) 207 { 208 self.defaults = [ property-set.create 209 [ feature.add-defaults $(self.raw) ] ] ; 210 } 211 return $(self.defaults) ; 212 } 213 214 rule as-path ( ) 215 { 216 if ! $(self.as-path) 217 { 218 self.as-path = [ property.as-path [ base ] ] ; 219 } 220 return $(self.as-path) ; 221 } 222 223 # Computes the path to be used for a target with the given properties. 224 # Returns a list of 225 # - the computed path 226 # - if the path is relative to the build directory, a value of 'true'. 227 # 228 rule target-path ( ) 229 { 230 if ! $(self.target-path) 231 { 232 # The <location> feature can be used to explicitly change the 233 # location of generated targets. 234 local l = [ get <location> ] ; 235 if $(l) 236 { 237 self.target-path = $(l) ; 238 } 239 else 240 { 241 local p = [ property-set.hash-maybe [ as-path ] ] ; 242 243 # A real ugly hack. Boost regression test system requires 244 # specific target paths, and it seems that changing it to handle 245 # other directory layout is really hard. For that reason, we 246 # teach V2 to do the things regression system requires. The 247 # value of '<location-prefix>' is prepended to the path. 248 local prefix = [ get <location-prefix> ] ; 249 if $(prefix) 250 { 251 self.target-path = [ path.join $(prefix) $(p) ] ; 252 } 253 else 254 { 255 self.target-path = $(p) ; 256 } 257 if ! $(self.target-path) 258 { 259 self.target-path = . ; 260 } 261 # The path is relative to build dir. 262 self.target-path += true ; 263 } 264 } 265 return $(self.target-path) ; 266 } 267 268 rule add ( ps ) 269 { 270 if ! $(self.added.$(ps)) 271 { 272 self.added.$(ps) = [ property-set.create $(self.raw) [ $(ps).raw ] ] 273 ; 274 } 275 return $(self.added.$(ps)) ; 276 } 277 278 rule add-raw ( properties * ) 279 { 280 return [ add [ property-set.create $(properties) ] ] ; 281 } 282 283 # Returns all values of 'feature'. 284 # 285 rule get ( feature ) 286 { 287 if ! $(self.map-built) 288 { 289 # For each feature, create a member var and assign all values to it. 290 # Since all regular member vars start with 'self', there will be no 291 # conflicts between names. 292 self.map-built = true ; 293 for local v in $(self.raw) 294 { 295 $(v:G) += $(v:G=) ; 296 } 297 } 298 return $($(feature)) ; 299 } 300 301 # Returns true if the property-set contains all the 302 # specified properties. 303 # 304 rule contains-raw ( properties * ) 305 { 306 if $(properties) in $(self.raw) 307 { 308 return true ; 309 } 310 } 311 312 # Returns true if the property-set has values for 313 # all the specified features 314 # 315 rule contains-features ( features * ) 316 { 317 if $(features) in $(self.raw:G) 318 { 319 return true ; 320 } 321 } 322 323 # private 324 325 rule init-base ( ) 326 { 327 for local p in $(self.raw) 328 { 329 local att = [ feature.attributes $(p:G) ] ; 330 # A feature can be both incidental and free, in which case we add it 331 # to incidental. 332 if incidental in $(att) 333 { 334 self.incidental += $(p) ; 335 } 336 else if free in $(att) 337 { 338 self.free += $(p) ; 339 } 340 else 341 { 342 self.base += $(p) ; 343 } 344 } 345 self.base-initialized = true ; 346 } 347 348 rule init-dependency ( ) 349 { 350 for local p in $(self.raw) 351 { 352 if dependency in [ feature.attributes $(p:G) ] 353 { 354 self.dependency += $(p) ; 355 } 356 else 357 { 358 self.non-dependency += $(p) ; 359 } 360 } 361 self.dependency-initialized = true ; 362 } 363 364 rule init-conditional ( ) 365 { 366 for local p in $(self.raw) 367 { 368 # TODO: Note that non-conditional properties may contain colon (':') 369 # characters as well, e.g. free or indirect properties. Indirect 370 # properties for example contain a full Jamfile path in their value 371 # which on Windows file systems contains ':' as the drive separator. 372 if [ MATCH (:) : $(p:G=) ] 373 { 374 self.conditional += $(p) ; 375 } 376 else 377 { 378 self.non-conditional += $(p) ; 379 } 380 } 381 self.conditional-initialized = true ; 382 } 383} 384 385 386# Creates a new 'property-set' instance for the given raw properties or returns 387# an already existing ones. 388# 389rule create ( raw-properties * ) 390{ 391 raw-properties = [ sequence.unique 392 [ sequence.insertion-sort $(raw-properties) ] ] ; 393 394 local key = $(raw-properties:J=-:E=) ; 395 396 if ! $(.ps.$(key)) 397 { 398 .ps.$(key) = [ new property-set $(raw-properties) ] ; 399 } 400 return $(.ps.$(key)) ; 401} 402NATIVE_RULE property-set : create ; 403 404if [ HAS_NATIVE_RULE class@property-set : get : 1 ] 405{ 406 NATIVE_RULE class@property-set : get ; 407} 408 409if [ HAS_NATIVE_RULE class@property-set : contains-features : 1 ] 410{ 411 NATIVE_RULE class@property-set : contains-features ; 412} 413 414# Creates a new 'property-set' instance after checking that all properties are 415# valid and converting implicit properties into gristed form. 416# 417rule create-with-validation ( raw-properties * ) 418{ 419 property.validate $(raw-properties) ; 420 return [ create [ property.make $(raw-properties) ] ] ; 421} 422 423 424# Creates a property-set from the input given by the user, in the context of 425# 'jamfile-module' at 'location'. 426# 427rule create-from-user-input ( raw-properties * : jamfile-module location ) 428{ 429 local project-id = [ project.attribute $(jamfile-module) id ] ; 430 project-id ?= [ path.root $(location) [ path.pwd ] ] ; 431 return [ property-set.create [ property.translate $(raw-properties) 432 : $(project-id) : $(location) : $(jamfile-module) ] ] ; 433} 434 435 436# Refines requirements with requirements provided by the user. Specially handles 437# "-<property>value" syntax in specification to remove given requirements. 438# - parent-requirements -- property-set object with requirements to refine. 439# - specification -- string list of requirements provided by the user. 440# - project-module -- module to which context indirect features will be 441# bound. 442# - location -- path to which path features are relative. 443# 444rule refine-from-user-input ( parent-requirements : specification * : 445 project-module : location ) 446{ 447 if ! $(specification) 448 { 449 return $(parent-requirements) ; 450 } 451 else 452 { 453 local add-requirements ; 454 local remove-requirements ; 455 456 for local r in $(specification) 457 { 458 local m = [ MATCH "^-(.*)" : $(r) ] ; 459 if $(m) 460 { 461 remove-requirements += $(m) ; 462 } 463 else 464 { 465 add-requirements += $(r) ; 466 } 467 } 468 469 if $(remove-requirements) 470 { 471 # Need to create a property set, so that path features and indirect 472 # features are translated just like they are in project 473 # requirements. 474 local ps = [ property-set.create-from-user-input 475 $(remove-requirements) : $(project-module) $(location) ] ; 476 477 parent-requirements = [ property-set.create 478 [ set.difference [ $(parent-requirements).raw ] 479 : [ $(ps).raw ] ] ] ; 480 specification = $(add-requirements) ; 481 } 482 483 local requirements = [ property-set.create-from-user-input 484 $(specification) : $(project-module) $(location) ] ; 485 486 return [ $(parent-requirements).refine $(requirements) ] ; 487 } 488} 489 490 491# Returns a property-set with an empty set of properties. 492# 493rule empty ( ) 494{ 495 if ! $(.empty) 496 { 497 .empty = [ create ] ; 498 } 499 return $(.empty) ; 500} 501 502 503if [ option.get hash : : yes ] = yes 504{ 505 rule hash-maybe ( path ? ) 506 { 507 path ?= "" ; 508 return [ MD5 $(path) ] ; 509 } 510} 511else 512{ 513 rule hash-maybe ( path ? ) 514 { 515 return $(path) ; 516 } 517} 518