Summary
Proposal: have the SDK default BuildProjectReferences=false when NoBuild=true, mirroring the existing rule for DesignTimeBuild. This would make project-to-project (P2P) reference resolution consistent with the --no-build contract and centrally fix a class of NETSDK1085 failures that occur when any target chain reaches ResolveReferences under NoBuild=true.
Background / current behavior
--no-build forwards NoBuild=true as a global property, which is inherited by referenced projects. Reference resolution in Microsoft.Common.CurrentVersion.targets (ResolveProjectReferences) chooses between two branches based on BuildProjectReferences:
- non-building
GetTargetPath call, taken when BuildProjectReferences != ''true''
- invoke the reference''s
Build target, taken when BuildProjectReferences == ''true''
<!-- Microsoft.Common.CurrentVersion.targets (~line 2133): non-building path -->
<MSBuild Projects="@(_MSBuildProjectReferenceExistent)" Targets="GetTargetPath"
Condition="... ('$(BuildingInsideVisualStudio)' == 'true' or '$(BuildProjectReferences)' != 'true') ..." />
<!-- (~line 2153): building path -->
<MSBuild Projects="@(_MSBuildProjectReferenceExistent)" Targets="%(_MSBuildProjectReferenceExistent.Targets)"
Condition="... '$(BuildProjectReferences)' == 'true' ..." />
Today there is no NoBuild -> BuildProjectReferences rule, and _GlobalPropertiesToRemoveFromProjectReferences only strips OutputPath (not NoBuild). So with the default BuildProjectReferences=true, a --no-build operation that resolves references will:
- take the building branch and invoke
Build on each P2P reference, then
- each reference sees the inherited
NoBuild=true, and
_CheckForBuildWithNoBuild (Microsoft.NET.Sdk.targets) fires error NETSDK1085: The 'NoBuild' property was set to true but the 'Build' target was invoked.
<!-- Microsoft.NET.Sdk.targets (~line 260) -->
<Target Name="_CheckForBuildWithNoBuild" Condition="'$(NoBuild)' == 'true'">
<NETSdkError ResourceName="NoBuildRequested" />
</Target>
How this surfaced
This was hit by NuGet''s Pack targets: _GetFrameworkAssemblyReferences DependsOnTargets="ResolveReferences". When packing a project that has P2P references with dotnet pack --no-build, resolving references invoked Build on the referenced projects and failed with NETSDK1085. See NuGet/NuGet.Client#7541 for the NuGet-side fix and a full repro. publish --no-build avoids the same shape only because it hand-rolls a separate _PublishNoBuildAlternative path in Microsoft.NET.Publish.targets.
Proposal
Add an SDK default that mirrors the existing DesignTimeBuild rule:
<!-- existing, Microsoft.Common.CurrentVersion.targets ~line 375 -->
<BuildProjectReferences Condition="'$(BuildProjectReferences)' == '' and '$(DesignTimeBuild)' == 'true'">false</BuildProjectReferences>
<!-- proposed addition -->
<BuildProjectReferences Condition="'$(BuildProjectReferences)' == '' and '$(NoBuild)' == 'true'">false</BuildProjectReferences>
(Exact placement TBD — could live in the .NET SDK props/targets rather than Common, so it only affects SDK-style projects.)
Why this is reasonable
- Semantically correct.
--no-build asserts "everything is already built; just consume outputs." Building references contradicts that; resolving them via GetTargetPath (read existing output paths) is exactly what no-build means.
- Cascades correctly. Because
NoBuild is global and inherited by every reference, each reference''s own evaluation independently sets BuildProjectReferences=false, so the whole transitive graph resolves without building — no need to also mark it global.
- Precedent exists.
DesignTimeBuild=true already forces BuildProjectReferences=false via the same mechanism.
- Centralizes a fix. Any target chain that reaches
ResolveReferences under NoBuild (Pack, custom output-group targets, third-party targets) benefits, instead of each having to special-case it the way publish and NuGet Pack do.
Considerations / risks
- Different failure mode when preconditions are not met. If someone runs
--no-build without having built first (or with stale/missing reference outputs), today they get NETSDK1085; afterward they''d get a downstream "reference assembly not found" style error from GetTargetPath returning a non-existent path. Arguably clearer, but it is a diagnostic change.
- Removes a (very unusual) capability. "Don''t build me, but do build my references" would no longer be expressible via
NoBuild alone. This seems like an anti-pattern, but calling it out.
- Only a default. Guarded on
'$(BuildProjectReferences)' == '', so anyone who explicitly sets BuildProjectReferences=true keeps current behavior.
Questions to dig into
- Should this live in Common.targets (all project types) or the .NET SDK targets/props (SDK-style only)?
- Does
test --no-build / any other NoBuild consumer rely on references being built today?
- Interaction with
BuildingInsideVisualStudio (VS already takes the non-building branch regardless).
- Would this let
Microsoft.NET.Publish.targets retire its bespoke no-build alternative, or is that still needed for other reasons?
Related
Summary
Proposal: have the SDK default
BuildProjectReferences=falsewhenNoBuild=true, mirroring the existing rule forDesignTimeBuild. This would make project-to-project (P2P) reference resolution consistent with the--no-buildcontract and centrally fix a class ofNETSDK1085failures that occur when any target chain reachesResolveReferencesunderNoBuild=true.Background / current behavior
--no-buildforwardsNoBuild=trueas a global property, which is inherited by referenced projects. Reference resolution inMicrosoft.Common.CurrentVersion.targets(ResolveProjectReferences) chooses between two branches based onBuildProjectReferences:GetTargetPathcall, taken whenBuildProjectReferences != ''true''Buildtarget, taken whenBuildProjectReferences == ''true''Today there is no
NoBuild -> BuildProjectReferencesrule, and_GlobalPropertiesToRemoveFromProjectReferencesonly stripsOutputPath(notNoBuild). So with the defaultBuildProjectReferences=true, a--no-buildoperation that resolves references will:Buildon each P2P reference, thenNoBuild=true, and_CheckForBuildWithNoBuild(Microsoft.NET.Sdk.targets) fireserror NETSDK1085: The 'NoBuild' property was set to true but the 'Build' target was invoked.How this surfaced
This was hit by NuGet''s Pack targets:
_GetFrameworkAssemblyReferencesDependsOnTargets="ResolveReferences". When packing a project that has P2P references withdotnet pack --no-build, resolving references invokedBuildon the referenced projects and failed with NETSDK1085. See NuGet/NuGet.Client#7541 for the NuGet-side fix and a full repro.publish --no-buildavoids the same shape only because it hand-rolls a separate_PublishNoBuildAlternativepath inMicrosoft.NET.Publish.targets.Proposal
Add an SDK default that mirrors the existing
DesignTimeBuildrule:(Exact placement TBD — could live in the .NET SDK props/targets rather than Common, so it only affects SDK-style projects.)
Why this is reasonable
--no-buildasserts "everything is already built; just consume outputs." Building references contradicts that; resolving them viaGetTargetPath(read existing output paths) is exactly what no-build means.NoBuildis global and inherited by every reference, each reference''s own evaluation independently setsBuildProjectReferences=false, so the whole transitive graph resolves without building — no need to also mark it global.DesignTimeBuild=truealready forcesBuildProjectReferences=falsevia the same mechanism.ResolveReferencesunderNoBuild(Pack, custom output-group targets, third-party targets) benefits, instead of each having to special-case it the waypublishand NuGet Pack do.Considerations / risks
--no-buildwithout having built first (or with stale/missing reference outputs), today they get NETSDK1085; afterward they''d get a downstream "reference assembly not found" style error fromGetTargetPathreturning a non-existent path. Arguably clearer, but it is a diagnostic change.NoBuildalone. This seems like an anti-pattern, but calling it out.'$(BuildProjectReferences)' == '', so anyone who explicitly setsBuildProjectReferences=truekeeps current behavior.Questions to dig into
test --no-build/ any otherNoBuildconsumer rely on references being built today?BuildingInsideVisualStudio(VS already takes the non-building branch regardless).Microsoft.NET.Publish.targetsretire its bespoke no-build alternative, or is that still needed for other reasons?Related