Release v0.9.3 #42
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
| name: Release | |
| run-name: Release v${{ inputs.version }} | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: "Release version without the v prefix, for example: 0.1.0" | |
| required: true | |
| type: string | |
| permissions: | |
| contents: write | |
| concurrency: | |
| group: release | |
| cancel-in-progress: false | |
| jobs: | |
| windows-release: | |
| name: Prepare release, squash merge, and publish Windows executable | |
| runs-on: windows-latest | |
| env: | |
| RUST_TARGET: x86_64-pc-windows-msvc | |
| CARGO_INCREMENTAL: 0 | |
| CARGO_TERM_COLOR: always | |
| steps: | |
| - name: Checkout default branch | |
| uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Validate release version | |
| shell: pwsh | |
| run: | | |
| $Version = "${{ inputs.version }}".Trim() | |
| if ($Version -notmatch '^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$') { | |
| Write-Error "Invalid version '$Version'. Use semantic version format like 0.1.0. Do not include the v prefix." | |
| exit 1 | |
| } | |
| $Tag = "v$Version" | |
| $ReleaseBranch = "chore/release-$Tag" | |
| "VERSION=$Version" >> $env:GITHUB_ENV | |
| "TAG=$Tag" >> $env:GITHUB_ENV | |
| "RELEASE_BRANCH=$ReleaseBranch" >> $env:GITHUB_ENV | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ env.RUST_TARGET }} | |
| - name: Compute Cargo dependency cache key | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $CargoToml = Get-Content "Cargo.toml" -Raw | |
| $DependencySectionPattern = '(?ms)^\[(dependencies|dev-dependencies|build-dependencies|workspace\.dependencies|target\.[^\]]+\.dependencies|target\.[^\]]+\.dev-dependencies|target\.[^\]]+\.build-dependencies)\]\s*.*?(?=^\[|\z)' | |
| $DependencySections = [regex]::Matches($CargoToml, $DependencySectionPattern) | |
| if ($DependencySections.Count -eq 0) { | |
| Write-Error "Could not find dependency sections in Cargo.toml for the release cache key." | |
| exit 1 | |
| } | |
| # Keep the cache stable across Audio Orbit release version changes. | |
| # Only dependency declarations and dependency lock metadata are used. | |
| $CacheParts = New-Object System.Collections.Generic.List[string] | |
| $CacheParts.Add("cargo-dependency-sections") | |
| foreach ($Section in $DependencySections) { | |
| $CacheParts.Add($Section.Value.Trim()) | |
| } | |
| if (Test-Path "Cargo.lock") { | |
| $CargoLock = Get-Content "Cargo.lock" -Raw | |
| $PackageBlocks = [regex]::Matches($CargoLock, '(?ms)^\[\[package\]\]\s*.*?(?=^\[\[package\]\]|\z)') | |
| $DependencyLockBlocks = foreach ($Block in $PackageBlocks) { | |
| $Value = $Block.Value.Trim() | |
| if ($Value -notmatch '(?m)^name\s*=\s*"audio-orbit"\s*$') { | |
| $Value | |
| } | |
| } | |
| if ($DependencyLockBlocks) { | |
| $CacheParts.Add("cargo-lock-dependency-packages") | |
| foreach ($Block in $DependencyLockBlocks) { | |
| $CacheParts.Add($Block) | |
| } | |
| } | |
| } | |
| $CacheParts.Add("rust-toolchain=stable") | |
| $CacheParts.Add("rust-target=$env:RUST_TARGET") | |
| $CacheInput = $CacheParts -join "`n`n" | |
| $CacheInputPath = Join-Path $env:RUNNER_TEMP "cargo-deps-cache-input.txt" | |
| Set-Content -Path $CacheInputPath -Value $CacheInput -NoNewline | |
| $CacheHash = (Get-FileHash $CacheInputPath -Algorithm SHA256).Hash.ToLowerInvariant() | |
| "CARGO_DEPS_CACHE_HASH=$CacheHash" >> $env:GITHUB_ENV | |
| Write-Host "Cargo dependency cache hash: $CacheHash" | |
| Write-Host "Cache ignores the Audio Orbit package version and invalidates only when dependency declarations or locked dependency packages change." | |
| - name: Cache Cargo dependencies | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.cargo/registry/index | |
| ~/.cargo/registry/cache | |
| ~/.cargo/git/db | |
| target/${{ env.RUST_TARGET }}/release/deps | |
| target/${{ env.RUST_TARGET }}/release/build | |
| target/${{ env.RUST_TARGET }}/release/.fingerprint | |
| key: cargo-${{ runner.os }}-${{ env.RUST_TARGET }}-stable-deps-${{ env.CARGO_DEPS_CACHE_HASH }} | |
| - name: Prepare release branch and commit version metadata | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $DefaultBranch = "${{ github.event.repository.default_branch }}" | |
| $CommitMessage = "chore: release $env:TAG" | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git fetch origin --prune --tags | |
| git checkout -B $DefaultBranch "origin/$DefaultBranch" | |
| git pull --ff-only origin $DefaultBranch | |
| $ExistingTag = git ls-remote --tags origin "refs/tags/$env:TAG" | |
| if ($ExistingTag) { | |
| Write-Error "Tag $env:TAG already exists. Choose a new release version." | |
| exit 1 | |
| } | |
| $ExistingReleaseBranch = git ls-remote --heads origin $env:RELEASE_BRANCH | |
| if ($ExistingReleaseBranch) { | |
| Write-Error "Release branch $env:RELEASE_BRANCH already exists. Delete it or choose a new release version." | |
| exit 1 | |
| } | |
| git checkout -b $env:RELEASE_BRANCH | |
| $CargoTomlPath = "Cargo.toml" | |
| $CargoToml = Get-Content $CargoTomlPath -Raw | |
| $CargoPattern = '(?ms)(^\[package\]\s*.*?^version\s*=\s*")[^"]+(")' | |
| if (-not [regex]::IsMatch($CargoToml, $CargoPattern)) { | |
| Write-Error "Could not find the package version in Cargo.toml." | |
| exit 1 | |
| } | |
| $CargoToml = [regex]::Replace($CargoToml, $CargoPattern, "`${1}$env:VERSION`${2}", 1) | |
| Set-Content -Path $CargoTomlPath -Value $CargoToml -NoNewline | |
| $ManifestPath = "src/audio-orbit.exe.manifest" | |
| if (Test-Path $ManifestPath) { | |
| $ManifestVersionMatch = [regex]::Match($env:VERSION, '^(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)') | |
| if (-not $ManifestVersionMatch.Success) { | |
| Write-Error "Could not derive a Windows manifest version from '$env:VERSION'." | |
| exit 1 | |
| } | |
| $ManifestVersion = "{0}.{1}.{2}.0" -f ` | |
| $ManifestVersionMatch.Groups["major"].Value, ` | |
| $ManifestVersionMatch.Groups["minor"].Value, ` | |
| $ManifestVersionMatch.Groups["patch"].Value | |
| $Manifest = Get-Content $ManifestPath -Raw | |
| $ManifestIdentityPattern = '(?s)(<assemblyIdentity\b(?=[^>]*\bname="AudioOrbit")[^>]*?\bversion=")[^"]+(")' | |
| if (-not [regex]::IsMatch($Manifest, $ManifestIdentityPattern)) { | |
| Write-Error "Could not find the AudioOrbit assemblyIdentity version in $ManifestPath." | |
| exit 1 | |
| } | |
| $Manifest = [regex]::Replace($Manifest, $ManifestIdentityPattern, "`${1}$ManifestVersion`${2}", 1) | |
| Set-Content -Path $ManifestPath -Value $Manifest -NoNewline | |
| } | |
| cargo generate-lockfile | |
| git add Cargo.toml Cargo.lock | |
| if (Test-Path $ManifestPath) { | |
| git add $ManifestPath | |
| } | |
| git status --short | |
| git commit --allow-empty -m $CommitMessage | |
| git push origin $env:RELEASE_BRANCH | |
| - name: Squash merge release branch before build | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $DefaultBranch = "${{ github.event.repository.default_branch }}" | |
| $CommitMessage = "chore: release $env:TAG" | |
| git checkout $DefaultBranch | |
| git pull --ff-only origin $DefaultBranch | |
| git merge --squash $env:RELEASE_BRANCH | |
| git diff --cached --quiet | |
| $HasStagedChanges = $LASTEXITCODE -ne 0 | |
| if (-not $HasStagedChanges) { | |
| git reset --hard HEAD | |
| $MergedSha = git rev-parse HEAD | |
| "MERGED_SHA=$MergedSha" >> $env:GITHUB_ENV | |
| "RELEASE_BRANCH_WAS_MERGED=false" >> $env:GITHUB_ENV | |
| Write-Host "Release branch $env:RELEASE_BRANCH has no version metadata changes to squash merge." | |
| Write-Host "Using existing $DefaultBranch commit $MergedSha for the release build." | |
| exit 0 | |
| } | |
| git status --short | |
| git commit -m $CommitMessage | |
| git push origin $DefaultBranch | |
| $MergedSha = git rev-parse HEAD | |
| "MERGED_SHA=$MergedSha" >> $env:GITHUB_ENV | |
| "RELEASE_BRANCH_WAS_MERGED=true" >> $env:GITHUB_ENV | |
| Write-Host "Squash merged $env:RELEASE_BRANCH into $DefaultBranch at $MergedSha." | |
| - name: Delete release branch after merge | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| git push origin --delete $env:RELEASE_BRANCH | |
| $LocalReleaseBranch = git branch --list $env:RELEASE_BRANCH | |
| if ($LocalReleaseBranch) { | |
| git branch -D $env:RELEASE_BRANCH | |
| } | |
| Write-Host "Deleted temporary release branch $env:RELEASE_BRANCH." | |
| - name: Build executable from merged release commit | |
| shell: pwsh | |
| run: cargo build --release --target $env:RUST_TARGET | |
| - name: Prepare release asset | |
| shell: pwsh | |
| run: | | |
| $ExeName = "audio-orbit-$env:TAG-windows-x64.exe" | |
| Copy-Item "target\$env:RUST_TARGET\release\audio-orbit.exe" $ExeName | |
| "ASSET=$ExeName" >> $env:GITHUB_ENV | |
| - name: Create GitHub release | |
| shell: pwsh | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| gh release create $env:TAG $env:ASSET ` | |
| --repo ${{ github.repository }} ` | |
| --target $env:MERGED_SHA ` | |
| --title $env:TAG ` | |
| --generate-notes |