1# Status: being ported by Vladimir Prus 2# Base revision: 40958 3# 4# Copyright 2003 Dave Abrahams 5# Copyright 2005 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 10""" Support for toolset definition. 11""" 12import sys 13 14import feature, property, generators, property_set 15import b2.util.set 16import bjam 17 18from b2.util import cached, qualify_jam_action, is_iterable_typed, is_iterable 19from b2.util.utility import * 20from b2.util import bjam_signature, sequence 21from b2.manager import get_manager 22 23__re_split_last_segment = re.compile (r'^(.+)\.([^\.])*') 24__re_two_ampersands = re.compile ('(&&)') 25__re_first_segment = re.compile ('([^.]*).*') 26__re_first_group = re.compile (r'[^.]*\.(.*)') 27_ignore_toolset_requirements = '--ignore-toolset-requirements' not in sys.argv 28 29# Flag is a mechanism to set a value 30# A single toolset flag. Specifies that when certain 31# properties are in build property set, certain values 32# should be appended to some variable. 33# 34# A flag applies to a specific action in specific module. 35# The list of all flags for a module is stored, and each 36# flag further contains the name of the rule it applies 37# for, 38class Flag: 39 40 def __init__(self, variable_name, values, condition, rule = None): 41 assert isinstance(variable_name, basestring) 42 assert is_iterable(values) and all( 43 isinstance(v, (basestring, type(None))) for v in values) 44 assert is_iterable_typed(condition, property_set.PropertySet) 45 assert isinstance(rule, (basestring, type(None))) 46 self.variable_name = variable_name 47 self.values = values 48 self.condition = condition 49 self.rule = rule 50 51 def __str__(self): 52 return("Flag(" + str(self.variable_name) + ", " + str(self.values) +\ 53 ", " + str(self.condition) + ", " + str(self.rule) + ")") 54 55def reset (): 56 """ Clear the module state. This is mainly for testing purposes. 57 """ 58 global __module_flags, __flags, __stv 59 60 # Mapping from module name to a list of all flags that apply 61 # to either that module directly, or to any rule in that module. 62 # Each element of the list is Flag instance. 63 # So, for module named xxx this might contain flags for 'xxx', 64 # for 'xxx.compile', for 'xxx.compile.c++', etc. 65 __module_flags = {} 66 67 # Mapping from specific rule or module name to a list of Flag instances 68 # that apply to that name. 69 # Say, it might contain flags for 'xxx.compile.c++'. If there are 70 # entries for module name 'xxx', they are flags for 'xxx' itself, 71 # not including any rules in that module. 72 __flags = {} 73 74 # A cache for variable settings. The key is generated from the rule name and the properties. 75 __stv = {} 76 77reset () 78 79# FIXME: --ignore-toolset-requirements 80def using(toolset_module, *args): 81 if isinstance(toolset_module, (list, tuple)): 82 toolset_module = toolset_module[0] 83 loaded_toolset_module= get_manager().projects().load_module(toolset_module, [os.getcwd()]); 84 loaded_toolset_module.init(*args) 85 86# FIXME push-checking-for-flags-module .... 87# FIXME: investigate existing uses of 'hack-hack' parameter 88# in jam code. 89 90@bjam_signature((["rule_or_module", "variable_name", "condition", "*"], 91 ["values", "*"])) 92def flags(rule_or_module, variable_name, condition, values = []): 93 """ Specifies the flags (variables) that must be set on targets under certain 94 conditions, described by arguments. 95 rule_or_module: If contains dot, should be a rule name. 96 The flags will be applied when that rule is 97 used to set up build actions. 98 99 If does not contain dot, should be a module name. 100 The flags will be applied for all rules in that 101 module. 102 If module for rule is different from the calling 103 module, an error is issued. 104 105 variable_name: Variable that should be set on target 106 107 condition A condition when this flag should be applied. 108 Should be set of property sets. If one of 109 those property sets is contained in build 110 properties, the flag will be used. 111 Implied values are not allowed: 112 "<toolset>gcc" should be used, not just 113 "gcc". Subfeatures, like in "<toolset>gcc-3.2" 114 are allowed. If left empty, the flag will 115 always used. 116 117 Property sets may use value-less properties 118 ('<a>' vs. '<a>value') to match absent 119 properties. This allows to separately match 120 121 <architecture>/<address-model>64 122 <architecture>ia64/<address-model> 123 124 Where both features are optional. Without this 125 syntax we'd be forced to define "default" value. 126 127 values: The value to add to variable. If <feature> 128 is specified, then the value of 'feature' 129 will be added. 130 """ 131 assert isinstance(rule_or_module, basestring) 132 assert isinstance(variable_name, basestring) 133 assert is_iterable_typed(condition, basestring) 134 assert is_iterable(values) and all(isinstance(v, (basestring, type(None))) for v in values) 135 caller = bjam.caller() 136 if not '.' in rule_or_module and caller and caller[:-1].startswith("Jamfile"): 137 # Unqualified rule name, used inside Jamfile. Most likely used with 138 # 'make' or 'notfile' rules. This prevents setting flags on the entire 139 # Jamfile module (this will be considered as rule), but who cares? 140 # Probably, 'flags' rule should be split into 'flags' and 141 # 'flags-on-module'. 142 rule_or_module = qualify_jam_action(rule_or_module, caller) 143 else: 144 # FIXME: revive checking that we don't set flags for a different 145 # module unintentionally 146 pass 147 148 if condition and not replace_grist (condition, ''): 149 # We have condition in the form '<feature>', that is, without 150 # value. That's a previous syntax: 151 # 152 # flags gcc.link RPATH <dll-path> ; 153 # for compatibility, convert it to 154 # flags gcc.link RPATH : <dll-path> ; 155 values = [ condition ] 156 condition = None 157 158 if condition: 159 transformed = [] 160 for c in condition: 161 # FIXME: 'split' might be a too raw tool here. 162 pl = [property.create_from_string(s,False,True) for s in c.split('/')] 163 pl = feature.expand_subfeatures(pl); 164 transformed.append(property_set.create(pl)) 165 condition = transformed 166 167 property.validate_property_sets(condition) 168 169 __add_flag (rule_or_module, variable_name, condition, values) 170 171def set_target_variables (manager, rule_or_module, targets, ps): 172 """ 173 """ 174 assert isinstance(rule_or_module, basestring) 175 assert is_iterable_typed(targets, basestring) 176 assert isinstance(ps, property_set.PropertySet) 177 settings = __set_target_variables_aux(manager, rule_or_module, ps) 178 179 if settings: 180 for s in settings: 181 for target in targets: 182 manager.engine ().set_target_variable (target, s [0], s[1], True) 183 184def find_satisfied_condition(conditions, ps): 185 """Returns the first element of 'property-sets' which is a subset of 186 'properties', or an empty list if no such element exists.""" 187 assert is_iterable_typed(conditions, property_set.PropertySet) 188 assert isinstance(ps, property_set.PropertySet) 189 190 for condition in conditions: 191 192 found_all = True 193 for i in condition.all(): 194 195 if i.value: 196 found = i.value in ps.get(i.feature) 197 else: 198 # Handle value-less properties like '<architecture>' (compare with 199 # '<architecture>x86'). 200 # If $(i) is a value-less property it should match default 201 # value of an optional property. See the first line in the 202 # example below: 203 # 204 # property set properties result 205 # <a> <b>foo <b>foo match 206 # <a> <b>foo <a>foo <b>foo no match 207 # <a>foo <b>foo <b>foo no match 208 # <a>foo <b>foo <a>foo <b>foo match 209 found = not ps.get(i.feature) 210 211 found_all = found_all and found 212 213 if found_all: 214 return condition 215 216 return None 217 218 219def register (toolset): 220 """ Registers a new toolset. 221 """ 222 assert isinstance(toolset, basestring) 223 feature.extend('toolset', [toolset]) 224 225def inherit_generators (toolset, properties, base, generators_to_ignore = []): 226 assert isinstance(toolset, basestring) 227 assert is_iterable_typed(properties, basestring) 228 assert isinstance(base, basestring) 229 assert is_iterable_typed(generators_to_ignore, basestring) 230 if not properties: 231 properties = [replace_grist (toolset, '<toolset>')] 232 233 base_generators = generators.generators_for_toolset(base) 234 235 for g in base_generators: 236 id = g.id() 237 238 if not id in generators_to_ignore: 239 # Some generator names have multiple periods in their name, so 240 # $(id:B=$(toolset)) doesn't generate the right new_id name. 241 # e.g. if id = gcc.compile.c++, $(id:B=darwin) = darwin.c++, 242 # which is not what we want. Manually parse the base and suffix 243 # (if there's a better way to do this, I'd love to see it.) 244 # See also register in module generators. 245 (base, suffix) = split_action_id(id) 246 247 new_id = toolset + '.' + suffix 248 249 generators.register(g.clone(new_id, properties)) 250 251def inherit_flags(toolset, base, prohibited_properties = []): 252 """Brings all flag definitions from the 'base' toolset into the 'toolset' 253 toolset. Flag definitions whose conditions make use of properties in 254 'prohibited-properties' are ignored. Don't confuse property and feature, for 255 example <debug-symbols>on and <debug-symbols>off, so blocking one of them does 256 not block the other one. 257 258 The flag conditions are not altered at all, so if a condition includes a name, 259 or version of a base toolset, it won't ever match the inheriting toolset. When 260 such flag settings must be inherited, define a rule in base toolset module and 261 call it as needed.""" 262 assert isinstance(toolset, basestring) 263 assert isinstance(base, basestring) 264 assert is_iterable_typed(prohibited_properties, basestring) 265 for f in __module_flags.get(base, []): 266 267 if not f.condition or b2.util.set.difference(f.condition, prohibited_properties): 268 match = __re_first_group.match(f.rule) 269 rule_ = None 270 if match: 271 rule_ = match.group(1) 272 273 new_rule_or_module = '' 274 275 if rule_: 276 new_rule_or_module = toolset + '.' + rule_ 277 else: 278 new_rule_or_module = toolset 279 280 __add_flag (new_rule_or_module, f.variable_name, f.condition, f.values) 281 282 283def inherit_rules(toolset, base): 284 engine = get_manager().engine() 285 new_actions = {} 286 for action_name, action in engine.actions.iteritems(): 287 module, id = split_action_id(action_name) 288 if module == base: 289 new_action_name = toolset + '.' + id 290 # make sure not to override any existing actions 291 # that may have been declared already 292 if new_action_name not in engine.actions: 293 new_actions[new_action_name] = action 294 295 engine.actions.update(new_actions) 296 297###################################################################################### 298# Private functions 299 300@cached 301def __set_target_variables_aux (manager, rule_or_module, ps): 302 """ Given a rule name and a property set, returns a list of tuples of 303 variables names and values, which must be set on targets for that 304 rule/properties combination. 305 """ 306 assert isinstance(rule_or_module, basestring) 307 assert isinstance(ps, property_set.PropertySet) 308 result = [] 309 310 for f in __flags.get(rule_or_module, []): 311 312 if not f.condition or find_satisfied_condition (f.condition, ps): 313 processed = [] 314 for v in f.values: 315 # The value might be <feature-name> so needs special 316 # treatment. 317 processed += __handle_flag_value (manager, v, ps) 318 319 for r in processed: 320 result.append ((f.variable_name, r)) 321 322 # strip away last dot separated part and recurse. 323 next = __re_split_last_segment.match(rule_or_module) 324 325 if next: 326 result.extend(__set_target_variables_aux( 327 manager, next.group(1), ps)) 328 329 return result 330 331def __handle_flag_value (manager, value, ps): 332 assert isinstance(value, basestring) 333 assert isinstance(ps, property_set.PropertySet) 334 result = [] 335 336 if get_grist (value): 337 f = feature.get(value) 338 values = ps.get(f) 339 340 for value in values: 341 342 if f.dependency: 343 # the value of a dependency feature is a target 344 # and must be actualized 345 result.append(value.actualize()) 346 347 elif f.path or f.free: 348 349 # Treat features with && in the value 350 # specially -- each &&-separated element is considered 351 # separate value. This is needed to handle searched 352 # libraries, which must be in specific order. 353 if not __re_two_ampersands.search(value): 354 result.append(value) 355 356 else: 357 result.extend(value.split ('&&')) 358 else: 359 result.append (value) 360 else: 361 result.append (value) 362 363 return sequence.unique(result, stable=True) 364 365def __add_flag (rule_or_module, variable_name, condition, values): 366 """ Adds a new flag setting with the specified values. 367 Does no checking. 368 """ 369 assert isinstance(rule_or_module, basestring) 370 assert isinstance(variable_name, basestring) 371 assert is_iterable_typed(condition, property_set.PropertySet) 372 assert is_iterable(values) and all( 373 isinstance(v, (basestring, type(None))) for v in values) 374 f = Flag(variable_name, values, condition, rule_or_module) 375 376 # Grab the name of the module 377 m = __re_first_segment.match (rule_or_module) 378 assert m 379 module = m.group(1) 380 381 __module_flags.setdefault(module, []).append(f) 382 __flags.setdefault(rule_or_module, []).append(f) 383 384__requirements = [] 385 386def requirements(): 387 """Return the list of global 'toolset requirements'. 388 Those requirements will be automatically added to the requirements of any main target.""" 389 return __requirements 390 391def add_requirements(requirements): 392 """Adds elements to the list of global 'toolset requirements'. The requirements 393 will be automatically added to the requirements for all main targets, as if 394 they were specified literally. For best results, all requirements added should 395 be conditional or indirect conditional.""" 396 assert is_iterable_typed(requirements, basestring) 397 398 if _ignore_toolset_requirements: 399 __requirements.extend(requirements) 400 401 402# Make toolset 'toolset', defined in a module of the same name, 403# inherit from 'base' 404# 1. The 'init' rule from 'base' is imported into 'toolset' with full 405# name. Another 'init' is called, which forwards to the base one. 406# 2. All generators from 'base' are cloned. The ids are adjusted and 407# <toolset> property in requires is adjusted too 408# 3. All flags are inherited 409# 4. All rules are imported. 410def inherit(toolset, base): 411 assert isinstance(toolset, basestring) 412 assert isinstance(base, basestring) 413 get_manager().projects().load_module(base, ['.']); 414 415 inherit_generators(toolset, [], base) 416 inherit_flags(toolset, base) 417 inherit_rules(toolset, base) 418