Skip to content

Add DependencyOnlyTargetFrameworks to eliminate System.ValueTuple from net471 NuGet graph#126773

Draft
Copilot wants to merge 12 commits intomainfrom
copilot/add-net471-targets-to-packages
Draft

Add DependencyOnlyTargetFrameworks to eliminate System.ValueTuple from net471 NuGet graph#126773
Copilot wants to merge 12 commits intomainfrom
copilot/add-net471-targets-to-packages

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 10, 2026

net471 projects referencing these packages get System.ValueTuple pulled into the NuGet graph via the net462 dependency group, even though net471 has System.ValueTuple inbox. This adds a net471 dependency group (with no System.ValueTuple) to the 13 packages that directly reference it, without shipping any additional assemblies and without any compilation overhead during regular builds.

Description

Approach

Three options were evaluated. Size measurements are based on the 10.0.0 packages from the NuGet global cache.

Option 1 — Add net471 to all ~43 packages (transitive + direct) ❌ Discarded

Each of the 43 packages that transitively depends on System.ValueTuple would get a lib/net471 assembly added. The new net471 assemblies would be source identical to the existing net462 assemblies — pure overhead.

Package lib/net462 size Note
Microsoft.Bcl.Cryptography 307.8 KB transitive
Microsoft.Bcl.Memory 76.3 KB direct
Microsoft.Bcl.TimeProvider 33.3 KB direct
Microsoft.Extensions.Caching.Memory 51.8 KB direct
Microsoft.Extensions.Configuration 47.3 KB transitive
Microsoft.Extensions.Configuration.Abstractions 31.8 KB direct
Microsoft.Extensions.Configuration.Binder 48.8 KB transitive
Microsoft.Extensions.Configuration.CommandLine 28.8 KB transitive
Microsoft.Extensions.Configuration.EnvironmentVariables 26.8 KB transitive
Microsoft.Extensions.Configuration.FileExtensions 31.3 KB transitive
Microsoft.Extensions.Configuration.Ini 28.8 KB transitive
Microsoft.Extensions.Configuration.Json 31.3 KB transitive
Microsoft.Extensions.Configuration.UserSecrets 29.8 KB transitive
Microsoft.Extensions.Configuration.Xml 40.8 KB transitive
Microsoft.Extensions.DependencyModel 87.8 KB transitive
Microsoft.Extensions.Diagnostics 38.8 KB transitive
Microsoft.Extensions.Diagnostics.Abstractions 35.3 KB transitive
Microsoft.Extensions.Hosting 77.8 KB transitive
Microsoft.Extensions.Hosting.Abstractions 58.8 KB transitive
Microsoft.Extensions.Hosting.WindowsServices 33.8 KB transitive
Microsoft.Extensions.Http 83.8 KB transitive
Microsoft.Extensions.Logging 53.8 KB direct
Microsoft.Extensions.Logging.Configuration 33.8 KB transitive
Microsoft.Extensions.Logging.Console 85.8 KB transitive
Microsoft.Extensions.Logging.Debug 24.3 KB transitive
Microsoft.Extensions.Logging.EventLog 30.3 KB transitive
Microsoft.Extensions.Logging.EventSource 41.3 KB transitive
Microsoft.Extensions.Logging.TraceSource 26.3 KB transitive
Microsoft.Extensions.Options 68.8 KB direct
Microsoft.Extensions.Options.ConfigurationExtensions 29.3 KB transitive
Microsoft.Extensions.Options.DataAnnotations 27.8 KB transitive
System.Formats.Asn1 107.3 KB direct
System.Formats.Cbor 98.8 KB direct
System.Formats.Nrbf 69.8 KB direct
System.Linq.AsyncEnumerable ~300 KB direct (new package, not yet published standalone)
System.Memory.Data 37.8 KB transitive
System.Net.Http.Json 73.3 KB transitive
System.Resources.Extensions 113.3 KB transitive
System.Security.Cryptography.Cose 91.8 KB direct
System.Security.Cryptography.Pkcs 23.3 KB transitive
System.Security.Cryptography.Xml 57.8 KB transitive
System.Text.Encoding.CodePages 751.8 KB direct
System.Text.Json 759.3 KB direct

