In a monorepo, every commit or pull request can trigger a full rebuild and test of all projects even when only one small piece changed. Meziantou.DeltaBuild is a .NET CLI tool that solves this problem by analyzing git changes and the MSBuild dependency graph to produce a filtered solution or build file that contains only the affected projects.
It can also help with test sharding by splitting affected test projects across multiple CI agents to reduce total test time.
#Installation
Install it as a global tool:
Shell
dotnet tool install --global Meziantou.DeltaBuild
#How it works
When you run Meziantou.DeltaBuild generate, it performs the following steps:
- Resolve commits: Determines the base and head commits. On GitHub Actions
pull_request events, it automatically reads GITHUB_BASE_REF to compute the merge base, so no extra configuration is required. - Get changed files: Runs
git diff --name-only between the two commits. - Parse the input: Reads the solution, traversal, or project file to discover all projects.
- Build the project graph: Constructs the MSBuild dependency graph using the selected analysis engine.
- Determine affected projects: Marks a project as affected if any of its owned files (source files, imports,
.props, .targets, .editorconfig, NuGet configs, etc.) appears in the changed file list. - Expand transitive dependents: Walks up the dependency graph to include every project that depends on an affected project, directly or indirectly.
- Write output: Produces a filtered solution or build file containing only the final set of affected projects.
Your CI pipeline then builds this smaller file instead of the full solution.
Meziantou.DeltaBuild loads the input using MSBuild APIs, so it understands all the complexities of MSBuild, including SDK-style projects, multi-targeting, conditional imports, and custom build logic. It also supports various output formats (solution files, traversal projects, JSON, text) to fit different build systems.
#Basic usage
The minimal command compares HEAD against the merge base of the default remote branch. On GitHub Actions pull_request events, Meziantou.DeltaBuild automatically uses GITHUB_BASE_REF to detect the base branch, so no --base-commit argument is needed. The --input file can be a solution (.sln or .slnx) or an MSBuild traversal project (.proj). The output format is inferred from the --output extension (solution file, traversal project, JSON, or text).
Shell
Meziantou.DeltaBuild generate --input MyRepo.slnx --output DeltaBuild.slnx
Then build only the affected projects:
Shell
dotnet build DeltaBuild.slnx
dotnet test DeltaBuild.slnx
#Local development
You can also compare uncommitted changes against a branch before committing:
Shell
Meziantou.DeltaBuild generate \
--input MyRepo.slnx \
--output delta.slnx \
--base-commit $(git merge-base origin/main HEAD) \
--working-tree
The --working-tree flag includes staged, unstaged, and untracked files in the analysis.
#Test sharding for parallel CI
Meziantou.DeltaBuild can also split test projects across multiple CI agents to reduce total test time. Use --shard and --total-shards to select a subset of affected test projects:
Shell
# Agent 1
Meziantou.DeltaBuild generate \
--input MyRepo.slnx \
--output delta.proj \
--test-projects-only \
--shard 1 \
--total-shards 3
# Agent 2
Meziantou.DeltaBuild generate \
--input MyRepo.slnx \
--output delta.proj \
--test-projects-only \
--shard 2 \
--total-shards 3
# Agent 3
Meziantou.DeltaBuild generate \
--input MyRepo.slnx \
--output delta.proj \
--test-projects-only \
--shard 3 \
--total-shards 3
The --shard-separate option lets you pin specific projects to different shards. This is useful when you have a slow integration test project that should never share a shard with another slow test project:
Shell
Meziantou.DeltaBuild generate \
--input MyRepo.slnx \
--output delta.proj \
--test-projects-only \
--shard 1 \
--total-shards 3 \
--shard-separate tests/Smoke/Smoke.Tests.csproj \
--shard-separate tests/Integration/Integration.Tests.csproj
Listed projects are spread across shards in declaration order (round-robin), so they end up in separate shards when possible.
#Full and hierarchical rebuild triggers
Some file changes should always trigger a full rebuild regardless of project ownership. Use --full-rebuild-trigger with glob patterns:
Shell
Meziantou.DeltaBuild generate \
--input MyRepo.slnx \
--output delta.slnx \
--full-rebuild-trigger "**/.github/workflows/**"
For files that should only trigger a rebuild within their folder subtree, use --hierarchical-rebuild-trigger. By default, global.json, nuget.config, and .editorconfig are hierarchical triggers. For example, changing src/nuget.config will only rebuild projects under src/.
The output format is inferred from the file extension of --output:
| Extension | Format |
|---|
.sln | Visual Studio solution (v12) |
.slnx | XML-based solution (Visual Studio 2022+) |
.proj | MSBuild Traversal SDK project |
.json | JSON array of affected project paths |
.txt | One project path per line |
#Additional resources
Do you have a question or a suggestion about this post? Contact me!