TransformXml Web.config while publishing - c#

I'm trying to use the TransformXml task to get all the config transformed at one shot irrespective of the build configuration that is selected in visual studio. I'm able to accomplish it by editing the .csproj file and here is it.
<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll"/>
<Target Name="TransformWebConfig" AfterTargets="AfterPublish">
<TransformXml Source="$(SolutionDir)WCFServices\Web.config"
Transform="$(SolutionDir)WCFServices\Web.Release.config"
Destination="$(OutDir)WebRelease.config"
StackTrace="true" />
</Target>
The problem here is while I do a publish, the transformed config files gets placed in the output dir but this happens before the delete operation. I would need the configs to be transformed after the delete operation.
Please advise?

Beware MSBuild and VSBuild DO differ. Afterpublish is VERY limited
Try having msbuild save the emitted sln Also here.
Have you verified the file is getting transformed at the expected time? Add in a <message importance="high" text="Doing Custom Transform" /> before your transformxml call, then another message with different text Finished Custom Transform after this call.
This post speaks directly to your desire to do things involving the specialized publishing targets.
General msbuild debugging information:
Hopefully this behavior IS msbuild dependant instead of VSBuild, since the two build processes differ. If you are in luck and this is following the same behavior as MSBuild would follow the msbuild debugging process:
As with any magical behavior in msbuild, in your CSProj file look for any import target= code and go find those files. Following these through will lead you to the part that is making the decision about any of this.
Also you can attach VS's debugger to your build process - http://blogs.msdn.com/b/visualstudio/archive/2010/07/06/debugging-msbuild-script-with-visual-studio.aspx + http://blogs.msdn.com/b/visualstudio/archive/2010/07/09/debugging-msbuild-script-with-visual-studio-2.aspx
Determining build order
Also in crank up the build verbosity temporarily http://blogs.msdn.com/b/msbuild/archive/2005/09/29/475157.aspx

Related

Web Application Build Error: The CodeDom provider type Microsoft.VisualC.CppCodeProvider could not be located

