Merge pull request #10065 from theotherjimmy/fix-postbuild-restrictions

Correct post-build-hook detection logic
pull/10081/head
Cruz Monrreal 2019-03-16 22:58:58 -05:00 committed by GitHub
commit 241e6d2b46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 112 additions and 58 deletions

View File

@ -22,9 +22,8 @@ import struct
import shutil import shutil
import inspect import inspect
import sys import sys
from collections import namedtuple
from copy import copy from copy import copy
from inspect import getmro
from collections import namedtuple, Mapping
from future.utils import raise_from from future.utils import raise_from
from tools.resources import FileType from tools.resources import FileType
from tools.targets.LPC import patch from tools.targets.LPC import patch
@ -45,16 +44,21 @@ CORE_LABELS = {
"Cortex-M4F": ["M4", "CORTEX_M", "RTOS_M4_M7", "LIKE_CORTEX_M4", "CORTEX"], "Cortex-M4F": ["M4", "CORTEX_M", "RTOS_M4_M7", "LIKE_CORTEX_M4", "CORTEX"],
"Cortex-M7": ["M7", "CORTEX_M", "RTOS_M4_M7", "LIKE_CORTEX_M7", "CORTEX"], "Cortex-M7": ["M7", "CORTEX_M", "RTOS_M4_M7", "LIKE_CORTEX_M7", "CORTEX"],
"Cortex-M7F": ["M7", "CORTEX_M", "RTOS_M4_M7", "LIKE_CORTEX_M7", "CORTEX"], "Cortex-M7F": ["M7", "CORTEX_M", "RTOS_M4_M7", "LIKE_CORTEX_M7", "CORTEX"],
"Cortex-M7FD": ["M7", "CORTEX_M", "RTOS_M4_M7", "LIKE_CORTEX_M7", "CORTEX"], "Cortex-M7FD": ["M7", "CORTEX_M", "RTOS_M4_M7", "LIKE_CORTEX_M7",
"CORTEX"],
"Cortex-A9": ["A9", "CORTEX_A", "LIKE_CORTEX_A9", "CORTEX"], "Cortex-A9": ["A9", "CORTEX_A", "LIKE_CORTEX_A9", "CORTEX"],
"Cortex-M23": ["M23", "CORTEX_M", "LIKE_CORTEX_M23", "CORTEX"], "Cortex-M23": ["M23", "CORTEX_M", "LIKE_CORTEX_M23", "CORTEX"],
"Cortex-M23-NS": ["M23", "M23_NS", "CORTEX_M", "LIKE_CORTEX_M23", "CORTEX"], "Cortex-M23-NS": ["M23", "M23_NS", "CORTEX_M", "LIKE_CORTEX_M23",
"CORTEX"],
"Cortex-M33": ["M33", "CORTEX_M", "LIKE_CORTEX_M33", "CORTEX"], "Cortex-M33": ["M33", "CORTEX_M", "LIKE_CORTEX_M33", "CORTEX"],
"Cortex-M33-NS": ["M33", "M33_NS", "CORTEX_M", "LIKE_CORTEX_M33", "CORTEX"], "Cortex-M33-NS": ["M33", "M33_NS", "CORTEX_M", "LIKE_CORTEX_M33",
"CORTEX"],
"Cortex-M33F": ["M33", "CORTEX_M", "LIKE_CORTEX_M33", "CORTEX"], "Cortex-M33F": ["M33", "CORTEX_M", "LIKE_CORTEX_M33", "CORTEX"],
"Cortex-M33F-NS": ["M33", "M33_NS", "CORTEX_M", "LIKE_CORTEX_M33", "CORTEX"], "Cortex-M33F-NS": ["M33", "M33_NS", "CORTEX_M", "LIKE_CORTEX_M33",
"CORTEX"],
"Cortex-M33FE": ["M33", "CORTEX_M", "LIKE_CORTEX_M33", "CORTEX"], "Cortex-M33FE": ["M33", "CORTEX_M", "LIKE_CORTEX_M33", "CORTEX"],
"Cortex-M33FE-NS": ["M33", "M33_NS", "CORTEX_M", "LIKE_CORTEX_M33", "CORTEX"] "Cortex-M33FE-NS": ["M33", "M33_NS", "CORTEX_M", "LIKE_CORTEX_M33",
"CORTEX"]
} }
CORE_ARCH = { CORE_ARCH = {
@ -78,16 +82,20 @@ CORE_ARCH = {
"Cortex-M33FE-NS": 8, "Cortex-M33FE-NS": 8,
} }
################################################################################ ###############################################################################
# Generic Target class that reads and interprets the data in targets.json # Generic Target class that reads and interprets the data in targets.json
class HookError(Exception): class HookError(Exception):
""" A simple class that represents all the exceptions associated with """ A simple class that represents all the exceptions associated with
hooking hooking
""" """
pass pass
CACHES = {} CACHES = {}
def cached(func): def cached(func):
"""A simple decorator used for automatically caching data returned by a """A simple decorator used for automatically caching data returned by a
function function
@ -102,9 +110,15 @@ def cached(func):
# Cumulative attributes can have values appended to them, so they # Cumulative attributes can have values appended to them, so they
# need to be computed differently than regular attributes # need to be computed differently than regular attributes
CUMULATIVE_ATTRIBUTES = ['extra_labels', 'macros', 'device_has', 'features', 'components']
default_build_tools_metadata = {u'version':0, u'public':False}
CUMULATIVE_ATTRIBUTES = [
'extra_labels', 'macros', 'device_has', 'features', 'components'
]
default_build_tools_metadata = {u'version': 0, u'public': False}
def get_resolution_order(json_data, target_name, order, level=0): def get_resolution_order(json_data, target_name, order, level=0):
""" Return the order in which target descriptions are searched for """ Return the order in which target descriptions are searched for
@ -128,8 +142,10 @@ def get_resolution_order(json_data, target_name, order, level=0):
def target(name, json_data): def target(name, json_data):
"""Construct a target object""" """Construct a target object"""
if name.startswith("_"): if name.startswith("_"):
raise Exception("Invalid target name '%s' specified, target name should not start with '_'" % name) raise Exception(
"Invalid target name '%s' specified,"
" target name should not start with '_'" % name
)
try: try:
resolution_order = get_resolution_order(json_data, name, []) resolution_order = get_resolution_order(json_data, name, [])
except KeyError as exc: except KeyError as exc:
@ -137,13 +153,18 @@ def target(name, json_data):
"target {} has an incomplete target definition".format(name) "target {} has an incomplete target definition".format(name)
), exc) ), exc)
resolution_order_names = [tgt for tgt, _ in resolution_order] resolution_order_names = [tgt for tgt, _ in resolution_order]
return Target(
return Target(name=name, name=name,
json_data={key: value for key, value in json_data.items() json_data={key: value for key, value in json_data.items()
if key in resolution_order_names}, if key in resolution_order_names},
resolution_order=resolution_order, resolution_order=resolution_order,
resolution_order_names=resolution_order_names, resolution_order_names=resolution_order_names,
build_tools_metadata=json_data.get("__build_tools_metadata__", default_build_tools_metadata)) build_tools_metadata=json_data.get(
"__build_tools_metadata__",
default_build_tools_metadata
)
)
def generate_py_target(new_targets, name): def generate_py_target(new_targets, name):
"""Add one or more new target(s) represented as a Python dictionary """Add one or more new target(s) represented as a Python dictionary
@ -158,15 +179,21 @@ def generate_py_target(new_targets, name):
total_data = {} total_data = {}
total_data.update(new_targets) total_data.update(new_targets)
total_data.update(base_targets) total_data.update(base_targets)
return target(name, total_data) return target(name, total_data)
class Target(namedtuple("Target", "name json_data resolution_order resolution_order_names build_tools_metadata")):
class Target(namedtuple(
"Target",
"name json_data resolution_order "
"resolution_order_names build_tools_metadata"
)):
"""An object to represent a Target (MCU/Board)""" """An object to represent a Target (MCU/Board)"""
# 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(
os.path.dirname(os.path.abspath(__file__)), '..', '..', 'targets', 'targets.json') os.path.dirname(os.path.abspath(__file__)),
'..', '..', 'targets', 'targets.json'
)
# Current/new location of the 'targets.json' file # Current/new location of the 'targets.json' file
__targets_json_location = None __targets_json_location = None
@ -188,8 +215,10 @@ class Target(namedtuple("Target", "name json_data resolution_order resolution_or
for extra_target in Target.__extra_target_json_files: for extra_target in Target.__extra_target_json_files:
for k, v in json_file_to_dict(extra_target).items(): for k, v in json_file_to_dict(extra_target).items():
if k in targets: if k in targets:
print('WARNING: Custom target "%s" cannot replace existing ' print(
'target.' % k) 'WARNING: Custom target "%s" cannot replace existing '
'target.' % k
)
else: else:
targets[k] = v targets[k] = v
targets[k]["_from_file"] = extra_target targets[k]["_from_file"] = extra_target
@ -206,8 +235,10 @@ class Target(namedtuple("Target", "name json_data resolution_order resolution_or
@staticmethod @staticmethod
def set_targets_json_location(location=None): def set_targets_json_location(location=None):
"""Set the location of the targets.json file""" """Set the location of the targets.json file"""
Target.__targets_json_location = (location or Target.__targets_json_location = (
Target.__targets_json_location_default) location or
Target.__targets_json_location_default
)
Target.__extra_target_json_files = [] Target.__extra_target_json_files = []
# Invalidate caches, since the location of the JSON file changed # Invalidate caches, since the location of the JSON file changed
CACHES.clear() CACHES.clear()
@ -229,8 +260,10 @@ class Target(namedtuple("Target", "name json_data resolution_order resolution_or
if isinstance(val, dict): if isinstance(val, dict):
out[key] = Target.__add_paths_to_progen(val) out[key] = Target.__add_paths_to_progen(val)
elif key == "template": elif key == "template":
out[key] = [os.path.join(os.path.dirname(__file__), 'export', v) out[key] = [
for v in val] os.path.join(os.path.dirname(__file__), 'export', v)
for v in val
]
else: else:
out[key] = val out[key] = val
return out return out
@ -301,14 +334,13 @@ class Target(namedtuple("Target", "name json_data resolution_order resolution_or
return self.__getattr_cumulative(attrname) return self.__getattr_cumulative(attrname)
else: else:
tdata = self.json_data tdata = self.json_data
starting_value = None
for tgt in self.resolution_order: for tgt in self.resolution_order:
data = tdata[tgt[0]] data = tdata[tgt[0]]
try: try:
return data[attrname] return data[attrname]
except KeyError: except KeyError:
pass pass
else: # Attribute not found else: # Attribute not found
raise AttributeError( raise AttributeError(
"Attribute '%s' not found in target '%s'" "Attribute '%s' not found in target '%s'"
% (attrname, self.name)) % (attrname, self.name))
@ -328,7 +360,6 @@ class Target(namedtuple("Target", "name json_data resolution_order resolution_or
""" Return the target instance starting from the target name """ """ Return the target instance starting from the target name """
return target(target_name, Target.get_json_target_data()) return target(target_name, Target.get_json_target_data())
@property @property
def program_cycle_s(self): def program_cycle_s(self):
"""Special override for program_cycle_s as it's default value depends """Special override for program_cycle_s as it's default value depends
@ -356,7 +387,7 @@ class Target(namedtuple("Target", "name json_data resolution_order resolution_or
def is_PSA_non_secure_target(self): def is_PSA_non_secure_target(self):
return 'NSPE_Target' in self.labels return 'NSPE_Target' in self.labels
def get_post_build_hook(self, toolchain): def get_post_build_hook(self, toolchain_labels):
"""Initialize the post-build hooks for a toolchain. For now, this """Initialize the post-build hooks for a toolchain. For now, this
function only allows "post binary" hooks (hooks that are executed function only allows "post binary" hooks (hooks that are executed
after the binary image is extracted from the executable file) after the binary image is extracted from the executable file)
@ -364,13 +395,15 @@ class Target(namedtuple("Target", "name json_data resolution_order resolution_or
Positional Arguments: Positional Arguments:
hook - the hook object to add post-binary-hooks to hook - the hook object to add post-binary-hooks to
toolchain - the toolchain object for inspection toolchain - the toolchain object for inspection
Return Value:
A callable if any post-build hook is applicable or None
""" """
# If there's no hook, simply return
try: try:
hook_data = self.post_binary_hook hook_data = self.post_binary_hook
except AttributeError: except AttributeError:
return return None
# A hook was found. The hook's name is in the format # A hook was found. The hook's name is in the format
# "classname.functionname" # "classname.functionname"
temp = hook_data["function"].split(".") temp = hook_data["function"].split(".")
@ -383,8 +416,7 @@ class Target(namedtuple("Target", "name json_data resolution_order resolution_or
# "class_name" must refer to a class in this file, so check if the # "class_name" must refer to a class in this file, so check if the
# class exists # class exists
mdata = self.get_module_data() mdata = self.get_module_data()
if class_name not in mdata or \ if not inspect.isclass(mdata.get(class_name, None)):
not inspect.isclass(mdata[class_name]):
raise HookError( raise HookError(
("Class '%s' required by '%s' in target '%s'" ("Class '%s' required by '%s' in target '%s'"
% (class_name, hook_data["function"], self.name)) + % (class_name, hook_data["function"], self.name)) +
@ -392,26 +424,25 @@ class Target(namedtuple("Target", "name json_data resolution_order resolution_or
# "function_name" must refer to a static function inside class # "function_name" must refer to a static function inside class
# "class_name" # "class_name"
cls = mdata[class_name] cls = mdata[class_name]
if (not hasattr(cls, function_name)) or \ if not inspect.isfunction(getattr(cls, function_name, None)):
(not inspect.isfunction(getattr(cls, function_name))):
raise HookError( raise HookError(
("Static function '%s' " % function_name) + ("Static function '%s' " % function_name) +
("required by '%s' " % hook_data["function"]) + ("required by '%s' " % hook_data["function"]) +
("in target '%s' " % self.name) + ("in target '%s' " % self.name) +
("not found in class '%s'" % class_name)) ("not found in class '%s'" % class_name))
# Check if the hook specification also has toolchain restrictions # Check if the hook specification also has toolchain restrictions
toolchain_restrictions = set(hook_data.get("toolchains", [])) toolchain_restrictions = set(hook_data.get("toolchains", []))
toolchain_labels = set(c.__name__ for c in getmro(toolchain.__class__))
if toolchain_restrictions and \ if toolchain_restrictions and \
not toolchain_labels.intersection(toolchain_restrictions): not set(toolchain_labels).intersection(toolchain_restrictions):
return return None
return getattr(cls, function_name) return getattr(cls, function_name)
################################################################################ ###############################################################################
# Target specific code goes in this section # Target specific code goes in this section
# This code can be invoked from the target description using the # This code can be invoked from the target description using the
# "post_binary_hook" key # "post_binary_hook" key
class LPCTargetCode(object): class LPCTargetCode(object):
"""General LPC Target patching code""" """General LPC Target patching code"""
@staticmethod @staticmethod
@ -420,6 +451,7 @@ class LPCTargetCode(object):
t_self.notify.debug("LPC Patch: %s" % os.path.split(binf)[1]) t_self.notify.debug("LPC Patch: %s" % os.path.split(binf)[1])
patch(binf) patch(binf)
class LPC4088Code(object): class LPC4088Code(object):
"""Code specific to the LPC4088""" """Code specific to the LPC4088"""
@staticmethod @staticmethod
@ -451,18 +483,22 @@ class LPC4088Code(object):
# file to 'binf' # file to 'binf'
shutil.rmtree(binf, True) shutil.rmtree(binf, True)
os.rename(binf + '.temp', binf) os.rename(binf + '.temp', binf)
t_self.notify.debug("Generated custom binary file (internal flash + SPIFI)") t_self.notify.debug(
"Generated custom binary file (internal flash + SPIFI)"
)
LPCTargetCode.lpc_patch(t_self, resources, elf, binf) LPCTargetCode.lpc_patch(t_self, resources, elf, binf)
class TEENSY3_1Code(object): class TEENSY3_1Code(object):
"""Hooks for the TEENSY3.1""" """Hooks for the TEENSY3.1"""
@staticmethod @staticmethod
def binary_hook(t_self, resources, elf, binf): def binary_hook(t_self, resources, elf, binf):
"""Hook that is run after elf is generated""" """Hook that is run after elf is generated"""
# This function is referenced by old versions of targets.json and should # This function is referenced by old versions of targets.json and
# be kept for backwards compatibility. # should be kept for backwards compatibility.
pass pass
class MTSCode(object): class MTSCode(object):
"""Generic MTS code""" """Generic MTS code"""
@staticmethod @staticmethod
@ -507,6 +543,7 @@ class MTSCode(object):
"""A hook for the MTB MTS Dragonfly""" """A hook for the MTB MTS Dragonfly"""
MTSCode._combine_bins_helper("MTB_MTS_DRAGONFLY", binf) MTSCode._combine_bins_helper("MTB_MTS_DRAGONFLY", binf)
class MCU_NRF51Code(object): class MCU_NRF51Code(object):
"""NRF51 Hooks""" """NRF51 Hooks"""
@staticmethod @staticmethod
@ -514,8 +551,8 @@ class MCU_NRF51Code(object):
"""Hook that merges the soft device with the bin file""" """Hook that merges the soft device with the bin file"""
# Scan to find the actual paths of soft device # Scan to find the actual paths of soft device
sdf = None sdf = None
for softdevice_and_offset_entry\ sd_with_offsets = t_self.target.EXPECTED_SOFTDEVICES_WITH_OFFSETS
in t_self.target.EXPECTED_SOFTDEVICES_WITH_OFFSETS: for softdevice_and_offset_entry in sd_with_offsets:
for hexf in resources.get_file_paths(FileType.HEX): for hexf in resources.get_file_paths(FileType.HEX):
if hexf.find(softdevice_and_offset_entry['name']) != -1: if hexf.find(softdevice_and_offset_entry['name']) != -1:
t_self.notify.debug("SoftDevice file found %s." t_self.notify.debug("SoftDevice file found %s."
@ -537,8 +574,10 @@ class MCU_NRF51Code(object):
if t_self.target.MERGE_BOOTLOADER is True: if t_self.target.MERGE_BOOTLOADER is True:
for hexf in resources.get_file_paths(FileType.HEX): for hexf in resources.get_file_paths(FileType.HEX):
if hexf.find(t_self.target.OVERRIDE_BOOTLOADER_FILENAME) != -1: if hexf.find(t_self.target.OVERRIDE_BOOTLOADER_FILENAME) != -1:
t_self.notify.debug("Bootloader file found %s." t_self.notify.debug(
% t_self.target.OVERRIDE_BOOTLOADER_FILENAME) "Bootloader file found %s."
% t_self.target.OVERRIDE_BOOTLOADER_FILENAME
)
blf = hexf blf = hexf
break break
elif hexf.find(softdevice_and_offset_entry['boot']) != -1: elif hexf.find(softdevice_and_offset_entry['boot']) != -1:
@ -572,6 +611,7 @@ class MCU_NRF51Code(object):
with open(binf.replace(".bin", ".hex"), "w") as fileout: with open(binf.replace(".bin", ".hex"), "w") as fileout:
binh.write_hex_file(fileout, write_start_addr=False) binh.write_hex_file(fileout, write_start_addr=False)
class NCS36510TargetCode: class NCS36510TargetCode:
@staticmethod @staticmethod
def ncs36510_addfib(t_self, resources, elf, binf): def ncs36510_addfib(t_self, resources, elf, binf):
@ -579,6 +619,7 @@ class NCS36510TargetCode:
print("binf ", binf) print("binf ", binf)
add_fib_at_start(binf[:-4]) add_fib_at_start(binf[:-4])
class RTL8195ACode: class RTL8195ACode:
"""RTL8195A Hooks""" """RTL8195A Hooks"""
@staticmethod @staticmethod
@ -586,6 +627,7 @@ class RTL8195ACode:
from tools.targets.REALTEK_RTL8195AM import rtl8195a_elf2bin from tools.targets.REALTEK_RTL8195AM import rtl8195a_elf2bin
rtl8195a_elf2bin(t_self, elf, binf) rtl8195a_elf2bin(t_self, elf, binf)
class PSOC6Code: class PSOC6Code:
@staticmethod @staticmethod
def complete(t_self, resources, elf, binf): def complete(t_self, resources, elf, binf):
@ -599,19 +641,27 @@ class PSOC6Code:
else: else:
psoc6_complete(t_self, elf, binf) psoc6_complete(t_self, elf, binf)
class LPC55S69Code: class LPC55S69Code:
"""LPC55S69 Hooks""" """LPC55S69 Hooks"""
@staticmethod @staticmethod
def binary_hook(t_self, resources, elf, binf): def binary_hook(t_self, resources, elf, binf):
from tools.targets.LPC55S69 import lpc55s69_complete from tools.targets.LPC55S69 import lpc55s69_complete
configured_secure_image_filename = t_self.target.secure_image_filename configured_secure_image_filename = t_self.target.secure_image_filename
secure_bin = find_secure_image(t_self.notify, resources, binf, configured_secure_image_filename, FileType.BIN) secure_bin = find_secure_image(
t_self.notify,
resources,
binf,
configured_secure_image_filename,
FileType.BIN
)
lpc55s69_complete(t_self, binf, secure_bin) lpc55s69_complete(t_self, binf, secure_bin)
################################################################################
# Instantiate all public targets # End Target specific section
###############################################################################
def update_target_data(): def update_target_data():
"""Instantiate all public targets"""
TARGETS[:] = [Target.get_target(tgt) for tgt, 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)]
@ -620,6 +670,7 @@ def update_target_data():
TARGET_MAP.update(dict([(tgt.name, tgt) for tgt in TARGETS])) TARGET_MAP.update(dict([(tgt.name, tgt) for tgt in TARGETS]))
TARGET_NAMES[:] = TARGET_MAP.keys() TARGET_NAMES[:] = TARGET_MAP.keys()
TARGETS = [] TARGETS = []
TARGET_MAP = dict() TARGET_MAP = dict()
TARGET_NAMES = [] TARGET_NAMES = []
@ -629,6 +680,7 @@ update_target_data()
# Some targets with different name have the same exporters # Some targets with different name have the same exporters
EXPORT_MAP = {} EXPORT_MAP = {}
# Detection APIs # Detection APIs
def get_target_detect_codes(): def get_target_detect_codes():
""" Returns dictionary mapping detect_code -> platform_name """ Returns dictionary mapping detect_code -> platform_name
@ -639,6 +691,7 @@ def get_target_detect_codes():
result[detect_code] = tgt.name result[detect_code] = tgt.name
return result return result
def set_targets_json_location(location=None): def set_targets_json_location(location=None):
"""Sets the location of the JSON file that contains the targets""" """Sets the location of the JSON file that contains the targets"""
# First instruct Target about the new location # First instruct Target about the new location
@ -648,4 +701,3 @@ def set_targets_json_location(location=None):
# 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"
update_target_data() update_target_data()

View File

@ -136,8 +136,6 @@ class mbedToolchain:
self.target = target self.target = target
self.name = self.__class__.__name__ self.name = self.__class__.__name__
# compile/assemble/link/binary hooks
self._post_build_hook = target.get_post_build_hook(self.name)
# Toolchain flags # Toolchain flags
self.flags = deepcopy(build_profile or self.profile_template) self.flags = deepcopy(build_profile or self.profile_template)
@ -754,9 +752,13 @@ class mbedToolchain:
else: else:
updatable = None updatable = None
if self._post_build_hook: # compile/assemble/link/binary hooks
post_build_hook = self.target.get_post_build_hook(
self._get_toolchain_labels()
)
if post_build_hook:
self.progress("post-build", name) self.progress("post-build", name)
self._post_build_hook(self, r, elf, full_path) post_build_hook(self, r, elf, full_path)
# Initialize memap and process map file. This doesn't generate output. # Initialize memap and process map file. This doesn't generate output.
self.mem_stats(mapfile) self.mem_stats(mapfile)