initial commit
This commit is contained in:
211
libraries/FastLED/ci/util/tools.py
Normal file
211
libraries/FastLED/ci/util/tools.py
Normal file
@@ -0,0 +1,211 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user