]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
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 | """ | |
12 | import sys | |
13 | ||
14 | import feature, property, generators, property_set | |
15 | import b2.util.set | |
16 | import bjam | |
17 | ||
18 | from b2.util import cached, qualify_jam_action, is_iterable_typed, is_iterable | |
19 | from b2.util.utility import * | |
20 | from b2.util import bjam_signature, sequence | |
21 | from 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, | |
38 | class 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 | ||
55 | def 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 | ||
92f5a8d4 | 74 | # A cache for variable settings. The key is generated from the rule name and the properties. |
7c673cae FG |
75 | __stv = {} |
76 | ||
77 | reset () | |
78 | ||
79 | # FIXME: --ignore-toolset-requirements | |
80 | def 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", "*"])) | |
92 | def 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 | ||
92f5a8d4 | 117 | Property sets may use value-less properties |
7c673cae FG |
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 | ||
171 | def 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 | ||
184 | def 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 | ||
219 | def register (toolset): | |
220 | """ Registers a new toolset. | |
221 | """ | |
222 | assert isinstance(toolset, basestring) | |
223 | feature.extend('toolset', [toolset]) | |
224 | ||
225 | def 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 | ||
251 | def 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 | ||
283 | def 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 | |
301 | def __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 | ||
331 | def __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 | ||
365 | def __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 | ||
386 | def 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 | ||
391 | def 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. | |
410 | def 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) |