Skip to content
Merged
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
97 changes: 0 additions & 97 deletions .github/workflows/release-finalize.yml

This file was deleted.

96 changes: 7 additions & 89 deletions .github/workflows/release-plz.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,115 +3,33 @@
#
# SPDX-License-Identifier: MIT OR Apache-2.0

# The upstream release-plz workflow, with documented deviations:
# Proposes the next release as a draft pull request, and never publishes; release.yml does, from a signed tag.
#
# * A GitHub App token, because GITHUB_TOKEN cannot trigger CI on the release pull request.
# * `environment: release` on both jobs, which holds the App and crates.io credentials.
# * Windows runners, because `cargo publish` verifies by building, and the `cfg(windows)` windows-sys dependency does not build on Linux.
name: Release-plz
# * A GitHub App token, because a pull request opened with GITHUB_TOKEN starts no CI.
# * `environment: release` holds the App's credentials, and deploys from main only.
# * A Windows runner, because `semver_check` builds the crate, and its `cfg(windows)` dependencies do not build on Linux.
name: Release PR

"on":
push:
branches: [main]
# Manual dispatch retries a failed release; it still publishes only when the manifest version is ahead of the registry.
workflow_dispatch:

permissions: {}

concurrency:
group: release-plz-${{ github.ref }}
group: release-plz-pr-${{ github.ref }}
cancel-in-progress: false

jobs:
release:
name: Release-plz release
if: github.repository == 'P4suta/windows-spawn'
runs-on: windows-2022
timeout-minutes: 45
environment:
name: release
permissions:
contents: write
id-token: write
outputs:
released: ${{ steps.run.outputs.releases_created }}
tag: ${{ steps.released.outputs.tag }}
version: ${{ steps.released.outputs.version }}
steps:
- name: Mint the release GitHub App token
id: app-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ vars.RELEASE_PLZ_APP_CLIENT_ID }}
private-key: ${{ secrets.RELEASE_PLZ_APP_PRIVATE_KEY }}
permission-contents: write
permission-pull-requests: read
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
with:
toolchain: stable
# The bootstrap token published 0.1.0; once a trusted publisher is registered, delete it and OpenID Connect takes over.
- name: Select the crates.io credential
id: auth-mode
env:
CRATES_IO_BOOTSTRAP_TOKEN: ${{ secrets.CRATES_IO_BOOTSTRAP_TOKEN }}
run: cargo xtask crates-io-auth-mode --github-output
- name: Authenticate with crates.io trusted publishing
id: crates-auth
if: steps.auth-mode.outputs.bootstrap == 'false'
uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5
- name: Run release-plz
id: run
uses: release-plz/action@2eb1d8bcb770b4c48ccfaad919734b38b51958c9 # v0.5.131
with:
command: release
version: 0.3.160
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
CARGO_REGISTRY_TOKEN: >-
${{ steps.crates-auth.outputs.token
|| secrets.CRATES_IO_BOOTSTRAP_TOKEN }}
- name: Read the released tag and version
id: released
if: steps.run.outputs.releases_created == 'true'
shell: bash
env:
RELEASES: ${{ steps.run.outputs.releases }}
run: |
set -euo pipefail
jq -r '.[0] | "tag=\(.tag)", "version=\(.version)"' <<< "$RELEASES" >> "$GITHUB_OUTPUT"

# Creates the GitHub release with the crate, SBOM, checksums, and attestations attached.
finalize:
name: Attest and publish the release
needs: release
if: needs.release.outputs.released == 'true'
permissions:
artifact-metadata: write
attestations: write
contents: write
id-token: write
uses: ./.github/workflows/release-finalize.yml
with:
tag: ${{ needs.release.outputs.tag }}
version: ${{ needs.release.outputs.version }}

release-pr:
name: Release-plz PR
# Runs after `release` so no release pull request opens for a version this run publishes.
needs: release
if: always() && github.repository == 'P4suta/windows-spawn'
if: github.repository == 'P4suta/windows-spawn'
runs-on: windows-2022
timeout-minutes: 30
environment:
name: release
permissions: {}
concurrency:
group: release-plz-pr-${{ github.ref }}
cancel-in-progress: false
steps:
- name: Mint the release GitHub App token
id: app-token
Expand Down
175 changes: 175 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
---
# SPDX-FileCopyrightText: 2026 Yasunobu Sakashita
#
# SPDX-License-Identifier: MIT OR Apache-2.0

# Publishes the version a maintainer's signed `vX.Y.Z` tag names.
#
# * The tag rulesets let only the maintainer create a `v*` tag, and `candidate` refuses one that is not annotated and verified as signed.
# This file runs as the tagged commit has it, so those checks are defence in depth; the creation rule and the approval are the gates.
# * `publish` deploys to the `crates-io` environment, which waits for the maintainer's approval.
# crates.io's trusted publisher admits only that environment of this workflow, so nothing else can mint a publish token.
# * Windows runners, because `cargo publish` verifies by building, and the `cfg(windows)` dependencies do not build on Linux.
name: Release

