Remove repeatable build configuration from your project files

Remove repeatable build configuration from your project files

2021, Jan 09    

Quite often, when developing middle-to-large .NET applications or libraries, some part of the build configuration (same NuGet packages, common meta properties) starts to replicate across solution.

An example is the need to connect static analyzers. Usually, analyzers are needed in the whole solution, so it is rather difficult to include them separately in each project (.csproj) file.

In addition to the banal inconvenience, when a new version of the package is released, you must manually update all project files in the solution. The same situation is for removing something common from all projects at once.

Directory.Build.props and Directory.Build.targets

Directory.Build.props / Directory.Build.targets comes to the rescue!

This feature was introduced in MSBuild 15 to allow general build settings to be moved into a separate file that will be applied to all projects within the solution where it is located.

Use case 1: Common author and version

The simplest example is that we have a solution that contains several projects, each of which must have an author and is released under the same version. In this case, you can create a Directory.Build.props file in the root of the solution and add information indicating the author and version:

<!-- Directory.Build.props -->
<Project>

  <PropertyGroup>
    <Version>1.2.3</Version>
    <Authors>John Doe</Authors>
  </PropertyGroup>

</Project>

Use case 2: Global static code analyzing

Another good example is installing StyleCop.Analyzers - a popular static code analysis package:

<!-- Directory.Build.props -->
<Project>

  <PropertyGroup>
    <!-- 1 -->
    <CodeAnalysisRuleset>$(MSBuildThisFileDirectory)MyRules.ruleset</CodeAnalysisRuleset>
  </PropertyGroup>

  <ItemGroup>
    <!-- 2  -->
    <PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
    
    <!-- 3 -->
    <AdditionalFiles Include="$(MSBuildThisFileDirectory)stylecop.json" />
  </ItemGroup>

</Project>

Lets look into each step:

  1. Add a link to the MyRules.ruleset file that contains the setting for the severity of the used analyzers rules
  2. Add StyleCop.Analyzers NuGet package.
  3. Add a link to stylecop.json, which contains the configuration of the StyleCop.Analyzers package itself.

More details on the process of connecting StyleCop.Analyzers with examples of configuration files can be found here.

But how to decide which file (.props or .targets) should we use for our purpose?

According to this issue comment on official Microsoft docs repo:

…there’s a widely-used convention:

  • .props files are imported early in the import order.
  • .targets files are imported late in the build order.

That’s enforced by <Project Sdk="Whatever"> imports (the import of Sdk.props comes first, before all of the contents of the file, then Sdk.targets comes last, after all of the contents of the file). But that just formalized the longstanding convention…

But there is no guarantee that .props files are always imported early and .targets imported lately. It is just a common convention, so do not take it as 100% truth.

My advice is to use Directory.Build.props for setting items, defaults, packages references and Directory.Build.targets to set dependent (on other) properties, build targets, and overrides (to default properties in .props file).

global.json

Placing global.json at solution level allows to unify and customize SDK version used across projects in solution.

It is very useful to strongly strict version that could be used to build and test solution. Sometimes there are bugs in SDK or unwanted behavior of some stuff, but you are sure that specific version and above works quite well.

In such case you could define global.json at the root:

{
  "sdk": {
    "version": "3.1.100",
    "rollForward": "disable"
  }
}

"rollForward": "disable" means that only specified version could be used to build project or solution. This property was introduced in .NET Core 3.0.

Full manual of using global.json files could be found here.

Summary

In the modern .NET world, there are existing opportunities for setting common global settings, allowing you to easily configure complex stuff in a single place, reducing the need for copy-paste to almost zero. There is not much information in the Web on such customization of the build process, so not every developer knows how to use such tools.

I hope, after reading this article, you have a general understanding of what can be achieved with the help of such simple manipulations. More information about all the things considered can be found on the links below.

See also:

If you like my work or want a special kind of technical support for one of the projects I made, you could support me on GitHub! :heart:

Sponsor seclerp