blob: 18ba79551cd4eef94df74e2f3d65ec4814ac57de [file] [edit]
#!/usr/bin/env python3
"""
Append an include-tree comment after the contents of each `memmap*.ld` under
`src/*/pico_platform/`.
The comment matches the output of `memmap_include_tree.py` (two columns, no color).
Re-running replaces a previous block marked `BEGIN_MEMMAP_INCLUDE_TREE` /
`END_MEMMAP_INCLUDE_TREE` (at the end of the file, or at the beginning for older runs).
Examples:
./tools/memmap_annotate.py # annotates all memmap*.ld files in the SDK
./tools/memmap_annotate.py --ci # does not write files; exits with failure if any memmap would need updating
"""
from __future__ import annotations
import argparse
import re
import sys
from pathlib import Path
_TOOLS = Path(__file__).resolve().parent
if str(_TOOLS) not in sys.path:
sys.path.insert(0, str(_TOOLS))
import memmap_include_tree as inc_tree
_ANNOT_BEGIN = "/* BEGIN_MEMMAP_INCLUDE_TREE\n"
_ANNOT_END = " * END_MEMMAP_INCLUDE_TREE */\n"
_ANNOT_BLOCK_CORE = (
r"/\* BEGIN_MEMMAP_INCLUDE_TREE\n.*?\n \* END_MEMMAP_INCLUDE_TREE \*/\s*"
)
def strip_annotation_block(text: str) -> str:
"""Remove a previously inserted annotation block (trailing, or legacy leading)."""
t = text
# Preferred: block at end of file
m = re.search(_ANNOT_BLOCK_CORE + r"\Z", t, re.DOTALL)
if m:
head = t[: m.start()].rstrip("\n")
if head:
head += "\n"
return head
# Legacy: block was prepended at the beginning
m2 = re.match(_ANNOT_BLOCK_CORE + r"\n?", t, re.DOTALL)
if m2:
return t[m2.end() :].lstrip("\n")
return t
def build_annotation_block(tree_lines: list[str]) -> str:
parts = [
_ANNOT_BEGIN,
" * This is the default include tree for this file (generated by tools/memmap_annotate.py\n"
" * and tools/memmap_include_tree.py). To override any files in this tree, see the\n"
" * pico_add_linker_script_override_path CMake function under pico_standard_link.\n",
" *\n",
]
for ln in tree_lines:
parts.append(" * " + ln + "\n")
parts.append(_ANNOT_END)
return "".join(parts)
def annotate_one(
path: Path,
sdk_root: Path,
platform: str | None,
*,
ci: bool,
) -> tuple[bool, str]:
"""Return (ok, status message). ok is False on unresolved includes."""
path = path.resolve()
search_dirs = inc_tree.default_search_dirs(sdk_root, path, platform)
visited: set[Path] = set()
user_l_set: frozenset[Path] = frozenset()
user_l_labels: dict[Path, str] = {}
root_node, root_children = inc_tree.walk_includes(
path, search_dirs, user_l_set, user_l_labels, visited
)
missing = inc_tree.collect_unresolved_include_paths(root_children)
if missing:
names = ", ".join(m.name for m in missing)
return False, f"unresolved INCLUDE: {names}"
lines = inc_tree.format_tree_lines(root_node, root_children, color=False)
block = build_annotation_block(lines)
old = path.read_text(encoding="utf-8", errors="replace")
body = strip_annotation_block(old)
if body and not body.endswith("\n"):
body += "\n"
new = body + "\n" + block if body else block
if new == old:
return True, "unchanged"
if ci:
return True, "needs updating"
path.write_text(new, encoding="utf-8")
return True, "updated"
def parse_args(argv: list[str]) -> argparse.Namespace:
p = argparse.ArgumentParser(
description=inc_tree.fold_docstring_for_argparse(__doc__ or ""),
formatter_class=argparse.RawDescriptionHelpFormatter,
)
p.add_argument(
"--sdk-root",
type=Path,
default=None,
help=inc_tree.CLI_HELP_SDK_ROOT,
)
p.add_argument(
"--platform",
default=None,
metavar="CHIP",
help=inc_tree.CLI_HELP_PLATFORM,
)
p.add_argument(
"--ci",
action="store_true",
help="Do not write files; exit with failure if any memmap would need updating",
)
p.add_argument(
"paths",
nargs="*",
type=Path,
metavar="MEMMAP.LD",
help="Linker scripts to annotate (default: all src/*/pico_platform/memmap*.ld)",
)
return p.parse_args(argv)
def main(argv: list[str]) -> int:
args = parse_args(argv)
sdk_root = args.sdk_root
if args.paths:
files = [p.resolve() for p in args.paths]
for f in files:
if not f.is_file():
print(f"error: not a file: {f}", file=sys.stderr)
return 1
if sdk_root is None:
sdk_root = inc_tree.infer_sdk_root(files[0])
if sdk_root is None:
print(inc_tree.CLI_MSG_SDK_ROOT_INFER_FAILED, file=sys.stderr)
return 1
else:
if sdk_root is None:
sdk_root = inc_tree.infer_sdk_root(Path.cwd())
if sdk_root is None:
print(inc_tree.CLI_MSG_SDK_ROOT_INFER_FAILED, file=sys.stderr)
return 1
files = sorted(sdk_root.glob("src/*/pico_platform/memmap*.ld"))
if not files:
print("error: no memmap*.ld files to process", file=sys.stderr)
return 1
sdk_root = sdk_root.resolve()
if args.platform is not None:
perr = inc_tree.validate_explicit_platform(sdk_root, args.platform)
if perr:
print(f"error: {perr}", file=sys.stderr)
return 1
err = 0
for f in files:
plat = args.platform or inc_tree.infer_platform_chip(f)
ok, msg = annotate_one(f, sdk_root, plat, ci=args.ci)
try:
tag = str(f.relative_to(sdk_root))
except ValueError:
tag = str(f)
if ok:
print(inc_tree.format_memmap_annotate_status_line(tag, msg))
if args.ci and msg == "needs updating":
err = 1
else:
print(f"{tag}: {msg}", file=sys.stderr)
err = 1
return err
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))