Conditional project or library reference in Visual Studio

In case you were wondering why you haven’t heard from me in a while, I’ve been busy, which isn’t really of much importance unless you know me on a personal level. What is relevant is that I recently graduated with a master in Game and Media Technology and am now in the process of making the project which I have been working on for my thesis open source. I’m very anxious to announce it, so you’ll read a lot more about it in the near future.

The project uses two of my other open source libraries, located in separate repositories. Adding these projects to the solution and referencing them as project references has the advantage of easier debugging and facilitates making changes to them. However, I do not want to force anyone interested in making changes to the main project to having to download the other repositories as well. Therefore I opted to use DLL references. This has one major downside. Whenever I do make changes to one of the dependent libraries, I need to manually copy the newly compiled DLLs to the main project. Wouldn’t it be easy to use two separate solution files, one with project references, and one with DLL references?

The first problem you’ll encounter is project references aren’t stored in the .sln file but in the .csproj file, which makes sense really. Since the .csproj file is shared by both solutions, we’ll have to conditionally reference either our DLLs or our projects, depending on which solution the project is opened in. This is possible using MSBuild, but you will need to create a new project configuration. Setting the configuration of the project differently in one solution from the other allows you to differentiate between the two of them. The following is part of a .csproj file which conditionally loads a reference by checking whether the project configuration is set to ‘Debug With Project References’.

  <Choose>
    <When Condition="'$(Configuration)' == 'Debug With Project References'">
      <ItemGroup>
        <ProjectReference Include="..\SomeProject\SomeProject.csproj">
          <Project>{6CA7AB2C-2D8D-422A-9FD4-2992BE62720A}</Project>
          <Name>SomeProject</Name>
        </ProjectReference>
      </ItemGroup>
    </When>
    <Otherwise>
      <ItemGroup>
        <Reference Include="SomeProject">
          <HintPath>..\Libraries\SomeProject.dll</HintPath>
        </Reference>
      </ItemGroup>
    </Otherwise>
  </Choose>

You could very well stop here, but I wanted to resolve another issue as well. Unless you follow a really strict folder structure, and force everyone else who wants to open your solution to do the same, the path used to reference the project can vary between people. Remember we are using separate repositories here, and the referenced projects aren’t included in the repository of the main solution, so relative paths don’t necessarily work. It is possible to specify the paths in an external ‘configuration’ file, and importing it into the .csproj file. Additionally, as a fallback when the project can’t be found at the given path, it is also useful to load the DLL instead. This way you can choose to load one or more, but not necessarily all references as a project reference.

The configuration file (ProjectReferences.txt):

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup Condition="$(Configuration) == 'Debug With Project References'">
	<SomeProject>..\SomeProject</SomeProject>
  </PropertyGroup>
</Project>

Segment from the .csproj file:

  <Import Project="..\ProjectReferences.txt" />
  <Choose>
    <When Condition="Exists($(SomeProject))">
      <ItemGroup>
        <ProjectReference Include="$(SomeProject)\SomeProject.csproj">
          <Project>{6CA7AB2C-2D8D-422A-9FD4-2992BE62720A}</Project>
          <Name>SomeProject</Name>
        </ProjectReference>
      </ItemGroup>
    </When>
    <Otherwise>
      <ItemGroup>
        <Reference Include="SomeProject">
          <HintPath>..\Libraries\SomeProject.dll</HintPath>
        </Reference>
      </ItemGroup>
    </Otherwise>
  </Choose>

Notice the project configuration check now occurs in the configuration file. The ‘$(SomeProject)’ property is only set when using the ‘Debug With Project References’ configuration, thus in all other scenarios the DLL will be loaded instead since the ‘Exists()‘ check will fail.

One last issue remains. We still need to manually copy the latest compiled DLLs to the ‘Libraries’ folder when changes were made for the solution which uses them to work as expected. We can exploit the fact that we now have the relevant project paths available in the configuration file. Using a post build event you can call a script which parses the XML data, and copies the DLLs to the correct location. This script should be called conditionally, only for the solution which includes the project references, and ideally also only for a Release build. I used a small Ruby script which offers a lot more flexibility than a Batch script.

The post build event:

if "$(SolutionName)" == "SomeSolution With Project References" if "$(ConfigurationName)" == "Release" ruby $(SolutionDir)UpdateLibraryDlls.rb $(SolutionDir)
Advertisements
  1. #1 by Kiko on September 9, 2014 - 12:23 pm

    I have been looking a solution like this for days. Thank you!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: