125 lines
4.1 KiB
Python
125 lines
4.1 KiB
Python
"""Configure CCACHE for PlatformIO builds."""
|
|
|
|
import os
|
|
import platform
|
|
import subprocess
|
|
from pathlib import Path
|
|
from typing import Any, Protocol
|
|
|
|
|
|
# ruff: noqa: F821
|
|
# pyright: reportUndefinedVariable=false
|
|
Import("env") # type: ignore # Import is provided by PlatformIO
|
|
|
|
|
|
class PlatformIOEnv(Protocol):
|
|
"""Type information for PlatformIO environment."""
|
|
|
|
def get(self, key: str, default: str | None = None) -> str | None:
|
|
"""Get a value from the environment."""
|
|
...
|
|
|
|
def Replace(self, **kwargs: Any) -> None:
|
|
"""Replace environment variables."""
|
|
...
|
|
|
|
|
|
def is_ccache_available() -> bool:
|
|
"""Check if ccache is available in the system."""
|
|
try:
|
|
subprocess.run(["ccache", "--version"], capture_output=True, check=True)
|
|
return True
|
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
return False
|
|
|
|
|
|
def get_ccache_path() -> str | None:
|
|
"""Get the full path to ccache executable."""
|
|
if platform.system() == "Windows":
|
|
# On Windows, look in chocolatey's bin directory
|
|
ccache_paths = [
|
|
"C:\\ProgramData\\chocolatey\\bin\\ccache.exe",
|
|
os.path.expanduser("~\\scoop\\shims\\ccache.exe"),
|
|
]
|
|
for path in ccache_paths:
|
|
if os.path.exists(path):
|
|
return path
|
|
else:
|
|
# On Unix-like systems, use which to find ccache
|
|
try:
|
|
return subprocess.check_output(["which", "ccache"]).decode().strip()
|
|
except subprocess.CalledProcessError:
|
|
pass
|
|
return None
|
|
|
|
|
|
def configure_ccache(env: PlatformIOEnv) -> None: # type: ignore # env is provided by PlatformIO
|
|
"""Configure CCACHE for the build environment."""
|
|
if not is_ccache_available():
|
|
print("CCACHE is not available. Skipping CCACHE configuration.")
|
|
return
|
|
|
|
ccache_path = get_ccache_path()
|
|
if not ccache_path:
|
|
print("Could not find CCACHE executable. Skipping CCACHE configuration.")
|
|
return
|
|
|
|
print(f"Found CCACHE at: {ccache_path}")
|
|
|
|
# Set up CCACHE environment variables if not already set
|
|
if "CCACHE_DIR" not in os.environ:
|
|
project_dir = env.get("PROJECT_DIR")
|
|
if project_dir is None:
|
|
project_dir = os.getcwd()
|
|
|
|
# Use board-specific ccache directory if PIOENV (board environment) is available
|
|
board_name = env.get("PIOENV")
|
|
if board_name:
|
|
ccache_dir = os.path.join(project_dir, ".ccache", board_name)
|
|
else:
|
|
ccache_dir = os.path.join(project_dir, ".ccache", "default")
|
|
|
|
os.environ["CCACHE_DIR"] = ccache_dir
|
|
Path(ccache_dir).mkdir(parents=True, exist_ok=True)
|
|
print(f"Using board-specific CCACHE directory: {ccache_dir}")
|
|
|
|
# Configure CCACHE for this build
|
|
project_dir = env.get("PROJECT_DIR")
|
|
if project_dir is None:
|
|
project_dir = os.getcwd()
|
|
os.environ["CCACHE_BASEDIR"] = project_dir
|
|
os.environ["CCACHE_COMPRESS"] = "true"
|
|
os.environ["CCACHE_COMPRESSLEVEL"] = "6"
|
|
os.environ["CCACHE_MAXSIZE"] = "400M"
|
|
|
|
# Wrap compiler commands with ccache
|
|
# STRICT: CC and CXX must be explicitly set - NO fallbacks allowed
|
|
original_cc = env.get("CC")
|
|
if not original_cc:
|
|
raise RuntimeError(
|
|
"CRITICAL: CC environment variable is required but not set. "
|
|
"Please set CC to the C compiler path (e.g., gcc, clang)."
|
|
)
|
|
original_cxx = env.get("CXX")
|
|
if not original_cxx:
|
|
raise RuntimeError(
|
|
"CRITICAL: CXX environment variable is required but not set. "
|
|
"Please set CXX to the C++ compiler path (e.g., g++, clang++)."
|
|
)
|
|
|
|
# Don't wrap if already wrapped
|
|
if original_cc is not None and "ccache" not in original_cc:
|
|
env.Replace(
|
|
CC=f"{ccache_path} {original_cc}",
|
|
CXX=f"{ccache_path} {original_cxx}",
|
|
)
|
|
print(f"Wrapped CC: {env.get('CC')}")
|
|
print(f"Wrapped CXX: {env.get('CXX')}")
|
|
|
|
# Show CCACHE stats
|
|
subprocess.run([ccache_path, "--show-stats"], check=False)
|
|
|
|
|
|
# ruff: noqa: F821
|
|
configure_ccache(env) # type: ignore # env is provided by PlatformIO
|