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/execution.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

153 lines
7.5 KiB
Python

"""Run the planned native commands of one target in the ordinary sandbox.
Commands are token lists started without a shell, with a minimal environment that
cannot carry release or runner credentials into source-controlled build code.
"""
import logging
import os
import subprocess
from collections.abc import Callable, Mapping, Sequence
from pathlib import Path
from platform_ci.build_tools import BUILD_TOOLS, POWERSHELL_LAUNCHER, POWERSHELL_PROGRAM_SUFFIX
from platform_ci.maven_settings import install_public_central_settings
from platform_ci.plan import Target
from platform_ci.source_helpers import verify_source_helpers
from platform_ci.source_tools import helper_invocation, subcommand_named
from platform_ci.workspace import PipelineError, resolve_component_directory
LOG = logging.getLogger(__name__)
# Host runners (Windows, macOS) need their own system variables to start a tool at all; none of them is a credential.
INHERITED_VARIABLES = (
"PATH",
"HOME",
"LANG",
"LC_ALL",
"TMPDIR",
"JAVA_HOME",
"MAVEN_OPTS",
"MAVEN_CONFIG",
"CI",
"SYSTEMROOT",
"USERPROFILE",
"TEMP",
"TMP",
"PATHEXT",
"COMSPEC",
"APPDATA",
"LOCALAPPDATA",
"PROGRAMFILES",
"PROGRAMDATA",
"DEVELOPER_DIR",
)
RUN_IDENTITY_VARIABLES = ("CI_RUN_ID", "CI_RUN_ATTEMPT")
"""Passed from the workflow's own context, only to a target whose environment carries the event facts; deliberately outside the target, so the build-input identity
(and with it reuse and the publisher's equivalence proof) does not change with every run."""
CommandRunner = Callable[[Sequence[str], Path, Mapping[str, str]], int]
class ExecutionError(PipelineError):
"""Raised when a planned command cannot be started at all."""
def sandbox_environment(parent_environment: Mapping[str, str]) -> dict[str, str]:
"""Keep only the allow-listed variables, so tokens injected by the runner never reach build code."""
return {name: value for name, value in parent_environment.items() if name in INHERITED_VARIABLES}
def git_trust_environment(directory: Path) -> dict[str, str]:
"""Return the environment that lets subprocess Git inspect exactly one checkout, without touching any host or user Git state.
The checkout is made by the runner's builtin checkout, whose owner can differ from the user the job runs as, so Git refuses it as
`dubious ownership`. Command-scope configuration through `GIT_CONFIG_COUNT` is honored by Git for `safe.directory` and exists only in the
environment of the one process started here: no global or system file is written, nothing is a wildcard, and only this exact resolved path
is trusted (a parent, a subdirectory or a sibling is not). `safe.directory` matches by path text, so forward slashes are used on every host.
"""
return {
"GIT_CONFIG_COUNT": "1",
"GIT_CONFIG_KEY_0": "safe.directory",
"GIT_CONFIG_VALUE_0": directory.resolve().as_posix(),
}
def run_process(command: Sequence[str], working_directory: Path, environment: Mapping[str, str]) -> int:
"""Start one tokenized command without a shell and return its exit code."""
try:
return subprocess.run(list(command), cwd=working_directory, env=dict(environment), shell=False, check=False).returncode # noqa: S603
except OSError as error:
raise ExecutionError(f"cannot start {command[0]!r}: {error}") from error
def launch_command(command: Sequence[str]) -> list[str]:
"""Return what to start for one declared program: the program itself, or a `.ps1` file behind the fixed PowerShell launcher.
The launcher is `pwsh -NoProfile -NonInteractive -File <program> <arguments>` on any platform: no shell, no execution-policy override, nothing downloaded.
A runtime without `pwsh` fails as the ordinary `cannot start` error.
"""
if command[0].lower().endswith(POWERSHELL_PROGRAM_SUFFIX):
return [*POWERSHELL_LAUNCHER, *command]
return list(command)
def _prepare_command(target: Target, planned_command: list[str], working_directory: Path, environment: dict[str, str]) -> tuple[list[str], dict[str, str]]:
"""Return the command to start and its environment: helper and program checks run first, so nothing outside the component directory is ever started."""
command, command_environment = planned_command, environment
if BUILD_TOOLS[target["build_tool"]].helper_subcommands:
command, helper_environment = helper_invocation(planned_command, working_directory)
command_environment = environment | helper_environment
subcommand = subcommand_named(planned_command[0])
if subcommand is not None and subcommand.inspects_checkout_with_git:
command_environment |= git_trust_environment(working_directory)
if BUILD_TOOLS[target["build_tool"]].subcommands:
program = (working_directory / command[1]).resolve()
if not program.is_relative_to(working_directory) or not program.is_file():
raise ExecutionError(f"{command[1]!r} is not a file inside the component directory")
verify_source_helpers(program.parent)
if BUILD_TOOLS[target["build_tool"]].program_in_arguments:
program = (working_directory / command[0]).resolve()
if not program.is_relative_to(working_directory) or not program.is_file():
raise ExecutionError(f"{command[0]!r} is not a file inside the component directory")
command = launch_command(command)
return command, command_environment
def run_target_commands(
target: Target,
source_directory: Path,
run_command: CommandRunner = run_process,
parent_environment: Mapping[str, str] = os.environ,
) -> bool:
"""Run the target's commands in order, stopping at the first failure; return whether all succeeded.
The environment is the allow-listed runner variables plus the target's own fixed, non-secret tool variables. A Maven
target first gets the credential-free public-repository settings in its home directory.
"""
working_directory = resolve_component_directory(source_directory, target["directory"])
environment = sandbox_environment(parent_environment) | target["environment"]
if "CI_EVENT_NAME" in target["environment"]:
# a script that opted in to event facts also gets the run identity the workflow supplies, and nothing else from the parent: two numeric values
for name in RUN_IDENTITY_VARIABLES:
value = parent_environment.get(name, "")
if not value.isascii() or not value.isdigit() or int(value) <= 0:
raise ExecutionError(f"{name} must be the workflow's positive run number for a target that reads event facts")
environment[name] = value
if BUILD_TOOLS[target["build_tool"]].inspects_checkout_with_git:
environment |= git_trust_environment(working_directory)
if BUILD_TOOLS[target["build_tool"]].public_central_settings or (BUILD_TOOLS[target["build_tool"]].toolchain_choices and target["toolchain"] == "java"):
home = environment.get("HOME", "")
if not home:
raise ExecutionError("HOME is not set, so the Maven settings location is unknown")
LOG.info("verify %s: Maven settings written to %s", target["name"], install_public_central_settings(Path(home)))
for planned_command in target["commands"]:
command, command_environment = _prepare_command(target, planned_command, working_directory, environment)
LOG.info("verify %s: running %s", target["name"], command)
exit_code = run_command(command, working_directory, command_environment)
if exit_code != 0:
LOG.error("verify %s: %s exited with %d", target["name"], command[0], exit_code)
return False
return True