Files
uv/scripts/manual-github-release.sh
zaniebotandZanie Blue 911f3a208c Refactor release artifact handling (#21556)
On top of #21447, give original build outputs and completed release
artifacts distinct names. Assemble every target into one release set,
using signed macOS and Windows replacements and copying Linux artifacts
and sdists unchanged. Publication downloads the complete release
artifacts by explicit ID. Python publication uploads the assembled
wheels and sdists; GitHub publication uses cargo-dist's asset list.
Distribution filenames and release URLs are unchanged.

The flow is:

```text
cargo-dist plan → dist-manifest-plan-<r>

build-release-binaries
├── build-wheels-<package>-<target>
├── build-sdist-<package>
└── build-github-archives-<target>
    └── SBOM inspection

prepare signing inputs from the original distributions
├── signing-assembly-inputs-<r>          (all original distributions)
└── unsigned-binaries-<system>-<r>
    └── sign and notarize
        └── signed-binaries-<system>-<r>

assemble original distributions and signed replacements
├── signed-wheels-<system>-<r>           (native verification)
├── release-wheels-<package>-<r>         (all targets)
├── release-sdist-<package>-<r>
└── release-github-archives-<r>          (all targets)

native verification succeeds
└── cargo-dist: local manifest, installers, checksums
    ├── dist-manifest-local-<r>
    ├── release-github-global-<r>
    ├── dist-manifest-global-<r>
    └── host: release-github-manifest-<r>
        ├── Python publication: exact wheel and sdist IDs
        └── GitHub publication: exact IDs and manifest asset list
            └── mirror: same artifact IDs, existing R2 file filters
```

Here `<package>` is `uv` or `uv_build`, `<system>` is `macos` or
`windows`, and `<r>` is `<run-id>-<run-attempt>`. The names below are
relative to #21447:

| Previous name | New name |
| --- | --- |
| `wheels_<package>-<target>` | `build-wheels-<package>-<target>` |
| `wheels_<package>-sdist` | `build-sdist-<package>` |
| `artifacts-<target>` | `build-github-archives-<target>` |
| `unsigned-<system>-<r>` | `unsigned-binaries-<system>-<r>` |
| `signing-assembly-<r>` | `signing-assembly-inputs-<r>` |
| `signed-<system>-<r>` | `signed-binaries-<system>-<r>` |
| `signed-<system>-wheels-<r>` | `signed-wheels-<system>-<r>` |
| `signed-uv-wheels-<r>`, `signed-uv-build-wheels-<r>` |
`release-wheels-<package>-<r>`; now includes every target |
| No assembled sdist artifact | `release-sdist-<package>-<r>` |
| `signed-github-release-archives-<r>` | `release-github-archives-<r>`;
now includes every target |
| `artifacts-plan-dist-manifest` | `dist-manifest-plan-<r>` |
| `artifacts-build-local-manifest` | `dist-manifest-local-<r>` |
| `artifacts-build-global` | `release-github-global-<r>` and
`dist-manifest-global-<r>` |
| `artifacts-dist-manifest` | `release-github-manifest-<r>` |

The intermediate cargo-dist manifests stay separate from publication
files. The manual release helper selects the three GitHub artifacts
associated with the latest hosted manifest, allowing successful producer
jobs to come from earlier attempts, and publishes only the manifest's
asset list.

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
2026-09-10 16:10:21 +00:00

89 lines
2.7 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Manually perform a GitHub release for a broken automated release that otherwise went out.
#
# PLEASE USE WITH CAUTION.
#
# It can take a while to download all the artifacts.
#
# Requires `gh` and `jq`.
set -euo pipefail
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
if [ -z "${COMMIT:-}" ]; then
echo "COMMIT is required."
exit 1
fi
if [ -z "${RUN_ID:-}" ]; then
echo "RUN_ID is required."
exit 1
fi
# Create directory for artifacts
mkdir -p "release_$RUN_ID"
cd "release_$RUN_ID"
REPO=$(gh repo view --json nameWithOwner | jq .nameWithOwner -r)
# Find the publication artifacts across all attempts of this run.
gh api --paginate "repos/$REPO/actions/runs/$RUN_ID/artifacts?per_page=100" |
jq -s '[.[].artifacts[]]' > workflow-artifacts.json
RUN_ATTEMPT=$(gh run view "$RUN_ID" --repo "$REPO" --json attempt --jq .attempt)
# Return the latest artifact in a family, up to the given run attempt.
artifact_name() {
jq -er --arg prefix "release-github-$1-$RUN_ID-" --argjson attempt "$2" '
[.[]
| select(.name | startswith($prefix))
| . + {attempt: (.name | ltrimstr($prefix) | tonumber)}
| select(.attempt <= $attempt)]
| max_by(.attempt)
| if . == null then error("Missing artifact: \($prefix)*")
elif .expired then error("Artifact has expired: \(.name)")
else .name end
' workflow-artifacts.json
}
# Successful upstream jobs may come from earlier attempts. Anchor their selection
# to the last hosted manifest, excluding outputs from any later failed rerun.
manifest_artifact=$(artifact_name manifest "$RUN_ATTEMPT")
manifest_attempt=${manifest_artifact##*-}
gh run download "$RUN_ID" --repo "$REPO" --name "$manifest_artifact" --dir artifacts
for artifact in archives global; do
name=$(artifact_name "$artifact" "$manifest_attempt")
gh run download "$RUN_ID" --repo "$REPO" \
--name "$name" --dir artifacts
done
MANIFEST="artifacts/dist-manifest.json"
"$SCRIPT_DIR/dist-manifest-to-files.sh" "$MANIFEST" > release-assets.txt
assets=()
while IFS= read -r asset; do
assets+=("$asset")
done < release-assets.txt
# Extract values from manifest
TAG=$(jq -r '.announcement_tag // .tag' "$MANIFEST")
TITLE=$(jq -r '.announcement_title' "$MANIFEST")
BODY=$(jq -r '.announcement_github_body' "$MANIFEST")
PRERELEASE=$(jq -r '.announcement_is_prerelease' "$MANIFEST")
# Write body to temp file
echo "$BODY" > notes.txt
# Create release
release_args=(
"$TAG"
--target "$COMMIT"
--title "$TITLE"
--notes-file notes.txt
)
if [ "$PRERELEASE" = "true" ]; then
release_args+=(--prerelease)
fi
gh release create "${release_args[@]}" "${assets[@]}"