Refactor Target and Config away from global variables

pull/2757/head
Jimmy Brisson 2016-09-01 15:44:36 -05:00
parent e4a40cd6a7
commit d4f9820577
5 changed files with 101 additions and 72 deletions

View File

@ -303,14 +303,8 @@ def prepare_toolchain(src_paths, target, toolchain_name,
src_paths = [src_paths[0]] + list(set(src_paths[1:]))
# If the configuration object was not yet created, create it now
config = config or Config(target, src_paths, app_config=app_config)
# If the 'target' argument is a string, convert it to a target instance
if isinstance(target, basestring):
try:
target = TARGET_MAP[target]
except KeyError:
raise KeyError("Target '%s' not found" % target)
config = config or Config(target, src_paths)
target = config.target
# Toolchain instance
try:

View File

@ -15,12 +15,12 @@ See the License for the specific language governing permissions and
limitations under the License.
"""
from copy import deepcopy
import os
import sys
# Implementation of mbed configuration mechanism
from tools.utils import json_file_to_dict
from tools.targets import Target
from tools.targets import CUMULATIVE_ATTRIBUTES, TARGET_MAP, \
generate_py_target, get_resolution_order
# Base class for all configuration exceptions
class ConfigException(Exception):
@ -350,7 +350,7 @@ class Config(object):
"UVISOR", "BLE", "CLIENT", "IPV4", "IPV6", "COMMON_PAL", "STORAGE"
]
def __init__(self, target, top_level_dirs=None, app_config=None):
def __init__(self, tgt, top_level_dirs=None, app_config=None):
"""Construct a mbed configuration
Positional arguments:
@ -397,16 +397,23 @@ class Config(object):
self.lib_config_data = {}
# Make sure that each config is processed only once
self.processed_configs = {}
self.target = target if isinstance(target, basestring) else target.name
self.target_labels = Target.get_target(self.target).get_labels()
if isinstance(tgt, basestring):
if tgt in TARGET_MAP:
self.target = TARGET_MAP[tgt]
else:
self.target = generate_py_target(
self.app_config_data.get("custom_targets", {}), tgt)
else:
self.target = tgt
self.target = deepcopy(self.target)
self.target_labels = self.target.labels
self.cumulative_overrides = {key: ConfigCumulativeOverride(key)
for key in
Target.cumulative_attributes}
for key in CUMULATIVE_ATTRIBUTES}
self._process_config_and_overrides(self.app_config_data, {}, "app",
"application")
self.target_labels = Target.get_target(self.target).get_labels()
self.config_errors = None
def add_config_files(self, flist):
@ -509,7 +516,7 @@ class Config(object):
label)))))
for cumulatives in self.cumulative_overrides.itervalues():
cumulatives.update_target(Target.get_target(self.target))
cumulatives.update_target(self.target)
return params
@ -528,10 +535,10 @@ class Config(object):
Arguments: None
"""
params, json_data = {}, Target.get_json_target_data()
params, json_data = {}, self.target.json_data
resolution_order = [e[0] for e
in sorted(
Target.get_target(self.target).resolution_order,
self.target.resolution_order,
key=lambda e: e[1], reverse=True)]
for tname in resolution_order:
# Read the target data directly from its description
@ -547,9 +554,11 @@ class Config(object):
# in the target inheritance tree, raise an error We need to use
# 'defined_by[7:]' to remove the "target:" prefix from
# defined_by
rel_names = [tgt for tgt, _ in
get_resolution_order(self.target.json_data, tname,
[])]
if (full_name not in params) or \
(params[full_name].defined_by[7:] not in
Target.get_target(tname).resolution_order_names):
(params[full_name].defined_by[7:] not in rel_names):
raise ConfigException(
"Attempt to override undefined parameter '%s' in '%s'"
% (name,
@ -680,15 +689,14 @@ class Config(object):
params, _ = self.get_config_data()
self._check_required_parameters(params)
self.cumulative_overrides['features']\
.update_target(Target.get_target(self.target))
features = Target.get_target(self.target).features
.update_target(self.target)
for feature in features:
for feature in self.target.features:
if feature not in self.__allowed_features:
raise ConfigException(
"Feature '%s' is not a supported features" % feature)
return features
return self.target.features
def validate_config(self):
""" Validate configuration settings. This either returns True or

