Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 4, 2025

Summary: Add warning when using --framework with solution files

This PR addresses the issue where using --framework on a solution build can cause referenced projects to build multiple times, leading to inconsistent builds and file locking issues.

Changes Made

  • Added NETSDK1234 warning string resource in Strings.resx
  • Modified FrameworkOption in CommonOptions.cs to set _CommandLineDefinedTargetFramework=true property
  • Added _CheckForSolutionLevelTargetFramework target in Microsoft.NET.Sdk.Solution.targets
  • Updated warning text to say "isn't recommended" instead of "isn't supported"
  • Resolved merge conflict with main branch
  • Verified changes work with manual testing

Implementation Details

Similar to the existing -o/--output warning (NETSDK1194), this adds warning NETSDK1234 when users specify --framework on a solution build.

How it works:

  1. When --framework is used, the CLI forwards both --property:TargetFramework=<value> and --property:_CommandLineDefinedTargetFramework=true
  2. The Solution targets file checks for the _CommandLineDefinedTargetFramework property
  3. If set, it displays NETSDK1234 warning before Build, Publish, Clean, Store, VSTest targets
  4. Using --property:TargetFramework=<value> directly does NOT trigger the warning (as expected)

Example Output

$ dotnet build --framework net10.0
warning NETSDK1234: The "--framework" option isn't recommended when building a solution. 
Specifying a solution-level framework results in all projects building for that framework, 
which can cause referenced projects to build multiple times and can lead to inconsistent builds.
Original prompt

This section details on the original issue you should resolve

<issue_title>dotnet build --framework builds referenced project twice</issue_title>
<issue_description><!--
Please keep in mind that the GitHub issue tracker is not intended as a general support forum, but for reporting non-security bugs and feature requests.

If you believe you have an issue that affects the SECURITY of the platform, please do NOT create an issue and instead email your issue details to secure@microsoft.com. Your report may be eligible for our bug bounty but ONLY if it is reported through email.
For other types of questions, consider using StackOverflow.

-->

Describe the bug

dotnet build with --framework builds the same project twice in parallel, if it is referenced by a solution and a ProjectReference. Without --framework, it builds only once.

To Reproduce

Dep.sln
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30114.105
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "One", "One\One.csproj", "{6C41A8DE-5C2D-4D36-9F5E-5070A5333DC9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Two", "Two\Two.csproj", "{8DEDAFB4-3647-450C-BD9A-0D1978A0A950}"
EndProject
Global
        GlobalSection(SolutionConfigurationPlatforms) = preSolution
                Debug|Any CPU = Debug|Any CPU
                Release|Any CPU = Release|Any CPU
        EndGlobalSection
        GlobalSection(SolutionProperties) = preSolution
                HideSolutionNode = FALSE
        EndGlobalSection
        GlobalSection(ProjectConfigurationPlatforms) = postSolution
                {6C41A8DE-5C2D-4D36-9F5E-5070A5333DC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
                {6C41A8DE-5C2D-4D36-9F5E-5070A5333DC9}.Debug|Any CPU.Build.0 = Debug|Any CPU
                {6C41A8DE-5C2D-4D36-9F5E-5070A5333DC9}.Release|Any CPU.ActiveCfg = Release|Any CPU
                {6C41A8DE-5C2D-4D36-9F5E-5070A5333DC9}.Release|Any CPU.Build.0 = Release|Any CPU
                {8DEDAFB4-3647-450C-BD9A-0D1978A0A950}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
                {8DEDAFB4-3647-450C-BD9A-0D1978A0A950}.Debug|Any CPU.Build.0 = Debug|Any CPU
                {8DEDAFB4-3647-450C-BD9A-0D1978A0A950}.Release|Any CPU.ActiveCfg = Release|Any CPU
                {8DEDAFB4-3647-450C-BD9A-0D1978A0A950}.Release|Any CPU.Build.0 = Release|Any CPU
        EndGlobalSection
EndGlobal
One/One.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.XmlSerializer.Generator" Version="6.0.0" />
  </ItemGroup>

  <ItemGroup>
    <DotNetCliToolReference Include="Microsoft.XmlSerializer.Generator" Version="6.0.0" />
  </ItemGroup>

</Project>
One/Class1.cs
namespace One
{
    [System.Xml.Serialization.XmlRoot]
    public class Class1
    {
    }
}
Two/Two.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\One\One.csproj" />
  </ItemGroup>

</Project>
Log of dotnet build --framework net6.0 becoming unexpectedly parallel and attempting to access a locked file
$ dotnet build --framework net6.0
Microsoft (R) Build Engine version 17.2.0+41abc5629 for .NET
Copyright (C) Microsoft Corporation. All rights reserved.

  Determining projects to restore...
  All projects are up-to-date for restore.
  One -> C:\Projects\Dep\One\bin\Debug\net6.0\One.dll
  One -> C:\Projects\Dep\One\bin\Debug\net6.0\One.dll
  .NET Xml Serialization Generation Utility, Version 6.0.0]
  Serialization Code File Name: C:\Projects\Dep\One\obj\Debug\net6.0\One.XmlSerializers.cs.
  Generated serialization code for assembly C:\Projects\Dep\One\obj\Debug\net6.0\One.dll --> 'C:\Projects\Dep\One\obj\Debug\net6.0\One.X
  mlSerializers.cs'.
  .NET Xml Serialization Generation Utility, Version 6.0.0]
  Serialization Code File Name: C:\Projects\Dep\One\obj\Debug\net6.0\One.XmlSerializers.cs.
  Generated serialization code for assembly C:\Projects\Dep\One\obj\Debug\net6.0\One.dll --> 'C:\Projects\Dep\One\obj\Debug\net6.0\One.X
  mlSerializers.cs'.
