msbuild icon indicating copy to clipboard operation
msbuild copied to clipboard

'dotnet build' assumes "Build Dependencies -> Project Dependencies" are project references

Open livarcocc opened this issue 8 years ago • 11 comments

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

livarcocc avatar Jul 10 '17 20:07 livarcocc

Moving to msbuild, per @rainersigwald

livarcocc avatar Jul 10 '17 20:07 livarcocc

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:

  1. 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.metaproj which first MSBuilds FF.csproj and then Core.csproj.
  2. 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.

cdmihai avatar Jul 12 '17 18:07 cdmihai

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.

rainersigwald avatar Jul 14 '17 21:07 rainersigwald

There's a dup of this here: https://github.com/Microsoft/msbuild/issues/2511.

davkean avatar Oct 10 '17 04:10 davkean

@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.

JeffCyr avatar Oct 02 '18 19:10 JeffCyr

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?

dasMulli avatar Oct 03 '18 06:10 dasMulli

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.

rainersigwald avatar Oct 03 '18 14:10 rainersigwald

@dasMulli I confirm that the issue is fixed in VS 15.9 Preview 3, thanks.

JeffCyr avatar Oct 09 '18 13:10 JeffCyr

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.

msedi avatar Jun 12 '24 05:06 msedi

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 avatar Jun 12 '24 13:06 rainersigwald

@rainersigwald: Thanks, if have opened an issue: #10234

msedi avatar Jun 13 '24 07:06 msedi