Set "Copy Local" to False by default?
Asked Answered
H

6

39

Can I set the default-option of "Copy Local" in Visual Studio to False? In most times, when I add a dll as dependency of a project, I want the Copy Local property set to False. Per default, it is True. Is there a way to change the default behaviour of Visual Studio? (2008)

Herbartian answered 23/6, 2010 at 11:13 Comment(1)
For a easy solution starting with msbuild v 15 see my answer below https://mcmap.net/q/399858/-set-quot-copy-local-quot-to-false-by-defaultApyretic
L
38

No - Visual Studio uses an internal set of rules to determine what to set Copy Local to.

From MSDN:

  1. If the reference is another project, called a project-to-project reference, then the value is true.
  2. If the assembly is found in the global assembly cache, the value is false.
  3. As a special case, the value for the mscorlib.dll reference is false.
  4. If the assembly is found in the Framework SDK folder, then the value is false.
  5. Otherwise, the value is true.
Locker answered 23/6, 2010 at 11:22 Comment(1)
The content in the MSDN link has been retiredSamora
O
29

Actually, you can. You need a couple things:

  1. Create .targets file that makes copylocal (<Private> tag, to be precise) false by default.
  2. Import the target in .csproj files. You can add it in the very last line, before closing </Project> tag, it'll look like <Import Project="..\Build\yourtarget.targets" />.

Now each project with this target has copylocal disabled by default.

The drawback is that you need to modify each and every csproj file, including new ones. You can work around the new project issue by modifying the VS project template. Instead of Class.cs described in the blog article, you need to modify Class.vstemplate (in the same zip file).

With that approach, there's one more problem - the path itself. If you use hardcoded relative path in newly-generated csproj files, they may be wrong (unless you have flat project structure).

You can:

  • Make VS generate correct relative path. Not sure how to do that and if that's even possible.
  • Ignore it and change the path manually for each new csproj (depending on the number of new project you have, while not ideal, that may be tolerable).
  • Use the environment variable instead of relative path. In that case every developer will need the same variable set.

There must be better solution for that, but haven't found it yet.

Output answered 31/5, 2012 at 11:3 Comment(4)
This appears to be the correct answer, I've followed the guidance and the links and it works!Replenish
@AnthonyMastrean: have you figured out a clever way to put import in a newly created csproj files?Output
We're playing with a common .csproj file that's imported into every new project by using a template. The template system isn't very clean and we're not sure if we like it yet.Replenish
You can list every project file under a root with dir /s /b *csproj > project-files.txt, then open them up with say notepad++ with for /f %i in (project-files.txt) do notepad++ "%i". As notepad++ allows you to replace in all open files, you could ask it to replace (using regular expressions) ^</Project> with <Import Project="$\(SolutionDir\)customTarget.targets /></Project>. You then just need to create a targets file customTarget.target alongside your .sln file. I wanted to try this but the barrier to entry seemed enormous (modifying every csproj!) but then this struck me ...Frisbie
A
12

Starting with msbuild v 15 you can copy a single file called Directory.Build.props in the root folder that contains your source:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemDefinitionGroup>
  <Reference>
    <Private>False</Private>
  </Reference>
  <ProjectReference>
     <Private>False</Private>
  </ProjectReference>
</ItemDefinitionGroup>
</Project>

Nothing more to do! This works well with Visual Studio 2017 and also the vNext Build. You might have to close Visual Studio and than open your solution again to take the file effect.

https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-your-build#directorybuildprops-and-directorybuildtargets

Apyretic answered 8/6, 2018 at 7:25 Comment(4)
Is this a method that can only be used for .net core projects? Or can you also do this for older .net projects e.g. .net 452?Avalos
From reading Glen Slayden's answer, would it not be better to also include <ProjectReference> <Private>False</Private> </ProjectReference> as well?Avalos
@Dave Barnett You are right, if this work for ProjectReferences too. Have you tried it? You can add the tag in my answer, if you like. I don't use Project References in my Project. I checked the solution with dll-References on.Net 4.7.1 and vNext Build on TFS 2018 and Visual Studio Enterprise 2017Apyretic
Yes I tried it on a .net 4.5.2 project. This one used project references and obviously didn't work until I realised I needed to use the project reference tag, then it did work. A really nice solution as there is no need to edit any existing files. I've added the tag and awaiting the peer review.Avalos
I
8

We don't use a .targets files (as suggested in the answer by ya23), so we just edit the .csproj project file manually in a text editor and add the <Private> element to the reference, like this:

<Reference Include="[...]">
  <Private>False</Private>
  [...]
</Reference>

The value of the <Private> element matches the value of the "Copy local" property. For instance, if <Private> is set to False, then "Copy local" is also false..

Intestate answered 29/4, 2014 at 12:33 Comment(0)
A
5

Regarding the solution posted by @herzbube, if you want to turn off "Copy Local" for all (or most) of the references in your .csproj file, you don't need to set <Private>False</Private> individually on each Reference, you can just put the following directly in the .csproj:

<ItemDefinitionGroup>
  <Reference>
    <Private>False</Private>
  </Reference>
</ItemDefinitionGroup>

This doesn't affect projects referenced with <ProjectReference>, but you can do the same thing--either instead or as well--for those:

<ItemDefinitionGroup>
  <ProjectReference>
    <Private>False</Private>
  </ProjectReference>
</ItemDefinitionGroup>

If you want both of these, you can merge them into a single group:

