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)