I'm building/packing a web application in a build server, and it fails with the following message:
ASPNETCOMPILER error ASPCONFIG: The CodeDom provider type
"Microsoft.VisualC.CppCodeProvider, CppCodeProvider, Version=10.0.0.0,
Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" could not be
located.
This is the build server environment:
Windows Server 2008 R2 Standard
TeamCity 8.0.4
.NET 4.5
Windows SDK for Windows 7 and .NET 4
Windows SDK for Windows 8 and .NET 4.5
Portable Class Library Tools
ASP MVC 4
It is a ASP MVC 4 web application, targeting .NET 4.5.
The build configuration consists in building the solution with MSBuild, and deploying it to a package, so I can publish it later.
Through the log of TeamCity, I can see the error arising when MSBuild runs aspnet_compiler.exe.
The builds with no problem in my DEV machine and can also publish it to a local IIS without problems.
Does anyone know what may be causing this issue?
UPDATE
See my answer below.
For me this error was popping up in VS2017 when building the web project. The fix was to make the node_modules directory hidden in File Explorer. Apparently this stops the ASP.NET compiler from scanning all these files and thus prevents the error.
This post gave me an important clue: apparently ASP.NET precompilation scans the project and output files and tries to compile every source file it finds in its way, despite its language (see here).
In the case, my web app depends on a project which includes some unmanaged dll along a ".h" file. These files are copied to the output directory ("Copy if newer") so I can pinvoke it at runtime.
It seems ASP.NET precompilation finds the ".h" and tries to compile it, even though there is no need of it. And, as I see it, it fails because my build server does not has the tools for the job (it looks like CppCodeProvider comes with the .NET 2.0 SDK).
When I changed the project not to copy those files to the output directory, the build ran fine. I also tested copying the files, but with "PrecompileBeforePublish" set to false in the publish profile, and it also worked.
Now I have some options, but I don't like any of them:
Disable "PrecompileBeforePublish". I believe the main disadvantage of that is the app users experience will be slower on the first site access.
Try to exclude files from the output folder and add them again after pre-compilation. That seems a lot of work for something I shouldn't be worrying in first place.
Try to tell "aspnet_compiler.exe" to exclude the offending files/folder when executing. I don't know how to do it using the publish profile, because I only have control over "PrecompileBeforePublish". Also, it seems "aspnet_compiler.exe" does not offer that option (here and here).
I think for now I'll disable "PrecompileBeforePublish", because it seems a fast path with few caveats. But I believe there should be a better way to handle it, excluding folders or file types from precompilation using the publish profile.
For the benefit of those who find this later on google...
Root Cause
As the error implies, the assembly "Microsoft.VisualC.CppCodeProvider" couldn't be found.
This was added to the Global Assembly Cache (GAC) as part of Visual Studio 2015 installation, but not Visual Studio 2017.
The Fix
The proper fix is to add the missing reference to the GAC.
Run the "Developer Command Prompt" as admin, and run the following
gacutil /i "path to CppCodeProvider.dll"
or gacutil /i "C:\Program Files (x86)\Microsoft Visual Studio\2
017\Professional\Common7\IDE\PublicAssemblies\CppCodeProvider.dll"
e.g.
C:\Windows\System32>gacutil /i "C:\Program Files (x86)\Microsoft Visual Studio\2
017\Professional\Common7\IDE\PublicAssemblies\CppCodeProvider.dll"
Microsoft (R) .NET Global Assembly Cache Utility. Version 4.0.30319.0
Copyright (c) Microsoft Corporation. All rights reserved.
Assembly successfully added to the cache
C:\Windows\System32>
On next build the following error is no longer thrown.
ASPNETCOMPILER error ASPCONFIG: The CodeDom provider type "Microsoft.VisualC.CppCodeProvider, CppCodeProvider, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" could not be located.
This started happening when I updating to VS2017. The problem for me was node.js, if I deleted the node_modules folder then the project would build without errors. It turns out that changing the value of MvcBuildViews to false in the csproj file as suggested by anders here fixes it. This isn't ideal though since mvc views won't be compiled until IIS renders them. Personally, I just hide the node_modules folder to get around the issue but I wanted to add this answer in case it helps shed some light on the underlying issue for someone else.
<MvcBuildViews>false</MvcBuildViews>
In my case I had added an angular website to my solution which caused this error.
Resolved the error with following steps.
On the menu bar, choose Build > Configuration Manager.
In the Project contexts table, exclude the angular website (which contained node_modules)
In the Build column for the project, clear the check box.
Choose the Close button, and then rebuild the solution.
In my scenario, I have to ship a Perl interpreter with my ASP.Net website (don't ask why I need Perl, and I'm sorry I do in advance!), and that included .c files that caused the aspnet_compiler.exe to error out, as others have mentioned being their problem. The perl directory is in my bin folder, and is required at runtime.
The trouble I found was when you attrib +H the folder, it indeed was skipped by aspnet_compiler, but then wouldn't be in my publish output folder. So I had to hack it even more by hiding the folder, compile views, unhide folder, and then copy folder to the right location. This involved modifying the original AspNetPreCompile task. See below:
<!-- Overwrite AspNetPreCompile task because it was trying to compile .c files found in the Perl directory. This prevents that but still copies Perl to publish file. -->
<!-- Taken from: C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\Microsoft\VisualStudio\v15.0\Web\Transform -->
<Target Name="AspNetPreCompile" DependsOnTargets="$(AspNetPreCompileDependsOn)" Condition="'$(AspNetPreCompile)' != 'false'">
<PropertyGroup Condition="'$(UseMetabasePath)' == 'true'" >
<_PreAspnetCompileMergeSingleTargetFolderFullPath></_PreAspnetCompileMergeSingleTargetFolderFullPath>
<_AspNetCompilerVirtualPath></_AspNetCompilerVirtualPath>
</PropertyGroup>
<PropertyGroup Condition="'$(UseMetabasePath)' != 'true'" >
<_PreAspnetCompileMergeSingleTargetFolderFullPath>$([System.IO.Path]::GetFullPath($(_PreAspnetCompileMergeSingleTargetFolder)))</_PreAspnetCompileMergeSingleTargetFolderFullPath>
</PropertyGroup>
<PropertyGroup>
<_PostAspnetCompileMergeSingleTargetFolderFullPath>$([System.IO.Path]::GetFullPath($(_PostAspnetCompileMergeSingleTargetFolder)))</_PostAspnetCompileMergeSingleTargetFolderFullPath>
</PropertyGroup>
<!-- Modification #1. -->
<Exec Command="attrib +H "$(IntermediateOutputPath)AspnetCompileMerge\Source\bin\perl"" />
<AspNetCompiler
PhysicalPath="$(_PreAspnetCompileMergeSingleTargetFolderFullPath)"
TargetPath="$(_PostAspnetCompileMergeSingleTargetFolderFullPath)"
VirtualPath="$(_AspNetCompilerVirtualPath)"
Force="$(_AspNetCompilerForce)"
Debug="$(DebugSymbols)"
Updateable="$(EnableUpdateable)"
KeyFile="$(_AspNetCompileMergeKeyFile)"
KeyContainer="$(_AspNetCompileMergeKeyContainer)"
DelaySign="$(DelaySign)"
AllowPartiallyTrustedCallers="$(AllowPartiallyTrustedCallers)"
FixedNames="$(_AspNetCompilerFixedNames)"
Clean="$(Clean)"
MetabasePath="$(_AspNetCompilerMetabasePath)"
ToolPath="$(AspnetCompilerPath)"
/>
<!-- Modification #2. -->
<Exec Command="attrib -H "$(IntermediateOutputPath)AspnetCompileMerge\Source\bin\perl"" />
<!--
Removing APP_DATA is done here so that the output groups reflect the fact that App_data is
not present
-->
<RemoveDir Condition="'$(DeleteAppDataFolder)' == 'true' And Exists('$(_PostAspnetCompileMergeSingleTargetFolderFullPath)\App_Data')"
Directories="$(_PostAspnetCompileMergeSingleTargetFolderFullPath)\App_Data" />
<CollectFilesinFolder Condition="'$(UseMerge)' != 'true'"
RootPath="$(_PostAspnetCompileMergeSingleTargetFolderFullPath)" >
<Output TaskParameter="Result" ItemName="_AspnetCompileMergePrecompiledOutputNoMetadata" />
</CollectFilesinFolder>
<ItemGroup Condition="'$(UseMerge)' != 'true'">
<FileWrites Include="$(_PostAspnetCompileMergeSingleTargetFolderFullPath)\**"/>
</ItemGroup>
<!-- Modification #3. -->
<ItemGroup>
<Perl Include="$(IntermediateOutputPath)AspnetCompileMerge\Source\bin\perl\**\*.*" />
</ItemGroup>
<!-- Modification #4. -->
<Copy SourceFiles="#(Perl)" DestinationFolder="$(_PostAspnetCompileMergeSingleTargetFolderFullPath)\bin\perl\%(RecursiveDir)"></Copy>
</Target>
DO NOT modify the original .targets file, copy this into your .csproj file as a child to the <project> node.
Key takeaways:
Use Exec command to attrib +H Directory before running aspnet_compiler.exe via the AspNetCompiler task, and attrib -H Directory afterwards.
Create an ItemGroup to suck in all the files that still need to be copied.
Run the Copy task, utilizing that ItemGroup to put the files where they need to be in order for the rest of the publish task to include them. We get to use all of the variables that Microsoft made when authoring this Task, so we can use those here too.
Pro to modifying the original task: very little changes about the normal behavior, so it should still just work.
Possible con to modifying the original task: Microsoft might change this task in the future, making our copy out of date.
If you don't have my weird requirements, the simpler solution to hiding a folder is as follows:
<Target Name="Test" BeforeTargets="AspNetPreCompile">
<Exec Command="attrib +H Directory" />
</Target>
<Target Name="Test" AfterTargets="AspNetPreCompile">
<Exec Command="attrib -H Directory" />
</Target>
Answer inspired by the comment twamley made in Arthur Nunes answer.
In my case it was the node_modules folder. I made this change in my csproj file for my .net 4.8 app to fix it.
This will just add the hidden attribute to the node_modules folder and then unhide it after the Razor pages are compiled.
<Target Name="MvcBuildViews" AfterTargets="AfterBuild" Condition="'$(MvcBuildViews)'=='true'">
<Exec Command="attrib +H "$(ProjectDir)node_modules"" />
<AspNetCompiler VirtualPath="temp" PhysicalPath="$(WebProjectOutputDir)" />
<Exec Command="attrib -H "$(ProjectDir)node_modules"" />
</Target>
Try doing the folowing.
Setting RequireTargetFramework to 4.0.
Link:ASPNETCOMPILER error ASPCONFIG: Could not load file or assembly 'Microsoft.VisualBasic.Activities.Compiler' or one of its dependencies
In my case the issue was that the web config of a parent solution (root level project) in IIS had this in it's web config (by mistake, not sure how it got there). Took a long time to track down, because nothing I could do in my solution/project could affect it in any way.
So might be worth checking the web.config of all that might be involved.
For me this error was showing when my website's physical path was invalid in IIS. To resolve that right click on website (Manage website -> Advanced settings -> Physical Path).
In my case, on a new machine, installed VS2017 and opened an asp.net core 1.1 web application from source control. The error showed up. I installed node.js and the project compiled.
My solution to this error was a combination of two pre-existing answers on this page. I had a .h file in my web project directory that had not caused a problem until I tried to build the project on a VS 2017 machine.
In my case I simply zipped it up, but the upshot seems to be that you can no longer keep unrelated code files in the web directory or VS will trip up trying to compile them.
I solved it with deleting node modules folder then running npm i from git bash and not from VS2019 built in terminal.
Copy cppprovider.dll from Visual Studio 2015 installation path to:
C:\Program Files (x86)\Microsoft Visual Studio
11.0\Common7\IDE\PublicAssemblies
An easy way to solve is that to reference the CppCodeProvider.dll.
It may locate at
C:\Program Files (x86)\Microsoft Visual Studio{version}\{edition}\Common7\IDE\PublicAssemblies
For example:
C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\IDE\PublicAssemblies\CppCodeProvider.dll
It will be in the bin folder.
I moved my solution from VS2019 to VS2022 and was having this error when I tried to publish solution. This is how I made the error disappear.
Right-click on References>> Add References
Then Search for Microsoft.VisualC
tick Microsoft.VisualC and Microsoft.VisualC.VSCodeProvider
click ok.
Error gone!
I installed VS2019 on a new laptop but kept getting the same error message as the OP. (it still worked fine on my desktop PC).
After a day or so of trying every answer on here and Google, and getting no joy, I tried using, from the toolbar, Build -> Publish Web App, which built my website into the Publish folder ok.
I then took this 'Publish' folder and copied it to a new place on my C:drive.
Then after closings and re-opening VS2019, started with "continue without code".
Then File -> Open -> Web Site... select my 'Publish' folder, and hooray I can now build and debug my project locally.
The issue was occurring for me when I was building a web project with node_modules. I fixed the error by enabling Desktop development with C++ option in my Visual Studio installer.
Source: https://developercommunity.visualstudio.com/t/cppcodeprovider-not-properly-installed-with-vs2017/240322#T-N333161
In Visual Studio 2017 CppCodeProvider.dll is getting shipped with “Desktop development with C++” as a result installing “Desktop development with C++” should resolve the issue.

