.NET Reference "Copy Local" True / False Being Set Based on Contents of GAC
Asked Answered
S

5

20

We had a very interesting problem with a Win Forms project. It's been resolved. We know what happened, but we want to understand why it happened. This may help other people out in the future who have a similar problem.

The WinForms project failed on 2 of our client's PCs. The error was an obscure kernel.dll error. The project ran fine on 3 other PCs.

We found that a .DLL (log4net.dll - a very popular open-source logging library) was missing from our release folder. It was previously in our release folder. Why was it missing in this latest release?

It was missing because I must have installed a program on my Dev box that used log4net.dll and it was added to the Global Assembly Cache.

When I checked the solution's references for log4net.dll, they were changed to "copy local=FALSE". They must have changed automatically because log4net.dll was present in my GAC.

Here's where my question starts:

Why did my reference for log4net.dll get changed from COPY LOCAL = TRUE to COPY LOCAL = FALSE? I suspect it's because it was added to my GAC by another program.

How can we prevent this from happening again? As it stands now, if I install a piece of software that uses a common library and it adds it to my GAC, then my SLNs that reference that DLL will change from Copy Local TRUE to FALSE.

Stefansson answered 20/3, 2010 at 0:54 Comment(1)
What is the General Assembly? en.wikipedia.org/wiki/Global_Assembly_Cache ?Elfreda
E
8

That happened because it doesn't make any sense to have Copy Local = True if an assembly is installed in the GAC. Because that local copy will never be used, the GAC is always searched first. Leaving it unchanged would cause major confusion, you'd think you are using the local copy but get another one instead. Changing it causes a confusion too, if you don't notice it, that could perhaps have been addressed with a message box at solution load time.

Log4net is a troublemaker, there are way too many versions in the wild without any deployment procedure that ensures that those versions don't bite each other. Something Apache apparently just didn't want to address, leaving it up to the programmer instead. Having products that take a dependency on Log4net and do something about the perceived DLL Hell risk is somewhat inevitable. Giving you a DLL Hell problem in return.

No clean simple answers, beyond being aware what's installed on your machine. Consider posting to connect.microsoft.com to ask for a warning when Visual Studio automatically updates the Copy Local property. It is a reasonable ask.

Embow answered 20/3, 2010 at 1:28 Comment(8)
From nobugz: "Consider posting to connect.microsoft.com to ask for a warning when Visual Studio automatically updates the Copy Local property." Thanks, bugz. This is a great idea. A notification when "copy local" changes would be great. This is VS2K5, btw. It reminds me of VS2K3's magical markup mangling. Thanks for the insight, too.Stefansson
True, and +1, although I wouldn't lay the blame entirely on Apache (or Neoworks, the original author) - I doubt they anticipated just how widespread the ad-hoc deployments would become. Most open source products aren't so arrogant as to try to weasel their way into your GAC, and if Apache changed the model now, it would break existing code (as has been demonstrated here).Sessler
Any way to prevent it from changing? I suspect that, "This is by design." If this is true, then I need to check refs periodically if my dev / build box is my main PC and I add / remove stuff frequently?Stefansson
It makes perfect sense to honor Copy Local = True ... the app will never run on the build server (except possibly in testing scenarios). Even if the version is identical, you're building for the purposes of deploying, not running locally, so copying to the output folder does not harm the build in any way.Burris
It makes even more sense to not put assemblies in the GAC on your build server.Embow
It makes even more sense for unrelated programs that happen to have been installed not to affect builds. It's not like these assemblies were intentionally added to the GAC.Topee
@HansPassant it's also makes sense to copy standard dotnet libraries in SQLCLR project, because using assemblies from blessed list is not enough for most of time.Kidnap
Even more fun... compile in Visual Studio 2015 and dependency is copied local. Compile the same solution in Visual Studio 2017 and the dependency is NOT copied local. Same proj files, same compile machine. Just lovely.Wingspread
P
3

I found a solution for us on our build servers.

Problem: In a specific date for no reason (I now assume the file was added to the GAC) the assembly System.Web.MVC.dll disappeared from many of our projects. I am sure this applies to any dll with the issue.

Solution: In visual studio change the reference (assuming Copy Local is already True).

  1. Change Copy Local = True to Copy Local = False
  2. Change Copy Local Back to True
  3. Notice in the .csproj file a <Private>True</Private> is added.
  4. Compile now and you will see the dll does indeed get included even though the message remains the same.

Solution2: Add the <private>True</private> property manually.

Conclusions: My understanding of <Private> is it is the Copy Local so if Copy Local = True the Private property should be added, but in our case at least it was not.

Additional Considerations:

  1. Tested using Visual Studio 2013 and 2015.
  2. Tested using msbuild 12 and 14.
  3. Tested using TFS build Template.
  4. Our code has been upgraded from 2008 to 2015 visual studio, the flag may have gotten messed up somewhere in that time, but the issue did not start until the assembly was added to the GAC somehow.

End Result:

<Reference Include="System.Web.Mvc, Version=4.0.0.1, Culture=neutral,PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
 <HintPath>packages\Microsoft.AspNet.Mvc.4.0.40804.0\lib\net40\System.Web.Mvc.dll        </HintPath>
        <Private>True</Private>
    </Reference>
Plunk answered 29/1, 2016 at 20:37 Comment(1)
This was the cleanest solution. Having "<private>True</private>" clearly specified in the .csproj file means that the behaviour should be consistent and easy to monitor. I also added "<SpecificVersion>False</SpecificVersion>" to ensure that what is on the build server wins.Affidavit
L
2

At run time, assemblies must be in one of two locations: the output path of the project or the global assembly cache (see Working with Assemblies and the Global Assembly Cache). If the project contains a reference to an object that is not in one of these locations, then when the project is built, the reference must be copied to the output path of the project. The CopyLocal property indicates whether this copy needs to be made. If the value is true, the reference is copied. If false, the reference is not copied.

The project-assigned value of CopyLocal is determined in the following order:

  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. Otherwise, the value is true.

Regards... Muse VSExtensions

Lockout answered 29/4, 2010 at 18:13 Comment(0)
M
1

I just encountered this problem on our build servers too. This is very annoying (and unexpected) behaviour by Visual Studio. Two workarounds:

  1. Remove the assemblies from the GAC.
  2. Add a PostBuild event to manually copy the assemblies into the output directory.
Mysterious answered 16/6, 2010 at 14:54 Comment(0)
E
0

Can you ensure yours is used by adding a hintpath to the msbuild/proj file?

From the above link:

HintPath : Relative or absolute path of the assembly.

Elfreda answered 20/3, 2010 at 1:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.