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:])) src_paths = [src_paths[0]] + list(set(src_paths[1:]))
# If the configuration object was not yet created, create it now # If the configuration object was not yet created, create it now
config = config or Config(target, src_paths, app_config=app_config) config = config or Config(target, src_paths)
target = config.target
# 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)
# Toolchain instance # Toolchain instance
try: try:

View File

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

View File

@ -21,10 +21,15 @@ import struct
import shutil import shutil
import inspect import inspect
import sys import sys
from collections import namedtuple
from tools.patch import patch from tools.patch import patch
from tools.paths import TOOLS_BOOTLOADERS from tools.paths import TOOLS_BOOTLOADERS
from tools.utils import json_file_to_dict 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 = { CORE_LABELS = {
"ARM7TDMI-S": ["ARM7", "LIKE_CORTEX_ARM7"], "ARM7TDMI-S": ["ARM7", "LIKE_CORTEX_ARM7"],
"Cortex-M0" : ["M0", "CORTEX_M", "LIKE_CORTEX_M0"], "Cortex-M0" : ["M0", "CORTEX_M", "LIKE_CORTEX_M0"],
@ -60,11 +65,58 @@ def cached(func):
return CACHES[(func.__name__, args)] return CACHES[(func.__name__, args)]
return wrapper 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)""" """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 # Default location of the 'targets.json' file
__targets_json_location_default = os.path.join( __targets_json_location_default = os.path.join(
@ -95,24 +147,6 @@ class Target(object):
return dict([(m[0], m[1]) for m in return dict([(m[0], m[1]) for m in
inspect.getmembers(sys.modules[__name__])]) 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 @staticmethod
def __add_paths_to_progen(data): def __add_paths_to_progen(data):
"""Modify the exporter specification ("progen") by changing all """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 """Look for the attribute in the class and its parents, as defined by
the resolution order the resolution order
""" """
tdata = self.get_json_target_data() tdata = self.json_data
# For a cumulative attribute, figure out when it was defined the # For a cumulative attribute, figure out when it was defined the
# last time (in attribute resolution order) then follow the "_add" # last time (in attribute resolution order) then follow the "_add"
# and "_remove" data fields # 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 # the attribute was defined at this level in the resolution
# order # order
if attrname in tdata[target[0]]: if attrname in tdata[tgt[0]]:
def_idx = idx def_idx = idx
break break
else: else:
@ -192,13 +226,13 @@ class Target(object):
def __getattr_helper(self, attrname): def __getattr_helper(self, attrname):
"""Compute the value of a given target attribute""" """Compute the value of a given target attribute"""
if attrname in self.cumulative_attributes: if attrname in CUMULATIVE_ATTRIBUTES:
return self.__getattr_cumulative(attrname) return self.__getattr_cumulative(attrname)
else: else:
tdata = self.get_json_target_data() tdata = self.json_data
starting_value = None starting_value = None
for target in self.resolution_order: for tgt in self.resolution_order:
data = tdata[target[0]] data = tdata[tgt[0]]
if data.has_key(attrname): if data.has_key(attrname):
starting_value = data[attrname] starting_value = data[attrname]
break break
@ -226,17 +260,8 @@ class Target(object):
@cached @cached
def get_target(target_name): def get_target(target_name):
""" Return the target instance starting from the 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 @property
def program_cycle_s(self): def program_cycle_s(self):
@ -248,7 +273,8 @@ class Target(object):
except AttributeError: except AttributeError:
return 4 if self.is_disk_virtual else 1.5 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""" """Get all possible labels for this target"""
labels = [self.name] + CORE_LABELS[self.core] + self.extra_labels labels = [self.name] + CORE_LABELS[self.core] + self.extra_labels
# Automatically define UVISOR_UNSUPPORTED if the target doesn't # 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 """ Returns dictionary mapping detect_code -> platform_name
""" """
result = {} result = {}
for target in TARGETS: for tgt in TARGETS:
for detect_code in target.detect_code: for detect_code in tgt.detect_code:
result[detect_code] = target.name result[detect_code] = tgt.name
return result return result
def set_targets_json_location(location=None): 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 # re-initialization does not create new variables, it keeps the old ones
# instead. This ensures compatibility with code that does # instead. This ensures compatibility with code that does
# "from tools.targets import TARGET_NAMES" # "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() in Target.get_json_target_data().items()
if obj.get("public", True)] if obj.get("public", True)]
TARGET_MAP.clear() 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() TARGET_NAMES[:] = TARGET_MAP.keys()

View File

@ -27,6 +27,7 @@ def compare_config(cfg, expected):
if cfg[k].value != expected[k]: if cfg[k].value != expected[k]:
return "'%s': expected '%s', got '%s'" % (k, expected[k], cfg[k].value) return "'%s': expected '%s', got '%s'" % (k, expected[k], cfg[k].value)
except KeyError: except KeyError:
raise
return "Unexpected key '%s' in configuration data" % k return "Unexpected key '%s' in configuration data" % k
for k in expected: for k in expected:
if k not in ["desc", "expected_macros", "expected_features"] + cfg.keys(): 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 = [c.__name__ for c in getmro(self.__class__)]
toolchain_labels.remove('mbedToolchain') toolchain_labels.remove('mbedToolchain')
self.labels = { 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, 'FEATURE': self.target.features,
'TOOLCHAIN': toolchain_labels 'TOOLCHAIN': toolchain_labels
} }