How to automatically delete Test Results

I run tests several times a day in Visual Studio 2012. I recently found that my disk space was very low. I found that the test results folder in my project was using 60 GB. I deleted the files, but I want to keep it from happening. I did a search for how to do this, but all I can find are solutions for 2008 and 2010. They stated I need to make some changes to the test tools in the options. I can't find this inside of my options. How can I keep from these files appearing, or keep them to a minimal?
Mark Seemann suggests extending the Clean target
Add this after the Import element at the end of the project file:
<PropertyGroup>
<TestResultsFolderPath>..\TestResults</TestResultsFolderPath>
</PropertyGroup>
<Target Name="AfterClean">
<RemoveDir Directories="$(TestResultsFolderPath)" Condition="Exists('$(TestResultsFolderPath)')" />
</Target>
Then whenever you want to manually remove the test results, you can just right-click in the Solution explorer and select Clean.
You can also achieve the same from the command line with the following
MSBuild /t:Clean MyProject.csproj
which can be scheduled if you want an automatic deletion once a week or whatever. As Mark points out, one nice feature of this approach is that you can control the deletion on a project by project basis.

Modify C# Code BEFORE Compile in VS 2012

i've got a similiar Problem like the question here: Create MSBuild custom task to modify C# code *before* compile
I need to change .cs files before compilation. Of course i don't want them to get changed in place, because of the version control. I already figured out, that a Custom MSBuild Task might be the right choice to do the job.
Quote of the answer from the above question:
Create custom task that accepts the list of cs files to adapt prior to compilation
The custom task adapts the list of files received and creates them on disk
The custom task sets the list of changed files on the output parameter
The output of the task would replace the original cs files list
The compilation is done against the changed files.
The solution seems clear and fine, but what i can't find out (all for Visual Studio 2012):
What target should that task get? BeforeCompile or is this too late?
How exactly can i give the "files for compilation" as parameter into the task? How is it defined?
How is the output defined exactly so that the compilation uses the modified files?
Thanks, it would be nice if somebody can help me with an example :-)
A customer of mine asked me to replace some version and copyright information in some AssemblyInfo.cs files prior to the compile. I'm doing this via FileUpdate task with a RegEx in place. This of course leads to modified files. But that's no problem at all, just use the Exec task and run "svn revert" at the end of the build job:
<Target Name="RevertModifications">
<Message Text="reverting modifications..." />
<Exec Command="svn revert -R $(RootDir)"/>
</Target>
Perhaps that might help?

