Skip to content

Commit e9c556d

Browse files
authored
Merge pull request #2757 from theotherjimmy/non-global-config
Refactor Target and Config away from global variables
2 parents 667c171 + d4f9820 commit e9c556d

File tree

5 files changed

+101
-72
lines changed

5 files changed

+101
-72
lines changed

tools/build_api.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -303,14 +303,8 @@ def prepare_toolchain(src_paths, target, toolchain_name,
303303
src_paths = [src_paths[0]] + list(set(src_paths[1:]))
304304

305305
# If the configuration object was not yet created, create it now
306-
config = config or Config(target, src_paths, app_config=app_config)
307-
308-
# If the 'target' argument is a string, convert it to a target instance
309-
if isinstance(target, basestring):
310-
try:
311-
target = TARGET_MAP[target]
312-
except KeyError:
313-
raise KeyError("Target '%s' not found" % target)
306+
config = config or Config(target, src_paths)
307+
target = config.target
314308

315309
# Toolchain instance
316310
try:

tools/config.py

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@
1515
limitations under the License.
1616
"""
1717

18+
from copy import deepcopy
1819
import os
19-
import sys
20-
2120
# Implementation of mbed configuration mechanism
2221
from tools.utils import json_file_to_dict
23-
from tools.targets import Target
22+
from tools.targets import CUMULATIVE_ATTRIBUTES, TARGET_MAP, \
23+
generate_py_target, get_resolution_order
2424

2525
# Base class for all configuration exceptions
2626
class ConfigException(Exception):
@@ -350,7 +350,7 @@ class Config(object):
350350
"UVISOR", "BLE", "CLIENT", "IPV4", "IPV6", "COMMON_PAL", "STORAGE"
351351
]
352352

353-
def __init__(self, target, top_level_dirs=None, app_config=None):
353+
def __init__(self, tgt, top_level_dirs=None, app_config=None):
354354
"""Construct a mbed configuration
355355
356356
Positional arguments:
@@ -397,16 +397,23 @@ def __init__(self, target, top_level_dirs=None, app_config=None):
397397
self.lib_config_data = {}
398398
# Make sure that each config is processed only once
399399
self.processed_configs = {}
400-
self.target = target if isinstance(target, basestring) else target.name
401-
self.target_labels = Target.get_target(self.target).get_labels()
400+
if isinstance(tgt, basestring):
401+
if tgt in TARGET_MAP:
402+
self.target = TARGET_MAP[tgt]
403+
else:
404+
self.target = generate_py_target(
405+
self.app_config_data.get("custom_targets", {}), tgt)
406+
407+
else:
408+
self.target = tgt
409+
self.target = deepcopy(self.target)
410+
self.target_labels = self.target.labels
402411

403412
self.cumulative_overrides = {key: ConfigCumulativeOverride(key)
404-
for key in
405-
Target.cumulative_attributes}
413+
for key in CUMULATIVE_ATTRIBUTES}
406414

407415
self._process_config_and_overrides(self.app_config_data, {}, "app",
408416
"application")
409-
self.target_labels = Target.get_target(self.target).get_labels()
410417
self.config_errors = None
411418

412419
def add_config_files(self, flist):
@@ -509,7 +516,7 @@ def _process_config_and_overrides(self, data, params, unit_name, unit_kind):
509516
label)))))
510517

511518
for cumulatives in self.cumulative_overrides.itervalues():
512-
cumulatives.update_target(Target.get_target(self.target))
519+
cumulatives.update_target(self.target)
513520

514521
return params
515522

@@ -528,10 +535,10 @@ def get_target_config_data(self):
528535
529536
Arguments: None
530537
"""
531-
params, json_data = {}, Target.get_json_target_data()
538+
params, json_data = {}, self.target.json_data
532539
resolution_order = [e[0] for e
533540
in sorted(
534-
Target.get_target(self.target).resolution_order,
541+
self.target.resolution_order,
535542
key=lambda e: e[1], reverse=True)]
536543
for tname in resolution_order:
537544
# Read the target data directly from its description
@@ -547,9 +554,11 @@ def get_target_config_data(self):
547554
# in the target inheritance tree, raise an error We need to use
548555
# 'defined_by[7:]' to remove the "target:" prefix from
549556
# defined_by
557+
rel_names = [tgt for tgt, _ in
558+
get_resolution_order(self.target.json_data, tname,
559+
[])]
550560
if (full_name not in params) or \
551-
(params[full_name].defined_by[7:] not in
552-
Target.get_target(tname).resolution_order_names):
561+
(params[full_name].defined_by[7:] not in rel_names):
553562
raise ConfigException(
554563
"Attempt to override undefined parameter '%s' in '%s'"
555564
% (name,
@@ -680,15 +689,14 @@ def get_features(self):
680689
params, _ = self.get_config_data()
681690
self._check_required_parameters(params)
682691
self.cumulative_overrides['features']\
683-
.update_target(Target.get_target(self.target))
684-
features = Target.get_target(self.target).features
692+
.update_target(self.target)
685693

686-
for feature in features:
694+
for feature in self.target.features:
687695
if feature not in self.__allowed_features:
688696
raise ConfigException(
689697
"Feature '%s' is not a supported features" % feature)
690698

691-
return features
699+
return self.target.features
692700

693701
def validate_config(self):
694702
""" Validate configuration settings. This either returns True or

