Improvements to the targets implementation

- added a method that can be used to new targets dynamically (this
  will be used by the configuration mechanism).
- the JSON parser now keeps the order of the keys read from the JSON
  file (will also be used by the configuration mechanism).
- there's now a global target cache in targets.py, so that a target
  with a given name will only be created once.
Bogdan Marinescu 2016-05-29 17:06:38 +03:00
parent 8dc9b0b226
commit 7c920259cc
2 changed files with 75 additions and 27 deletions

View File

@ -37,7 +37,7 @@ from paths import TOOLS_BOOTLOADERS
import json import json
import inspect import inspect
import sys import sys
from tools.utils import json_file_to_dict
######################################################################################################################## ########################################################################################################################
# Generic Target class that reads and interprets the data in targets.json # Generic Target class that reads and interprets the data in targets.json
@ -60,27 +60,17 @@ class Target:
# need to be computed differently than regular attributes # need to be computed differently than regular attributes
__cumulative_attributes = ['extra_labels', 'macros'] __cumulative_attributes = ['extra_labels', 'macros']
# Utility function: traverse a dictionary and change all the strings in the dictionary to # {target_name: target_instance} map for all the targets in the system
# ASCII from Unicode. Needed because the original mbed target definitions were written in __target_map = {}
# Python and used only ASCII strings, but the Python JSON decoder always returns Unicode
# Based on http://stackoverflow.com/a/13105359 # List of targets that were added dynamically using "add_py_targets" (see below)
@staticmethod __py_targets = set()
def to_ascii(input):
if isinstance(input, dict):
return dict([(Target.to_ascii(key), Target.to_ascii(value)) for key, value in input.iteritems()])
elif isinstance(input, list):
return [Target.to_ascii(element) for element in input]
elif isinstance(input, unicode):
return input.encode('ascii')
else:
return input
# Load the description of JSON target data # Load the description of JSON target data
@staticmethod @staticmethod
@cached @cached
def get_json_target_data(): def get_json_target_data():
with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../mbed/hal/targets.json"), "rt") as f: return json_file_to_dict(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../mbed/hal/targets.json"))
return Target.to_ascii(json.load(f))
# Get the members of this module using Python's "inspect" module # Get the members of this module using Python's "inspect" module
@staticmethod @staticmethod
@ -172,21 +162,58 @@ class Target:
return v if attrname != "progen" else self.__add_paths_to_progen(v) return v if attrname != "progen" else self.__add_paths_to_progen(v)
# Return the value of an attribute # Return the value of an attribute
# This function only looks for the attribute's value in the cache, the real work of computing the # This function only computes the attribute's value once, then adds it to the instance attributes
# attribute's value is done in the function above (__getattr_helper) # (in __dict__), so the next time it is returned directly
def __getattr__(self, attrname): def __getattr__(self, attrname):
if not self.attr_cache.has_key(attrname): v = self.__getattr_helper(attrname)
self.attr_cache[attrname] = self.__getattr_helper(attrname) self.__dict__[attrname] = v
return self.attr_cache[attrname] return v
# Add one or more new target(s) represented as a Python dictionary in 'new_targets'
# It it an error to add a target with a name that exists in "targets.json"
# However, it is OK to add a target that was previously added via "add_py_targets"
# (this makes testing easier without changing the regular semantics)
@staticmethod
def add_py_targets(new_targets):
crt_data = Target.get_json_target_data()
# First add all elemnts to the internal dictionary
for tk, tv in new_targets.items():
if crt_data.has_key(tk) and (not tk in Target.__py_targets):
raise Exception("Attempt to add target '%s' that already exists" % tk)
crt_data[tk] = tv
Target.__py_targets.add(tk)
# Then create the new instances and update global variables if needed
for tk, tv in new_targets.items():
# Is the target already created?
old_target = Target.__target_map.get(tk, None)
# Instantiate this target. If it is public, update the data in
# in TARGETS, TARGET_MAP, TARGET_NAMES
new_target = Target(tk)
if tv.get("public", True):
if old_target: # remove the old target from TARGETS and TARGET_NAMES
TARGETS.remove(old_target)
TARGET_NAMES.remove(tk)
# Add the new target
TARGETS.append(new_target)
TARGET_MAP[tk] = new_target
TARGET_NAMES.append(tk)
# Update the target cache
Target.__target_map[tk] = new_target
# Return the target instance starting from the target name
@staticmethod
def get_target(name):
if not Target.__target_map.has_key(name):
Target.__target_map[name] = Target(name)
return Target.__target_map[name]
def __init__(self, name): def __init__(self, name):
self.name = name self.name = name
# Compute resolution order once (it will be used later in __getattr__) # Compute resolution order once (it will be used later in __getattr__)
self.resolution_order = self.__get_resolution_order(self.name, []) self.resolution_order = self.__get_resolution_order(self.name, [])
# Create also a list with only the names of the targets in the resolution order
# Attribute cache: once an attribute's value is computed, don't compute it again self.resolution_order_names = [t[0] for t in self.resolution_order]
self.attr_cache = {}
def program_cycle_s(self): def program_cycle_s(self):
try: try:
@ -364,7 +391,7 @@ class MCU_NRF51Code:
######################################################################################################################## ########################################################################################################################
# Instantiate all public targets # Instantiate all public targets
TARGETS = [Target(name) for name, value in Target.get_json_target_data().items() if value.get("public", True)] TARGETS = [Target.get_target(name) for name, value in Target.get_json_target_data().items() if value.get("public", True)]
# Map each target name to its unique instance # Map each target name to its unique instance
TARGET_MAP = dict([(t.name, t) for t in TARGETS]) TARGET_MAP = dict([(t.name, t) for t in TARGETS])

View File

@ -21,7 +21,8 @@ from os import listdir, remove, makedirs
from shutil import copyfile from shutil import copyfile
from os.path import isdir, join, exists, split, relpath, splitext from os.path import isdir, join, exists, split, relpath, splitext
from subprocess import Popen, PIPE, STDOUT, call from subprocess import Popen, PIPE, STDOUT, call
import json
from collections import OrderedDict
def cmd(l, check=True, verbose=False, shell=False, cwd=None): def cmd(l, check=True, verbose=False, shell=False, cwd=None):
text = l if shell else ' '.join(l) text = l if shell else ' '.join(l)
@ -174,3 +175,23 @@ def check_required_modules(required_modules, verbose=True):
return False return False
else: else:
return True return True
# Utility function: traverse a dictionary and change all the strings in the dictionary to
# ASCII from Unicode. Useful when reading ASCII JSON data, because the JSON decoder always
# returns Unicode string.
# Based on http://stackoverflow.com/a/13105359
def dict_to_ascii(input):
if isinstance(input, dict):
return OrderedDict([(dict_to_ascii(key), dict_to_ascii(value)) for key, value in input.iteritems()])
elif isinstance(input, list):
return [dict_to_ascii(element) for element in input]
elif isinstance(input, unicode):
return input.encode('ascii')
else:
return input
# Read a JSON file and return its Python representation, transforming all the strings from Unicode
# to ASCII. The order of keys in the JSON file is preserved.
def json_file_to_dict(fname):
with open(fname, "rt") as f:
return dict_to_ascii(json.load(f, object_pairs_hook=OrderedDict))