Skip to content

[Enhancement] Add net9.0 and net10.0 targets #1822

Closed

Description

@martincostello

coverlet NuGet packages (coverlet.collector, coverlet.msbuild, coverlet.console, coverlet.MTP)
currently ship only a net8.0 TFM asset folder (driven by $(NetMinimum) = net8.0).
When a user's machine has only the .NET 9 or .NET 10 SDK installed the tracker class injected
at instrumentation time cannot load System.Runtime 8.0.0.0, causing a TypeInitializationException
at test-run time (see #1818).

Adding net9.0 and net10.0 TFM asset folders to each package allows the runtime assembly
resolver to pick the TFM-appropriate copy automatically.

Strategy

Variable Current After
NetMinimum net8.0 net8.0 (unchanged)
NetCurrent net9.0 net10.0 (promoted)
net9.0 packages + CI CI integration-test only
LangVersion 12.0 latest

Activity

  1. Bertk commented on Feb 18, 2026

    @Bertk
    Collaborator

    Plan: Add net9.0 and net10.0 Targets

    Steps

    1 · Directory.Build.props — TFM variables and LangVersion

    File: Directory.Build.props

    • <NetCurrent>net9.0</NetCurrent> → <NetCurrent>net10.0</NetCurrent>
    • <LangVersion>12.0</LangVersion> → <LangVersion>latest</LangVersion>

    2 · global.json — SDK pin

    File: global.json

    • "version": "9.0.310" → "version": "10.0.100" (or the latest .NET 10 GA SDK build)

    3 · Directory.Packages.props — dependency versions

    File: Directory.Packages.props

    Packages still pinned to 8.x must be bumped to 9.x/10.x-compatible versions:

    Package Current Target
    Microsoft.Bcl.AsyncInterfaces 8.0.0 9.0.0
    Microsoft.Extensions.Configuration.Json 8.0.1 9.0.0
    Microsoft.Extensions.DependencyInjection 8.0.1 9.0.0
    Microsoft.Extensions.DependencyModel 8.0.2 9.0.0
    Microsoft.Extensions.FileSystemGlobbing 8.0.0 9.0.0
    Microsoft.Extensions.Logging.Abstractions 8.0.3 9.0.0
    System.Collections.Immutable 8.0.0 9.0.0
    System.Configuration.ConfigurationManager 8.0.1 9.0.0
    System.Diagnostics.EventLog 8.0.2 9.0.0
    System.IO.Pipelines 8.0.0 9.0.0
    System.Reflection.Metadata 8.0.1 9.0.0
    System.Text.Encoding.CodePages 8.0.0 9.0.0
    System.Text.Json 8.0.6 9.0.0

    Already at a compatible version — no change needed:
    Microsoft.Extensions.Configuration (10.0.1), System.Formats.Asn1 (10.0.0),
    System.Security.Cryptography.Pkcs (10.0.0).

    Also update the MicrosoftBuildVersion comment from <!-- .NET 8.0 support --> to
    <!-- .NET 10.0 support --> and verify the package version supports the .NET 10 MSBuild
    (current: 17.11.48).

    4 · Source projects — add $(NetCurrent) to TargetFrameworks

    File Current New
    src/coverlet.collector/coverlet.collector.csproj $(NetMinimum) $(NetMinimum);$(NetCurrent)
    src/coverlet.console/coverlet.console.csproj $(NetMinimum) $(NetMinimum);$(NetCurrent)
    src/coverlet.msbuild.tasks/coverlet.msbuild.tasks.csproj $(NetMinimum);netstandard2.0 $(NetMinimum);$(NetCurrent);netstandard2.0
    src/coverlet.MTP/coverlet.MTP.csproj netstandard2.0;$(NetMinimum) netstandard2.0;$(NetMinimum);$(NetCurrent)

    src/coverlet.core/coverlet.core.csproj already uses $(NetMinimum);$(NetCurrent);netstandard2.0 — no change.

    5 · coverlet.msbuild.props — dynamic TFM path selection

    File: src/coverlet.msbuild.tasks/buildMultiTargeting/coverlet.msbuild.props

    Lines 23 and 27 hardcode ..\tasks\net8.0\. Replace with chained MSBuild version
    conditions so the highest available TFM folder is chosen at MSBuild evaluation time.
    Apply the same pattern for Windows (\) and non-Windows (/) variants:

    <!-- Windows -->
    <CoverletToolsPath Condition="'$(CoverletToolsPath)'=='' and '$(MSBuildRuntimeType)'=='Core'
        and $([MSBuild]::VersionGreaterThanOrEquals('$(NETCoreSdkVersion)', '10.0'))">
      $(MSBuildThisFileDirectory)..\tasks\net10.0\
    </CoverletToolsPath>
    <CoverletToolsPath Condition="'$(CoverletToolsPath)'=='' and '$(MSBuildRuntimeType)'=='Core'
        and $([MSBuild]::VersionGreaterThanOrEquals('$(NETCoreSdkVersion)', '9.0'))">
      $(MSBuildThisFileDirectory)..\tasks\net9.0\
    </CoverletToolsPath>
    <CoverletToolsPath Condition="'$(CoverletToolsPath)'=='' and '$(MSBuildRuntimeType)'=='Core'">
      $(MSBuildThisFileDirectory)..\tasks\net8.0\
    </CoverletToolsPath>

    The netstandard2.0 fallback (non-Core MSBuild) remains unchanged.

    6 · test/Directory.Build.targets — fix hardcoded artifact path

    File: test/Directory.Build.targets (line 17)

    <!-- Before -->
    <CoverletToolsPath>$(RepoRoot)artifacts\bin\coverlet.msbuild.tasks\$(Configuration.ToLowerInvariant())_net8.0\</CoverletToolsPath>
    
    <!-- After -->
    <CoverletToolsPath>$(RepoRoot)artifacts\bin\coverlet.msbuild.tasks\$(Configuration.ToLowerInvariant())_$(NetMinimum)\</CoverletToolsPath>

    7 · Unit-test projects — multi-target against NetMinimum and NetCurrent

    Change literal net8.0 to $(NetMinimum);$(NetCurrent) (<TargetFrameworks> plural):

    File Current
    test/coverlet.core.tests/coverlet.core.tests.csproj net8.0
    test/coverlet.collector.tests/coverlet.collector.tests.csproj net8.0
    test/coverlet.core.coverage.tests/coverlet.core.coverage.tests.csproj net8.0
    test/coverlet.msbuild.tasks.tests/coverlet.msbuild.tasks.tests.csproj $(NetMinimum)
    test/coverlet.MTP.tests/coverlet.MTP.tests.csproj net8.0
    test/coverlet.MTP.validation.tests/coverlet.MTP.validation.tests.csproj net8.0

    All six become <TargetFrameworks>$(NetMinimum);$(NetCurrent)</TargetFrameworks>.

    test/coverlet.integration.tests already uses $(NetCurrent);$(NetMinimum) — no change.

    test/coverlet.integration.determisticbuild uses literals net9.0;net8.0; change to
    $(NetCurrent);$(NetMinimum).

    8 · eng/build.yml — CI SDK installs and test runs

    File: eng/build.yml

    8a · Install .NET 10 SDK

    Add a third UseDotNet@2 step (after the existing 8.0.x and global.json steps):

    - task: UseDotNet@2
      inputs:
        version: 10.0.x
      displayName: Install .NET SDK 10.0

    (Once global.json is updated to 10.x the useGlobalJson: true step covers this;
    the explicit step is a safety net while migration is in progress.)

    8b · Add net10.0 integration-test run

    In the "Run unit tests with coverage" script, add alongside the existing
    -f net8.0 and -f net9.0 steps:

    dotnet test test/coverlet.integration.tests/coverlet.integration.tests.csproj \
      -c $(BuildConfiguration) -f net10.0 --no-build -bl:test.integration.binlog \
      --results-directory "$(Build.SourcesDirectory)/artifacts/reports/" \
      --logger "trx;LogFileName=coverlet.integration.tests.net10.0.trx" \
      --diag:"$(Build.SourcesDirectory)/artifacts/log/coverlet.integration.tests.net10.0.diag;tracelevel=verbose"

    Keep the net8.0 and net9.0 runs — all three TFMs are tested in CI.

    8c · Fix cleanup/delete step (line 62)

    del coverlet.integration.determisticbuild\coverage.net8.0.cobertura.xml
    del coverlet.integration.determisticbuild\coverage.net9.0.cobertura.xml
    del coverlet.integration.determisticbuild\coverage.net10.0.cobertura.xml

    9 · eng/azure-pipelines.yml — agent images

    File: eng/azure-pipelines.yml

    Verify windows-latest, ubuntu-latest, and macOS-latest Azure Pipelines agents
    include the .NET 10 SDK (or that step 8a covers it). No matrix changes are required.


    Verification

    1. Build: dotnet build -c Debug — confirm artifacts/bin/coverlet.msbuild.tasks/
      contains both debug_net8.0/ and debug_net10.0/ output folders.
    2. Pack: dotnet pack -c Debug — inspect nupkgs; verify
      tasks/net8.0/, tasks/net10.0/ and lib/net8.0/, lib/net10.0/ entries are present.
    3. Unit tests (multi-TFM):
      dotnet test test/coverlet.core.tests/coverlet.core.tests.csproj -c Debug
      dotnet test test/coverlet.collector.tests/coverlet.collector.tests.csproj -c Debug
      dotnet test test/coverlet.core.coverage.tests/coverlet.core.coverage.tests.csproj -c Debug
      dotnet test test/coverlet.msbuild.tasks.tests/coverlet.msbuild.tasks.tests.csproj -c Debug
      
      Each should report results for both net8.0 and net10.0.
    4. Integration tests (three TFMs):
      dotnet test test/coverlet.integration.tests/... -f net8.0 -c Debug
      dotnet test test/coverlet.integration.tests/... -f net9.0 -c Debug
      dotnet test test/coverlet.integration.tests/... -f net10.0 -c Debug
      
    5. Package validation: build with EnablePackageValidation=true; confirm no baseline
      compatibility breaks for coverlet.collector and coverlet.msbuild.
    6. CI: push to a *_validate branch to trigger all three OS × two configuration jobs
      in Azure Pipelines; confirm green.

    File Change Summary

    File Change
    Directory.Build.props NetCurrent net9→net10, LangVersion 12.0→latest
    global.json SDK 9.0.310 → 10.0.x
    Directory.Packages.props Bump 13 packages from 8.x → 9.x
    src/coverlet.collector/coverlet.collector.csproj Add $(NetCurrent) to TFMs
    src/coverlet.console/coverlet.console.csproj Add $(NetCurrent) to TFMs
    src/coverlet.msbuild.tasks/coverlet.msbuild.tasks.csproj Add $(NetCurrent) to TFMs
    src/coverlet.MTP/coverlet.MTP.csproj Add $(NetCurrent) to TFMs
    src/coverlet.msbuild.tasks/buildMultiTargeting/coverlet.msbuild.props Dynamic TFM path (net8/net9/net10 conditions)
    test/Directory.Build.targets Replace _net8.0 with _$(NetMinimum)
    test/coverlet.core.tests/coverlet.core.tests.csproj net8.0 → $(NetMinimum);$(NetCurrent)
    test/coverlet.collector.tests/coverlet.collector.tests.csproj net8.0 → $(NetMinimum);$(NetCurrent)
    test/coverlet.core.coverage.tests/coverlet.core.coverage.tests.csproj net8.0 → $(NetMinimum);$(NetCurrent)
    test/coverlet.msbuild.tasks.tests/coverlet.msbuild.tasks.tests.csproj $(NetMinimum) → $(NetMinimum);$(NetCurrent)
    test/coverlet.MTP.tests/coverlet.MTP.tests.csproj net8.0 → $(NetMinimum);$(NetCurrent)
    test/coverlet.MTP.validation.tests/coverlet.MTP.validation.tests.csproj net8.0 → $(NetMinimum);$(NetCurrent)
    test/coverlet.integration.determisticbuild/coverlet.integration.determisticbuild.csproj Literals net9;net8 → $(NetCurrent);$(NetMinimum)
    eng/build.yml Add .NET 10 SDK install; add net10.0 integration test run; extend delete step
    eng/azure-pipelines.yml Verify agent image includes .NET 10
  2. added
    enhancementGeneral enhancement request
    breaking-changeIssue or PR that represents a breaking change in features or functional.
    and removed
    untriagedTo be investigated
    on Feb 18, 2026
  3. martincostello commented on Mar 22, 2026

    @martincostello
    ContributorAuthor

    I don't see what enhancements for .NET 9 and 10 has to do with support for .NET Framework being completely broken by v8.

  4. Bertk commented on Mar 22, 2026

    @Bertk
    Collaborator

    I don't see what enhancements for .NET 9 and 10 has to do with support for .NET Framework being completely broken by v8.

    Thank you for raising this point. The changes in .NET 9 and 10 are not directly related to the issue, but I’d like to clarify the situation.

    coverlet.core now depends on the System.Text.Json NuGet package, which requires the .NET 8.0 runtime for test execution. As part of this update, the major version was incremented to reflect the introduction of breaking changes.

    If you’re unable to use the .NET 8 runtime, you can continue using Coverlet version 6.0.x, which remains compatible with earlier environments.

    Anyway, you should test coverlet.msbuild V8.1.x again after the PR 1823 is merged. The nuget package will also target netstandard2.0 with required dependencies.

    Image
  5. martincostello commented on Mar 22, 2026

    @martincostello
    ContributorAuthor

    So, .NET Framework not working anymore is an intentional change, so the bugs people are reporting about it aren't actually bugs but "by design"?

    The responses to the issues are giving mixed messages.

    If it's a bug, it should be fixed (and the original issue reopened as users report it isn't actually resolved for them). The need for .NET to be installed should be irrelevant because .NET Framework is an independent product.

    If it's not a bug, the issues should just be closed and users should be directed to find alternative solutions to coverlet where they need to support .NET Framework.

    In my case I'm multi-targeting and I need a solution that works for both.

    If the 6.x version isn't going to get any new features or maintenance, then as much as I've been a big fan of coverlet I'll need to switch to alternative tools.

    Dropping support (or fundamentally breaking it unintentionally) for .NET Framework for a tool of this kind is unfortunate.

  6. Bertk commented on Mar 22, 2026

    @Bertk
    Collaborator

    @martincostello Please provide a minimal repo for your scenario with an exact runtime environment description and I will run a test using a devcontainer. Please do not use an artificial runtime environment which will not exist on actual windows installations. This will be a one-time effort and not maintained.

    Anyway, you should test coverlet.msbuild V8.1.x again after the PR 1823 is merged. The nuget package will also target netstandard2.0 with required dependencies.

  7. 46 remaining items

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    SolvedThe issue is solved and can be closedbreaking-changeIssue or PR that represents a breaking change in features or functional.enhancementGeneral enhancement request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions