Skip to content

Commit 25dd465

Browse files
committed
Tools: consolidate new board checks into test_new_boards.py
some checks went into check_branch_conventions.py instead. Adds a comment so this doesn't happen again
1 parent eb50e28 commit 25dd465

2 files changed

Lines changed: 93 additions & 176 deletions

File tree

Tools/scripts/check_branch_conventions.py

Lines changed: 6 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@
99
- commit messages have a well-formed subsystem prefix before ':'
1010
- commit subject lines are <= 160 characters
1111
- changed markdown files pass markdownlint-cli2
12-
- new hwdef board directories include a README.md, except ODID variants
13-
- new hwdef board READMEs contain at least one local image added in the PR,
14-
except AP_Periph and ODID variants
12+
13+
(new hwdef board README/image requirements are validated by test_new_boards.py)
1514
1615
AP_FLAKE8_CLEAN
1716
'''
@@ -28,7 +27,6 @@
2827
import urllib.error
2928
import urllib.request
3029

31-
import board_list
3230
import build_script_base
3331

3432
DOCS_URL = "https://ardupilot.org/dev/docs/submitting-patches-back-to-master.html"
@@ -58,14 +56,12 @@
5856
class CheckBranchConventions(build_script_base.BuildScriptBase):
5957

6058
DEFAULT_UPSTREAM = "origin/master"
61-
HWDEF_PREFIX = "libraries/AP_HAL_ChibiOS/hwdef/"
6259

6360
def __init__(self, base_branch: str | None = None) -> None:
6461
super().__init__()
6562
self.base_branch = base_branch
6663
repo_root = self.run_git(['rev-parse', '--show-toplevel'], show_output=False).strip()
6764
self.board_types_path = pathlib.Path(repo_root, 'Tools', 'AP_Bootloader', 'board_types.txt')
68-
self._board_list: board_list.BoardList | None = None
6965

7066
def progress_prefix(self) -> str:
7167
return "CBC"
@@ -77,34 +73,6 @@ def run_git(self, args, show_output=True, source_dir=None):
7773
show_output=show_output, show_command=False, cwd=source_dir,
7874
)
7975

80-
def board_list(self) -> board_list.BoardList:
81-
if self._board_list is None:
82-
self._board_list = board_list.BoardList()
83-
return self._board_list
84-
85-
def is_odid_board(self, board_name: str) -> bool:
86-
try:
87-
hwdef = self.board_list().hwdef_for_board(board_name)
88-
except KeyError:
89-
return False
90-
return hwdef.intdefines.get('AP_OPENDRONEID_ENABLED', 0) == 1
91-
92-
def is_ap_periph_board(self, board_name: str) -> bool:
93-
try:
94-
return bool(self.board_list().board_by_name(board_name).is_ap_periph)
95-
except KeyError:
96-
return False
97-
98-
def check_board_readme_required(self, board_name: str) -> bool:
99-
return not self.is_odid_board(board_name)
100-
101-
def check_board_image_required(self, board_name: str) -> bool:
102-
if self.is_odid_board(board_name):
103-
return False
104-
if self.is_ap_periph_board(board_name):
105-
return False
106-
return True
107-
10876
def check_merge_commits(self) -> bool:
10977
merge_commits = self.run_git(
11078
["log", f"{self.base_branch}..HEAD", "--merges", "--oneline"],
@@ -591,132 +559,10 @@ def check_board_ids(self) -> bool:
591559

592560
return all_board_ids_are_valid
593561

594-
def check_new_board_has_readme(self) -> bool:
595-
'''Every new hwdef board directory must include a README.md.'''
596-
597-
added_raw = self.run_git(
598-
["diff", "--name-only", "--diff-filter=A",
599-
f"{self.base_branch}...HEAD"],
600-
show_output=False,
601-
).strip()
602-
added_files = set(added_raw.splitlines()) if added_raw else set()
603-
604-
# Board dirs that have at least one newly added file at depth 1 under hwdef/
605-
candidate_boards = {
606-
f.split("/")[3]
607-
for f in added_files
608-
if f.startswith(self.HWDEF_PREFIX) and f.count("/") == 4
609-
}
610-
611-
if not candidate_boards:
612-
print(f"{PASS} No new hwdef board directories added.")
613-
return True
614-
615-
# Determine which of those dirs are genuinely new (absent in base branch)
616-
existing_raw = self.run_git(
617-
["ls-tree", "--name-only", self.base_branch,
618-
self.HWDEF_PREFIX],
619-
show_output=False,
620-
).strip()
621-
existing_boards = {
622-
os.path.basename(p)
623-
for p in existing_raw.splitlines()
624-
if p.strip()
625-
}
626-
627-
new_boards = sorted(candidate_boards - existing_boards)
628-
if not new_boards:
629-
print(f"{PASS} No new hwdef board directories added.")
630-
return True
631-
632-
ok = True
633-
for board_name in new_boards:
634-
if not self.check_board_readme_required(board_name):
635-
print(f"{PASS} {board_name}: README.md not required for ODID variant.")
636-
continue
637-
638-
board_prefix = f"{self.HWDEF_PREFIX}{board_name}/"
639-
has_readme = any(
640-
os.path.basename(f) == "README.md"
641-
for f in added_files
642-
if f.startswith(board_prefix)
643-
)
644-
if has_readme:
645-
print(f"{PASS} {board_name}: README.md present.")
646-
else:
647-
print(f"{FAIL} {board_name}: new board directory has no README.md.")
648-
ok = False
649-
650-
return ok
651-
652-
def check_new_board_images(self) -> bool:
653-
'''For each newly added hwdef board README, verify it contains at least one
654-
local image reference that is also a newly added file in the PR.'''
655-
656-
# All files added (not modified) in this PR
657-
added_raw = self.run_git(
658-
["diff", "--name-only", "--diff-filter=A",
659-
f"{self.base_branch}...HEAD"],
660-
show_output=False,
661-
).strip()
662-
added_files = set(added_raw.splitlines()) if added_raw else set()
663-
664-
# New READMEs exactly one directory level under hwdef/
665-
# e.g. libraries/AP_HAL_ChibiOS/hwdef/NewBoard/README.md → 4 slashes
666-
new_readmes = sorted(
667-
f for f in added_files
668-
if f.startswith(self.HWDEF_PREFIX)
669-
and os.path.basename(f) == "README.md"
670-
and f.count("/") == 4
671-
)
672-
673-
if not new_readmes:
674-
print(f"{PASS} No new hwdef board READMEs added.")
675-
return True
676-
677-
img_md_re = re.compile(r'!\[[^\]]*\]\(([^)#?\s]+)')
678-
679-
ok = True
680-
for readme_path in new_readmes:
681-
board_name = readme_path.split("/")[3]
682-
683-
if not self.check_board_image_required(board_name):
684-
print(f"{PASS} {board_name}: README.md image not required.")
685-
continue
686-
687-
if not os.path.exists(readme_path):
688-
print(f"{SKIP} {board_name}: README not present locally, skipping image check.")
689-
continue
690-
691-
with open(readme_path, encoding="utf-8", errors="replace") as fh:
692-
content = fh.read()
693-
694-
readme_dir = os.path.dirname(readme_path)
695-
local_refs = []
696-
for m in img_md_re.finditer(content):
697-
src = m.group(1).strip()
698-
if not src.startswith(("http://", "https://", "//")):
699-
local_refs.append(src)
700-
701-
if not local_refs:
702-
print(f"{FAIL} {board_name}: README.md contains no local image references.")
703-
ok = False
704-
continue
705-
706-
found = any(
707-
os.path.normpath(os.path.join(readme_dir, src)) in added_files
708-
for src in local_refs
709-
)
710-
if found:
711-
print(f"{PASS} {board_name}: README.md includes at least one image added in this PR.")
712-
else:
713-
print(f"{FAIL} {board_name}: README.md has no local images added in this PR.")
714-
for src in local_refs:
715-
resolved = os.path.normpath(os.path.join(readme_dir, src))
716-
print(f" {src!r}{resolved}")
717-
ok = False
718-
719-
return ok
562+
# NOTE: checks concerning new hwdef boards (README.md presence, README
563+
# images, defaults.parm contents, and the board build itself) live in
564+
# test_new_boards.py, not here. Add new-board-related checks there to keep
565+
# them in one place and avoid the duplication this file once had.
720566

721567
def check_markdown(self) -> bool:
722568
changed_md = self.run_git(
@@ -762,8 +608,6 @@ def run(self) -> None:
762608
self.check_author_emails(),
763609
self.check_submodule_isolation(),
764610
self.check_submodule_references_exist(),
765-
self.check_new_board_has_readme(),
766-
self.check_new_board_images(),
767611
self.check_board_ids(),
768612
self.check_markdown(),
769613
self.check_markdown_rst_hyperlinks(),

Tools/scripts/test_new_boards.py

Lines changed: 87 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@
33
'''
44
Look at the difference between this and its merge-base with master
55
6-
for each new hwdef file, build the board
6+
for each new hwdef file, build the board.
7+
8+
Also validates each new board directory:
9+
- it contains a README.md (except ODID variants, which are exempt)
10+
- that README.md references at least one image added in this branch
11+
(except AP_Periph boards, which need only the README, no image)
12+
- defaults.parm does not set parameters that belong in hwdef.dat
713
814
AP_FLAKE8_CLEAN
915
'''
@@ -32,9 +38,9 @@ def progress_prefix(self) -> str:
3238
'''pretty-print progress'''
3339
return 'TNB'
3440

35-
def get_new_hwdef_paths(self) -> Set[str]:
36-
'''returns list of newly added hwdef filepaths relative to the root.
37-
Only .dat files for both main and bootloader firmwares'''
41+
def get_added_files(self) -> Set[str]:
42+
'''returns the set of files newly added (git status 'A') relative to the
43+
merge-base with master, as paths relative to the repo root'''
3844
current_commitish = self.find_current_git_branch_or_sha1()
3945
merge_base = self.find_git_branch_merge_base(current_commitish, self.master_branch)
4046
delta_files = self.run_git([
@@ -44,7 +50,6 @@ def get_new_hwdef_paths(self) -> Set[str]:
4450
], show_output=False)
4551

4652
ret = set()
47-
4853
for line in delta_files.split("\n"):
4954
if not line:
5055
continue
@@ -55,11 +60,19 @@ def get_new_hwdef_paths(self) -> Set[str]:
5560
# Only process added files (status 'A')
5661
if status != 'A':
5762
continue
63+
ret.add(filepath)
64+
65+
return ret
66+
67+
def get_added_hwdef_paths(self) -> Set[str]:
68+
'''returns the newly added hwdef filepaths relative to the repo root.
69+
Only .dat files for both main and bootloader firmwares'''
70+
ret = set()
71+
for filepath in self.get_added_files():
5872
if "/hwdef/" not in filepath:
5973
continue
6074
if not filepath.endswith(("hwdef.dat", "hwdef-bl.dat")):
6175
continue
62-
6376
ret.add(filepath)
6477

6578
return ret
@@ -97,6 +110,54 @@ def check_defaults_parm(self, defaults_parm_path: str, board_name: str) -> None:
97110
f"defined in hwdef.dat instead:\n" + "\n".join(issues)
98111
)
99112

113+
# markdown image reference: ![alt](path), capturing the path
114+
IMG_MD_RE = re.compile(r'!\[[^\]]*\]\(([^)#?\s]+)')
115+
116+
def is_odid_board(self, board: board_list.Board) -> bool:
117+
'''True if the board enables OpenDroneID in its hwdef'''
118+
hwdef = board.get_hwdef()
119+
return hwdef.intdefines.get('AP_OPENDRONEID_ENABLED', 0) == 1
120+
121+
def check_new_board_readme(self, board_name: str, hwdef_dir: str, added_files: Set[str],
122+
image_required: bool = True) -> None:
123+
'''A new board directory must contain a README.md. Unless image_required
124+
is False, that README must also reference at least one local image that is
125+
also added in this branch (AP_Periph boards need only the README).'''
126+
readme_path = os.path.join(hwdef_dir, 'README.md')
127+
if not os.path.exists(readme_path):
128+
raise ValueError(f"Missing README.md for new board: {readme_path} does not exist")
129+
130+
if not image_required:
131+
return
132+
133+
with open(readme_path, encoding="utf-8", errors="replace") as fh:
134+
content = fh.read()
135+
136+
local_refs = []
137+
for m in self.IMG_MD_RE.finditer(content):
138+
src = m.group(1).strip()
139+
if not src.startswith(("http://", "https://", "//")):
140+
local_refs.append(src)
141+
142+
if not local_refs:
143+
raise ValueError(
144+
f"README.md for new board {board_name} contains no local image references"
145+
)
146+
147+
found = any(
148+
os.path.normpath(os.path.join(hwdef_dir, src)) in added_files
149+
for src in local_refs
150+
)
151+
if not found:
152+
resolved = "\n".join(
153+
f" {src!r} -> {os.path.normpath(os.path.join(hwdef_dir, src))}"
154+
for src in local_refs
155+
)
156+
raise ValueError(
157+
f"README.md for new board {board_name} references no image added in "
158+
f"this branch:\n{resolved}"
159+
)
160+
100161
def build_board(self, board : board_list.Board):
101162
self.run_waf(["configure", "--board", board.name], show_output=False)
102163
if board.is_ap_periph:
@@ -124,7 +185,8 @@ def build_bootloader(self, board : board_list.Board) -> None:
124185
self.run_waf(["bootloader"], show_output=False)
125186

126187
def run(self) -> None:
127-
hwdef_filepaths = self.get_new_hwdef_paths()
188+
added_files = self.get_added_files()
189+
hwdef_filepaths = self.get_added_hwdef_paths()
128190

129191
@dataclass
130192
class BoardToTest():
@@ -154,14 +216,17 @@ class BoardToTest():
154216
# Build each unique board (find it in board list as an additional check)
155217
bl = board_list.BoardList()
156218

157-
# First pass: check for README.md for non-AP_Periph boards
219+
# First pass: validate README.md and defaults.parm for each new board.
220+
# A board directory may contribute both hwdef.dat and hwdef-bl.dat, so
221+
# only validate each directory once.
222+
checked_board_dirs = set()
158223
for hwdef_filepath in hwdef_filepaths:
159224
# Extract board name from path like libraries/AP_HAL_ChibiOS/hwdef/BoardName/hwdef.dat
160225
parts = hwdef_filepath.split('/')
161226
hwdef_idx = parts.index('hwdef')
162227
board_name = parts[hwdef_idx + 1]
163228

164-
# Find the board object to check if it's AP_Periph
229+
# Find the board object to check if it's AP_Periph / ODID
165230
board = None
166231
for b in bl.boards:
167232
if b.name == board_name:
@@ -172,12 +237,20 @@ class BoardToTest():
172237
raise ValueError(f"Board {board_name} not found in board list")
173238

174239
hwdef_dir = os.path.join(*parts[:hwdef_idx + 2])
240+
if hwdef_dir in checked_board_dirs:
241+
continue
242+
checked_board_dirs.add(hwdef_dir)
175243

176-
# Only require README.md for non-AP_Periph boards
177-
if not board.is_ap_periph:
178-
readme_path = os.path.join(hwdef_dir, 'README.md')
179-
if not os.path.exists(readme_path):
180-
raise ValueError(f"Missing README.md for new board: {readme_path} does not exist")
244+
# ODID variants are exempt from the README requirement entirely.
245+
# AP_Periph boards must ship a README but are not required to embed
246+
# an image. All other boards require both a README and an image.
247+
if self.is_odid_board(board):
248+
self.progress(f"README.md not required for {board_name} (ODID)")
249+
else:
250+
self.check_new_board_readme(
251+
board_name, hwdef_dir, added_files,
252+
image_required=not board.is_ap_periph,
253+
)
181254

182255
defaults_parm_path = os.path.join(hwdef_dir, 'defaults.parm')
183256
if os.path.exists(defaults_parm_path):

0 commit comments

Comments
 (0)