Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
livarcocc opened this issue Jul 10, 2017 · 11 comments
Labels

Comments

@livarcocc
Copy link
Contributor

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: af1e668

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
Copy link
Contributor Author

Moving to msbuild, per @rainersigwald

@cdmihai
Copy link
Contributor

cdmihai commented Jul 12, 2017

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.

@rainersigwald
Copy link
Member

The reason this longstanding behavior breaks the build is that the sdk doesn't respect ProjectReferences with ReferenceOutputAssembly=false, which is tracked by dotnet/sdk#939.

But it still seems odd to have this belt-and suspenders approach.

@davkean
Copy link
Member

davkean commented Oct 10, 2017

There's a dup of this here: #2511.

@JeffCyr
Copy link

JeffCyr commented Oct 2, 2018

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

@dasMulli
Copy link
Contributor

dasMulli commented Oct 3, 2018

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?

@rainersigwald
Copy link
Member

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.

@JeffCyr
Copy link

JeffCyr commented Oct 9, 2018

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

@msedi
Copy link

msedi commented Jun 12, 2024

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.

@rainersigwald
Copy link
Member

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.

@msedi
Copy link

msedi commented Jun 13, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

8 participants