Total additional assembly size: ~4.1 MB (3,835 KB measured + ~300 KB estimated for System.Linq.AsyncEnumerable). 43 packages grow.

Option 2 — Add net471 only to the 13 packages with direct System.ValueTuple references ❌ Discarded

NuGet resolves each package's dependency group independently, so fixing only the 13 packages that directly reference System.ValueTuple is sufficient for net471 consumers. However, each of the 13 packages would still need a lib/net471 assembly — again source identical to the lib/net462 assembly.

Package lib/net462 size (= added lib/net471 size)
Microsoft.Bcl.Memory 76.3 KB
Microsoft.Bcl.TimeProvider 33.3 KB
Microsoft.Extensions.Caching.Memory 51.8 KB
Microsoft.Extensions.Configuration.Abstractions 31.8 KB
Microsoft.Extensions.Logging 53.8 KB
Microsoft.Extensions.Options 68.8 KB
System.Formats.Asn1 107.3 KB
System.Formats.Cbor 98.8 KB
System.Formats.Nrbf 69.8 KB
System.Linq.AsyncEnumerable ~300 KB (new package, not yet published standalone)
System.Security.Cryptography.Cose 91.8 KB
System.Text.Encoding.CodePages 751.8 KB
System.Text.Json 759.3 KB

Total additional assembly size: ~2.4 MB (2,102 KB measured + ~300 KB estimated for System.Linq.AsyncEnumerable). 13 packages grow.

Option 3 (chosen) — DependencyOnlyTargetFrameworks

Same NuGet graph fix as Option 2 (a net471 dependency group is emitted in the nuspec for each of the 13 packages), but no lib/net471 assembly is added. Consumers fall back to lib/net462 for the assembly. Total additional assembly size: 0 bytes. No packages grow. Dep-only TFMs are not compiled during regular builds.


