Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
82 changes: 82 additions & 0 deletions .github/workflows/publish-versions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Publish python-build-standalone version information to the versions repository.
name: publish-versions

on:
workflow_call:
inputs:
tag:
required: true
type: string

permissions: {}

jobs:
publish-versions:
runs-on: ubuntu-latest
env:
TAG: ${{ inputs.tag }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false

- name: "Install uv"
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0

- name: "Download release artifacts"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
mkdir -p dist
gh release download "$TAG" --dir dist

- name: "Generate versions payload"
env:
GITHUB_EVENT_INPUTS_TAG: ${{ inputs.tag }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: uv run generate-version-metadata.py

- name: "Set branch name"
run: echo "BRANCH_NAME=update-versions-$TAG-$(date +%s)" >> $GITHUB_ENV

- name: "Clone versions repo"
run: git clone https://${{ secrets.ASTRAL_VERSIONS_PAT }}@github.com/astral-sh/versions.git astral-versions

- name: "Update versions"
run: cat dist/python-build-standalone.json | uv run astral-versions/scripts/publish-version.py --format json --name python-build-standalone --output astral-versions/v1

- name: "Commit versions"
working-directory: astral-versions
run: |
git config user.name "astral-versions-bot"
git config user.email "176161322+astral-versions-bot@users.noreply.github.com"

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.

Does the numerical prefix here has significance? Would a prefix that helped id the commit as coming from the pbs workflow help debug in the future?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Apparently that's the GitHub user id of the bot account which makes it robust to account name changes 🤷‍♀️ {user_id}+{username}@users.noreply.github.com


git checkout -b "$BRANCH_NAME"
git add -A
git commit -m "Update python-build-standalone to $TAG"

- name: "Create Pull Request"
working-directory: astral-versions
env:
GITHUB_TOKEN: ${{ secrets.ASTRAL_VERSIONS_PAT }}
run: |
pull_request_title="Update python-build-standalone versions for $TAG"

gh pr list --state open --json title --jq ".[] | select(.title == \"$pull_request_title\") | .number" | \
xargs -I {} gh pr close {}

git push origin "$BRANCH_NAME"

gh pr create --base main --head "$BRANCH_NAME" \
--title "$pull_request_title" \
--body "Automated versions update for $TAG" \
--label "automation"

- name: "Merge Pull Request"
working-directory: astral-versions
env:
GITHUB_TOKEN: ${{ secrets.ASTRAL_VERSIONS_PAT }}
run: |
# Wait for PR to be created before merging
sleep 10
gh pr merge --squash "$BRANCH_NAME"
8 changes: 8 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,11 @@
subject-path: |
dist/*.tar.gz
dist/*.tar.zst

publish-versions:
needs: release
if: ${{ github.event.inputs.dry-run == 'false' }}
uses: ./.github/workflows/publish-versions.yml

Check warning

Code scanning / zizmor

secrets unconditionally inherited by called workflow Warning

secrets unconditionally inherited by called workflow
with:
tag: ${{ github.event.inputs.tag }}
secrets: inherit
105 changes: 105 additions & 0 deletions generate-version-metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# /// script
# requires-python = ">=3.11"
# ///
"""Generate versions payload for python-build-standalone releases."""

from __future__ import annotations

import json
import os
import re
from collections import defaultdict
from datetime import datetime, timezone
from pathlib import Path
from urllib.parse import quote

FILENAME_RE = re.compile(
r"""(?x)
^
cpython-
(?P<py>\d+\.\d+\.\d+(?:(?:a|b|rc)\d+)?)(?:\+\d+)?\+
(?P<tag>\d+)-
(?P<triple>[a-z\d_]+-[a-z\d]+(?:-[a-z\d]+)?-[a-z\d_]+)-
(?:(?P<build>.+)-)?
(?P<flavor>[a-z_]+)?
\.tar\.(?:gz|zst)
$
"""
)


def main() -> None:
tag = os.environ["GITHUB_EVENT_INPUTS_TAG"]
repo = os.environ["GITHUB_REPOSITORY"]
dist = Path("dist")
checksums = dist / "SHA256SUMS"
Comment thread
jjhelmus marked this conversation as resolved.

if not checksums.exists():
raise SystemExit("SHA256SUMS not found in dist/")

checksum_map: dict[str, str] = {}
for line in checksums.read_text().splitlines():
line = line.strip()
if not line:
continue
checksum, filename = line.split(maxsplit=1)
checksum_map[filename.lstrip("*")] = checksum

versions: dict[str, list[dict[str, str]]] = defaultdict(list)
for path in sorted(dist.glob("cpython-*.tar.*")):
match = FILENAME_RE.match(path.name)
if match is None:
continue
python_version = match.group("py")
build_version = match.group("tag")
version = f"{python_version}+{build_version}"
build = match.group("build")
flavor = match.group("flavor")
variant_parts: list[str] = []
if build:
variant_parts.extend(build.split("+"))
if flavor:
variant_parts.append(flavor)
variant = "+".join(variant_parts) if variant_parts else ""

url_prefix = f"https://github.com/{repo}/releases/download/{tag}/"
url = url_prefix + quote(path.name, safe="")
archive_format = "tar.zst" if path.name.endswith(".tar.zst") else "tar.gz"

artifact = {
"platform": match.group("triple"),
"variant": variant,
"url": url,
"archive_format": archive_format,
"sha256": checksum_map.get(path.name, ""),
}
if not artifact["sha256"]:
artifact.pop("sha256")
versions[version].append(artifact)

payload_versions: list[dict[str, object]] = []
now = datetime.now(timezone.utc).isoformat()
for version, artifacts in sorted(versions.items(), reverse=True):
artifacts.sort(
key=lambda artifact: (artifact["platform"], artifact.get("variant", ""))
)
payload_versions.append(
{
"version": version,
"date": now,
"artifacts": artifacts,
}
)

payload = {
"name": "python-build-standalone",
"versions": payload_versions,
}

output = dist / "python-build-standalone.json"
output.write_text(json.dumps(payload, separators=(",", ":")))
print(f"Wrote {output}")


if __name__ == "__main__":
main()