1# Status: ported. 2# Base revision: 40480 3 4# Copyright (C) Vladimir Prus 2002. Permission to copy, use, modify, sell and 5# distribute this software is granted provided this copyright notice appears in 6# all copies. This software is provided "as is" without express or implied 7# warranty, and with no claim as to its suitability for any purpose. 8 9import hashlib 10 11from b2.util.utility import * 12import property, feature 13import b2.build.feature 14from b2.exceptions import * 15from b2.build.property import get_abbreviated_paths 16from b2.util.sequence import unique 17from b2.util.set import difference 18from b2.util import cached, abbreviate_dashed 19 20from b2.manager import get_manager 21 22 23def reset (): 24 """ Clear the module state. This is mainly for testing purposes. 25 """ 26 global __cache 27 28 # A cache of property sets 29 # TODO: use a map of weak refs? 30 __cache = {} 31 32reset () 33 34 35def create (raw_properties = []): 36 """ Creates a new 'PropertySet' instance for the given raw properties, 37 or returns an already existing one. 38 """ 39 # FIXME: propagate to callers. 40 if len(raw_properties) > 0 and isinstance(raw_properties[0], property.Property): 41 x = raw_properties 42 else: 43 x = [property.create_from_string(ps) for ps in raw_properties] 44 x.sort() 45 x = unique(x, stable=True) 46 47 # FIXME: can we do better, e.g. by directly computing 48 # hash value of the list? 49 key = tuple(x) 50 51 if not __cache.has_key (key): 52 __cache [key] = PropertySet(x) 53 54 return __cache [key] 55 56def create_with_validation (raw_properties): 57 """ Creates new 'PropertySet' instances after checking 58 that all properties are valid and converting implicit 59 properties into gristed form. 60 """ 61 properties = [property.create_from_string(s) for s in raw_properties] 62 property.validate(properties) 63 64 return create(properties) 65 66def empty (): 67 """ Returns PropertySet with empty set of properties. 68 """ 69 return create () 70 71def create_from_user_input(raw_properties, jamfile_module, location): 72 """Creates a property-set from the input given by the user, in the 73 context of 'jamfile-module' at 'location'""" 74 75 properties = property.create_from_strings(raw_properties, True) 76 properties = property.translate_paths(properties, location) 77 properties = property.translate_indirect(properties, jamfile_module) 78 79 project_id = get_manager().projects().attributeDefault(jamfile_module, 'id', None) 80 if not project_id: 81 project_id = os.path.abspath(location) 82 properties = property.translate_dependencies(properties, project_id, location) 83 properties = property.expand_subfeatures_in_conditions(properties) 84 return create(properties) 85 86 87def refine_from_user_input(parent_requirements, specification, jamfile_module, 88 location): 89 """Refines requirements with requirements provided by the user. 90 Specially handles "-<property>value" syntax in specification 91 to remove given requirements. 92 - parent-requirements -- property-set object with requirements 93 to refine 94 - specification -- string list of requirements provided by the use 95 - project-module -- the module to which context indirect features 96 will be bound. 97 - location -- the path to which path features are relative.""" 98 99 100 if not specification: 101 return parent_requirements 102 103 104 add_requirements = [] 105 remove_requirements = [] 106 107 for r in specification: 108 if r[0] == '-': 109 remove_requirements.append(r[1:]) 110 else: 111 add_requirements.append(r) 112 113 if remove_requirements: 114 # Need to create property set, so that path features 115 # and indirect features are translated just like they 116 # are in project requirements. 117 ps = create_from_user_input(remove_requirements, 118 jamfile_module, location) 119 120 parent_requirements = create(difference(parent_requirements.all(), 121 ps.all())) 122 specification = add_requirements 123 124 requirements = create_from_user_input(specification, 125 jamfile_module, location) 126 127 return parent_requirements.refine(requirements) 128 129class PropertySet: 130 """ Class for storing a set of properties. 131 - there's 1<->1 correspondence between identity and value. No 132 two instances of the class are equal. To maintain this property, 133 the 'PropertySet.create' rule should be used to create new instances. 134 Instances are immutable. 135 136 - each property is classified with regard to it's effect on build 137 results. Incidental properties have no effect on build results, from 138 Boost.Build point of view. Others are either free, or non-free, which we 139 call 'base'. Each property belong to exactly one of those categories and 140 it's possible to get list of properties in each category. 141 142 In addition, it's possible to get list of properties with specific 143 attribute. 144 145 - several operations, like and refine and as_path are provided. They all use 146 caching whenever possible. 147 """ 148 def __init__ (self, properties = []): 149 150 151 raw_properties = [] 152 for p in properties: 153 raw_properties.append(p.to_raw()) 154 155 self.all_ = properties 156 self.all_raw_ = raw_properties 157 self.all_set_ = set(properties) 158 159 self.incidental_ = [] 160 self.free_ = [] 161 self.base_ = [] 162 self.dependency_ = [] 163 self.non_dependency_ = [] 164 self.conditional_ = [] 165 self.non_conditional_ = [] 166 self.propagated_ = [] 167 self.link_incompatible = [] 168 169 # A cache of refined properties. 170 self.refined_ = {} 171 172 # A cache of property sets created by adding properties to this one. 173 self.added_ = {} 174 175 # Cache for the default properties. 176 self.defaults_ = None 177 178 # Cache for the expanded properties. 179 self.expanded_ = None 180 181 # Cache for the expanded composite properties 182 self.composites_ = None 183 184 # Cache for property set with expanded subfeatures 185 self.subfeatures_ = None 186 187 # Cache for the property set containing propagated properties. 188 self.propagated_ps_ = None 189 190 # A map of features to its values. 191 self.feature_map_ = None 192 193 # A tuple (target path, is relative to build directory) 194 self.target_path_ = None 195 196 self.as_path_ = None 197 198 # A cache for already evaluated sets. 199 self.evaluated_ = {} 200 201 for p in raw_properties: 202 if not get_grist (p): 203 raise BaseException ("Invalid property: '%s'" % p) 204 205 att = feature.attributes (get_grist (p)) 206 207 if 'propagated' in att: 208 self.propagated_.append (p) 209 210 if 'link_incompatible' in att: 211 self.link_incompatible.append (p) 212 213 for p in properties: 214 215 # A feature can be both incidental and free, 216 # in which case we add it to incidental. 217 if p.feature().incidental(): 218 self.incidental_.append(p) 219 elif p.feature().free(): 220 self.free_.append(p) 221 else: 222 self.base_.append(p) 223 224 if p.condition(): 225 self.conditional_.append(p) 226 else: 227 self.non_conditional_.append(p) 228 229 if p.feature().dependency(): 230 self.dependency_.append (p) 231 else: 232 self.non_dependency_.append (p) 233 234 235 def all(self): 236 return self.all_ 237 238 def raw (self): 239 """ Returns the list of stored properties. 240 """ 241 return self.all_raw_ 242 243 def __str__(self): 244 return ' '.join(str(p) for p in self.all_) 245 246 def base (self): 247 """ Returns properties that are neither incidental nor free. 248 """ 249 return self.base_ 250 251 def free (self): 252 """ Returns free properties which are not dependency properties. 253 """ 254 return self.free_ 255 256 def non_free(self): 257 return self.base_ + self.incidental_ 258 259 def dependency (self): 260 """ Returns dependency properties. 261 """ 262 return self.dependency_ 263 264 def non_dependency (self): 265 """ Returns properties that are not dependencies. 266 """ 267 return self.non_dependency_ 268 269 def conditional (self): 270 """ Returns conditional properties. 271 """ 272 return self.conditional_ 273 274 def non_conditional (self): 275 """ Returns properties that are not conditional. 276 """ 277 return self.non_conditional_ 278 279 def incidental (self): 280 """ Returns incidental properties. 281 """ 282 return self.incidental_ 283 284 def refine (self, requirements): 285 """ Refines this set's properties using the requirements passed as an argument. 286 """ 287 assert isinstance(requirements, PropertySet) 288 if not self.refined_.has_key (requirements): 289 r = property.refine(self.all_, requirements.all_) 290 291 self.refined_[requirements] = create(r) 292 293 return self.refined_[requirements] 294 295 def expand (self): 296 if not self.expanded_: 297 expanded = feature.expand(self.all_) 298 self.expanded_ = create(expanded) 299 return self.expanded_ 300 301 def expand_subfeatures(self): 302 if not self.subfeatures_: 303 self.subfeatures_ = create(feature.expand_subfeatures(self.all_)) 304 return self.subfeatures_ 305 306 def evaluate_conditionals(self, context=None): 307 if not context: 308 context = self 309 310 if not self.evaluated_.has_key(context): 311 # FIXME: figure why the call messes up first parameter 312 self.evaluated_[context] = create( 313 property.evaluate_conditionals_in_context(self.all(), context)) 314 315 return self.evaluated_[context] 316 317 def propagated (self): 318 if not self.propagated_ps_: 319 self.propagated_ps_ = create (self.propagated_) 320 return self.propagated_ps_ 321 322 def add_defaults (self): 323 # FIXME: this caching is invalidated when new features 324 # are declare inside non-root Jamfiles. 325 if not self.defaults_: 326 expanded = feature.add_defaults(self.all_) 327 self.defaults_ = create(expanded) 328 return self.defaults_ 329 330 def as_path (self): 331 if not self.as_path_: 332 333 def path_order (p1, p2): 334 335 i1 = p1.feature().implicit() 336 i2 = p2.feature().implicit() 337 338 if i1 != i2: 339 return i2 - i1 340 else: 341 return cmp(p1.feature().name(), p2.feature().name()) 342 343 # trim redundancy 344 properties = feature.minimize(self.base_) 345 346 # sort according to path_order 347 properties.sort (path_order) 348 349 components = [] 350 for p in properties: 351 if p.feature().implicit(): 352 components.append(p.value()) 353 else: 354 value = p.feature().name() + "-" + p.value() 355 if property.get_abbreviated_paths(): 356 value = abbreviate_dashed(value) 357 components.append(value) 358 359 self.as_path_ = '/'.join (components) 360 361 return self.as_path_ 362 363 def target_path (self): 364 """ Computes the target path that should be used for 365 target with these properties. 366 Returns a tuple of 367 - the computed path 368 - if the path is relative to build directory, a value of 369 'true'. 370 """ 371 if not self.target_path_: 372 # The <location> feature can be used to explicitly 373 # change the location of generated targets 374 l = self.get ('<location>') 375 if l: 376 computed = l[0] 377 is_relative = False 378 379 else: 380 p = self.as_path() 381 if hash_maybe: 382 p = hash_maybe(p) 383 384 # Really, an ugly hack. Boost regression test system requires 385 # specific target paths, and it seems that changing it to handle 386 # other directory layout is really hard. For that reason, 387 # we teach V2 to do the things regression system requires. 388 # The value o '<location-prefix>' is predended to the path. 389 prefix = self.get ('<location-prefix>') 390 391 if prefix: 392 if len (prefix) > 1: 393 raise AlreadyDefined ("Two <location-prefix> properties specified: '%s'" % prefix) 394 395 computed = os.path.join(prefix[0], p) 396 397 else: 398 computed = p 399 400 if not computed: 401 computed = "." 402 403 is_relative = True 404 405 self.target_path_ = (computed, is_relative) 406 407 return self.target_path_ 408 409 def add (self, ps): 410 """ Creates a new property set containing the properties in this one, 411 plus the ones of the property set passed as argument. 412 """ 413 if not self.added_.has_key(ps): 414 self.added_[ps] = create(self.all_ + ps.all()) 415 return self.added_[ps] 416 417 def add_raw (self, properties): 418 """ Creates a new property set containing the properties in this one, 419 plus the ones passed as argument. 420 """ 421 return self.add (create (properties)) 422 423 424 def get (self, feature): 425 """ Returns all values of 'feature'. 426 """ 427 if type(feature) == type([]): 428 feature = feature[0] 429 if not isinstance(feature, b2.build.feature.Feature): 430 feature = b2.build.feature.get(feature) 431 432 if not self.feature_map_: 433 self.feature_map_ = {} 434 435 for v in self.all_: 436 if not self.feature_map_.has_key(v.feature()): 437 self.feature_map_[v.feature()] = [] 438 self.feature_map_[v.feature()].append(v.value()) 439 440 return self.feature_map_.get(feature, []) 441 442 @cached 443 def get_properties(self, feature): 444 """Returns all contained properties associated with 'feature'""" 445 446 if not isinstance(feature, b2.build.feature.Feature): 447 feature = b2.build.feature.get(feature) 448 449 result = [] 450 for p in self.all_: 451 if p.feature() == feature: 452 result.append(p) 453 return result 454 455 def __contains__(self, item): 456 return item in self.all_set_ 457 458def hash(p): 459 m = hashlib.md5() 460 m.update(p) 461 return m.hexdigest() 462 463hash_maybe = hash if "--hash" in bjam.variable("ARGV") else None 464 465