Build and Release Gum Tool #25
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: Build and Release Gum Tool | |
| # When to execute (This means manual trigger only) | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| release_kind: | |
| description: 'Type' | |
| type: choice | |
| options: [release, prerelease, test] # test = draft | |
| default: test | |
| required: true | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| # Define the jobs to run | |
| jobs: | |
| build: | |
| # Which OS to run on | |
| runs-on: windows-latest | |
| # A list of "named" steps | |
| steps: | |
| # Pull down the source code | |
| # | |
| # actions/checkout's `submodules: recursive` does shallow (--depth=1) | |
| # submodule fetches. Sokol.NET's box2d submodule pins a SHA that lives | |
| # on the `sokol-dev` branch and is not reachable from box2d's default | |
| # branch (`main`), so a shallow fetch misses it. git then tries a | |
| # direct-SHA fetch, falls back to credentialed cloning, and dies with | |
| # "could not read Username" because GIT_TERMINAL_PROMPT=0 on runners. | |
| # Doing `git submodule update --init --recursive` ourselves does full | |
| # (non-shallow) clones, so the pinned SHA is always reachable. | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| # REF is not needed when using workflow_dispatch | |
| #ref: ${{ github.ref_name }} | |
| submodules: false | |
| # Supposedly needed for tags | |
| fetch-depth: 0 | |
| - name: Init submodules (non-shallow) | |
| run: git submodule update --init --recursive | |
| # Install .net dependency | |
| - name: Install .NET SDK | |
| uses: actions/setup-dotnet@v5 | |
| with: | |
| dotnet-version: '9.0.x' | |
| # Restore NuGet packages once. ExcludeIOS / ExcludeAndroid let the tool build | |
| # on CI runners that do not have the mobile workloads installed. Mobile-TFM | |
| # assemblies are skipped; consumers who need them build locally with the | |
| # workloads installed. | |
| # Uses GumFull.sln so the CLI is built and copied into the Gum tool output | |
| - name: Restore | |
| run: dotnet restore GumFull.sln -p:ExcludeIOS=true -p:ExcludeAndroid=true | |
| # Update AssemblyInfo.cs version dynamically to current date | |
| - name: Update AssemblyInfo.cs version | |
| shell: pwsh | |
| run: | | |
| $version = (Get-Date).ToString('yyyy.MM.dd') | |
| $currentYear = (Get-Date).Year | |
| $copyrightLine = "Copyright © FlatRedBall 2017-$currentYear." | |
| $file = "Gum/Properties/AssemblyInfo.cs" | |
| Write-Host "Updating version in $file to $version" | |
| (Get-Content $file) -replace 'AssemblyVersion\(".*"\)', "AssemblyVersion(`"$version`")" ` | |
| -replace 'AssemblyFileVersion\(".*"\)', "AssemblyFileVersion(`"$version`")" ` | |
| -replace 'AssemblyCopyright\(".*"\)', "AssemblyCopyright(`"$copyrightLine`")" | | |
| Set-Content $file | |
| Write-Host "Updated AssemblyInfo.cs to version $version" | |
| # Build GUM TOOL (GumFull.sln includes Gum.Cli, which gets copied into the tool output; | |
| # plugins use $(SolutionDir) in their post-build events so they must be built via the solution) | |
| - name: Build Gum Tool | |
| run: dotnet build GumFull.sln -c Release --no-restore -p:ExcludeIOS=true -p:ExcludeAndroid=true | |
| # Publish Gum as a framework-dependent single-file bundle. | |
| # All managed assemblies are bundled into Gum.exe; the .NET runtime is still required. | |
| # -r win-x64 is required for PublishSingleFile even when not self-contained. | |
| - name: Publish Gum (single-file) | |
| run: dotnet publish Gum/Gum.csproj -c Release -r win-x64 --self-contained false -p:PublishSingleFile=true -o dist/publish | |
| # Some content files (e.g. apos-shapes.xnb) are deposited into bin/Release/Content/ by | |
| # plugin post-build events (EditorTabPlugin_XNA copies the XNB from Runtimes/GumShapes/). | |
| # dotnet publish has no knowledge of files placed there outside of project items, so we | |
| # merge the full build-output Content/ folder into the publish output. | |
| - name: Copy content to publish output | |
| shell: pwsh | |
| run: | | |
| if (Test-Path "Gum/bin/Release/Content") { | |
| New-Item -ItemType Directory -Force -Path "dist/publish/Content" | Out-Null | |
| Copy-Item -Path "Gum/bin/Release/Content/*" -Destination "dist/publish/Content" -Recurse -Force | |
| } | |
| # Plugins are built by GumFull.sln and copied to bin/Release/Plugins via their own | |
| # post-build events (which use $(SolutionDir) and don't run during dotnet publish). | |
| # Use wildcard source so Copy-Item copies the *contents* into the destination folder | |
| # rather than nesting the folder inside it (which happens when the destination already exists). | |
| # dotnet publish creates dist/publish/Plugins/ for Plugins/readme.txt, so -Destination | |
| # "dist/publish/Plugins" already exists and a non-wildcard copy would produce Plugins/Plugins/. | |
| - name: Copy plugins to publish output | |
| shell: pwsh | |
| run: | | |
| if (Test-Path "Gum/bin/Release/Plugins") { | |
| New-Item -ItemType Directory -Force -Path "dist/publish/Plugins" | Out-Null | |
| Copy-Item -Path "Gum/bin/Release/Plugins/*" -Destination "dist/publish/Plugins" -Recurse -Force | |
| } | |
| - name: Copy GumCli to publish output | |
| shell: pwsh | |
| run: | | |
| if (Test-Path "Gum/bin/Release/GumCli") { | |
| New-Item -ItemType Directory -Force -Path "dist/publish/GumCli" | Out-Null | |
| Copy-Item -Path "Gum/bin/Release/GumCli/*" -Destination "dist/publish/GumCli" -Recurse -Force | |
| } | |
| # 1) Package the publish output | |
| - name: Package release | |
| shell: pwsh | |
| run: | | |
| New-Item -ItemType Directory -Force -Path dist | Out-Null | |
| $zip = "dist/Gum.zip" | |
| if (Test-Path $zip) { Remove-Item $zip -Force } | |
| Compress-Archive -Path "dist/publish/*" -DestinationPath $zip | |
| # 2) Compute tag + title depending on the kind of release | |
| - name: Compute tag + title | |
| id: meta | |
| shell: pwsh | |
| run: | | |
| $kind = "${{ github.event.inputs.release_kind }}" | |
| switch ($kind) { | |
| 'release' { $prefix='Release'; $prerelease='false'; $draft='false' } | |
| 'prerelease' { $prefix='PreRelease'; $prerelease='true'; $draft='false' } | |
| 'test' { $prefix='Test'; $prerelease='true'; $draft='true' } | |
| } | |
| $title = "$prefix " + (Get-Date).ToString('MMMM d, yyyy') | |
| $tag = "${prefix}_" + (Get-Date).ToString('MMMM_dd_yyyy') | |
| if ($kind -eq 'test') { $tag = "$tag-$($env:GITHUB_RUN_NUMBER)" } | |
| "tag=$tag" >> $env:GITHUB_OUTPUT | |
| "title=$title" >> $env:GITHUB_OUTPUT | |
| "prerelease=$prerelease" >> $env:GITHUB_OUTPUT | |
| "draft=$draft" >> $env:GITHUB_OUTPUT | |
| # 2b) Capture the previous tag BEFORE this run creates its own tag, so the | |
| # auto-generated release notes diff against the actual prior release. Without this, | |
| # GitHub picks the comparison base itself, and the <Prefix>_<Month>_DD_YYYY tag scheme | |
| # uses month *names* (which don't sort chronologically), so its guess can reach far | |
| # back (e.g. a months-old prerelease turning up as the base for a new prerelease) | |
| # and dump unrelated PRs into the notes. | |
| # checkout used fetch-depth: 0, so all tags are present; the new tag does not exist | |
| # yet at this step. | |
| # | |
| # - Full releases summarize everything since the last full release (including work | |
| # already shipped in an intermediate prerelease), so the base is restricted to | |
| # Release_* tags only. | |
| # - Prerelease/test builds want the diff since whatever was published most recently, | |
| # full release or prerelease, so the base is the newest tag across all of | |
| # Release_*/PreRelease_*/Hotfix_* by tagger date. | |
| - name: Capture previous release tag | |
| id: prevtag | |
| shell: pwsh | |
| run: | | |
| $kind = "${{ github.event.inputs.release_kind }}" | |
| $pattern = if ($kind -eq 'release') { 'refs/tags/Release_*' } else { 'refs/tags/Release_*','refs/tags/PreRelease_*','refs/tags/Hotfix_*' } | |
| $prev = git for-each-ref --sort=-creatordate --format='%(refname:short)' $pattern | | |
| Where-Object { $_ -ne "${{ steps.meta.outputs.tag }}" } | | |
| Select-Object -First 1 | |
| if ([string]::IsNullOrWhiteSpace($prev)) { | |
| Write-Host "No previous tag found; GitHub will auto-select the comparison base." | |
| } else { | |
| Write-Host "Previous tag for notes comparison: $prev" | |
| } | |
| "prev_tag=$prev" >> $env:GITHUB_OUTPUT | |
| # 3) Commit version bump on a new branch and create tag | |
| - name: Commit version bump, create tag, push branch+tag | |
| if: github.event.inputs.release_kind == 'release' | |
| shell: pwsh | |
| run: | | |
| git config user.name "github-actions" | |
| git config user.email "actions@github.com" | |
| $branchName = "version-bump/${{ steps.meta.outputs.tag }}" | |
| Write-Host "Creating branch $branchName" | |
| git checkout -b "$branchName" | |
| Write-Host "Committing version bump..." | |
| git add Gum/Properties/AssemblyInfo.cs | |
| git commit -m "Bump version to ${{ steps.meta.outputs.tag }}" || echo "No changes to commit" | |
| Write-Host "Creating annotated tag..." | |
| git tag -a "${{ steps.meta.outputs.tag }}" -m "${{ steps.meta.outputs.title }}" | |
| Write-Host "Pushing branch and tag..." | |
| git push origin "$branchName" | |
| git push origin "${{ steps.meta.outputs.tag }}" | |
| # 3b) Create PR for version bump | |
| - name: Create pull request for version bump | |
| if: github.event.inputs.release_kind == 'release' | |
| uses: peter-evans/create-pull-request@v7 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| branch: version-bump/${{ steps.meta.outputs.tag }} | |
| title: "Bump version to ${{ steps.meta.outputs.tag }}" | |
| body: | | |
| This PR was automatically created by the Build and Release workflow. | |
| - Bumps `Gum/Properties/AssemblyInfo.cs` to version **${{ steps.meta.outputs.tag }}** | |
| - Release title: **${{ steps.meta.outputs.title }}** | |
| base: main | |
| labels: | | |
| automated | |
| version-bump | |
| # 4) Create GitHub Release and attach the zip | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ steps.meta.outputs.tag }} | |
| name: ${{ steps.meta.outputs.title }} | |
| generate_release_notes: true | |
| # Pin the comparison base so the auto-notes diff against the actual prior tag | |
| # (see "Capture previous release tag") instead of GitHub's own guess. Empty only | |
| # if no prior tag was found, which softprops treats as omitted. | |
| previous_tag: ${{ steps.prevtag.outputs.prev_tag }} | |
| prerelease: ${{ steps.meta.outputs.prerelease }} | |
| draft: ${{ steps.meta.outputs.draft }} | |
| files: | | |
| dist/Gum.zip |