Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is MSBuild rebuilding shared projects?

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>
like image 414
MzunguMark Avatar asked Sep 27 '22 21:09

MzunguMark


2 Answers

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>
like image 169
CheGueVerra Avatar answered Nov 15 '22 05:11

CheGueVerra


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.

like image 22
MzunguMark Avatar answered Nov 15 '22 07:11

MzunguMark