initial commit
This commit is contained in:
271
libraries/FastLED/ci/ci-iwyu.py
Executable file
271
libraries/FastLED/ci/ci-iwyu.py
Executable file
@@ -0,0 +1,271 @@
|
||||
#!/usr/bin/env python3
|
||||
# pyright: reportUnknownMemberType=false
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run include-what-you-use on the project"
|
||||
)
|
||||
parser.add_argument("board", nargs="?", help="Board to check, optional")
|
||||
parser.add_argument(
|
||||
"--fix",
|
||||
action="store_true",
|
||||
help="Automatically apply suggested fixes using fix_includes",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--mapping-file",
|
||||
action="append",
|
||||
help="Additional mapping file to use (can be specified multiple times)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--max-line-length",
|
||||
type=int,
|
||||
default=100,
|
||||
help="Maximum line length for suggestions (default: 100)",
|
||||
)
|
||||
parser.add_argument("--verbose", action="store_true", help="Enable verbose output")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def find_platformio_project_dir(board_dir: Path) -> Path | None:
|
||||
"""Find a directory containing platformio.ini file in the board's build directory.
|
||||
|
||||
With the new pio ci build system, the structure is:
|
||||
.build/uno/Blink/platformio.ini
|
||||
.build/uno/SomeExample/platformio.ini
|
||||
|
||||
We need to find one of these example directories that has a platformio.ini file.
|
||||
"""
|
||||
if not board_dir.exists():
|
||||
return None
|
||||
|
||||
# Look for subdirectories containing platformio.ini
|
||||
for subdir in board_dir.iterdir():
|
||||
if subdir.is_dir():
|
||||
platformio_ini = subdir / "platformio.ini"
|
||||
if platformio_ini.exists():
|
||||
print(f"Found platformio.ini in {subdir}")
|
||||
return subdir
|
||||
|
||||
# Fallback: check if there's a platformio.ini directly in the board directory (old system)
|
||||
if (board_dir / "platformio.ini").exists():
|
||||
print(f"Found platformio.ini directly in {board_dir}")
|
||||
return board_dir
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def check_iwyu_available() -> bool:
|
||||
"""Check if include-what-you-use is available in the system"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["include-what-you-use", "--version"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10,
|
||||
)
|
||||
return result.returncode == 0
|
||||
except (
|
||||
subprocess.CalledProcessError,
|
||||
FileNotFoundError,
|
||||
subprocess.TimeoutExpired,
|
||||
):
|
||||
return False
|
||||
|
||||
|
||||
def run_iwyu_on_cpp_tests(args: argparse.Namespace) -> int:
|
||||
"""Run IWYU on the C++ test suite using CMake integration"""
|
||||
here = Path(__file__).parent
|
||||
project_root = here.parent
|
||||
|
||||
print("Running include-what-you-use on C++ test suite...")
|
||||
|
||||
# Use the existing test infrastructure with --check flag
|
||||
cmd = [
|
||||
"uv",
|
||||
"run",
|
||||
"test.py",
|
||||
"--cpp",
|
||||
"--check", # This enables IWYU via CMake
|
||||
"--clang",
|
||||
"--no-interactive",
|
||||
]
|
||||
|
||||
if args.verbose:
|
||||
cmd.append("--verbose")
|
||||
|
||||
try:
|
||||
result = subprocess.run(cmd, cwd=project_root)
|
||||
return result.returncode
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"IWYU analysis failed with return code {e.returncode}")
|
||||
return e.returncode
|
||||
|
||||
|
||||
def run_iwyu_on_platformio_project(project_dir: Path, args: argparse.Namespace) -> int:
|
||||
"""Run IWYU on a PlatformIO project"""
|
||||
print(f"Running include-what-you-use in {project_dir}")
|
||||
os.chdir(str(project_dir))
|
||||
|
||||
# Build mapping file arguments
|
||||
mapping_args: List[str] = []
|
||||
|
||||
# Add FastLED mapping files if they exist
|
||||
project_root = project_dir
|
||||
while project_root.parent != project_root: # Find project root
|
||||
if (project_root / "ci" / "iwyu").exists():
|
||||
break
|
||||
project_root = project_root.parent
|
||||
|
||||
fastled_mapping = project_root / "ci" / "iwyu" / "fastled.imp"
|
||||
stdlib_mapping = project_root / "ci" / "iwyu" / "stdlib.imp"
|
||||
|
||||
if fastled_mapping.exists():
|
||||
mapping_args.extend(["--mapping_file", str(fastled_mapping)])
|
||||
|
||||
if stdlib_mapping.exists():
|
||||
mapping_args.extend(["--mapping_file", str(stdlib_mapping)])
|
||||
|
||||
# Add user-specified mapping files
|
||||
if args.mapping_file:
|
||||
for mapping in args.mapping_file:
|
||||
mapping_args.extend(["--mapping_file", mapping])
|
||||
|
||||
# Build IWYU command
|
||||
iwyu_cmd = [
|
||||
"include-what-you-use",
|
||||
f"--max_line_length={args.max_line_length}",
|
||||
"--quoted_includes_first",
|
||||
"--no_comments",
|
||||
]
|
||||
|
||||
if args.verbose:
|
||||
iwyu_cmd.append("--verbose=3")
|
||||
else:
|
||||
iwyu_cmd.append("--verbose=1")
|
||||
|
||||
iwyu_cmd.extend(mapping_args)
|
||||
|
||||
# Run through PlatformIO's check system with IWYU
|
||||
pio_cmd = [
|
||||
"pio",
|
||||
"check",
|
||||
"--skip-packages",
|
||||
"--src-filters=+<src/>",
|
||||
"--tool=include-what-you-use",
|
||||
"--flags",
|
||||
] + iwyu_cmd[1:] # Skip the include-what-you-use binary name
|
||||
|
||||
try:
|
||||
result = subprocess.run(pio_cmd)
|
||||
return result.returncode
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"PlatformIO IWYU check failed with return code {e.returncode}")
|
||||
return e.returncode
|
||||
|
||||
|
||||
def apply_iwyu_fixes(source_dir: Path) -> int:
|
||||
"""Apply IWYU fixes using fix_includes tool"""
|
||||
try:
|
||||
# Check if fix_includes is available
|
||||
subprocess.run(["fix_includes", "--help"], capture_output=True, check=True)
|
||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||
print(
|
||||
"Warning: fix_includes tool not found. Install IWYU tools to use --fix option."
|
||||
)
|
||||
return 1
|
||||
|
||||
# Find all .h and .cpp files
|
||||
cpp_files: List[Path] = []
|
||||
for pattern in ["**/*.cpp", "**/*.h", "**/*.hpp"]:
|
||||
cpp_files.extend(source_dir.glob(pattern))
|
||||
|
||||
if not cpp_files:
|
||||
print("No C++ files found to fix")
|
||||
return 0
|
||||
|
||||
print(f"Applying IWYU fixes to {len(cpp_files)} files...")
|
||||
|
||||
# Run fix_includes on the source directory
|
||||
cmd = ["fix_includes", "--update_comments", str(source_dir)]
|
||||
|
||||
try:
|
||||
result = subprocess.run(cmd)
|
||||
if result.returncode == 0:
|
||||
print("IWYU fixes applied successfully")
|
||||
else:
|
||||
print(f"fix_includes failed with return code {result.returncode}")
|
||||
return result.returncode
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Failed to apply IWYU fixes: {e}")
|
||||
return e.returncode
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
here = Path(__file__).parent
|
||||
project_root = here.parent
|
||||
|
||||
# Check if IWYU is available
|
||||
if not check_iwyu_available():
|
||||
print("Error: include-what-you-use not found in PATH")
|
||||
print("Install it with:")
|
||||
print(" Ubuntu/Debian: sudo apt install iwyu")
|
||||
print(" macOS: brew install include-what-you-use")
|
||||
print(" Or build from source: https://include-what-you-use.org/")
|
||||
return 1
|
||||
|
||||
print("Found include-what-you-use")
|
||||
|
||||
# If no board specified, run on C++ test suite
|
||||
if not args.board:
|
||||
print("No board specified, running IWYU on C++ test suite")
|
||||
return run_iwyu_on_cpp_tests(args)
|
||||
|
||||
# Run on specific board
|
||||
build = project_root / ".build"
|
||||
|
||||
if not build.exists():
|
||||
print(f"Build directory {build} not found")
|
||||
print("Run a compilation first: ./compile [board] --examples [example]")
|
||||
return 1
|
||||
|
||||
board_dir = build / args.board
|
||||
if not board_dir.exists():
|
||||
print(f"Board {args.board} not found in {build}")
|
||||
print("Available boards:")
|
||||
for d in build.iterdir():
|
||||
if d.is_dir():
|
||||
print(f" {d.name}")
|
||||
return 1
|
||||
|
||||
project_dir = find_platformio_project_dir(board_dir)
|
||||
if not project_dir:
|
||||
print(f"No platformio.ini found in {board_dir} or its subdirectories")
|
||||
print("This usually means the board hasn't been compiled yet.")
|
||||
print(f"Try running: ./compile {args.board} --examples Blink")
|
||||
return 1
|
||||
|
||||
# Run IWYU on the PlatformIO project
|
||||
result = run_iwyu_on_platformio_project(project_dir, args)
|
||||
|
||||
# Apply fixes if requested and analysis succeeded
|
||||
if args.fix and result == 0:
|
||||
src_dir = project_dir / "src"
|
||||
if src_dir.exists():
|
||||
fix_result = apply_iwyu_fixes(src_dir)
|
||||
if fix_result != 0:
|
||||
result = fix_result
|
||||
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user