View File

@ -21,10 +21,15 @@ import struct
import shutil
import inspect
import sys
from collections import namedtuple
from tools.patch import patch
from tools.paths import TOOLS_BOOTLOADERS
from tools.utils import json_file_to_dict
__all__ = ["target", "TARGETS", "TARGET_MAP", "TARGET_NAMES", "CORE_LABELS",
"HookError", "generate_py_target", "Target",
"CUMULATIVE_ATTRIBUTES", "get_resolution_order"]
CORE_LABELS = {
"ARM7TDMI-S": ["ARM7", "LIKE_CORTEX_ARM7"],
"Cortex-M0" : ["M0", "CORTEX_M", "LIKE_CORTEX_M0"],
@ -60,11 +65,58 @@ def cached(func):
return CACHES[(func.__name__, args)]
return wrapper
class Target(object):
# Cumulative attributes can have values appended to them, so they
# need to be computed differently than regular attributes
CUMULATIVE_ATTRIBUTES = ['extra_labels', 'macros', 'device_has', 'features']
def get_resolution_order(json_data, target_name, order, level=0):
""" Return the order in which target descriptions are searched for
attributes. This mimics the Python 2.2 method resolution order, which
is what the old targets.py module used. For more details, check
http://makina-corpus.com/blog/metier/2014/python-tutorial-understanding-python-mro-class-search-path
The resolution order contains (name, level) tuples, where "name" is the
name of the class and "level" is the level in the inheritance hierarchy
(the target itself is at level 0, its first parent at level 1, its
parent's parent at level 2 and so on)
"""
# the resolution order can't contain duplicate target names
if target_name not in [l[0] for l in order]:
order.append((target_name, level))
parents = json_data[target_name].get("inherits", [])
for par in parents:
order = get_resolution_order(json_data, par, order, level + 1)
return order
def target(name, json_data):
"""Construct a target object"""
resolution_order = get_resolution_order(json_data, name, [])
resolution_order_names = [tgt for tgt, _ in resolution_order]
return Target(name=name,
json_data={key: value for key, value in json_data.items()
if key in resolution_order_names},
resolution_order=resolution_order,
resolution_order_names=resolution_order_names)
def generate_py_target(new_targets, name):
"""Add one or more new target(s) represented as a Python dictionary
in 'new_targets'. It is an error to add a target with a name that
already exists.
"""
base_targets = Target.get_json_target_data()
for new_target in new_targets.keys():
if new_target in base_targets:
raise Exception("Attempt to add target '%s' that already exists"
% new_target)
total_data = {}
total_data.update(new_targets)
total_data.update(base_targets)
return target(name, total_data)
class Target(namedtuple("Target", "name json_data resolution_order resolution_order_names")):
"""An object to represent a Target (MCU/Board)"""
# Cumulative attributes can have values appended to them, so they
# need to be computed differently than regular attributes
cumulative_attributes = ['extra_labels', 'macros', 'device_has', 'features']
# Default location of the 'targets.json' file
__targets_json_location_default = os.path.join(
@ -95,24 +147,6 @@ class Target(object):
return dict([(m[0], m[1]) for m in
inspect.getmembers(sys.modules[__name__])])
def __get_resolution_order(self, target_name, order, level=0):
""" Return the order in which target descriptions are searched for
attributes. This mimics the Python 2.2 method resolution order, which
is what the old targets.py module used. For more details, check
http://makina-corpus.com/blog/metier/2014/python-tutorial-understanding-python-mro-class-search-path
The resolution order contains (name, level) tuples, where "name" is the
name of the class and "level" is the level in the inheritance hierarchy
(the target itself is at level 0, its first parent at level 1, its
parent's parent at level 2 and so on)
"""
# the resolution order can't contain duplicate target names
if target_name not in [l[0] for l in order]:
order.append((target_name, level))
parents = self.get_json_target_data()[target_name].get("inherits", [])
for par in parents:
order = self.__get_resolution_order(par, order, level + 1)
return order
@staticmethod
def __add_paths_to_progen(data):
"""Modify the exporter specification ("progen") by changing all
@ -133,14 +167,14 @@ class Target(object):
"""Look for the attribute in the class and its parents, as defined by
the resolution order
"""
tdata = self.get_json_target_data()
tdata = self.json_data
# For a cumulative attribute, figure out when it was defined the
# last time (in attribute resolution order) then follow the "_add"
# and "_remove" data fields
for idx, target in enumerate(self.resolution_order):
for idx, tgt in enumerate(self.resolution_order):
# the attribute was defined at this level in the resolution
# order
if attrname in tdata[target[0]]:
if attrname in tdata[tgt[0]]:
def_idx = idx
break
else:
@ -192,13 +226,13 @@ class Target(object):
def __getattr_helper(self, attrname):
"""Compute the value of a given target attribute"""
if attrname in self.cumulative_attributes:
if attrname in CUMULATIVE_ATTRIBUTES:
return self.__getattr_cumulative(attrname)
else:
tdata = self.get_json_target_data()
tdata = self.json_data
starting_value = None
for target in self.resolution_order:
data = tdata[target[0]]
for tgt in self.resolution_order:
data = tdata[tgt[0]]
if data.has_key(attrname):
starting_value = data[attrname]
break
@ -226,17 +260,8 @@ class Target(object):
@cached
def get_target(target_name):
""" Return the target instance starting from the target name """
return Target(target_name)
return target(target_name, Target.get_json_target_data())
def __init__(self, target_name):
self.name = target_name
# Compute resolution order once (it will be used later in __getattr__)
self.resolution_order = self.__get_resolution_order(self.name, [])
# Create also a list with only the names of the targets in the
# resolution order
self.resolution_order_names = [target[0] for target
in self.resolution_order]
@property
def program_cycle_s(self):
@ -248,7 +273,8 @@ class Target(object):
except AttributeError:
return 4 if self.is_disk_virtual else 1.5
def get_labels(self):
@property
def labels(self):
"""Get all possible labels for this target"""
labels = [self.name] + CORE_LABELS[self.core] + self.extra_labels
# Automatically define UVISOR_UNSUPPORTED if the target doesn't
@ -487,9 +513,9 @@ def get_target_detect_codes():
""" Returns dictionary mapping detect_code -> platform_name
"""
result = {}
for target in TARGETS:
for detect_code in target.detect_code:
result[detect_code] = target.name
for tgt in TARGETS:
for detect_code in tgt.detect_code:
result[detect_code] = tgt.name
return result
def set_targets_json_location(location=None):
@ -500,9 +526,9 @@ def set_targets_json_location(location=None):
# re-initialization does not create new variables, it keeps the old ones
# instead. This ensures compatibility with code that does
# "from tools.targets import TARGET_NAMES"
TARGETS[:] = [Target.get_target(target) for target, obj
TARGETS[:] = [Target.get_target(tgt) for tgt, obj
in Target.get_json_target_data().items()
if obj.get("public", True)]
TARGET_MAP.clear()
TARGET_MAP.update(dict([(target.name, target) for target in TARGETS]))
TARGET_MAP.update(dict([(tgt.name, tgt) for tgt in TARGETS]))
TARGET_NAMES[:] = TARGET_MAP.keys()

View File

@ -27,6 +27,7 @@ def compare_config(cfg, expected):
if cfg[k].value != expected[k]:
return "'%s': expected '%s', got '%s'" % (k, expected[k], cfg[k].value)
except KeyError:
raise
return "Unexpected key '%s' in configuration data" % k
for k in expected:
if k not in ["desc", "expected_macros", "expected_features"] + cfg.keys():

View File

@ -428,7 +428,7 @@ class mbedToolchain:
toolchain_labels = [c.__name__ for c in getmro(self.__class__)]
toolchain_labels.remove('mbedToolchain')
self.labels = {
'TARGET': self.target.get_labels() + ["DEBUG" if "debug-info" in self.options else "RELEASE"],
'TARGET': self.target.labels + ["DEBUG" if "debug-info" in self.options else "RELEASE"],
'FEATURE': self.target.features,
'TOOLCHAIN': toolchain_labels
}