CSC : warning CS1504: Source file 'C:\Projects\Dep\One\obj\Debug\net6.0\One.XmlSerializers.cs' could not be opened -- The process cannot
 access the file 'C:\Projects\Dep\One\obj\Debug\net6.0\One.XmlSerializers.cs' because it is being used by another ...

</details>

- Fixes dotnet/sdk#26690

<!-- START COPILOT CODING AGENT TIPS -->
---

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs.

Copilot AI and others added 2 commits November 4, 2025 21:57
- Added NETSDK1234 warning string in Strings.resx
- Modified FrameworkOption to set _CommandLineDefinedTargetFramework property
- Added _CheckForSolutionLevelTargetFramework target in Microsoft.NET.Sdk.Solution.targets
- Created FrameworkOptionTests.cs with test cases for the new warning

Co-authored-by: marcpopMSFT <12663534+marcpopMSFT@users.noreply.github.com>
Manual testing confirms the feature works correctly:
- Using --framework on solution shows NETSDK1234 warning
- Using --property:TargetFramework does not show warning
- Warning appears for build, clean, publish, and test commands

Changes:
- Added NETSDK1234 warning string in Strings.resx
- Modified FrameworkOption to set _CommandLineDefinedTargetFramework property
- Added _CheckForSolutionLevelTargetFramework target in Microsoft.NET.Sdk.Solution.targets

Co-authored-by: marcpopMSFT <12663534+marcpopMSFT@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix dotnet build to avoid double project builds Add warning when using --framework with solution files Nov 4, 2025
Copilot AI requested a review from marcpopMSFT November 4, 2025 22:30
Copilot finished work on behalf of marcpopMSFT November 4, 2025 22:30
@marcpopMSFT
Copy link
Member

@baronfel in the spirit of limiting new warnings, should we only spit out this warning if the TFM is net11? People are definitely going to hit it.

@baronfel
Copy link
Member

baronfel commented Nov 7, 2025

The problem (similar to the output path) is that we don't know at the solution-level that these targets run at if the projects target 11.

We probably should create a Target similar to what Restore does and have the solution gather the TFMs of the project and flag if the TFM is present.

@KalleOlaviNiemitalo
Copy link
Contributor

Does this warn when dotnet test --no-build is used with --framework on a solution? I expect it shouldn't.

@marcpopMSFT marcpopMSFT added the breaking-change Using this label will notify dotnet/compat and trigger a request to file a compat bug label Nov 13, 2025
@dotnet-policy-service
Copy link
Contributor

Added needs-breaking-change-doc-created label because this PR has the breaking-change label.

When you commit this breaking change:

  1. Create and link to this PR and the issue a matching issue in the dotnet/docs repo using the breaking change documentation template, then remove this needs-breaking-change-doc-created label.
  2. Ask a committer to mail the .NET SDK Breaking Change Notification email list.

