Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions .github/actions/stage/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Stage Release Artefacts
description: Stage release artefacts using a TOML configuration file.

inputs:
config-file:
description: Path to the project-specific TOML staging configuration file.
required: true
target:
description: The target key from the configuration file to be staged.
required: true

outputs:
artifact_dir:
description: Absolute path to the directory containing the staged artefacts.
value: ${{ steps.run-stage.outputs.artifact_dir }}
dist_dir:
description: Absolute path to the workspace distribution directory.
value: ${{ steps.run-stage.outputs.dist_dir }}
staged_files:
description: Newline-separated list of staged file names.
value: ${{ steps.run-stage.outputs.staged_files }}
artefact_map:
description: JSON map of named artefact outputs to their absolute paths.
value: ${{ steps.run-stage.outputs.artefact_map }}
checksum_map:
description: JSON map of staged file names to their checksum digests.
value: ${{ steps.run-stage.outputs.checksum_map }}
binary_path:
description: Absolute path to the staged binary artefact, when available.
value: ${{ steps.run-stage.outputs.binary_path }}
man_path:
description: Absolute path to the staged manual page artefact, when available.
value: ${{ steps.run-stage.outputs.man_path }}
license_path:
description: Absolute path to the staged licence artefact, when available.
value: ${{ steps.run-stage.outputs.license_path }}

runs:
using: composite
steps:
- name: Install uv
uses: astral-sh/setup-uv@8d55fbecc275b1c35dbe060458839f8d30439ccf
with:
python-version: '3.11'
- id: check-uv
name: Verify uv is available
shell: bash
run: |
set -euo pipefail
if ! command -v uv >/dev/null 2>&1; then
echo "::error title=Missing dependency::uv not found on PATH. Install it (e.g. with astral-sh/setup-uv) before this action."
exit 1
fi
- id: run-stage
name: Run staging script
shell: bash
run: |
set -euo pipefail
uv run "${{ github.action_path }}/scripts/stage.py" "${{ inputs.config-file }}" "${{ inputs.target }}"
61 changes: 61 additions & 0 deletions .github/actions/stage/scripts/stage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "cyclopts>=0.14",
# ]
# ///

"""Command-line entry point for the staging helper.

Examples
--------
Run the staging helper locally after exporting the required environment
variables::

export GITHUB_WORKSPACE="$(pwd)"
export GITHUB_OUTPUT="$(mktemp)"
uv run .github/actions/stage/scripts/stage.py \
.github/release-staging.toml linux-x86_64
"""

from __future__ import annotations

import sys
from pathlib import Path

from stage_common import StageError, load_config, require_env_path, stage_artefacts

import cyclopts

app = cyclopts.App(help="Stage release artefacts using a TOML configuration file.")


@app.default
def main(config_file: Path, target: str) -> None:
"""Stage artefacts for ``target`` using ``config_file``.

Parameters
----------
config_file:
Path to the project-specific TOML configuration file.
target:
Target key in the configuration file (for example ``"linux-x86_64"``).
"""
try:
config_path = Path(config_file)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Remove redundant Path conversion.

The parameter config_file is already typed as Path, so converting it again is unnecessary.

Apply this diff:

-        config_path = Path(config_file)
         github_output = require_env_path("GITHUB_OUTPUT")
-        config = load_config(config_path, target)
+        config = load_config(config_file, target)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
config_path = Path(config_file)
github_output = require_env_path("GITHUB_OUTPUT")
config = load_config(config_file, target)
🤖 Prompt for AI Agents
.github/actions/stage/scripts/stage.py around line 45: config_file is already a
Path so remove the redundant conversion; replace the line creating config_path =
Path(config_file) by using config_file directly (e.g., rename usages of
config_path to config_file or assign config_path = config_file without calling
Path()), and update any type hints/comments if needed to reflect that a Path is
passed in.

github_output = require_env_path("GITHUB_OUTPUT")
config = load_config(config_path, target)
result = stage_artefacts(config, github_output)
except (FileNotFoundError, StageError) as exc:
print(f"::error title=Staging Failure::{exc}", file=sys.stderr)
raise SystemExit(1) from exc

staged_rel = result.staging_dir.relative_to(config.workspace)
print(
f"Staged {len(result.staged_artefacts)} artefact(s) into '{staged_rel}'.",
file=sys.stderr,
)


if __name__ == "__main__":
app()
17 changes: 17 additions & 0 deletions .github/actions/stage/scripts/stage_common/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Public interface for the staging helper package."""

from .config import ArtefactConfig, StagingConfig, load_config
from .environment import require_env_path
from .errors import StageError
from .staging import RESERVED_OUTPUT_KEYS, StageResult, stage_artefacts

__all__ = [
"ArtefactConfig",
"RESERVED_OUTPUT_KEYS",
"StageError",
"StageResult",
"StagingConfig",
"load_config",
"require_env_path",
"stage_artefacts",
]
21 changes: 21 additions & 0 deletions .github/actions/stage/scripts/stage_common/checksum_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""Checksum helpers for staged artefacts."""

from __future__ import annotations

import hashlib
from pathlib import Path

__all__ = ["write_checksum"]


def write_checksum(path: Path, algorithm: str) -> str:
"""Write the checksum sidecar for ``path`` using ``algorithm``."""

hasher = hashlib.new(algorithm)
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(8192), b""):
hasher.update(chunk)
digest = hasher.hexdigest()
checksum_path = path.with_name(f"{path.name}.{algorithm}")
checksum_path.write_text(f"{digest} {path.name}\n", encoding="utf-8")
return digest
Loading
Loading