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

183 lines
5.7 KiB
Python

#!/usr/bin/env python3
"""
SCCACHE Configuration for FastLED Builds
Provides sccache (distributed compiler cache) support for faster compilation.
SCCACHE is faster and more reliable than ccache, especially for CI/CD environments.
"""
import os
import platform
import shutil
import subprocess
from pathlib import Path
from typing import Any, Protocol
class PlatformIOEnv(Protocol):
"""Type hint for PlatformIO environment object."""
def get(self, key: str) -> str | None:
"""Get environment variable value."""
...
def Replace(self, **kwargs: Any) -> None:
"""Replace environment variables."""
...
def is_sccache_available() -> bool:
"""Check if sccache is available in the system."""
try:
subprocess.run(["sccache", "--version"], capture_output=True, check=True)
return True
except (subprocess.CalledProcessError, FileNotFoundError):
return False
def get_sccache_path() -> str | None:
"""Get the full path to sccache executable."""
# Use shutil.which for cross-platform executable finding
sccache_path = shutil.which("sccache")
if sccache_path:
return sccache_path
# Additional Windows-specific paths
if platform.system() == "Windows":
additional_paths = [
"C:\\ProgramData\\chocolatey\\bin\\sccache.exe",
os.path.expanduser("~\\scoop\\shims\\sccache.exe"),
os.path.expanduser("~\\.cargo\\bin\\sccache.exe"),
]
for path in additional_paths:
if os.path.exists(path):
return path
return None
def configure_sccache(env: PlatformIOEnv) -> None:
"""Configure SCCACHE for the build environment."""
if not is_sccache_available():
print("SCCACHE is not available. Skipping SCCACHE configuration.")
return
sccache_path = get_sccache_path()
if not sccache_path:
print("Could not find SCCACHE executable. Skipping SCCACHE configuration.")
return
print(f"Found SCCACHE at: {sccache_path}")
# Set up SCCACHE environment variables
project_dir = env.get("PROJECT_DIR")
if project_dir is None:
project_dir = os.getcwd()
# Use board-specific sccache directory if PIOENV (board environment) is available
board_name = env.get("PIOENV")
if board_name:
sccache_dir = os.path.join(project_dir, ".sccache", board_name)
else:
sccache_dir = os.path.join(project_dir, ".sccache", "default")
# Create sccache directory
Path(sccache_dir).mkdir(parents=True, exist_ok=True)
print(f"Using board-specific SCCACHE directory: {sccache_dir}")
# Configure SCCACHE environment variables
os.environ["SCCACHE_DIR"] = sccache_dir
os.environ["SCCACHE_CACHE_SIZE"] = "2G" # Larger cache for better hit rates
# Optional: Configure distributed caching (Redis/Memcached) if available
# This can be enabled by setting environment variables before build:
# export SCCACHE_REDIS=redis://localhost:6379
# export SCCACHE_MEMCACHED=localhost:11211
# Configure compression for better storage efficiency
if platform.system() != "Windows":
# Only set on Unix-like systems where it's more reliable
os.environ["SCCACHE_LOG"] = "info"
# Get current compiler paths
original_cc = env.get("CC")
original_cxx = env.get("CXX")
# Note: For PlatformIO, we don't need to manually wrap CC/CXX here
# Instead, we'll use build_flags to wrap the compiler commands
# This is handled in the Board configuration via extra_scripts
print(f"SCCACHE configuration completed")
print(f"Cache directory: {sccache_dir}")
print(f"Cache size limit: 2G")
# Show SCCACHE stats if available
try:
result = subprocess.run(
[sccache_path, "--show-stats"], capture_output=True, text=True, check=False
)
if result.returncode == 0:
print("SCCACHE Statistics:")
print(result.stdout)
except Exception:
pass # Don't fail build if stats aren't available
def get_sccache_wrapper_script_content() -> str:
"""Generate content for sccache wrapper script for PlatformIO extra_scripts."""
return '''
# SCCACHE wrapper script for PlatformIO builds
# This script automatically wraps compiler commands with sccache
Import("env")
import os
import shutil
from pathlib import Path
def setup_sccache_wrapper():
"""Setup sccache wrapper for compiler commands."""
# Check if sccache is available
sccache_path = shutil.which("sccache")
if not sccache_path:
print("SCCACHE not found, compilation will proceed without caching")
return
print(f"Setting up SCCACHE wrapper: {sccache_path}")
# Get current build environment
project_dir = env.get("PROJECT_DIR", os.getcwd())
board_name = env.get("PIOENV", "default")
# Setup sccache directory
sccache_dir = os.path.join(project_dir, ".sccache", board_name)
Path(sccache_dir).mkdir(parents=True, exist_ok=True)
# Configure sccache environment
os.environ["SCCACHE_DIR"] = sccache_dir
os.environ["SCCACHE_CACHE_SIZE"] = "2G"
# Wrap compiler commands
current_cc = env.get("CC", "gcc")
current_cxx = env.get("CXX", "g++")
# Only wrap if not already wrapped
if "sccache" not in current_cc:
env.Replace(
CC=f'"{sccache_path}" {current_cc}',
CXX=f'"{sccache_path}" {current_cxx}',
)
print(f"Wrapped CC with SCCACHE: {env.get('CC')}")
print(f"Wrapped CXX with SCCACHE: {env.get('CXX')}")
# Setup sccache wrapper
setup_sccache_wrapper()
'''
# This script can be executed directly by PlatformIO as an extra_script
if __name__ == "__main__":
# For direct execution by PlatformIO extra_scripts
pass