1# Status: ported, except for tests. 2# Base revision: 64070 3# 4# Copyright 2001, 2002, 2003 Dave Abrahams 5# Copyright 2006 Rene Rivera 6# Copyright 2002, 2003, 2004, 2005, 2006 Vladimir Prus 7# Distributed under the Boost Software License, Version 1.0. 8# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) 9 10import re 11import sys 12from b2.util.utility import * 13from b2.build import feature 14from b2.util import sequence, qualify_jam_action 15import b2.util.set 16from b2.manager import get_manager 17 18__re_two_ampersands = re.compile ('&&') 19__re_comma = re.compile (',') 20__re_split_condition = re.compile ('(.*):(<.*)') 21__re_split_conditional = re.compile (r'(.+):<(.+)') 22__re_colon = re.compile (':') 23__re_has_condition = re.compile (r':<') 24__re_separate_condition_and_property = re.compile (r'(.*):(<.*)') 25 26__not_applicable_feature='not-applicable-in-this-context' 27feature.feature(__not_applicable_feature, [], ['free']) 28 29__abbreviated_paths = False 30 31class Property(object): 32 33 __slots__ = ('_feature', '_value', '_condition') 34 35 def __init__(self, f, value, condition = []): 36 if type(f) == type(""): 37 f = feature.get(f) 38 # At present, single property has a single value. 39 assert type(value) != type([]) 40 assert(f.free() or value.find(':') == -1) 41 self._feature = f 42 self._value = value 43 self._condition = condition 44 45 def feature(self): 46 return self._feature 47 48 def value(self): 49 return self._value 50 51 def condition(self): 52 return self._condition 53 54 def to_raw(self): 55 result = "<" + self._feature.name() + ">" + str(self._value) 56 if self._condition: 57 result = ",".join(str(p) for p in self._condition) + ':' + result 58 return result 59 60 def __str__(self): 61 return self.to_raw() 62 63 def __hash__(self): 64 # FIXME: consider if this class should be value-is-identity one 65 return hash((self._feature, self._value, tuple(self._condition))) 66 67 def __cmp__(self, other): 68 return cmp((self._feature.name(), self._value, self._condition), 69 (other._feature.name(), other._value, other._condition)) 70 71 72def create_from_string(s, allow_condition=False,allow_missing_value=False): 73 74 condition = [] 75 import types 76 if not isinstance(s, types.StringType): 77 print type(s) 78 if __re_has_condition.search(s): 79 80 if not allow_condition: 81 raise BaseException("Conditional property is not allowed in this context") 82 83 m = __re_separate_condition_and_property.match(s) 84 condition = m.group(1) 85 s = m.group(2) 86 87 # FIXME: break dependency cycle 88 from b2.manager import get_manager 89 90 feature_name = get_grist(s) 91 if not feature_name: 92 if feature.is_implicit_value(s): 93 f = feature.implied_feature(s) 94 value = s 95 else: 96 raise get_manager().errors()("Invalid property '%s' -- unknown feature" % s) 97 else: 98 if feature.valid(feature_name): 99 f = feature.get(feature_name) 100 value = get_value(s) 101 else: 102 # In case feature name is not known, it is wrong to do a hard error. 103 # Feature sets change depending on the toolset. So e.g. 104 # <toolset-X:version> is an unknown feature when using toolset Y. 105 # 106 # Ideally we would like to ignore this value, but most of 107 # Boost.Build code expects that we return a valid Property. For this 108 # reason we use a sentinel <not-applicable-in-this-context> feature. 109 # 110 # The underlying cause for this problem is that python port Property 111 # is more strict than its Jam counterpart and must always reference 112 # a valid feature. 113 f = feature.get(__not_applicable_feature) 114 value = s 115 116 if not value and not allow_missing_value: 117 get_manager().errors()("Invalid property '%s' -- no value specified" % s) 118 119 120 if condition: 121 condition = [create_from_string(x) for x in condition.split(',')] 122 123 return Property(f, value, condition) 124 125def create_from_strings(string_list, allow_condition=False): 126 127 return [create_from_string(s, allow_condition) for s in string_list] 128 129def reset (): 130 """ Clear the module state. This is mainly for testing purposes. 131 """ 132 global __results 133 134 # A cache of results from as_path 135 __results = {} 136 137reset () 138 139 140def set_abbreviated_paths(on=True): 141 global __abbreviated_paths 142 __abbreviated_paths = on 143 144 145def get_abbreviated_paths(): 146 return __abbreviated_paths or '--abbreviated-paths' in sys.argv 147 148 149def path_order (x, y): 150 """ Helper for as_path, below. Orders properties with the implicit ones 151 first, and within the two sections in alphabetical order of feature 152 name. 153 """ 154 if x == y: 155 return 0 156 157 xg = get_grist (x) 158 yg = get_grist (y) 159 160 if yg and not xg: 161 return -1 162 163 elif xg and not yg: 164 return 1 165 166 else: 167 if not xg: 168 x = feature.expand_subfeatures([x]) 169 y = feature.expand_subfeatures([y]) 170 171 if x < y: 172 return -1 173 elif x > y: 174 return 1 175 else: 176 return 0 177 178def identify(string): 179 return string 180 181# Uses Property 182def refine (properties, requirements): 183 """ Refines 'properties' by overriding any non-free properties 184 for which a different value is specified in 'requirements'. 185 Conditional requirements are just added without modification. 186 Returns the resulting list of properties. 187 """ 188 # The result has no duplicates, so we store it in a set 189 result = set() 190 191 # Records all requirements. 192 required = {} 193 194 # All the elements of requirements should be present in the result 195 # Record them so that we can handle 'properties'. 196 for r in requirements: 197 # Don't consider conditional requirements. 198 if not r.condition(): 199 required[r.feature()] = r 200 201 for p in properties: 202 # Skip conditional properties 203 if p.condition(): 204 result.add(p) 205 # No processing for free properties 206 elif p.feature().free(): 207 result.add(p) 208 else: 209 if required.has_key(p.feature()): 210 result.add(required[p.feature()]) 211 else: 212 result.add(p) 213 214 return sequence.unique(list(result) + requirements) 215 216def translate_paths (properties, path): 217 """ Interpret all path properties in 'properties' as relative to 'path' 218 The property values are assumed to be in system-specific form, and 219 will be translated into normalized form. 220 """ 221 result = [] 222 223 for p in properties: 224 225 if p.feature().path(): 226 values = __re_two_ampersands.split(p.value()) 227 228 new_value = "&&".join(os.path.join(path, v) for v in values) 229 230 if new_value != p.value(): 231 result.append(Property(p.feature(), new_value, p.condition())) 232 else: 233 result.append(p) 234 235 else: 236 result.append (p) 237 238 return result 239 240def translate_indirect(properties, context_module): 241 """Assumes that all feature values that start with '@' are 242 names of rules, used in 'context-module'. Such rules can be 243 either local to the module or global. Qualified local rules 244 with the name of the module.""" 245 result = [] 246 for p in properties: 247 if p.value()[0] == '@': 248 q = qualify_jam_action(p.value()[1:], context_module) 249 get_manager().engine().register_bjam_action(q) 250 result.append(Property(p.feature(), '@' + q, p.condition())) 251 else: 252 result.append(p) 253 254 return result 255 256def validate (properties): 257 """ Exit with error if any of the properties is not valid. 258 properties may be a single property or a sequence of properties. 259 """ 260 261 if isinstance (properties, str): 262 __validate1 (properties) 263 else: 264 for p in properties: 265 __validate1 (p) 266 267def expand_subfeatures_in_conditions (properties): 268 269 result = [] 270 for p in properties: 271 272 if not p.condition(): 273 result.append(p) 274 else: 275 expanded = [] 276 for c in p.condition(): 277 278 if c.feature().name().startswith("toolset") or c.feature().name() == "os": 279 # It common that condition includes a toolset which 280 # was never defined, or mentiones subfeatures which 281 # were never defined. In that case, validation will 282 # only produce an spirious error, so don't validate. 283 expanded.extend(feature.expand_subfeatures ([c], True)) 284 else: 285 expanded.extend(feature.expand_subfeatures([c])) 286 287 result.append(Property(p.feature(), p.value(), expanded)) 288 289 return result 290 291# FIXME: this should go 292def split_conditional (property): 293 """ If 'property' is conditional property, returns 294 condition and the property, e.g 295 <variant>debug,<toolset>gcc:<inlining>full will become 296 <variant>debug,<toolset>gcc <inlining>full. 297 Otherwise, returns empty string. 298 """ 299 m = __re_split_conditional.match (property) 300 301 if m: 302 return (m.group (1), '<' + m.group (2)) 303 304 return None 305 306 307def select (features, properties): 308 """ Selects properties which correspond to any of the given features. 309 """ 310 result = [] 311 312 # add any missing angle brackets 313 features = add_grist (features) 314 315 return [p for p in properties if get_grist(p) in features] 316 317def validate_property_sets (sets): 318 for s in sets: 319 validate(s.all()) 320 321def evaluate_conditionals_in_context (properties, context): 322 """ Removes all conditional properties which conditions are not met 323 For those with met conditions, removes the condition. Properies 324 in conditions are looked up in 'context' 325 """ 326 base = [] 327 conditional = [] 328 329 for p in properties: 330 if p.condition(): 331 conditional.append (p) 332 else: 333 base.append (p) 334 335 result = base[:] 336 for p in conditional: 337 338 # Evaluate condition 339 # FIXME: probably inefficient 340 if all(x in context for x in p.condition()): 341 result.append(Property(p.feature(), p.value())) 342 343 return result 344 345 346def change (properties, feature, value = None): 347 """ Returns a modified version of properties with all values of the 348 given feature replaced by the given value. 349 If 'value' is None the feature will be removed. 350 """ 351 result = [] 352 353 feature = add_grist (feature) 354 355 for p in properties: 356 if get_grist (p) == feature: 357 if value: 358 result.append (replace_grist (value, feature)) 359 360 else: 361 result.append (p) 362 363 return result 364 365 366################################################################ 367# Private functions 368 369def __validate1 (property): 370 """ Exit with error if property is not valid. 371 """ 372 msg = None 373 374 if not property.feature().free(): 375 feature.validate_value_string (property.feature(), property.value()) 376 377 378################################################################### 379# Still to port. 380# Original lines are prefixed with "# " 381# 382# 383# import utility : ungrist ; 384# import sequence : unique ; 385# import errors : error ; 386# import feature ; 387# import regex ; 388# import sequence ; 389# import set ; 390# import path ; 391# import assert ; 392# 393# 394 395 396# rule validate-property-sets ( property-sets * ) 397# { 398# for local s in $(property-sets) 399# { 400# validate [ feature.split $(s) ] ; 401# } 402# } 403# 404 405def remove(attributes, properties): 406 """Returns a property sets which include all the elements 407 in 'properties' that do not have attributes listed in 'attributes'.""" 408 409 result = [] 410 for e in properties: 411 attributes_new = feature.attributes(get_grist(e)) 412 has_common_features = 0 413 for a in attributes_new: 414 if a in attributes: 415 has_common_features = 1 416 break 417 418 if not has_common_features: 419 result += e 420 421 return result 422 423 424def take(attributes, properties): 425 """Returns a property set which include all 426 properties in 'properties' that have any of 'attributes'.""" 427 result = [] 428 for e in properties: 429 if b2.util.set.intersection(attributes, feature.attributes(get_grist(e))): 430 result.append(e) 431 return result 432 433def translate_dependencies(properties, project_id, location): 434 435 result = [] 436 for p in properties: 437 438 if not p.feature().dependency(): 439 result.append(p) 440 else: 441 v = p.value() 442 m = re.match("(.*)//(.*)", v) 443 if m: 444 rooted = m.group(1) 445 if rooted[0] == '/': 446 # Either project id or absolute Linux path, do nothing. 447 pass 448 else: 449 rooted = os.path.join(os.getcwd(), location, rooted) 450 451 result.append(Property(p.feature(), rooted + "//" + m.group(2), p.condition())) 452 453 elif os.path.isabs(v): 454 result.append(p) 455 else: 456 result.append(Property(p.feature(), project_id + "//" + v, p.condition())) 457 458 return result 459 460 461class PropertyMap: 462 """ Class which maintains a property set -> string mapping. 463 """ 464 def __init__ (self): 465 self.__properties = [] 466 self.__values = [] 467 468 def insert (self, properties, value): 469 """ Associate value with properties. 470 """ 471 self.__properties.append(properties) 472 self.__values.append(value) 473 474 def find (self, properties): 475 """ Return the value associated with properties 476 or any subset of it. If more than one 477 subset has value assigned to it, return the 478 value for the longest subset, if it's unique. 479 """ 480 return self.find_replace (properties) 481 482 def find_replace(self, properties, value=None): 483 matches = [] 484 match_ranks = [] 485 486 for i in range(0, len(self.__properties)): 487 p = self.__properties[i] 488 489 if b2.util.set.contains (p, properties): 490 matches.append (i) 491 match_ranks.append(len(p)) 492 493 best = sequence.select_highest_ranked (matches, match_ranks) 494 495 if not best: 496 return None 497 498 if len (best) > 1: 499 raise NoBestMatchingAlternative () 500 501 best = best [0] 502 503 original = self.__values[best] 504 505 if value: 506 self.__values[best] = value 507 508 return original 509 510# local rule __test__ ( ) 511# { 512# import errors : try catch ; 513# import feature ; 514# import feature : feature subfeature compose ; 515# 516# # local rules must be explicitly re-imported 517# import property : path-order ; 518# 519# feature.prepare-test property-test-temp ; 520# 521# feature toolset : gcc : implicit symmetric ; 522# subfeature toolset gcc : version : 2.95.2 2.95.3 2.95.4 523# 3.0 3.0.1 3.0.2 : optional ; 524# feature define : : free ; 525# feature runtime-link : dynamic static : symmetric link-incompatible ; 526# feature optimization : on off ; 527# feature variant : debug release : implicit composite symmetric ; 528# feature rtti : on off : link-incompatible ; 529# 530# compose <variant>debug : <define>_DEBUG <optimization>off ; 531# compose <variant>release : <define>NDEBUG <optimization>on ; 532# 533# import assert ; 534# import "class" : new ; 535# 536# validate <toolset>gcc <toolset>gcc-3.0.1 : $(test-space) ; 537# 538# assert.result <toolset>gcc <rtti>off <define>FOO 539# : refine <toolset>gcc <rtti>off 540# : <define>FOO 541# : $(test-space) 542# ; 543# 544# assert.result <toolset>gcc <optimization>on 545# : refine <toolset>gcc <optimization>off 546# : <optimization>on 547# : $(test-space) 548# ; 549# 550# assert.result <toolset>gcc <rtti>off 551# : refine <toolset>gcc : <rtti>off : $(test-space) 552# ; 553# 554# assert.result <toolset>gcc <rtti>off <rtti>off:<define>FOO 555# : refine <toolset>gcc : <rtti>off <rtti>off:<define>FOO 556# : $(test-space) 557# ; 558# 559# assert.result <toolset>gcc:<define>foo <toolset>gcc:<define>bar 560# : refine <toolset>gcc:<define>foo : <toolset>gcc:<define>bar 561# : $(test-space) 562# ; 563# 564# assert.result <define>MY_RELEASE 565# : evaluate-conditionals-in-context 566# <variant>release,<rtti>off:<define>MY_RELEASE 567# : <toolset>gcc <variant>release <rtti>off 568# 569# ; 570# 571# try ; 572# validate <feature>value : $(test-space) ; 573# catch "Invalid property '<feature>value': unknown feature 'feature'." ; 574# 575# try ; 576# validate <rtti>default : $(test-space) ; 577# catch \"default\" is not a known value of feature <rtti> ; 578# 579# validate <define>WHATEVER : $(test-space) ; 580# 581# try ; 582# validate <rtti> : $(test-space) ; 583# catch "Invalid property '<rtti>': No value specified for feature 'rtti'." ; 584# 585# try ; 586# validate value : $(test-space) ; 587# catch "value" is not a value of an implicit feature ; 588# 589# 590# assert.result <rtti>on 591# : remove free implicit : <toolset>gcc <define>foo <rtti>on : $(test-space) ; 592# 593# assert.result <include>a 594# : select include : <include>a <toolset>gcc ; 595# 596# assert.result <include>a 597# : select include bar : <include>a <toolset>gcc ; 598# 599# assert.result <include>a <toolset>gcc 600# : select include <bar> <toolset> : <include>a <toolset>gcc ; 601# 602# assert.result <toolset>kylix <include>a 603# : change <toolset>gcc <include>a : <toolset> kylix ; 604# 605# # Test ordinary properties 606# assert.result 607# : split-conditional <toolset>gcc 608# ; 609# 610# # Test properties with ":" 611# assert.result 612# : split-conditional <define>FOO=A::B 613# ; 614# 615# # Test conditional feature 616# assert.result <toolset>gcc,<toolset-gcc:version>3.0 <define>FOO 617# : split-conditional <toolset>gcc,<toolset-gcc:version>3.0:<define>FOO 618# ; 619# 620# feature.finish-test property-test-temp ; 621# } 622# 623 624