Skip to content

Merge pull request #18 from JamesLinYJ/dependabot/github_actions/acti… #3

Merge pull request #18 from JamesLinYJ/dependabot/github_actions/acti…

Merge pull request #18 from JamesLinYJ/dependabot/github_actions/acti… #3

Workflow file for this run

# +-------------------------------------------------------------------------
#
# taskmgr-rs - GitHub Actions 正式发布
#
# 文件: .github/workflows/release.yml
#
# 日期: 2026年07月31日
# 环境: Windows 10 Pro Dev(Build 29634.1000)x86_64;Rust 1.97.0;MSVC 14.50.35729.0
# 作者: OpenAI Codex
# --------------------------------------------------------------------------
name: Release
on:
push:
branches:
- main
tags:
- "v*"
paths:
- Cargo.toml
- Cargo.lock
- .github/workflows/release.yml
workflow_dispatch:
inputs:
tag:
description: Existing version tag to build and publish (for example, v0.2.5)
required: true
type: string
permissions:
contents: read
concurrency:
group: release-${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}
cancel-in-progress: false
env:
CARGO_INCREMENTAL: "0"
CARGO_TERM_COLOR: always
defaults:
run:
shell: pwsh
jobs:
prepare:
name: Resolve release tag
runs-on: windows-2025-vs2026
timeout-minutes: 10
permissions:
contents: write
outputs:
release_tag: ${{ steps.release.outputs.release_tag }}
should_release: ${{ steps.release.outputs.should_release }}
steps:
- name: Checkout source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Install Rust toolchain
run: |
rustup set profile minimal
rustup toolchain install stable --profile minimal
rustup show active-toolchain
- name: Resolve or create release tag
id: release
env:
GH_TOKEN: ${{ github.token }}
REQUESTED_RELEASE_TAG: ${{ inputs.tag }}
run: |
$eventName = '${{ github.event_name }}'
if ($eventName -eq 'workflow_dispatch') {
$tag = $env:REQUESTED_RELEASE_TAG
$shouldRelease = 'true'
} elseif ($env:GITHUB_REF -like 'refs/tags/*') {
$tag = $env:GITHUB_REF_NAME
$shouldRelease = 'true'
} else {
$metadataText = & cargo metadata --locked --no-deps --format-version 1
if ($LASTEXITCODE -ne 0) {
throw "cargo metadata failed with exit code $LASTEXITCODE"
}
$metadata = $metadataText | ConvertFrom-Json
$package = @($metadata.packages) |
Where-Object { $_.name -eq 'taskmgr-rs' } |
Select-Object -First 1
if ($null -eq $package) {
throw 'cargo metadata did not contain taskmgr-rs'
}
$tag = "v$($package.version)"
$PSNativeCommandUseErrorActionPreference = $false
& git ls-remote --exit-code --tags origin "refs/tags/$tag" *> $null
$tagLookupExitCode = $LASTEXITCODE
if ($tagLookupExitCode -eq 0) {
function Find-ReleaseByTag {
param([Parameter(Mandatory)][string]$Tag)
$page = 1
while ($true) {
$pageText = & gh api `
"repos/$env:GITHUB_REPOSITORY/releases?per_page=100&page=$page"
if ($LASTEXITCODE -ne 0) {
throw "listing releases failed with exit code $LASTEXITCODE"
}
$pageItems = @($pageText | ConvertFrom-Json)
$matches = @(
$pageItems |
Where-Object { $_.tag_name -ceq $Tag }
)
if ($matches.Count -gt 1) {
throw "multiple releases use tag $Tag"
}
if ($matches.Count -eq 1) {
return $matches[0]
}
if ($pageItems.Count -lt 100) {
return $null
}
$page += 1
}
}
$existingRelease = Find-ReleaseByTag -Tag $tag
if ($null -eq $existingRelease) {
Write-Host "Release tag $tag exists without a release; publication will resume."
$shouldRelease = 'true'
} elseif ($existingRelease.draft) {
Write-Host "Draft release $tag exists; publication will resume."
$shouldRelease = 'true'
} else {
Write-Host "Published release $tag already exists; nothing to publish for this push."
$shouldRelease = 'false'
}
} elseif ($tagLookupExitCode -eq 2) {
& gh api `
--method POST `
"repos/$env:GITHUB_REPOSITORY/git/refs" `
-f "ref=refs/tags/$tag" `
-f "sha=$env:GITHUB_SHA"
if ($LASTEXITCODE -ne 0) {
throw "creating release tag $tag failed with exit code $LASTEXITCODE"
}
$shouldRelease = 'true'
} else {
throw "checking release tag $tag failed with exit code $tagLookupExitCode"
}
}
if ($tag -notmatch '^v[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$') {
throw "release tag is not a supported semantic version: $tag"
}
"release_tag=$tag" | Out-File `
-FilePath $env:GITHUB_OUTPUT `
-Encoding utf8 `
-Append
"should_release=$shouldRelease" | Out-File `
-FilePath $env:GITHUB_OUTPUT `
-Encoding utf8 `
-Append
validate:
name: Validate release tag
needs: prepare
if: needs.prepare.outputs.should_release == 'true'
runs-on: windows-2025-vs2026
timeout-minutes: 10
env:
RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }}
outputs:
version: ${{ steps.metadata.outputs.version }}
steps:
- name: Checkout tagged source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ env.RELEASE_TAG }}
fetch-depth: 0
persist-credentials: false
- name: Install Rust toolchain
run: |
rustup set profile minimal
rustup toolchain install stable --profile minimal
rustup show active-toolchain
- name: Validate tag and package version
id: metadata
run: |
$tag = $env:RELEASE_TAG
if ($tag -notmatch '^v[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$') {
throw "release tag is not a supported semantic version: $tag"
}
$pointingTags = @(git tag --points-at HEAD)
if ($pointingTags -notcontains $tag) {
throw "checked-out commit is not referenced by tag $tag"
}
$metadataText = & cargo metadata --locked --no-deps --format-version 1
if ($LASTEXITCODE -ne 0) {
throw "cargo metadata failed with exit code $LASTEXITCODE"
}
$metadata = $metadataText | ConvertFrom-Json
$package = @($metadata.packages) |
Where-Object { $_.name -eq 'taskmgr-rs' } |
Select-Object -First 1
if ($null -eq $package) {
throw "cargo metadata did not contain taskmgr-rs"
}
$expectedTag = "v$($package.version)"
if ($tag -cne $expectedTag) {
throw "release tag $tag does not match Cargo package version $($package.version)"
}
"version=$($package.version)" | Out-File `
-FilePath $env:GITHUB_OUTPUT `
-Encoding utf8 `
-Append
build:
name: Build (${{ matrix.target }})
needs:
- prepare
- validate
runs-on: windows-2025-vs2026
timeout-minutes: 30
env:
RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }}
strategy:
fail-fast: false
matrix:
target:
- x86_64-pc-windows-msvc
- i686-pc-windows-msvc
- aarch64-pc-windows-msvc
steps:
- name: Checkout tagged source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ env.RELEASE_TAG }}
persist-credentials: false
- name: Install Rust toolchain
id: toolchain
run: |
rustup set profile minimal
rustup toolchain install stable `
--target '${{ matrix.target }}'
rustup show active-toolchain
$commit = @(
rustc -Vv |
Select-String '^commit-hash:' |
ForEach-Object { $_.Line.Split(':', 2)[1].Trim() }
)[0]
if ([string]::IsNullOrWhiteSpace($commit)) {
throw 'rustc did not report its commit hash'
}
"commit=$commit" | Out-File `
-FilePath $env:GITHUB_OUTPUT `
-Encoding utf8 `
-Append
- name: Restore Cargo cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git/db
target
key: ${{ runner.os }}-rust-release-${{ matrix.target }}-${{ steps.toolchain.outputs.commit }}-${{ hashFiles('Cargo.lock', 'rust-toolchain.toml', '.cargo/config.toml') }}
restore-keys: |
${{ runner.os }}-rust-release-${{ matrix.target }}-${{ steps.toolchain.outputs.commit }}-
${{ runner.os }}-rust-release-${{ matrix.target }}-
- name: Build path-remapped executable
run: |
& ./scripts/release-clean.ps1 -Target '${{ matrix.target }}'
- name: Validate and package executable
id: package
run: |
& ./scripts/package-release.ps1 `
-Target '${{ matrix.target }}' `
-Executable "target\${{ matrix.target }}\release\taskmgr.exe" `
-OutputDirectory dist `
-ExpectedVersion '${{ needs.validate.outputs.version }}'
- name: Upload release asset
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ steps.package.outputs.asset_name }}
path: ${{ steps.package.outputs.asset_path }}
if-no-files-found: error
retention-days: 7
compression-level: 0
publish:
name: Publish GitHub Release
needs:
- prepare
- validate
- build
runs-on: windows-2025-vs2026
timeout-minutes: 15
env:
RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }}
permissions:
contents: write
id-token: write
attestations: write
steps:
- name: Download release assets
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: taskmgr-windows-*
path: dist
merge-multiple: true
- name: Verify complete asset set and write checksums
run: |
$expected = @(
'taskmgr-windows-arm64.exe'
'taskmgr-windows-x86.exe'
'taskmgr-windows-x86_64.exe'
)
$actual = @(
Get-ChildItem -LiteralPath dist -File |
ForEach-Object Name
)
$missing = @($expected | Where-Object { $_ -notin $actual })
$unexpected = @($actual | Where-Object { $_ -notin $expected })
if ($missing.Count -ne 0 -or $unexpected.Count -ne 0) {
throw "release asset set mismatch; missing=[$($missing -join ', ')], unexpected=[$($unexpected -join ', ')]"
}
$checksumLines = foreach ($name in ($expected | Sort-Object)) {
$path = Join-Path dist $name
$hash = (Get-FileHash -Algorithm SHA256 -LiteralPath $path).Hash.ToLowerInvariant()
"$hash $name"
}
[IO.File]::WriteAllLines(
(Join-Path (Resolve-Path -LiteralPath dist).Path 'SHA256SUMS.txt'),
$checksumLines,
[Text.Encoding]::ASCII
)
- name: Attest release assets
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
with:
subject-path: |
dist/taskmgr-windows-arm64.exe
dist/taskmgr-windows-x86.exe
dist/taskmgr-windows-x86_64.exe
dist/SHA256SUMS.txt
- name: Create or resume draft release
env:
GH_TOKEN: ${{ github.token }}
run: |
$PSNativeCommandUseErrorActionPreference = $false
$tag = $env:RELEASE_TAG
$assetPaths = @(
Get-ChildItem -LiteralPath dist -File |
Sort-Object Name |
ForEach-Object FullName
)
function Find-ReleaseByTag {
param([Parameter(Mandatory)][string]$Tag)
$page = 1
while ($true) {
$pageText = & gh api `
"repos/$env:GITHUB_REPOSITORY/releases?per_page=100&page=$page"
if ($LASTEXITCODE -ne 0) {
throw "listing releases failed with exit code $LASTEXITCODE"
}
$pageItems = @($pageText | ConvertFrom-Json)
$matches = @(
$pageItems |
Where-Object { $_.tag_name -ceq $Tag }
)
if ($matches.Count -gt 1) {
throw "multiple releases use tag $Tag"
}
if ($matches.Count -eq 1) {
return $matches[0]
}
if ($pageItems.Count -lt 100) {
return $null
}
$page += 1
}
}
$existing = Find-ReleaseByTag -Tag $tag
if ($null -ne $existing) {
if (-not $existing.draft) {
Write-Host "Published release $tag already exists; assets will only be verified."
} else {
& gh release upload $tag @assetPaths `
--repo $env:GITHUB_REPOSITORY `
--clobber
if ($LASTEXITCODE -ne 0) {
throw "updating draft release assets failed with exit code $LASTEXITCODE"
}
}
} else {
$notes = 'Automated Windows builds for x86_64, x86, and ARM64. SHA256SUMS.txt and GitHub build-provenance attestations are included. The executables are currently unsigned.'
& gh release create $tag @assetPaths `
--repo $env:GITHUB_REPOSITORY `
--verify-tag `
--draft `
--generate-notes `
--title "taskmgr-rs $tag" `
--notes $notes
if ($LASTEXITCODE -ne 0) {
throw "creating draft release failed with exit code $LASTEXITCODE"
}
}
- name: Verify uploaded asset digests
id: release
env:
GH_TOKEN: ${{ github.token }}
run: |
$tag = $env:RELEASE_TAG
function Find-ReleaseByTag {
param([Parameter(Mandatory)][string]$Tag)
$page = 1
while ($true) {
$pageText = & gh api `
"repos/$env:GITHUB_REPOSITORY/releases?per_page=100&page=$page"
if ($LASTEXITCODE -ne 0) {
throw "listing releases failed with exit code $LASTEXITCODE"
}
$pageItems = @($pageText | ConvertFrom-Json)
$matches = @(
$pageItems |
Where-Object { $_.tag_name -ceq $Tag }
)
if ($matches.Count -gt 1) {
throw "multiple releases use tag $Tag"
}
if ($matches.Count -eq 1) {
return $matches[0]
}
if ($pageItems.Count -lt 100) {
return $null
}
$page += 1
}
}
$release = Find-ReleaseByTag -Tag $tag
if ($null -eq $release) {
throw "release metadata for $tag was not found"
}
$localFiles = @(Get-ChildItem -LiteralPath dist -File)
foreach ($localFile in $localFiles) {
$matches = @(
$release.assets |
Where-Object { $_.name -ceq $localFile.Name }
)
if ($matches.Count -ne 1) {
throw "release must contain exactly one asset named $($localFile.Name)"
}
$localHash = (
Get-FileHash -Algorithm SHA256 -LiteralPath $localFile.FullName
).Hash.ToLowerInvariant()
$expectedDigest = "sha256:$localHash"
if ($matches[0].digest -cne $expectedDigest) {
throw "digest mismatch for $($localFile.Name): expected $expectedDigest, got $($matches[0].digest)"
}
if ([uint64]$matches[0].size -ne [uint64]$localFile.Length) {
throw "size mismatch for $($localFile.Name)"
}
}
if ($release.assets.Count -ne $localFiles.Count) {
throw "release contains unexpected assets"
}
"release_id=$($release.id)" | Out-File `
-FilePath $env:GITHUB_OUTPUT `
-Encoding utf8 `
-Append
"is_draft=$($release.draft.ToString().ToLowerInvariant())" | Out-File `
-FilePath $env:GITHUB_OUTPUT `
-Encoding utf8 `
-Append
- name: Publish verified draft
env:
GH_TOKEN: ${{ github.token }}
run: |
$tag = $env:RELEASE_TAG
$releaseId = '${{ steps.release.outputs.release_id }}'
$isDraft = '${{ steps.release.outputs.is_draft }}'
if ($isDraft -eq 'true') {
& gh api `
--method PATCH `
"repos/$env:GITHUB_REPOSITORY/releases/$releaseId" `
-F draft=false `
-f make_latest=true
if ($LASTEXITCODE -ne 0) {
throw "publishing release failed with exit code $LASTEXITCODE"
}
}
$publishedText = & gh api `
"repos/$env:GITHUB_REPOSITORY/releases/tags/$tag"
if ($LASTEXITCODE -ne 0) {
throw "reading published release failed with exit code $LASTEXITCODE"
}
$published = $publishedText | ConvertFrom-Json
if ($published.draft -or $published.tag_name -cne $tag) {
throw "release $tag was not published with the expected tag"
}
"Published release: https://github.com/$env:GITHUB_REPOSITORY/releases/tag/$tag" |
Add-Content -LiteralPath $env:GITHUB_STEP_SUMMARY