Files
klubhaus-doorbell/libraries/FastLED/ci/util/tools.py
2026-02-12 00:45:31 -08:00

212 lines
6.4 KiB
Python

# pyright: reportUnknownMemberType=false
"""
Tools for working with build info and tool paths.
"""
import json
import os
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Dict
from ci.util.paths import BUILD
@dataclass
class Tools:
as_path: Path
ld_path: Path
objcopy_path: Path
objdump_path: Path
cpp_filt_path: Path
nm_path: Path
def load_tools(build_info_path: Path) -> Tools:
build_info: Dict[str, Any] = json.loads(build_info_path.read_text())
board_info: Dict[str, Any] = build_info[next(iter(build_info))]
aliases: Dict[str, str] = board_info["aliases"]
as_path = Path(aliases["as"])
ld_path = Path(aliases["ld"])
objcopy_path = Path(aliases["objcopy"])
objdump_path = Path(aliases["objdump"])
cpp_filt_path = Path(aliases["c++filt"])
nm_path = Path(aliases["nm"])
if sys.platform == "win32":
as_path = as_path.with_suffix(".exe")
ld_path = ld_path.with_suffix(".exe")
objcopy_path = objcopy_path.with_suffix(".exe")
objdump_path = objdump_path.with_suffix(".exe")
cpp_filt_path = cpp_filt_path.with_suffix(".exe")
nm_path = nm_path.with_suffix(".exe")
out = Tools(as_path, ld_path, objcopy_path, objdump_path, cpp_filt_path, nm_path)
tools = [as_path, ld_path, objcopy_path, objdump_path, cpp_filt_path, nm_path]
for tool in tools:
if not tool.exists():
raise FileNotFoundError(f"Tool not found: {tool}")
return out
def _list_builds() -> list[Path]:
str_paths = os.listdir(BUILD)
paths = [BUILD / p for p in str_paths]
dirs = [p for p in paths if p.is_dir()]
return dirs
def _check_build(build: Path) -> bool:
# 1. should contain a build_info.json file
# 2. should contain a .pio/build directory
has_build_info = (build / "build_info.json").exists()
has_pio_build = (build / ".pio" / "build").exists()
return has_build_info and has_pio_build
def _prompt_build() -> Path:
builds = _list_builds()
if not builds:
print("Error: No builds found", file=sys.stderr)
sys.exit(1)
print("Select a build:")
for i, build in enumerate(builds):
print(f" [{i}]: {build}")
while True:
try:
which = int(input("Enter the number of the build to use: "))
if 0 <= which < len(builds):
valid = _check_build(BUILD / builds[which])
if valid:
return BUILD / builds[which]
print("Error: Invalid build", file=sys.stderr)
else:
print("Error: Invalid selection", file=sys.stderr)
continue
except ValueError:
print("Error: Invalid input", file=sys.stderr)
continue
def _prompt_object_file(build: Path) -> Path:
# Look for object files in .pio/build directory
build_dir = build / ".pio" / "build"
object_files: list[Path] = []
# Walk through build directory to find .o files
for root, _, files in os.walk(build_dir):
for file in files:
if file.endswith(".o") and "FrameworkArduino" not in file:
full_path = Path(root) / file
if "FrameworkArduino" not in full_path.parts:
object_files.append(full_path)
if not object_files:
print("Error: No object files found", file=sys.stderr)
sys.exit(1)
print("\nSelect an object file:")
for i, obj_file in enumerate(object_files):
print(f" [{i}]: {obj_file.relative_to(build_dir)}")
while True:
try:
which = int(input("Enter the number of the object file to use: "))
if 0 <= which < len(object_files):
return object_files[which]
print("Error: Invalid selection", file=sys.stderr)
except ValueError:
print("Error: Invalid input", file=sys.stderr)
continue
def cli() -> None:
import argparse
parser = argparse.ArgumentParser(
description="Dump object file information using build tools"
)
parser.add_argument(
"build_path",
type=Path,
nargs="?",
help="Path to build directory containing build info JSON file",
)
parser.add_argument(
"--symbols", action="store_true", help="Dump symbol table using nm"
)
parser.add_argument(
"--disassemble", action="store_true", help="Dump disassembly using objdump"
)
args = parser.parse_args()
build_path = args.build_path
symbols = args.symbols
disassemble = args.disassemble
# Check if object file was provided and exists
if build_path is None:
build_path = _prompt_build()
else:
if not _check_build(build_path):
print("Error: Invalid build directory", file=sys.stderr)
sys.exit(1)
assert build_path is not None
assert build_path
build_info_path = build_path / "build_info.json"
assert build_info_path.exists(), f"File not found: {build_info_path}"
tools = load_tools(build_info_path)
if not symbols and not disassemble:
while True:
print(
"Error: Please specify at least one action to perform", file=sys.stderr
)
action = input(
"Enter 's' to dump symbols, 'd' to disassemble, or 'q' to quit: "
)
if action == "s":
symbols = True
break
elif action == "d":
disassemble = True
break
elif action == "q":
sys.exit(0)
else:
print("Error: Invalid action", file=sys.stderr)
object_file = _prompt_object_file(build_path)
if symbols:
import subprocess
cmd_str = subprocess.list2cmdline(
[str(tools.objdump_path), str(object_file), "--syms"]
)
print(f"Running command: {cmd_str}")
subprocess.run([str(tools.objdump_path), str(object_file)])
if disassemble:
import subprocess
cmd_str = subprocess.list2cmdline(
[str(tools.objdump_path), "-d", str(object_file)]
)
print(f"Running command: {cmd_str}")
subprocess.run([str(tools.objdump_path), "-d", str(object_file)])
if not (symbols or disassemble):
parser.print_help()
if __name__ == "__main__":
try:
cli()
except KeyboardInterrupt:
print("Exiting...")
sys.exit(1)