Skip to content

Commit 37a3e20

Browse files
committed
Revamp option parsing and add more checks
1 parent 32966bb commit 37a3e20

File tree

1 file changed

+121
-37
lines changed

1 file changed

+121
-37
lines changed

tools/targets/lint.py

Lines changed: 121 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
1-
"""A linting utility for targets.json"""
1+
"""A linting utility for targets.json
2+
3+
This linting utility may be called as follows:
4+
python <path-to>/lint.py TARGET [TARGET ...]
5+
6+
all targets will be linted
7+
"""
28

39
from os.path import join, abspath, dirname
410
if __name__ == "__main__":
511
import sys
612
ROOT = abspath(join(dirname(__file__), "..", ".."))
713
sys.path.insert(0, ROOT)
814
from copy import copy
9-
from yaml import dump
15+
from yaml import dump_all
16+
import argparse
1017
from tools.targets import Target, set_targets_json_location, TARGET_MAP
1118

1219
def must_have_keys(keys, dict):
@@ -27,49 +34,74 @@ def may_have_keys(keys, dict):
2734
if key not in keys:
2835
yield "%s found, and is not allowed" % key
2936

37+
def check_extra_labels(dict):
38+
"""Check that extra_labels does not contain any Target names
39+
40+
is a generator for errors
41+
"""
42+
for label in (dict.get("extra_labels", []) +
43+
dict.get("extra_labels_add", [])):
44+
if label in Target.get_json_target_data():
45+
yield "%s is not allowed in extra_labels" % label
46+
47+
def check_release_version(dict):
48+
"""Verify that release version 5 is combined with support for all toolcahins
49+
50+
is a generator for errors
51+
"""
52+
if ("release_versions" in dict and
53+
"5" in dict["release_versions"] and
54+
"supported_toolchains" in dict):
55+
for toolc in ["GCC_ARM", "ARM", "IAR"]:
56+
if toolc not in dict["supported_toolchains"]:
57+
yield ("%s not found in supported_toolchains, and is "
58+
"required by mbed OS 5" % toolc)
59+
60+
def check_inherits(dict):
61+
if ("inherits" in dict and len(dict["inherits"]) > 1):
62+
yield "multiple inheritance is forbidden"
3063

3164
MCU_REQUIRED_KEYS = ["release_versions", "supported_toolchains",
32-
"default_lib", "public", "inherits"]
33-
MCU_ALLOWED_KEYS = ["device_has", "core", "extra_labels", "features",
34-
"bootloader_supported", "device_name", "post_binary_hook",
35-
"default_toolchain"] + MCU_REQUIRED_KEYS
65+
"default_lib", "public", "inherits", "device_has"]
66+
MCU_ALLOWED_KEYS = ["device_has", "device_has_add", "device_has_remove", "core",
67+
"extra_labels", "features", "features_add",
68+
"features_remove", "bootloader_supported", "device_name",
69+
"post_binary_hook", "default_toolchain", "config",
70+
"extra_labels_add", "extra_labels_remove",
71+
"target_overrides"] + MCU_REQUIRED_KEYS
3672
def check_mcu(mcu_json, strict=False):
3773
"""Generate a list of problems with an MCU
3874
3975
:param: mcu_json the MCU's dict to check
4076
:param: strict enforce required keys
4177
"""
78+
errors = list(may_have_keys(MCU_ALLOWED_KEYS, mcu_json))
4279
if strict:
43-
for err in must_have_keys(MCU_REQUIRED_KEYS, mcu_json):
44-
yield err
45-
for err in may_have_keys(MCU_ALLOWED_KEYS, mcu_json):
46-
yield err
80+
errors.extend(must_have_keys(MCU_REQUIRED_KEYS, mcu_json))
81+
errors.extend(check_extra_labels(mcu_json))
82+
errors.extend(check_release_version(mcu_json))
83+
errors.extend(check_inherits(mcu_json))
4784
if 'public' in mcu_json and mcu_json['public']:
48-
yield "public must be false"
49-
if ("release_versions" in mcu_json and
50-
"5" in mcu_json["release_versions"] and
51-
"supported_toolchains" in mcu_json):
52-
for toolc in ["GCC_ARM", "ARM", "IAR"]:
53-
if toolc not in mcu_json["supported_toolchains"]:
54-
yield ("%s not found in supported_toolchains, and is "
55-
"required by mbed OS 5" % toolc)
85+
errors.append("public must be false")
86+
return errors
5687

5788
BOARD_REQUIRED_KEYS = ["inherits"]
5889
BOARD_ALLOWED_KEYS = ["supported_form_factors", "is_disk_virtual",
59-
"detect_code", "device_name", "extra_labels",
60-
"public"] + BOARD_REQUIRED_KEYS
90+
"detect_code", "extra_labels", "extra_labels_add",
91+
"extra_labels_remove", "public", "config",
92+
"forced_reset_timeout", "target_overrides"] + BOARD_REQUIRED_KEYS
6193
def check_board(board_json, strict=False):
6294
"""Generate a list of problems with an board
6395
6496
:param: board_json the mcus dict to check
6597
:param: strict enforce required keys
6698
"""
99+
errors = list(may_have_keys(BOARD_ALLOWED_KEYS, board_json))
67100
if strict:
68-
for err in must_have_keys(BOARD_REQUIRED_KEYS, board_json):
69-
yield err
70-
for err in may_have_keys(BOARD_ALLOWED_KEYS, board_json):
71-
yield err
72-
101+
errors.extend(must_have_keys(BOARD_REQUIRED_KEYS, board_json))
102+
errors.extend(check_extra_labels(board_json))
103+
errors.extend(check_inherits(board_json))
104+
return errors
73105