Changes

  • eng/packaging.targets — Introduces DependencyOnlyTargetFrameworks: a general-purpose semicolon-separated property for TFMs that should emit a NuGet dependency group but ship no lib/ assembly. When set:

    • Dep-only TFMs are appended to TargetFrameworks so NuGet restore (including RestoreUseStaticGraphEvaluation) and pack both see them.
    • _NormalizedDependencyOnlyTargetFrameworks + IncludeBuildOutput=false (delimiter-based, case-insensitive) suppresses lib/ output for dep-only TFM inner builds invoked during pack.
    • NU5128 suppressed (intentional dep-group/lib mismatch — consumers fall back to the nearest compatible lib/ entry).
    • ExcludeDependencyOnlyTFMsFromBuildAndP2P (BeforeTargets="GetTargetFrameworksWithPlatformFromInnerBuilds;DispatchToInnerBuilds"): removes dep-only TFMs from _InnerBuildProjects before both the P2P TFM query (so consuming projects don't accidentally resolve a dep-only TFM) and the outer build dispatch (so dep-only TFMs are not compiled during regular dotnet build). The removed TFMs are not restored — pack's _WalkEachTargetPerFramework still invokes their inner builds directly via MSBuild task (not through _InnerBuildProjects), generating the nuspec dependency groups from project.assets.json.
  • Directory.Build.props — Introduces NetFrameworkWithValueTuple=net471 as a repo-wide property.

  • 13 src .csproj files — Add a DependencyOnlyTargetFrameworks property set to $(NetFrameworkWithValueTuple) and restrict System.ValueTuple PackageReference to $(NetFrameworkMinimum) only. Affected packages: Microsoft.Bcl.Memory, Microsoft.Bcl.TimeProvider, Microsoft.Extensions.Caching.Memory, Microsoft.Extensions.Configuration.Abstractions, Microsoft.Extensions.Logging, Microsoft.Extensions.Options, System.Formats.Asn1, System.Formats.Cbor, System.Formats.Nrbf, System.Linq.AsyncEnumerable, System.Security.Cryptography.Cose, System.Text.Encoding.CodePages, System.Text.Json

  • docs/coding-guidelines/libraries-packaging.md — Added a DependencyOnlyTargetFrameworks section documenting the feature: what it is, when to use it, a code example using $(NetFrameworkWithValueTuple), and how the eng/packaging.targets infrastructure implements it.

Result

For each affected package, the nuspec gains a .NETFramework4.7.1 dependency group without System.ValueTuple. net471 consumers resolve no System.ValueTuple dependency and fall back to lib/net462 for the assembly. No new assemblies ship; package sizes are unchanged. Dep-only TFMs are excluded from _InnerBuildProjects so they are never compiled during regular builds and never selected by P2P resolution. The approach works correctly with both standard and static-graph (RestoreUseStaticGraphEvaluation) restore.

  • Apply DependencyOnlyTargetFrameworks to 13 affected .csproj files
  • Introduce NetFrameworkWithValueTuple in Directory.Build.props
  • Implement DependencyOnlyTargetFrameworks in eng/packaging.targets via TargetFrameworks augmentation + ExcludeDependencyOnlyTFMsFromBuildAndP2P target + IncludeBuildOutput=false (compatible with RestoreUseStaticGraphEvaluation)
  • Filter DependencyOnlyTargetFrameworks TFMs from P2P TFM resolution and regular build dispatch
  • Add DependencyOnlyTargetFrameworks documentation to docs/coding-guidelines/libraries-packaging.md
  • Update PR description with Option 1/2 size measurements

Copilot AI and others added 4 commits April 10, 2026 16:34
…irect users (Option 2)

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/55b33308-5b6c-4b57-9bdb-81b49c84b6bf

Co-authored-by: ericstj <8918108+ericstj@users.noreply.github.com>
…ref assets shipped)

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/55b33308-5b6c-4b57-9bdb-81b49c84b6bf

Co-authored-by: ericstj <8918108+ericstj@users.noreply.github.com>
…n packaging.targets

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/474682a7-ecbe-4a65-b384-1477530033eb

Co-authored-by: ericstj <8918108+ericstj@users.noreply.github.com>
@ericstj
Copy link
Copy Markdown
Member

ericstj commented Apr 10, 2026

@copilot can you fix the docs, can you also update this issue with the data we gathered by trying both option 1 and option 2. All of these measurements should be part of https://github.com/dotnet/runtime/tasks/1eacb5f4-97cd-4cc7-8fc3-812fe976ef9b?session_id=3ab46313-c615-4bf8-ab41-c39ac2b60d77

Copilot AI requested review from Copilot and removed request for Copilot April 10, 2026 22:54
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-infrastructure-libraries
See info in area-owners.md if you want to be subscribed.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 10, 2026

@copilot can you fix the docs, can you also update this issue with the data we gathered by trying both option 1 and option 2. All of these measurements should be part of https://github.com/dotnet/ru...

Added the documentation in commit 02fa65e (docs/coding-guidelines/libraries-packaging.md). For the issue data — the GitHub Tasks link points to an external session I can't access directly. The approach comparison (Options 1, 2, 3) is captured in the PR description above; if you'd like specific package size measurements included in the issue, please share those numbers and I can add them to the PR description or the tracking task.

Copilot AI review requested due to automatic review settings April 11, 2026 18:44
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the libraries packaging infrastructure to allow emitting NuGet dependency groups for additional TFMs (specifically net471) without shipping extra lib/ assets, eliminating unnecessary System.ValueTuple dependencies for net471 consumers while keeping package size unchanged.

