Skip to content

Commit d2cb0c6

Browse files
authored
Merge pull request #4215 from theotherjimmy/lint-targets-json
Add script to lint targets.json
2 parents e0f56d1 + 18bca08 commit d2cb0c6

File tree

1 file changed

+277
-0
lines changed

1 file changed

+277
-0
lines changed

tools/targets/lint.py

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
"""A linting utility for targets.json
2+
3+
This linting utility may be called as follows:
4+
python <path-to>/lint.py targets TARGET [TARGET ...]
5+
6+
all targets will be linted
7+
"""
8+
9+
# mbed SDK
10+
# Copyright (c) 2017 ARM Limited
11+
#
12+
# Licensed under the Apache License, Version 2.0 (the "License");
13+
# you may not use this file except in compliance with the License.
14+
# You may obtain a copy of the License at
15+
#
16+
# http://www.apache.org/licenses/LICENSE-2.0
17+
#
18+
# Unless required by applicable law or agreed to in writing, software
19+
# distributed under the License is distributed on an "AS IS" BASIS,
20+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21+
# See the License for the specific language governing permissions and
22+
# limitations under the License.
23+
24+
from os.path import join, abspath, dirname
25+
if __name__ == "__main__":
26+
import sys
27+
ROOT = abspath(join(dirname(__file__), "..", ".."))
28+
sys.path.insert(0, ROOT)
29+
from copy import copy
30+
from yaml import dump_all
31+
import argparse
32+
33+
from tools.targets import Target, set_targets_json_location, TARGET_MAP
34+
35+
def must_have_keys(keys, dict):
36+
"""Require keys in an MCU/Board
37+
38+
is a generator for errors
39+
"""
40+
for key in keys:
41+
if key not in dict:
42+
yield "%s not found, and is required" % key
43+
44+
def may_have_keys(keys, dict):
45+
"""Disable all other keys in an MCU/Board
46+
47+
is a generator for errors
48+
"""
49+
for key in dict.keys():
50+
if key not in keys:
51+
yield "%s found, and is not allowed" % key
52+
53+
def check_extra_labels(dict):
54+
"""Check that extra_labels does not contain any Target names
55+
56+
is a generator for errors
57+
"""
58+
for label in (dict.get("extra_labels", []) +
59+
dict.get("extra_labels_add", [])):
60+
if label in Target.get_json_target_data():
61+
yield "%s is not allowed in extra_labels" % label
62+
63+
def check_release_version(dict):
64+
"""Verify that release version 5 is combined with support for all toolcahins
65+
66+
is a generator for errors
67+
"""
68+
if ("release_versions" in dict and
69+
"5" in dict["release_versions"] and
70+
"supported_toolchains" in dict):
71+
for toolc in ["GCC_ARM", "ARM", "IAR"]:
72+
if toolc not in dict["supported_toolchains"]:
73+
yield ("%s not found in supported_toolchains, and is "
74+
"required by mbed OS 5" % toolc)
75+
76+
def check_inherits(dict):
77+
if ("inherits" in dict and len(dict["inherits"]) > 1):
78+
yield "multiple inheritance is forbidden"
79+
80+
DEVICE_HAS_ALLOWED = ["ANALOGIN", "ANALOGOUT", "CAN", "ETHERNET", "EMAC",
81+
"FLASH", "I2C", "I2CSLAVE", "I2C_ASYNCH", "INTERRUPTIN",
82+
"LOWPOWERTIMER", "PORTIN", "PORTINOUT", "PORTOUT",
83+
"PWMOUT", "RTC", "TRNG","SERIAL", "SERIAL_ASYNCH",
84+
"SERIAL_FC", "SLEEP", "SPI", "SPI_ASYNCH", "SPISLAVE",
85+
"STORAGE"]
86+
def check_device_has(dict):
87+
for name in dict.get("device_has", []):
88+
if name not in DEVICE_HAS_ALLOWED:
89+
yield "%s is not allowed in device_has" % name
90+
91+
MCU_REQUIRED_KEYS = ["release_versions", "supported_toolchains",
92+
"default_lib", "public", "inherits", "device_has"]
93+
MCU_ALLOWED_KEYS = ["device_has_add", "device_has_remove", "core",
94+
"extra_labels", "features", "features_add",
95+
"features_remove", "bootloader_supported", "device_name",
96+
"post_binary_hook", "default_toolchain", "config",
97+
"extra_labels_add", "extra_labels_remove",
98+
"target_overrides"] + MCU_REQUIRED_KEYS
99+
def check_mcu(mcu_json, strict=False):
100+
"""Generate a list of problems with an MCU
101+
102+
:param: mcu_json the MCU's dict to check
103+
:param: strict enforce required keys
104+
"""
105+
errors = list(may_have_keys(MCU_ALLOWED_KEYS, mcu_json))
106+
if strict:
107+
errors.extend(must_have_keys(MCU_REQUIRED_KEYS, mcu_json))
108+
errors.extend(check_extra_labels(mcu_json))
109+
errors.extend(check_release_version(mcu_json))
110+
errors.extend(check_inherits(mcu_json))
111+
errors.extend(check_device_has(mcu_json))
112+
if 'public' in mcu_json and mcu_json['public']:
113+
errors.append("public must be false")
114+
return errors
115+
116+
BOARD_REQUIRED_KEYS = ["inherits"]
117+
BOARD_ALLOWED_KEYS = ["supported_form_factors", "is_disk_virtual",
118+
"detect_code", "extra_labels", "extra_labels_add",
119+
"extra_labels_remove", "public", "config",
120+
"forced_reset_timeout", "target_overrides"] + BOARD_REQUIRED_KEYS
121+
def check_board(board_json, strict=False):
122+
"""Generate a list of problems with an board
123+
124+
:param: board_json the mcus dict to check
125+
:param: strict enforce required keys
126+
"""
127+
errors = list(may_have_keys(BOARD_ALLOWED_KEYS, board_json))
128+
if strict:
129+
errors.extend(must_have_keys(BOARD_REQUIRED_KEYS, board_json))
130+
errors.extend(check_extra_labels(board_json))
131+
errors.extend(check_inherits(board_json))
132+
return errors
133+
134+
def add_if(dict, key, val):
135+
"""Add a value to a dict if it's non-empty"""
136+
if val:
137+
dict[key] = val
138+
139+
def _split_boards(resolution_order, tgt):
140+
"""Split the resolution order between boards and mcus"""
141+
mcus = []
142+
boards = []
143+
iterable = iter(resolution_order)
144+
for name in iterable:
145+
mcu_json = tgt.json_data[name]
146+
if (len(list(check_mcu(mcu_json, True))) >
147+
len(list(check_board(mcu_json, True)))):
148+
boards.append(name)
149+
else:
150+
mcus.append(name)
151+
break
152+
mcus.extend(iterable)
153+
mcus.reverse()
154+
boards.reverse()
155+
return mcus, boards
156+
157+
158+
MCU_FORMAT_STRING = {1: "MCU (%s) ->",
159+
2: "Family (%s) -> MCU (%s) ->",
160+
3: "Family (%s) -> SubFamily (%s) -> MCU (%s) ->"}
161+
BOARD_FORMAT_STRING = {1: "Board (%s)",
162+
2: "Module (%s) -> Board (%s)"}
163+
def _generate_hierarchy_string(mcus, boards):
164+
global_errors = []
165+
if len(mcus) < 1:
166+
global_errors.append("No MCUS found in heirarchy")
167+
mcus_string = "??? ->"
168+
elif len(mcus) > 3:
169+
global_errors.append("No name for targets %s" % ", ".join(mcus[3:]))
170+
mcus_string = MCU_FORMAT_STRING[3] % tuple(mcus[:3])
171+
for name in mcus[3:]:
172+
mcus_string += " ??? (%s) ->" % name
173+
else:
174+
mcus_string = MCU_FORMAT_STRING[len(mcus)] % tuple(mcus)
175+
176+
if len(boards) < 1:
177+
global_errors.append("no boards found in heirarchy")
178+
boards_string = "???"
179+
elif len(boards) > 2:
180+
global_errors.append("no name for targets %s" % ", ".join(boards[2:]))
181+
boards_string = BOARD_FORMAT_STRING[2] % tuple(boards[:2])
182+
for name in boards[2:]:
183+
boards_string += " -> ??? (%s)" % name
184+
else:
185+
boards_string = BOARD_FORMAT_STRING[len(boards)] % tuple(boards)
186+
return mcus_string + " " + boards_string, global_errors
187+
188+
189+
def check_hierarchy(tgt):
190+
"""Atempts to assign labels to the heirarchy"""
191+
resolution_order = copy(tgt.resolution_order_names[:-1])
192+
mcus, boards = _split_boards(resolution_order, tgt)
193+
194+
target_errors = {}
195+
hierachy_string, hierachy_errors = _generate_hierarchy_string(mcus, boards)
196+
to_ret = {"hierarchy": hierachy_string}
197+
add_if(to_ret, "hierarchy errors", hierachy_errors)
198+
199+
for name in mcus[:-1]:
200+
add_if(target_errors, name, list(check_mcu(tgt.json_data[name])))
201+
if len(mcus) >= 1:
202+
add_if(target_errors, mcus[-1],
203+
list(check_mcu(tgt.json_data[mcus[-1]], True)))
204+
for name in boards:
205+
add_if(target_errors, name, list(check_board(tgt.json_data[name])))
206+
if len(boards) >= 1:
207+
add_if(target_errors, boards[-1],
208+
list(check_board(tgt.json_data[boards[-1]], True)))
209+
add_if(to_ret, "target errors", target_errors)
210+
return to_ret
211+
212+
PARSER = argparse.ArgumentParser(prog="targets/lint.py")
213+
SUBPARSERS = PARSER.add_subparsers(title="Commands")
214+
215+
def subcommand(name, *args, **kwargs):
216+
def __subcommand(command):
217+
kwargs['description'] = command.__doc__
218+
subparser = SUBPARSERS.add_parser(name, **kwargs)
219+
for arg in args:
220+
arg = dict(arg)
221+
opt = arg['name']
222+
del arg['name']
223+
224+
if isinstance(opt, basestring):
225+
subparser.add_argument(opt, **arg)
226+
else:
227+
subparser.add_argument(*opt, **arg)
228+
229+
def _thunk(parsed_args):
230+
argv = [arg['dest'] if 'dest' in arg else arg['name']
231+
for arg in args]
232+
argv = [(arg if isinstance(arg, basestring)
233+
else arg[-1]).strip('-').replace('-', '_')
234+
for arg in argv]
235+
argv = {arg: vars(parsed_args)[arg] for arg in argv
236+
if vars(parsed_args)[arg] is not None}
237+
238+
return command(**argv)
239+
240+
subparser.set_defaults(command=_thunk)
241+
return command
242+
return __subcommand
243+
244+
@subcommand("targets",
245+
dict(name="mcus", nargs="+", metavar="MCU",
246+
choices=TARGET_MAP.keys(), type=str.upper))
247+
def targets_cmd(mcus=[]):
248+
"""Find and print errors about specific targets"""
249+
print dump_all([check_hierarchy(TARGET_MAP[m]) for m in mcus],
250+
default_flow_style=False)
251+
252+
@subcommand("all-targets")
253+
def all_targets_cmd():
254+
"""Print all errors about all parts"""
255+
print dump_all([check_hierarchy(m) for m in TARGET_MAP.values()],
256+
default_flow_style=False)
257+
258+
@subcommand("orphans")
259+
def orphans_cmd():
260+
"""Find and print all orphan targets"""
261+
orphans = Target.get_json_target_data().keys()
262+
for tgt in TARGET_MAP.values():
263+
for name in tgt.resolution_order_names:
264+
if name in orphans:
265+
orphans.remove(name)
266+
if orphans:
267+
print dump_all([orphans], default_flow_style=False)
268+
return len(orphans)
269+
270+
def main():
271+
"""entry point"""
272+
options = PARSER.parse_args()
273+
return options.command(options)
274+
275+
if __name__ == "__main__":
276+
sys.exit(main())
277+

0 commit comments

Comments
 (0)