Setting web.config properties during build (not through publishing)

I'm trying to deploy an app using a Web Setup Project. The problem I'm running into is that the web.config file is never transformed. According to this post it's by design that transformation only takes place during a publish. How do I get the web.config properties to update correctly if building the Setup Project in turn calls the other assemblies build command?
I fixed it by adding a dummy web.Template.config file like Andriy K suggested in this post, and then calling TransformXml during my BeforeBuild event like so:
<Target Name="BeforeBuild">
<TransformXml Source="$(WebFolderName)Web.Template.config"
Transform="$(WebFolderName)Web.$(Configuration).config"
Destination="$(WebFolderName)Web.config" />
</Target>
The simplest option is to install a command-line xslt utility and launch it in the post-build action of your project. You could also use one of the many MSBuild XSLT tasks and add it into the .csproj file. (It's just an MSBuild script file; there are comments already in there near the bottom explaining how to customize the build.)
You could also perform either of these steps in the pre-build action of your setup project, instead of the post-build action of your web application. If you also use the publishing wizard, this second option may work better as it won't interfere with the normal XSLT transforming going on in the publisher.
Microsoft XSLT command-line utility: http://www.microsoft.com/downloads/en/details.aspx?FamilyID=2fb55371-c94e-4373-b0e9-db4816552e41&displaylang=en
Example MSBuild XSLT Task: http://www.arlt.eu/blog/2007/10/01/msbuild-xslt-task/

What is the best practice for "Copy Local" and with project references?

I have a large c# solution file (~100 projects), and I am trying to improve build times. I think that "Copy Local" is wasteful in many cases for us, but I am wondering about best practices.
In our .sln, we have application A depending on assembly B which depends on assembly C. In our case, there are dozens of "B" and a handful of "C". Since these are all included in the .sln, we're using project references. All assemblies currently build into $(SolutionDir)/Debug (or Release).
By default, Visual Studio marks these project references as "Copy Local", which results in every "C" being copied into $(SolutionDir)/Debug once for every "B" that builds. This seems wasteful. What can go wrong if I just turn "Copy Local" off? What do other people with large systems do?
FOLLOWUP:
Lots of responses suggest breaking up the build into smaller .sln files... In the example above, I would build the foundation classes "C" first, followed by the bulk of the modules "B", and then a few applications, "A". In this model, I need to have non-project references to C from B. The problem I run into there is that "Debug" or "Release" gets baked into the hint path and I wind up building my Release builds of "B" against debug builds of "C".
For those of you that split the build up into multiple .sln files, how do you manage this problem?
In a previous project I worked with one big solution with project references and bumped into a performance problem as well. The solution was three fold:
Always set the Copy Local property to false and enforce this via a custom msbuild step
Set the output directory for each project to the same directory (preferably relative to $(SolutionDir)
The default cs targets that get shipped with the framework calculate the set of references to be copied to the output directory of the project currently being built. Since this requires calculating a transitive closure under the 'References' relation this can become VERY costly. My workaround for this was to redefine the GetCopyToOutputDirectoryItems target in a common targets file (eg. Common.targets ) that's imported in every project after the import of the Microsoft.CSharp.targets. Resulting in every project file to look like the following:
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
... snip ...
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="[relative path to Common.targets]" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
This reduced our build time at a given time from a couple of hours (mostly due to memory constraints), to a couple of minutes.
The redefined GetCopyToOutputDirectoryItems can be created by copying the lines 2,438–2,450 and 2,474–2,524 from C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Microsoft.Common.targets into Common.targets.
For completeness the resulting target definition then becomes:
<!-- This is a modified version of the Microsoft.Common.targets
version of this target it does not include transitively
referenced projects. Since this leads to enormous memory
consumption and is not needed since we use the single
output directory strategy.
============================================================
GetCopyToOutputDirectoryItems
Get all project items that may need to be transferred to the
output directory.
============================================================ -->
<Target
Name="GetCopyToOutputDirectoryItems"
Outputs="#(AllItemsFullPathWithTargetPath)"
DependsOnTargets="AssignTargetPaths;_SplitProjectReferencesByFileExistence">
<!-- Get items from this project last so that they will be copied last. -->
<CreateItem
Include="#(ContentWithTargetPath->'%(FullPath)')"
Condition="'%(ContentWithTargetPath.CopyToOutputDirectory)'=='Always' or '%(ContentWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"
>
<Output TaskParameter="Include" ItemName="AllItemsFullPathWithTargetPath"/>
<Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectoryAlways"
Condition="'%(ContentWithTargetPath.CopyToOutputDirectory)'=='Always'"/>
<Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectory"
Condition="'%(ContentWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"/>
</CreateItem>
<CreateItem
Include="#(_EmbeddedResourceWithTargetPath->'%(FullPath)')"
Condition="'%(_EmbeddedResourceWithTargetPath.CopyToOutputDirectory)'=='Always' or '%(_EmbeddedResourceWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"
>
<Output TaskParameter="Include" ItemName="AllItemsFullPathWithTargetPath"/>
<Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectoryAlways"
Condition="'%(_EmbeddedResourceWithTargetPath.CopyToOutputDirectory)'=='Always'"/>
<Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectory"
Condition="'%(_EmbeddedResourceWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"/>
</CreateItem>
<CreateItem
Include="#(Compile->'%(FullPath)')"
Condition="'%(Compile.CopyToOutputDirectory)'=='Always' or '%(Compile.CopyToOutputDirectory)'=='PreserveNewest'">
<Output TaskParameter="Include" ItemName="_CompileItemsToCopy"/>
</CreateItem>
<AssignTargetPath Files="#(_CompileItemsToCopy)" RootFolder="$(MSBuildProjectDirectory)">
<Output TaskParameter="AssignedFiles" ItemName="_CompileItemsToCopyWithTargetPath" />
</AssignTargetPath>
<CreateItem Include="#(_CompileItemsToCopyWithTargetPath)">
<Output TaskParameter="Include" ItemName="AllItemsFullPathWithTargetPath"/>
<Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectoryAlways"
Condition="'%(_CompileItemsToCopyWithTargetPath.CopyToOutputDirectory)'=='Always'"/>
<Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectory"
Condition="'%(_CompileItemsToCopyWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"/>
</CreateItem>
<CreateItem
Include="#(_NoneWithTargetPath->'%(FullPath)')"
Condition="'%(_NoneWithTargetPath.CopyToOutputDirectory)'=='Always' or '%(_NoneWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"
>
<Output TaskParameter="Include" ItemName="AllItemsFullPathWithTargetPath"/>
<Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectoryAlways"
Condition="'%(_NoneWithTargetPath.CopyToOutputDirectory)'=='Always'"/>
<Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectory"
Condition="'%(_NoneWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"/>
</CreateItem>
</Target>
With this workaround in place I found it workable to have as much as > 120 projects in one solution, this has the main benefit that the build order of the projects can still be determined by VS instead of doing that by hand by splitting up your solution.
I'll suggest you to read Patric Smacchia's articles on that subject :
Partitioning Your Code Base Through .NET Assemblies and Visual Studio Projects --> Should every Visual Studio project really be in its own assembly? And what does 'Copy Local=True' really mean?
Lessons learned from the NUnit code base --> The VisualStudio Project Reference + Copy Local true option is evil!)
Analyzing the code base of CruiseControl.NET --> Bad usage of Copy Local Reference Assembly option set to True)
CC.Net VS projects rely on the copy local reference assembly option set to true. [...]
Not only this increase significantly the compilation time (x3 in the case of NUnit), but also it messes up your working environment. Last but not least, doing so introduces the risk for versioning potential problems. Btw, NDepend will emit a warning if it founds 2 assemblies in 2 different directories with the same name, but not the same content or version.
The right thing to do is to define 2 directories $RootDir$\bin\Debug and $RootDir$\bin\Release, and configure your VisualStudio projects to emit assemblies in these directories. All project references should reference assemblies in the Debug directory.
You could also read this article to help you reduce your projects number and improve your compilation time.
I suggest having copy local = false for almost all projects except the one that is at the top of the dependency tree. And for all the references in the one at the top set copy local = true. I see many people suggesting sharing an output directory; I think this is a horrible idea based on experience. If your startup project holds references to a dll that any other project holds a reference to you will at some point experience an access\sharing violation even if copy local = false on everything and your build will fail. This issue is very annoying and hard to track down. I completely suggest staying away from a shard output directory and instead of having the project at the top of the dependency chain write the needed assemblies to the corresponding folder. If you don't have a project at the "top," then I would suggest a post-build copy to get everything in the right place. Also, I would try and keep in mind the ease of debugging. Any exe projects I still leave copy local=true so the F5 debugging experience will work.
You are correct. CopyLocal will absolutely kill your build times. If you have a large source tree then you should disable CopyLocal. Unfortunately it not as easy as it should be to disable it cleanly. I have answered this exact question about disabling CopyLocal at How do I override CopyLocal (Private) setting for references in .NET from MSBUILD. Check it out. As well as Best practices for large solutions in Visual Studio (2008).
Here is some more info on CopyLocal as I see it.
CopyLocal was implemented really to support local debugging. When you prepare your application for packaging and deployment you should build your projects to the same output folder and make sure you have all the references you need there.
I have written about how to deal with building large source trees in the article MSBuild: Best Practices For Creating Reliable Builds, Part 2.
In my opinion, having a solution with 100 projects is a BIG mistake. You could probably split your solution in valid logical small units, thus simplifying both maintenance and builds.
I am surprised no one has mentioned using hardlinks. Instead of copying the files, it creates a hardlink to the original file. This saves disk space as well as greatly speeding up build. This can enabled on the command line with the following properties:
/p:CreateHardLinksForAdditionalFilesIfPossible=true;CreateHardLinksForCopyAdditionalFilesIfPossible=true;CreateHardLinksForCopyFilesToOutputDirectoryIfPossible=true;CreateHardLinksForCopyLocalIfPossible=true;CreateHardLinksForPublishFilesIfPossible=true
You can also add this to a central import file so that all your projects can also get this benefit.
If you got the dependency structure defined via project references or via solution level dependencies it's safe to turn of "Copy Local" I would even say that it's a best practice todo so since that will let you use MSBuild 3.5 to run your build in parallel (via /maxcpucount) without diffrent processes tripping over each other when trying to copy referenced assemblies.
our "best practise" is to avoid solutions with many projects.
We have a directory named "matrix" with current versions of assemblies, and all references are from this directory. If you change some project and you can say "now the change is complete" you can copy the assembly into the "matrix" directory. So all projects that depends on this assembly will have the current(=latest) version.
If you have few projects in solution, the build process is much faster.
You can automate the "copy assembly to matrix directory" step using visual studio macros or with "menu -> tools -> external tools...".
You don't need to change CopyLocal values. All you need to do is predefine a common $(OutputPath) for all projects in the solution and preset $(UseCommonOutputDirectory) to true. See this:
http://blogs.msdn.com/b/kirillosenkov/archive/2015/04/04/using-a-common-intermediate-and-output-directory-for-your-solution.aspx
Set CopyLocal=false will reduce build time, but can cause different issues during deployment.
There are many scenarios, when you need to have Copy Local’ left to True, e.g.
Top-level projects,
Second-level dependencies,
DLLs called by reflection
The possible issues described in SO questions
"When should copy-local be set to true and when should it not?",
"Error message 'Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information.'"
and  aaron-stainback's answer for this question.
My experience with setting CopyLocal=false was NOT successful. See my blog post "Do NOT Change "Copy Local” project references to false, unless understand subsequences."
The time to solve the issues overweight the benefits of setting copyLocal=false.
I tend to build to a common directory (e.g. ..\bin), so I can create small test solutions.
You can try to use a folder where all assemblies that are shared between projects will be copied, then make an DEVPATH environment variable and set <developmentMode developerInstallation="true" /> in machine.config file on each developer's workstation. The only thing you need to do is to copy any new version in your folder where DEVPATH variable points.
Also divide your solution into few smaller solutions if possible.
This may not be best pratice, but this is how I work.
I noticed that Managed C++ dumps all of its binaries into $(SolutionDir)/'DebugOrRelease'.
So I dumped all my C# projects there too. I also turned off the "Copy Local" of all references to projects in the solution. I had noticable build time improvement in my small 10 project solution. This solution is a mixture of C#, managed C++, native C++, C# webservice, and installer projects.
Maybe something is broken, but since this is the only way I work, I do not notice it.
It would be interesting to find out what I am breaking.
Usually, you only need to Copy Local if you want your project using the DLL that is in your Bin vs. what is somewhere else (the GAC, other projects, etc.)
I would tend to agree with the other folks that you should also try, if at all possible, to break up that solution.
You can also use Configuration Manager to make yourself different build configurations within that one solution that will only build given sets of projects.
It would seem odd if all 100 projects relied on one another, so you should be able to either break it up or use Configuration Manager to help yourself out.
You can have your projects references pointing to the debug versions of the dlls.
Than on your msbuild script, you can set the /p:Configuration=Release, thus you will have a release version of your application and all satellite assemblies.
If you want to have a central place to reference a DLL using copy local false will fail without the GAC unless you do this.
http://nbaked.wordpress.com/2010/03/28/gac-alternative/
If the reference is not contained within the GAC, we must set the Copy Local to true so that the application will work, if we are sure that the reference will be preinstalled in the GAC then it can be set to false.
Well, I certainly don't know how the problems works out, but i had contact with a build solution that helped itself in such that all created files where put on an ramdisk with the help of symbolic links.
c:\solution folder\bin -> ramdisk r:\solution folder\bin\
c:\solution folder\obj -> ramdisk r:\solution folder\obj\
You can also tell additionally the visual studio which temp directory it can use for the build.
Actually that wasn't all what it did. But it really hit my understanding of performance.
100% processor use and a huge project in under 3 Minute with all dependencies.

Categories

Resources