Skip to content

Release

Release #28

Workflow file for this run

name: Release
on:
workflow_dispatch:
inputs:
platform-linux:
description: Include Linux
default: true
type: boolean
platform-android:
description: Include Android
default: true
type: boolean
platform-ios:
description: Include iOS
default: true
type: boolean
platform-macos:
description: Include macOS
default: true
type: boolean
sign:
description: Code sign packages
default: true
type: boolean
build-prerequisites-installer:
description: Build prerequisites installer
default: true
type: boolean
version-suffix:
description: Prerelease suffix, no leading dash (e.g. "beta" -> 4.4.123-beta). Empty = stable.
default: ''
type: string
deploy:
description: Deploy to NuGet.org and create GitHub Release
default: false
type: boolean
bump-version:
description: After a stable deploy, commit the next Patch to the branch (open next dev version)
default: true
type: boolean
upload-binlog:
description: Capture and upload an MSBuild binlog (parallel/race diagnostics). Ignored on signed builds — a binlog records env vars/task params and would leak signing secrets.
default: false
type: boolean
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
jobs:
#
# Validate inputs and permissions
#
Validate:
name: Validate
runs-on: ubuntu-latest
steps:
- name: Check deploy requires signing
if: ${{ inputs.deploy && !inputs.sign }}
run: |
echo "::error::Cannot deploy unsigned packages. Enable 'sign' to deploy."
exit 1
- name: Check release permissions
if: ${{ inputs.sign || inputs.deploy }}
env:
GH_TOKEN: ${{ secrets.GH_PAT }}
run: |
ORG=$(echo "${{ github.repository }}" | cut -d/ -f1)
TEAM="stride-release-managers"
USER="${{ github.actor }}"
STATUS=$(gh api "orgs/$ORG/teams/$TEAM/memberships/$USER" --silent 2>&1 && echo "ok" || echo "fail")
if [ "$STATUS" = "fail" ]; then
echo "::error::User $USER is not a member of $ORG/$TEAM. Sign/deploy requires stride-release-managers team membership."
exit 1
fi
#
# Build and package
#
Package:
name: Package
needs: Validate
runs-on: windows-2025-vs2026
environment: production
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v4
with:
lfs: true
fetch-depth: 0 # Full history + tags needed for tag-based versioning
fetch-tags: true
- uses: actions/setup-dotnet@v4
with:
# 6.0.x required by deps/Gettext.Net/GNU.Gettext.Msgfmt.exe (see #3113)
dotnet-version: |
6.0.x
10.0.x
- name: Install Apple Workloads
# VS provisions the android workload but not ios/macos.
if: ${{ inputs.platform-ios || inputs.platform-macos }}
uses: ./.github/actions/stride-workload
with:
workloads: ${{ inputs.platform-ios && inputs.platform-macos && 'ios macos' || inputs.platform-ios && 'ios' || 'macos' }}
- name: Install Advanced Installer
if: ${{ inputs.build-prerequisites-installer }}
shell: pwsh
run: |
Invoke-WebRequest -Uri "https://www.advancedinstaller.com/downloads/22.0/advinst.msi" -OutFile advinst.msi
msiexec /i advinst.msi /qn ADDLOCAL=ALL | Out-Null
# Register license if provided
if ("${{ secrets.ADVINST_LICENSE_KEY }}" -ne "") {
& "${env:ProgramFiles(x86)}\Caphyon\Advanced Installer 22.0\bin\x86\AdvancedInstaller.com" /register "${{ secrets.ADVINST_LICENSE_KEY }}"
}
- name: Build Package
run: |
$platforms = "Windows"
if ("${{ inputs.platform-linux }}" -eq "true") { $platforms += ";Linux" }
if ("${{ inputs.platform-android }}" -eq "true") { $platforms += ";Android" }
if ("${{ inputs.platform-ios }}" -eq "true") { $platforms += ";iOS" }
if ("${{ inputs.platform-macos }}" -eq "true") { $platforms += ";macOS" }
echo "Building for platforms: $platforms"
# --no-restore: Stride.build's targets restore their own solutions (same as the
# previous desktop msbuild invocation, which never passed /restore).
dotnet build build\Stride.build `
-t:Package `
--no-restore `
${{ inputs.upload-binlog && !inputs.sign && '-bl' || '' }} -m -nr:false `
-p:StridePlatforms=$($platforms.Replace(';','%3B')) `
-p:StrideGraphicsApiDependentBuildAll=true `
-p:StrideSign=${{ inputs.sign }} `
-p:StrideVersionSuffix=${{ inputs.version-suffix }} `
-p:StrideBuildPrerequisitesInstaller=${{ inputs.build-prerequisites-installer }} `
-p:StrideNativeBuildMode=Clang
env:
StrideDisableAssetCompilerExecServerProxy: true
StrideSignTenantId: ${{ inputs.sign && secrets.STRIDE_SIGN_TENANT_ID || '' }}
StrideSignClientId: ${{ inputs.sign && secrets.STRIDE_SIGN_CLIENT_ID || '' }}
StrideSignClientSecret: ${{ inputs.sign && secrets.STRIDE_SIGN_CLIENT_SECRET || '' }}
StrideSignKeyVaultCertificate: ${{ inputs.sign && secrets.STRIDE_SIGN_KEYVAULT_CERTIFICATE || '' }}
StrideSignKeyVaultName: ${{ inputs.sign && secrets.STRIDE_SIGN_KEYVAULT_NAME || '' }}
- name: Detect version
id: version
shell: pwsh
run: |
$pkg = Get-ChildItem -Path bin/packages -Filter "Stride.Core.*.nupkg" | Select-Object -First 1
if (-not $pkg) {
echo "::error::No Stride.Core package found"
exit 1
}
$version = $pkg.Name -replace 'Stride\.Core\.(.*?)\.nupkg','$1'
# Guard against a malformed SharedAssemblyInfo.cs slipping a weird version into the package name.
if ($version -notmatch '^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$') {
echo "::error::Detected package version '$version' is not a valid version - check SharedAssemblyInfo.cs"
exit 1
}
echo "version=$version" >> $env:GITHUB_OUTPUT
echo "::notice::Package version: $version"
# Forget-to-bump guard: the version is the committed value in SharedAssemblyInfo.cs, bumped per release. If it
# wasn't bumped, its releases/<version> tag already exists on the previously-released commit, so fail the
# deploy. Re-running the release on the exact tagged commit (idempotent) is allowed (tag points at HEAD).
- name: Guard against re-publishing an existing version
if: ${{ inputs.deploy }}
run: |
TAG="releases/${{ steps.version.outputs.version }}"
if git rev-parse "$TAG" >/dev/null 2>&1 && [ -z "$(git tag --points-at HEAD --list "$TAG")" ]; then
echo "::error::Version ${{ steps.version.outputs.version }} is already released (tag $TAG on another commit). Bump Patch/NuGetVersionSuffix in SharedAssemblyInfo.cs."
exit 1
fi
- name: Upload NuGet packages
uses: actions/upload-artifact@v4
with:
name: packages
path: bin/packages/*.nupkg
if-no-files-found: error
- name: Upload build log
uses: actions/upload-artifact@v4
if: ${{ always() && inputs.upload-binlog && !inputs.sign }}
with:
name: build-log
path: msbuild.binlog
if-no-files-found: ignore
#
# Surface the package version in the run UI. A job can't reference its own outputs in its
# name, so this lightweight job shows Package's version even when Deploy is skipped.
#
Version:
name: Packaged ${{ needs.Package.outputs.version }}
needs: Package
if: ${{ always() && needs.Package.result == 'success' }}
runs-on: ubuntu-latest
steps:
- run: echo "::notice title=Packaged::${{ needs.Package.outputs.version }}"
#
# Deploy to NuGet.org and create GitHub Release
#
Deploy:
name: Deploy ${{ needs.Package.outputs.version }}
if: ${{ inputs.deploy && inputs.sign }}
needs: Package
permissions:
contents: write
uses: ./.github/workflows/release-deploy.yml
with:
run-id: ${{ github.run_id }}
bump-version: ${{ inputs.bump-version }}
secrets: inherit