Repository navigation
Merge pull request #18 from JamesLinYJ/dependabot/github_actions/acti… #3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # +------------------------------------------------------------------------- | |
| # | |
| # 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 |