Damian Hickey

Mostly software and .NET related. Mostly.

Shipping Code Contracts Reference Assemblies

Problem

So say I have SolutionA that has ProjectA that outputs:

    bin\Release\ProjectA.dll
    bin\Release\CodeContracts\ProjectA.Contracts.dll

A post build step on our CI server copies this build output to a specific LocationA ( a build artifact folder to be specific)

Then we have SolutionB, that has ProjectB, that has a reference to ProjectA in LocationA. The static checker picks ProjectA.Contracts.dll just fine. All good.

The build output from SolutionB is:

    bin\Release\ProjectA.dll
    bin\Release\ProjectB.dll
    bin\Release\CodeContracts\ProjectB.Contracts.dll

Note, ProjectA.Contracts.dll is not part of the build output. Post build step copies this output to LocationB.

Now, we have a third solution, SolutionC, that has ProjectC, that has explicit dependency on ProjectB and implicit dependency on ProjectA. Both of which are in LocationB.

The problem here is that static checker, when analyzing ProjectC, has no access to ProjectA.Contracts.dll and so has warnings.

What I think I would like is to have ProjectB's output look like:

    bin\Release\ProjectA.dll
    bin\Release\ProjectB.dll
    bin\Release\CodeContracts\ProjectA.Contracts.dll <- include this in output
    bin\Release\CodeContracts\ProjectB.Contracts.dll

And correspondingly, I'd like ProjectC's output to look like:

    bin\Release\ProjectA.dll
    bin\Release\ProjectB.dll
    bin\Release\ProjectC.dll
    bin\Release\CodeContracts\ProjectA.Contracts.dll
    bin\Release\CodeContracts\ProjectB.Contracts.dll
    bin\Release\CodeContracts\ProjectC.Contracts.dll

etc.

Solution

Add an AfterTarget the Microsoft.CodeContracts.targets file (%ProgramFiles(x86)%\Microsoft\Contracts\MsBuild\v4.0  and \3.5) that will copy it's code contracts reference assemblies, if they exist, to the output directory of the project being compiled:

<!--=====================================================================
   Copy reference's Contract Reference Assembles that are marked as "CopyLocal"
  ======================================================================-->
 <Target
   Name="CopyReferenceCodeContractReferenceAssemblies"
	 Condition="'@(ReferenceCopyLocalPaths)' != ''"
	 AfterTargets="CodeContractReferenceAssembly">
  <ItemGroup>
    <CodeContractsReferenceAssembliesFiles
	   Include="@(ReferenceCopyLocalPaths->'%(RootDir)%(Directory)CodeContracts\%(Filename).Contracts%(Extension)')"
	   />
	  <CodeContractsReferenceAssembliesCopiedFiles/>
  </ItemGroup>
  <Copy
    SourceFiles="@(CodeContractsReferenceAssembliesFiles)"
    DestinationFolder="$(OutDir)CodeContracts"
    SkipUnchangedFiles="$(SkipCopyUnchangedFiles)"
    Retries="$(CopyRetryCount)"
    RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)"
    Condition="'$(UseCommonOutputDirectory)' != 'true' And Exists('%(RootDir)%(Directory)%(Filename)%(Extension)')"
    >
	  <Output TaskParameter="CopiedFiles" ItemName="CodeContractsReferenceAssembliesCopiedFiles"/>
	</Copy>
	<ItemGroup>
    <FileWrites Include="@(CodeContractsReferenceAssembliesCopiedFiles)"/>
	</ItemGroup>
 </Target>

This will need to be done on your build server too.

We have been using this at my company for several months now without issues.

Original discussion. Thx to Mike Barnett for the suggestion.