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

481 lines
15 KiB
Python

#!/usr/bin/env python3
# pyright: reportUnknownMemberType=false
"""
Build Info Analyzer - Tool for extracting platform information from build_info.json files.
This tool provides easy access to platform-specific defines, compiler flags, toolchain paths,
and other build configuration information generated by PlatformIO.
Usage:
python build_info_analyzer.py --board uno --show-defines
python build_info_analyzer.py --board esp32dev --show-all
python build_info_analyzer.py --list-boards
"""
import argparse
import json
from dataclasses import asdict, dataclass, field
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
@dataclass
class CompilerInfo:
"""Information about compilers and toolchain"""
cc_path: str = ""
cxx_path: str = ""
cc_flags: List[str] = field(default_factory=lambda: list())
cxx_flags: List[str] = field(default_factory=lambda: list())
compiler_type: str = ""
build_type: str = ""
@dataclass
class BuildInfo:
"""Complete build information for a platform"""
board_name: str
defines: Dict[str, str] = field(default_factory=lambda: dict())
compiler_info: CompilerInfo = field(default_factory=CompilerInfo)
aliases: Dict[str, Optional[str]] = field(default_factory=lambda: dict())
class BuildInfoAnalyzer:
"""Analyzer for build_info.json files generated by PlatformIO builds."""
def __init__(self, build_dir: str):
"""
Initialize the analyzer.
Args:
build_dir: Directory containing platform build directories
"""
self.build_dir = Path(build_dir)
def list_available_boards(self) -> List[str]:
"""
List all boards that have build_info.json files.
Returns:
List of board names that have been built
"""
boards: List[str] = []
if not self.build_dir.exists():
return boards
for item in self.build_dir.iterdir():
if item.is_dir() and (item / "build_info.json").exists():
boards.append(item.name)
return sorted(boards)
def get_build_info_path(self, board_name: str) -> Optional[Path]:
"""
Get the path to build_info.json for a specific board.
Args:
board_name: Name of the board (e.g., 'uno', 'esp32dev')
Returns:
Path to build_info.json file or None if not found
"""
build_info_path = self.build_dir / board_name / "build_info.json"
if build_info_path.exists():
return build_info_path
return None
def load_build_info(self, board_name: str) -> Optional[Dict[str, Any]]:
"""
Load and parse build_info.json for a board.
Args:
board_name: Name of the board
Returns:
Parsed JSON data or None if file not found
"""
build_info_path = self.get_build_info_path(board_name)
if not build_info_path:
return None
try:
with open(build_info_path, "r") as f:
return json.load(f)
except (json.JSONDecodeError, IOError) as e:
print(f"Error loading build_info.json for {board_name}: {e}")
return None
def create_board_key_from_build_info(
self, data: Dict[str, Any], board_name: str
) -> Optional[str]:
"""
Get the actual board key used in build_info.json.
Sometimes the board name differs from the directory name.
Args:
data: Parsed build_info.json data
board_name: Directory name of the board
Returns:
Actual board key used in JSON or None if not found
"""
# Try exact match first
if board_name in data:
return board_name
# If only one key, use it
keys = list(data.keys())
if len(keys) == 1:
return keys[0]
# Try to find a close match
for key in keys:
if board_name.lower() in key.lower() or key.lower() in board_name.lower():
return key
return None
def get_platform_defines(self, board_name: str) -> Tuple[bool, List[str], str]:
"""
Get platform-specific preprocessor defines for a board.
Args:
board_name: Name of the board
Returns:
Tuple of (success, defines_list, error_message)
"""
data = self.load_build_info(board_name)
if not data:
return False, [], f"Build info not found for {board_name}"
board_key = self.create_board_key_from_build_info(data, board_name)
if not board_key:
available_keys = list(data.keys())
return False, [], f"Board key not found. Available keys: {available_keys}"
board_data = data[board_key]
defines = board_data.get("defines", [])
return True, defines, ""
def get_compiler_info(self, board_name: str) -> Tuple[bool, CompilerInfo, str]:
"""
Get compiler information for a board.
Args:
board_name: Name of the board
Returns:
Tuple of (success, compiler_info, error_message)
"""
data = self.load_build_info(board_name)
if not data:
return False, CompilerInfo(), f"Build info not found for {board_name}"
board_key = self.create_board_key_from_build_info(data, board_name)
if not board_key:
return False, CompilerInfo(), "Board key not found in build_info.json"
board_data = data[board_key]
compiler_info = CompilerInfo(
cc_path=board_data.get("cc_path", ""),
cxx_path=board_data.get("cxx_path", ""),
cc_flags=board_data.get("cc_flags", []),
cxx_flags=board_data.get("cxx_flags", []),
compiler_type=board_data.get("compiler_type", ""),
build_type=board_data.get("build_type", ""),
)
return True, compiler_info, ""
def get_toolchain_aliases(
self, board_name: str
) -> Tuple[bool, Dict[str, str], str]:
"""
Get toolchain tool aliases for a board.
Args:
board_name: Name of the board
Returns:
Tuple of (success, aliases_dict, error_message)
"""
data = self.load_build_info(board_name)
if not data:
return False, {}, f"Build info not found for {board_name}"
board_key = self.create_board_key_from_build_info(data, board_name)
if not board_key:
return False, {}, "Board key not found in build_info.json"
board_data = data[board_key]
aliases = board_data.get("aliases", {})
return True, aliases, ""
def get_all_info(self, board_name: str) -> Tuple[bool, Dict[str, Any], str]:
"""
Get all available information for a board.
Args:
board_name: Name of the board
Returns:
Tuple of (success, all_info_dict, error_message)
"""
data = self.load_build_info(board_name)
if not data:
return False, {}, f"Build info not found for {board_name}"
board_key = self.create_board_key_from_build_info(data, board_name)
if not board_key:
return False, {}, "Board key not found in build_info.json"
return True, data[board_key], ""
def compare_defines(
self, board1: str, board2: str
) -> Tuple[bool, Dict[str, Any], str]:
"""
Compare platform defines between two boards.
Args:
board1: First board name
board2: Second board name
Returns:
Tuple of (success, comparison_dict, error_message)
"""
success1, defines1, err1 = self.get_platform_defines(board1)
success2, defines2, err2 = self.get_platform_defines(board2)
if not success1:
return False, {}, f"Error getting defines for {board1}: {err1}"
if not success2:
return False, {}, f"Error getting defines for {board2}: {err2}"
set1 = set(defines1)
set2 = set(defines2)
comparison = {
"board1": board1,
"board2": board2,
"board1_only": sorted(list(set1 - set2)),
"board2_only": sorted(list(set2 - set1)),
"common": sorted(list(set1 & set2)),
"board1_total": len(defines1),
"board2_total": len(defines2),
"common_count": len(set1 & set2),
}
return True, comparison, ""
def print_defines(defines: List[str], board_name: str):
"""Print platform defines in a formatted way."""
print(f"\n📋 Platform Defines for {board_name.upper()}:")
print("=" * 50)
for define in defines:
print(f" {define}")
print(f"\nTotal: {len(defines)} defines")
def print_compiler_info(compiler_info: CompilerInfo, board_name: str):
"""Print compiler information in a formatted way."""
print(f"\n🔧 Compiler Information for {board_name.upper()}:")
print("=" * 50)
print(f"Compiler Type: {compiler_info.compiler_type or 'Unknown'}")
print(f"Build Type: {compiler_info.build_type or 'Unknown'}")
print(f"C Compiler: {compiler_info.cc_path or 'Unknown'}")
print(f"C++ Compiler: {compiler_info.cxx_path or 'Unknown'}")
if compiler_info.cc_flags:
print(f"\nC Flags ({len(compiler_info.cc_flags)}):")
for flag in compiler_info.cc_flags:
print(f" {flag}")
if compiler_info.cxx_flags:
print(f"\nC++ Flags ({len(compiler_info.cxx_flags)}):")
for flag in compiler_info.cxx_flags:
print(f" {flag}")
def print_toolchain_aliases(aliases: Dict[str, str], board_name: str):
"""Print toolchain aliases in a formatted way."""
print(f"\n⚙️ Toolchain Aliases for {board_name.upper()}:")
print("=" * 50)
for tool, path in aliases.items():
if path:
# Show just the tool name from the path for readability
tool_name = Path(path).name if path else "Not available"
print(f" {tool:10}: {tool_name}")
else:
print(f" {tool:10}: Not available")
def print_comparison(comparison: Dict[str, Any]):
"""Print a comparison between two boards."""
board1 = comparison["board1"]
board2 = comparison["board2"]
print("\n🔍 Platform Defines Comparison:")
print("=" * 60)
print(f"📊 {board1.upper()} vs {board2.upper()}")
print(f" {board1}: {comparison['board1_total']} defines")
print(f" {board2}: {comparison['board2_total']} defines")
print(f" Common: {comparison['common_count']} defines")
if comparison["board1_only"]:
print(f"\n🔴 Only in {board1.upper()} ({len(comparison['board1_only'])}):")
for define in comparison["board1_only"]:
print(f" {define}")
if comparison["board2_only"]:
print(f"\n🔵 Only in {board2.upper()} ({len(comparison['board2_only'])}):")
for define in comparison["board2_only"]:
print(f" {define}")
if comparison["common"]:
print(f"\n🟢 Common Defines ({len(comparison['common'])}):")
for define in comparison["common"]:
print(f" {define}")
def main():
"""Main function for command line usage."""
parser = argparse.ArgumentParser(
description="Analyze build_info.json files to extract platform information",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s --list-boards # List all available boards
%(prog)s --board uno --show-defines # Show UNO platform defines
%(prog)s --board esp32dev --show-compiler # Show ESP32 compiler info
%(prog)s --board teensy31 --show-all # Show all info for Teensy 3.1
%(prog)s --compare uno esp32dev # Compare defines between boards
""",
)
parser.add_argument(
"--build-dir",
default=".build",
help="Build directory containing platform subdirectories (default: .build)",
)
parser.add_argument(
"--list-boards",
action="store_true",
help="List all boards with build_info.json files",
)
parser.add_argument(
"--board", help="Board name to analyze (e.g., uno, esp32dev, teensy31)"
)
parser.add_argument(
"--show-defines", action="store_true", help="Show platform preprocessor defines"
)
parser.add_argument(
"--show-compiler", action="store_true", help="Show compiler information"
)
parser.add_argument(
"--show-toolchain", action="store_true", help="Show toolchain aliases"
)
parser.add_argument(
"--show-all", action="store_true", help="Show all available information"
)
parser.add_argument(
"--compare",
nargs=2,
metavar=("BOARD1", "BOARD2"),
help="Compare platform defines between two boards",
)
parser.add_argument(
"--json", action="store_true", help="Output results in JSON format"
)
args = parser.parse_args()
analyzer = BuildInfoAnalyzer(args.build_dir)
if args.list_boards:
boards = analyzer.list_available_boards()
if not boards:
print("❌ No boards with build_info.json found in build directory")
print(f" Directory: {analyzer.build_dir}")
print(
" Try running a compilation first: uv run python -m ci.ci-compile uno --examples Blink"
)
return 1
print(f"📋 Available boards ({len(boards)}):")
for board in boards:
print(f"{board}")
return 0
if args.compare:
board1, board2 = args.compare
success, comparison, error = analyzer.compare_defines(board1, board2)
if not success:
print(f"❌ Error: {error}")
return 1
if args.json:
print(json.dumps(comparison, indent=2))
else:
print_comparison(comparison)
return 0
if not args.board:
print("❌ Error: --board is required (or use --list-boards or --compare)")
return 1
# Handle single board analysis
if args.show_defines or args.show_all:
success, defines, error = analyzer.get_platform_defines(args.board)
if not success:
print(f"❌ Error getting defines: {error}")
return 1
if args.json:
print(json.dumps({"defines": defines}, indent=2))
else:
print_defines(defines, args.board)
if args.show_compiler or args.show_all:
success, compiler_info, error = analyzer.get_compiler_info(args.board)
if not success:
print(f"❌ Error getting compiler info: {error}")
return 1
if args.json:
print(json.dumps({"compiler": asdict(compiler_info)}, indent=2))
else:
print_compiler_info(compiler_info, args.board)
if args.show_toolchain or args.show_all:
success, aliases, error = analyzer.get_toolchain_aliases(args.board)
if not success:
print(f"❌ Error getting toolchain aliases: {error}")
return 1
if args.json:
print(json.dumps({"toolchain": aliases}, indent=2))
else:
print_toolchain_aliases(aliases, args.board)
return 0
if __name__ == "__main__":
exit(main())