183 lines
5.7 KiB
Python
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
|