Files
BrowserOS/packages/browseros/build/common/resolver.py
Nikhil 2901e73547 patches & fixes for 0.31.1 release (#219)
* chore: new browseros-server binaries

* bugfix: was writing chromium_patches/ in wrong location

* patch: ntp footer disabled by default

* patch: browseros alpha flag

* patch: add log for port saving

* chore: increment PATCH

* feat: use packages/browseros as root_dir properly in context.py
2025-12-05 16:39:56 -08:00

321 lines
9.7 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
def resolve_config(
cli_args: Dict[str, Any],
yaml_config: Optional[Dict[str, Any]] = None,
) -> 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:
Fully resolved Context object
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]
) -> Context:
"""CONFIG MODE: YAML is base, CLI can override.
Args:
yaml_config: YAML configuration dictionary
cli_args: CLI arguments (can override YAML values)
Returns:
Context with values from YAML, optionally overridden by CLI
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
architecture = (
cli_args.get("arch")
or build_section.get("architecture")
or build_section.get("arch")
)
arch_source = "cli" if cli_args.get("arch") else "yaml"
if not architecture:
architecture = get_platform_arch()
arch_source = "default"
log_info(f"CONFIG MODE: Using platform default architecture: {architecture}")
# 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})")
log_info(f"✓ CONFIG MODE: architecture={architecture} ({arch_source})")
log_info(f"✓ CONFIG MODE: build_type={build_type} ({build_type_source})")
return Context(
chromium_src=chromium_src,
architecture=architecture,
build_type=build_type,
)
def _resolve_direct_mode(cli_args: Dict[str, Any]) -> Context:
"""DIRECT MODE: CLI > Env > Defaults.
Args:
cli_args: CLI arguments (None if not provided by user)
Returns:
Context with resolved values
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}")
# 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