Skip to content

Build and Release Gum Tool #25

Build and Release Gum Tool

Build and Release Gum Tool #25

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