diff --git a/cli/python/base_dev/engine.py b/cli/python/base_dev/engine.py index bc82e5c1..31e8fa16 100644 --- a/cli/python/base_dev/engine.py +++ b/cli/python/base_dev/engine.py @@ -2,14 +2,15 @@ import json import sys -from dataclasses import dataclass from pathlib import Path import base_cli -from base_setup.artifacts import reconcile_artifact, resolve_artifact_definitions +from base_setup.artifacts import reconcile_artifact +from base_setup.artifacts import resolve_artifact_definitions # pylint: disable=unused-import from base_setup.checks import DIAGNOSTIC_JSON_SCHEMA_VERSION from base_setup.errors import ArtifactError -from base_setup.manifest import ArtifactRequest, BaseManifest, ManifestError, read_manifest +from base_setup.manifest import ArtifactRequest, BaseManifest, ManifestError +from base_setup.manifest import read_manifest # pylint: disable=unused-import from base_setup.prerequisites import GitHubCliAuthCheckRequest from base_setup.prerequisites import HomebrewPackageCheckRequest from base_setup.prerequisites import PrerequisiteCheck @@ -38,27 +39,19 @@ from .linux_profile import profile_setup_fix from .linux_profile import reconcile_linux_debian_apt_artifact from .linux_profile import reconcile_linux_debian_github_cli_artifact +from .profiles import SUPPORTED_PROFILES # pylint: disable=unused-import +from .profiles import ProfileError +from .profiles import ProfileManifest +from .profiles import ProfileRuntime +from .profiles import dev_manifest_path # pylint: disable=unused-import +from .profiles import normalize_profiles +from .profiles import profile_manifest_path +from .profiles import read_dev_manifest # pylint: disable=unused-import +from .profiles import read_profile_manifest # pylint: disable=unused-import +from .profiles import read_profile_manifests app = base_cli.App(name="base_dev") -SUPPORTED_PROFILES = ("dev", "sre", "ai", "linux-lab") - - -@dataclass(frozen=True) -class ProfileManifest: - name: str - manifest: BaseManifest - definitions: tuple[ArtifactDefinition, ...] - - -@dataclass(frozen=True) -class ProfileRuntime: - profile: str - project: str - - -class ProfileError(ValueError): - pass def main(argv: list[str] | None = None) -> int: @@ -97,59 +90,6 @@ def run(ctx: base_cli.Context, action: str, dry_run: bool, output_format: str, p return base_cli.ExitCode.USAGE_ERROR -def normalize_profiles(profiles: tuple[str, ...]) -> tuple[str, ...]: - if not profiles: - return ("dev",) - - normalized: list[str] = [] - for profile_list in profiles: - for raw_profile in profile_list.split(","): - profile = raw_profile.strip().lower() - if not profile: - raise ProfileError("Profile list must not contain empty entries.") - if profile not in SUPPORTED_PROFILES: - display_profile = raw_profile.strip() or raw_profile - raise ProfileError( - f"Unsupported profile '{display_profile}'. Expected one of: {', '.join(SUPPORTED_PROFILES)}." - ) - if profile not in normalized: - normalized.append(profile) - return tuple(normalized) - - -def read_profile_manifests(ctx: base_cli.Context, profiles: tuple[str, ...]) -> tuple[ProfileManifest, ...]: - profile_manifests: list[ProfileManifest] = [] - for profile in profiles: - if profile in {"ai", "linux-lab"}: - continue - manifest = read_profile_manifest(ctx, profile) - definitions = resolve_artifact_definitions(manifest.artifacts) - profile_manifests.append(ProfileManifest(profile, manifest, definitions)) - return tuple(profile_manifests) - - -def read_profile_manifest(ctx: base_cli.Context, profile: str) -> BaseManifest: - if ctx.base_home is None: - raise ManifestError("BASE_HOME is required to load Base's prerequisite profile manifests.") - return read_manifest(profile_manifest_path(ctx.base_home, profile)) - - -def read_dev_manifest(ctx: base_cli.Context) -> BaseManifest: - if ctx.base_home is None: - raise ManifestError("BASE_HOME is required to load Base's developer prerequisite manifest.") - return read_profile_manifest(ctx, "dev") - - -def dev_manifest_path(base_home: Path) -> Path: - return base_home / "lib" / "base" / "dev_manifest.yaml" - - -def profile_manifest_path(base_home: Path, profile: str) -> Path: - if profile == "dev": - return dev_manifest_path(base_home) - return base_home / "lib" / "base" / f"{profile}_manifest.yaml" - - def setup_profile_manifests( ctx: base_cli.Context, profile_manifests: tuple[ProfileManifest, ...], diff --git a/cli/python/base_dev/profiles.py b/cli/python/base_dev/profiles.py new file mode 100644 index 00000000..1435b493 --- /dev/null +++ b/cli/python/base_dev/profiles.py @@ -0,0 +1,81 @@ +from __future__ import annotations + +from dataclasses import dataclass +from pathlib import Path + +import base_cli +from base_setup.artifacts import resolve_artifact_definitions +from base_setup.manifest import BaseManifest, ManifestError, read_manifest +from base_setup.registry import ArtifactDefinition + +SUPPORTED_PROFILES = ("dev", "sre", "ai", "linux-lab") + + +@dataclass(frozen=True) +class ProfileManifest: + name: str + manifest: BaseManifest + definitions: tuple[ArtifactDefinition, ...] + + +@dataclass(frozen=True) +class ProfileRuntime: + profile: str + project: str + + +class ProfileError(ValueError): + pass + + +def normalize_profiles(profiles: tuple[str, ...]) -> tuple[str, ...]: + if not profiles: + return ("dev",) + + normalized: list[str] = [] + for profile_list in profiles: + for raw_profile in profile_list.split(","): + profile = raw_profile.strip().lower() + if not profile: + raise ProfileError("Profile list must not contain empty entries.") + if profile not in SUPPORTED_PROFILES: + display_profile = raw_profile.strip() or raw_profile + raise ProfileError( + f"Unsupported profile '{display_profile}'. Expected one of: {', '.join(SUPPORTED_PROFILES)}." + ) + if profile not in normalized: + normalized.append(profile) + return tuple(normalized) + + +def read_profile_manifests(ctx: base_cli.Context, profiles: tuple[str, ...]) -> tuple[ProfileManifest, ...]: + profile_manifests: list[ProfileManifest] = [] + for profile in profiles: + if profile in {"ai", "linux-lab"}: + continue + manifest = read_profile_manifest(ctx, profile) + definitions = resolve_artifact_definitions(manifest.artifacts) + profile_manifests.append(ProfileManifest(profile, manifest, definitions)) + return tuple(profile_manifests) + + +def read_profile_manifest(ctx: base_cli.Context, profile: str) -> BaseManifest: + if ctx.base_home is None: + raise ManifestError("BASE_HOME is required to load Base's prerequisite profile manifests.") + return read_manifest(profile_manifest_path(ctx.base_home, profile)) + + +def read_dev_manifest(ctx: base_cli.Context) -> BaseManifest: + if ctx.base_home is None: + raise ManifestError("BASE_HOME is required to load Base's developer prerequisite manifest.") + return read_profile_manifest(ctx, "dev") + + +def dev_manifest_path(base_home: Path) -> Path: + return base_home / "lib" / "base" / "dev_manifest.yaml" + + +def profile_manifest_path(base_home: Path, profile: str) -> Path: + if profile == "dev": + return dev_manifest_path(base_home) + return base_home / "lib" / "base" / f"{profile}_manifest.yaml" diff --git a/cli/python/base_dev/tests/test_engine.py b/cli/python/base_dev/tests/test_engine.py index c2a909a7..0fecbf6e 100644 --- a/cli/python/base_dev/tests/test_engine.py +++ b/cli/python/base_dev/tests/test_engine.py @@ -71,6 +71,26 @@ def test_main_reports_missing_action_without_traceback(self) -> None: self.assertIn("Missing argument", stderr) self.assertNotIn("Traceback", stderr) + def test_engine_reexports_profile_helpers(self) -> None: + from base_dev import profiles + + expected_names = ( + "ProfileError", + "ProfileManifest", + "ProfileRuntime", + "SUPPORTED_PROFILES", + "dev_manifest_path", + "normalize_profiles", + "profile_manifest_path", + "read_dev_manifest", + "read_profile_manifest", + "read_profile_manifests", + ) + + for name in expected_names: + with self.subTest(name=name): + self.assertIs(getattr(engine, name), getattr(profiles, name)) + def test_dev_manifest_declares_supported_developer_tools(self) -> None: manifest = engine.read_manifest(Path(__file__).resolve().parents[4] / "lib" / "base" / "dev_manifest.yaml") artifacts = {(artifact.artifact_type, artifact.name, artifact.version) for artifact in manifest.artifacts}