Release #21
Workflow file for this run
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 | |
| 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 | |
| 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 ($pkg) { | |
| $version = $pkg.Name -replace 'Stride\.Core\.(.*?)\.nupkg','$1' | |
| echo "version=$version" >> $env:GITHUB_OUTPUT | |
| echo "::notice::Package version: $version" | |
| } else { | |
| echo "::error::No Stride.Core package found" | |
| exit 1 | |
| } | |
| # Backstop: a stale/fallback version (e.g. tags not fetched) would re-use an already-released | |
| # number. The version is max(reachable tags)+1, so its tag should not exist unless we're rebuilding | |
| # the exact tagged commit. Fail a deploy that would re-publish a number tagged on another commit. | |
| - 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 }} already exists as $TAG on a different commit (tags likely not fetched). Aborting deploy." | |
| 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 | |
| runs-on: ubuntu-latest # NuGet push doesn't need Windows | |
| environment: production # Requires manual approval in GitHub settings | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| fetch-tags: true # Tag release step checks for an existing releases/<version> tag | |
| - uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '10.0.x' | |
| - name: Download packages | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: packages | |
| path: bin/packages | |
| - name: List packages | |
| shell: pwsh | |
| run: | | |
| echo "## Packages to deploy" >> $env:GITHUB_STEP_SUMMARY | |
| echo '```' >> $env:GITHUB_STEP_SUMMARY | |
| Get-ChildItem -Path bin -Recurse -Filter "*.nupkg" | ForEach-Object { | |
| echo "$($_.Name)" >> $env:GITHUB_STEP_SUMMARY | |
| } | |
| echo '```' >> $env:GITHUB_STEP_SUMMARY | |
| - name: Push NuGet packages | |
| shell: pwsh | |
| run: | | |
| $packages = Get-ChildItem -Path bin/packages -Filter "*.nupkg" | |
| $main = $packages | Where-Object { $_.Name -notmatch 'GameStudio' -and $_.Name -notmatch 'Samples\.Templates' } | |
| $samples = $packages | Where-Object { $_.Name -match 'Samples\.Templates' } | |
| $gameStudio = $packages | Where-Object { $_.Name -match 'GameStudio' } | |
| echo "::group::Pushing main packages ($($main.Count))" | |
| foreach ($pkg in $main) { | |
| echo "Pushing $($pkg.Name)..." | |
| dotnet nuget push $pkg.FullName --api-key $env:STRIDE_NUGET_API_KEY --source "https://api.nuget.org/v3/index.json" --timeout 1800 --skip-duplicate | |
| } | |
| echo "::endgroup::" | |
| if ($samples) { | |
| echo "::group::Pushing Samples.Templates" | |
| foreach ($pkg in $samples) { | |
| dotnet nuget push $pkg.FullName --api-key $env:STRIDE_NUGET_API_KEY --source "https://api.nuget.org/v3/index.json" --timeout 1800 --skip-duplicate | |
| } | |
| echo "::endgroup::" | |
| } | |
| if ($gameStudio) { | |
| echo "::group::Pushing GameStudio (last, so dependencies are already available)" | |
| foreach ($pkg in $gameStudio) { | |
| dotnet nuget push $pkg.FullName --api-key $env:STRIDE_NUGET_API_KEY --source "https://api.nuget.org/v3/index.json" --timeout 1800 | |
| } | |
| echo "::endgroup::" | |
| } | |
| env: | |
| STRIDE_NUGET_API_KEY: ${{ secrets.STRIDE_NUGET_API_KEY }} | |
| - name: Tag release | |
| run: | | |
| if git rev-parse "releases/${{ needs.Package.outputs.version }}" >/dev/null 2>&1; then | |
| echo "Tag releases/${{ needs.Package.outputs.version }} already exists, skipping" | |
| else | |
| git tag "releases/${{ needs.Package.outputs.version }}" | |
| git push origin "releases/${{ needs.Package.outputs.version }}" | |
| fi | |
| - name: Create GitHub Release | |
| run: | | |
| gh release create "releases/${{ needs.Package.outputs.version }}" \ | |
| --title "Stride ${{ needs.Package.outputs.version }}" \ | |
| --generate-notes \ | |
| bin/packages/*.nupkg | |
| env: | |
| GH_TOKEN: ${{ secrets.GH_PAT }} | |
| - name: Publish summary | |
| if: success() | |
| shell: pwsh | |
| env: | |
| VERSION: ${{ needs.Package.outputs.version }} | |
| REPO_URL: ${{ github.server_url }}/${{ github.repository }} | |
| run: | | |
| $v = $env:VERSION | |
| echo "## Published $v" >> $env:GITHUB_STEP_SUMMARY | |
| echo "" >> $env:GITHUB_STEP_SUMMARY | |
| echo "[GitHub Release]($env:REPO_URL/releases/tag/releases/$v)" >> $env:GITHUB_STEP_SUMMARY | |
| echo "" >> $env:GITHUB_STEP_SUMMARY | |
| echo "| Package | nuget.org |" >> $env:GITHUB_STEP_SUMMARY | |
| echo "|---|---|" >> $env:GITHUB_STEP_SUMMARY | |
| Get-ChildItem -Path bin/packages -Filter "*.nupkg" | Sort-Object Name | ForEach-Object { | |
| $id = $_.Name -replace "\.$([regex]::Escape($v))\.nupkg$", '' | |
| echo "| $id | https://www.nuget.org/packages/$id/$v |" >> $env:GITHUB_STEP_SUMMARY | |
| } |