Related
I am trying to track down a VS 2019 build issue for a few weeks which drives me crazy. I have a C# project (targeting .Net Framework 4.8) which VS rebuilds regularly even when nothing has changed. The project is not very complex and has no specific dependencies, but a postbuild event which must always be executed. Hence I used the approach described in this answer, which forces msbuild to do the "up-to-date" check instead of the VS IDE. This has worked well for years, but started to make trouble a few weeks ago.
To create a minimal reproducible example:
use the VS project wizard to create a trivial "hello world" console app, .Net Fw 4.8, AnyCpu
add the following lines to the csproj file:
<PropertyGroup>
<RunPostBuildEvent>Always</RunPostBuildEvent>
<PostBuildEvent>
</PostBuildEvent>
<DisableFastUpToDateCheck>true</DisableFastUpToDateCheck>
</PropertyGroup>
change the "Run Post Build" setting in the IDE's project settings to "On Build Success" (or back to "Always", this does not really matter), save the settings
The Postbuild action is intentionally left empty, one can add arbitrary actions here, but even an empty action will produce the issue.
When I choose to rebuild the solution in the IDE (using Ctrl-Shift-B), without touching anything the source code, the executable is recreated. This effect occurs when the time between two consecutive builds is approx. 10 seconds or more, when I rebuild the solution quicker, the exe file stays untouched.
To make the effect more visible, I set "AssemblyVersion("1.0.0.*"), stripped the "AssemblyInformationalVersion" attribute from AssemblyInfo.cs, so the build system assigns a new build number to the exe file with each new creation, which allows to observe the effect in the Windows Explorer more easily (by activating the "File Version" column in the Explorer view).
Note this effect does not seem to occur when I comment out either the post build event, or the DisableFastUpToDateCheck setting.
I observed this with VS 2019 V16.11.9 and V16.11.10 (currently the latest versions in the "2019" product line).
In my real project, this happens for a central DLL inside a solution where more then 70 other projects depend on, including a large C++/CLI dll, resulting in a build time of ~2 minutes - every time I only want to start the debugger, since this causes a new build! And yes, I also tried to set the "project build output" settings to "Diagnostic", but could not find anything suspicious in the large amount of messages.
PostBuildEvent is problematic and 'old style' since it doesn't define its inputs and outputs. Because of that msbuild can't calculate whether it caused any files to change and thus forces a rebuild.
By replacing the postbuildevent with a custom target and by correctly specifying the inputs and outputs of the target, MSBuild can check whether any of the inpu ts have changed and whether the outputs are up to date to properly decide to skip the build altogether.
See:
https://learn.microsoft.com/en-us/visualstudio/msbuild/incremental-builds?view=vs-2022
PostBuildEvent is also no longer supported in new style SDK projects. Visual Studio will now automatically generate a new target when you setup a postbuild event in the UI.
Starting with jesshouwing's answer, and using this information from Microsoft how to extend the VS build process, I implemented this workaround: in the csproj, I added the following section the end:
<Target Name="CustomAfterBuild" AfterTargets="Build"
Inputs="... input for postbuild step ..."
Outputs=" ... output of postbuild step ..">
<Message Text="... some message here ..." />
<!-- here are the post build actions -->
</Target>
This seems to work well for now without the nasty effects. The only drawback here is that this custom build step does not show up in the Visual Studio project editor, but I can work with that.
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.
I have an asp.net/C# app that uses subversion for source control.
My app automatically increases it's AssembleVersion and AssemblyFileVersion on each build which works like a charm, and displays the build number in the administration side of the site.
We keep track of AssembleVersion and AssemblyFileVersion's when we do deployment, however, when an issue arises and we need to roll back to a certain version, we have no idea which revision to target in subversion.
I have few ideas:
Save AssembleVersion as comment in each file
Have a keyword in commit comments that get's replaced by AssembleVersion on each commit(still need to figure out how to do it)
Any help and suggestions will be appreciated
Updated:
option "1" is actually a stupid idea,cause this will mean that everytime i build, all files will be marked as updated and when i commit, every single file will be updated
When I build, I put that build number everywhere.
I put it in a tag in svn.
I put it in the Assembly metadata of every assembly I build.
I append it to the end of the filename in my installers.
I put it in the footer of each of my deployed webpages.
I put it in the footer of my reports.
I put it in the splash screen of my client side apps.
I put it in the welcome screen for my installers.
The only thing I don't put it in is my coffee, which I take black.
All of this lets a maintainer know at a glance exactly where the code came from for what they're seeing, whether they're viewing a webpage, or looking at the properties of one of the built assemblies in Explorer, or whatever.
How about using tags.
http://svnbook.red-bean.com/en/1.1/ch04s06.html
Tags aren't really useful if you happen to build often. Maybe find a way to update Assembly version based on the svn revision instead? Also include the branch name, because they share the revisions.
And you should be able to extract the assembly version in your ASP.NET pages and print it programmatically in a footer or something.
You could tag the Subversion trunk with the AssembleVersion or AssemblyFileVersion, whichever makes the most sense.
You could also keep track of the Subversion revision number the same way you currently keep track of the AssembleVersion and AssemblyFileVersion when you deploy.
Apply a tag to your source tree after you have updated the AssemblyVersion and AssemblyFileVersion.
You could "branch for release". Before creating a release build you could branch the trunk and then create a tag on the new branch with the release version number.
+ release tag
/
+--------------------- release branch
/
----------+----------------------------------------------------- trunk
This would allow you to keep track of all individual releases in SVN. It would also allow you to make isolated bug fixes on release branches that could be released as patches. The bug fix could then be merged back into the trunk.
+ + patch release tag
/ /
+-----------------+-+---- release branch
/ | merged fix into trunk...
----------+----------------------------------------------------- trunk
Tags/branches are definately the recommended approach here.
You can also (or additionally) include the svn revision number in your AssemblyInfo. One approach is to use the AssemblyInfo task from the msbuildtasks project at http://msbuildtasks.tigris.org
For more info, google msbuild svn revision assemblyinfo
You could then do without tags/branches, as you can always check out a specific revision, and/or create a branch from a specific revision.
Another option is to use last changed revision as your build number. This means each time you build you auto-tag. It's easy with hudson/jenkins since you have an environment variable SVN_REVISION. The problem is that revision number get very large and hallway discussions about 1.0.0.20456 vs 1.0.0.20489 are a mouthful.
I was just wondering how I could automatically increment the build (and version?) of my files using Visual Studio (2005).
If I look up the properties of say C:\Windows\notepad.exe, the Version tab gives "File version: 5.1.2600.2180". I would like to get these cool numbers in the version of my dll's too, not version 1.0.0.0, which let's face it is a bit dull.
I tried a few things, but it doesn't seem to be out-of-box functionality, or maybe I'm just looking in the wrong place (as usual).
I work with mainly web projects....
I looked at both:
http://www.codeproject.com/KB/dotnet/Auto_Increment_Version.aspx
http://www.codeproject.com/KB/dotnet/build_versioning.aspx
and I couldn't believe it so much effort to do something is standard practice.
EDIT:
It does not work in VS2005 as far I can tell (http://www.codeproject.com/KB/dotnet/AutoIncrementVersion.aspx)
In visual Studio 2008, the following works.
Find the AssemblyInfo.cs file and find these 2 lines:
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
You could try changing this to:
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("1.0.*")]
But this won't give you the desired result, you will end up with a Product Version of 1.0.* and a File Version of 1.0.0.0. Not what you want!
However, if you remove the second of these lines and just have:
[assembly: AssemblyVersion("1.0.*")]
Then the compiler will set the File Version to be equal to the Product Version and you will get your desired result of an automatically increment product and file version which are in sync. E.g. 1.0.3266.92689
open up the AssemblyInfo.cs file and change
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
to
[assembly: AssemblyVersion("1.0.*")]
//[assembly: AssemblyFileVersion("1.0.0.0")]
you can do this in IDE by going to project -> properties -> assembly information
This however will only allow you to auto increment the Assembly version and will give you the
Assembly File Version: A wildcard ("*") is not allowed in this field
message box if you try place a * in the file version field.
So just open up the assemblyinfo.cs and do it manually.
Another option for changing version numbers in each build is to use the Version task of MSBuild.Community.Tasks. Just download their installer, install it, then adapt the following code and paste it after <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> in your .csproj file:
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" />
<Target Name="BeforeBuild">
<Version VersionFile="Properties\version.txt" Major="1" Minor="0" BuildType="Automatic" StartDate="12/31/2009" RevisionType="BuildIncrement">
<Output TaskParameter="Major" PropertyName="Major" />
<Output TaskParameter="Minor" PropertyName="Minor" />
<Output TaskParameter="Build" PropertyName="Build" />
<Output TaskParameter="Revision" PropertyName="Revision" />
</Version>
<AssemblyInfo CodeLanguage="CS"
OutputFile="Properties\VersionInfo.cs"
AssemblyVersion="$(Major).$(Minor)"
AssemblyFileVersion="$(Major).$(Minor).$(Build).$(Revision)" />
</Target>
Note: Adapt the StartDate property to your locale. It currently does not use the invariant culture.
For the third build on January 14th, 2010, this creates a VersionInfo.cs with this content:
[assembly: AssemblyVersion("1.0")]
[assembly: AssemblyFileVersion("1.0.14.2")]
This file then has to be added to the project (via Add existing item), and the AssemblyVersion and AssemblyFileVersion lines have to be removed from AssemblyInfo.cs.
The different algorithms for changing the version components are described in $(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.chm and Version Properties.
I came up with a solution similar to Christians but without depending on the Community MSBuild tasks, this is not an option for me as I do not want to install these tasks for all of our developers.
I am generating code and compiling to an Assembly and want to auto-increment version numbers. However, I can not use the VS 6.0.* AssemblyVersion trick as it auto-increments build numbers each day and breaks compatibility with Assemblies that use an older build number. Instead, I want to have a hard-coded AssemblyVersion but an auto-incrementing AssemblyFileVersion. I've accomplished this by specifying AssemblyVersion in the AssemblyInfo.cs and generating a VersionInfo.cs in MSBuild like this,
<PropertyGroup>
<Year>$([System.DateTime]::Now.ToString("yy"))</Year>
<Month>$([System.DateTime]::Now.ToString("MM"))</Month>
<Date>$([System.DateTime]::Now.ToString("dd"))</Date>
<Time>$([System.DateTime]::Now.ToString("HHmm"))</Time>
<AssemblyFileVersionAttribute>[assembly:System.Reflection.AssemblyFileVersion("$(Year).$(Month).$(Date).$(Time)")]</AssemblyFileVersionAttribute>
</PropertyGroup>
<Target Name="BeforeBuild">
<WriteLinesToFile File="Properties\VersionInfo.cs" Lines="$(AssemblyFileVersionAttribute)" Overwrite="true">
</WriteLinesToFile>
</Target>
This will generate a VersionInfo.cs file with an Assembly attribute for AssemblyFileVersion where the version follows the schema of YY.MM.DD.TTTT with the build date. You must include this file in your project and build with it.
There is a visual studio extension Automatic Versions which supports Visual Studio (2017,2019 & 2022)
Screen Shots
Install the Build Version Increment add-in. It gives you way more control than the * option.
To get the version numbers try
System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();
System.Reflection.AssemblyName assemblyName = assembly.GetName();
Version version = assemblyName.Version;
To set the version number, create/edit AssemblyInfo.cs
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("1.0.*")]
Also as a side note, the third number is the number of days since 2/1/2000 and the fourth number is half of the amount of total seconds in the day. So if you compile at midnight it should be zero.
In Visual Studio 2019
It was not enough for me adding
[assembly: AssemblyVersion("1.0.*")]
When building it throws me this error
The specified version string does not conform to the required format
Solution
The format was finally accepted after I set Deterministic to False in project.csproj
<Deterministic>false</Deterministic>
Edit:
For some reason setting Deterministic to False messed up my config file loading it and saving it on different locations.
Workaround:
I setup a post-build event to increment the revision number:
Post-Build Event batch script
This calls a powershell script named autoincrement_version.ps1 passing as argument the path of AssemblyInfo.cs
if $(ConfigurationName) == Release (
PowerShell -ExecutionPolicy RemoteSigned $(ProjectDir)autoincrement_version.ps1 '$(ProjectDir)My Project\AssemblyInfo.cs'
)
Poweshell script
It autoincrements the revision number using Regex
param( [string]$file );
$regex_revision = '(?<=Version\("(?:\d+\.)+)(\d+)(?="\))'
$found = (Get-Content $file) | Select-String -Pattern $regex_revision
$revision = $found.matches[0].value
$new_revision = [int]$revision + 1
(Get-Content $file) -replace $regex_revision, $new_revision | Set-Content $file -Encoding UTF8
Setting a * in the version number in AssemblyInfo or under project properties as described in the other posts does not work with all versions of Visual Studio / .NET.
Afaik it did not work in VS 2005 (but in VS 2003 and VS 2008). For VS 2005 you could use the following: Auto Increment Visual Studio 2005 version build and revision number on compile time.
But be aware that changing the version number automatically is not recommended for strong-named assemblies. The reason is that all references to such an assembly must be updated each time the referenced assembly is rebuilt due to the fact that strong-named assembly references are always a reference to a specific assembly version. Microsoft themselves change the version number of the .NET Framework assemblies only if there are changes in interfaces. (NB: I'm still searching for the link in MSDN where I read that.)
To get incrementing (DateTime) information into the AssemblyFileVersion property which has the advantage of not breaking any dependencies.
Building on Boog's solution (did not work for me, maybe because of VS2008?), you can use a combination of a pre-build event generating a file, adding that file (including its version properties) and then using a way to read out those values again. That is..
Pre-Build-Event:
echo [assembly:System.Reflection.AssemblyFileVersion("%date:~-4,4%.%date:~-7,2%%date:~-10,2%.%time:~0,2%%time:~3,2%.%time:~-5,2%")] > $(ProjectDir)Properties\VersionInfo.cs
Include the resulting VersionInfo.cs file (Properties subfolder) into your project
Code to get Date back (years down to seconds):
var version = assembly.GetName().Version;
var fileVersionString = System.Diagnostics.FileVersionInfo.GetVersionInfo(assembly.Location).FileVersion;
Version fileVersion = new Version(fileVersionString);
var buildDateTime = new DateTime(fileVersion.Major, fileVersion.Minor/100, fileVersion.Minor%100, fileVersion.Build/100, fileVersion.Build%100, fileVersion.Revision);
Not very comfortable.. also, I do not know if it creates a lot of force-rebuilds (since a file always changes).
You could make it smarter for example if you only update the VersionInfo.cs file every few minutes/hours (by using a temporary file and then copying/overwriting the real VersionInfo.cs if a change large enough is detected). I did this once pretty successfully.
Set the version number to "1.0.*" and it will automatically fill in the last two number with the date (in days from some point) and the time (half the seconds from midnight)
It is in your project properties under Publish
(~ http://screencast.com/t/Vj7rhqJO)
Cake supports AssemblyInfo files patching. With cake in hands you have infinite ways to implement automatic version incrementing.
Simple example of incrementing version like C# compiler does:
Setup(() =>
{
// Executed BEFORE the first task.
var datetimeNow = DateTime.Now;
var daysPart = (datetimeNow - new DateTime(2000, 1, 1)).Days;
var secondsPart = (long)datetimeNow.TimeOfDay.TotalSeconds/2;
var assemblyInfo = new AssemblyInfoSettings
{
Version = "3.0.0.0",
FileVersion = string.Format("3.0.{0}.{1}", daysPart, secondsPart)
};
CreateAssemblyInfo("MyProject/Properties/AssemblyInfo.cs", assemblyInfo);
});
Here:
Version - is assembly version. Best practice is to lock major version number and leave remaining with zeroes (like "1.0.0.0").
FileVersion - is assembly file version.
Note that you can patch not only versions but also all other necessary information.
How to get the version {major}.{year}.1{date}.1{time}
This one is kind of experimental, but I like it. Inspired by Jeff Atwood # CodingHorror (link).
The resulting version number becomes 1.2016.10709.11641 (meaning 2016-07-09 16:41), which allows for
poor mans zero padding (with the stupid leading 1s)
nearly-human readable local DateTime embedded into the version number
leaving Major version alone for really major breaking changes.
Add a new item to your project, select General -> Text Template, name it something like CustomVersionNumber and (where applicable) comment out the AssemblyVersion and AssemblyFileVersion in Properties/AssemblyInfo.cs.
Then, when saving this file, or building the project, this will regenerate a .cs file located as a sub-item under the created .tt file.
<## template language="C#" #>
<## assembly name="System.Core" #>
<## import namespace="System.Linq" #>
//
// This code was generated by a tool. Any changes made manually will be lost
// the next time this code is regenerated.
//
using System.Reflection;
<#
var date = DateTime.Now;
int major = 1;
int minor = date.Year;
int build = 10000 + int.Parse(date.ToString("MMdd"));
int revision = 10000 + int.Parse(date.ToString("HHmm"));
#>
[assembly: AssemblyVersion("<#= $"{major}.{minor}.{build}.{revision}" #>")]
[assembly: AssemblyFileVersion("<#= $"{major}.{minor}.{build}.{revision}" #>")]
Go to Project | Properties and then Assembly Information and then Assembly Version and put an * in the last or the second-to-last box (you can't auto-increment the Major or Minor components).
Use the AssemblyInfo task from the MSBuild Community Tasks (http://msbuildtasks.tigris.org/) project, and integrate it into your .csproj/.vbproj file.
It has a number of options, including one to tie the version number to the date and time of day.
Recommended.
As of right now, for my application,
string ver = Application.ProductVersion;
returns ver = 1.0.3251.27860
The value 3251 is the number of days since 1/1/2000. I use it to put a version creation date on the splash screen of my application. When dealing with a user, I can ask the creation date which is easier to communicate than some long number.
(I'm a one-man dept supporting a small company. This approach may not work for you.)
Maybe, for this task, you can use code like this:
private bool IncreaseFileVersionBuild()
{
if (System.Diagnostics.Debugger.IsAttached)
{
try
{
var fi = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory).Parent.Parent.GetDirectories("Properties")[0].GetFiles("AssemblyInfo.cs")[0];
var ve = System.Diagnostics.FileVersionInfo.GetVersionInfo(System.Reflection.Assembly.GetExecutingAssembly().Location);
string ol = ve.FileMajorPart.ToString() + "." + ve.FileMinorPart.ToString() + "." + ve.FileBuildPart.ToString() + "." + ve.FilePrivatePart.ToString();
string ne = ve.FileMajorPart.ToString() + "." + ve.FileMinorPart.ToString() + "." + (ve.FileBuildPart + 1).ToString() + "." + ve.FilePrivatePart.ToString();
System.IO.File.WriteAllText(fi.FullName, System.IO.File.ReadAllText(fi.FullName).Replace("[assembly: AssemblyFileVersion(\"" + ol + "\")]", "[assembly: AssemblyFileVersion(\"" + ne + "\")]"));
return true;
}
catch
{
return false;
}
}
return false;
}
and call it from form loading.
With this code you can update any part of file info in AssemblyInfo.cs (but you must use "standard" directory structure).
Changing the AssemblyInfo works in VS2012. It seems strange that there's not more support for this in Visual Studio, you'd think this was a basic part of the build/release process.
I have created an application to increment the file version automatically.
Download Application
add the following line to pre-build event command line
C:\temp\IncrementFileVersion.exe $(SolutionDir)\Properties\AssemblyInfo.cs
Build the project
To keep it simple the app only throws messages if there is an error, to confirm it worked fine you will need to check the file version in 'Assembly Information'
Note : You will have to reload the solution in Visual studio for 'Assembly Information' button to populate the fields, however your output file will have the updated version.
For suggestions and requests please email me at telson_alva#yahoo.com
AssemblyInfoUtil. Free. Open-source.
I'm using this approach https://stackoverflow.com/a/827209/3975786 by placing the T4 template in a "Solution Items" and using it with "Add as Link" within each project.
Maybe it's too late to answer here but hope that will solve someone's hectic problem.
An automatic way to change assembly version of all of your projects using PowerShell script. This article will solve many of your problems.
I tried this with Visual Studio 2019 and it did not work.
In newer versions of VS at least the Deterministic-flag prevents the auto-update. But changing the 14th line of
Your-project-name.csproj to <Deterministic>false</Deterministic>
and changing the version number string to "1.0.*" did not help me.
So I made a litle vbs script that does the job.
it changes the version number to (Major version).(Minor version).([year][dayofyear]).(increment).
Copy the script into a folder and put the following into pre-compile build-commandline:
"Path-to-this-script\UpdateVersion.vbs" "$(ProjectDir)"
(including the quotes and filling in the real path of Your machine)
and You are done.
Get it here:
https://github.com/abtzero/VS_UpdateVersion.git
Each time I do a build it auto-increments the least-significant digit.
I don't have any idea how to update the others, but you should at least be seeing that already...
For anyone using Tortoise Subversion, you can tie one of your version numbers to the subversion Revision number of your source code. I find this very useful (Auditors really like this too!). You do this by calling the WCREV utility in your pre-build and generating your AssemblyInfo.cs from a template.
If your template is called AssemblyInfo.wcrev and sits in the normal AssemblyInfo.cs directory, and tortoise is in the default installation directory, then your Pre-Build command looks like this (N.B. All on one line):
"C:\Program Files\TortoiseSVN\bin\SubWCRev.exe" "$(ProjectDir)." "$(ProjectDir)Properties\AssemblyInfo.wcrev" "$(ProjectDir)Properties\AssemblyInfo.cs"
The template file would include the wcrev token substitution string: $WCREV$
e.g.
[assembly: AssemblyFileVersion("1.0.0.$WCREV$")]
Note:
As your AssemblyInfo.cs is now generated you do not want it version controled.
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.