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