Skip to content

Commit

Permalink
Refactor Target and Config away from global variables
Browse files Browse the repository at this point in the history
  • Loading branch information
theotherjimmy committed Sep 20, 2016
1 parent e4a40cd commit d4f9820
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 72 deletions.
10 changes: 2 additions & 8 deletions tools/build_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
44 changes: 26 additions & 18 deletions tools/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@
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):
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -397,16 +397,23 @@ def __init__(self, target, top_level_dirs=None, app_config=None):
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):
Expand Down Expand Up @@ -509,7 +516,7 @@ def _process_config_and_overrides(self, data, params, unit_name, unit_kind):
label)))))

for cumulatives in self.cumulative_overrides.itervalues():
cumulatives.update_target(Target.get_target(self.target))
cumulatives.update_target(self.target)

return params

Expand All @@ -528,10 +535,10 @@ def get_target_config_data(self):
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
Expand All @@ -547,9 +554,11 @@ def get_target_config_data(self):
# 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,
Expand Down Expand Up @@ -680,15 +689,14 @@ def get_features(self):
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
Expand Down
116 changes: 71 additions & 45 deletions tools/targets.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,15 @@
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"],
Expand Down Expand Up @@ -60,11 +65,58 @@ def wrapper(*args, **kwargs):
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(
Expand Down Expand Up @@ -95,24 +147,6 @@ def get_module_data():
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
Expand All @@ -133,14 +167,14 @@ def __getattr_cumulative(self, attrname):
"""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:
Expand Down Expand Up @@ -192,13 +226,13 @@ def __getattr_cumulative(self, attrname):

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
Expand Down Expand Up @@ -226,17 +260,8 @@ def __getattr__(self, attrname):
@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):
Expand All @@ -248,7 +273,8 @@ def program_cycle_s(self):
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
Expand Down Expand Up @@ -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):
Expand All @@ -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()
1 change: 1 addition & 0 deletions tools/test/config_test/config_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down
2 changes: 1 addition & 1 deletion tools/toolchains/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ def get_labels(self):
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
}
Expand Down

0 comments on commit d4f9820

Please sign in to comment.