Skip to content

feat: support .NET 10 via multi-targeting (net8.0;net10.0) - #427

Open
akashsinghal wants to merge 2 commits into
oras-project:mainfrom
akashsinghal:feat/net10-multitarget
Open

feat: support .NET 10 via multi-targeting (net8.0;net10.0)#427
akashsinghal wants to merge 2 commits into
oras-project:mainfrom
akashsinghal:feat/net10-multitarget

Conversation

@akashsinghal

Copy link
Copy Markdown
Collaborator

What

Adds .NET 10 support by multi-targeting net8.0;net10.0.

Closes #426

Why this does not break .NET 8 consumers

The net10.0 target is added, not swapped in. The published package ships both lib/net8.0/ and lib/net10.0/, and NuGet selects the best folder at or below the consuming project's target framework. A net8.0 application resolves lib/net8.0/ exactly as it does today and needs no SDK, runtime, or code change.

Replacing net8.0 instead would have failed every .NET 8 consumer at restore with NU1202.

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.0 is 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 conditional ItemGroups.
  • tests/OrasProject.Oras.Tests: matching target frameworks.
  • global.json (new): pins SDK 10.0.100, rollForward: latestFeature, allowPrerelease: false. Building a net10.0 target requires the .NET 10 SDK, so this turns an obscure NETSDK1045 into a clear "install 10.0.100". Prerelease is disabled so a preview feature band cannot silently become the release compiler under TreatWarningsAsErrors.
  • System.Text.Json reference 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's TreatWarningsAsErrors. 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 a package-compat job that packs the library, asserts both lib/ folders exist, and builds a real net8.0 console app against the packed .nupkg using the .NET 8 SDK — so the compatibility claim above is verified in CI rather than assumed.
  • Other workflows: install both SDKs.
  • docs/docfx.json: pin TargetFramework so API docs are deterministic for a multi-targeted project.
  • OrasProject.Oras.sln: add a missing EndProject after the ExternalTokenBroker entry. Pre-existing malformation that can break solution parsing and CodeQL autobuild.
  • .github/dependabot.yml: add a dotnet-sdk entry so the new global.json receives 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 build succeeds for both target frameworks, 0 warnings.

  • dotnet test passes 637/637 on net8.0 and 637/637 on net10.0.

  • dotnet build OrasProject.Oras.sln succeeds, including all three net8.0 example projects referencing the multi-targeted library.

  • The packed .nupkg contains lib/net8.0/OrasProject.Oras.dll and lib/net10.0/OrasProject.Oras.dll, with these nuspec dependency groups:

    net8.0  -> Microsoft.Extensions.Caching.Abstractions 8.0.0, Microsoft.Extensions.Caching.Memory 8.0.1
    net10.0 -> Microsoft.Extensions.Caching.Abstractions 10.0.0, Microsoft.Extensions.Caching.Memory 10.0.0
    
  • A fresh net8.0 console app restored the packed package ("compatible with all the specified frameworks"), compiled against Descriptor, 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.0 should not be dropped before its Nov 10, 2026 end of support, and then only in a MAJOR release.

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>
Copilot AI lite review requested due to automatic review settings August 19, 2026 17:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.json and 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.

Copilot AI review requested due to automatic review settings August 19, 2026 17:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.0 can 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 CoverletOutput is 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. Set CoverletOutput to 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: false to the consumer global.json as well. That prevents a preview SDK (if present on the runner) from being selected via roll-forward, which can introduce unexpected restore/build behavior under TreatWarningsAsErrors.
          printf '{ "sdk": { "version": "8.0.100", "rollForward": "latestFeature" } }\n' > global.json

@codecov

codecov Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.34%. Comparing base (9fae4c5) to head (7ca1d6e).

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           
Flag Coverage Δ
net10.0 93.34% <ø> (?)
net8.0 93.34% <ø> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@cshung cshung left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support .NET 10 via multi-targeting (net8.0;net10.0)

3 participants