Skip to content

Commit ce0606a

Browse files
committed
[build tools] Added better support for features and recursive configs
per @screamerbg
1 parent b4f3e12 commit ce0606a

File tree

3 files changed

+101
-34
lines changed

3 files changed

+101
-34
lines changed

tools/build_api.py

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -195,16 +195,30 @@ def build_project(src_path, build_path, target, toolchain_name,
195195
else:
196196
resources.inc_dirs.append(inc_dirs)
197197

198-
# Update the configuration with any .json files found while scanning
199-
config.add_config_files(resources.json_files)
198+
# Update configuration files until added features creates no changes
199+
prev_features = set()
200+
while True:
201+
# Update the configuration with any .json files found while scanning
202+
config.add_config_files(resources.json_files)
203+
204+
# Add features while we find new ones
205+
features = config.get_features()
206+
if features == prev_features:
207+
break
208+
209+
for feature in features:
210+
if feature not in resources.features:
211+
raise KeyError("Feature %s is unavailable" % feature)
212+
resources += resources.features[feature]
213+
214+
prev_features = features
215+
200216
# And add the configuration macros to the toolchain
201217
toolchain.add_macros(config.get_config_data_macros())
202218

203219
# Compile Sources
204-
for path in src_paths:
205-
src = toolchain.scan_resources(path)
206-
objects = toolchain.compile_sources(src, build_path, resources.inc_dirs)
207-
resources.objects.extend(objects)
220+
objects = toolchain.compile_sources(resources, build_path, resources.inc_dirs)
221+
resources.objects.extend(objects)
208222

209223
# Link Program
210224
res, _ = toolchain.link_program(resources, build_path, name)
@@ -237,7 +251,7 @@ def build_project(src_path, build_path, target, toolchain_name,
237251
add_result_to_report(report, cur_result)
238252

239253
# Let Exception propagate
240-
raise e
254+
raise
241255

242256
def build_library(src_paths, build_path, target, toolchain_name,
243257
dependencies_paths=None, options=None, name=None, clean=False, archive=True,
@@ -346,16 +360,29 @@ def build_library(src_paths, build_path, target, toolchain_name,
346360

347361
# Handle configuration
348362
config = Config(target)
349-
# Update the configuration with any .json files found while scanning
350-
config.add_config_files(resources.json_files)
363+
364+
# Update configuration files until added features creates no changes
365+
prev_features = set()
366+
while True:
367+
# Update the configuration with any .json files found while scanning
368+
config.add_config_files(resources.json_files)
369+
370+
# Add features while we find new ones
371+
features = config.get_features()
372+
if features == prev_features:
373+
break
374+
375+
for feature in features:
376+
resources += resources.features[feature]
377+
378+
prev_features = features
379+
351380
# And add the configuration macros to the toolchain
352381
toolchain.add_macros(config.get_config_data_macros())
353382

354383
# Compile Sources
355-
for path in src_paths:
356-
src = toolchain.scan_resources(path)
357-
objects = toolchain.compile_sources(src, abspath(tmp_path), resources.inc_dirs)
358-
resources.objects.extend(objects)
384+
objects = toolchain.compile_sources(resources, build_path, resources.inc_dirs)
385+
resources.objects.extend(objects)
359386

360387
if archive:
361388
toolchain.build_library(objects, build_path, name)

tools/config.py

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,9 @@ def __init__(self, target, top_level_dirs = []):
176176
self.processed_configs = {}
177177
self.target = target if isinstance(target, str) else target.name
178178
self.target_labels = Target.get_target(self.target).get_labels()
179-
self.target_instance = Target.get_target(self.target)
179+
self.added_features = set()
180+
self.removed_features = set()
181+
self.removed_unecessary_features = False
180182

181183
# Add one or more configuration files
182184
def add_config_files(self, flist):
@@ -212,6 +214,23 @@ def _process_config_parameters(self, data, params, unit_name, unit_kind):
212214
params[full_name] = ConfigParameter(name, v if isinstance(v, dict) else {"value": v}, unit_name, unit_kind)
213215
return params
214216

217+
# Add features to the available features
218+
def remove_features(self, features):
219+
for feature in features:
220+
if feature in self.added_features:
221+
raise ConfigException("Configuration conflict. Feature %s both added and removed." % feature)
222+
223+
self.removed_features |= set(features)
224+
225+
# Remove features from the available features
226+
def add_features(self, features):
227+
for feature in features:
228+
if (feature in self.removed_features
229+
or (self.removed_unecessary_features and feature not in self.added_features)):
230+
raise ConfigException("Configuration conflict. Feature %s both added and removed." % feature)
231+
232+
self.added_features |= set(features)
233+
215234
# Helper function: process "config_parameters" and "target_config_overrides" in a given dictionary
216235
# data: the configuration data of the library/appliation
217236
# params: storage for the discovered configuration parameters
@@ -222,25 +241,21 @@ def _process_config_and_overrides(self, data, params, unit_name, unit_kind):
222241
for label, overrides in data.get("target_overrides", {}).items():
223242
# If the label is defined by the target or it has the special value "*", process the overrides
224243
if (label == '*') or (label in self.target_labels):
225-
# Parse out cumulative attributes
226-
for attr in Target._Target__cumulative_attributes:
227-
attrs = getattr(self.target_instance, attr)
228-
229-
if attr in overrides:
230-
del attrs[:]
231-
attrs.extend(overrides[attr])
232-
del overrides[attr]
233-
234-
if attr+'_add' in overrides:
235-
attrs.extend(overrides[attr+'_add'])
236-
del overrides[attr+'_add']
237-
238-
if attr+'_remove' in overrides:
239-
for a in overrides[attr+'_remove']:
240-
attrs.remove(a)
241-
del overrides[attr+'_remove']
242-
243-
setattr(self.target_instance, attr, attrs)
244+
# Parse out features
245+
if 'features' in overrides:
246+
features = overrides['features']
247+
self.remove_features(list(set(self.added_features) - set(features)))
248+
self.add_features(features)
249+
self.removed_unecessary_features = True
250+
del overrides['features']
251+
252+
if 'features_add' in overrides:
253+
self.add_features(overrides['features_add'])
254+
del overrides['features_add']
255+
256+
if 'features_remove' in overrides:
257+
self.remove_features(overrides['features_remove'])
258+
del overrides['features_remove']
244259

245260
# Consider the others as overrides
246261
for name, v in overrides.items():
@@ -345,3 +360,11 @@ def get_config_data_macros(self):
345360
params, macros = self.get_config_data()
346361
self._check_required_parameters(params)
347362
return macros + self.parameters_to_macros(params)
363+
364+
# Returns any features in the configuration data
365+
def get_features(self):
366+
params, _ = self.get_config_data()
367+
self._check_required_parameters(params)
368+
return ((set(Target.get_target(self.target).features)
369+
| self.added_features) - self.removed_features)
370+

tools/toolchains/__init__.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ def __init__(self, base_path=None):
8989
self.bin_files = []
9090
self.json_files = []
9191

92+
# Features
93+
self.features = {}
94+
9295
def __add__(self, resources):
9396
if resources is None:
9497
return self
@@ -126,6 +129,8 @@ def add(self, resources):
126129
self.bin_files += resources.bin_files
127130
self.json_files += resources.json_files
128131

132+
self.features.update(resources.features)
133+
129134
return self
130135

131136
def relative_to(self, base, dot=False):
@@ -135,6 +140,10 @@ def relative_to(self, base, dot=False):
135140
'hex_files', 'bin_files', 'json_files']:
136141
v = [rel_path(f, base, dot) for f in getattr(self, field)]
137142
setattr(self, field, v)
143+
144+
for f in self.features:
145+
self.features[f] = rel_path(self.features[f], base, dot)
146+
138147
if self.linker_script is not None:
139148
self.linker_script = rel_path(self.linker_script, base, dot)
140149

@@ -145,6 +154,10 @@ def win_to_unix(self):
145154
'hex_files', 'bin_files', 'json_files']:
146155
v = [f.replace('\\', '/') for f in getattr(self, field)]
147156
setattr(self, field, v)
157+
158+
for f in self.features:
159+
self.features[f] = self.features[f].replace('\\', '/')
160+
148161
if self.linker_script is not None:
149162
self.linker_script = self.linker_script.replace('\\', '/')
150163

@@ -165,6 +178,8 @@ def __str__(self):
165178

166179
('Hex files', self.hex_files),
167180
('Bin files', self.bin_files),
181+
182+
('Features', self.features),
168183
):
169184
if resources:
170185
s.append('%s:\n ' % label + '\n '.join(resources))
@@ -425,11 +440,13 @@ def scan_resources(self, path, exclude_paths=None):
425440

426441
if ((d.startswith('.') or d in self.legacy_ignore_dirs) or
427442
(d.startswith('TARGET_') and d[7:] not in labels['TARGET']) or
428-
(d.startswith('FEATURE_') and d[8:] not in labels['FEATURE']) or
429443
(d.startswith('TOOLCHAIN_') and d[10:] not in labels['TOOLCHAIN']) or
430444
(d == 'TESTS')):
431445
dirs.remove(d)
432446

447+
if (d.startswith('FEATURE_')):
448+
resources.features[d[8:]] = self.scan_resources(dir_path)
449+
dirs.remove(d)
433450

434451
# Remove dirs that already match the ignorepatterns
435452
# to avoid travelling into them and to prevent them

0 commit comments

Comments
 (0)