74106
def add_if(dict, key, val):
75107
"""Add a value to a dict if it's non-empty"""
@@ -106,7 +138,7 @@ def _generate_hierarchy_string(mcus, boards):
106138
global_errors.append("No MCUS found in heirarchy")
107139
mcus_string = "??? ->"
108140
elif len(mcus) > 3:
109-
global_errors.append("No name for targets: %s" % mcus[3:])
141+
global_errors.append("No name for targets %s" % ", ".join(mcus[3:]))
110142
mcus_string = MCU_FORMAT_STRING[3] % tuple(mcus[:3])
111143
for name in mcus[3:]:
112144
mcus_string += " ??? (%s) ->" % name
@@ -117,10 +149,10 @@ def _generate_hierarchy_string(mcus, boards):
117149
global_errors.append("no boards found in heirarchy")
118150
boards_string = "???"
119151
elif len(boards) > 2:
120-
global_errors.append("no name for targets: %s" % boards[2:])
121-
boards_string = BOARD_FORMAT_STRING[3] % tuple(boards[:2])
152+
global_errors.append("no name for targets %s" % ", ".join(boards[2:]))
153+
boards_string = BOARD_FORMAT_STRING[2] % tuple(boards[:2])
122154
for name in boards[2:]:
123-
boards_string += " ??? (%s)" % name
155+
boards_string += " -> ??? (%s)" % name
124156
else:
125157
boards_string = BOARD_FORMAT_STRING[len(boards)] % tuple(boards)
126158
return mcus_string + " " + boards_string, global_errors
@@ -134,7 +166,7 @@ def check_hierarchy(tgt):
134166
target_errors = {}
135167
hierachy_string, hierachy_errors = _generate_hierarchy_string(mcus, boards)
136168
to_ret = {"hierarchy": hierachy_string}
137-
add_if(to_ret, "hierachy errors", hierachy_errors)
169+
add_if(to_ret, "hierarchy errors", hierachy_errors)
138170

139171
for name in mcus[:-1]:
140172
add_if(target_errors, name, list(check_mcu(tgt.json_data[name])))
@@ -149,16 +181,68 @@ def check_hierarchy(tgt):
149181
add_if(to_ret, "target errors", target_errors)
150182
return to_ret
151183

184+
PARSER = argparse.ArgumentParser(prog="targets/lint.py")
185+
SUBPARSERS = PARSER.add_subparsers(title="Commands")
186+
187+
def subcommand(name, *args, **kwargs):
188+
def __subcommand(command):
189+
kwargs['description'] = command.__doc__
190+
subparser = SUBPARSERS.add_parser(name, **kwargs)
191+
for arg in args:
192+
arg = dict(arg)
193+
opt = arg['name']
194+
del arg['name']
195+
196+
if isinstance(opt, basestring):
197+
subparser.add_argument(opt, **arg)
198+
else:
199+
subparser.add_argument(*opt, **arg)
200+
201+
def _thunk(parsed_args):
202+
argv = [arg['dest'] if 'dest' in arg else arg['name']
203+
for arg in args]
204+
argv = [(arg if isinstance(arg, basestring)
205+
else arg[-1]).strip('-').replace('-', '_')
206+
for arg in argv]
207+
argv = {arg: vars(parsed_args)[arg] for arg in argv
208+
if vars(parsed_args)[arg] is not None}
209+
210+
return command(**argv)
211+
212+
subparser.set_defaults(command=_thunk)
213+
return command
214+
return __subcommand
215+
216+
@subcommand("targets",
217+
dict(name="mcus", nargs="+", metavar="MCU",
218+
choices=TARGET_MAP.keys(), type=str.upper))
219+
def targets_cmd(mcus=[]):
220+
"""Find and print errors about specific targets"""
221+
print dump_all([check_hierarchy(TARGET_MAP[m]) for m in mcus],
222+
default_flow_style=False)
223+
224+
@subcommand("all-targets")
225+
def all_targets_cmd():
226+
"""Print all errors about all parts"""
227+
print dump_all([check_hierarchy(m) for m in TARGET_MAP.values()],
228+
default_flow_style=False)
229+
230+
@subcommand("orphans")
231+
def orphans_cmd():
232+
"""Find and print all orphan targets"""
233+
orphans = Target.get_json_target_data().keys()
234+
for tgt in TARGET_MAP.values():
235+
for name in tgt.resolution_order_names:
236+
if name in orphans:
237+
orphans.remove(name)
238+
if orphans:
239+
print dump_all([orphans], default_flow_style=False)
240+
return len(orphans)
152241

153242
def main():
154243
"""entry point"""
155-
import argparse
156-
parser = argparse.ArgumentParser()
157-
parser.add_argument("mcu", choices=TARGET_MAP.keys(), metavar="MCU", )
158-
options = parser.parse_args()
159-
print dump(check_hierarchy(TARGET_MAP[options.mcu]),
160-
default_flow_style=False)
161-
return 0
244+
options = PARSER.parse_args()
245+
return options.command(options)
162246

163247
if __name__ == "__main__":
164248
sys.exit(main())

0 commit comments

Comments
 (0)