<ItemDefinitionGroup>
  <Reference>
    <Private>False</Private>
  </Reference>
  <ProjectReference>
    <Private>False</Private>
  </ProjectReference>
</ItemDefinitionGroup>

Make sure you put these overrides prior to the first actual <Reference … > or <ProjectReference … > that you want to affect because these blocks will only apply to those references that appear below them. Then, if there are a few that you do actually want to be locally copied, you can just override those back individually (i.e., within the individual tag itself), this time using True.

For more advanced cases you can switch the overriding value back and forth between True and False multiple times in the same .csproj file. Another advanced technique would be to strategically place some of your references below these blocks, and others above, so the latter won't be affected.

All of this should make the XML in your .csproj much cleaner and easier to read. But there's even more good news, so read on...


As for selecting which projects should be be marked <Private>False</Private> this will usually depend on the specific situation, but there is something fundamental everyone can and should do for starters. It's a step so basic, simple and effective and it delivers such huge MSBuild reliability improvements1. and build-time speedup--and with little downside--that every large solution that uses the default (i.e. local per-project) C# output locations should almost always make this adjustment:

In any and every Visual Studio solution which builds multiple C# class libraries with any non-trivial number of <ProjectReference> inter-dependencies, and which culminates in building one more applications (i.e. executables):

  1. Near the top the .csproj for every class library, insert the <ProjectReference> block shown above.
    REASON: There is no need for any .dll to gather any of the libraries it references into a sub-directory of its own, since no executable is ever run from that location. Such rampant copying is useless busywork and may be unnecessarily slowing down your build, possibly quite dramatically.

  2. On the other hand, do not modify the .csproj for any of your solution's applications.
    REASON: Executables need to have all the privately-built libraries they need in their respective sub-directories, but the build for each app alone should be responsible for individually gathering each dependency, directly from its respective sub-directory, into the app's sub-directory.

This works perfectly because the .csproj for a class library may reference multiple other class libraries, but the .csproj for an executable usually never references another executable. Thus, for every locally-built library, the only .dll in its bin folder will be itself, whereas every locally-built application will contain the full set of locally-built libraries it references.

Conveniently, nothing changes for the referenced libraries that are not built by your solution, since these usually use <Reference> instead of <ProjectReference>, and we didn't modify the former tag at all. But do note the assumption just mentioned; if it is violated by some of your projects, you may need to make some adjustments.

[1.] Reliability improvements could be related to file collisions that may occur when gathering the same library from multiple disjoint paths in a dependency graph, especially in concurrent builds.

Austen answered 16/1, 2018 at 8:34 Comment(5)
Correct me if I'm wrong, but I don't think this is correct. By marking <ProjectReference> with the private attribute you're saying that you don't want to copy referenced project DLLs into the build folder. This doesn't include other package references you might be using in that class library. Moreover, if you end up including false attribute to <Reference> node, transitive dependencies that are not explicitly referenced in the application project will not be copied to the build folder.Cristinecristiona
@Cristinecristiona It's true that the transitive references must be maintained by the developer without IDE assistance--but only for the executable csprojs, and crucially not for the DLLs. I say crucially because (at least for me) the ratio of libraries per .exe is typically overwhelming, so it isn't an inconvenience when I consider the trade-off in build speed. This maintenance "burden" is furthermore trivial since you get a fully detailed message about missing libraries when you run the .exe, and only once, after changing a library reference. In my experience, it has been a total non-issue.Austen
What is the suggested way of maintaining those dependencies? Do you suggest explicitly adding transitive references to the .csproj file and letting build tools handle it or adding them by hand after the build process? I'd like to find the easiest way to do this in a CI/CD pipeline. My problem is that unused transitive dependencies are bloating my build to an unreasonable point.Cristinecristiona
@Cristinecristiona Dependencies are maintained in the .csproj as normal, mixing-in Visual Studio project references as desired, and all IDE edit/build/test/run functionality continues to work just fine. The only difference here are 1.) the use of <private> in the class lib .csproj files only, 2.) the infrequent (and insignificant) maintenance issue you pointed out above, and 3.) in exchange, not replicating 1,000 support .dll files, every time you build, into locations which are, by definition, never "executed-from," the latter fact thus making their presence there, IMHO, useless, or even harmful.Austen
@Cristinecristiona To summarize just in case I wasn't clear, your points could be valid for certain build scenarios, so my assumptions are: 1.) There are always few apps compared to lots of dlls generally across all solution s 2.) Most or all dlls referenced by solution apps are, in fact, explicitly referenced by those exes (making your issue moot), and 3.) a workflow where .csproj cross-referencing doesn't change all the time and/or the infrequent errors which are clearly exposed at runtime by the debugger can be trivially detected and addressed.Austen
O
4

Bumping this because it seems there's now a nuget package allowing exactly this...

https://nuget.org/packages/CopyLocalFalse

Haven't tried yet, just hoping it helps.

Oao answered 24/1, 2013 at 12:44 Comment(1)
With this nuget package there is no guarantee. It has some predefined filtering in the install script which will skip some of the references. Also it rus only at install time of the nuget package. The nuget package may be already installed and a new reference added meantime. Also what if the nuget package is restored before other packages are restored, which other packages may reset copy local of their referenced assemblies? Not recommended approach. Better use a target to override the copy local and have your own filtering.Proviso

© 2022 - 2024 — McMap. All rights reserved.