initial commit
This commit is contained in:
477
libraries/FastLED/ci/util/check_implementation_files.py.disabled
Normal file
477
libraries/FastLED/ci/util/check_implementation_files.py.disabled
Normal file
@@ -0,0 +1,477 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Implementation Files Checker
|
||||
Scans src/fl/ and src/fx/ directories for *.hpp and *.cpp.hpp files.
|
||||
Provides statistics and can verify inclusion in the all-source build.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Set
|
||||
|
||||
|
||||
# Get project root directory
|
||||
PROJECT_ROOT = Path(__file__).parent.parent.parent
|
||||
SRC_ROOT = PROJECT_ROOT / "src"
|
||||
FL_DIR = SRC_ROOT / "fl"
|
||||
FX_DIR = SRC_ROOT / "fx"
|
||||
ALL_SOURCE_BUILD_FILE = SRC_ROOT / "fastled_compile.hpp.cpp"
|
||||
|
||||
# Hierarchical compile files
|
||||
HIERARCHICAL_FILES = [
|
||||
SRC_ROOT / "fl" / "fl_compile.hpp",
|
||||
SRC_ROOT / "fx" / "fx_compile.hpp",
|
||||
SRC_ROOT / "sensors" / "sensors_compile.hpp",
|
||||
SRC_ROOT / "platforms" / "platforms_compile.hpp",
|
||||
SRC_ROOT / "third_party" / "third_party_compile.hpp",
|
||||
SRC_ROOT / "src_compile.hpp",
|
||||
]
|
||||
|
||||
# Detect if running in CI/test environment for ASCII-only output
|
||||
USE_ASCII_ONLY = (
|
||||
os.environ.get("FASTLED_CI_NO_INTERACTIVE") == "true"
|
||||
or os.environ.get("GITHUB_ACTIONS") == "true"
|
||||
or os.environ.get("CI") == "true"
|
||||
)
|
||||
|
||||
|
||||
def collect_files_by_type(directory: Path) -> Dict[str, List[Path]]:
|
||||
"""Collect files by type (.hpp vs .cpp.hpp) from a directory.
|
||||
|
||||
Args:
|
||||
directory: Directory to scan
|
||||
|
||||
Returns:
|
||||
Dictionary with 'hpp' and 'cpp_hpp' keys containing lists of files
|
||||
"""
|
||||
files = {"hpp": [], "cpp_hpp": []}
|
||||
|
||||
if not directory.exists():
|
||||
print(f"Warning: Directory {directory} does not exist")
|
||||
return files
|
||||
|
||||
# Recursively find all .hpp and .cpp.hpp files
|
||||
for file_path in directory.rglob("*.hpp"):
|
||||
if file_path.name.endswith(".cpp.hpp"):
|
||||
files["cpp_hpp"].append(file_path)
|
||||
else:
|
||||
files["hpp"].append(file_path)
|
||||
|
||||
return files
|
||||
|
||||
|
||||
def get_all_source_build_includes() -> Set[str]:
|
||||
"""Extract the list of #include statements from the all-source build files.
|
||||
|
||||
This function handles the hierarchical structure by checking:
|
||||
1. The main all-source build file (fastled_compile.hpp.cpp)
|
||||
2. All hierarchical module compile files (fl_compile.hpp, fx_compile.hpp, etc.)
|
||||
|
||||
Returns:
|
||||
Set of included file paths (relative to src/)
|
||||
"""
|
||||
includes = set()
|
||||
|
||||
# Check main all-source build file
|
||||
if not ALL_SOURCE_BUILD_FILE.exists():
|
||||
print(f"Warning: All-source build file {ALL_SOURCE_BUILD_FILE} does not exist")
|
||||
return includes
|
||||
|
||||
# Function to extract includes from a file
|
||||
def extract_includes_from_file(file_path: Path) -> Set[str]:
|
||||
file_includes = set()
|
||||
if not file_path.exists():
|
||||
return file_includes
|
||||
|
||||
try:
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
# Look for #include statements
|
||||
if line.startswith('#include "') and line.endswith('"'):
|
||||
# Extract the include path
|
||||
include_path = line[10:-1] # Remove '#include "' and '"'
|
||||
file_includes.add(include_path)
|
||||
except Exception as e:
|
||||
print(f"Error reading file {file_path}: {e}")
|
||||
|
||||
return file_includes
|
||||
|
||||
# Extract includes from main file
|
||||
includes.update(extract_includes_from_file(ALL_SOURCE_BUILD_FILE))
|
||||
|
||||
# Extract includes from all hierarchical files
|
||||
for hierarchical_file in HIERARCHICAL_FILES:
|
||||
if hierarchical_file.exists():
|
||||
hierarchical_includes = extract_includes_from_file(hierarchical_file)
|
||||
includes.update(hierarchical_includes)
|
||||
|
||||
return includes
|
||||
|
||||
|
||||
def check_inclusion_in_all_source_build(
|
||||
files: Dict[str, List[Path]], base_dir: Path
|
||||
) -> Dict[str, Dict[str, bool]]:
|
||||
"""Check which implementation files are included in the all-source build.
|
||||
|
||||
Args:
|
||||
files: Dictionary of files by type
|
||||
base_dir: Base directory (fl or fx) for relative path calculation
|
||||
|
||||
Returns:
|
||||
Dictionary mapping file types to dictionaries of file -> included status
|
||||
"""
|
||||
all_source_includes = get_all_source_build_includes()
|
||||
|
||||
inclusion_status = {"hpp": {}, "cpp_hpp": {}}
|
||||
|
||||
for file_type, file_list in files.items():
|
||||
if file_type == "cpp_hpp": # Only check .cpp.hpp files for inclusion
|
||||
for file_path in file_list:
|
||||
# Calculate relative path from src/
|
||||
relative_path = file_path.relative_to(SRC_ROOT)
|
||||
relative_path_str = str(relative_path).replace(
|
||||
"\\", "/"
|
||||
) # Normalize path separators
|
||||
|
||||
# Check if this file is included
|
||||
is_included = relative_path_str in all_source_includes
|
||||
inclusion_status[file_type][str(file_path)] = is_included
|
||||
|
||||
return inclusion_status
|
||||
|
||||
|
||||
def print_file_list(
|
||||
files: List[Path], title: str, base_dir: Path, show_relative: bool = True
|
||||
):
|
||||
"""Print a formatted list of files.
|
||||
|
||||
Args:
|
||||
files: List of file paths
|
||||
title: Title for the section
|
||||
base_dir: Base directory for relative path calculation
|
||||
show_relative: Whether to show relative paths
|
||||
"""
|
||||
print(f"\n{title} ({len(files)} files):")
|
||||
print("-" * (len(title) + 20))
|
||||
|
||||
if not files:
|
||||
print(" (none)")
|
||||
return
|
||||
|
||||
# Sort files for consistent output
|
||||
sorted_files = sorted(files, key=lambda p: str(p))
|
||||
|
||||
for i, file_path in enumerate(sorted_files, 1):
|
||||
if show_relative:
|
||||
rel_path = file_path.relative_to(base_dir)
|
||||
print(f" {i:2d}. {rel_path}")
|
||||
else:
|
||||
print(f" {i:2d}. {file_path.name}")
|
||||
|
||||
|
||||
def print_inclusion_report(
|
||||
inclusion_status: Dict[str, Dict[str, bool]], base_dir: Path
|
||||
):
|
||||
"""Print a report of which implementation files are included in all-source build.
|
||||
|
||||
Args:
|
||||
inclusion_status: Dictionary of inclusion status by file type
|
||||
base_dir: Base directory for relative path calculation
|
||||
"""
|
||||
cpp_hpp_status = inclusion_status.get("cpp_hpp", {})
|
||||
|
||||
if not cpp_hpp_status:
|
||||
print("\nNo .cpp.hpp files found to check for inclusion")
|
||||
return
|
||||
|
||||
included_files = [path for path, included in cpp_hpp_status.items() if included]
|
||||
missing_files = [path for path, included in cpp_hpp_status.items() if not included]
|
||||
|
||||
print("\nALL-SOURCE BUILD INCLUSION STATUS:")
|
||||
print("=" * 50)
|
||||
|
||||
# Use ASCII or Unicode symbols based on environment
|
||||
check_symbol = "[+]" if USE_ASCII_ONLY else "✅"
|
||||
cross_symbol = "[-]" if USE_ASCII_ONLY else "❌"
|
||||
|
||||
print(f"{check_symbol} Included in all-source build: {len(included_files)}")
|
||||
if included_files:
|
||||
for file_path in sorted(included_files):
|
||||
rel_path = Path(file_path).relative_to(SRC_ROOT)
|
||||
print(f" {check_symbol} {rel_path}")
|
||||
|
||||
print(f"\n{cross_symbol} Missing from all-source build: {len(missing_files)}")
|
||||
if missing_files:
|
||||
for file_path in sorted(missing_files):
|
||||
rel_path = Path(file_path).relative_to(SRC_ROOT)
|
||||
print(f" {cross_symbol} {rel_path}")
|
||||
|
||||
|
||||
def generate_summary_report(
|
||||
fl_files: Dict[str, List[Path]], fx_files: Dict[str, List[Path]]
|
||||
) -> Dict:
|
||||
"""Generate a summary report with statistics.
|
||||
|
||||
Args:
|
||||
fl_files: Files found in fl/ directory
|
||||
fx_files: Files found in fx/ directory
|
||||
|
||||
Returns:
|
||||
Dictionary containing summary statistics
|
||||
"""
|
||||
summary = {
|
||||
"fl_directory": {
|
||||
"hpp_files": len(fl_files["hpp"]),
|
||||
"cpp_hpp_files": len(fl_files["cpp_hpp"]),
|
||||
"total_files": len(fl_files["hpp"]) + len(fl_files["cpp_hpp"]),
|
||||
},
|
||||
"fx_directory": {
|
||||
"hpp_files": len(fx_files["hpp"]),
|
||||
"cpp_hpp_files": len(fx_files["cpp_hpp"]),
|
||||
"total_files": len(fx_files["hpp"]) + len(fx_files["cpp_hpp"]),
|
||||
},
|
||||
"totals": {
|
||||
"hpp_files": len(fl_files["hpp"]) + len(fx_files["hpp"]),
|
||||
"cpp_hpp_files": len(fl_files["cpp_hpp"]) + len(fx_files["cpp_hpp"]),
|
||||
"total_files": len(fl_files["hpp"])
|
||||
+ len(fl_files["cpp_hpp"])
|
||||
+ len(fx_files["hpp"])
|
||||
+ len(fx_files["cpp_hpp"]),
|
||||
},
|
||||
}
|
||||
|
||||
return summary
|
||||
|
||||
|
||||
def print_summary_report(summary: Dict):
|
||||
"""Print a formatted summary report.
|
||||
|
||||
Args:
|
||||
summary: Summary statistics dictionary
|
||||
"""
|
||||
print("\n" + "=" * 80)
|
||||
print("IMPLEMENTATION FILES SUMMARY REPORT")
|
||||
print("=" * 80)
|
||||
|
||||
# Use ASCII or Unicode symbols based on environment
|
||||
folder_symbol = "[DIR]" if USE_ASCII_ONLY else "📁"
|
||||
chart_symbol = "[STATS]" if USE_ASCII_ONLY else "📊"
|
||||
ratio_symbol = "[RATIO]" if USE_ASCII_ONLY else "📈"
|
||||
|
||||
print(f"\n{folder_symbol} FL DIRECTORY ({FL_DIR.relative_to(PROJECT_ROOT)}):")
|
||||
print(
|
||||
f" Header files (.hpp): {summary['fl_directory']['hpp_files']:3d}"
|
||||
)
|
||||
print(
|
||||
f" Implementation files (.cpp.hpp): {summary['fl_directory']['cpp_hpp_files']:3d}"
|
||||
)
|
||||
print(
|
||||
f" Total files: {summary['fl_directory']['total_files']:3d}"
|
||||
)
|
||||
|
||||
print(f"\n{folder_symbol} FX DIRECTORY ({FX_DIR.relative_to(PROJECT_ROOT)}):")
|
||||
print(
|
||||
f" Header files (.hpp): {summary['fx_directory']['hpp_files']:3d}"
|
||||
)
|
||||
print(
|
||||
f" Implementation files (.cpp.hpp): {summary['fx_directory']['cpp_hpp_files']:3d}"
|
||||
)
|
||||
print(
|
||||
f" Total files: {summary['fx_directory']['total_files']:3d}"
|
||||
)
|
||||
|
||||
print(f"\n{chart_symbol} TOTALS:")
|
||||
print(f" Header files (.hpp): {summary['totals']['hpp_files']:3d}")
|
||||
print(
|
||||
f" Implementation files (.cpp.hpp): {summary['totals']['cpp_hpp_files']:3d}"
|
||||
)
|
||||
print(f" Total files: {summary['totals']['total_files']:3d}")
|
||||
|
||||
# Calculate ratios
|
||||
total_hpp = summary["totals"]["hpp_files"]
|
||||
total_cpp_hpp = summary["totals"]["cpp_hpp_files"]
|
||||
|
||||
if total_hpp > 0:
|
||||
impl_ratio = (total_cpp_hpp / total_hpp) * 100
|
||||
print(f"\n{ratio_symbol} IMPLEMENTATION RATIO:")
|
||||
print(f" Implementation files per header: {impl_ratio:.1f}%")
|
||||
print(f" ({total_cpp_hpp} .cpp.hpp files for {total_hpp} .hpp files)")
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function to run the implementation files checker."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Check *.hpp and *.cpp.hpp files in fl/ and fx/ directories"
|
||||
)
|
||||
parser.add_argument("--list", action="store_true", help="List all files found")
|
||||
parser.add_argument(
|
||||
"--check-inclusion",
|
||||
action="store_true",
|
||||
help="Check which .cpp.hpp files are included in all-source build",
|
||||
)
|
||||
parser.add_argument("--json", action="store_true", help="Output summary as JSON")
|
||||
parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
|
||||
parser.add_argument(
|
||||
"--ascii-only",
|
||||
action="store_true",
|
||||
help="Use ASCII-only output (no Unicode emoji)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--suppress-summary-on-100-percent",
|
||||
action="store_true",
|
||||
help="Suppress summary report when inclusion percentage is 100%",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Override USE_ASCII_ONLY if command line flag is set
|
||||
global USE_ASCII_ONLY
|
||||
if args.ascii_only:
|
||||
USE_ASCII_ONLY = True
|
||||
|
||||
# Collect files from both directories
|
||||
print("Scanning implementation files...")
|
||||
fl_files = collect_files_by_type(FL_DIR)
|
||||
fx_files = collect_files_by_type(FX_DIR)
|
||||
|
||||
# Generate summary
|
||||
summary = generate_summary_report(fl_files, fx_files)
|
||||
|
||||
# Define symbols once for all output modes
|
||||
search_symbol = "[SEARCH]" if USE_ASCII_ONLY else "🔍"
|
||||
stats_symbol = "[STATS]" if USE_ASCII_ONLY else "📊"
|
||||
config_symbol = "[CONFIG]" if USE_ASCII_ONLY else "🔧"
|
||||
|
||||
# Calculate inclusion percentage to determine if we should suppress summary
|
||||
should_suppress_summary = False
|
||||
if args.suppress_summary_on_100_percent:
|
||||
all_cpp_hpp_files = fl_files["cpp_hpp"] + fx_files["cpp_hpp"]
|
||||
all_source_includes = get_all_source_build_includes()
|
||||
|
||||
included_count = 0
|
||||
for file_path in all_cpp_hpp_files:
|
||||
relative_path = file_path.relative_to(SRC_ROOT)
|
||||
relative_path_str = str(relative_path).replace("\\", "/")
|
||||
if relative_path_str in all_source_includes:
|
||||
included_count += 1
|
||||
|
||||
total_impl_files = len(all_cpp_hpp_files)
|
||||
if total_impl_files > 0:
|
||||
inclusion_percentage = (included_count / total_impl_files) * 100
|
||||
should_suppress_summary = inclusion_percentage >= 100.0
|
||||
|
||||
# Output based on requested format
|
||||
if args.json:
|
||||
# Add file lists to summary for JSON output
|
||||
summary["fl_files"] = {
|
||||
"hpp": [str(p.relative_to(SRC_ROOT)) for p in fl_files["hpp"]],
|
||||
"cpp_hpp": [str(p.relative_to(SRC_ROOT)) for p in fl_files["cpp_hpp"]],
|
||||
}
|
||||
summary["fx_files"] = {
|
||||
"hpp": [str(p.relative_to(SRC_ROOT)) for p in fx_files["hpp"]],
|
||||
"cpp_hpp": [str(p.relative_to(SRC_ROOT)) for p in fx_files["cpp_hpp"]],
|
||||
}
|
||||
print(json.dumps(summary, indent=2))
|
||||
return
|
||||
|
||||
# Print summary report only if not suppressed
|
||||
if not should_suppress_summary:
|
||||
print_summary_report(summary)
|
||||
else:
|
||||
print("Summary report suppressed: 100% inclusion coverage achieved")
|
||||
|
||||
# List files if requested
|
||||
if args.list:
|
||||
print("\n" + "=" * 80)
|
||||
print("DETAILED FILE LISTINGS")
|
||||
print("=" * 80)
|
||||
|
||||
# FL directory files
|
||||
print_file_list(fl_files["hpp"], "FL Header Files (.hpp)", FL_DIR)
|
||||
print_file_list(
|
||||
fl_files["cpp_hpp"], "FL Implementation Files (.cpp.hpp)", FL_DIR
|
||||
)
|
||||
|
||||
# FX directory files
|
||||
print_file_list(fx_files["hpp"], "FX Header Files (.hpp)", FX_DIR)
|
||||
print_file_list(
|
||||
fx_files["cpp_hpp"], "FX Implementation Files (.cpp.hpp)", FX_DIR
|
||||
)
|
||||
|
||||
# Check inclusion in all-source build if requested
|
||||
if args.check_inclusion:
|
||||
# Only show inclusion check if not suppressing or if we don't have 100% coverage
|
||||
if not should_suppress_summary:
|
||||
print("\n" + "=" * 80)
|
||||
print("ALL-SOURCE BUILD INCLUSION CHECK")
|
||||
print("=" * 80)
|
||||
|
||||
fl_inclusion = check_inclusion_in_all_source_build(fl_files, FL_DIR)
|
||||
fx_inclusion = check_inclusion_in_all_source_build(fx_files, FX_DIR)
|
||||
|
||||
print(f"\n{search_symbol} FL DIRECTORY INCLUSION:")
|
||||
print_inclusion_report(fl_inclusion, FL_DIR)
|
||||
|
||||
print(f"\n{search_symbol} FX DIRECTORY INCLUSION:")
|
||||
print_inclusion_report(fx_inclusion, FX_DIR)
|
||||
|
||||
# Overall inclusion statistics
|
||||
all_cpp_hpp_files = fl_files["cpp_hpp"] + fx_files["cpp_hpp"]
|
||||
all_source_includes = get_all_source_build_includes()
|
||||
|
||||
included_count = 0
|
||||
for file_path in all_cpp_hpp_files:
|
||||
relative_path = file_path.relative_to(SRC_ROOT)
|
||||
relative_path_str = str(relative_path).replace("\\", "/")
|
||||
if relative_path_str in all_source_includes:
|
||||
included_count += 1
|
||||
|
||||
total_impl_files = len(all_cpp_hpp_files)
|
||||
if total_impl_files > 0:
|
||||
inclusion_percentage = (included_count / total_impl_files) * 100
|
||||
print(f"\n{stats_symbol} OVERALL INCLUSION STATISTICS:")
|
||||
print(f" Total .cpp.hpp files found: {total_impl_files}")
|
||||
print(f" Included in all-source build: {included_count}")
|
||||
print(f" Inclusion percentage: {inclusion_percentage:.1f}%")
|
||||
|
||||
# Always check for missing files and exit with error if any are missing
|
||||
all_cpp_hpp_files = fl_files["cpp_hpp"] + fx_files["cpp_hpp"]
|
||||
all_source_includes = get_all_source_build_includes()
|
||||
|
||||
included_count = 0
|
||||
for file_path in all_cpp_hpp_files:
|
||||
relative_path = file_path.relative_to(SRC_ROOT)
|
||||
relative_path_str = str(relative_path).replace("\\", "/")
|
||||
if relative_path_str in all_source_includes:
|
||||
included_count += 1
|
||||
|
||||
total_impl_files = len(all_cpp_hpp_files)
|
||||
total_missing = total_impl_files - included_count
|
||||
if total_missing > 0:
|
||||
# Print an explicit error message before exiting
|
||||
error_symbol = "[ERROR]" if USE_ASCII_ONLY else "🚨"
|
||||
print(
|
||||
f"\n{error_symbol} {total_missing} implementation file(s) are missing from the all-source build!"
|
||||
)
|
||||
print(" Failing script due to incomplete all-source build inclusion.")
|
||||
import sys
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
if args.verbose:
|
||||
print(f"\n{config_symbol} CONFIGURATION:")
|
||||
print(f" Project root: {PROJECT_ROOT}")
|
||||
print(f" FL directory: {FL_DIR}")
|
||||
print(f" FX directory: {FX_DIR}")
|
||||
print(f" All-source build file: {ALL_SOURCE_BUILD_FILE}")
|
||||
print(" Hierarchical compile files:")
|
||||
for hfile in HIERARCHICAL_FILES:
|
||||
status = "✓" if hfile.exists() else "✗"
|
||||
print(f" {status} {hfile.relative_to(PROJECT_ROOT)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user