msbuild
msbuild copied to clipboard
'dotnet build' assumes "Build Dependencies -> Project Dependencies" are project references
From @evildour on July 5, 2017 15:54
Steps to reproduce
Create new .NET Core class library (and solution) in VS 2017 Right click solution and add new .NET Core class library Edit csproj file for the 2nd class library Change its TargetFramework from netcoreapp1.1 to net45 Right click on the first class library's project and click "Build dependencies -> Project dependencies..." Check the box to make it depend on the other class library Rebuild in VS (everything works fine) Open a command line and go to the sln file's directory Run 'dotnet build'
Expected behavior
The build succeeds as it did in VS.
Actual behavior
The following error occurs: Project '...' targets '.NETFramework,Version=v4.5'. It cannot be referenced by a project that targets '.NETCoreApp,Version=v1.1'.
Environment data
dotnet --info output:
.NET Command Line Tools (1.0.4)
Product Information: Version: 1.0.4 Commit SHA-1 hash: af1e6684fd
Runtime Environment: OS Name: Windows OS Version: 10.0.10586 OS Platform: Windows RID: win10-x64 Base Path: C:\Program Files\dotnet\sdk\1.0.4
Copied from original issue: dotnet/cli#7075
Moving to msbuild, per @rainersigwald
TL;DR
We suggest that the Microsoft.Net.Sdk set the AddSyntheticProjectReferencesForSolutionDependencies property to false in its props. Builds would then behave the same for VS, dotnet build, and direct msbuild invocations. The impact would also be minimal, since it would be scoped for sdk based projects, and the sln based project dependencies are obsolete either way.
Solutions files offer the capability of defining build ordering. This is probably obsolete functionality from the time before MSBuild. Here is a sln snippet containing a project dependency:
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Core\Core.csproj", "{334778F6-6AC6-4BBA-AC3A-0D9AF15503EE}"
ProjectSection(ProjectDependencies) = postProject
{5BDF9C4F-F6FA-4DD3-8F60-0FE79B7A77A3} = {5BDF9C4F-F6FA-4DD3-8F60-0FE79B7A77A3}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FF", "FF\FF.csproj", "{5BDF9C4F-F6FA-4DD3-8F60-0FE79B7A77A3}"
EndProject
The above snippet states that FF.csproj must be built before Core.csproj.
MSBuild converts solution files into in-memory msbuild projects (called meta projects), and then proceeds to build the meta projects. They are very similar to traversal targets. To write these in-memory to disk, you have to set the MSBuildEmitSolution environment variable.
For some reason I don't understand, sln project dependencies are implemented twice in msbuild's sln interpretation:
- When a project has dependencies, MSBuild generates a new metaproject for that project and calls the MSBuild task twice, once on the dependencies and then again on the project itself. When no sln project dependencies are specified, there exists only one meta project. So in our example above, MSBuild generates
Core.csproj.metaprojwhich first MSBuildsFF.csprojand thenCore.csproj. - The main solution meta project defines a property containing the solution project dependencies:
<CurrentSolutionConfigurationContents>
<SolutionConfiguration
xmlns="">
<ProjectConfiguration Project="{334778F6-6AC6-4BBA-AC3A-0D9AF15503EE}" AbsolutePath="D:\projects\tests\projects\CoreOnFF\Core\Core.csproj" BuildProjectInSolution="True">Debug|AnyCPU
<ProjectDependency Project="{5BDF9C4F-F6FA-4DD3-8F60-0FE79B7A77A3}" />
</ProjectConfiguration>
<ProjectConfiguration Project="{5BDF9C4F-F6FA-4DD3-8F60-0FE79B7A77A3}" AbsolutePath="D:\projects\tests\projects\CoreOnFF\FF\FF.csproj" BuildProjectInSolution="True">Debug|AnyCPU</ProjectConfiguration>
</SolutionConfiguration>
</CurrentSolutionConfigurationContents>
This property is then recursively piped down to projects and gets read in Microsoft.Common.CurrentVersion.targets:AssignProjectConfiguration. AssignProjectConfiguration then parses this property and injects extra items into the ProjectReference item, representing the extra dependencies from the solution file.
Mechanism 1. seems sufficient to obtain the same behaviour as VS. Mechanism 2. is the one causing the crash in this issue because it injects the full framework project as a ProjectReference project dependency into the .net core project. I don't understand why mechanism 2. even exists.
To turn off mechanism 2. the following property must be set to false: /p:AddSyntheticProjectReferencesForSolutionDependencies=false. The build then succeeds, with the correct sln based project ordering.
The reason this longstanding behavior breaks the build is that the sdk doesn't respect ProjectReferences with ReferenceOutputAssembly=false, which is tracked by https://github.com/dotnet/sdk/issues/939.
But it still seems odd to have this belt-and suspenders approach.
There's a dup of this here: https://github.com/Microsoft/msbuild/issues/2511.
@cdmihai
I was happy to try the AddSyntheticProjectReferencesForSolutionDependencies=false workaround but it unfortunately doesn't work.
Consider this sln:
- ProjectA
- ProjectB (ProjectDependency => ProjectA)
- ProjectC (ProjectReference => ProjectB)
ProjectB and ProjectC build order is not enforced by the sln. If ProjectC is built first, it will build ProjectB because it's a ProjectReference (without going through the metaproj), but with AddSyntheticProjectReferencesForSolutionDependencies=false, ProjectA won't be built before ProjectB and the build may fail.
The second workaround would be to specify a ProjectDependency even if there is a ProjectReference between the projects, but this is unmaintainable.
The proper behavior would be achieved if the build tree was processed manually by getting each project's ProjectReference, correlated with custom BuildDependencies, and built with BuildProjectReferences=false
Another solution could be to add an option to skip the build when a TargetFramework is not supported instead of failing.
I think the issues with synthetic project references have mostly been addressed and should work for single-TFM projects in VS 15.8 / 2.1.4xx CLIs.
An issue with multi-targeting projects has been fixed in 15.9 / 2.1.5xx, @JeffCyr could you try to build this with the preview bits and not setting the AddSyntheticProjectReferencesForSolutionDependencies property to false?
Hey, there is a reason for the synthetic references to exist! Thanks @JeffCyr.
Repro: mixed-dependencies.zip
I think the best way forward in this situation is to change the B->A link to a ProjectReference, either setting the TF for the reference explicitly
<ProjectReference Include="..\ProjectA\ProjectA.csproj"
SetTargetFramework="TargetFramework=net46"
ReferenceOutputAssembly="false" />
Or adding A's TF to B's AssetTargetFallback property
<PropertyGroup>
<AssetTargetFallback>netcoreapp2.0</AssetTargetFallback>
</PropertyGroup>
Unfortunately, we can't make this behavior the default for ReferenceOutputAssembly="false", because that broke people that depended on the TF negotiation being correct even when not referencing the output directly.
@dasMulli I confirm that the issue is fixed in VS 15.9 Preview 3, thanks.
It would be interesting to solve this problem. We were searching many days and couldn't explain what was happening. The different behaviors of dotnet build and MSBuild are really a pain point and costs of a lot of investigation time.
The different behaviors of dotnet build and MSBuild are really a pain point and costs of a lot of investigation time.
Please start a new issue with details--the behavior described in this issue does not vary between MSBuild.exe and dotnet build.
@rainersigwald: Thanks, if have opened an issue: #10234