Directory.Build.props - Centralize your builds

13/02/2022
MSBuildC#

The Directory.Build.props works almost like a "normal" csproj file. You can define default dependencies or even apply implicit usings. That is for example very convenient when you have a test project where you might always want to import Moq and XUnit namespace due to your setup.

How does it work?

It is simple. MSBuild will traverse from your current csproj path upwards to file-hierarchy and selects the first More to that later Directory.Build.props it can find and includes it. Have us a look at the following file-structure:

src/
+- Infrastructure/
¦  +- Infrastructure.csproj
+- Tools/
¦  +- Tools.csproj
+- Web/
¦  +- Web.csproj
+- Directoy.Build.props

Under our src there is directly the Directory.Build.props and then we have some folders which contain the "real" projects. Those "real" projects automatically import our Directory.Build.props.

Let us have a look at an example:

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
	<ItemGroup>
      <PackageReference Include="SonarAnalyzer.CSharp" Version="8.35.0.42613">
        <PrivateAssets>all</PrivateAssets>
        <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      </PackageReference>
      <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.406">
        <PrivateAssets>all</PrivateAssets>
        <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      </PackageReference>
    </ItemGroup>

    <ItemGroup>
        <AdditionalFiles Include="$(SolutionDir)\stylecop.json" Link="stylecop.json" />
    </ItemGroup>
    <PropertyGroup>
        <CodeAnalysisRuleSet>$(SolutionDir)\stylecop.analyzers.ruleset</CodeAnalysisRuleSet>
    </PropertyGroup>

    <PropertyGroup>
        <WarningLevel>5</WarningLevel>
        <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    </PropertyGroup>

</Project>

This is taken directly from my blog github repository: here

So in our case Infrastructure.csproj, Web.csproj and Tools.csproj will automatically include SonarAnalyzer.CSharp and StyleCop.Analyzers as nuget package. If I update the version in my Directory.Build.props all of them get automatically updated!

Another example:
Let's say you have the following test setup:

tests/
+- IntegrationTests/
¦  +- IntegrationTest.csproj
+- TestUtilities/
¦  +- TestUtilities.csproj
+- UnitTests/
¦  +- UnitTest.csproj
+- Directory.Build.props

As the nature of tests you will most likely use one test framework throughout all of your tests plus the same mocking framework. I personally love XUnit for various reasons and Moq as mocking library. Therefore I want to use them in all my test projects. So my Directory.Build.props would look like this:

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <ItemGroup>
    <PackageReference Include="FluentAssertions" Version="6.5.1" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0-preview-20220131-20" />
    <PackageReference Include="Moq" Version="4.16.1" />
    <PackageReference Include="xunit" Version="2.4.2-pre.12" />
  </ItemGroup>

  <ItemGroup Label="Implicit usings">
    <Using Include="FluentAssertions" />
    <Using Include="Moq" />
    <Using Include="Xunit" />
  </ItemGroup>

</Project>

Taken from here

Super convenient, isn't it?

Now let's say the Tools project does not need all this includes and stuff or in general: How can I exclude a project?. That is quite easy as we are using the normal syntax we would use in a csproj: Condition. Taking the example from above, let us exclude the TestUtilities.csproj:

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <ItemGroup Condition="$(MSBuildProjectName) != 'TestUtilities'">
    <PackageReference Include="FluentAssertions" Version="6.5.1" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0-preview-20220131-20" />
    <PackageReference Include="Moq" Version="4.16.1" />
    <PackageReference Include="xunit" Version="2.4.2-pre.12" />
  </ItemGroup>

  <ItemGroup Label="Implicit usings" Condition="$(MSBuildProjectName) != 'TestUtilities'">
    <Using Include="FluentAssertions" />
    <Using Include="Moq" />
    <Using Include="Xunit" />
  </ItemGroup>

</Project>

Also very simple. Just add the name of your project without the "csproj" file extension.

Multiple Directory.Build.props

What happens if we have multiple Directory.Build.props?

tests/
+- IntegrationTests/
¦  +- IntegrationTest.csproj
+- TestUtilities/
¦  +- TestUtilities.csproj
+- UnitTests/
¦  +- UnitTest.csproj
+- Directory.Build.props
Directory.Build.props

Now we have another Directory.Build.props one level above the "tests" folder. Well this one will not automatically get included. You have to tell MSBuild it should continue scanning. You can do this by adding this to the inner Directory.Build.props (the one under "tests").

<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />

Conclusion

Directory.Build.props gives you a handy tool the centralize common dependencies and configurations.

Further read here on the Microsoft documentation

3
An error has occurred. This application may no longer respond until reloaded. Reload x