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

[Bug]: Files from referenced assembly are copied despite setting Private to False #8756

Closed
alex-netkachov opened this issue May 13, 2023 · 9 comments

Comments

@alex-netkachov
Copy link

Issue Description

A minimal sample project that reproduces the issue:

Main.sln

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "P1", "P1\P1.csproj", "{48CDE3E7-2C01-4AFB-BB03-A42E20C81E10}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "P2", "P2\P2.csproj", "{22FE0983-46BC-40D9-8501-BB33338F7A1B}"
	ProjectSection(ProjectDependencies) = postProject
		{48CDE3E7-2C01-4AFB-BB03-A42E20C81E10} = {48CDE3E7-2C01-4AFB-BB03-A42E20C81E10}
	EndProjectSection
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
		{48CDE3E7-2C01-4AFB-BB03-A42E20C81E10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{48CDE3E7-2C01-4AFB-BB03-A42E20C81E10}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{48CDE3E7-2C01-4AFB-BB03-A42E20C81E10}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{48CDE3E7-2C01-4AFB-BB03-A42E20C81E10}.Release|Any CPU.Build.0 = Release|Any CPU
		{22FE0983-46BC-40D9-8501-BB33338F7A1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{22FE0983-46BC-40D9-8501-BB33338F7A1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{22FE0983-46BC-40D9-8501-BB33338F7A1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{22FE0983-46BC-40D9-8501-BB33338F7A1B}.Release|Any CPU.Build.0 = Release|Any CPU
	EndGlobalSection
EndGlobal

P1\P1.csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net48</TargetFramework>
    <OutputType>Exe</OutputType>
  </PropertyGroup>
  <ItemGroup>
    <None Include="Test.txt">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>
</Project>

P1\Main.cs

namespace P1 {
  public static class Program {
    public static void Main() { }
  }
}

P1\Test.txt

-

P2\P2.csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net48</TargetFramework>
    <OutputType>library</OutputType>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="P1">
      <HintPath>..\P1\bin\Debug\net48\P1.exe</HintPath>
      <Private>False</Private>
    </Reference>
  </ItemGroup>
</Project>

P2\C1.cs

namespace P2 {
  public class C1 { }
}

Steps to Reproduce

Now build the project:

dotnet build

Expected Behavior

The expected result is not having Test.txt in the output folder.

When building project one-by-one this doesn't happen:

> dotnet build .\P1\P1.csproj
> dotnet build .\P2\P2.csproj
> dir .\P2\bin\Debug\net48\
... prints P2.dll and P2.pdb ...

Actual Behavior

Output folder contains Test.txt.

> dir .\P2\bin\Debug\net48\
... prints P2.dll, P2.pdb, and Test.txt ...

Analysis

No response

Versions & Configurations

> dotnet --version
7.0.203
> dotnet --list-sdks
7.0.203 [C:\Program Files\dotnet\sdk]
@alex-netkachov alex-netkachov added bug needs-triage Have yet to determine what bucket this goes in. labels May 13, 2023
@KalleOlaviNiemitalo
Copy link

You have this in Main.sln:

Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "P2", "P2\P2.csproj", "{22FE0983-46BC-40D9-8501-BB33338F7A1B}"
	ProjectSection(ProjectDependencies) = postProject
		{48CDE3E7-2C01-4AFB-BB03-A42E20C81E10} = {48CDE3E7-2C01-4AFB-BB03-A42E20C81E10}
	EndProjectSection
EndProject

MSBuild translates that to a ProjectReference that does not have Private metadata. If you build with MSBUILDEMITSOLUTION=1 in the environment, then MSBuild saves P2/P2.csproj.metaproj, which includes the generated ProjectReference.

The ProjectReference then causes P1/Test.txt to be copied to P2/bin/Debug/net48/Test.txt, even if you delete this Reference from P2/P2.csproj:

  <ItemGroup>
    <Reference Include="P1">
      <HintPath>..\P1\bin\Debug\net48\P1.exe</HintPath>
      <Private>False</Private>
    </Reference>
  </ItemGroup>

I think you should replace the Reference in P2/P2.csproj with a ProjectReference so that you can control its Private metadata, and delete the dependency from Main.sln.

@KalleOlaviNiemitalo
Copy link

If you build with MSBUILDEMITSOLUTION=1 in the environment, then MSBuild saves P2/P2.csproj.metaproj, which includes the generated ProjectReference.

Oops, the ProjectReference in P2/P2.csproj.metaproj seems to be used for sequencing only. The item that is used for copying content instead comes from the AssignProjectConfiguration task executed by the AssignProjectConfiguration target. This task gets <ProjectDependency Project="{48CDE3E7-2C01-4AFB-BB03-A42E20C81E10}" /> as part of its SolutionConfigurationContents parameter. The resulting ProjectReferenceWithConfiguration item has metadata CopyLocal=false, but it does not have the Private metadata that the _GetCopyToOutputDirectoryItemsFromTransitiveProjectReferences target would check.

The CopyLocal metadata is apparently added here:

item.SetMetadata("CopyLocal", "false");

I wonder if that should be changed to add Private=false as well, or alternatively, _GetCopyToOutputDirectoryItemsFromTransitiveProjectReferences could check the CopyLocal metadata.

Condition="'@(_MSBuildProjectReferenceExistent)' != '' and '$(_GetChildProjectCopyToOutputDirectoryItems)' == 'true' and '%(_MSBuildProjectReferenceExistent.Private)' != 'false' and '$(UseCommonOutputDirectory)' != 'true'"

@alex-netkachov
Copy link
Author

I've tried to use ProjectReference, but it also does not respect Private completely. If I add a project "P0" and reference it from P1 and then use project reference from P2 (i.e. P2 project-references P1 with Private=False, P1 project-references P0) then on build dotnet copies P0 binaries to P2's target folder (and does not copy P1's binaries, so Private is respected but not for dependencies).

@alex-netkachov
Copy link
Author

@KalleOlaviNiemitalo Thank you for looking into that. Indeed, I've added dependency into solution for sequencing only.

I do not mind using ProjectReference but ideally a non-private reference should not copy anything to the current project. Unfortunately, it does.

@KalleOlaviNiemitalo
Copy link

I've tried to use ProjectReference, but it also does not respect Private completely.

Does it respect CopyLocal?

@alex-netkachov
Copy link
Author

alex-netkachov commented May 15, 2023

I've tried to use ProjectReference, but it also does not respect Private completely.

Does it respect CopyLocal?

It is the same thing, isn't it?

https://learn.microsoft.com/en-us/visualstudio/msbuild/common-msbuild-project-items?view=vs-2022#projectreference
Private Optional boolean. Specifies whether the reference should be copied to the output folder. This attribute matches the Copy Local property of the reference that's in the Visual Studio IDE.

@JanKrivanek
Copy link
Member

JanKrivanek commented May 28, 2023

Related: #4795 (attempt to express build ordering only)

@AlexG5T It sounds that above mentioned case is related to you. In your case you need only <Private>false</Private> (to prevent copying of build outputs of P1 into P2 output) and <ExcludeAssets>all</ExcludeAssets> (as P1 is and .exe, so additional supplemental files need to be explictly removed).
Note those apply to ProjectReference. Using a Reference for a build output of a project that is a part of the build is not recommended practise (and while build of entire solution behaves unexpectedly here - as it is able to locate P1.csproj project via _MSBuildProjectReferenceExistent metada populated during the build of P1.csproj - it's of a low priority for addressing now as it has recommended workaround - expresing the relationship directly via ProjectReference).

So the suggested P2.csproj:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net48</TargetFramework>
    <OutputType>library</OutputType>
  </PropertyGroup>
  <ItemGroup>
    <ProjectReference Include="../P1/P1.csproj">
      <Private>false</Private>
      <ExcludeAssets>all</ExcludeAssets>
    </ProjectReference>
  </ItemGroup>
</Project>

@JanKrivanek JanKrivanek removed the needs-triage Have yet to determine what bucket this goes in. label May 28, 2023
@alex-netkachov
Copy link
Author

Thank you for the workaround. ProjectReference with <Private>false</Private> and <ExcludeAssets>all</ExcludeAssets> solved the build ordering without the solution.

@JanKrivanek
Copy link
Member

Closing as duplicate of #4795

@JanKrivanek JanKrivanek closed this as not planned Won't fix, can't repro, duplicate, stale May 29, 2023
@AR-May AR-May added the triaged label Feb 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants