diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..6894a5a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +# GitHub Actions are pinned to immutable commit SHAs. Dependabot keeps those pins current. +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + open-pull-requests-limit: 5 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..903d392 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,179 @@ +# +------------------------------------------------------------------------- +# +# taskmgr-rs - GitHub Actions 持续集成 +# +# 文件: .github/workflows/ci.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: CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + CARGO_INCREMENTAL: "0" + CARGO_TERM_COLOR: always + RUST_BACKTRACE: "1" + +defaults: + run: + shell: pwsh + +jobs: + quality: + name: Quality (x86_64) + runs-on: windows-2025-vs2026 + timeout-minutes: 30 + + steps: + - name: Checkout source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Install Rust toolchain + id: toolchain + run: | + rustup set profile minimal + rustup toolchain install stable ` + --component rustfmt ` + --component clippy ` + --target x86_64-pc-windows-msvc + 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-quality-${{ steps.toolchain.outputs.commit }}-${{ hashFiles('Cargo.lock', 'rust-toolchain.toml', '.cargo/config.toml') }} + restore-keys: | + ${{ runner.os }}-rust-quality-${{ steps.toolchain.outputs.commit }}- + ${{ runner.os }}-rust-quality- + + - name: Check changed-line whitespace + env: + BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }} + run: | + if ([string]::IsNullOrWhiteSpace($env:BASE_SHA) -or $env:BASE_SHA -match '^0+$') { + git show --check --format= HEAD + } else { + git diff --check $env:BASE_SHA $env:GITHUB_SHA + } + if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE + } + + - name: Check formatting + run: cargo fmt --all -- --check + + - name: Check all targets + run: cargo check --all-targets --locked + + - name: Run strict Clippy + run: cargo clippy --all-targets --locked -- -D warnings + + - name: Run all tests + run: cargo test --all-targets --locked + + - name: Build release executable + run: cargo build --release --locked + + architecture: + name: Architecture (${{ matrix.target }}) + runs-on: windows-2025-vs2026 + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - target: i686-pc-windows-msvc + run_tests: true + - target: aarch64-pc-windows-msvc + run_tests: false + + steps: + - name: Checkout source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Install Rust toolchain + id: toolchain + run: | + rustup set profile minimal + rustup toolchain install stable ` + --component clippy ` + --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-${{ matrix.target }}-${{ steps.toolchain.outputs.commit }}-${{ hashFiles('Cargo.lock', 'rust-toolchain.toml', '.cargo/config.toml') }} + restore-keys: | + ${{ runner.os }}-rust-${{ matrix.target }}-${{ steps.toolchain.outputs.commit }}- + ${{ runner.os }}-rust-${{ matrix.target }}- + + - name: Check all targets + run: cargo check --all-targets --target '${{ matrix.target }}' --locked + + - name: Run strict Clippy + run: cargo clippy --all-targets --target '${{ matrix.target }}' --locked -- -D warnings + + - name: Run executable tests + if: matrix.run_tests + run: cargo test --all-targets --target '${{ matrix.target }}' --locked + + - name: Build release executable + run: cargo build --release --target '${{ matrix.target }}' --locked diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..e85e1e0 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,331 @@ +# +------------------------------------------------------------------------- +# +# 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: + tags: + - "v*" + 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 + RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }} + +defaults: + run: + shell: pwsh + +jobs: + validate: + name: Validate release tag + runs-on: windows-2025-vs2026 + timeout-minutes: 10 + 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: validate + runs-on: windows-2025-vs2026 + timeout-minutes: 30 + 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: + - validate + - build + runs-on: windows-2025-vs2026 + timeout-minutes: 15 + 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@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1 + 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 + ) + + $existingText = & gh release view $tag ` + --repo $env:GITHUB_REPOSITORY ` + --json isDraft 2>$null + $releaseExists = $LASTEXITCODE -eq 0 + if ($releaseExists) { + $existing = $existingText | ConvertFrom-Json + if (-not $existing.isDraft) { + 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 + env: + GH_TOKEN: ${{ github.token }} + run: | + $tag = $env:RELEASE_TAG + $releaseText = & gh api ` + "repos/$env:GITHUB_REPOSITORY/releases/tags/$tag" + if ($LASTEXITCODE -ne 0) { + throw "reading release metadata failed with exit code $LASTEXITCODE" + } + $release = $releaseText | ConvertFrom-Json + $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" + } + + - name: Publish verified draft + env: + GH_TOKEN: ${{ github.token }} + run: | + $tag = $env:RELEASE_TAG + $releaseText = & gh release view $tag ` + --repo $env:GITHUB_REPOSITORY ` + --json isDraft + if ($LASTEXITCODE -ne 0) { + throw "reading release state failed with exit code $LASTEXITCODE" + } + $release = $releaseText | ConvertFrom-Json + if ($release.isDraft) { + & gh release edit $tag ` + --repo $env:GITHUB_REPOSITORY ` + --draft=false ` + --latest + if ($LASTEXITCODE -ne 0) { + throw "publishing release failed with exit code $LASTEXITCODE" + } + } + + "Published release: https://github.com/$env:GITHUB_REPOSITORY/releases/tag/$tag" | + Add-Content -LiteralPath $env:GITHUB_STEP_SUMMARY diff --git a/README.md b/README.md index 2089f82..aba2498 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ English | [简体中文](README.zh-CN.md) +[![CI](https://github.com/JamesLinYJ/taskmgr-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/JamesLinYJ/taskmgr-rs/actions/workflows/ci.yml) + A Windows task manager written in Rust with native Win32 APIs. The UI follows the older Task Manager layout, while sampling and background refresh use current Windows interfaces. This is not a copy of the modern Task Manager. CPU and GPU details come from system APIs rather than model lookup tables. If Windows does not return a value, the UI says it is unavailable. A failed refresh also leaves the last valid result on screen instead of replacing it with zeros. @@ -20,9 +22,10 @@ This is not a copy of the modern Task Manager. CPU and GPU details come from sys The current build is on the [Releases page](https://github.com/JamesLinYJ/taskmgr-rs/releases/latest): - [Windows x86_64](https://github.com/JamesLinYJ/taskmgr-rs/releases/latest/download/taskmgr-windows-x86_64.exe) for most Intel and AMD Windows PCs. +- [Windows x86](https://github.com/JamesLinYJ/taskmgr-rs/releases/latest/download/taskmgr-windows-x86.exe) for 32-bit Windows. - [Windows ARM64](https://github.com/JamesLinYJ/taskmgr-rs/releases/latest/download/taskmgr-windows-arm64.exe) for Windows on Arm devices. -Both downloads are single EXE files. The program asks for administrator access because some process and session actions require it. +All three downloads are single EXE files. The program asks for administrator access because some process and session actions require it. The release files are not code-signed at the moment, so Windows may show a SmartScreen warning. Each release includes SHA-256 hashes for manual verification. @@ -70,6 +73,21 @@ rustup target add aarch64-pc-windows-msvc The executable is written to `target//release/taskmgr.exe`. +## Automation + +Pull requests and pushes to `main` run formatting, all-target checks, strict Clippy, tests, and an +x86_64 release build on GitHub's Windows Server 2025 / Visual Studio 2026 runner. Separate jobs +compile i686 and ARM64; i686 tests also run under WOW64. + +To publish a version, first update `Cargo.toml` and `Cargo.lock`, merge the change, then push the +matching tag such as `v0.2.5`. The Release workflow rejects a tag that does not exactly match the +Cargo package version. It builds all three architectures in parallel, verifies the PE machine and +Windows version resources, writes `SHA256SUMS.txt`, creates GitHub build-provenance attestations, +and publishes only after the uploaded sizes and digests match. A publishing failure leaves only a +draft; a build failure creates no release, so neither path exposes a partial release. + +Workflow dependencies are pinned to immutable commit SHAs. Dependabot checks those pins weekly. + ## Diagnostic logs Redacted basic diagnostics are recorded by default under diff --git a/README.zh-CN.md b/README.zh-CN.md index d8076bc..79b3a55 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -2,6 +2,8 @@ [English](README.md) | 简体中文 +[![CI](https://github.com/JamesLinYJ/taskmgr-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/JamesLinYJ/taskmgr-rs/actions/workflows/ci.yml) + 用 Rust 和原生 Win32 API 写的 Windows 任务管理器。界面沿用经典任务管理器的布局,系统采样和后台刷新则按现在的 Windows 接口重新实现。 它不是现代任务管理器的复刻,也不靠 CPU 或 GPU 型号表猜数据。系统没有返回某项信息时,页面会直接显示不可用;刷新失败时,上一份有效结果仍会留在界面上。 @@ -20,9 +22,10 @@ 最新版本在 [Releases](https://github.com/JamesLinYJ/taskmgr-rs/releases/latest): - [Windows x86_64](https://github.com/JamesLinYJ/taskmgr-rs/releases/latest/download/taskmgr-windows-x86_64.exe),适合常见的 Intel 和 AMD Windows 电脑。 +- [Windows x86](https://github.com/JamesLinYJ/taskmgr-rs/releases/latest/download/taskmgr-windows-x86.exe),用于 32 位 Windows。 - [Windows ARM64](https://github.com/JamesLinYJ/taskmgr-rs/releases/latest/download/taskmgr-windows-arm64.exe),用于 Windows on Arm 设备。 -两个版本都是单文件 EXE,不需要安装。程序会申请管理员权限,因为部分进程和会话操作需要它。 +三个版本都是单文件 EXE,不需要安装。程序会申请管理员权限,因为部分进程和会话操作需要它。 目前发布文件没有代码签名,Windows 可能会显示 SmartScreen 提示。每个版本的发布说明都列出了 SHA-256,可以下载后自行核对。 @@ -70,6 +73,20 @@ rustup target add aarch64-pc-windows-msvc 生成的文件位于 `target//release/taskmgr.exe`。 +## 自动化 + +Pull Request 和推送到 `main` 的提交会在 GitHub 的 Windows Server 2025 / Visual Studio 2026 +runner 上执行格式检查、全目标 check、严格 Clippy、完整测试和 x86_64 release 构建。独立任务 +同时验证 i686 与 ARM64,i686 测试会在 WOW64 下实际执行。 + +发布版本时,先同步修改 `Cargo.toml` 与 `Cargo.lock` 并合入主线,再推送与版本完全一致的 tag, +例如 `v0.2.5`。Release 工作流会拒绝与 Cargo 包版本不一致的 tag,并行构建三种架构,核对 PE +机器类型和 Windows 版本资源,生成 `SHA256SUMS.txt` 与 GitHub 构建来源证明。只有 GitHub +服务器上的资产大小和摘要都与本地一致时才公开 Release。发布阶段失败时只保留草稿,构建 +阶段失败时不会创建 Release,因此都不会暴露不完整发布。 + +工作流依赖全部固定到不可变的完整提交 SHA,并由 Dependabot 每周检查更新。 + ## 诊断日志 程序默认把经过脱敏的基础诊断日志写入 diff --git a/scripts/package-release.ps1 b/scripts/package-release.ps1 new file mode 100644 index 0000000..d9a2e3e --- /dev/null +++ b/scripts/package-release.ps1 @@ -0,0 +1,172 @@ +# +------------------------------------------------------------------------- +# +# taskmgr-rs - 发布资产架构与版本校验 +# +# 文件: scripts/package-release.ps1 +# +# 日期: 2026年07月31日 +# 环境: Windows 10 Pro Dev(Build 29634.1000)x86_64;Rust 1.97.0;MSVC 14.50.35729.0 +# 作者: OpenAI Codex +# -------------------------------------------------------------------------- + +param( + [Parameter(Mandatory)] + [ValidateSet( + "x86_64-pc-windows-msvc", + "i686-pc-windows-msvc", + "aarch64-pc-windows-msvc" + )] + [string]$Target, + + [Parameter(Mandatory)] + [string]$Executable, + + [Parameter(Mandatory)] + [string]$OutputDirectory, + + [string]$ExpectedVersion +) + +$ErrorActionPreference = "Stop" + +$targetDetails = @{ + "x86_64-pc-windows-msvc" = @{ + Machine = [uint16]0x8664 + Asset = "taskmgr-windows-x86_64.exe" + } + "i686-pc-windows-msvc" = @{ + Machine = [uint16]0x014c + Asset = "taskmgr-windows-x86.exe" + } + "aarch64-pc-windows-msvc" = @{ + Machine = [uint16]0xaa64 + Asset = "taskmgr-windows-arm64.exe" + } +} + +function Get-PeMachine { + param( + [Parameter(Mandatory)] + [string]$Path + ) + + $stream = [IO.File]::Open( + $Path, + [IO.FileMode]::Open, + [IO.FileAccess]::Read, + [IO.FileShare]::Read + ) + try { + if ($stream.Length -lt 64) { + throw "file is too small to contain a PE header: $Path" + } + + $reader = [IO.BinaryReader]::new($stream, [Text.Encoding]::ASCII, $true) + try { + if ($reader.ReadUInt16() -ne 0x5a4d) { + throw "file does not have an MZ header: $Path" + } + + $stream.Position = 0x3c + $peOffset = [uint64]$reader.ReadUInt32() + if ($peOffset -gt [uint64]($stream.Length - 6)) { + throw "PE header offset is outside the file: $Path" + } + + $stream.Position = [int64]$peOffset + if ($reader.ReadUInt32() -ne 0x00004550) { + throw "file does not have a PE signature: $Path" + } + return $reader.ReadUInt16() + } finally { + $reader.Dispose() + } + } finally { + $stream.Dispose() + } +} + +$repoRoot = Split-Path -Parent $PSScriptRoot +$executablePath = if ([IO.Path]::IsPathRooted($Executable)) { + $Executable +} else { + Join-Path $repoRoot $Executable +} +$executableItem = Get-Item -LiteralPath $executablePath +if ($executableItem.PSIsContainer) { + throw "executable path names a directory: $($executableItem.FullName)" +} + +$metadataText = & cargo metadata ` + --manifest-path (Join-Path $repoRoot "Cargo.toml") ` + --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" +} +$version = [string]$package.version +if ($ExpectedVersion -and $version -cne $ExpectedVersion) { + throw "Cargo package version $version does not match expected version $ExpectedVersion" +} + +$targetDetail = $targetDetails[$Target] +$machine = Get-PeMachine -Path $executableItem.FullName +if ($machine -ne $targetDetail.Machine) { + throw ( + "PE machine mismatch for {0}: expected 0x{1:X4}, got 0x{2:X4}" -f + $Target, + $targetDetail.Machine, + $machine + ) +} + +$fileVersion = $executableItem.VersionInfo.FileVersion.Trim() +$productVersion = $executableItem.VersionInfo.ProductVersion.Trim() +if ($fileVersion -cne $version) { + throw "file version $fileVersion does not match Cargo package version $version" +} +if ($productVersion -cne $version) { + throw "product version $productVersion does not match Cargo package version $version" +} + +$outputPath = if ([IO.Path]::IsPathRooted($OutputDirectory)) { + $OutputDirectory +} else { + Join-Path $repoRoot $OutputDirectory +} +$outputItem = New-Item -ItemType Directory -Path $outputPath -Force +$assetPath = Join-Path $outputItem.FullName $targetDetail.Asset +Copy-Item -LiteralPath $executableItem.FullName -Destination $assetPath -Force + +$assetItem = Get-Item -LiteralPath $assetPath +$sha256 = ( + Get-FileHash -Algorithm SHA256 -LiteralPath $assetItem.FullName +).Hash.ToLowerInvariant() + +if ($env:GITHUB_OUTPUT) { + @( + "asset_name=$($assetItem.Name)" + "asset_path=$($assetItem.FullName)" + "machine=0x$($machine.ToString('X4'))" + "sha256=$sha256" + "size=$($assetItem.Length)" + "version=$version" + ) | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append +} + +[pscustomobject]@{ + Target = $Target + Machine = "0x$($machine.ToString('X4'))" + Version = $version + Asset = $assetItem.FullName + Size = $assetItem.Length + SHA256 = $sha256 +} | Format-List diff --git a/scripts/release-clean.ps1 b/scripts/release-clean.ps1 index d86a105..8164036 100644 --- a/scripts/release-clean.ps1 +++ b/scripts/release-clean.ps1 @@ -9,6 +9,7 @@ param( ) $ErrorActionPreference = "Stop" +$previousRustFlags = $env:RUSTFLAGS function Add-RemapPrefix { param( @@ -30,7 +31,6 @@ function Add-RemapPrefix { } $repoRoot = Split-Path -Parent $PSScriptRoot -$toolchain = if ($UseNightlyBuildStd) { "nightly" } else { "" } $rustflags = New-Object 'System.Collections.Generic.List[string]' Add-RemapPrefix $rustflags $repoRoot "." @@ -76,10 +76,19 @@ try { cargo +nightly build ` -Z build-std=std,panic_abort ` --target $Target ` - --release + --release ` + --locked } else { - cargo build --target $Target --release + cargo build --target $Target --release --locked + } + if ($LASTEXITCODE -ne 0) { + throw "cargo release build failed with exit code $LASTEXITCODE" } } finally { Pop-Location + if ($null -eq $previousRustFlags) { + Remove-Item Env:RUSTFLAGS -ErrorAction SilentlyContinue + } else { + $env:RUSTFLAGS = $previousRustFlags + } }