Changes:

  • Add DependencyOnlyTargetFrameworks support in eng/packaging.targets to emit dependency-only TFM groups (and suppress NU5128) without producing lib/ outputs.
  • Update affected library .csproj files to use DependencyOnlyTargetFrameworks and restrict System.ValueTuple PackageReferences to $(NetFrameworkMinimum) only.
  • Add documentation for DependencyOnlyTargetFrameworks and introduce NetFrameworkWithValueTuple=net471 for reuse.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
eng/packaging.targets Implements DependencyOnlyTargetFrameworks behavior for packing (append TFMs, suppress build output in package, suppress NU5128).
Directory.Build.props Introduces NetFrameworkWithValueTuple (set to net471) to centralize the chosen dependency-only TFM.
docs/coding-guidelines/libraries-packaging.md Documents DependencyOnlyTargetFrameworks usage and behavior.
src/libraries/System.Text.Json/src/System.Text.Json.csproj Uses DependencyOnlyTargetFrameworks and scopes System.ValueTuple dependency to $(NetFrameworkMinimum).
src/libraries/System.Text.Json/ref/System.Text.Json.csproj Scopes System.ValueTuple dependency to $(NetFrameworkMinimum) in ref project.
src/libraries/System.Text.Encoding.CodePages/src/System.Text.Encoding.CodePages.csproj Uses DependencyOnlyTargetFrameworks and scopes System.ValueTuple dependency to $(NetFrameworkMinimum).
src/libraries/System.Linq.AsyncEnumerable/src/System.Linq.AsyncEnumerable.csproj Uses DependencyOnlyTargetFrameworks and scopes System.ValueTuple dependency to $(NetFrameworkMinimum).
src/libraries/System.Linq.AsyncEnumerable/ref/System.Linq.AsyncEnumerable.csproj Scopes System.ValueTuple dependency to $(NetFrameworkMinimum) in ref project.
src/libraries/System.Formats.Nrbf/src/System.Formats.Nrbf.csproj Uses DependencyOnlyTargetFrameworks and scopes System.ValueTuple dependency to $(NetFrameworkMinimum).
src/libraries/System.Formats.Cbor/src/System.Formats.Cbor.csproj Uses DependencyOnlyTargetFrameworks and scopes System.ValueTuple dependency to $(NetFrameworkMinimum).
src/libraries/System.Formats.Asn1/src/System.Formats.Asn1.csproj Uses DependencyOnlyTargetFrameworks and scopes System.ValueTuple dependency to $(NetFrameworkMinimum).
src/libraries/Microsoft.Extensions.Options/src/Microsoft.Extensions.Options.csproj Uses DependencyOnlyTargetFrameworks and scopes System.ValueTuple dependency to $(NetFrameworkMinimum).
src/libraries/Microsoft.Extensions.Logging/src/Microsoft.Extensions.Logging.csproj Uses DependencyOnlyTargetFrameworks and scopes System.ValueTuple dependency to $(NetFrameworkMinimum).
src/libraries/Microsoft.Extensions.Configuration.Abstractions/src/Microsoft.Extensions.Configuration.Abstractions.csproj Uses DependencyOnlyTargetFrameworks and scopes System.ValueTuple dependency to $(NetFrameworkMinimum).
src/libraries/Microsoft.Extensions.Caching.Memory/src/Microsoft.Extensions.Caching.Memory.csproj Uses DependencyOnlyTargetFrameworks and scopes System.ValueTuple dependency to $(NetFrameworkMinimum).
src/libraries/Microsoft.Bcl.TimeProvider/src/Microsoft.Bcl.TimeProvider.csproj Uses DependencyOnlyTargetFrameworks and scopes System.ValueTuple dependency to $(NetFrameworkMinimum).
src/libraries/Microsoft.Bcl.Memory/src/Microsoft.Bcl.Memory.csproj Uses DependencyOnlyTargetFrameworks and scopes System.ValueTuple dependency to $(NetFrameworkMinimum).
src/libraries/System.Security.Cryptography.Cose/ref/System.Security.Cryptography.Cose.csproj Scopes System.ValueTuple dependency to $(NetFrameworkMinimum) in ref project.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 18 out of 18 changed files in this pull request and generated 1 comment.

