This repository has been archived on 2026-11-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
platform-ci/platform_ci/build_tools.py
forge-recoveryandClaude Sonnet 5.5 a36f17b857
KofTwentyTwo/ci-policy: ci / fork-guard (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / go-0 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / go-nix-1 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / gradle-2 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / gradle-3 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / java-docker-4 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / java-maven-5 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / manifests-6 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / nix-7 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / node-8 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / python-9 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / rust-10 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / rust-11 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / rust-12 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / source-config-13 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / source-config-14 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / image-build-0 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / image-build-1 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / image-build-2 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / image-build-3 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / image-build-4 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / image-build-5 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / image-build-6 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / image-build-7 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / not-enrolled (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / content-validate-0 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / standards-0 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / node-matrix-0 (pull_request) Skipped
KofTwentyTwo/ci-policy: ci / source-config-15 (pull_request) Blocked by required conditions
KofTwentyTwo/ci-policy: Shared source and config CI / verify (pull_request) Blocked by required conditions
KofTwentyTwo/ci-policy: Shared source and config CI / secrets (pull_request) Blocked by required conditions
KofTwentyTwo/ci-policy: Shared source and config CI / gate (pull_request) Waiting for needed jobs
KofTwentyTwo/ci-policy: ci / ci-result (pull_request) Waiting for needed jobs
KofTwentyTwo/ci-policy: native / fork-deny (pull_request) Skipped
KofTwentyTwo/ci-policy: native / build-macos-0 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / build-macos-1 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / build-macos-2 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / build-macos-3 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / build-windows-4 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / build-windows-5 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / build-windows-6 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / build-windows-7 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / dotnet-8 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / dotnet-9 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / dotnet-10 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / dotnet-11 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / swift-12 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / swift-13 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / swift-14 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / swift-15 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / swift-16 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / swift-17 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / swift-18 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / swift-19 (pull_request) Skipped
KofTwentyTwo/ci-policy: native / native-result (pull_request) Canceled after 6s
feat(ci): advance shared pipeline capabilities
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 10:30:25 +00:00

205 lines
14 KiB
Python

"""The native build tools this pipeline can run and the platforms they run on.
This is data, not behavior: each row names the executable a tool starts, the catalog toolchain that supplies it, the
platforms it supports, and the small fixed set of non-secret variables its container needs. Nothing here receives a
repository name or owner, so a row can only ever be selected by a validated `.ci.yaml` field. Adding a tool is one row.
"""
from dataclasses import dataclass
from typing import Final
CONTAINER_EXECUTION: Final = "container"
HOST_EXECUTION: Final = "host"
POOL_EXECUTION: Final = "pool"
"""A Linux container job on a profile pool for a component needing only the browser profile: workspace and shared memory, no container daemon access."""
DOCKER_POOL_EXECUTION: Final = "pool-docker"
"""The same for a component that requires the docker profile: the only job that mounts the separate test daemon socket."""
STANDARD_PROFILE: Final = "standard"
SUPPORTED_PROFILES: Final = ("browser", "docker")
IMAGE_PROFILE: Final = "image"
"""The runtime for a component that packages OCI images from its own verified output: the native tool, BuildKit and the scanner in ONE image.
It is implied by a component's `images` and composes with `browser` and `docker`: the component then needs one runtime with every capability, which the
catalog lists for each combination or marks null, and the job and mounts stay those of the browser/docker split (docker in `requires` selects the docker job)."""
EMULATOR_PROFILE: Final = "emulator"
"""The runtime for a `script` component on the `android` toolchain that runs an Android emulator: an image with the SDK command-line tools, the emulator and `adb`, on the
existing profile pool job with ONE added container option, the KVM device (see PROFILE_CONTAINER_OPTIONS). It is declared explicitly, stands alone (it never combines with
another profile), and is unqualified until an operator records both its image (`emulator_image`) and its pool label in the catalog; no host device permission or runner
privilege is granted by this declaration."""
IMAGE_RECIPES: Final = ("node-webapp-runner",)
"""Central image recipes: Dockerfile templates shipped in the platform revision (`platform_ci/image_recipes/<name>.Dockerfile`)."""
IMAGE_OUTPUT_DIRECTORY: Final = "ci-image-output"
"""Where `package-images` writes `<image>/` beside the tested outputs, so the ordinary `package` step retains it in the one verified bundle."""
IMAGE_ARCHIVE_FILE: Final = "image.tar"
IMAGE_RECIPE_RECORD: Final = "recipe.json"
IMAGE_EVIDENCE_FILES: Final = ("SHA256SUMS", "SOURCE_COMMIT", "sbom.cdx.json", "tested-output.json", "trivy.json")
def image_archive_path(name: str) -> str:
"""Return the component-relative path of one image's OCI archive, the single file of its `container` artifact."""
return f"{IMAGE_OUTPUT_DIRECTORY}/{name}/{IMAGE_ARCHIVE_FILE}"
def image_evidence_paths(name: str, *, recipe: bool) -> tuple[str, ...]:
"""Return the component-relative paths of one image's scan, SBOM and provenance files (and the recipe record for a recipe image)."""
files = (*IMAGE_EVIDENCE_FILES, IMAGE_RECIPE_RECORD) if recipe else IMAGE_EVIDENCE_FILES
return tuple(f"{IMAGE_OUTPUT_DIRECTORY}/{name}/{file_name}" for file_name in files)
PROFILE_CONTAINER_OPTIONS: Final = {"browser": ("--init", "--shm-size=256m"), EMULATOR_PROFILE: ("--device=/dev/kvm",)}
"""Container options a runtime profile adds in the pool job (the browser's captured from the approved legacy browser pool); keyed by profile, never by repository.
The emulator's one option only asks the container runtime for the device: the pool's host must already provide it and its permissions, which nothing here grants."""
DOCKER_PROFILE_ENVIRONMENT: Final = (
("DOCKER_HOST", "unix:///run/test-docker.sock"),
("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", "/run/user/1002/docker.sock"),
("TESTCONTAINERS_HOST_OVERRIDE", "172.17.0.1"),
)
"""The fixed, non-secret pool contract of the docker profile: where the separate rootless test daemon is mounted in the job (`DOCKER_HOST`) and how
Testcontainers reaches it. The plan gives these to every target that requires `docker` (alone or with `browser`) so the build process sees them, because
execution passes only an allow-list plus the target's own environment; no other target receives them. The pool-docker job in the shared workflow sets
the same values for the job itself and `tests/test_pool_jobs.py` requires the two to stay equal. The browser profile needs no variable: its Chrome and
chromedriver are on the image's PATH (see the browser stage of the image recipe), so nothing is added for it."""
POWERSHELL_PROGRAM_SUFFIX: Final = ".ps1"
POWERSHELL_LAUNCHER: Final = ("pwsh", "-NoProfile", "-NonInteractive", "-File")
"""A `script` program ending in `.ps1` is started by this fixed launcher on whatever platform the component declares: PowerShell 7 `pwsh`, `-File`, no shell string and no
execution-policy override. `pwsh` must already be on the runtime; nothing downloads or installs it, and a missing one is the ordinary missing-tool error."""
@dataclass(frozen=True)
class PlatformSpec:
"""How a platform is displayed and where its jobs run: in a catalog image (container) or directly on a runner (host)."""
display: str
execution: str
PLATFORMS: Final = {
"linux-amd64": PlatformSpec("Linux", CONTAINER_EXECUTION),
"windows-amd64": PlatformSpec("Windows", HOST_EXECUTION),
"macos-arm64": PlatformSpec("macOS", HOST_EXECUTION),
}
TOOLCHAIN_DISPLAY: Final = {
"java": "Java",
"node": "Node",
"go": "Go",
"rust": "Rust",
"python": "Python",
"dotnet": ".NET",
"xcode": "Xcode",
"android": "Android",
"container": "BuildKit",
"control": "Control",
"nix": "Nix",
"source-tools": "Source tools",
}
@dataclass(frozen=True)
class HelperSubcommand:
"""One closed subcommand of a helper-script tool: the receipted script it starts and the fixed variables that select its check.
`path_variable` names the variable that receives the declared relative paths (comma separated, validated at prepare); empty means
the subcommand takes no paths and any are rejected. `fixed_environment` holds the non-secret selector the helper reads (the
`INPUT_CHECKS` name); nothing here depends on a repository. `inspects_checkout_with_git` has the meaning it has on `BuildTool` and is
set only for a subcommand that itself calls `git` in the checkout (`policy`: `git ls-files`) or runs repository-owned programs that may (`make-bats`).
"""
name: str
script: str
path_variable: str = ""
fixed_environment: tuple[tuple[str, str], ...] = ()
inspects_checkout_with_git: bool = False
@dataclass(frozen=True)
class BuildTool:
"""One native build capability.
`executable` is started without a shell, in the component directory; a leading `./` names the project's own wrapper.
`install_commands` are argument lists run before the declared commands. `profiles` lists the optional runtime
profiles (`browser`, `docker`) a component may require. `environment` applies on every platform and
`container_environment` only in a catalog image, where `HOME` is a scratch directory. None of it is secret.
`public_central_settings` writes the credential-free Maven Central mirror settings before the first command.
`version_file` names the file in the component directory that declares the version a package carries; empty means the tool has none.
`program_in_arguments` means the tool has no executable of its own: each declared command names a checked-in program
relative to the component directory (`./scripts/check.sh`), which must stay inside it.
`inspects_checkout_with_git` marks a tool whose program runs `git` on the checkout (the builtin checkout's owner can differ from the job's user, so Git
reports dubious ownership); for it, execution trusts exactly that one checkout directory, for that invocation only (see `execution.git_trust_environment`).
`helper_subcommands` is the same closed idea for the receipted shell and Python helpers of the `source-tools` image: each declared
command is a subcommand name, followed by validated relative paths only where that subcommand takes them; the helper runs unchanged.
`subcommands` is a closed vocabulary: each declared command is exactly one subcommand name, mapped here to the fixed program and
arguments it runs (a checked-in native helper of the platform checkout). Nothing else can be declared and nothing names a repository.
"""
executable: str
toolchain: str
platforms: tuple[str, ...]
install_commands: tuple[tuple[str, ...], ...] = ()
profiles: tuple[str, ...] = ()
environment: tuple[tuple[str, str], ...] = ()
container_environment: tuple[tuple[str, str], ...] = ()
public_central_settings: bool = False
version_file: str = ""
program_in_arguments: bool = False
subcommands: tuple[tuple[str, tuple[str, ...]], ...] = ()
inspects_checkout_with_git: bool = False
helper_subcommands: tuple[HelperSubcommand, ...] = ()
toolchain_choices: tuple[str, ...] = ()
"""When set, a component declares exactly one of these toolchains (`toolchain` is the default name used when none is chosen elsewhere)."""
# The job container's own scratch directory: toolchain caches go there because the image's home directory is not writable.
_SCRATCH: Final = "/tmp" # noqa: S108
_DOTNET_ENVIRONMENT: Final = (("DOTNET_CLI_TELEMETRY_OPTOUT", "1"), ("DOTNET_NOLOGO", "1"))
_GRADLE_ENVIRONMENT: Final = (("GRADLE_USER_HOME", f"{_SCRATCH}/.gradle"), ("GRADLE_OPTS", "-Xmx1g"))
_GO_ENVIRONMENT: Final = (("GOPATH", f"{_SCRATCH}/go"), ("GOCACHE", f"{_SCRATCH}/go-cache"))
# Only the download cache moves to scratch: the image's own RUSTUP_HOME holds the installed toolchain and must not be hidden.
_CARGO_ENVIRONMENT: Final = (("CARGO_HOME", f"{_SCRATCH}/cargo"),)
_EVERY_PLATFORM: Final = tuple(PLATFORMS)
# The unchanged, receipted native source check shipped in the platform checkout (see source_checks/receipt.json); it runs in the
# repository root, so this path is relative to a component directory of "." and the platform checkout sits at .platform-ci.
SOURCE_CHECK_PROGRAM: Final = ".platform-ci/source_checks/check-source.mjs"
SOURCE_TOOLS_SUBCOMMANDS: Final = (
HelperSubcommand("actionlint", "actionlint-verify.sh"),
HelperSubcommand("shellcheck-orb", "source-checks.sh", fixed_environment=(("INPUT_CHECKS", "shellcheck-orb"),)),
# `make test-scripts test-templates` and `bats tests` are the repository's own programs and may call git in the checkout
HelperSubcommand("make-bats", "source-checks.sh", fixed_environment=(("INPUT_CHECKS", "make-bats"),), inspects_checkout_with_git=True),
HelperSubcommand("kustomize-render", "manifest-verify.sh", path_variable="INPUT_ROOTS"),
HelperSubcommand("blueprint-lint", "blueprint-lint.sh", path_variable="INPUT_PATHS"),
# The legacy application-free policy validator, unchanged: it lists `git ls-files` in the checkout, so it needs the exact-checkout Git trust
HelperSubcommand("policy", "policy-verify.sh", inspects_checkout_with_git=True),
)
# The image sets this itself, but the runner passes only an allow-listed environment, so the plan carries it: the directory that holds the
# pinned PyYAML package the blueprint lint imports. A fixed, non-secret path inside the source-tools image.
_SOURCE_TOOLS_ENVIRONMENT: Final = (("PYYAML_DIR", "/opt/blueprint-tools"),)
BUILD_TOOLS: Final = {
"maven": BuildTool("mvn", "java", ("linux-amd64",), profiles=(*SUPPORTED_PROFILES, IMAGE_PROFILE), public_central_settings=True, version_file="pom.xml"),
"node": BuildTool("npm", "node", ("linux-amd64",), install_commands=(("ci",),), profiles=("browser", IMAGE_PROFILE), version_file="package.json"),
"gradle": BuildTool("./gradlew", "java", ("linux-amd64",), profiles=("browser",), container_environment=_GRADLE_ENVIRONMENT),
"android": BuildTool("./gradlew", "android", ("linux-amd64",), container_environment=_GRADLE_ENVIRONMENT),
"go": BuildTool("go", "go", _EVERY_PLATFORM, container_environment=_GO_ENVIRONMENT),
"cargo": BuildTool("cargo", "rust", _EVERY_PLATFORM, container_environment=_CARGO_ENVIRONMENT),
# `python`, not `python3`: the toolchain image exposes its interpreter only as `python`, so the control image's `python3`
# (3.13+ with PyYAML, which the pipeline itself runs on) is never shadowed by the project's interpreter.
"python": BuildTool("python", "python", ("linux-amd64",)),
"container": BuildTool("buildctl", "container", ("linux-amd64",)),
# A checked-in program run in the declared runtime: one of control (the default), java or node, or on a host platform xcode or dotnet, chosen by the component's `toolchains` key.
# Availability is decided by the runtime catalog (images, hosts, runner labels); a `.ps1` program is started by POWERSHELL_LAUNCHER. Linux stays first, so it is the default platform.
"script": BuildTool("", "control", _EVERY_PLATFORM, profiles=(*SUPPORTED_PROFILES, IMAGE_PROFILE, EMULATOR_PROFILE), program_in_arguments=True, toolchain_choices=("control", "java", "node", "xcode", "dotnet", "android", "rust")),
"source": BuildTool("node", "node", ("linux-amd64",), subcommands=(("tracked-files", ("node", SOURCE_CHECK_PROGRAM)),), inspects_checkout_with_git=True),
# The larger source-tools image (actionlint, ShellCheck, make/Bats, kubectl, PyYAML), selected only by repositories that declare these
# checks; the lightweight `source` row above keeps the Node image for the basic tracked-files check. Runs `bash` on the helpers.
"source-tools": BuildTool("bash", "source-tools", ("linux-amd64",), container_environment=_SOURCE_TOOLS_ENVIRONMENT, helper_subcommands=SOURCE_TOOLS_SUBCOMMANDS),
# `nix flake check` and `nix build` of x86_64-linux flakes: the captured nix-verify profile. Single-user, flake nixConfig never accepted.
"nix": BuildTool("nix", "nix", ("linux-amd64",)),
"make": BuildTool("make", "xcode", ("macos-arm64",)),
"dotnet": BuildTool("dotnet", "dotnet", ("windows-amd64", "linux-amd64"), environment=_DOTNET_ENVIRONMENT),
"swift": BuildTool("swift", "xcode", ("macos-arm64",)),
"xcodebuild": BuildTool("xcodebuild", "xcode", ("macos-arm64",)),
}
TOOLCHAIN_NAMES: Final = tuple(sorted({tool.toolchain for tool in BUILD_TOOLS.values()}))