175 lines
5.8 KiB
Python
Executable File
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) |