feat: support .NET 10 via multi-targeting (net8.0;net10.0) - #427
feat: support .NET 10 via multi-targeting (net8.0;net10.0)#427akashsinghal wants to merge 2 commits into
Conversation
Add net10.0 alongside net8.0 rather than replacing it, so the published package stays resolvable for .NET 8 consumers. Dependencies are pinned per target framework so a net8.0 consumer's dependency closure is unchanged. Building a net10.0 target requires the .NET 10 SDK, so global.json pins the SDK and CONTRIBUTING documents that this is a contributor-only requirement. Running the net8.0 test pass additionally needs the .NET 8 runtime, because .NET does not roll forward across major versions by default. The explicit System.Text.Json reference is removed: it is provided by the shared framework on net8.0+, and the .NET 10 SDK now reports it as a prunable reference (NU1510), which is an error under TreatWarningsAsErrors. Adds a CI job that packs the library and builds a net8.0 consumer against the resulting package using the .NET 8 SDK, so the compatibility guarantee is verified rather than assumed. Also repairs a missing EndProject entry in the solution file. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Akash Singhal <akashsinghal@microsoft.com>
There was a problem hiding this comment.
Pull request overview
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Adds .NET 10 support by multi-targeting the library and tests while keeping .NET 8 compatibility, and updates CI/tooling/docs to build, test, pack, and document deterministically across TFMs.
Changes:
- Multi-target library + test projects (
net8.0;net10.0) and split package references per TFM. - Pin SDK selection via
global.jsonand update GitHub Actions workflows to install both SDKs and validate package compatibility. - Make DocFX output deterministic for a multi-targeted project and fix a malformed solution file entry.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| tests/OrasProject.Oras.Tests/OrasProject.Oras.Tests.csproj | Multi-target tests for net8.0 + net10.0. |
| src/OrasProject.Oras/OrasProject.Oras.csproj | Multi-target library and split PackageReferences per TFM; remove prunable System.Text.Json package reference. |
| global.json | Pin SDK version/roll-forward behavior to support building net10.0. |
| docs/docfx.json | Pin DocFX metadata build TFM for deterministic output. |
| OrasProject.Oras.sln | Add missing EndProject to fix solution structure. |
| CONTRIBUTING.md | Update contributor prerequisites and document multi-targeting policy + test invocation. |
| .github/workflows/release-nuget.yml | Install both .NET 8 and .NET 10 SDKs in release workflow. |
| .github/workflows/deploy-to-github-pages.yml | Install both SDKs for docs deployment pipeline. |
| .github/workflows/codeql-analysis.yml | Install both SDKs for CodeQL autobuild equivalence with multi-targeting. |
| .github/workflows/build.yml | Test per TFM, adjust Codecov upload per-TFM, and add package compatibility guard job. |
| .github/dependabot.yml | Add Dependabot updates for global.json SDK pin. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.
Suppressed comments (3)
docs/docfx.json:14
- Pinning DocFX to
net10.0can produce API docs that reflect net10-only surface area if the library ever diverges by TFM (e.g., conditional APIs), which may be misleading for net8 consumers. Consider generating docs from the lowest supported TFM (net8.0) or generating per-TFM docs (and clearly labeling them) so the published API reference matches intended consumer expectations.
"properties": {
"TargetFramework": "net10.0"
},
.github/workflows/build.yml:50
- Coverlet's
CoverletOutputis more robust when set to a full file path (rather than only a directory). Using a directory can behave differently across coverlet/MSBuild versions and risks producing an unexpected filename/location, which can make Codecov uploads flaky. SetCoverletOutputto an explicit file (e.g.,${{ github.workspace }}/coverage/${{ matrix.target-framework }}/coverage.opencover.xml) and keep the Codecov upload aligned to that path.
- name: Run unit tests (${{ matrix.target-framework }})
run: >
dotnet test ./tests/OrasProject.Oras.Tests
--framework ${{ matrix.target-framework }}
/p:CollectCoverage=true
/p:CoverletOutputFormat=opencover
/p:CoverletOutput=${{ github.workspace }}/coverage/${{ matrix.target-framework }}/
.github/workflows/build.yml:88
- For determinism in the compatibility check, consider adding
allowPrerelease: falseto the consumerglobal.jsonas well. That prevents a preview SDK (if present on the runner) from being selected via roll-forward, which can introduce unexpected restore/build behavior underTreatWarningsAsErrors.
printf '{ "sdk": { "version": "8.0.100", "rollForward": "latestFeature" } }\n' > global.json
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #427 +/- ##
=======================================
Coverage 93.34% 93.34%
=======================================
Files 69 69
Lines 3470 3470
Branches 431 431
=======================================
Hits 3239 3239
Misses 139 139
Partials 92 92
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
What
Adds .NET 10 support by multi-targeting
net8.0;net10.0.Closes #426
Why this does not break .NET 8 consumers
The
net10.0target is added, not swapped in. The published package ships bothlib/net8.0/andlib/net10.0/, and NuGet selects the best folder at or below the consuming project's target framework. Anet8.0application resolveslib/net8.0/exactly as it does today and needs no SDK, runtime, or code change.Replacing
net8.0instead would have failed every .NET 8 consumer at restore withNU1202.Dependencies are pinned per target framework, so a net8.0 consumer's dependency closure is unchanged. Without that split, net8.0 consumers would have been silently moved onto 10.0.x dependencies.
net9.0is intentionally excluded: it reaches end of support the same day as .NET 8 (Nov 10, 2026), so it would add CI cost for no coverage gain.Changes
src/OrasProject.Oras:TargetFrameworks=net8.0;net10.0;PackageReferences split into per-TFM conditionalItemGroups.tests/OrasProject.Oras.Tests: matching target frameworks.global.json(new): pins SDK10.0.100,rollForward: latestFeature,allowPrerelease: false. Building anet10.0target requires the .NET 10 SDK, so this turns an obscureNETSDK1045into a clear "install 10.0.100". Prerelease is disabled so a preview feature band cannot silently become the release compiler underTreatWarningsAsErrors.System.Text.Jsonreference removed: it is provided by the shared framework on net8.0+, and the .NET 10 SDK reports it as a prunable reference (NU1510), which is an error under this repo'sTreatWarningsAsErrors. Per Microsoft guidance the correct fix is to remove it; the framework-provided assembly is what actually loads at runtime either way, and runtime patching remains the correct CVE mitigation..github/workflows/build.yml: matrix is now over target framework rather than SDK version, and installs both SDKs. Adds apackage-compatjob that packs the library, asserts bothlib/folders exist, and builds a real net8.0 console app against the packed.nupkgusing the .NET 8 SDK — so the compatibility claim above is verified in CI rather than assumed.docs/docfx.json: pinTargetFrameworkso API docs are deterministic for a multi-targeted project.OrasProject.Oras.sln: add a missingEndProjectafter the ExternalTokenBroker entry. Pre-existing malformation that can break solution parsing and CodeQL autobuild..github/dependabot.yml: add adotnet-sdkentry so the newglobal.jsonreceives SDK updates.CONTRIBUTING.md: document the .NET 10 SDK build requirement, note the .NET 8 runtime is also needed for the net8.0 test pass, and record the "add, never replace a TFM" policy.Verification
Performed locally with SDK 10.0.303 and the .NET 8 runtime installed:
dotnet buildsucceeds for both target frameworks, 0 warnings.dotnet testpasses 637/637 on net8.0 and 637/637 on net10.0.dotnet build OrasProject.Oras.slnsucceeds, including all three net8.0 example projects referencing the multi-targeted library.The packed
.nupkgcontainslib/net8.0/OrasProject.Oras.dllandlib/net10.0/OrasProject.Oras.dll, with these nuspec dependency groups:A fresh
net8.0console app restored the packed package ("compatible with all the specified frameworks"), compiled againstDescriptor, and ran successfully.Contributor impact
Building the repo now requires the .NET 10 SDK; an older SDK cannot build a newer target framework. Running the full test suite also needs the .NET 8 runtime installed. This is a build-time requirement only and does not affect consumers of the package.
Versioning
Additive, so a MINOR bump under the SemVer policy in the README.
net8.0should not be dropped before its Nov 10, 2026 end of support, and then only in a MAJOR release.