You can refer to the .NET SDK breaking change guidelines

@marcpopMSFT
Copy link
Member

@copilot can you change the test to say "isn't recommended" rather than "isn't supported"

@marcpopMSFT
Copy link
Member

Does this warn when dotnet test --no-build is used with --framework on a solution? I expect it shouldn't.
Currently the implementation does. I'm not quite sure I understand the scenario test + --no-build + --framework is used for against a solution. CC @baronfel and @dotnet/dotnet-testing-admin for their opinion on what the behavior should be in that situation.

Updated NETSDK1234 warning text per feedback to be less severe.

Co-authored-by: marcpopMSFT <12663534+marcpopMSFT@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Nov 13, 2025

@copilot can you change the test to say "isn't recommended" rather than "isn't supported"

Changed warning text from "isn't supported" to "isn't recommended" in commit 35d513c.

Updated warning output:

warning NETSDK1234: The "--framework" option isn't recommended when building a solution. Specifying a solution-level framework results in all projects building for that framework, which can cause referenced projects to build multiple times and can lead to inconsistent builds.

Copilot finished work on behalf of marcpopMSFT November 13, 2025 22:04
@Evangelink
Copy link
Member

Cc @Youssef1313 and @nohwnd

@marcpopMSFT
Copy link
Member

@copilot fix the merge conflict

Resolved conflict in CommonOptions.cs by adapting to the new Option structure:
- Uses Option<string> instead of DynamicForwardedOption<string>
- Sets IsDynamic = true
- Uses ForwardAsMany to forward both TargetFramework and _CommandLineDefinedTargetFramework properties
- Maintains AddCompletions at the end
Copy link
Contributor Author

Copilot AI commented Nov 14, 2025

@copilot fix the merge conflict

Merge conflict resolved in commit 6e5f32a. The FrameworkOption was adapted to the new Option structure from main while maintaining the warning functionality.

Copilot finished work on behalf of marcpopMSFT November 14, 2025 00:50
@Youssef1313
Copy link
Member

That sounds like a non-trivial breaking change. But as long as it applies to Build, I'm fine with applying it to everything else (test, both MTP and VSTest, publish, etc..).

Comment on lines +40 to +42
<Target Name="_CheckForSolutionLevelTargetFramework"
BeforeTargets="Build;Publish;Clean;Store;VSTest;_MTPBuild"
Condition="'$(_CommandLineDefinedTargetFramework)' == 'true'">
Copy link
Member

Choose a reason for hiding this comment

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

Does this condition make sense? Users will still be able to bypass that with -p:TargetFramework=...? Is it intended to allow the bypass? If yes, IMO deserves a comment saying it explicitly and demonstrating why.

</Target>

<Target Name="_CheckForSolutionLevelTargetFramework"
BeforeTargets="Build;Publish;Clean;Store;VSTest;_MTPBuild"
Copy link
Member

Choose a reason for hiding this comment

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

Note: for the case of dotnet test --no-build -f ..., this target isn't going to run.


</Target>

<Target Name="_CheckForSolutionLevelTargetFramework"
Copy link
Member

Choose a reason for hiding this comment

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

Do we need Pack as well?


</Target>

<Target Name="_CheckForSolutionLevelTargetFramework"
Copy link
Member

Choose a reason for hiding this comment

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

Is it fine during restore?

@Youssef1313
Copy link
Member

I'd love to see some tests here for the affected commands, to ensure it doesn't break with any future refactorings.

@KalleOlaviNiemitalo
Copy link
Contributor

I'm not quite sure I understand the scenario test + --no-build + --framework is used for against a solution.

Testing some libraries on both .NET Framework and .NET Core, and collecting the test results from different frameworks to different files so that a web UI can easily display them as separate.

@Youssef1313
Copy link
Member

Also worth noting that this scenario could be quite different between VSTest and MTP per the current way it's implemented. In VSTest, we always go through the VSTest target, and there we decide if VSTestNoBuild property is true or not to decide if we will build or just run.

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

Labels

breaking-change Using this label will notify dotnet/compat and trigger a request to file a compat bug needs-breaking-change-doc-created

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants