Clone of official tools
targets/lint.py
- Committer:
- Anders Blomdell
- Date:
- 2021-02-04
- Revision:
- 47:21ae3e5a7128
- Parent:
- 43:2a7da56ebd24
File content as of revision 47:21ae3e5a7128:
"""A linting utility for targets.json This linting utility may be called as follows: python <path-to>/lint.py targets TARGET [TARGET ...] all targets will be linted """ # mbed SDK # Copyright (c) 2017 ARM Limited # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from os.path import join, abspath, dirname if __name__ == "__main__": import sys ROOT = abspath(join(dirname(__file__), "..", "..")) sys.path.insert(0, ROOT) from copy import copy from yaml import dump_all import argparse from tools.targets import Target, set_targets_json_location, TARGET_MAP def must_have_keys(keys, dict): """Require keys in an MCU/Board is a generator for errors """ for key in keys: if key not in dict: yield "%s not found, and is required" % key def may_have_keys(keys, dict): """Disable all other keys in an MCU/Board is a generator for errors """ for key in dict.keys(): if key not in keys: yield "%s found, and is not allowed" % key def check_extra_labels(dict): """Check that extra_labels does not contain any Target names is a generator for errors """ for label in (dict.get("extra_labels", []) + dict.get("extra_labels_add", [])): if label in Target.get_json_target_data(): yield "%s is not allowed in extra_labels" % label def check_release_version(dict): """Verify that release version 5 is combined with support for all toolcahins is a generator for errors """ if ("release_versions" in dict and "5" in dict["release_versions"] and "supported_toolchains" in dict): for toolc in ["GCC_ARM", "ARM", "IAR"]: if toolc not in dict["supported_toolchains"]: yield ("%s not found in supported_toolchains, and is " "required by mbed OS 5" % toolc) def check_inherits(dict): if ("inherits" in dict and len(dict["inherits"]) > 1): yield "multiple inheritance is forbidden" DEVICE_HAS_ALLOWED = ["ANALOGIN", "ANALOGOUT", "CAN", "ETHERNET", "EMAC", "FLASH", "I2C", "I2CSLAVE", "I2C_ASYNCH", "INTERRUPTIN", "LPTICKER", "PORTIN", "PORTINOUT", "PORTOUT", "PWMOUT", "RTC", "TRNG","SERIAL", "SERIAL_ASYNCH", "SERIAL_FC", "SLEEP", "SPI", "SPI_ASYNCH", "SPISLAVE", "STORAGE", "STCLK_OFF_DURING_SLEEP"] def check_device_has(dict): for name in dict.get("device_has", []): if name not in DEVICE_HAS_ALLOWED: yield "%s is not allowed in device_has" % name MCU_REQUIRED_KEYS = ["release_versions", "supported_toolchains", "default_lib", "public", "inherits", "device_has"] MCU_ALLOWED_KEYS = ["device_has_add", "device_has_remove", "core", "extra_labels", "features", "features_add", "features_remove", "bootloader_supported", "device_name", "post_binary_hook", "default_toolchain", "config", "extra_labels_add", "extra_labels_remove", "target_overrides"] + MCU_REQUIRED_KEYS def check_mcu(mcu_json, strict=False): """Generate a list of problems with an MCU :param: mcu_json the MCU's dict to check :param: strict enforce required keys """ errors = list(may_have_keys(MCU_ALLOWED_KEYS, mcu_json)) if strict: errors.extend(must_have_keys(MCU_REQUIRED_KEYS, mcu_json)) errors.extend(check_extra_labels(mcu_json)) errors.extend(check_release_version(mcu_json)) errors.extend(check_inherits(mcu_json)) errors.extend(check_device_has(mcu_json)) if 'public' in mcu_json and mcu_json['public']: errors.append("public must be false") return errors BOARD_REQUIRED_KEYS = ["inherits"] BOARD_ALLOWED_KEYS = ["supported_form_factors", "is_disk_virtual", "detect_code", "extra_labels", "extra_labels_add", "extra_labels_remove", "public", "config", "forced_reset_timeout", "target_overrides"] + BOARD_REQUIRED_KEYS def check_board(board_json, strict=False): """Generate a list of problems with an board :param: board_json the mcus dict to check :param: strict enforce required keys """ errors = list(may_have_keys(BOARD_ALLOWED_KEYS, board_json)) if strict: errors.extend(must_have_keys(BOARD_REQUIRED_KEYS, board_json)) errors.extend(check_extra_labels(board_json)) errors.extend(check_inherits(board_json)) return errors def add_if(dict, key, val): """Add a value to a dict if it's non-empty""" if val: dict[key] = val def _split_boards(resolution_order, tgt): """Split the resolution order between boards and mcus""" mcus = [] boards = [] iterable = iter(resolution_order) for name in iterable: mcu_json = tgt.json_data[name] if (len(list(check_mcu(mcu_json, True))) > len(list(check_board(mcu_json, True)))): boards.append(name) else: mcus.append(name) break mcus.extend(iterable) mcus.reverse() boards.reverse() return mcus, boards MCU_FORMAT_STRING = {1: "MCU (%s) ->", 2: "Family (%s) -> MCU (%s) ->", 3: "Family (%s) -> SubFamily (%s) -> MCU (%s) ->"} BOARD_FORMAT_STRING = {1: "Board (%s)", 2: "Module (%s) -> Board (%s)"} def _generate_hierarchy_string(mcus, boards): global_errors = [] if len(mcus) < 1: global_errors.append("No MCUS found in hierarchy") mcus_string = "??? ->" elif len(mcus) > 3: global_errors.append("No name for targets %s" % ", ".join(mcus[3:])) mcus_string = MCU_FORMAT_STRING[3] % tuple(mcus[:3]) for name in mcus[3:]: mcus_string += " ??? (%s) ->" % name else: mcus_string = MCU_FORMAT_STRING[len(mcus)] % tuple(mcus) if len(boards) < 1: global_errors.append("no boards found in hierarchy") boards_string = "???" elif len(boards) > 2: global_errors.append("no name for targets %s" % ", ".join(boards[2:])) boards_string = BOARD_FORMAT_STRING[2] % tuple(boards[:2]) for name in boards[2:]: boards_string += " -> ??? (%s)" % name else: boards_string = BOARD_FORMAT_STRING[len(boards)] % tuple(boards) return mcus_string + " " + boards_string, global_errors def check_hierarchy(tgt): """Atempts to assign labels to the hierarchy""" resolution_order = copy(tgt.resolution_order_names[:-1]) mcus, boards = _split_boards(resolution_order, tgt) target_errors = {} hierachy_string, hierachy_errors = _generate_hierarchy_string(mcus, boards) to_ret = {"hierarchy": hierachy_string} add_if(to_ret, "hierarchy errors", hierachy_errors) for name in mcus[:-1]: add_if(target_errors, name, list(check_mcu(tgt.json_data[name]))) if len(mcus) >= 1: add_if(target_errors, mcus[-1], list(check_mcu(tgt.json_data[mcus[-1]], True))) for name in boards: add_if(target_errors, name, list(check_board(tgt.json_data[name]))) if len(boards) >= 1: add_if(target_errors, boards[-1], list(check_board(tgt.json_data[boards[-1]], True))) add_if(to_ret, "target errors", target_errors) return to_ret PARSER = argparse.ArgumentParser(prog="targets/lint.py") SUBPARSERS = PARSER.add_subparsers(title="Commands") def subcommand(name, *args, **kwargs): def __subcommand(command): kwargs['description'] = command.__doc__ subparser = SUBPARSERS.add_parser(name, **kwargs) for arg in args: arg = dict(arg) opt = arg['name'] del arg['name'] if isinstance(opt, basestring): subparser.add_argument(opt, **arg) else: subparser.add_argument(*opt, **arg) def _thunk(parsed_args): argv = [arg['dest'] if 'dest' in arg else arg['name'] for arg in args] argv = [(arg if isinstance(arg, basestring) else arg[-1]).strip('-').replace('-', '_') for arg in argv] argv = {arg: vars(parsed_args)[arg] for arg in argv if vars(parsed_args)[arg] is not None} return command(**argv) subparser.set_defaults(command=_thunk) return command return __subcommand @subcommand("targets", dict(name="mcus", nargs="+", metavar="MCU", choices=TARGET_MAP.keys(), type=str.upper)) def targets_cmd(mcus=[]): """Find and print errors about specific targets""" print dump_all([check_hierarchy(TARGET_MAP[m]) for m in mcus], default_flow_style=False) @subcommand("all-targets") def all_targets_cmd(): """Print all errors about all parts""" print dump_all([check_hierarchy(m) for m in TARGET_MAP.values()], default_flow_style=False) @subcommand("orphans") def orphans_cmd(): """Find and print all orphan targets""" orphans = Target.get_json_target_data().keys() for tgt in TARGET_MAP.values(): for name in tgt.resolution_order_names: if name in orphans: orphans.remove(name) if orphans: print dump_all([orphans], default_flow_style=False) return len(orphans) def main(): """entry point""" options = PARSER.parse_args() return options.command(options) if __name__ == "__main__": sys.exit(main())