tools/targets.py

Lines changed: 71 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,15 @@
2121
import shutil
2222
import inspect
2323
import sys
24+
from collections import namedtuple
2425
from tools.patch import patch
2526
from tools.paths import TOOLS_BOOTLOADERS
2627
from tools.utils import json_file_to_dict
2728

29+
__all__ = ["target", "TARGETS", "TARGET_MAP", "TARGET_NAMES", "CORE_LABELS",
30+
"HookError", "generate_py_target", "Target",
31+
"CUMULATIVE_ATTRIBUTES", "get_resolution_order"]
32+
2833
CORE_LABELS = {
2934
"ARM7TDMI-S": ["ARM7", "LIKE_CORTEX_ARM7"],
3035
"Cortex-M0" : ["M0", "CORTEX_M", "LIKE_CORTEX_M0"],
@@ -60,11 +65,58 @@ def wrapper(*args, **kwargs):
6065
return CACHES[(func.__name__, args)]
6166
return wrapper
6267

63-
class Target(object):
68+
69+
# Cumulative attributes can have values appended to them, so they
70+
# need to be computed differently than regular attributes
71+
CUMULATIVE_ATTRIBUTES = ['extra_labels', 'macros', 'device_has', 'features']
72+
73+
74+
def get_resolution_order(json_data, target_name, order, level=0):
75+
""" Return the order in which target descriptions are searched for
76+
attributes. This mimics the Python 2.2 method resolution order, which
77+
is what the old targets.py module used. For more details, check
78+
http://makina-corpus.com/blog/metier/2014/python-tutorial-understanding-python-mro-class-search-path
79+
The resolution order contains (name, level) tuples, where "name" is the
80+
name of the class and "level" is the level in the inheritance hierarchy
81+
(the target itself is at level 0, its first parent at level 1, its
82+
parent's parent at level 2 and so on)
83+
"""
84+
# the resolution order can't contain duplicate target names
85+
if target_name not in [l[0] for l in order]:
86+
order.append((target_name, level))
87+
parents = json_data[target_name].get("inherits", [])
88+
for par in parents:
89+
order = get_resolution_order(json_data, par, order, level + 1)
90+
return order
91+
92+
93+
def target(name, json_data):
94+
"""Construct a target object"""
95+
resolution_order = get_resolution_order(json_data, name, [])
96+
resolution_order_names = [tgt for tgt, _ in resolution_order]
97+
return Target(name=name,
98+
json_data={key: value for key, value in json_data.items()
99+
if key in resolution_order_names},
100+
resolution_order=resolution_order,
101+
resolution_order_names=resolution_order_names)
102+
103+
def generate_py_target(new_targets, name):
104+
"""Add one or more new target(s) represented as a Python dictionary
105+
in 'new_targets'. It is an error to add a target with a name that
106+
already exists.
107+
"""
108+
base_targets = Target.get_json_target_data()
109+
for new_target in new_targets.keys():
110+
if new_target in base_targets:
111+
raise Exception("Attempt to add target '%s' that already exists"
112+
% new_target)
113+
total_data = {}
114+
total_data.update(new_targets)
115+
total_data.update(base_targets)
116+
return target(name, total_data)
117+
118+
class Target(namedtuple("Target", "name json_data resolution_order resolution_order_names")):
64119
"""An object to represent a Target (MCU/Board)"""
65-
# Cumulative attributes can have values appended to them, so they
66-
# need to be computed differently than regular attributes
67-
cumulative_attributes = ['extra_labels', 'macros', 'device_has', 'features']
68120

69121
# Default location of the 'targets.json' file
70122
__targets_json_location_default = os.path.join(
@@ -95,24 +147,6 @@ def get_module_data():
95147
return dict([(m[0], m[1]) for m in
96148
inspect.getmembers(sys.modules[__name__])])
97149

98-
def __get_resolution_order(self, target_name, order, level=0):
99-
""" Return the order in which target descriptions are searched for
100-
attributes. This mimics the Python 2.2 method resolution order, which
101-
is what the old targets.py module used. For more details, check
102-
http://makina-corpus.com/blog/metier/2014/python-tutorial-understanding-python-mro-class-search-path
103-
The resolution order contains (name, level) tuples, where "name" is the
104-
name of the class and "level" is the level in the inheritance hierarchy
105-
(the target itself is at level 0, its first parent at level 1, its
106-
parent's parent at level 2 and so on)
107-
"""
108-
# the resolution order can't contain duplicate target names
109-
if target_name not in [l[0] for l in order]:
110-
order.append((target_name, level))
111-
parents = self.get_json_target_data()[target_name].get("inherits", [])
112-
for par in parents:
113-
order = self.__get_resolution_order(par, order, level + 1)
114-
return order
115-
116150
@staticmethod
117151
def __add_paths_to_progen(data):
118152
"""Modify the exporter specification ("progen") by changing all
@@ -133,14 +167,14 @@ def __getattr_cumulative(self, attrname):
133167
"""Look for the attribute in the class and its parents, as defined by
134168
the resolution order
135169
"""
136-
tdata = self.get_json_target_data()
170+
tdata = self.json_data
137171
# For a cumulative attribute, figure out when it was defined the
138172
# last time (in attribute resolution order) then follow the "_add"
139173
# and "_remove" data fields
140-
for idx, target in enumerate(self.resolution_order):
174+
for idx, tgt in enumerate(self.resolution_order):
141175
# the attribute was defined at this level in the resolution
142176
# order
143-
if attrname in tdata[target[0]]:
177+
if attrname in tdata[tgt[0]]:
144178
def_idx = idx
145179
break
146180
else:
@@ -192,13 +226,13 @@ def __getattr_cumulative(self, attrname):
192226

193227
def __getattr_helper(self, attrname):
194228
"""Compute the value of a given target attribute"""
195-
if attrname in self.cumulative_attributes:
229+
if attrname in CUMULATIVE_ATTRIBUTES:
196230
return self.__getattr_cumulative(attrname)
197231
else:
198-
tdata = self.get_json_target_data()
232+
tdata = self.json_data
199233
starting_value = None
200-
for target in self.resolution_order:
201-
data = tdata[target[0]]
234+
for tgt in self.resolution_order:
235+
data = tdata[tgt[0]]
202236
if data.has_key(attrname):
203237
starting_value = data[attrname]
204238
break
@@ -226,17 +260,8 @@ def __getattr__(self, attrname):
226260
@cached
227261
def get_target(target_name):
228262
""" Return the target instance starting from the target name """
229-
return Target(target_name)
263+
return target(target_name, Target.get_json_target_data())
230264

231-
def __init__(self, target_name):
232-
self.name = target_name
233-
234-
# Compute resolution order once (it will be used later in __getattr__)
235-
self.resolution_order = self.__get_resolution_order(self.name, [])
236-
# Create also a list with only the names of the targets in the
237-
# resolution order
238-
self.resolution_order_names = [target[0] for target
239-
in self.resolution_order]
240265

241266
@property
242267
def program_cycle_s(self):
@@ -248,7 +273,8 @@ def program_cycle_s(self):
248273
except AttributeError:
249274
return 4 if self.is_disk_virtual else 1.5
250275

251-
def get_labels(self):
276+
@property
277+
def labels(self):
252278
"""Get all possible labels for this target"""
253279
labels = [self.name] + CORE_LABELS[self.core] + self.extra_labels
254280
# Automatically define UVISOR_UNSUPPORTED if the target doesn't
@@ -487,9 +513,9 @@ def get_target_detect_codes():
487513
""" Returns dictionary mapping detect_code -> platform_name
488514
"""
489515
result = {}
490-
for target in TARGETS:
491-
for detect_code in target.detect_code:
492-
result[detect_code] = target.name
516+
for tgt in TARGETS:
517+
for detect_code in tgt.detect_code:
518+
result[detect_code] = tgt.name
493519
return result
494520

495521
def set_targets_json_location(location=None):
@@ -500,9 +526,9 @@ def set_targets_json_location(location=None):
500526
# re-initialization does not create new variables, it keeps the old ones
501527
# instead. This ensures compatibility with code that does
502528
# "from tools.targets import TARGET_NAMES"
503-
TARGETS[:] = [Target.get_target(target) for target, obj
529+
TARGETS[:] = [Target.get_target(tgt) for tgt, obj
504530
in Target.get_json_target_data().items()
505531
if obj.get("public", True)]
506532
TARGET_MAP.clear()
507-
TARGET_MAP.update(dict([(target.name, target) for target in TARGETS]))
533+
TARGET_MAP.update(dict([(tgt.name, tgt) for tgt in TARGETS]))
508534
TARGET_NAMES[:] = TARGET_MAP.keys()

tools/test/config_test/config_test.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def compare_config(cfg, expected):
2727
if cfg[k].value != expected[k]:
2828
return "'%s': expected '%s', got '%s'" % (k, expected[k], cfg[k].value)
2929
except KeyError:
30+
raise
3031
return "Unexpected key '%s' in configuration data" % k
3132
for k in expected:
3233
if k not in ["desc", "expected_macros", "expected_features"] + cfg.keys():

tools/toolchains/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,7 @@ def get_labels(self):
428428
toolchain_labels = [c.__name__ for c in getmro(self.__class__)]
429429
toolchain_labels.remove('mbedToolchain')
430430
self.labels = {
431-
'TARGET': self.target.get_labels() + ["DEBUG" if "debug-info" in self.options else "RELEASE"],
431+
'TARGET': self.target.labels + ["DEBUG" if "debug-info" in self.options else "RELEASE"],
432432
'FEATURE': self.target.features,
433433
'TOOLCHAIN': toolchain_labels
434434
}

0 commit comments

Comments
 (0)