"on":
push:
tags: ["v[0-9]+.[0-9]+.[0-9]+"]

permissions: {}

concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false

jobs:
candidate:
name: Verify the tag and attest the release candidate
if: github.repository == 'P4suta/windows-spawn'
runs-on: windows-latest
timeout-minutes: 45
permissions:
artifact-metadata: write
attestations: write
contents: read
id-token: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
with:
toolchain: stable
- uses: taiki-e/install-action@6a1bd70eaac3c8bdf093356838d7ee09fda951cf # v2.85.5
with:
tool: cargo-cyclonedx@0.5.9
- run: python -m pip install "reuse[charset-normalizer]==6.2.0"
- name: Require an annotated tag whose signature GitHub verified
shell: bash
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
ref="$(gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/${TAG}")"
if [ "$(jq -r .object.type <<<"${ref}")" != tag ]; then
echo "::error::${TAG} is a lightweight tag; a release tag is annotated and signed"
exit 1
fi
tag="$(gh api "repos/${GITHUB_REPOSITORY}/git/tags/$(jq -r .object.sha <<<"${ref}")")"
if [ "$(jq -r .verification.verified <<<"${tag}")" != true ]; then
echo "::error::GitHub did not verify the signature on ${TAG}: $(jq -r .verification.reason <<<"${tag}")"
exit 1
fi
- name: Verify the tag, its commit on main, and the Cargo version
run: cargo xtask verify-release-tag "$env:GITHUB_REF_NAME"
- name: Build and verify the release candidate
id: candidate
run: cargo xtask release-candidate --github-output
- name: Attest SLSA v1 provenance
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-path: |
target/release-candidate/*.crate
target/release-candidate/*.cdx.json
target/release-candidate/*.reuse.spdx
target/release-candidate/SHA256SUMS
- name: Attest CycloneDX SBOM
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-path: target/release-candidate/*.crate
sbom-path: ${{ steps.candidate.outputs.sbom }}
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-candidate
path: target/release-candidate/
if-no-files-found: error

publish:
name: Publish to crates.io
needs: candidate
runs-on: windows-latest
timeout-minutes: 30
environment:
name: crates-io
url: https://crates.io/crates/windows-spawn
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
with:
toolchain: stable
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-candidate
path: ${{ runner.temp }}/release-candidate
# The attestations cover the candidate, so the archive `cargo publish` uploads must be that very file.
# `cargo package` is deterministic for a source tree; a mismatch stops the release before anything is published.
- name: Require the archive to publish to be the attested candidate
shell: pwsh
run: |
$version = $env:GITHUB_REF_NAME.Substring(1)
$name = "windows-spawn-$version.crate"
cargo package --locked
if ($LASTEXITCODE -ne 0) { throw "cargo package exited $LASTEXITCODE" }
$packaged = (Get-FileHash "target/package/$name" -Algorithm SHA256).Hash
$candidate = (Get-FileHash "$env:RUNNER_TEMP/release-candidate/$name" -Algorithm SHA256).Hash
if ($packaged -ne $candidate) {
throw "packaged $name is $packaged, but the attested candidate is $candidate"
}
"packaged archive matches the attested candidate: $packaged" | Write-Output
- name: Authenticate with crates.io trusted publishing
id: crates-auth
uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5
- name: Publish the crate
run: cargo publish --locked
env:
CARGO_REGISTRY_TOKEN: ${{ steps.crates-auth.outputs.token }}
- name: Require crates.io to serve the attested candidate
shell: pwsh
run: |
$version = $env:GITHUB_REF_NAME.Substring(1)
$name = "windows-spawn-$version.crate"
$candidate = (Get-FileHash "$env:RUNNER_TEMP/release-candidate/$name" -Algorithm SHA256).Hash
$uri = "https://crates.io/api/v1/crates/windows-spawn/$version/download"
$probe = Join-Path $env:RUNNER_TEMP 'published.crate'
Invoke-WebRequest -Uri $uri -OutFile $probe -TimeoutSec 60 `
-Headers @{ 'User-Agent' = 'windows-spawn-release' }
$published = (Get-FileHash $probe -Algorithm SHA256).Hash
if ($published -ne $candidate) {
throw "crates.io serves $published, but the attested candidate is $candidate"
}
"crates.io serves the attested candidate: $published" | Write-Output

github-release:
name: Publish the GitHub release
needs: publish
runs-on: windows-latest
timeout-minutes: 30
permissions:
contents: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
with:
toolchain: stable
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-candidate
path: target/release-candidate
# Assets go onto a draft first, because a published release cannot gain them without being seen half-built.
- name: Create the draft GitHub release
env:
GH_TOKEN: ${{ github.token }}
run: cargo xtask draft-release "$env:GITHUB_REF_NAME" --github-output
- name: Publish the GitHub release
env:
GH_TOKEN: ${{ github.token }}
run: gh release edit "$env:GITHUB_REF_NAME" --draft=false --latest
Loading
Loading