From 9c6b6117b6ac4b7ccfb0129c355f77a98922bcc4 Mon Sep 17 00:00:00 2001 From: ahall Date: Fri, 10 Jul 2026 09:54:17 +0100 Subject: [PATCH 1/3] ci: pin iOS workload set and runner image to fix Xcode version mismatch (#529) todoapp-avalonia / ios and todoapp-maui / ios both call 'dotnet workload install ios' / 'maui-ios' unpinned (loose-manifests mode), which always resolves the newest published Microsoft.iOS.Sdk.net10.0_* pack. As of workload set 10.0.108, that's 26.5.10284, which requires Xcode 26.5 - not installed on either macOS-15 or macOS-26 runner images currently backing the macos-latest label during its migration window (see actions/runner-images#14167), causing intermittent 'This version of .NET for iOS (26.5.10284) requires Xcode 26.5' build failures. - Add repo-root global.json pinning sdk.workloadVersion to 10.0.107, the last workload set before the iOS/MacCatalyst/macOS/tvOS manifest moved to the 26.4/26.5 pack line, while keeping the MAUI (10.0.20) and Android (36.1.53) manifests at their latest versions. This applies automatically to every 'dotnet workload install' call in CI (including the currently-disabled todoapp-uno / ios-maccatalyst job, once #506 is resolved and it's wired back in) with no per-job or per-project edits needed. - Pin the todoapp-avalonia / ios and todoapp-maui / ios jobs to macos-15 (instead of macos-latest) as defense-in-depth so the Xcode version these jobs build against is deterministic during the migration window, rather than randomly landing on macos-15 or macos-26. --- .../build-samples-todoapp-avalonia.yml | 9 ++++++- .../workflows/build-samples-todoapp-maui.yml | 9 ++++++- global.json | 27 +++++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 global.json diff --git a/.github/workflows/build-samples-todoapp-avalonia.yml b/.github/workflows/build-samples-todoapp-avalonia.yml index a5e5a08..ef896cc 100644 --- a/.github/workflows/build-samples-todoapp-avalonia.yml +++ b/.github/workflows/build-samples-todoapp-avalonia.yml @@ -76,7 +76,14 @@ jobs: ios: # iOS builds require Xcode, which is only available on macOS runners. - runs-on: macos-latest + # Pinned to macos-15 (rather than macos-latest) while that label migrates to macos-26 + # (see https://github.com/actions/runner-images/issues/14167, rollout through + # 2026-07-15): macos-latest can currently land on either image at random, each with a + # different default Xcode (16.4 vs 26.4.1). Combined with the workload-set pin in the + # repo-root global.json, this keeps the Xcode version this job builds against + # deterministic. See https://github.com/CommunityToolkit/Datasync/issues/529. Revisit + # once we deliberately upgrade to an Xcode-26-compatible workload set. + runs-on: macos-15 steps: - name: Checkout Repository uses: actions/checkout@v7 diff --git a/.github/workflows/build-samples-todoapp-maui.yml b/.github/workflows/build-samples-todoapp-maui.yml index 5f03eda..8dbe7bc 100644 --- a/.github/workflows/build-samples-todoapp-maui.yml +++ b/.github/workflows/build-samples-todoapp-maui.yml @@ -48,7 +48,14 @@ jobs: ios: # iOS builds require Xcode, which is only available on macOS runners. - runs-on: macos-latest + # Pinned to macos-15 (rather than macos-latest) while that label migrates to macos-26 + # (see https://github.com/actions/runner-images/issues/14167, rollout through + # 2026-07-15): macos-latest can currently land on either image at random, each with a + # different default Xcode (16.4 vs 26.4.1). Combined with the workload-set pin in the + # repo-root global.json, this keeps the Xcode version this job builds against + # deterministic. See https://github.com/CommunityToolkit/Datasync/issues/529. Revisit + # once we deliberately upgrade to an Xcode-26-compatible workload set. + runs-on: macos-15 steps: - name: Checkout Repository uses: actions/checkout@v7 diff --git a/global.json b/global.json new file mode 100644 index 0000000..68739d1 --- /dev/null +++ b/global.json @@ -0,0 +1,27 @@ +{ + // Pins the .NET SDK workload SET version (not just the SDK itself) so that every + // `dotnet workload install ` call in CI (todoapp-avalonia / ios, todoapp-maui / ios, + // and the currently-disabled todoapp-uno / ios-maccatalyst once #506 is resolved) resolves + // a consistent, known-good bundle of workload manifests instead of always floating to the + // newest individually-published manifest for each workload ("loose manifests" mode, the + // default when no workloadVersion is pinned). + // + // Why this is needed (see https://github.com/CommunityToolkit/Datasync/issues/529): + // Without this pin, `dotnet workload install ios` / `maui-ios` on a macos-latest runner + // installs the newest published Microsoft.iOS.Sdk.net10.0_* pack. As of workload set + // 10.0.108 (iOS manifest 26.5.10284), that pack requires Xcode 26.5, which isn't installed + // on either the macOS-15 or macOS-26 runner images backing the macos-latest label during + // its migration window (see https://github.com/actions/runner-images/issues/14167) - + // causing "This version of .NET for iOS (26.5.10284) requires Xcode 26.5" build failures. + // + // 10.0.107 is pinned because it's the last workload set before the iOS/MacCatalyst/macOS/ + // tvOS manifest moved to the 26.4/26.5 pack line (see workload-set history on nuget.org, + // package id Microsoft.NET.Workloads.10.0.100), while keeping the MAUI (10.0.20) and + // Android (36.1.53) manifests at their latest versions to avoid an unnecessary regression + // there. This must live at the repo root (not e.g. samples/todoapp/) because the .NET SDK + // muxer resolves global.json by walking up from the current working directory, and the CI + // `dotnet workload install`/`dotnet build` steps all run with cwd = repo root. + "sdk": { + "workloadVersion": "10.0.107" + } +} From f93a23611d66168e880b37150b6a2acb6cd44547 Mon Sep 17 00:00:00 2001 From: ahall Date: Fri, 10 Jul 2026 09:58:00 +0100 Subject: [PATCH 2/3] ci: pin iOS workload via --version instead of repo-root global.json (#529) The repo-root global.json approach broke every other sample build (todoapp-mvc, todoapp-blazor-wasm, datasync-server, etc.) on a fresh runner with: error MSB4242: SDK Resolver Failure: ... Workload version 10.0.107, which was specified in .../global.json, was not found. Run 'dotnet workload restore' to install this workload version. Microsoft.NET.Sdk.ImportWorkloads.props enforces the pinned workload version for every 'dotnet build'/'restore' invocation whose cwd falls under a global.json declaring sdk.workloadVersion - regardless of whether that specific project has any workload dependency - and it's not satisfied lazily; it must already be installed. Confirmed via a real workflow_dispatch run (see https://github.com/adrianhall/CommunityToolkit-Datasync/actions/runs/29081276609). Replaced with 'dotnet workload install ios/maui-ios --version 10.0.107' scoped to just the ios job's env/steps in each affected workflow. This pins only that job's install (and the restore/build steps that follow it on the same runner), leaving every other job - including the android jobs in the same workflows - on unpinned/latest workload resolution. --- .../build-samples-todoapp-avalonia.yml | 23 ++++++++++++---- .../workflows/build-samples-todoapp-maui.yml | 24 +++++++++++++---- global.json | 27 ------------------- 3 files changed, 37 insertions(+), 37 deletions(-) delete mode 100644 global.json diff --git a/.github/workflows/build-samples-todoapp-avalonia.yml b/.github/workflows/build-samples-todoapp-avalonia.yml index ef896cc..bfe9c65 100644 --- a/.github/workflows/build-samples-todoapp-avalonia.yml +++ b/.github/workflows/build-samples-todoapp-avalonia.yml @@ -79,11 +79,24 @@ jobs: # Pinned to macos-15 (rather than macos-latest) while that label migrates to macos-26 # (see https://github.com/actions/runner-images/issues/14167, rollout through # 2026-07-15): macos-latest can currently land on either image at random, each with a - # different default Xcode (16.4 vs 26.4.1). Combined with the workload-set pin in the - # repo-root global.json, this keeps the Xcode version this job builds against - # deterministic. See https://github.com/CommunityToolkit/Datasync/issues/529. Revisit - # once we deliberately upgrade to an Xcode-26-compatible workload set. + # different default Xcode (16.4 vs 26.4.1). Combined with the workload-set version pin + # below, this keeps the Xcode version this job builds against deterministic. See + # https://github.com/CommunityToolkit/Datasync/issues/529. Revisit once we deliberately + # upgrade to an Xcode-26-compatible workload set. runs-on: macos-15 + env: + # `dotnet workload install ios` alone always resolves the newest published + # Microsoft.iOS.Sdk.net10.0_* pack. As of workload set 10.0.108, that's 26.5.10284, + # which requires Xcode 26.5 - not installed on macos-15. Pinning to 10.0.107 (the last + # set before the iOS/MacCatalyst/macOS/tvOS manifest moved to the 26.4/26.5 pack line; + # see workload-set history for package id Microsoft.NET.Workloads.10.0.100 on + # nuget.org) resolves 26.2.10233 instead, while keeping other manifests (e.g. Android) + # unaffected. `--version` pins only this job's `dotnet workload install` invocation and + # the `restore`/`build` steps that follow it in the same runner - it's intentionally + # not a repo-wide global.json pin, which would force every `dotnet` invocation in the + # whole repo (including samples with no workload dependency at all) to require this + # exact workload version to already be installed, breaking them on a fresh runner. + WorkloadSetVersion: '10.0.107' steps: - name: Checkout Repository uses: actions/checkout@v7 @@ -94,7 +107,7 @@ jobs: dotnet-version: ${{ env.DOTNET_VERSION }} - name: Install iOS workload - run: dotnet workload install ios + run: dotnet workload install ios --version ${{ env.WorkloadSetVersion }} - name: Restore iOS project run: dotnet restore ${{ env.iOSProjectFile }} diff --git a/.github/workflows/build-samples-todoapp-maui.yml b/.github/workflows/build-samples-todoapp-maui.yml index 8dbe7bc..85c8fda 100644 --- a/.github/workflows/build-samples-todoapp-maui.yml +++ b/.github/workflows/build-samples-todoapp-maui.yml @@ -51,11 +51,25 @@ jobs: # Pinned to macos-15 (rather than macos-latest) while that label migrates to macos-26 # (see https://github.com/actions/runner-images/issues/14167, rollout through # 2026-07-15): macos-latest can currently land on either image at random, each with a - # different default Xcode (16.4 vs 26.4.1). Combined with the workload-set pin in the - # repo-root global.json, this keeps the Xcode version this job builds against - # deterministic. See https://github.com/CommunityToolkit/Datasync/issues/529. Revisit - # once we deliberately upgrade to an Xcode-26-compatible workload set. + # different default Xcode (16.4 vs 26.4.1). Combined with the workload-set version pin + # below, this keeps the Xcode version this job builds against deterministic. See + # https://github.com/CommunityToolkit/Datasync/issues/529. Revisit once we deliberately + # upgrade to an Xcode-26-compatible workload set. runs-on: macos-15 + env: + # `dotnet workload install maui-ios` alone always resolves the newest published + # Microsoft.iOS.Sdk.net10.0_* pack. As of workload set 10.0.108, that's 26.5.10284, + # which requires Xcode 26.5 - not installed on macos-15. Pinning to 10.0.107 (the last + # set before the iOS/MacCatalyst/macOS/tvOS manifest moved to the 26.4/26.5 pack line; + # see workload-set history for package id Microsoft.NET.Workloads.10.0.100 on + # nuget.org) resolves 26.2.10233 instead, while keeping other manifests (e.g. MAUI, + # Android) unaffected. `--version` pins only this job's `dotnet workload install` + # invocation and the `restore`/`build` steps that follow it in the same runner - it's + # intentionally not a repo-wide global.json pin, which would force every `dotnet` + # invocation in the whole repo (including samples with no workload dependency at all) + # to require this exact workload version to already be installed, breaking them on a + # fresh runner. + WorkloadSetVersion: '10.0.107' steps: - name: Checkout Repository uses: actions/checkout@v7 @@ -66,7 +80,7 @@ jobs: dotnet-version: ${{ env.DOTNET_VERSION }} - name: Install MAUI iOS workload - run: dotnet workload install maui-ios + run: dotnet workload install maui-ios --version ${{ env.WorkloadSetVersion }} - name: Restore MAUI project (iOS TFM only) run: dotnet restore ${{ env.MauiProjectFile }} -p:TargetFramework=${{ env.iOSTargetFramework }} diff --git a/global.json b/global.json deleted file mode 100644 index 68739d1..0000000 --- a/global.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - // Pins the .NET SDK workload SET version (not just the SDK itself) so that every - // `dotnet workload install ` call in CI (todoapp-avalonia / ios, todoapp-maui / ios, - // and the currently-disabled todoapp-uno / ios-maccatalyst once #506 is resolved) resolves - // a consistent, known-good bundle of workload manifests instead of always floating to the - // newest individually-published manifest for each workload ("loose manifests" mode, the - // default when no workloadVersion is pinned). - // - // Why this is needed (see https://github.com/CommunityToolkit/Datasync/issues/529): - // Without this pin, `dotnet workload install ios` / `maui-ios` on a macos-latest runner - // installs the newest published Microsoft.iOS.Sdk.net10.0_* pack. As of workload set - // 10.0.108 (iOS manifest 26.5.10284), that pack requires Xcode 26.5, which isn't installed - // on either the macOS-15 or macOS-26 runner images backing the macos-latest label during - // its migration window (see https://github.com/actions/runner-images/issues/14167) - - // causing "This version of .NET for iOS (26.5.10284) requires Xcode 26.5" build failures. - // - // 10.0.107 is pinned because it's the last workload set before the iOS/MacCatalyst/macOS/ - // tvOS manifest moved to the 26.4/26.5 pack line (see workload-set history on nuget.org, - // package id Microsoft.NET.Workloads.10.0.100), while keeping the MAUI (10.0.20) and - // Android (36.1.53) manifests at their latest versions to avoid an unnecessary regression - // there. This must live at the repo root (not e.g. samples/todoapp/) because the .NET SDK - // muxer resolves global.json by walking up from the current working directory, and the CI - // `dotnet workload install`/`dotnet build` steps all run with cwd = repo root. - "sdk": { - "workloadVersion": "10.0.107" - } -} From 4efe53e251be013a2e0b77428afba7ee36dad71a Mon Sep 17 00:00:00 2001 From: ahall Date: Fri, 10 Jul 2026 10:03:42 +0100 Subject: [PATCH 3/3] ci: explicitly select Xcode 26.3 to match the pinned iOS workload (#529) Testing the --version 10.0.107 workload pin alone (previous commit) still failed on macos-15: error: This version of .NET for iOS (26.2.10233) requires Xcode 26.3. The current version of Xcode is 16.4. Confirmed via a real workflow_dispatch run (https://github.com/adrianhall/CommunityToolkit-Datasync/actions/runs/29081474204) that both todoapp-avalonia/ios and todoapp-maui/ios resolve to the SAME newest-available pack within the pinned workload set (26.2.10233, _RecommendedXcodeVersion=26.3 per Microsoft.iOS.Sdk.Versions.props), not an older Xcode-16.4-compatible one - there is no currently-published net10.0 iOS SDK pack (checked back to workload set 10.0.100, published 2025-11-11) whose _RecommendedXcodeVersion is anywhere close to 16.4, so no workload-version pin alone can satisfy macos-15's default Xcode. Per actions/runner-images' macos-15 Readme, Xcode 26.3 IS already installed on the image (just not the default) at /Applications/Xcode_26.3.app, alongside 26.0.1/26.1.1/26.2. Added a 'sudo xcode-select -s' step to explicitly select it, matching the 26.2.10233 pack's requirement, instead of installing anything extra. --- .../build-samples-todoapp-avalonia.yml | 39 ++++++++++++------ .../workflows/build-samples-todoapp-maui.yml | 40 ++++++++++++------- 2 files changed, 52 insertions(+), 27 deletions(-) diff --git a/.github/workflows/build-samples-todoapp-avalonia.yml b/.github/workflows/build-samples-todoapp-avalonia.yml index bfe9c65..2312b59 100644 --- a/.github/workflows/build-samples-todoapp-avalonia.yml +++ b/.github/workflows/build-samples-todoapp-avalonia.yml @@ -79,28 +79,41 @@ jobs: # Pinned to macos-15 (rather than macos-latest) while that label migrates to macos-26 # (see https://github.com/actions/runner-images/issues/14167, rollout through # 2026-07-15): macos-latest can currently land on either image at random, each with a - # different default Xcode (16.4 vs 26.4.1). Combined with the workload-set version pin - # below, this keeps the Xcode version this job builds against deterministic. See - # https://github.com/CommunityToolkit/Datasync/issues/529. Revisit once we deliberately - # upgrade to an Xcode-26-compatible workload set. + # different default Xcode (16.4 vs 26.4.1), and macos-15's *default* Xcode (16.4) doesn't + # match what any currently-published net10.0 iOS SDK pack requires (see WorkloadSetVersion + # below) - so this job also explicitly selects a non-default Xcode already installed on + # the image (see the "Select Xcode" step) to match. See + # https://github.com/CommunityToolkit/Datasync/issues/529. Revisit this whole job once we + # deliberately upgrade to a newer, Xcode-26-compatible workload set. runs-on: macos-15 env: # `dotnet workload install ios` alone always resolves the newest published # Microsoft.iOS.Sdk.net10.0_* pack. As of workload set 10.0.108, that's 26.5.10284, - # which requires Xcode 26.5 - not installed on macos-15. Pinning to 10.0.107 (the last - # set before the iOS/MacCatalyst/macOS/tvOS manifest moved to the 26.4/26.5 pack line; - # see workload-set history for package id Microsoft.NET.Workloads.10.0.100 on - # nuget.org) resolves 26.2.10233 instead, while keeping other manifests (e.g. Android) - # unaffected. `--version` pins only this job's `dotnet workload install` invocation and - # the `restore`/`build` steps that follow it in the same runner - it's intentionally - # not a repo-wide global.json pin, which would force every `dotnet` invocation in the - # whole repo (including samples with no workload dependency at all) to require this - # exact workload version to already be installed, breaking them on a fresh runner. + # which requires Xcode 26.5 - not installed on macos-15 (even as a non-default Xcode). + # Pinning to 10.0.107 (the last set before the iOS/MacCatalyst/macOS/tvOS manifest moved + # to the 26.4/26.5 pack line; see workload-set history for package id + # Microsoft.NET.Workloads.10.0.100 on nuget.org) resolves 26.2.10233 instead, which + # requires Xcode 26.3 - installed (non-default) on macos-15 as of image 20260629.0276.1 + # (see the "Select Xcode" step below). This also keeps other manifests (e.g. Android) at + # their latest versions. `--version` pins only this job's `dotnet workload install` + # invocation and the `restore`/`build` steps that follow it in the same runner - it's + # intentionally not a repo-wide global.json pin, which would force every `dotnet` + # invocation in the whole repo (including samples with no workload dependency at all) + # to require this exact workload version to already be installed, breaking them on a + # fresh runner. WorkloadSetVersion: '10.0.107' + RequiredXcodeVersion: '26.3' steps: - name: Checkout Repository uses: actions/checkout@v7 + - name: Select Xcode ${{ env.RequiredXcodeVersion }} + # macos-15 defaults to Xcode 16.4, which doesn't satisfy what the pinned iOS + # workload (see WorkloadSetVersion above) requires. Xcode 26.3 is already installed + # on the image, just not selected by default - so switch to it explicitly rather + # than installing anything extra. + run: sudo xcode-select -s /Applications/Xcode_${{ env.RequiredXcodeVersion }}.app + - name: Setup .NET uses: actions/setup-dotnet@v5 with: diff --git a/.github/workflows/build-samples-todoapp-maui.yml b/.github/workflows/build-samples-todoapp-maui.yml index 85c8fda..0f977cb 100644 --- a/.github/workflows/build-samples-todoapp-maui.yml +++ b/.github/workflows/build-samples-todoapp-maui.yml @@ -51,29 +51,41 @@ jobs: # Pinned to macos-15 (rather than macos-latest) while that label migrates to macos-26 # (see https://github.com/actions/runner-images/issues/14167, rollout through # 2026-07-15): macos-latest can currently land on either image at random, each with a - # different default Xcode (16.4 vs 26.4.1). Combined with the workload-set version pin - # below, this keeps the Xcode version this job builds against deterministic. See - # https://github.com/CommunityToolkit/Datasync/issues/529. Revisit once we deliberately - # upgrade to an Xcode-26-compatible workload set. + # different default Xcode (16.4 vs 26.4.1), and macos-15's *default* Xcode (16.4) doesn't + # match what any currently-published net10.0 iOS SDK pack requires (see WorkloadSetVersion + # below) - so this job also explicitly selects a non-default Xcode already installed on + # the image (see the "Select Xcode" step) to match. See + # https://github.com/CommunityToolkit/Datasync/issues/529. Revisit this whole job once we + # deliberately upgrade to a newer, Xcode-26-compatible workload set. runs-on: macos-15 env: # `dotnet workload install maui-ios` alone always resolves the newest published # Microsoft.iOS.Sdk.net10.0_* pack. As of workload set 10.0.108, that's 26.5.10284, - # which requires Xcode 26.5 - not installed on macos-15. Pinning to 10.0.107 (the last - # set before the iOS/MacCatalyst/macOS/tvOS manifest moved to the 26.4/26.5 pack line; - # see workload-set history for package id Microsoft.NET.Workloads.10.0.100 on - # nuget.org) resolves 26.2.10233 instead, while keeping other manifests (e.g. MAUI, - # Android) unaffected. `--version` pins only this job's `dotnet workload install` - # invocation and the `restore`/`build` steps that follow it in the same runner - it's - # intentionally not a repo-wide global.json pin, which would force every `dotnet` - # invocation in the whole repo (including samples with no workload dependency at all) - # to require this exact workload version to already be installed, breaking them on a - # fresh runner. + # which requires Xcode 26.5 - not installed on macos-15 (even as a non-default Xcode). + # Pinning to 10.0.107 (the last set before the iOS/MacCatalyst/macOS/tvOS manifest moved + # to the 26.4/26.5 pack line; see workload-set history for package id + # Microsoft.NET.Workloads.10.0.100 on nuget.org) resolves 26.2.10233 instead, which + # requires Xcode 26.3 - installed (non-default) on macos-15 as of image 20260629.0276.1 + # (see the "Select Xcode" step below). This also keeps other manifests (e.g. MAUI, + # Android) at their latest versions. `--version` pins only this job's + # `dotnet workload install` invocation and the `restore`/`build` steps that follow it in + # the same runner - it's intentionally not a repo-wide global.json pin, which would + # force every `dotnet` invocation in the whole repo (including samples with no workload + # dependency at all) to require this exact workload version to already be installed, + # breaking them on a fresh runner. WorkloadSetVersion: '10.0.107' + RequiredXcodeVersion: '26.3' steps: - name: Checkout Repository uses: actions/checkout@v7 + - name: Select Xcode ${{ env.RequiredXcodeVersion }} + # macos-15 defaults to Xcode 16.4, which doesn't satisfy what the pinned iOS + # workload (see WorkloadSetVersion above) requires. Xcode 26.3 is already installed + # on the image, just not selected by default - so switch to it explicitly rather + # than installing anything extra. + run: sudo xcode-select -s /Applications/Xcode_${{ env.RequiredXcodeVersion }}.app + - name: Setup .NET uses: actions/setup-dotnet@v5 with: