Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Fork of mbed-sdk-tools by
Diff: targets.py
- Revision:
- 29:1210849dba19
- Parent:
- 27:5461402c33f8
- Child:
- 30:f12ce67666d0
diff -r e080013bb94e -r 1210849dba19 targets.py --- a/targets.py Mon Aug 29 10:55:42 2016 +0100 +++ b/targets.py Mon Aug 29 11:18:36 2016 +0100 @@ -15,6 +15,16 @@ limitations under the License. """ +import os +import binascii +import struct +import shutil +import inspect +import sys +from tools.patch import patch +from tools.paths import TOOLS_BOOTLOADERS +from tools.utils import json_file_to_dict + CORE_LABELS = { "ARM7TDMI-S": ["ARM7", "LIKE_CORTEX_ARM7"], "Cortex-M0" : ["M0", "CORTEX_M", "LIKE_CORTEX_M0"], @@ -29,240 +39,313 @@ "Cortex-A9" : ["A9", "CORTEX_A", "LIKE_CORTEX_A9"] } -import os -import binascii -import struct -import shutil -from tools.patch import patch -from paths import TOOLS_BOOTLOADERS -import json -import inspect -import sys -from tools.utils import json_file_to_dict - -######################################################################################################################## +################################################################################ # Generic Target class that reads and interprets the data in targets.json -# A simple class that represents all the exceptions associated with hooking class HookError(Exception): + """ A simple class that represents all the exceptions associated with + hooking + """ pass -# A simple decorator used for automatically caching data returned by a function -caches = {} +CACHES = {} def cached(func): + """A simple decorator used for automatically caching data returned by a + function + """ def wrapper(*args, **kwargs): - if not caches.has_key((func.__name__, args)): - caches[(func.__name__, args)] = func(*args, **kwargs) - return caches[(func.__name__, args)] + """The wrapped function itself""" + if not CACHES.has_key((func.__name__, args)): + CACHES[(func.__name__, args)] = func(*args, **kwargs) + return CACHES[(func.__name__, args)] return wrapper -class Target: +class Target(object): + """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'] + cumulative_attributes = ['extra_labels', 'macros', 'device_has', 'features'] - # List of targets that were added dynamically using "add_py_targets" (see below) + # List of targets that were added dynamically using "add_py_targets" (see + # below) __py_targets = set() - # Location of the 'targets.json' file - __targets_json_location = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'latest_targets.json') + # Default location of the 'targets.json' file + __targets_json_location_default = os.path.join( + os.path.dirname(os.path.abspath(__file__)), 'latest_targets.json') - # Load the description of JSON target data + # Current/new location of the 'targets.json' file + __targets_json_location = None + @staticmethod @cached def get_json_target_data(): - return json_file_to_dict(Target.__targets_json_location) + """Load the description of JSON target data""" + return json_file_to_dict(Target.__targets_json_location or + Target.__targets_json_location_default) - # Set the location of the targets.json file @staticmethod - def set_targets_json_location(location): - Target.__targets_json_location = location + def set_targets_json_location(location=None): + """Set the location of the targets.json file""" + Target.__targets_json_location = (location or + Target.__targets_json_location_default) # Invalidate caches, since the location of the JSON file changed - caches.clear() + CACHES.clear() - # Get the members of this module using Python's "inspect" module @staticmethod @cached def get_module_data(): - return dict([(m[0], m[1]) for m in inspect.getmembers(sys.modules[__name__])]) + """Get the members of this module using Python's "inspect" module""" + return dict([(m[0], m[1]) for m in + inspect.getmembers(sys.modules[__name__])]) - # 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 1 and so on) - def __get_resolution_order(self, target_name, order, level = 0): - if not target_name in [l[0] for l in order]: # the resolution order can't contain duplicate target names + 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 p in parents: - order = self.__get_resolution_order(p, order, level + 1) + for par in parents: + order = self.__get_resolution_order(par, order, level + 1) return order - # Modify the exporter specification ("progen") by changing all "template" keys to full paths @staticmethod def __add_paths_to_progen(data): + """Modify the exporter specification ("progen") by changing all + "template" keys to full paths + """ out = {} - for key, value in data.items(): - if isinstance(value, dict): - out[key] = Target.__add_paths_to_progen(value) + for key, val in data.items(): + if isinstance(val, dict): + out[key] = Target.__add_paths_to_progen(val) elif key == "template": - out[key] = [os.path.join(os.path.dirname(__file__), 'export', v) for v in value] + out[key] = [os.path.join(os.path.dirname(__file__), 'export', v) + for v in val] else: - out[key] = value + out[key] = val return out - # Comute the value of a given target attribute - def __getattr_helper(self, attrname): + 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() - if attrname in self.__cumulative_attributes: - # 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, t in enumerate(self.resolution_order): - if attrname in tdata[t[0]]: # the attribute was defined at this level in the resolution order - def_idx = idx + # 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): + # the attribute was defined at this level in the resolution + # order + if attrname in tdata[target[0]]: + def_idx = idx + break + else: + raise AttributeError("Attribute '%s' not found in target '%s'" + % (attrname, self.name)) + # Get the starting value of the attribute + starting_value = (tdata[self.resolution_order[def_idx][0]][attrname] + or [])[:] + # Traverse the resolution list in high inheritance to low + # inheritance level, left to right order to figure out all the + # other classes that change the definition by adding or removing + # elements + for idx in xrange(self.resolution_order[def_idx][1] - 1, -1, -1): + same_level_targets = [tar[0] for tar in self.resolution_order + if tar[1] == idx] + for tar in same_level_targets: + data = tdata[tar] + # Do we have anything to add ? + if data.has_key(attrname + "_add"): + starting_value.extend(data[attrname + "_add"]) + # Do we have anything to remove ? + if data.has_key(attrname + "_remove"): + # Macros can be defined either without a value (MACRO) + # or with a value (MACRO=10). When removing, we specify + # only the name of the macro, without the value. So we + # need to create a mapping between the macro name and + # its value. This will work for extra_labels and other + # type of arrays as well, since they fall into the + # "macros without a value" category (simple definitions + # without a value). + name_def_map = {} + for crtv in starting_value: + if crtv.find('=') != -1: + temp = crtv.split('=') + if len(temp) != 2: + raise ValueError( + "Invalid macro definition '%s'" % crtv) + name_def_map[temp[0]] = crtv + else: + name_def_map[crtv] = crtv + for element in data[attrname + "_remove"]: + if element not in name_def_map: + raise ValueError( + ("Unable to remove '%s' in '%s.%s' since " + % (element, self.name, attrname)) + + "it doesn't exist") + starting_value.remove(name_def_map[element]) + return starting_value + + def __getattr_helper(self, attrname): + """Compute the value of a given target attribute""" + if attrname in self.cumulative_attributes: + return self.__getattr_cumulative(attrname) + else: + tdata = self.get_json_target_data() + starting_value = None + for target in self.resolution_order: + data = tdata[target[0]] + if data.has_key(attrname): + starting_value = data[attrname] break + else: # Attribute not found + raise AttributeError( + "Attribute '%s' not found in target '%s'" + % (attrname, self.name)) + # 'progen' needs the full path to the template (the path in JSON is + # relative to tools/export) + if attrname == "progen": + return self.__add_paths_to_progen(starting_value) else: - raise AttributeError("Attribute '%s' not found in target '%s'" % (attrname, self.name)) - # Get the starting value of the attribute - v = (tdata[self.resolution_order[def_idx][0]][attrname] or [])[:] - # Traverse the resolution list in high inheritance to low inheritance level, left to right order - # to figure out all the other classes that change the definition by adding or removing elements - for idx in xrange(self.resolution_order[def_idx][1] - 1, -1, -1): - same_level_targets = [t[0] for t in self.resolution_order if t[1] == idx] - for t in same_level_targets: - data = tdata[t] - # Do we have anything to add ? - if data.has_key(attrname + "_add"): - v.extend(data[attrname + "_add"]) - # Do we have anything to remove ? - if data.has_key(attrname + "_remove"): - # Macros can be defined either without a value (MACRO) or with a value (MACRO=10). - # When removing, we specify only the name of the macro, without the value. So we need - # to create a mapping between the macro name and its value. This will work for - # extra_labels and other type of arrays as well, since they fall into the "macros - # without a value" category (simple definitions without a value). - name_def_map = {} - for crtv in v: - if crtv.find('=') != -1: - temp = crtv.split('=') - if len(temp) != 2: - raise ValueError("Invalid macro definition '%s'" % crtv) - name_def_map[temp[0]] = crtv - else: - name_def_map[crtv] = crtv - for e in data[attrname + "_remove"]: - if not e in name_def_map: - raise ValueError("Unable to remove '%s' in '%s.%s' since it doesn't exist" % (e, self.name, attrname)) - v.remove(name_def_map[e]) - return v - # Look for the attribute in the class and its parents, as defined by the resolution order - v = None - for t in self.resolution_order: - data = tdata[t[0]] - if data.has_key(attrname): - v = data[attrname] - break - else: # Attribute not found - raise AttributeError("Attribute '%s' not found in target '%s'" % (attrname, self.name)) - # 'progen' needs the full path to the template (the path in JSON is relative to tools/export) - return v if attrname != "progen" else self.__add_paths_to_progen(v) + return starting_value - # Return the value of an attribute - # This function only computes the attribute's value once, then adds it to the instance attributes - # (in __dict__), so the next time it is returned directly def __getattr__(self, attrname): - v = self.__getattr_helper(attrname) - self.__dict__[attrname] = v - return v + """ Return the value of an attribute. This function only computes the + attribute's value once, then adds it to the instance attributes (in + __dict__), so the next time it is returned directly + """ + result = self.__getattr_helper(attrname) + self.__dict__[attrname] = result + return result - # 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. @staticmethod def add_py_targets(new_targets): + """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. + """ crt_data = Target.get_json_target_data() - for tk, tv in new_targets.items(): - if crt_data.has_key(tk): - raise Exception("Attempt to add target '%s' that already exists" % tk) + for target_key, target_value in new_targets.items(): + if crt_data.has_key(target_key): + raise Exception( + "Attempt to add target '%s' that already exists" + % target_key) # Add target data to the internal target dictionary - crt_data[tk] = tv + crt_data[target_key] = target_value # Create the new target and add it to the relevant data structures - new_target = Target(tk) + new_target = Target(target_key) TARGETS.append(new_target) - TARGET_MAP[tk] = new_target - TARGET_NAMES.append(tk) + TARGET_MAP[target_key] = new_target + TARGET_NAMES.append(target_key) - # Return the target instance starting from the target name @staticmethod @cached - def get_target(name): - return Target(name) + def get_target(target_name): + """ Return the target instance starting from the target name """ + return Target(target_name) - def __init__(self, name): - self.name = name + 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 = [t[0] for t in self.resolution_order] + # 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): + """Special override for program_cycle_s as it's default value depends + upon is_disk_virtual + """ try: return self.__getattr__("program_cycle_s") except AttributeError: return 4 if self.is_disk_virtual else 1.5 def get_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 specifically - # define UVISOR_SUPPORTED - if not "UVISOR_SUPPORTED" in labels: + # Automatically define UVISOR_UNSUPPORTED if the target doesn't + # specifically define UVISOR_SUPPORTED + if "UVISOR_SUPPORTED" not in labels: labels.append("UVISOR_UNSUPPORTED") return labels - # For now, this function only allows "post binary" hooks (hooks that are executed after - # the binary image is extracted from the executable file) def init_hooks(self, hook, toolchain_name): + """Initialize the post-build hooks for a toolchain. For now, this + function only allows "post binary" hooks (hooks that are executed + after the binary image is extracted from the executable file) + """ + # If there's no hook, simply return try: hook_data = self.post_binary_hook except AttributeError: return - # A hook was found. The hook's name is in the format "classname.functionname" + # A hook was found. The hook's name is in the format + # "classname.functionname" temp = hook_data["function"].split(".") if len(temp) != 2: - raise HookError("Invalid format for hook '%s' in target '%s' (must be 'class_name.function_name')" % (hook_data["function"], self.name)) + raise HookError( + ("Invalid format for hook '%s' in target '%s'" + % (hook_data["function"], self.name)) + + " (must be 'class_name.function_name')") class_name, function_name = temp[0], temp[1] - # "class_name" must refer to a class in this file, so check if the class exists + # "class_name" must refer to a class in this file, so check if the + # class exists mdata = self.get_module_data() - if not mdata.has_key(class_name) or not inspect.isclass(mdata[class_name]): - raise HookError("Class '%s' required by '%s' in target '%s' not found in targets.py" % (class_name, hook_data["function"], self.name)) - # "function_name" must refer to a static function inside class "class_name" + if not mdata.has_key(class_name) or \ + not inspect.isclass(mdata[class_name]): + raise HookError( + ("Class '%s' required by '%s' in target '%s'" + % (class_name, hook_data["function"], self.name)) + + " not found in targets.py") + # "function_name" must refer to a static function inside class + # "class_name" cls = mdata[class_name] - if (not hasattr(cls, function_name)) or (not inspect.isfunction(getattr(cls, function_name))): - raise HookError("Static function '%s' required by '%s' in target '%s' not found in class '%s'" % (function_name, hook_data["function"], self.name, class_name)) + if (not hasattr(cls, function_name)) or \ + (not inspect.isfunction(getattr(cls, function_name))): + raise HookError( + ("Static function '%s' " % function_name) + + ("required by '%s' " % hook_data["function"]) + + ("in target '%s' " % self.name) + + ("not found in class '%s'" % class_name)) # Check if the hook specification also has target restrictions toolchain_restrictions = hook_data.get("toolchains", []) - if toolchain_restrictions and (toolchain_name not in toolchain_restrictions): + if toolchain_restrictions and \ + (toolchain_name not in toolchain_restrictions): return # Finally, hook the requested function hook.hook_add_binary("post", getattr(cls, function_name)) -######################################################################################################################## +################################################################################ # Target specific code goes in this section -# This code can be invoked from the target description using the "post_binary_hook" key +# This code can be invoked from the target description using the +# "post_binary_hook" key -class LPCTargetCode: +class LPCTargetCode(object): + """General LPC Target patching code""" @staticmethod def lpc_patch(t_self, resources, elf, binf): + """Patch an elf file""" t_self.debug("LPC Patch: %s" % os.path.split(binf)[1]) patch(binf) -class LPC4088Code: +class LPC4088Code(object): + """Code specific to the LPC4088""" @staticmethod def binary_hook(t_self, resources, elf, binf): + """Hook to be run after an elf file is built""" if not os.path.isdir(binf): # Regular binary file, nothing to do LPCTargetCode.lpc_patch(t_self, resources, elf, binf) @@ -274,7 +357,8 @@ outbin.write(data) outbin.write('\xFF' * (512*1024 - len(data))) partf.close() - # Read and append the second part (external flash) in chunks of fixed size + # Read and append the second part (external flash) in chunks of fixed + # size chunksize = 128 * 1024 partf = open(os.path.join(binf, "ER_IROM2"), "rb") while True: @@ -291,19 +375,23 @@ t_self.debug("Generated custom binary file (internal flash + SPIFI)") LPCTargetCode.lpc_patch(t_self, resources, elf, binf) -class TEENSY3_1Code: +class TEENSY3_1Code(object): + """Hooks for the TEENSY3.1""" @staticmethod def binary_hook(t_self, resources, elf, binf): + """Hook that is run after elf is generated""" from intelhex import IntelHex binh = IntelHex() - binh.loadbin(binf, offset = 0) + binh.loadbin(binf, offset=0) + + with open(binf.replace(".bin", ".hex"), "w") as file_desc: + binh.tofile(file_desc, format='hex') - with open(binf.replace(".bin", ".hex"), "w") as f: - binh.tofile(f, format='hex') - -class MTSCode: +class MTSCode(object): + """Generic MTS code""" @staticmethod - def _combine_bins_helper(target_name, t_self, resources, elf, binf): + def _combine_bins_helper(target_name, binf): + """combine bins with the bootloader for a particular target""" loader = os.path.join(TOOLS_BOOTLOADERS, target_name, "bootloader.bin") target = binf + ".tmp" if not os.path.exists(loader): @@ -330,51 +418,62 @@ @staticmethod def combine_bins_mts_dot(t_self, resources, elf, binf): - MTSCode._combine_bins_helper("MTS_MDOT_F411RE", t_self, resources, elf, binf) + """A hook for the MTS MDOT""" + MTSCode._combine_bins_helper("MTS_MDOT_F411RE", binf) @staticmethod def combine_bins_mts_dragonfly(t_self, resources, elf, binf): - MTSCode._combine_bins_helper("MTS_DRAGONFLY_F411RE", t_self, resources, elf, binf) + """A hoof for the MTS Dragonfly""" + MTSCode._combine_bins_helper("MTS_DRAGONFLY_F411RE", binf) -class MCU_NRF51Code: +class MCU_NRF51Code(object): + """NRF51 Hooks""" @staticmethod - def binary_hook(t_self, resources, elf, binf): - + def binary_hook(t_self, resources, _, binf): + """Hook that merges the soft device with the bin file""" # Scan to find the actual paths of soft device sdf = None - for softdeviceAndOffsetEntry in t_self.target.EXPECTED_SOFTDEVICES_WITH_OFFSETS: + for softdevice_and_offset_entry\ + in t_self.target.EXPECTED_SOFTDEVICES_WITH_OFFSETS: for hexf in resources.hex_files: - if hexf.find(softdeviceAndOffsetEntry['name']) != -1: - t_self.debug("SoftDevice file found %s." % softdeviceAndOffsetEntry['name']) + if hexf.find(softdevice_and_offset_entry['name']) != -1: + t_self.debug("SoftDevice file found %s." + % softdevice_and_offset_entry['name']) sdf = hexf - if sdf is not None: break - if sdf is not None: break + if sdf is not None: + break + if sdf is not None: + break if sdf is None: t_self.debug("Hex file not found. Aborting.") return - # Look for bootloader file that matches this soft device or bootloader override image + # Look for bootloader file that matches this soft device or bootloader + # override image blf = None if t_self.target.MERGE_BOOTLOADER is True: for hexf in resources.hex_files: if hexf.find(t_self.target.OVERRIDE_BOOTLOADER_FILENAME) != -1: - t_self.debug("Bootloader file found %s." % t_self.target.OVERRIDE_BOOTLOADER_FILENAME) + t_self.debug("Bootloader file found %s." + % t_self.target.OVERRIDE_BOOTLOADER_FILENAME) blf = hexf break - elif hexf.find(softdeviceAndOffsetEntry['boot']) != -1: - t_self.debug("Bootloader file found %s." % softdeviceAndOffsetEntry['boot']) + elif hexf.find(softdevice_and_offset_entry['boot']) != -1: + t_self.debug("Bootloader file found %s." + % softdevice_and_offset_entry['boot']) blf = hexf break # Merge user code with softdevice from intelhex import IntelHex binh = IntelHex() - binh.loadbin(binf, offset=softdeviceAndOffsetEntry['offset']) + binh.loadbin(binf, offset=softdevice_and_offset_entry['offset']) if t_self.target.MERGE_SOFT_DEVICE is True: - t_self.debug("Merge SoftDevice file %s" % softdeviceAndOffsetEntry['name']) + t_self.debug("Merge SoftDevice file %s" + % softdevice_and_offset_entry['name']) sdh = IntelHex(sdf) binh.merge(sdh) @@ -383,13 +482,21 @@ blh = IntelHex(blf) binh.merge(blh) - with open(binf.replace(".bin", ".hex"), "w") as f: - binh.tofile(f, format='hex') + with open(binf.replace(".bin", ".hex"), "w") as fileout: + binh.tofile(fileout, format='hex') -######################################################################################################################## +class NCS36510TargetCode: + @staticmethod + def ncs36510_addfib(t_self, resources, elf, binf): + from tools.add_fib import add_fib_at_start + print("binf ", binf) + add_fib_at_start(binf[:-4]) +################################################################################ # Instantiate all public targets -TARGETS = [Target.get_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 TARGET_MAP = dict([(t.name, t) for t in TARGETS]) @@ -409,14 +516,17 @@ result[detect_code] = target.name return result -# Sets the location of the JSON file that contains the targets -def set_targets_json_location(location): +def set_targets_json_location(location=None): + """Sets the location of the JSON file that contains the targets""" # First instruct Target about the new location Target.set_targets_json_location(location) - # Then re-initialize TARGETS, TARGET_MAP and TARGET_NAMES - # The 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(name) for name, value in Target.get_json_target_data().items() if value.get("public", True)] + # Then re-initialize TARGETS, TARGET_MAP and TARGET_NAMES. The + # 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 + in Target.get_json_target_data().items() + if obj.get("public", True)] TARGET_MAP.clear() - TARGET_MAP.update(dict([(t.name, t) for t in TARGETS])) + TARGET_MAP.update(dict([(target.name, target) for target in TARGETS])) TARGET_NAMES[:] = TARGET_MAP.keys()