The Problem
I have been using the TeamCity continuous integration server to generate and publish NuGet packages automatically. The approach I have taken is based on that proposed by David Peden in this StackOverflow thread (see Option #1). Much appreciated, David.
This works pretty well until you try to generate a NuGet package that has dependencies on other NuGet packages, and in particular if a referenced package has different .Net builds in its lib folder. This problem can be illustrated in Visual Studio. If you change the .Net version of a project that has a NuGet reference to a package that contains specific .Net builds you might see an error.
This is because the reference was created when the NuGet package was added to the project and has a path to the appropriate binaries in the NuGet package. Looking in the .csproj file for the references illustrates this further:
<ItemGroup> <Reference Include="Andy.French.Logging"> <HintPath>..\packages\Andy.French.Logging.1.0.9\lib\net451\Andy.French.Logging.dll</HintPath> </Reference> <Reference Include="System" /> <Reference Include="System.Core" /> <Reference Include="System.Xml.Linq" /> <Reference Include="System.Data.DataSetExtensions" /> <Reference Include="Microsoft.CSharp" /> <Reference Include="System.Data" /> <Reference Include="System.Xml" /> </ItemGroup>
Notice the logging framework reference path is to the net451 folder and therefore binaries built for that .Net version.
The same thing happened when I tried to generate the NuGet packages using David Peden’s approach without modification because it runs separate build steps for the different .Net versions. As a result in some cases the NuGet references were wrong for the step in question.
The Solution
For the time being I have come up with a somewhat hacky solution. It works for now but I am concerned it may not prove particularly maintainable. Time will tell.
The solution involves manually editing the .csproj file to include conditional references, something like this:
<ItemGroup> <Reference Condition="'$(TargetFrameworkVersion)' == 'v4.5.1'" Include="Andy.French.Logging, Version=1.0.1.0, Culture=neutral, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\Andy.French.Logging.1.0.9\lib\net451\Andy.French.Logging.dll</HintPath> </Reference> <Reference Condition="'$(TargetFrameworkVersion)' == 'v4.5'" Include="Andy.French.Logging, Version=1.0.1.0, Culture=neutral, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\Andy.French.Logging.1.0.9\lib\net45\Andy.French.Logging.dll</HintPath> </Reference> <Reference Condition="'$(TargetFrameworkVersion)' == 'v4.0'" Include="Andy.French.Logging, Version=1.0.1.0, Culture=neutral, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\Andy.French.Logging.1.0.9\lib\net40\Andy.French.Logging.dll</HintPath> </Reference> <Reference Include="log4net, Version=1.2.13.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\log4net.2.0.3\lib\net40-full\log4net.dll</HintPath> </Reference> <Reference Include="System" /> <Reference Include="System.Core" /> <Reference Include="System.Xml.Linq" /> <Reference Include="System.Data.DataSetExtensions" /> <Reference Include="Microsoft.CSharp" /> <Reference Include="System.Data" /> <Reference Include="System.Xml" /> </ItemGroup>
When TeamCity runs the build for each target framework the appropriate reference will be used. For more complex sets of references the following approach can be used:
<Choose> <When Condition="'$(TargetFrameworkVersion)' == 'v4.5.1'"> <ItemGroup> <Reference Include="Andy.French.Domain.Driven.Design, Version=1.0.6.0, Culture=neutral, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\Andy.French.Domain.Driven.Design.1.0.6\lib\net451\Andy.French.Domain.Driven.Design.dll</HintPath> </Reference> <Reference Include="Andy.French.Repository, Version=1.0.1.0, Culture=neutral, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\Andy.French.Repository.1.0.6\lib\net451\Andy.French.Repository.dll</HintPath> </Reference> <Reference Include="EntityFramework"> <HintPath>..\packages\EntityFramework.6.1.1\lib\net45\EntityFramework.dll</HintPath> </Reference> <Reference Include="EntityFramework.SqlServer"> <HintPath>..\packages\EntityFramework.6.1.1\lib\net45\EntityFramework.SqlServer.dll</HintPath> </Reference> </ItemGroup> </When> <When Condition="'$(TargetFrameworkVersion)' == 'v4.5'"> <ItemGroup> <Reference Include="Andy.French.Domain.Driven.Design, Version=1.0.6.0, Culture=neutral, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\Andy.French.Domain.Driven.Design.1.0.6\lib\net45\Andy.French.Domain.Driven.Design.dll</HintPath> </Reference> <Reference Include="Andy.French.Repository, Version=1.0.1.0, Culture=neutral, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\Andy.French.Repository.1.0.6\lib\net45\Andy.French.Repository.dll</HintPath> </Reference> <Reference Include="EntityFramework"> <HintPath>..\packages\EntityFramework.6.1.1\lib\net45\EntityFramework.dll</HintPath> </Reference> <Reference Include="EntityFramework.SqlServer"> <HintPath>..\packages\EntityFramework.6.1.1\lib\net45\EntityFramework.SqlServer.dll</HintPath> </Reference> </ItemGroup> </When> <When Condition="'$(TargetFrameworkVersion)' == 'v4.0'"> <ItemGroup> <Reference Include="Andy.French.Domain.Driven.Design, Version=1.0.6.0, Culture=neutral, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\Andy.French.Domain.Driven.Design.1.0.6\lib\net40\Andy.French.Domain.Driven.Design.dll</HintPath> </Reference> <Reference Include="Andy.French.Repository, Version=1.0.1.0, Culture=neutral, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\Andy.French.Repository.1.0.6\lib\net40\Andy.French.Repository.dll</HintPath> </Reference> <Reference Include="EntityFramework"> <HintPath>..\packages\EntityFramework.6.1.1\lib\net40\EntityFramework.dll</HintPath> </Reference> <Reference Include="EntityFramework.SqlServer"> <HintPath>..\packages\EntityFramework.6.1.1\lib\net40\EntityFramework.SqlServer.dll</HintPath> </Reference> </ItemGroup> </When> </Choose> <ItemGroup> <Reference Include="System" /> <Reference Include="System.ComponentModel.DataAnnotations" /> <Reference Include="System.Core" /> <Reference Include="System.Xml.Linq" /> <Reference Include="System.Data.DataSetExtensions" /> <Reference Include="Microsoft.CSharp" /> <Reference Include="System.Data" /> <Reference Include="System.Xml" /> </ItemGroup>
It’s not perfect but it keeps me moving on for now.