@github-actions
Copy link
Copy Markdown
Contributor

🤖 Copilot Code Review — PR #126773

Note

This review was generated by Copilot.

Holistic Assessment

Motivation: Well-justified. net471 projects consuming these packages unnecessarily pull in System.ValueTuple via the net462 dependency group, even though net471 has System.ValueTuple inbox. The PR description includes thorough size analysis of three options, demonstrating clear engineering rigor.

Approach: The chosen approach (Option 3 — DependencyOnlyTargetFrameworks) is the best of the three evaluated. It emits a net471 dependency group (without System.ValueTuple) while shipping zero additional assemblies, since the net471 assemblies were byte-for-byte identical to net462. The general-purpose MSBuild property is well-designed for reuse.

Summary: ✅ LGTM. The implementation is correct, the approach is well-motivated, and the documentation is clear. One minor cosmetic nit below. All verification points checked out.


Detailed Findings

✅ MSBuild Logic — Correctly implemented

The core logic in eng/packaging.targets is sound:

  • Sentinel delimiters (;$(DependencyOnlyTargetFrameworks.ToUpperInvariant());) prevent partial TFM matches (e.g., net47 won't accidentally match net471). The ToUpperInvariant() handles case-insensitive comparison correctly.
  • IncludeBuildOutput=false is correctly conditioned on TargetFramework != '' (inner builds only), so outer builds are unaffected.
  • NU5128 suppression is appropriate — the missing lib/ entry is intentional.
  • Source-build compatibility: $(NetFrameworkWithValueTuple) is empty when DotNetBuildSourceOnly=true, so DependencyOnlyTargetFrameworks is empty and the entire mechanism is a no-op. ✅

✅ No build time impact

The existing Copilot review comment about "increasing build time" is a false positive. Before this PR, $(NetFrameworkWithValueTuple) (net471) was already in TargetFrameworks in all 12 src projects — the inner build for net471 was already happening. This PR doesn't add a new inner build; it only changes what goes into the package (IncludeBuildOutput=false). Build time is unchanged.

✅ ValueTuple references correctly scoped

Verified all 12 affected src projects: every System.ValueTuple PackageReference is conditioned on '$(TargetFramework)' == '$(NetFrameworkMinimum)' (net462). The net471 inner build therefore produces a dependency group that correctly omits System.ValueTuple. This is the key behavior the PR aims to achieve. ✅

✅ Completeness — All affected packages converted

All 12 src/ csprojs that previously had $(NetFrameworkWithValueTuple) in TargetFrameworks now use DependencyOnlyTargetFrameworks. All 10 ref/ csprojs (Microsoft.Bcl.Memory and Microsoft.Bcl.TimeProvider have no ref projects) correctly remove $(NetFrameworkWithValueTuple) from their TargetFrameworks without adding DependencyOnlyTargetFrameworks, since ref projects don't drive package creation. No remaining uses of $(NetFrameworkWithValueTuple) in TargetFrameworks exist anywhere in src/. ✅

✅ Documentation — Clear and accurate

The DependencyOnlyTargetFrameworks section in libraries-packaging.md explains the concept, provides a concrete example with $(NetFrameworkWithValueTuple), and describes the three packaging.targets behaviors. No trailing whitespace issues. ✅

💡 Duplicate comment — Minor cosmetic nit

In eng/packaging.targets, lines 36 and 38 have the identical comment:

<!-- Suppress build output for these TFMs so no lib/ entry is added to the package. -->

Line 36 applies to _NormalizedDependencyOnlyTargetFrameworks (normalization), while line 38 applies to IncludeBuildOutput (the actual suppression). Consider updating the line 36 comment to describe the normalization step, e.g., <!-- Normalize TFMs for case-insensitive matching. -->. Not a blocker.

✅ Interaction with existing packaging.targets logic

  • Line 18 (IncludeBuildOutput=false for platform-specific builds): No conflict — targets different conditions (.NETCoreApp + platform identifier).
  • Lines 131–136 (NETStandardCompatError items that check TargetFrameworks.Contains('net47')): Net471 was already in TargetFrameworks before this PR, so the compat error items were already generated. No behavioral change.
  • PackageValidationBaselineFrameworkToIgnore: Does not include net471, so validation will check the net471 framework. Since the assemblies removed from lib/net471 were identical to lib/net462, this should be a non-issue for API compat. If baseline validation flags it at release time, net471 could be added to the ignore list.

Generated by Code Review for issue #126773 ·

-->
<!-- Append DependencyOnlyTargetFrameworks to TargetFrameworks so the inner builds run. -->
<TargetFrameworks Condition="'$(DependencyOnlyTargetFrameworks)' != '' and '$(TargetFrameworks)' != ''">$(TargetFrameworks);$(DependencyOnlyTargetFrameworks)</TargetFrameworks>
<!-- Suppress build output for these TFMs so no lib/ entry is added to the package. -->
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
<!-- Suppress build output for these TFMs so no lib/ entry is added to the package. -->

Copilot AI requested a review from ericstj April 11, 2026 20:04
</ItemGroup>

<!--
Exclude DependencyOnlyTargetFrameworks TFMs from P2P TFM resolution so that test projects or
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Comment on lines +67 to +106
<!--
Exclude DependencyOnlyTargetFrameworks TFMs from P2P TFM resolution so that test projects or
consuming projects don't accidentally resolve the dependency-only TFM instead of the shipping
NetFrameworkMinimum TFM. The dependency-only TFMs are only meaningful for NuGet packaging
(to emit an empty dependency group); they should be invisible to project references.

The save/restore pattern mirrors the approach used in the arcade SDK's buildMultiTargeting
targets: filter _InnerBuildProjects before GetTargetFrameworksWithPlatformFromInnerBuilds
(the P2P query target) and restore afterward so DispatchToInnerBuilds still builds them.
-->
<Target Name="ExcludeDependencyOnlyTFMsFromP2PQuery"
Condition="'$(DependencyOnlyTargetFrameworks)' != '' and '$(IsCrossTargetingBuild)' == 'true'"
BeforeTargets="GetTargetFrameworksWithPlatformFromInnerBuilds">
<ItemGroup>
<!-- Save the full set so it can be restored after the P2P TFM query. -->
<_InnerBuildProjectsWithDependencyOnlyTFMs Include="@(_InnerBuildProjects)" />
<!-- Build one filter item per DependencyOnly TFM, using the same AdditionalProperties
format that _ComputeTargetFrameworkItems uses for _InnerBuildProjects. -->
<_DependencyOnlyTFMItems Include="$(DependencyOnlyTargetFrameworks)" />
<_DependencyOnlyInnerBuildFilter Include="$(MSBuildProjectFile)">
<AdditionalProperties>TargetFramework=%(_DependencyOnlyTFMItems.Identity)</AdditionalProperties>
</_DependencyOnlyInnerBuildFilter>
<!-- Remove DependencyOnly TFMs from the inner builds visible to P2P callers. -->
<_InnerBuildProjects Remove="@(_DependencyOnlyInnerBuildFilter)"
MatchOnMetadata="AdditionalProperties"
MatchOnMetadataOptions="CaseInsensitive" />
</ItemGroup>
</Target>

<Target Name="RestoreInnerBuildProjectsAfterP2PQuery"
Condition="'@(_InnerBuildProjectsWithDependencyOnlyTFMs)' != ''"
AfterTargets="GetTargetFrameworksWithPlatformFromInnerBuilds">
<ItemGroup>
<!-- Restore the full set so DispatchToInnerBuilds still builds the DependencyOnly TFMs
(they need to compile for NuGet to generate the dependency group in the nuspec). -->
<_InnerBuildProjects Remove="@(_InnerBuildProjects)" />
<_InnerBuildProjects Include="@(_InnerBuildProjectsWithDependencyOnlyTFMs)" />
</ItemGroup>
</Target>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You know I was thinking we might be able to simplify even more. What if we were able to completely remove these from TargetFrameworks -- so that they are only seen by restore and nothing else. If we can find the right hook, that might be simpler. Then we don't need builds, IncludeBuildOutput, nothing. @copilot can you find the correct place to hook and the right things to modify?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in commits 70549aa and 25aee97. The approach uses two hooks into the NuGet SDK's internal item groups, so dep-only TFMs are never added to TargetFrameworks and require no compilation:

  1. AddDependencyOnlyTFMsToRestoreGraph (AfterTargets="_GetRestoreTargetFrameworksAsItems"): adds dep-only TFMs to _RestoreTargetFrameworkItems — the list that _GenerateProjectRestoreGraphAllFrameworks iterates to evaluate the project per-TFM. NuGet re-evaluates the project with TargetFramework=net471, finds no System.ValueTuple (the PackageReference is conditioned on NetFrameworkMinimum), and writes the correct dependencies to project.assets.json.

  2. AddDependencyOnlyTFMsToPackFrameworks (AfterTargets="_WalkEachTargetPerFramework"): adds dep-only TFMs to _TargetFrameworks after _WalkEachTargetPerFramework has already invoked all inner builds for the real TFMs. The PackTask sees them in _TargetFrameworks and generates nuspec dependency groups by reading project.assets.json — no inner build is triggered.

This eliminates IncludeBuildOutput, _NormalizedDependencyOnlyTargetFrameworks, NU5128 suppression scope reduction, and the two P2P filtering targets.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@copilot This won't work for RestoreUseStaticGraphEvaluation. I think we might be able to use the previous technique with ExcludeDependencyOnlyTFMsFromP2PQuery, but hook at BeforeTargets="GetTargetFrameworksWithPlatformFromInnerBuilds;DispatchToInnerBuilds" to remove from both build and p2p and don't bother adding them back. We'll need to bring back IncludeBuildOutput setting

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in da13fde. Reverted to the TargetFrameworks-based approach to work correctly with RestoreUseStaticGraphEvaluation. The two previous targets are replaced with a single ExcludeDependencyOnlyTFMsFromBuildAndP2P target:

<Target Name="ExcludeDependencyOnlyTFMsFromBuildAndP2P"
        Condition="'$(DependencyOnlyTargetFrameworks)' != '' and '$(IsCrossTargetingBuild)' == 'true'"
        BeforeTargets="GetTargetFrameworksWithPlatformFromInnerBuilds;DispatchToInnerBuilds">

This removes dep-only TFMs from _InnerBuildProjects before both the P2P query and the build dispatch, and does not restore them afterward. Pack's _WalkEachTargetPerFramework still invokes them directly via MSBuild task (not through _InnerBuildProjects), so nuspec dep groups are still generated. IncludeBuildOutput=false is restored to prevent lib/ output for those TFMs during pack.

Copilot AI requested review from Copilot and removed request for Copilot April 11, 2026 21:06
Copilot AI requested review from Copilot and removed request for Copilot April 11, 2026 21:07
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 11, 2026

@copilot can you fix the docs, can you also update this issue with the data we gathered by trying both option 1 and option 2. All of these measurements should be part of https://github.com/dotnet/ru...

The docs have been updated in the latest commits to reflect the simplified restore/pack-hook approach — no mention of builds or IncludeBuildOutput any more.

Copilot AI requested review from Copilot and removed request for Copilot April 12, 2026 11:28
…jects matching

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/58641b9e-4679-48a0-8d13-0bc7069613f9

Co-authored-by: ericstj <8918108+ericstj@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot April 12, 2026 11:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants