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

How to keep referenced Assembly out of Build Directory? Private not working #4371

Closed
RickStrahl opened this issue May 11, 2019 · 24 comments
Closed
Labels
needs-triage Have yet to determine what bucket this goes in.

Comments

@RickStrahl
Copy link

RickStrahl commented May 11, 2019

Steps to reproduce

I'm trying to build a project that has a Project Dependency to another project where the final output folder should not contain the original assembly and its depedencies. Essentially I need to use Copy Local: No behavior. This works for assembly references, but not for project references.

The options show in Visual Studio and in the project, but they seem to be ignored when the project is built.

Here's what the relevant project section I'm using looks like:

  <ItemGroup>
      <ReferenceOutputAssembly>true</ReferenceOutputAssembly>
    <ProjectReference Include="..\..\MarkdownMonster\MarkdownMonster.csproj">
      <Private>false</Private>
      <CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
    </ProjectReference>
  </ItemGroup>

The project above is the main project of the application, and this project is an addin that runs in the context of that host application.

The project compiles fine, but the output folder of the addin project still gets all the host project references AND all of its dependencies. IOW, it pulls in the entire host application.

image

I've been working around this by using an explicit assembly reference instead of a project reference like this:

<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.0'">
    <Reference Include="$(SolutionDir)MarkdownMonster\bin\$(Configuration)\$(TargetFramework)\win-x86\MarkdownMonster.dll">
      <Private>false</Private>
    </Reference> 
  </ItemGroup>
  <ItemGroup Condition=" '$(TargetFramework)' == 'net462'">
    <Reference Include="$(SolutionDir)MarkdownMonster\bin\$(Configuration)\$(TargetFramework)\win-x86\MarkdownMonster.exe">
      <Private>false</Private>
    </Reference>
  </ItemGroup>

This works as expected, but this has build order problems where often compilation fails if the main project has not been compiled (even if explicitly setting the project dependencies in the solution file).

Other

This project is a multi-targeted WPF project to build both .NET Core 3.0 and for 4.6.2 and both the 3.0 and 4.6.2 build do the same thing.

This used to work just by Copy Local: No on the assembly reference added by a project reference in old style .csproj projects.

Expected behavior

I'd like to see a Project Reference behave the same way as the Assembly Reference with <Private>false</Private> providing a compilation reference, but not actually copying the output into the build target folder.

Actual behavior

Files are copied local even Copy Local: No

Environment data

OS info:
Windows 10
dotnet core SDK 3.0 Preview 5
.NET Core 3.0 WPF Project

@Skyppid
Copy link

Skyppid commented Oct 8, 2020

Any progress on that? We're struggling with this issue as well. Always trashes our modules folder which should only contain the module libraries as their dependencies are resolved at runtime anyways. Can't believe that something so simple still hasn't made it into the toolchain.

@vfofanov
Copy link

vfofanov commented Nov 20, 2020

I found answer here
You need to set PrivateAssets=all to MarkdownMonster.csproj reference

@mgexo
Copy link

mgexo commented Apr 8, 2021

We also have the problem that project dependency spams our output folders unnecessarily, even when with

<Private>false</Private>
 <CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>

The PrivateAssets=all workaround seems only to apply to nuget PackageReference - not to project dependency references.

@trivalik
Copy link

You have to apply the <PrivateAssets>all</PrivateAssets> to the parent of your dependency, since the copy to output trigger was earlier in dependency tree. This works well. Alternative solution can be to add the dependency again and force it to not copy. Another solution would be to add PrivateAssets to Directory.Build.props as follows:

<Project>
       <ItemDefinitionGroup>
              <PackageReference>
                     <PrivateAssets>all</PrivateAssets>
              </PackageReference>
              <ProjectReference>
                     <PrivateAssets>all</PrivateAssets>
              </ProjectReference>
       </ItemDefinitionGroup>
</Project>

Source of this information is https://curia.me/net-core-transitive-references-and-how-to-block-them/

@FlaynGordt
Copy link

FlaynGordt commented Feb 14, 2022

How is this still not fixed? It was working fine with the old csproj format.

@rainersigwald rainersigwald added the needs-triage Have yet to determine what bucket this goes in. label Feb 14, 2022
@FlaynGordt
Copy link

I have found a workaround for this by adding DisableTransitiveProjectReferences to the project settings.

@ds5678
Copy link

ds5678 commented Mar 5, 2022

+1 This is so frustrating

@ds5678
Copy link

ds5678 commented Mar 5, 2022

None of the recommended workarounds worked for me. I only see two viable options:

  • Write a script for deleting dependency files
  • Write a script for copying the nondependency files

Both of these are ridiculous and shouldn't be necessary.

@trivalik
Copy link

trivalik commented Mar 7, 2022

@ds5678 If you set <PrivateAssets>all</PrivateAssets> for every dependency correct then it should work. If you have a longer dependency chain, then probably every item of the chain require this. You can play arround with msbuild log set to diagnostics to find the cause and set then PrivateAssets.

@FlaynGordt
Copy link

@ds5678 If you set <PrivateAssets>all</PrivateAssets> for every dependency correct then it should work. If you have a longer dependency chain, then probably every item of the chain require this. You can play arround with msbuild log set to diagnostics to find the cause and set then PrivateAssets.

I think that only works for nuget references.

@chrisp0
Copy link

chrisp0 commented Apr 10, 2022

+1

Any progress on that? We're struggling with this issue as well. Always trashes our modules folder which should only contain the module libraries as their dependencies are resolved at runtime anyways.

Referenced project will not be copied but dependecies of the project will copied to output

@RickStrahl
Copy link
Author

RickStrahl commented Apr 10, 2022

I've taken to explicitly referencing the project's assembly instead of the project in this case.

  <ItemGroup Condition=" '$(TargetFramework)' == 'net60'">
    <Reference Include="$(SolutionDir)MarkdownMonster\\bin\\$(Configuration)\$(TargetFramework)\\win-x64\\MarkdownMonster.dll">
      <Private>false</Private>
    </Reference>
  </ItemGroup>

Using <Private>false</Private> keeps the dependencies from being copied over.

None of the other things suggested (<PrivateAssets>) in the posts above seem to work for me for using a project reference.

I fail to see why a project reference should not work the same as a another reference using the same settings. At the end of the day the end result that is expected is the same and you should be able to use a direct reference, Nuget reference or project reference pretty much interchangeably.

@chrisp0
Copy link

chrisp0 commented Apr 11, 2022

Ok, i will try this too

But maybe it's still a bug after all that referencing a project works different from referencing a dll/assembly/package.

@tisis2
Copy link

tisis2 commented Apr 12, 2022

i am heading the same issue atm and its really frustrating that a workflow that is even documented here (https://docs.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support#simple-plugin-with-no-dependencies) is not working cause of that issue

@80O
Copy link

80O commented May 23, 2022

I dont know why, but this ended up working for me:

In the library .csproj:

<Project Sdk="Microsoft.NET.Sdk">

	<PropertyGroup>
		<TargetFramework>net6.0</TargetFramework>
		<ImplicitUsings>enable</ImplicitUsings>
		<Nullable>enable</Nullable>
	</PropertyGroup>

	<ItemGroup>
		<Reference Include="Main Project">
			<HintPath>..\MainProject\bin\Debug\net6.0\MainProject.dll</HintPath>
			<Private>false</Private>
		</Reference>
	</ItemGroup>
</Project>

When you test, make sure to clean the output folder or you might think it does not work.

@trivalik
Copy link

@80O Your hint path looks like it will not work if you clean your solution and build it with "Release" configuration.

@RickStrahl
Copy link
Author

RickStrahl commented Jul 24, 2022

@80O The syntax you want is this:

<ItemGroup>
      <Reference Include="$(SolutionDir)MarkdownMonster\bin\$(Configuration)\$(TargetFramework)\win-x64\MarkdownMonster.dll">
      <Private>false</Private>
    </Reference>
</ItemGroup>

This works, but it shouldn't be this hard.

There's no reason we shouldn't be able to reference the .csproj as a <ProjectReference> and it should be able to figure out to reference the main assembly in the correct folder without all this MsBuild stuff.

Especially since none of the tooling (Visual Studio, Rider etc.) will set this up correctly if you set Copy Local to false.

The no Copy Local is sort of an edge case - typically for Addins or pluggable components - but this should still work and it seems to me this syntax should just work:

<ItemGroup>
    <Reference Include="$(SolutionDir)MarkdownMonster\bin\$(Configuration)\$(TargetFramework)\win-x64\MarkdownMonster.dll">
      <Private>false</Private>
    </Reference>
</ItemGroup>

rainersigwald added a commit to rainersigwald/demo-msbuild-4371 that referenced this issue Jul 25, 2022
@rainersigwald
Copy link
Member

@RickStrahl can you give an example of a project that is behaving as you describe? I set up a small demo at https://github.com/rainersigwald/demo-msbuild-4371, and put up rainersigwald/demo-msbuild-4371#1 to show how I solved the problem you're describing, which didn't require any of the reference juggling.

I would like to discourage people from replacing ProjectReference with Reference as much as possible; it's highly likely to cause you build-order-related headaches down the road, in addition to having to hardcode fragile reconstructed paths.

@trivalik
Copy link

@rainersigwald In your example it would be enough to set <Private>False</Private> for both projects/ProjectReference.
But if would configure outputdir of Referencing by <BaseOutputPath>SomeWhereElse</BaseOutputPath> this will not work anymore. Then you have additionally to configure <PrivateAssets>all</PrivateAssets> in Referenced project for the ProjectReference TransitiveReferenced. See rainersigwald/demo-msbuild-4371#2

@rainersigwald
Copy link
Member

The conversation moved through a few channels, but @RickStrahl confirmed that you can accomplish the goals

  1. Don't mess with the app/referenced project's output directory.
  2. Don't copy the app/referenced project to the plugin/referencing project's output folder.
  3. Don't copy the app's ProjectReferences to the plugin/referencing project's output folder.
  4. Don't copy the app's PackageReferences to the plugin/referencing project's output folder.

With a few properties and metadata entries:

  1. Set Private="false" on the ProjectReference from the plugin project to the app. This avoids the copies from 2 and 3. VS will do this for you if you set the reference CopyLocal through the UI.
  2. Set ExcludeAssets="all" on the ProjectReference from the plugin project to the app. This avoids the copies from 4, which arise because at restore time NuGet finds transitive references of both PackageReferences and ProjectReferences. This isn't very discoverable: the XSD does list ExcludeAssets for PackageReference items but only in its element form, a subset of Update IntelliSense XSD to support multiple metadata representations #7028. It's also not clear that annotating a ProjectReference in this way applies to transitive assets: ProjectReference NuGet assets stuff is transitive #7849.
  3. Set <DisableTransitiveProjectReferences>true</DisableTransitiveProjectReferences> in the plugin project. This keeps the .NET SDK from creating ProjectReference items to all projects that are referenced by any ProjectReferences that are directly in the project. This was undocumented (Add DisableTransitiveProjectReferences docs#30341) and not in the XSD (XSD entry for DisableTransitiveProjectReferences #7850).

@RickStrahl
Copy link
Author

RickStrahl commented Jul 27, 2022

As a follow up - the solution appears to be in this declaration to reference the main project:

<ItemGroup>
    <ProjectReference Include="../../MarkdownMonster/MarkdownMonster.csproj">
        <Private>false</Private>
        <ExcludeAssets>all</ExcludeAssets>
    </ProjectReference>
</ItemGroup>

It also works with <IncludeAssets>compile</IncludeAssets>.

Note that both Private and ExcludeAssets or IncludeAssets have to be used in combination, and Visual Studio's Copy Local=false setting only sets the Private flag, so that doesn't work out of box.

This generates only the local project main assembly plus any direct project references, but excludes any incoming dependencies from the main MarkdownMonster project.

I've updated and somewhat cleaned up my old post with more detailed information on how to best reference resources from a typical addin style project here:

.NET Core SDK Projects: Controlling Output Folders and Dependencies Output

@glenn-slayden
Copy link

glenn-slayden commented Apr 27, 2023

@rainersigwald helpfully wrote:

  1. Set Private="false" on the ProjectReference from the plugin project to the app.

If desired, there is a further trick to this step. If you have many .dlls that need this treatment, and they are all below a singular file system node (subtree) such that there is a prevailing Directory.Build.Props file, then you don't have to manually clutter-up every .csproj file all over the place. That job is actually far worse that it seems at first, because for each of those sub-modules, you have to go in and expand each and every one of the <ProjectReference> it makes, within them all, in turn.

Instead, you can automatically apply the behavior to any/all of the (sub-suib-) <ProjectReference> "mentions" that are made by any of the respective (subsumed) submodules by doing the following:

In an appropriately prevailing Directory.Build.Props:

<Project>
	<ItemDefinitionGroup>
		<ProjectReference>
			<Private Condition="'$(OutputType)' == 'library'">false</Private>
		</ProjectReference>
	</ItemDefinitionGroup>
</Project>

Intuitively enough, there's an extra level of nesting, which is the magic here: the <ItemDefinitionGroup> element (note: not <ItemGroup> that you're familiar with), deploys an abstracted operation, like a "macro", over any/all number of scoped <ProjectReference> elements that may later follow.

As with the theory of this solution in general, we only want to affect .dll builds, because this is the "sub-sub-module" proliferation that becomes so useless and problematic. The common-sense rule-of-thumb (indeed, the TL;DR; of the whole issue here IMHO) is that, since a sub-module itself can never be a primary runtime host, it should never receive or privately posess any binaries of any (putative) referents (beyond itself), as a result of--or during the course of--its being built. This is called "nipping the (.dll proliferation) problem in the bud."

Only the final executable host in a modular system should have to worry about and implement some policy for gathering the disparate parts needed for actual, runtime use, That policy should not be "regularly freeze the hard disk by wastefully reducing-down a gigatic union of 80% binary images which are byte-duplicates, since every sub-module has pointlessly hoarded duplicate copies for itself."

Who cares about tracking overlapping usage between adjacent or unrelated plug-ins? It's a non-problem that can't affect anything; leave it to the host-build where it's comically trivial to just de-duplicate the entire set of all comers, for once and only. In fact, if the method described here is followed assiduously, there's not even any de-duping during the host build--it can just copy each sub-module's \bin directory into its own, and ideally there will be zero excess-copying and no .DLL file will ever be overwritten. I've done it this way for many years and the builds are obviously way faster, not to mention more reliable due to reduced overwriting collisions in general.

A similar syntax works for subduing all <PackageReference> entries that may happen to be referenced by any/all of the submodules as well:

<Project>
	<ItemDefinitionGroup>
		<PackageReference Condition="'$(OutputType)' != 'library'">
			<ExcludeAssets>runtime</ExcludeAssets>
		</PackageReference>
	</ItemDefinitionGroup>
</Project>

Oh, and <Reference> works similar to <ProjectReference>, but indeed it, and in fact all three of the reference-tag-types I've mentioned are treated independently by the <ItemDefinitionGroup> handing, so you can (or must) deploy any combination of them according to your needs.

@panagiotidisc
Copy link

I have a <ProjectReference> that passes a Transitive ProjectReferences. Both the project A, and its dependency project B require a third project C to build, and A references C through B.
I wish to exclude both B and C from A's build output. In A I can <DisableTransitiveProjectReferences>true</DisableTransitiveProjectReferences>, add C's <ProjectReference> and add <Private>False</Private> to it, but I was wondering if there is another (shorter) way to do it.
And no, doing it in an <ItemDefinitionGroup> block is undesirable to me, since I want this to apply only to B and C, and not to other <ProjectReference>s.

@kimdiego2098
Copy link

Is there still no good solution

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs-triage Have yet to determine what bucket this goes in.
Projects
None yet
Development

No branches or pull requests