Skip to content

Commit 7e0e685

Browse files
authored
Merge pull request #1940 from geky/fix-features
[build tools] Added better support for features and recursive configs
2 parents fd983d1 + 0d9d463 commit 7e0e685

File tree

25 files changed

+330
-45
lines changed

25 files changed

+330
-45
lines changed

tools/build_api.py

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,27 @@ def get_config(src_path, target, toolchain_name):
105105
for path in src_paths[1:]:
106106
resources.add(toolchain.scan_resources(path))
107107

108-
config.add_config_files(resources.json_files)
109-
return config.get_config_data()
108+
# Update configuration files until added features creates no changes
109+
prev_features = set()
110+
while True:
111+
# Update the configuration with any .json files found while scanning
112+
config.add_config_files(resources.json_files)
113+
114+
# Add features while we find new ones
115+
features = config.get_features()
116+
if features == prev_features:
117+
break
118+
119+
for feature in features:
120+
if feature in resources.features:
121+
resources += resources.features[feature]
122+
123+
prev_features = features
124+
config.validate_config()
125+
126+
cfg, macros = config.get_config_data()
127+
features = config.get_features()
128+
return cfg, macros, features
110129

111130
def build_project(src_path, build_path, target, toolchain_name,
112131
libraries_paths=None, options=None, linker_script=None,
@@ -195,8 +214,24 @@ def build_project(src_path, build_path, target, toolchain_name,
195214
else:
196215
resources.inc_dirs.append(inc_dirs)
197216

198-
# Update the configuration with any .json files found while scanning
199-
config.add_config_files(resources.json_files)
217+
# Update configuration files until added features creates no changes
218+
prev_features = set()
219+
while True:
220+
# Update the configuration with any .json files found while scanning
221+
config.add_config_files(resources.json_files)
222+
223+
# Add features while we find new ones
224+
features = config.get_features()
225+
if features == prev_features:
226+
break
227+
228+
for feature in features:
229+
if feature in resources.features:
230+
resources += resources.features[feature]
231+
232+
prev_features = features
233+
config.validate_config()
234+
200235
# And add the configuration macros to the toolchain
201236
toolchain.add_macros(config.get_config_data_macros())
202237

@@ -237,7 +272,7 @@ def build_project(src_path, build_path, target, toolchain_name,
237272
add_result_to_report(report, cur_result)
238273

239274
# Let Exception propagate
240-
raise e
275+
raise
241276

242277
def build_library(src_paths, build_path, target, toolchain_name,
243278
dependencies_paths=None, options=None, name=None, clean=False, archive=True,
@@ -349,8 +384,25 @@ def build_library(src_paths, build_path, target, toolchain_name,
349384

350385
# Handle configuration
351386
config = Config(target)
352-
# Update the configuration with any .json files found while scanning
353-
config.add_config_files(resources.json_files)
387+
388+
# Update configuration files until added features creates no changes
389+
prev_features = set()
390+
while True:
391+
# Update the configuration with any .json files found while scanning
392+
config.add_config_files(resources.json_files)
393+
394+
# Add features while we find new ones
395+
features = config.get_features()
396+
if features == prev_features:
397+
break
398+
399+
for feature in features:
400+
if feature in resources.features:
401+
resources += resources.features[feature]
402+
403+
prev_features = features
404+
config.validate_config()
405+
354406
# And add the configuration macros to the toolchain
355407
toolchain.add_macros(config.get_config_data_macros())
356408

tools/config.py

Lines changed: 67 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ def __init__(self, name, data, unit_name, unit_kind):
4040
self.value = data.get("value", None)
4141
self.required = data.get("required", False)
4242
self.macro_name = data.get("macro_name", "MBED_CONF_%s" % self.sanitize(self.name.upper()))
43+
self.config_errors = []
4344

4445
# Return the full (prefixed) name of a parameter.
4546
# If the parameter already has a prefix, check if it is valid
@@ -147,6 +148,11 @@ class Config:
147148
"application": set(["config", "custom_targets", "target_overrides", "macros", "__config_path"])
148149
}
149150

151+
# Allowed features in configurations
152+
__allowed_features = [
153+
"UVISOR", "BLE", "CLIENT", "IPV4", "IPV6"
154+
]
155+
150156
# The initialization arguments for Config are:
151157
# target: the name of the mbed target used for this configuration instance
152158
# top_level_dirs: a list of top level source directories (where mbed_abb_config.json could be found)
@@ -176,7 +182,9 @@ def __init__(self, target, top_level_dirs = []):
176182
self.processed_configs = {}
177183
self.target = target if isinstance(target, str) else target.name
178184
self.target_labels = Target.get_target(self.target).get_labels()
179-
self.target_instance = Target.get_target(self.target)
185+
self.added_features = set()
186+
self.removed_features = set()
187+
self.removed_unecessary_features = False
180188

181189
# Add one or more configuration files
182190
def add_config_files(self, flist):
@@ -212,44 +220,59 @@ def _process_config_parameters(self, data, params, unit_name, unit_kind):
212220
params[full_name] = ConfigParameter(name, v if isinstance(v, dict) else {"value": v}, unit_name, unit_kind)
213221
return params
214222

223+
# Add features to the available features
224+
def remove_features(self, features):
225+
for feature in features:
226+
if feature in self.added_features:
227+
raise ConfigException("Configuration conflict. Feature %s both added and removed." % feature)
228+
229+
self.removed_features |= set(features)
230+
231+
# Remove features from the available features
232+
def add_features(self, features):
233+
for feature in features:
234+
if (feature in self.removed_features
235+
or (self.removed_unecessary_features and feature not in self.added_features)):
236+
raise ConfigException("Configuration conflict. Feature %s both added and removed." % feature)
237+
238+
self.added_features |= set(features)
239+
215240
# Helper function: process "config_parameters" and "target_config_overrides" in a given dictionary
216241
# data: the configuration data of the library/appliation
217242
# params: storage for the discovered configuration parameters
218243
# unit_name: the unit (library/application) that defines this parameter
219244
# unit_kind: the kind of the unit ("library" or "application")
220245
def _process_config_and_overrides(self, data, params, unit_name, unit_kind):
246+
self.config_errors = []
221247
self._process_config_parameters(data.get("config", {}), params, unit_name, unit_kind)
222248
for label, overrides in data.get("target_overrides", {}).items():
223249
# If the label is defined by the target or it has the special value "*", process the overrides
224250
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)
251+
# Parse out features
252+
if 'target.features' in overrides:
253+
features = overrides['target.features']
254+
self.remove_features(self.added_features - set(features))
255+
self.add_features(features)
256+
self.removed_unecessary_features = True
257+
del overrides['target.features']
258+
259+
if 'target.features_add' in overrides:
260+
self.add_features(overrides['target.features_add'])
261+
del overrides['target.features_add']
262+
263+
if 'target.features_remove' in overrides:
264+
self.remove_features(overrides['target.features_remove'])
265+
del overrides['target.features_remove']
244266

245267
# Consider the others as overrides
246268
for name, v in overrides.items():
247269
# Get the full name of the parameter
248270
full_name = ConfigParameter.get_full_name(name, unit_name, unit_kind, label)
249-
# If an attempt is made to override a parameter that isn't defined, raise an error
250-
if not full_name in params:
251-
raise ConfigException("Attempt to override undefined parameter '%s' in '%s'" % (full_name, ConfigParameter.get_display_name(unit_name, unit_kind, label)))
252-
params[full_name].set_value(v, unit_name, unit_kind, label)
271+
if full_name in params:
272+
params[full_name].set_value(v, unit_name, unit_kind, label)
273+
else:
274+
self.config_errors.append(ConfigException("Attempt to override undefined parameter '%s' in '%s'"
275+
% (full_name, ConfigParameter.get_display_name(unit_name, unit_kind, label))))
253276
return params
254277

255278
# Read and interpret configuration data defined by targets
@@ -345,3 +368,23 @@ def get_config_data_macros(self):
345368
params, macros = self.get_config_data()
346369
self._check_required_parameters(params)
347370
return macros + self.parameters_to_macros(params)
371+
372+
# Returns any features in the configuration data
373+
def get_features(self):
374+
params, _ = self.get_config_data()
375+
self._check_required_parameters(params)
376+
features = ((set(Target.get_target(self.target).features)
377+
| self.added_features) - self.removed_features)
378+
379+
for feature in features:
380+
if feature not in self.__allowed_features:
381+
raise ConfigException("Feature '%s' is not a supported features" % feature)
382+
383+
return features
384+
385+
# Validate configuration settings. This either returns True or raises an exception
386+
def validate_config(self):
387+
if self.config_errors:
388+
raise self.config_errors[0]
389+
return True
390+

tools/test/config_test/config_test.py

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def compare_config(cfg, expected):
2828
except KeyError:
2929
return "Unexpected key '%s' in configuration data" % k
3030
for k in expected:
31-
if k != "desc" and k != "expected_macros" and not k in cfg:
31+
if k not in ["desc", "expected_macros", "expected_features"] + cfg.keys():
3232
return "Expected key '%s' was not found in configuration data" % k
3333
return ""
3434

@@ -43,7 +43,7 @@ def test_tree(full_name, name):
4343
sys.stdout.flush()
4444
err_msg = None
4545
try:
46-
cfg, macros = get_config(full_name, target, "GCC_ARM")
46+
cfg, macros, features = get_config(full_name, target, "GCC_ARM")
4747
except ConfigException as e:
4848
err_msg = e.message
4949
if err_msg:
@@ -63,23 +63,33 @@ def test_tree(full_name, name):
6363
failed += 1
6464
else:
6565
res = compare_config(cfg, expected)
66+
expected_macros = expected.get("expected_macros", None)
67+
expected_features = expected.get("expected_features", None)
68+
6669
if res:
6770
print "FAILED!"
6871
sys.stdout.write(" " + res + "\n")
6972
failed += 1
70-
else:
71-
expected_macros = expected.get("expected_macros", None)
72-
if expected_macros is not None:
73-
if sorted(expected_macros) != sorted(macros):
74-
print "FAILED!"
75-
sys.stderr.write(" List of macros doesn't match\n")
76-
sys.stderr.write(" Expected: '%s'\n" % ",".join(sorted(expected_macros)))
77-
sys.stderr.write(" Got: '%s'\n" % ",".join(sorted(expected_macros)))
78-
failed += 1
79-
else:
80-
print "OK"
73+
elif expected_macros is not None:
74+
if sorted(expected_macros) != sorted(macros):
75+
print "FAILED!"
76+
sys.stderr.write(" List of macros doesn't match\n")
77+
sys.stderr.write(" Expected: '%s'\n" % ",".join(sorted(expected_macros)))
78+
sys.stderr.write(" Got: '%s'\n" % ",".join(sorted(expected_macros)))
79+
failed += 1
8180
else:
8281
print "OK"
82+
elif expected_features is not None:
83+
if sorted(expected_features) != sorted(features):
84+
print "FAILED!"
85+
sys.stderr.write(" List of features doesn't match\n")
86+
sys.stderr.write(" Expected: '%s'\n" % ",".join(sorted(expected_features)))
87+
sys.stderr.write(" Got: '%s'\n" % ",".join(sorted(expected_features)))
88+
failed += 1
89+
else:
90+
print "OK"
91+
else:
92+
print "OK"
8393
sys.path.remove(full_name)
8494
return failed
8595

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"target_overrides": {
3+
"*": {
4+
"target.features": ["IPV4", "IPV6"]
5+
}
6+
}
7+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Testing basic features
2+
3+
expected_results = {
4+
"K64F": {
5+
"desc": "test basic features",
6+
"expected_features": ["IPV4", "IPV6"]
7+
}
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "lib1",
3+
"target_overrides": {
4+
"*": {
5+
"target.features_add": ["IPV4"]
6+
}
7+
}
8+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"target_overrides": {
3+
"*": {
4+
"target.features_add": ["IPV6"]
5+
}
6+
}
7+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Testing when adding two features
2+
3+
expected_results = {
4+
"K64F": {
5+
"desc": "test composing features",
6+
"expected_features": ["IPV4", "IPV6"]
7+
}
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "lib1",
3+
"target_overrides": {
4+
"*": {
5+
"target.features_add": ["IPV4"]
6+
}
7+
}
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "lib2",
3+
"target_overrides": {
4+
"*": {
5+
"target.features_remove": ["IPV4"]
6+
}
7+
}
8+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"target_overrides": {
3+
"*": {
4+
"target.features_add": ["IPV6"]
5+
}
6+
}
7+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Testing when two features collide
2+
3+
expected_results = {
4+
"K64F": {
5+
"desc": "test feature collisions",
6+
"exception_msg": "Configuration conflict. Feature IPV4 both added and removed."
7+
}
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "lib1",
3+
"target_overrides": {
4+
"*": {
5+
"target.features_add": ["IPV6"]
6+
}
7+
}
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "lib2",
3+
"target_overrides": {
4+
"*": {
5+
"target.features_add": ["UVISOR"]
6+
}
7+
}
8+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"target_overrides": {
3+
"*": {
4+
"target.features_add": ["IPV4"]
5+
}
6+
}
7+
}

0 commit comments

Comments
 (0)