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