fester/backend/compiler_env.py

175 lines
5.8 KiB
Python
Executable File

import os
from backend.cache_local import hash_dict
# BTC integration (best-effort -- module may not be present in minimal installs)
try:
from backend.toolchain.btc import (
probe_btc,
merge_compiler_env,
verify_btc_stamp,
)
HAS_BTC = True
except Exception:
HAS_BTC = False
probe_btc = None
merge_compiler_env = None
verify_btc_stamp = None
# Chroot provider system (generic + distro-specific backends)
try:
from backend.toolchain.chroot import (
get_provider,
list_providers,
)
HAS_CHROOT = True
except Exception:
HAS_CHROOT = False
get_provider = None
list_providers = None
def build_ccache_env(project):
"""
Configure compiler caching layer safely.
"""
env = {}
# ----------------------------
# enable ccache
# ----------------------------
env["USE_CCACHE"] = "1"
env["CCACHE_DIR"] = os.environ.get("FESTER_CCACHE_DIR", "/var/cache/ccache")
os.makedirs(env["CCACHE_DIR"], exist_ok=True)
# ----------------------------
# distcc + ccache integration
# critical ordering fix
# ----------------------------
env["CC"] = "ccache gcc"
env["CXX"] = "ccache g++"
# ensure distcc wraps AFTER ccache
env["CCACHE_PREFIX"] = "distcc"
# ----------------------------
# tuning for distributed builds
# ----------------------------
env["CCACHE_COMPRESS"] = "1"
env["CCACHE_COMPRESSLEVEL"] = "5"
env["CCACHE_MAXSIZE"] = os.environ.get("FESTER_CCACHE_MAXSIZE", "20G")
# ----------------------------
# project-specific salt
# ensures correct cache isolation
# ----------------------------
env["CCACHE_SLOPPINESS"] = "time_macros,locale"
env["FESTER_CCACHE_KEY"] = hash_dict(project.get("build_env", {}))
return env
def build_project_env(project, node_config=None):
"""Build the full compiler environment for a project on a given node.
Toolchain selection priority (first match wins):
1. Per-node ``toolchain.provider`` override in node_config
2. Global ``toolchain.provider`` in config.yaml
3. BTC (if btc.enabled is true and a golden image exists)
4. Chroot provider (if toolchain.provider is set)
5. Native host gcc fallback
Returns a dict with all environment variables plus a
``toolchain_source`` key whose value is one of:
"native" -- host gcc (ccache + distcc)
"native-cross" -- host cross-compiler detected
"btc" -- BTC sovereign toolchain is active
"btc-fallback-gcc" -- BTC probed but not usable, fell back to gcc
"chroot" -- generic distro chroot native
"chroot-cross" -- generic distro chroot cross-compiler
"sourcemage" -- SourceMage GL chroot
"gentoo" -- Gentoo stage3 chroot
"lede" -- LEDE/OpenWrt SDK
"lunar" -- Lunar Linux chroot
"buildroot" -- Buildroot SDK
"gcc" -- fallback (same as native)
"""
node_config = node_config or {}
from backend.config import CONFIG
# --- 1. Check per-node toolchain override ---
node_toolchain = node_config.get("toolchain", {})
if isinstance(node_toolchain, dict):
node_provider = node_toolchain.get("provider", "")
if node_provider and HAS_CHROOT and get_provider:
return _resolve_provider(node_provider, node_toolchain, project)
# --- 2. Check global toolchain config ---
global_toolchain = CONFIG.get("toolchain", {})
if isinstance(global_toolchain, dict):
global_provider = global_toolchain.get("provider", "")
if global_provider and HAS_CHROOT and get_provider:
return _resolve_provider(global_provider, global_toolchain, project)
# --- 3. BTC (if enabled and available) ---
if HAS_BTC and merge_compiler_env is not None:
btc_cfg = CONFIG.get("btc", {})
btc_enabled = btc_cfg.get("enabled", True)
if btc_enabled:
btc_root = btc_cfg.get("root", "/opt/BTC")
return merge_compiler_env(project, node_config, btc_root=btc_root)
# --- 4. Fallback: native host gcc ---
env = build_ccache_env(project)
env["toolchain_source"] = "gcc"
# If the host has cross-compilers and a target_arch is requested,
# try to use them (via the native provider).
if HAS_CHROOT and get_provider:
target_arch = node_config.get("toolchain", {}).get("target_arch", "") if isinstance(node_config.get("toolchain"), dict) else ""
if not target_arch:
target_arch = global_toolchain.get("target_arch", "")
if target_arch:
native_spec = get_provider("native")
if native_spec:
return native_spec.build_env({"target_arch": target_arch})
return env
def _resolve_provider(
provider_name: str,
provider_config: dict,
project: dict,
) -> dict:
"""Resolve a toolchain provider and build its environment.
Probes the provider first; if unavailable, falls back to
native host gcc with a warning.
"""
spec = get_provider(provider_name)
if spec is None:
import logging
logging.getLogger(__name__).warning(
"toolchain: unknown provider '%s', falling back to native gcc",
provider_name,
)
env = build_ccache_env(project)
env["toolchain_source"] = "gcc"
return env
probe = spec.probe(provider_config)
if not probe.get("available", False):
import logging
logging.getLogger(__name__).warning(
"toolchain: provider '%s' not available (%s), falling back to native gcc",
provider_name,
probe.get("reason", "probe failed"),
)
env = build_ccache_env(project)
env["toolchain_source"] = "gcc"
return env
return spec.build_env(provider_config)