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)
No - Visual Studio uses an internal set of rules to determine what to set Copy Local to.
From MSDN:
If the reference is another project, called a project-to-project reference, then the value is true.
If the assembly is found in the global assembly cache, the value is false.
As a special case, the value for the mscorlib.dll reference is false.
If the assembly is found in the Framework SDK folder, then the value is false.
Otherwise, the value is true.
Actually, you can. You need a couple things:
Create .targets file that makes copylocal (<Private> tag, to be precise) false by default.
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.
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
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..
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.
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):
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.
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.
Related
We want to use the shared bin directory for local development builds similarly to how it is used on the CI server. This way a fully built branch of our application occupies less than one third of the space. Which means more branches can fit on our small SSDs.
So, we did that and it all works fine, except VS 2017 IDE no longer uses the fast up-to-date check heuristics.
So, before the change when we built the code the second time the build output reported something like this:
========== Build: 0 succeeded, 0 failed, 94 up-to-date, 0 skipped ==========
Meaning IDE employed the heuristic, decided that all the projects are up-to-date (very fast) and skipped msbuild entirely.
After the change, this does not happen, IDE actually hands off all the projects to msbuild, which takes about a minute to burn through all the projects. It does not actually compile any code, but the whole process takes longer now.
It is as if we have set the DisableFastUpToDateCheck msbuild property, except that we have not.
So, how do we troubleshoot it? I want the heuristic back.
EDIT 1
I owe a few words on how we did it. Every project imports a special targets file just before importing the standard targets (like Microsoft.CSharp.targets). This was developed in the pre MSBuild 15 days, so we do not use Directory.Build.targets. In this file we have OutDir explicitly set to some shared bin directory within the workspace. So, all the projects specify OutputPath as usual, but it is overridden with the OutDir in this special targets file. I think this is the root cause for losing the heuristic - it only looks at the csproj, so it sees OutputPath, but not OutDir. And since no binaries are found at OutputPath the heuristic is not satisfied.
I will test this hypethesis and if it turns out to be true will repost this comment as my answer.
Just to add some details about I want the heuristic back.
1.Hmm, just did some tests and confirmed that your Edit1 is correct.
We can simply reproduce the same issue in local machine without using shared folder. If we use $(OutDir) in imported targets, since VS will always look for $(OutputPath), it affects the normal build behavior. (Agree with your Edit1, it should be a good answer!)
2.To resolve the issue, we must avoid using $(OutDir) to specify the final output path. So can we use $(OutputPath), sadly No!
The $(OutputPath) in project file will always override the same-name $(OutputPath) defined in imported xx.targets file. See this: If the project contains a property definition that has the same name as an environment property, the property in the project overrides the value of the environment variable..
The only possible way I can imagine is that we define $(CustomPath)(represents the shared folder) in xx.targets file, and modify the all OutputPath properties in xx.csproj to <OutputPath>$(CustomPath)\$(Configuration)</OutputPath>. That could be a boring job cause you have almost 100 project files :(
I guess the boring job is hard to avoid unless we give up the shared-folder way. This answer is a bit negative but hope it makes some help...
This is one of the rare cases when binary log is useless, because it is produced by msbuild, i.e. too late. One has to turn the diagnostics build in Visual Studio, because the first line of it would be from the Heuristic. It specifies the reason why the project is built. All the rest of the diagnostics log can be discarded, but the first line is priceless. It has guided me to the following solution:
Enabling the Heuristic
I added the following targets to the targets file we import from every project. Those who do not care about backwards compatibility with MSBuild 14 can add it to Directory.Build.Targets:
* WE PICKED ANOTHER WAY AT THE END - SEE EDIT 2 *
<Target Name="SatisfyVisualStudioFastUpToDateCheckHeuristic" AfterTargets="AfterBuild" Condition="'$(OutDir)' != '' And '$(GatedCheckIn)' != True">
<ItemGroup>
<Files Include="$(TargetFileName).config" Condition="'$(AppConfig)' != '' And Exists('$(AppConfig)')" />
<Files Include="$(TargetName).pdb" />
<Files Include="$(TargetName)$(TargetExt)" />
<Files Include="#(IntermediateAssembly->'%(Filename)%(Extension)')" />
<Files Include="$(_SGenDllName)" Condition="'$(_SGenDllCreated)'=='true'" />
<Files Include="#(ReferenceCopyLocalPaths->'%(DestinationSubDirectory)%(Filename)%(Extension)')" />
<Files Include="#(_SourceItemsToCopyToOutputDirectory->'%(TargetPath)')" />
</ItemGroup>
<MakeDir Directories="$(OutputPath)\%(Files.RelativeDir)" />
<Touch Files="#(Files->'$(OutputPath)%(Identity)')" AlwaysCreate="true" ContinueOnError="true"/>
</Target>
<Target Name="CleanFakeOutputFiles" AfterTargets="AfterClean" Condition="'$(OutDir)' != '' And '$(GatedCheckIn)' != True">
<RemoveDir Directories="$(OutputPath)" />
</Target>
This target creates zero length fake surrogate output files in the location expected by the heuristic.
Subsequent action 1
Doing so may actually fail the build, if one has projects referencing other projects as DLLs rather than as project references. For instance, if the project Alice references the project Bob like this:
<Reference Include="Bob, Version=1.0.0.0, Culture=neutral, PublicKeyToken=..., processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\Bob\bin\$(Configuration)\Bob.dll</HintPath>
</Reference>
The problem is with the HintPath. When we build with a shared bin and without the fake surrogate outputs, the HintPath is ignored, because it does not lead to any existing file. So, Bob.dll would be found in the shared bin directory and loaded from there. But, when there are fakes in bin\$(Configuration) to satisfy the heuristic, the HintPath returns an existing file, so the search for Bob.dll ends with the zero length fake. Obviously, compilation would fail. The fix is to either change it to the respective project reference or eliminate the HintPath with all the metadata:
<Reference Include="Bob" />
One has to reference Bob.dll just like we reference system dependencies from the GAC.
Subsequent action 2
At this point the code should build and the Heuristic works. Except sometimes it would tell that it cannot find some dependency, like System.IO.dll. In this case one has to check if the following two conditions hold:
The project references System.IO.dll from some directory, like the NuGet packages.
System.IO.dll is found in the GAC and is a better match for the target framework.
In my case I had several projects referencing System.IO*, System.Runtime*, System.Security* and System.ValueTuple from NuGet whereas these dlls exist in the GAC by virtue of having .NET 4.7.2 runtime on the system. The Heuristic looks at the HintPath for these references and expects to find them in the bin\$(Configuration) folder.
However, the build actually takes them from GAC and the dlls referenced from GAC are not copied by default to the target bin. Therefore there are no fake surrogates to satisfy the Heuristic.
My solution was to replace all these NuGet references with the respective references from the GAC.
EDIT 1
Issues 1
The side effect of the subsequent action 1 is that it is no longer easy to go back to local bin folders, because the change to the project dll references is not backwards compatible.
It is possible to keep the HintPath metadata, but all of them must point to the shared bin directory, in which case it is still incompatible with the old way.
Issue 2
Visual Studio overwrites .vs\config\applicationhost.config every time a web application project is loaded. Which means:
When solution is open
When the project is unloaded, then reloaded
This is a major bummer, because part of the shared bin approach is to update the paths in .vs\config\applicationhost.config through scripting. But this behaviour of VS makes it a major nuisance.
EDIT 2
We have changed the way we enable the heuristic. Instead of generating bogus files where VS expects we do the following:
Early in the build generate a junction bin.link --> bin
Set OutDir = bin.link in the targets file hidden from the Heuristic
Modified all the projects with OutputPath = bin
This way the Heuristic sees the right location (OutputPath), but at the same time OutputPath != OutDir and so the publishing code works correctly.
So, no more zero length garbage files.
As for the VS overwriting the .vs\config\applicationhost.config - we created a custom VS extension for that.
We have an internal NuGet Package that consists of some .NET Code and a TypeScript Definition File (*.d.ts). This is the content of the package:
After installing the package into a new .NET Core project, the folder structure in the solution explorer looks like this.
So far, everything went as expected. But note the small arrow symbols on the "i18n" folder and the "Index.d.ts" file. It looks like they're just links to the actual file. When I click on the d.ts file the content seems correct. But Visual Studio fails to recognize the declarations in it, so I can't use it in my TypeScripts.
One idea was to include the path to the packages in the tsconfig.json, but that can't be the solution... Any other ideas how to do that?
How to add TypeScript Definitions to .NET Core Nuget Packages
As far as I know, Definitely Typed packages are not compatible with .NET Core projects. That because the script files should be included in <contentFiles> element. You can refer to the Including content files for more detail info.
Besides, just as Martin comment, npm is the recommended method of installing Definitely Typed packages:
https://github.com/DefinitelyTyped/DefinitelyTyped#how-do-i-get-them
So, after seeing the replies here and not giving up, I have to put in my approach to this.
As I'm working on a large solution with over 100 subprojects - many of them fast moving NuGets, I couldn't give up. I wanted to have my .NET object models including their interface/class representations in TS, being able to have both imported by including one NuGet (and thereby reduce dependency hell a little bit). I have to mention, I tested this only with my own object model, which has no external dependencies - and I tested only on VS2022.
But in this restricted scenario it works without any issues.
On the project containing the TS definitions
Set the build action for the ts definitions you need to be included in the NuGet to "content". This will include them into the NuGet package.
On the consumer side
Adjust your package reference, add the following property/value:
<GeneratePathProperty>True</GeneratePathProperty>
This will create an MsBuild property/variable referencing the path to the local presence of the restored NuGet file (important if your building on multiple, different machines - like on CI pipelines, build servers etc.) and allowing you to avoid any hardcoded absolute paths.
The generated property has the following format
$(Pkg<PackageNameWithDotsBeingReplacedByUnderlines>)
So a package named "MyPackage.Hello" would result in the variable $(PkgMyPackage_Hello)
Now we create a new build target to copy the files from the restored package's contentfiles folder (as it's restored, and we have the restored and thereby extracted path, we can finally target them).
<Target Name="CopyImportedTypes" BeforeTargets="Build">
<ItemGroup>
<TsTypesToCopy Include="$(PkgMyPackage_Hello)\contentFiles\any\net6.0-windows10.0.20348\*.ts" />
</ItemGroup>
<Copy SourceFiles="#(TsTypesToCopy)" DestinationFolder="$(MSBuildProjectDirectory)\AnyProjectSubFolderIfDesired" SkipUnchangedFiles="true" OverwriteReadOnlyFiles="true" />
</Target>
Make sure to adjust the "Include" path to your package (TFM, Platform etc.). An easy way to get the relative path is to open up the solution explorer, expand your consuming project, expand dependencies and then packages, expand the package with your ts definitions and open up the properties of the contentfiles.
This target is being executed before the actual build (so we can use the imported types on the build being happening right this moment) (BeforeTargets property). The ItemGroup is nothing else than a definition of items (in our case, source files) we want to use, being stored into #(TsTypesToCopy) which is being used by the copy task.
Thankfully, VS does automatically set new files to the right build action (in most cases), so the brand new ts files should be in the right mode automatically - so we don't have to tweak anything manually.
We have a VS2008 solution file that contains around 80 projects (yes i know it sucks).
The projects are arranged in various folders, and some may depend on other 3rd party DLLs in a top level "Libs" folder.
We'd like to refactor this into several smaller .sln files, each containing a reasonable number of projects.
The problem is, when moving the project files around, the relative paths stored within them will break and so we will have to do lots of manual "patch ups" to fix that.
Is there any tool or any proven technique for doing something like this ?
We did something similar, and to fix the references, we wrote a quick utility that would parse the .csproj or .vbproj files (which are basically xml files) and fix the affected paths based on where the .proj file itself was located after the refactoring. this was better than manually changing the project xml or adding removing references to avoid human errors.
Once you know where the proj file is and where common files (or other files) will be, you modify the Reference node in the project file with the relative path. So for e.g., you might have to change the original
<Reference Include="NHibernate">
<HintPath>..\..\ServicesShared\Library\NHibernate.dll</HintPath>
</Reference>
in the .proj file to
<Reference Include="NHibernate">
<HintPath>..\Common\ServicesShared\Library\NHibernate.dll</HintPath>
</Reference>
if that's where the NHibernate.dll lives now.
Hopefully this will work for you guys.
So, being completely obsessive compulsive, I was digging around in the .csproj file for one of my assemblies and was looking at the schema for the XML. I noticed in the <ItemGroup>, the various .dll files are referenced using an element called <Reference Include="..." />.
Out of curiosity, I did some digging and found that I can change things up a bit by modifying it with an included <Name> element. Like so ...
<Reference Include="Microsoft.CSharp">
<Name>System.Dynamics</Name>
</Reference>
I of course expected this to crash everything, but behold, when I reloaded the project and compiled, everything ran just fine.
Is this just there for aesthetics? Or am I doing damage by changing names around? Are there any long term effects of this? I did not experience any build, runtime, or editor issues from doing this.
I think it's just the display name of the reference.
Edit: The MSBuild schema defines the Reference\Name element as "Friendly display name (optional)."
The schema for MSBuild is located here: C:\Windows\Microsoft.NET\Framework\v4.0.30319\Microsoft.Build.xsd
Also, check out the MSBuild Reference.
I have some content files that I would like to share between a number of projects in Visual Studio.
I have put these files in their own project, set the build action to "Content", and the copy to output directory to "Copy if newer". I would like all these files to be copied to the bin/debug directory of the projects that reference them.
I can get it to work by including a reference to the "contents" project in each of the projects that need the files, but that requires that a minimal assembly be generated (3K). I assume there is a way, using MSBuild, to make this all work without creating the empty assembly?
Thanks to everone who took the time to make a suggestion about how to solve this problem.
It turns out that if I want my compiled content files to be treated like content files (in that they get copied to the output directory of any other project that references my project), I need to create a target which runs before GetCopyToOutputDirectoryItems, and add the full path of the compiled content files to the AllItemsFullPathWithTargetPath ItemGroup. MSBuild calls GetCopyToOutputDirectoryItems for projects on which the current project depends, and uses the resulting file list to determine the files that are copied along with the assembly.dll. Here is the XML from my .csproj, just in case someone else has a similar problem.
I have a custom task called "ZipDictionary", and I accumulate all the files that I am going to compile in an ItemGroup called DictionaryCompile. My target, "FixGetCopyToOutputDirectoryItems" is executed before "GetCopyToOutputDirectoryItems". I don't do the actual compilation there, since this target can be called multiple times by referencing projects, and it would hurt performance. The target does some transforms to get the post-compilation file names, and then returns the full paths to all the files, since relative paths will not work when copy is called from the referencing project.
<ItemGroup>
<DictionaryCompile Include="Dictionaries\it-IT.dic">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</DictionaryCompile>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<UsingTask TaskName="ZipDictionary" AssemblyFile="..\LogicTree.DictionaryCompiler\bin\Debug\LogicTree.DictionaryCompiler.dll"/>
<Target Name="BeforeCompile">
<Message Text="Files #(DictionaryCompile)" Importance="high" />
<ZipDictionary DictionaryFiles="#(DictionaryCompile)" OutputDirectory="$(OutputPath)">
<Output TaskParameter="OutputFiles" ItemName="DictionaryOutputFiles" />
</ZipDictionary>
</Target>
<Target Name="FixGetCopyToOutputDirectoryItems" BeforeTargets="GetCopyToOutputDirectoryItems">
<ItemGroup>
<_DictionaryCompile Include="#(DictionaryCompile->'$(OutputPath)Dictionaries\%(FileName).ltdic')" />
</ItemGroup>
<AssignTargetPath Files="#(_DictionaryCompile)" RootFolder="$(MSBuildProjectDirectory)\$(OutputPath)">
<Output TaskParameter="AssignedFiles" ItemName="_DictionaryCompileWithTargetPath" />
</AssignTargetPath>
<ItemGroup>
<AllItemsFullPathWithTargetPath Include="#(_DictionaryCompileWithTargetPath->'%(FullPath)')" Condition="'%(_DictionaryCompileWithTargetPath.CopyToOutputDirectory)'=='Always' or '%(_DictionaryCompileWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'" />
<_SourceItemsToCopyToOutputDirectoryAlways Include="#(_DictionaryCompileWithTargetPath->'%(FullPath)')" Condition="'%(_DictionaryCompileWithTargetPath.CopyToOutputDirectory)'=='Always'" />
<_SourceItemsToCopyToOutputDirectory Include="#(_DictionaryCompileWithTargetPath->'%(FullPath)')" Condition="'%(_DictionaryCompileWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'" />
</ItemGroup>
</Target>
A better possible solution would be to
place a common directory in the solution dir and place your common content files there.
in VS, in each project that should share this content, right-click add existing item, browse to the desired item(s), select, click the down-arrow on the add button and select add as link. In the project, you will notice the files are added with a 'shortcut' overlay.
In the project, select the newly added links and right-click->properties and select Build Action: content, Copy To Output Directory: Copy Always.
This is a simple solution to the problem given.
I use this technique for things like SQL scripts and partial config files (using configSource) with great success. This allows me to make changes to these files in a single location with the assurance that they will be propigated throughout the solution.
A more robust solution would be to create a project with embedded resources. This requires a bit more work to manage the content on the receiving end but may be worth it in the long run as having a bunch of loose artifacts flying about can become problematic.
Hope that helps.
A similar solution like the one Sky suggested can be found in my answer to "Is there a way to automatically include content files into asp.net project file?".
It allows to share your content but you must not touch the folder or its content inside VS because this breaks the recursive path.
This approach works best for auto-generated content - you don't have to bother about including new content files to your solution.
And of course you can reuse this in multiple solutions/projects.
We do something similar where we have "...ReleaseBuilds" that reference dlls and content we require for specific projects. Compiling copies everything to the bin debug folder and indeed creates the empty assembly.
Within Visual Studio we have a post-build event in the "...RealeaseBuild" (in project properties) that copies/deletes or run batch files to make sure we have all the files (configs, services etc etc) required and to delete the empty assembly.
HTH