Monday, 12 May 2014

Code Contracts failing to find satellite references

Code Contracts : Could not resolve type reference

I've found that the code contracts compiler is a bit on the sensitive side. 

We recently re-factored our build pipeline to use pre-compiled NuGet modules,whereby we import NuGet packages of pre-compiled segments of our platform, rather than rebuilding each time.

Interestingly, this project gave me some insight into auto-magic performed by MSBuild in helping us get things built. In this instance, it would seem that MSBuild must maintain a list of search paths for assemblies. Each output or reference it encounters perhaps joining the list as it goes along. 

In short, MSBuild was previously filling in the gaps for us, gaps we didn't know existed. It was automatically finding matching assemblies from its dynamic search path. It transpired that we had many 'implicit' references to satellite assemblies that only held true during a full build. These assemblies were not included in our NuGet packages, and so we would see an error such as this:

The problem

These missing libraries were nearly always in our shared libraries folder. 

Target CodeContractRewrite:

"C:\Program Files (x86)\Microsoft\Contracts\Bin\ccrewrite.exe" "@Org.Namespace.Solution.DomainCCRewrite.rsp"

Reading assembly '----.-----.-----.----' from '-----.-----.-----.Repositories.dll' resulted in errors.

Assembly reference not resolved: EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089.

Could not resolve type reference: [EntityFramework]System.Data.Entity.Core.Objects.IObjectSet`1.
Could not resolve type reference: [EntityFramework]System.Data.Entity.Core.Objects.ObjectStateEntry.
Could not resolve type reference: [EntityFramework]System.Data.Entity.Core.Objects.ObjectQuery`1.
ccrewrite : error : Rewrite aborted due to metadata errors. Check output window

The Solution

We created a custom target, that would furnish MSBuild with all the search paths for our Libraries folder. This would enable it to match on satellite assemblies that it couldn't find itself.

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="BeforeResolveReferences">
    <ItemGroup>
      <LibrariesSearchPathFiles Include="$(MSBuildThisFileDirectory)..\..\..\Libraries\**\*.dll" />
    </ItemGroup>
    <Message Text="Adding Libraries directory to assembly search paths..." />
    
    <RemoveDuplicates Inputs="@(LibrariesSearchPathFiles->'%(RootDir)%(Directory)')">
      <Output TaskParameter="Filtered" ItemName="LibrariesSearchPath" />
    </RemoveDuplicates>

    <CreateProperty Value="$(AssemblySearchPaths);@(LibrariesSearchPath)">
      <Output TaskParameter="Value"
          PropertyName="AssemblySearchPaths" />
    </CreateProperty>
<CreateProperty Value="$(CodeContractsLibPaths);@(LibrariesSearchPath)">
      <Output TaskParameter="Value"
          PropertyName="CodeContractsLibPaths" />
    </CreateProperty>
  </Target>
</Project>


And, in every .CSPROJ that was throwing up the CodeContractRewrite error, we would add:

<Import Project="$(ProjectDir)..\..\..\ALM\Build\targets\Libraries.targets" />

After this, the Code Contracts compiler had access to a search path that contained all our library items.