For some reason, every project in my solution has decided to rebuild every time I try to build the solution -- even if that particular project has no changes and none of the dependent projects have any changes. I thought it might have been cause by my recent change to turn Copy Local off for inter-solution references to help speed up the build process. But even reverting the code didn't seem to fix it. Deleting the .suo and .user files that Visual Studio keeps it's editor state in didn't help either. So I decided to track down the issue.
I'd googled and found a few others reporting similar issues but all for older versions of VS.NET or with solutions that didn't work. So I had to figure it out for myself. To start, I turned on diagnostic output so I can see the entire MSBuild process and decision trees.
After building the solution again, I saved the contents of the Output window (Select it then Ctrl+S) to a text file so I could review it in my favorite text editor. Searching for the Csc task (which represents the actual compile operation) I saw that each task was prefixed with the text:
Target "CoreCompile" in file "C:\Windows\Microsoft.NET\Framework\v3.5\Microsoft.CSharp.targets": Building target "CoreCompile" completely. Output file "__NonExistentSubDir__\__NonExistentFile__" does not exist.
That seemed really odd so first step was to google to see what others had to say about __NonExistentFile__. The search returned lots of build logs and online duplicates of the Microsoft.Common.targets file. So it looks like that is where I wanted to start searching. Looking through the Microsoft.Common.targets (normally found at %WINDIR%\Microsoft.NET\Framework\[Version]\) file in my favorite text editor turned up the following:
<!-- ================================================================ _ComputeNonExistentFileProperty There are certain situations in which we want to always run the CoreCompile target (and thus the Csc task), even if the timestamps of the outputs appear to be up-to-date on disk. If we're inside the IDE during design-time, then the Csc/Vbc/Vjc task is simply being used to initialize the host compiler, so we always want to run it. Also, if we're inside the IDE, and the host compiler is responsible for doing the compilation during an actual build, we want to let the host compiler determine whether the output is up-to-date, because there may be source files in the IDE's in-memory buffers that we don't know about. So, we always run the CoreCompile target if we're in the IDE, and either we're in design-time or we're delegating to the host compiler for the actual build. ================================================================ --> <Target Name="_ComputeNonExistentFileProperty" Condition=" ('$(BuildingInsideVisualStudio)' == 'true') and ( ('$(BuildingProject)' == 'false') or ('$(UseHostCompilerIfAvailable)' == 'true') ) "> <PropertyGroup> <NonExistentFile> __NonExistentSubDir__\__NonExistentFile__ </NonExistentFile> </PropertyGroup> </Target>
Well it seems that VS isn't doing it's job in determining that the output is up-to-date so I'm going to stop it from being responsible any more. All of our build scripts are designed to by run from the command line or Team City so I'm not concerned about loosing any functionality by letting MSBuild own the build even in the IDE.
Since we've got all of the projects set up to include a shared .targets file that we can put custom build targets in, overriding the behavior was simple. I just added the following:
<PropertyGroup> <UseHostCompilerIfAvailable>false</UseHostCompilerIfAvailable> </PropertyGroup>
I could have chosen to override the BuildingInsideVisualStudio or BuildingProject properties instead but the Microsoft.CSharp.targets file includes a comment indicating that overriding the UseHostCompilerIfAvailable property is a supported scenario.
After making this change I noticed that my builds are considerably faster than before. I haven't done any formal testing but on a solution with 50+ projects it seems to be at least twice as fast. That's not accounting for the unnecessary project builds I had to sit through before.