Reputation: 175
I am trying to speed up the compilation of a set of solutions by only compiling shared projects once. This is a simplified description of my problem.
I have two solutions -- one.sln and two.sln. They both include shared project shared.csproj. I open the two solutions in Visual Studio, clean both, then build one.sln, then build two.sln. When two.sln is built, shared.dll is NOT regenerated, which is the behavior I expect.
To automate this process, I have created an msbuild .proj file (below). When I call msbuild on the .proj file, shared.dll is recompiled for BOTH solutions. What am I doing wrong?
<Project ToolsVersion="4.0"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
DefaultTargets="Build">
<ItemGroup>
<Solution Include="C:\one.sln"/>
<Solution Include="C:\two.sln"/>
</ItemGroup>
<Target Name="Build">
<MSBuild Projects="@(Solution)" Targets="Build" RunEachTargetSeparately="false" StopOnFirstFailure="true" />
</Target>
</Project>
Upvotes: 5
Views: 1199
Reputation: 5554
Visual Studio may make rebuilds a little harder to reason because it has a "Fast Up-To-Date checker" that has custom heuristics. You can find out why a project is being rebuilt by enabling the verbosity of the fast up to date checker in the registry key:
Visual Studio 2017 (must be running) [2]
New-ItemProperty `
-Name U2DCheckVerbosity `
-PropertyType DWORD -Value 1 `
-Path HKCU:\Software\Microsoft\VisualStudio\15.0\General -Force
Visual Studio 2015
New-ItemProperty `
-Name U2DCheckVerbosity `
-PropertyType DWORD -Value 1 `
-Path HKCU:\Software\Microsoft\VisualStudio\14.0\General -Force
You should be able to see in the build log messages like
Project 'Caliburn.Micro.Silverlight.Extensions' is not up to date. Project item 'C:\dev\projects\Caliburn.Micro.Silverlight.Extensions\NavigationBootstrapperSample.cs.pp' has 'Copy to Output Directory' attribute set to 'Copy always'.
[2] https://visualstudioextensions.vlasovstudio.com/2017/06/29/changing-visual-studio-2017-private-registry-settings/ Visual Studio 2017 uses private registry hive
Upvotes: 0
Reputation: 175
I figured out why the shared projects were being recompiled for every solution. We have a post build step in each project that code signs the target file. After each previous compilation of a project, the .dll file is newer than the .pdb file, so it recompiles the project. I found this out by using the /v:d switch for verbose logging, then searching for "completely" in the output to find out why projects were being built completely each time.
Upvotes: 1
Reputation: 7963
IIRC, the reason why the reference is compiled twice, is that it is declared as a reference project in both solutions. So both will call the Build Target of their reference. That's how I understand it.
Create an Itemgroup for the reference project
<!--Project reference-->
<ItemGroup>
<ProjectReference Include="refProject\refProject.csproj">
<Targets>Build</Targets>
</ProjectReference>
</ItemGroup>
Add a target to build Reference
<Target Name="ComputeProjectReference" Inputs="@(ProjectReference)" Outputs="%(ProjectReference.Identity)__Forced">
<MSBuild Projects="@(ProjectReference)" Targets="%(ProjectReference.Targets)">
<Output TaskParameter="TargetOutputs" ItemName="ResolvedProjectReferences"/>
</MSBuild>
</Target>
<Target Name="AfterProjectReference" AfterTargets="ComputeProjectReference">
<CreateItem Include="@(ResolvedProjectReferences)">
<Output TaskParameter="Include" ItemName="CopyFiles" />
</CreateItem>
<Copy SourceFiles="@(CopyFiles)" DestinationFolder="$(AssemblyName)\$(OutputPath)" SkipUnchangedFiles="false" />
<ItemGroup>
<NewAssemblies Include="$(AssemblyName)\$(OutputPath)%(CopyFiles.FileName)%(CopyFiles.Extension)" />
</ItemGroup>
</Target>
Unfortunately, MSBuild task doesn't take references as a parameter. I suggest creating itemGroups that represent each project required. something like
<ItemGroup>
<CompileA Include="ConsProject\Program.cs" />
<CompileA Include="ConsProject\Properties\AssemblyInfo.cs" />
<CompileA Include="ConsProject\Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<DependentUpon>Settings.settings</DependentUpon>
</CompileA>
</ItemGroup>
<ItemGroup>
<CompileB Include="OtherProject\Program.cs" />
<CompileB Include="OtherProject\Properties\AssemblyInfo.cs" />
<CompileB Include="OtherProject\Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<DependentUpon>Settings.settings</DependentUpon>
</CompileB>
</ItemGroup>
And create a target to build the projects with the references compiled once
<!--Build Process-->
<Target Name="Build" DependsOnTargets="ComputeProjectReference" >
<Csc Sources="@(CompileA)" References="@(NewAssemblies)" TargetType="exe" OutputAssembly="$(AssemblyName)\$(OutputPath)\$(AssemblyName).exe" />
<Csc Sources="@(CompileB)" References="@(NewAssemblies)" TargetType="exe" OutputAssembly="$(AssemblyName)\$(OutputPath)\$(AssemblyName).exe" />
</Target>
Upvotes: 3