mirror of
https://github.com/browseros-ai/BrowserOS.git
synced 2026-05-18 19:16:22 +00:00
`release.linux.yaml` now declares `architecture: [x64, arm64]` and the runner loops the entire pipeline once per architecture. depot_tools fetches both Linux sysroots automatically — `git_setup` idempotently ensures `target_cpus = ['x64', 'arm64']` is in `.gclient` before `gclient sync`, so cross-compiling arm64 from an x64 host just works. The resolver returns `List[Context]` (single-element for the common single-arch case), and `build/cli/build.py` loops `execute_pipeline` over the per-arch contexts. Modules stay 100% arch-agnostic — no new orchestration module, no new YAML schema beyond the list form. Also fix a cross-compile bug in `build/modules/package/linux.py`: the appimagetool binary must match the BUILD machine's arch (it executes locally), not the target arch. Split into a host-keyed `LINUX_HOST_APPIMAGETOOL` lookup vs the existing target-keyed `LINUX_ARCHITECTURE_CONFIG`. Target arch is still passed to appimagetool via the `ARCH` env var. - build/common/resolver.py: scalar OR list `architecture` -> List[Context] - build/cli/build.py: loop pipeline per arch, log multi-arch headers - build/config/release.linux.yaml: `architecture: [x64, arm64]` - build/modules/setup/git.py: idempotent `target_cpus` edit on Linux - build/modules/package/linux.py: host vs target appimagetool split - build/modules/package/linux_test.py: cover the host/target split
358 lines
11 KiB
Python
Generated
358 lines
11 KiB
Python
Generated
#!/usr/bin/env python3
|
|
"""
|
|
Configuration resolver - single source of truth for all config resolution
|
|
|
|
Two mutually exclusive modes:
|
|
1. CONFIG mode (--config FILE): YAML controls everything
|
|
2. DIRECT mode (no --config): CLI args > Env > Defaults
|
|
|
|
Precedence (CONFIG mode):
|
|
- YAML (authoritative)
|
|
- Env vars (only for secrets/credentials via EnvConfig)
|
|
- Error if required fields missing
|
|
|
|
Precedence (DIRECT mode):
|
|
- CLI args (explicit, Typer defaults must be None)
|
|
- Environment variables (CHROMIUM_SRC, ARCH)
|
|
- Hardcoded defaults
|
|
|
|
This centralizes ALL configuration resolution in one place.
|
|
"""
|
|
|
|
from pathlib import Path
|
|
from typing import Optional, List, Dict, Any, Tuple
|
|
|
|
from .context import Context
|
|
from .env import EnvConfig
|
|
from .utils import get_platform_arch, log_info
|
|
|
|
VALID_ARCHITECTURES = {"x64", "arm64", "universal"}
|
|
|
|
|
|
def resolve_config(
|
|
cli_args: Dict[str, Any],
|
|
yaml_config: Optional[Dict[str, Any]] = None,
|
|
) -> List[Context]:
|
|
"""Resolve build configuration - single entry point.
|
|
|
|
Args:
|
|
cli_args: Dictionary of CLI arguments (all values should be None if not provided)
|
|
yaml_config: Optional YAML configuration (triggers CONFIG mode)
|
|
|
|
Returns:
|
|
List of fully resolved Context objects. Single-element for the
|
|
common single-arch case; multi-element when YAML declares
|
|
`architecture: [x64, arm64]` (Linux multi-arch).
|
|
|
|
Raises:
|
|
ValueError: If required fields missing or invalid
|
|
|
|
Modes:
|
|
- CONFIG mode (yaml_config provided): YAML is authoritative
|
|
- DIRECT mode (no yaml_config): CLI > Env > Defaults
|
|
|
|
Note:
|
|
root_dir is always computed from package location via get_package_root(),
|
|
never from config or cwd.
|
|
"""
|
|
if yaml_config:
|
|
return _resolve_config_mode(yaml_config, cli_args)
|
|
else:
|
|
return _resolve_direct_mode(cli_args)
|
|
|
|
|
|
def _resolve_config_mode(
|
|
yaml_config: Dict[str, Any], cli_args: Dict[str, Any]
|
|
) -> List[Context]:
|
|
"""CONFIG MODE: YAML is base, CLI can override.
|
|
|
|
Args:
|
|
yaml_config: YAML configuration dictionary
|
|
cli_args: CLI arguments (can override YAML values)
|
|
|
|
Returns:
|
|
List of Contexts. One per architecture when YAML provides a list.
|
|
|
|
Raises:
|
|
ValueError: If required fields missing from both YAML and CLI
|
|
"""
|
|
build_section = yaml_config.get("build", {})
|
|
|
|
# chromium_src: CLI override > YAML > error
|
|
chromium_src_str = cli_args.get("chromium_src") or build_section.get("chromium_src")
|
|
if not chromium_src_str:
|
|
raise ValueError(
|
|
"CONFIG MODE: chromium_src required in YAML!\n"
|
|
"Add to your config:\n"
|
|
" build:\n"
|
|
" chromium_src: /path/to/chromium"
|
|
)
|
|
|
|
chromium_src = Path(chromium_src_str)
|
|
chromium_src_source = "cli" if cli_args.get("chromium_src") else "yaml"
|
|
|
|
# Validate chromium_src exists
|
|
if not chromium_src.exists():
|
|
raise ValueError(
|
|
f"CONFIG MODE: chromium_src does not exist: {chromium_src}\n"
|
|
f"Expected directory with Chromium source code"
|
|
)
|
|
|
|
# architecture: CLI override > YAML > platform default.
|
|
# YAML may be a string OR a list (e.g. [x64, arm64]) — list form runs
|
|
# the entire pipeline once per arch.
|
|
cli_arch = cli_args.get("arch")
|
|
yaml_arch = build_section.get("architecture") or build_section.get("arch")
|
|
|
|
if cli_arch:
|
|
architectures = [cli_arch]
|
|
arch_source = "cli"
|
|
elif yaml_arch is not None:
|
|
architectures = yaml_arch if isinstance(yaml_arch, list) else [yaml_arch]
|
|
arch_source = "yaml"
|
|
else:
|
|
architectures = [get_platform_arch()]
|
|
arch_source = "default"
|
|
log_info(
|
|
f"CONFIG MODE: Using platform default architecture: {architectures[0]}"
|
|
)
|
|
|
|
for arch in architectures:
|
|
if arch not in VALID_ARCHITECTURES:
|
|
raise ValueError(
|
|
f"CONFIG MODE: invalid architecture '{arch}'. "
|
|
f"Valid: {sorted(VALID_ARCHITECTURES)}"
|
|
)
|
|
|
|
# build_type: CLI override > YAML > debug
|
|
build_type = cli_args.get("build_type") or build_section.get("type", "debug")
|
|
build_type_source = "cli" if cli_args.get("build_type") else "yaml"
|
|
|
|
log_info(f"✓ CONFIG MODE: chromium_src={chromium_src} ({chromium_src_source})")
|
|
if len(architectures) > 1:
|
|
log_info(
|
|
f"✓ CONFIG MODE: architectures={architectures} ({arch_source}, multi-arch loop)"
|
|
)
|
|
else:
|
|
log_info(
|
|
f"✓ CONFIG MODE: architecture={architectures[0]} ({arch_source})"
|
|
)
|
|
log_info(f"✓ CONFIG MODE: build_type={build_type} ({build_type_source})")
|
|
|
|
return [
|
|
Context(
|
|
chromium_src=chromium_src,
|
|
architecture=arch,
|
|
build_type=build_type,
|
|
)
|
|
for arch in architectures
|
|
]
|
|
|
|
|
|
def _resolve_direct_mode(cli_args: Dict[str, Any]) -> List[Context]:
|
|
"""DIRECT MODE: CLI > Env > Defaults.
|
|
|
|
Args:
|
|
cli_args: CLI arguments (None if not provided by user)
|
|
|
|
Returns:
|
|
Single-element list with the resolved Context. DIRECT mode is
|
|
always single-arch (CLI --arch is a scalar).
|
|
|
|
Raises:
|
|
ValueError: If chromium_src not provided
|
|
"""
|
|
env = EnvConfig()
|
|
|
|
# chromium_src: CLI > Env > Error
|
|
chromium_src = cli_args.get("chromium_src") or env.chromium_src
|
|
if not chromium_src:
|
|
raise ValueError(
|
|
"DIRECT MODE: chromium_src required!\n"
|
|
"Provide via one of:\n"
|
|
" --chromium-src PATH\n"
|
|
" CHROMIUM_SRC environment variable"
|
|
)
|
|
|
|
chromium_src = Path(chromium_src)
|
|
|
|
# Validate chromium_src exists
|
|
if not chromium_src.exists():
|
|
raise ValueError(
|
|
f"DIRECT MODE: chromium_src does not exist: {chromium_src}\n"
|
|
f"Expected directory with Chromium source code"
|
|
)
|
|
|
|
# architecture: CLI > Env > Platform default
|
|
architecture = cli_args.get("arch") or env.arch
|
|
if not architecture:
|
|
architecture = get_platform_arch()
|
|
log_info(f"DIRECT MODE: Using platform default architecture: {architecture}")
|
|
|
|
if architecture not in VALID_ARCHITECTURES:
|
|
raise ValueError(
|
|
f"DIRECT MODE: invalid architecture '{architecture}'. "
|
|
f"Valid: {sorted(VALID_ARCHITECTURES)}"
|
|
)
|
|
|
|
# build_type: CLI > Default
|
|
build_type = cli_args.get("build_type") or "debug"
|
|
|
|
log_info(f"✓ DIRECT MODE: chromium_src={chromium_src} (cli/env)")
|
|
log_info(f"✓ DIRECT MODE: architecture={architecture} (cli/env/default)")
|
|
log_info(f"✓ DIRECT MODE: build_type={build_type} (cli/default)")
|
|
|
|
return [
|
|
Context(
|
|
chromium_src=chromium_src,
|
|
architecture=architecture,
|
|
build_type=build_type,
|
|
)
|
|
]
|
|
|
|
|
|
def resolve_pipeline(
|
|
cli_args: Dict[str, Any],
|
|
yaml_config: Optional[Dict[str, Any]] = None,
|
|
execution_order: Optional[List[Tuple[str, List[str]]]] = None,
|
|
) -> List[str]:
|
|
"""Resolve build pipeline - single entry point.
|
|
|
|
Args:
|
|
cli_args: CLI arguments dictionary
|
|
yaml_config: Optional YAML configuration (triggers CONFIG mode)
|
|
execution_order: Required for DIRECT mode with phase flags
|
|
|
|
Returns:
|
|
List of module names in execution order
|
|
|
|
Raises:
|
|
ValueError: If no pipeline specified or conflicting modes
|
|
|
|
Modes:
|
|
- CONFIG mode: Returns yaml_config["modules"]
|
|
- DIRECT mode: --modules or phase flags
|
|
"""
|
|
if yaml_config:
|
|
return _resolve_pipeline_config_mode(yaml_config)
|
|
else:
|
|
return _resolve_pipeline_direct_mode(cli_args, execution_order)
|
|
|
|
|
|
def _resolve_pipeline_config_mode(yaml_config: Dict[str, Any]) -> List[str]:
|
|
"""CONFIG MODE: Pipeline from YAML modules list.
|
|
|
|
Args:
|
|
yaml_config: YAML configuration dictionary
|
|
|
|
Returns:
|
|
Module list from YAML
|
|
|
|
Raises:
|
|
ValueError: If modules not specified in YAML
|
|
"""
|
|
modules = yaml_config.get("modules")
|
|
if not modules:
|
|
raise ValueError(
|
|
"CONFIG MODE: modules required in YAML!\n"
|
|
"Add to your config:\n"
|
|
" modules: [clean, configure, compile, sign_macos]"
|
|
)
|
|
|
|
log_info(f"✓ CONFIG MODE: pipeline={modules} (yaml)")
|
|
return modules
|
|
|
|
|
|
def _resolve_pipeline_direct_mode(
|
|
cli_args: Dict[str, Any],
|
|
execution_order: Optional[List[Tuple[str, List[str]]]],
|
|
) -> List[str]:
|
|
"""DIRECT MODE: Pipeline from --modules or phase flags.
|
|
|
|
Args:
|
|
cli_args: CLI arguments dictionary
|
|
execution_order: Phase execution order (required for flag mode)
|
|
|
|
Returns:
|
|
Module list in execution order
|
|
|
|
Raises:
|
|
ValueError: If no pipeline specified or both modes used
|
|
"""
|
|
has_modules = cli_args.get("modules") is not None
|
|
has_flags = _has_phase_flags(cli_args)
|
|
|
|
if not has_modules and not has_flags:
|
|
raise ValueError(
|
|
"DIRECT MODE: No pipeline specified!\n"
|
|
"Use one of:\n"
|
|
" --modules clean,compile,...\n"
|
|
" --setup --build --sign (phase flags)"
|
|
)
|
|
|
|
if has_modules and has_flags:
|
|
raise ValueError(
|
|
"DIRECT MODE: Cannot use both --modules and phase flags!\n"
|
|
"Choose one approach."
|
|
)
|
|
|
|
if has_modules:
|
|
modules_str = cli_args["modules"]
|
|
pipeline = [m.strip() for m in modules_str.split(",")]
|
|
log_info(f"✓ DIRECT MODE: pipeline={pipeline} (--modules)")
|
|
return pipeline
|
|
|
|
if has_flags:
|
|
if execution_order is None:
|
|
raise ValueError(
|
|
"DIRECT MODE: execution_order required for phase flag resolution"
|
|
)
|
|
pipeline = _build_pipeline_from_flags(cli_args, execution_order)
|
|
log_info(f"✓ DIRECT MODE: pipeline={pipeline} (phase flags)")
|
|
return pipeline
|
|
|
|
raise ValueError("DIRECT MODE: Internal error - no pipeline resolution matched")
|
|
|
|
|
|
def _has_phase_flags(cli_args: Dict[str, Any]) -> bool:
|
|
"""Check if any phase flags are set.
|
|
|
|
Args:
|
|
cli_args: CLI arguments dictionary
|
|
|
|
Returns:
|
|
True if any phase flag is True
|
|
"""
|
|
phase_flags = ["setup", "prep", "build", "sign", "package", "upload"]
|
|
return any(cli_args.get(flag, False) for flag in phase_flags)
|
|
|
|
|
|
def _build_pipeline_from_flags(
|
|
cli_args: Dict[str, Any],
|
|
execution_order: List[Tuple[str, List[str]]],
|
|
) -> List[str]:
|
|
"""Build pipeline from phase flags with fixed execution order.
|
|
|
|
Args:
|
|
cli_args: CLI arguments with phase flag keys
|
|
execution_order: List of (phase_name, modules) defining order
|
|
|
|
Returns:
|
|
Module list in predetermined order
|
|
"""
|
|
enabled_phases = {
|
|
"setup": cli_args.get("setup", False),
|
|
"prep": cli_args.get("prep", False),
|
|
"build": cli_args.get("build", False),
|
|
"sign": cli_args.get("sign", False),
|
|
"package": cli_args.get("package", False),
|
|
"upload": cli_args.get("upload", False),
|
|
}
|
|
|
|
pipeline = []
|
|
for phase_name, phase_modules in execution_order:
|
|
if enabled_phases.get(phase_name, False):
|
|
pipeline.extend(phase_modules)
|
|
|
|
return pipeline
|