From 11eb9de0cfd3578aeca543f289ac7944d92fff97 Mon Sep 17 00:00:00 2001 From: zignd Date: Sat, 7 Nov 2026 01:21:49 -0300 Subject: [PATCH 01/26] ci: run the Windows leg on the fleet's Windows VM The self-hosted matrix left Windows out because there was no Windows machine. The fleet now has one: a Windows Server 2022 VM on the Linux server, registered as an ephemeral runner with the Windows labels. The leg keeps its hosted fallback, so unsetting TRACE_CI_RUNNERS still gives the four hosted legs. --- .github/workflows/main.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c5c60f3a09b..e8f671fcc04 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,15 +14,15 @@ jobs: build: name: build-${{ matrix.platform }} # TRACE_CI_RUNNERS picks GitHub-hosted runners (unset) or the local fleet (`self-hosted`); see - # docs/self-hosted-runners.md in Trace Arena. Under the flag only the legs the local fleet can host exist: - # there is no Windows machine, and the arm64 Linux pool is a 1.5 GB container that cannot build the native - # runtime. Hosted CI keeps all four legs. - runs-on: ${{ vars.TRACE_CI_RUNNERS == 'self-hosted' && (matrix.platform == 'macos' && fromJSON('["self-hosted","macOS","ARM64"]') || fromJSON('["self-hosted","Linux","X64","yiazmat-linux-x64-native"]')) || (matrix.platform == 'windows' && 'windows-latest' || matrix.platform == 'macos' && 'macos-latest' || matrix.platform == 'linux-arm' && 'ubuntu-24.04-arm' || 'ubuntu-latest') }} + # docs/self-hosted-runners.md in Trace Arena. Under the flag the legs the local fleet can host exist: the Windows VM on the + # Linux server, the native x64 Linux server and the macOS machine. The arm64 Linux pool is a 1.5 GB container that cannot build + # the native runtime, so that leg stays hosted-only. Hosted CI keeps all four legs. + runs-on: ${{ vars.TRACE_CI_RUNNERS == 'self-hosted' && (matrix.platform == 'windows' && fromJSON('["self-hosted","Windows","X64"]') || matrix.platform == 'macos' && fromJSON('["self-hosted","macOS","ARM64"]') || fromJSON('["self-hosted","Linux","X64","yiazmat-linux-x64-native"]')) || (matrix.platform == 'windows' && 'windows-latest' || matrix.platform == 'macos' && 'macos-latest' || matrix.platform == 'linux-arm' && 'ubuntu-24.04-arm' || 'ubuntu-latest') }} env: MONOGAME_WINDOWS_PLATFORM_TOOLSET: ${{ matrix.platform == 'windows' && 'v145' || '' }} strategy: matrix: - platform: ${{ fromJSON(vars.TRACE_CI_RUNNERS == 'self-hosted' && '["macos","linux"]' || '["windows","macos","linux","linux-arm"]') }} + platform: ${{ fromJSON(vars.TRACE_CI_RUNNERS == 'self-hosted' && '["windows","macos","linux"]' || '["windows","macos","linux","linux-arm"]') }} fail-fast: false defaults: run: From 9cd92649632af1a605186609497dfa7a07e38cb9 Mon Sep 17 00:00:00 2001 From: zignd Date: Sat, 7 Nov 2026 01:38:01 -0300 Subject: [PATCH 02/26] ci: re-run the Windows leg after the runner toolchain fix From b9ceb5d0d17c8d18ed1d0bc33971ed97d7b84b14 Mon Sep 17 00:00:00 2001 From: zignd Date: Sat, 7 Nov 2026 02:42:05 -0300 Subject: [PATCH 03/26] ci: re-run the Windows leg with bash on PATH From 9443b8f8079400b02ab90bb93298bd18abdf4fc1 Mon Sep 17 00:00:00 2001 From: zignd Date: Sat, 7 Nov 2026 02:48:47 -0300 Subject: [PATCH 04/26] ci: re-run the Windows leg after the PATH refresh From 316540b694ad322a518a1a20f29f04a4cc2d5062 Mon Sep 17 00:00:00 2001 From: zignd Date: Sat, 7 Nov 2026 02:57:47 -0300 Subject: [PATCH 05/26] ci: re-run the Windows leg with jq available From cb0142a2ca475c5708dbfa6a4806ecbd8bc81f2f Mon Sep 17 00:00:00 2001 From: zignd Date: Sat, 7 Nov 2026 03:07:49 -0300 Subject: [PATCH 06/26] ci: re-run the Windows leg with cmake and ninja From fa968d8d6717617201c8e3565a85bc863e91b218 Mon Sep 17 00:00:00 2001 From: zignd Date: Sat, 7 Nov 2026 03:18:23 -0300 Subject: [PATCH 07/26] ci: re-run the Windows leg with the toolchain on the runner PATH From b6a0cd3c4c7f2ccf73a894eb61d95c45bc93a60c Mon Sep 17 00:00:00 2001 From: zignd Date: Sat, 7 Nov 2026 03:34:47 -0300 Subject: [PATCH 08/26] ci: re-run the Windows leg with the C++ toolchain From bbc964bce10b423939e5cb884f4786160ae2865c Mon Sep 17 00:00:00 2001 From: zignd Date: Sat, 7 Nov 2026 12:54:05 -0300 Subject: [PATCH 09/26] ci: re-run the Windows leg with the ARM64 C++ tools From ce6f35c590f5d01eb336e32264b0209723a62dfd Mon Sep 17 00:00:00 2001 From: zignd Date: Sat, 7 Nov 2026 13:16:07 -0300 Subject: [PATCH 10/26] ci: use the v143 toolset on the self-hosted Windows VM The hosted image ships the v145 platform toolset; the fleet's Windows VM has VS 2022 Build Tools, which is v143. The hosted legs keep v145. --- .github/workflows/main.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e8f671fcc04..2345caac886 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,7 +19,8 @@ jobs: # the native runtime, so that leg stays hosted-only. Hosted CI keeps all four legs. runs-on: ${{ vars.TRACE_CI_RUNNERS == 'self-hosted' && (matrix.platform == 'windows' && fromJSON('["self-hosted","Windows","X64"]') || matrix.platform == 'macos' && fromJSON('["self-hosted","macOS","ARM64"]') || fromJSON('["self-hosted","Linux","X64","yiazmat-linux-x64-native"]')) || (matrix.platform == 'windows' && 'windows-latest' || matrix.platform == 'macos' && 'macos-latest' || matrix.platform == 'linux-arm' && 'ubuntu-24.04-arm' || 'ubuntu-latest') }} env: - MONOGAME_WINDOWS_PLATFORM_TOOLSET: ${{ matrix.platform == 'windows' && 'v145' || '' }} + # v145 ships with the newest Visual Studio; the fleet's Windows VM has VS 2022 Build Tools, whose toolset is v143. + MONOGAME_WINDOWS_PLATFORM_TOOLSET: ${{ matrix.platform == 'windows' && (vars.TRACE_CI_RUNNERS == 'self-hosted' && 'v143' || 'v145') || '' }} strategy: matrix: platform: ${{ fromJSON(vars.TRACE_CI_RUNNERS == 'self-hosted' && '["windows","macos","linux"]' || '["windows","macos","linux","linux-arm"]') }} From 6f215b2e8f7ed3921f6d5ae37968cdad465c87c4 Mon Sep 17 00:00:00 2001 From: zignd Date: Sat, 7 Nov 2026 13:39:45 -0300 Subject: [PATCH 11/26] build: let vswhere find a Build Tools-only install The content pipeline target asks vswhere for MSBuild without -products *, and vswhere's default product filter skips the standalone Build Tools. On the fleet's Windows VM that returned an empty path and the build ran an empty command; the hosted image has full Visual Studio, so it never showed there. --- .../MonoGame.Framework.Content.Pipeline.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MonoGame.Framework.Content.Pipeline/MonoGame.Framework.Content.Pipeline.csproj b/MonoGame.Framework.Content.Pipeline/MonoGame.Framework.Content.Pipeline.csproj index 0b70c82ff3e..f2cf1637b29 100644 --- a/MonoGame.Framework.Content.Pipeline/MonoGame.Framework.Content.Pipeline.csproj +++ b/MonoGame.Framework.Content.Pipeline/MonoGame.Framework.Content.Pipeline.csproj @@ -141,7 +141,8 @@ /p:PlatformToolset=$(MONOGAME_WINDOWS_PLATFORM_TOOLSET) - + + From 41f2bf2dd49a4d7d777444398b158123936ea899 Mon Sep 17 00:00:00 2001 From: zignd Date: Sat, 7 Nov 2026 14:02:34 -0300 Subject: [PATCH 12/26] build: find a Build Tools-only install when checking the native library Cake's VSWhereLatest uses vswhere's default product filter, which skips the standalone Build Tools, so the content pipeline's library check dereferenced a null path on the fleet's Windows VM. Products = "*" finds it; the hosted image has full Visual Studio and is unaffected. --- build/Utils/StaticLibCheck.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build/Utils/StaticLibCheck.cs b/build/Utils/StaticLibCheck.cs index 5eb0f408daf..ac656a3b1a2 100644 --- a/build/Utils/StaticLibCheck.cs +++ b/build/Utils/StaticLibCheck.cs @@ -48,7 +48,8 @@ public static class StaticLibCheck public static void CheckWindows(BuildContext context, string filePath) { var vswhere = new VSWhereLatest(context.FileSystem, context.Environment, context.ProcessRunner, context.Tools); - var devcmdPath = vswhere.Latest(new VSWhereLatestSettings()).FullPath + @"\Common7\Tools\vsdevcmd.bat"; + // Products = "*" so a Build Tools-only machine (the fleet's Windows VM) is found: the default product filter skips it. + var devcmdPath = vswhere.Latest(new VSWhereLatestSettings { Products = "*" }).FullPath + @"\Common7\Tools\vsdevcmd.bat"; context.StartProcess( devcmdPath, From 130c51a691b1ba800783b936bbf200922b71ed01 Mon Sep 17 00:00:00 2001 From: zignd Date: Sat, 7 Nov 2026 14:36:17 -0300 Subject: [PATCH 13/26] ci: re-run the Windows leg with the SDK resolver directory set From 7d9b0b7241884485cb21d4c33c247e0687163af8 Mon Sep 17 00:00:00 2001 From: zignd Date: Sat, 7 Nov 2026 14:58:29 -0300 Subject: [PATCH 14/26] ci: re-run the Windows leg with the dotnet install location in the registry From 577bbc0b4b63b005b663f24fb8769ccfbb5f5b39 Mon Sep 17 00:00:00 2001 From: zignd Date: Sat, 7 Nov 2026 20:31:04 -0300 Subject: [PATCH 15/26] ci: re-run the Windows leg after the server came back From 1408cb1eb856400616b3c88e68f0a822fa70112a Mon Sep 17 00:00:00 2001 From: zignd Date: Sat, 7 Nov 2026 21:10:21 -0300 Subject: [PATCH 16/26] ci: re-run the Windows leg with the VS .NET SDK component From d717f5b5e1b48021efad677dd4f7935556b4cc83 Mon Sep 17 00:00:00 2001 From: zignd Date: Sat, 7 Nov 2026 21:40:34 -0300 Subject: [PATCH 17/26] build: skip the VSIX templates where the Visual Studio IDE SDK is absent The VS templates project needs the full Visual Studio IDE (VSSDK1077: no extensions directory), which the fleet's Windows VM does not have: it carries the Build Tools, which is what the framework, the tools and the content pipeline need. The workflow sets MONOGAME_SKIP_VS_TEMPLATES for the self-hosted Windows leg; the hosted legs keep building the extension. --- .github/workflows/main.yml | 3 +++ build/BuildTemplatesTasks/BuildVSTemplatesTask.cs | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2345caac886..966aefd7914 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,6 +19,9 @@ jobs: # the native runtime, so that leg stays hosted-only. Hosted CI keeps all four legs. runs-on: ${{ vars.TRACE_CI_RUNNERS == 'self-hosted' && (matrix.platform == 'windows' && fromJSON('["self-hosted","Windows","X64"]') || matrix.platform == 'macos' && fromJSON('["self-hosted","macOS","ARM64"]') || fromJSON('["self-hosted","Linux","X64","yiazmat-linux-x64-native"]')) || (matrix.platform == 'windows' && 'windows-latest' || matrix.platform == 'macos' && 'macos-latest' || matrix.platform == 'linux-arm' && 'ubuntu-24.04-arm' || 'ubuntu-latest') }} env: + # The VSIX templates project needs the full Visual Studio IDE SDK, which the fleet's VM (Build Tools) does not have. The hosted + # legs keep building it. + MONOGAME_SKIP_VS_TEMPLATES: ${{ vars.TRACE_CI_RUNNERS == 'self-hosted' && matrix.platform == 'windows' && '1' || '' }} # v145 ships with the newest Visual Studio; the fleet's Windows VM has VS 2022 Build Tools, whose toolset is v143. MONOGAME_WINDOWS_PLATFORM_TOOLSET: ${{ matrix.platform == 'windows' && (vars.TRACE_CI_RUNNERS == 'self-hosted' && 'v143' || 'v145') || '' }} strategy: diff --git a/build/BuildTemplatesTasks/BuildVSTemplatesTask.cs b/build/BuildTemplatesTasks/BuildVSTemplatesTask.cs index 83f801ae24b..5749150295b 100644 --- a/build/BuildTemplatesTasks/BuildVSTemplatesTask.cs +++ b/build/BuildTemplatesTasks/BuildVSTemplatesTask.cs @@ -6,7 +6,10 @@ namespace BuildScripts; [IsDependentOn(typeof(BuildDotNetTemplatesTask))] public sealed class BuildVSTemplatesTask : FrostingTask { - public override bool ShouldRun(BuildContext context) => context.IsRunningOnWindows(); + // The VSIX project needs the full Visual Studio IDE SDK (VSSDK1077: no extensions directory on a Build Tools-only machine). + // The workflow sets the variable for the self-hosted Windows VM, which has the Build Tools; the hosted legs keep building it. + public override bool ShouldRun(BuildContext context) => + context.IsRunningOnWindows() && Environment.GetEnvironmentVariable("MONOGAME_SKIP_VS_TEMPLATES") != "1"; public override void Run(BuildContext context) { From e294d266acc83faa9038ac34a6c929a380d0a72b Mon Sep 17 00:00:00 2001 From: zignd Date: Sat, 7 Nov 2026 22:11:51 -0300 Subject: [PATCH 18/26] test(tools): assert the relative-path round trip, not the hosted drive layout The parser stores each directory relative to the working directory. On the hosted runners the workspace is on D: and C:/This/Does/Not/Exist cannot be relativized, so the path stays absolute and the old assertion passed; on the fleet's Windows VM the workspace is on C: and the parser produces a relative path. The test now asserts what the parser promises. --- Tools/MonoGame.Tools.Tests/BuilderTests.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Tools/MonoGame.Tools.Tests/BuilderTests.cs b/Tools/MonoGame.Tools.Tests/BuilderTests.cs index 0bd604714e6..4f1bf444304 100644 --- a/Tools/MonoGame.Tools.Tests/BuilderTests.cs +++ b/Tools/MonoGame.Tools.Tests/BuilderTests.cs @@ -45,7 +45,10 @@ public void CommandLineParserTests() Assert.AreEqual(MakePath(Directory.GetCurrentDirectory(), "obj\\Content"), args.RootedIntermediateDirectory); args = ContentBuilderParams.Parse("build", "-s", "C:/This/Does/Not/Exist"); - Assert.AreEqual(MakePath("C:/This/Does/Not/Exist"), args.SourceDirectory); + // The parser stores the directory relative to the working directory. On a different drive it cannot, and the path stays + // absolute (the hosted runners work on D:); on the same drive it relativizes (the fleet's VM works on C:). Assert the + // round trip, not the hosted image's drive layout. + Assert.AreEqual(MakePath(Path.GetRelativePath(Directory.GetCurrentDirectory(), "C:/This/Does/Not/Exist")), args.SourceDirectory); args = ContentBuilderParams.Parse( "build", From 36e8352c750fa21229450e8faa2bdb081f4efee0 Mon Sep 17 00:00:00 2001 From: zignd Date: Sat, 7 Nov 2026 22:39:02 -0300 Subject: [PATCH 19/26] ci: re-run the Windows leg with python3 available From 52e2186c8b5937cef77b129950bf3dd4080d6822 Mon Sep 17 00:00:00 2001 From: zignd Date: Sat, 7 Nov 2026 23:17:23 -0300 Subject: [PATCH 20/26] ci: re-run the Windows leg with the android workload From 3e79255a86018c4f8b35ffc6b93649d1d0a44498 Mon Sep 17 00:00:00 2001 From: zignd Date: Sat, 7 Nov 2026 23:50:31 -0300 Subject: [PATCH 21/26] ci: re-run the Windows leg after clearing the runner action cache From 497596c20814b1deaa0162d0456fe5e4702dabd9 Mon Sep 17 00:00:00 2001 From: zignd Date: Sun, 8 Nov 2026 00:23:27 -0300 Subject: [PATCH 22/26] ci: re-run the Windows leg with the Android SDK From 01150e22a01692f99010b9e0d8824d750c7cd731 Mon Sep 17 00:00:00 2001 From: zignd Date: Sun, 8 Nov 2026 00:56:49 -0300 Subject: [PATCH 23/26] ci: re-run the Windows leg with the Android SDK packages From b6545ee76defb53975cb446825f7c472fe168bf2 Mon Sep 17 00:00:00 2001 From: zignd Date: Sun, 8 Nov 2026 01:30:14 -0300 Subject: [PATCH 24/26] ci: re-run the Windows leg with the ios and maccatalyst workloads From 5f9e36d22c676abc587c92cf3d7696259072596b Mon Sep 17 00:00:00 2001 From: zignd Date: Sun, 8 Nov 2026 11:19:57 -0300 Subject: [PATCH 25/26] ci: re-run after installing the Metal toolchain on the macOS runner From 4bf159b0d05ff7897c3afb7d6410d3a9e9ae2b7b Mon Sep 17 00:00:00 2001 From: zignd Date: Sun, 8 Nov 2026 12:21:48 -0300 Subject: [PATCH 26/26] test: clear the cached template package before installing it dotnet new uninstall removes the template but leaves the nupkg in ~/.templateengine/packages, so the next dotnet new install of the same version fails with 'File ... already exists' (templating exit code 106) and the template test then builds an empty project. A rerun on the same Mac hit it; the first run on a fresh machine did not. --- .../TestMonoGameTemplateTaskBase.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/build/TestNuGetsTasks/TestMonoGameTemplateTaskBase.cs b/build/TestNuGetsTasks/TestMonoGameTemplateTaskBase.cs index 6a462c1cfd3..bde48fbb40b 100644 --- a/build/TestNuGetsTasks/TestMonoGameTemplateTaskBase.cs +++ b/build/TestNuGetsTasks/TestMonoGameTemplateTaskBase.cs @@ -190,6 +190,25 @@ private void UninstallExistingTemplates(BuildContext context) private void InstallTemplates(BuildContext context, string templateVersion, string nugetSourcePath) { + // `dotnet new uninstall` removes the template but leaves its package in ~/.templateengine/packages, and the next + // `dotnet new install` of the same version fails with "File ... already exists" (templating exit code 106) instead of + // reinstalling. Clear the cached package so a rerun on the same machine starts from a clean template store. + var templateCache = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".templateengine", "packages"); + if (Directory.Exists(templateCache)) + { + foreach (var cached in Directory.GetFiles(templateCache, "MonoGame.Templates.CSharp.*.nupkg")) + { + try + { + File.Delete(cached); + } + catch (IOException) + { + // A package held open by another dotnet process: the install below will report it if it matters. + } + } + } + context.Information($"Installing MonoGame templates version {templateVersion}..."); context.StartProcess("dotnet", new ProcessSettings {