documentation/updating-dotnet-version.md
This checklist documents every file that needs updating when bumping the .NET SDK version (e.g., .NET 10 → .NET 11).
| Property | Purpose | Example |
|---|---|---|
| TFMBase | Lowest .NET for class libraries only (no platform TFMs) | net6.0 |
| TFMPrevious | Previous .NET with full platform support | net9.0 |
| TFMCurrent | Current .NET with full platform support | net10.0 |
| TPV*Previous | Target Platform Versions for TFMPrevious | TPViOSPrevious=18.0 |
| TPV*Current | Target Platform Versions for TFMCurrent | TPViOSCurrent=26.0 |
Important: Starting with .NET 10, Apple TPVs use Xcode 26 unified SDK versioning. iOS, MacCatalyst, tvOS, and macOS all use
26.0(not the OS version like18.0or15.0). Check for valid TPVs withdotnet new console -f net10.0-iosand observe the error message listing valid versions.
global.json — Update sdk.version to the new SDK feature band (e.g., 10.0.100). Use "rollForward": "latestPatch" to accept any patch version available on CI agents.scripts/azure-templates-variables.yml — Update DOTNET_VERSION and DOTNET_WORKLOAD_VERSION to match the new SDK versionscripts/install-dotnet-workloads.ps1 — Review Tizen script URL (Samsung repo may update)Note: Do NOT set
workloadVersioninglobal.json. Native builds skip SDK install but still read global.json, causing failures if the pinned workload version isn't pre-installed.
source/SkiaSharp.Build.props — This is the most critical file:
dotnet workload list)SupportedOSPlatformVersion: Each .NET version may raise the minimum supported OS versions. For .NET 10: iOS/tvOS minimum is 12.2, MacCatalyst minimum is 15.0, macOS minimum is 12.0. These are enforced by the workloads and will cause build errors if too low.
All use $(TFMPrevious)-platform$(TPVPrevious);$(TFMCurrent)-platform$(TPVCurrent) pattern.
binding/SkiaSharp.NativeAssets.Android/SkiaSharp.NativeAssets.Android.csprojbinding/SkiaSharp.NativeAssets.iOS/SkiaSharp.NativeAssets.iOS.csprojbinding/SkiaSharp.NativeAssets.MacCatalyst/SkiaSharp.NativeAssets.MacCatalyst.csprojbinding/SkiaSharp.NativeAssets.tvOS/SkiaSharp.NativeAssets.tvOS.csprojbinding/SkiaSharp.NativeAssets.Tizen/SkiaSharp.NativeAssets.Tizen.csprojbinding/SkiaSharp.NativeAssets.macOS/SkiaSharp.NativeAssets.macOS.csproj (also has BasicTargetFrameworks)binding/HarfBuzzSharp.NativeAssets.Android/HarfBuzzSharp.NativeAssets.Android.csprojbinding/HarfBuzzSharp.NativeAssets.iOS/HarfBuzzSharp.NativeAssets.iOS.csprojbinding/HarfBuzzSharp.NativeAssets.MacCatalyst/HarfBuzzSharp.NativeAssets.MacCatalyst.csprojbinding/HarfBuzzSharp.NativeAssets.tvOS/HarfBuzzSharp.NativeAssets.tvOS.csprojbinding/HarfBuzzSharp.NativeAssets.Tizen/HarfBuzzSharp.NativeAssets.Tizen.csprojbinding/HarfBuzzSharp.NativeAssets.macOS/HarfBuzzSharp.NativeAssets.macOS.csprojsource/SkiaSharp.Views/SkiaSharp.Views.Blazor/SkiaSharp.Views.Blazor.csproj — Update TFM list and add PackageReference for new Microsoft.AspNetCore.Components.Web versiontests/SkiaSharp.Tests.Devices/SkiaSharp.Tests.Devices.csproj — Uses $(MauiTargetFrameworksAppCurrent)tests/SkiaSharp.Tests.Integration/SkiaSharp.Tests.Integration.csproj — Hardcoded TFMtests/SkiaSharp.Tests.Integration/Tests/LinuxConsoleTests.cs — Hardcoded TFM in string templatetests/SkiaSharp.Tests.Integration/Tests/Maui*Tests.cs — Hardcoded TFMs in TargetFramework propertybuild.cake — 4 hardcoded TFMs in test tasks (~lines 285, 333, 365, 397)scripts/cake/UtilsManaged.cake — Framework check list (add new netX.0)scripts/cake/UpdateDocs.cake — Apple/Android ref package names include TFM+TPV (e.g., Microsoft.iOS.Ref.net10.0_18.0)native/winui/build.cake — WinUI Projection output path uses $(WindowsTargetFrameworksPrevious)utils/SkiaSharpGenerator/SkiaSharpGenerator.csprojutils/WasmTestRunner/WasmTestRunner.csprojutils/NativeLibraryMiniTest/docker/NativeLibraryMiniTest.csprojsamples/Basic/*/SkiaSharpSample.csproj — 16 files with hardcoded TFMssamples/Basic/UnoPlatform/SkiaSharpSample/Properties/launchSettings.jsonscripts/azure-templates-variables.yml — DOTNET_VERSION, DOTNET_WORKLOAD_VERSION, XCODE_VERSION, EMSCRIPTEN_VERSION, test device versionsscripts/azure-templates-stages-native-wasm.yml — Add new .NET emscripten entryscripts/azure-templates-jobs-bootstrapper.yml — Review workload install stepscripts/Docker/*/Dockerfile — Update FROM mcr.microsoft.com/dotnet/sdk:X.0 to new version
nuget.config — Remove old preview feeds, keep dotnet-public + dotnet-eng + test-device-runnersNote:
nuget.orgis a disallowed source in the SkiaSharp CI pipeline. If you encounter missing package restore errors during development, you can temporarily add nuget.org to work through issues, but it must be removed before merging. Request mirroring for any missing packages.
Before merging a .NET upgrade PR, verify these items:
nuget.config — Must NOT contain nuget.org source (disallowed in CI)scripts/azure-pipelines-complete.yml — buildExternals parameter must be reset to 'latest' (not a specific build ID)documentation/updating-dotnet-version.md reflects any new learningsWhen upgrading .NET versions, watch for these common issues:
.NET 9 changed System.Numerics.Matrix4x4.CreateFromAxisAngle to go through Quaternion, producing slightly different floating-point results. Tests using exact float comparisons may need tolerance adjustments. The AssertSimilar helper in tests/Tests/SkiaSharp/SKTest.cs uses Math.Round() (not truncation) to handle this.
Starting with .NET 10, Apple workloads use Xcode 26 unified SDK versioning. The TPV is 26.0 for all Apple platforms, not the OS version numbers like 18.0 (iOS), 15.0 (MacCatalyst), etc. Build errors like NETSDK1140: 18.0 is not a valid TargetPlatformVersion for iOS indicate this issue.
Check the MAUI release notes for API changes. Common issues:
Microsoft.Maui.Hosting.Compatibility removed in .NET 10)Tizen is not an official Microsoft workload. Samsung may lag behind on .NET version support. Check https://github.com/Samsung/Tizen.NET for compatibility before upgrading.
These use MSBuild properties from SkiaSharp.Build.props:
$(BasicTargetFrameworks) — NativeAssets.Linux, Win32, WebAssembly, etc.$(WindowsTargetFrameworks) — NativeAssets.WinUI, NanoServer, Views.WinUI$(MauiTargetFrameworks) — Views.Maui.Core, Views.Maui.Controls$(UnoTargetFrameworks) — Views.Uno.WinUI, Skia, Wasm$(TFMCurrent) — Benchmarks, test console projects, Direct3Dbinding/NativeAssets.Build.targets — Uses $(TFMCurrent)native/winui/.../SkiaSharp.Views.WinUI.Native.Projection.csproj — Uses $(WindowsTargetFrameworksPrevious)IsTargetFrameworkCompatible('net7.0') conditions in binding csproj files — floor checkIncludeNativeAssets.*.targets — VersionGreaterThanOrEquals('9.0') covers future versions.sln / .slnf files — don't encode TFMssamples/Gallery/ — Legacy samples, not updatedSince platform workloads only support 2 versions at a time, testing a preview means shifting the TFM chain:
TFMPrevious ← old TFMCurrent (e.g., net10.0)TFMCurrent ← the preview version (e.g., net11.0)global.json SDK version ← preview SDK (e.g., 11.0.100-preview.1)global.json allowPrerelease ← trueDOTNET_VERSION ← preview SDK versionDOTNET_WORKLOAD_VERSION ← preview workload set versionThere is no side-by-side preview mechanism — the DOTNET_VERSION in the pipeline IS the SDK version, preview or not.
After installing the new SDK, check actual workload TPVs:
dotnet workload list
# Then check manifest files in:
# ~/.dotnet/sdk-manifests/<version>/
# Or try to create a project and observe the error for valid TPVs:
dotnet new console -f net10.0-ios
# Error will list valid TPVs like: 26.0, 26.2
Workloads are pinned via the DOTNET_WORKLOAD_VERSION pipeline variable, which is passed to install-dotnet-workloads.ps1 as -WorkloadVersion. This uses the .NET SDK workload sets feature (dotnet workload install --version <version>) for reproducible builds.
Why not use workloadVersion in global.json? Native builds (which skip SDK/workload install) still read global.json. If the pinned workload version isn't pre-installed on the agent, the build fails immediately. By passing the version through the pipeline variable, we control when workload pinning applies.
Exception: Tizen is not an official workload — it uses Samsung's custom install scripts from Samsung/Tizen.NET repository.
To speed up CI iteration when debugging managed code issues, set the buildExternals parameter to a previous build ID that has successful native stages. This skips native compilation and downloads artifacts from the specified build instead.
If CI agents don't have the exact SDK version in global.json, use "rollForward": "latestPatch" to accept any patch version in the same feature band (e.g., 10.0.100 accepts 10.0.102).
If dotnet workload restore fails with "no project found", the pipeline uses explicit dotnet workload install with a list of workloads instead.