Advanced MSBuild: Adding a ProjectReference sibling project to a PackageReference project

TL;DR: Sometimes changes need to be made throughout a dependency chain in which each link is delivered as separate nuget package. Trying things out during development can be slow and cumbersome with PackageReference. Adding an equivalent project using ProjectReference instead can speed-up the feedback cycle.

This post is part of the Advanced MSBuild series.

Imagine there is a project A that depends on the functionality of a project B.
It consumes project B via a nuget package B.

To implement a new feature, project B needs to be updated with breaking API changes.
You do not know yet how the API will look like in the end, but hope to find out by trying a few things.

Luckily you can easily check out both projects on your local machine.

The fact the project A has a PackageReference on package B complicates things here.
In order to try out an API change, you have to create a new package B, then update project A to consume the new version of package B.
Automated builds and nuget’s wildcard support for preview package versions help, but feedback will still be far from instantaneous.

You could use a tool like NugetReferenceSwitcher.
But then you need to change your project back and forth and be careful not to check the changes into version control.

I propose a different solution where you keep project A and add a sibling project A’ that uses a ProjectReference to project B instead.
In my particular case project A and project B were living in the same repo, so I added project A’ to that repo as well.
If project A and project B live in different repos, you may have to create project A’ in an independent location, possibly with a git submodule for simple access to project B’s source.
Either way the assumption will be that relative file paths between A’ and A as well as A’ and B will be stable enough for project A’ to have a reasonable shelf-life.

We will do the following:

  1. Extract everything except for the PackageReference from project A csproj into an external props file and import that props file into project A.
  2. Create a project A’ that imports the same props file and declares ProjectReference where project A declares PackageReference
  3. Change the build output folders of project A’ so that the two builds do not interfere
  4. Make the code work for both project A and A’ by defining and employing preprocessor directives

1. Extract project A declarations

This should be straightforward.
Cut and paste all XML tags except for the PackageReference for project B below the <Project> tag into a projectA.props file in the same directory as projectA.csproj.
Make sure that all file paths are declared relative to the props file, e. g. use $(MSBuildThisFileDirectory)../some/path instead of ../some/path.
The latter will not work with a project file that lives in another folder.

Then add the import to projectA.csproj.
Your projectA.csproj now looks like this:

<Project Sdk="Microsoft.NET.Sdk" />
  <Import Project="projectA.props" />
  <ItemGroup>
    <PackageReference Include="projectB" Version="1.2.3" />
  </ItemGroup>
</Project>

2. Create the ProjectReference sibling project

Create a new file projectAprime.csproj

<Project Sdk="Microsoft.NET.Sdk" />
  <Import Project="projectA.props" />
  <ItemGroup>
    <ProjectReference Include="../projectB.csproj" />
  </ItemGroup>
</Project>

If the package B version is not source-compatible with the currently checked out code of project B, you will get build errors.
Ignore those for now.

3. Change the build output folder of the sibling project

If you created the sibling project in the same folder as the original project, you need to change the build output folder of at least one of the projects so the builds do not interfere with each other.
This post describes how to do this.

4. Make the code work

If package B is incompatible with project B, but the differences are small in comparison to the API surface used by project A (and A’), you can employ the preprocessor directive technique to maintain source compatibility to both within the same source files.

5. Enjoy

Congratulations!
You can now change project B with instantaneous feedback in project A’ for a nice development workflow while you try out things.
When you are done with the changes in project B, create a package as usual update the package reference in project A.

1 Comment

Comments are closed