Instructions for GitHub Copilot and other AI coding agents working with the MSBuild repository.
MSBuild is the Microsoft Build Engine - performance-critical infrastructure for .NET and Visual Studio builds. This repository contains the code for the MSBuild build engine, including its public C# API, its internal implementation of the MSBuild programming language, and core targets and tasks used for builds.
- Microsoft.Build: Core MSBuild engine and public API
- Microsoft.Build.Framework: Framework interfaces and base types
- Microsoft.Build.Tasks: Built-in MSBuild tasks
- Microsoft.Build.Utilities: Utility classes for task authors
- MSBuild CLI: Command-line tool for invoking builds
- .NET 10.0 and .NET Framework 4.7.2
- C# 13 features (especially collection expressions)
- xUnit with Shouldly for testing
- Multi-platform support (Windows, Linux, macOS)
- Performance is the top priority - minimize allocations, avoid LINQ in hot paths, use efficient algorithms.
- Always use the latest C# features, currently C# 13, especially collection expressions (
[]overnew Type[]). - Match the style of surrounding code when making edits, but modernize aggressively for substantial changes.
Official builds treat all warnings as errors, so do not introduce new warnings.
When reviewing pull requests:
- Flag any unnecessary allocations in hot paths
- Flag LINQ usage in performance-critical code paths
- Check for proper use of
Span<T>andReadOnlySpan<T>for string parsing - Ensure immutable collections use the correct type (
ImmutableArray<T>andFrozenDictionary<TKey, TValue>for read-heavy,ImmutableList<T>for incremental building)
When reviewing pull requests:
- Flag any changes to NuGet.config that add external package sources without justification
- Package sources should use approved internal feeds when possible
- Apply code-formatting style defined in
.editorconfig. - Prefer file-scoped namespace declarations and single-line using directives.
- Insert a newline before the opening curly brace of any code block.
- Use pattern matching and switch expressions wherever possible.
- Use
nameofinstead of string literals when referring to member names.
- New files: Always use nullable reference types (do NOT add
#nullable disable) - Existing files with
#nullable disable: Match the existing style; don't add nullable annotations (?) to types - Existing files with nullable enabled: Use proper nullable annotations
- Always use
is nulloris not nullinstead of== nullor!= null
// GOOD: Clear and efficient
return errorNumber switch
{
>= 3001 and <= 3999 => Category.Tasks,
>= 4001 and <= 4099 => Category.General,
>= 4100 and <= 4199 => Category.Evaluation,
_ => Category.Other
};- Use
MSBuildNameIgnoreCaseComparerfor case-insensitive comparisons of MSBuild names; useStringComparer.OrdinalIgnoreCaseonly for non-MSBuild string comparisons - Use
char.ToUpperInvariant()for single-character comparisons - Use
ReadOnlySpan<char>andSlice()to avoid string allocations - Use
int.TryParse(span, out var result)on .NET Core+ for allocation-free parsing
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsCompilerPrefix(string value) => ...Build once, read many times (most common in MSBuild):
ImmutableArray<string> items = source.Select(x => x.Name).ToImmutableArray();
FrozenDictionary<string, int> lookup = pairs.ToFrozenDictionary(x => x.Key, x => x.Value);Build incrementally over time:
ImmutableList<string> items = ...; // Use when adding items one by one
ImmutableDictionary<string, int> lookup = ...;NEVER pipe MSBuild output to a file. If you want a list of errors, add -flp:"v=q;LogFile=ErrorsAndWarnings.log" to the arguments.
When considering a subset of build errors, always look at the BEGINNING of the set, not the end.
Build individual projects with dotnet msbuild {path/to/project.csproj} -v:q.
| Platform | Command | Timeout |
|---|---|---|
| Windows | .\build.cmd -v quiet |
300+ seconds (~2-3 minutes) |
| macOS/Linux | ./build.sh -v quiet |
300+ seconds (~2-3 minutes) |
After building the whole repo, to use the just-built MSBuild to build things use the bootstrap environment (described in `skills/use-bootstrap-msbuild).
- If build fails with "Could not resolve SDK", run the bootstrap environment script
- Verify
dotnet --versionshows the preview/internal version - Use repository sample projects for testing, not external projects
- Build artifacts go to
./artifacts/directory
Windows:
# Full test suite (~9 minutes) - NEVER CANCEL
.\build.cmd -test
# Individual test project (recommended):
dotnet test src/Framework.UnitTests/Microsoft.Build.Framework.UnitTests.csprojmacOS/Linux:
# Full test suite (~9 minutes) - NEVER CANCEL
./build.sh --test
# Individual test project (recommended):
dotnet test src/Framework.UnitTests/Microsoft.Build.Framework.UnitTests.csprojYou can run a single unit test with --filter {methodname}, for example dotnet test src/Framework.UnitTests/ --filter ExerciseBuildEventContext.
- Individual Test Project: ~10-60 seconds per project
- Full Test Suite: ~9 minutes
src/
├── Build/ # Core MSBuild engine (Microsoft.Build)
├── Build.UnitTests/ # Unit tests for core engine
├── MSBuild/ # MSBuild command-line tool
├── Framework/ # MSBuild Framework (Microsoft.Build.Framework)
├── Framework.UnitTests/ # Unit tests for framework
├── Tasks/ # Built-in MSBuild tasks (Microsoft.Build.Tasks)
├── Tasks.UnitTests/ # Unit tests for tasks
├── Utilities/ # MSBuild utilities (Microsoft.Build.Utilities)
├── Utilities.UnitTests/ # Unit tests for utilities
├── Shared/ # Shared code across assemblies
└── Samples/ # Sample projects and extensions
artifacts/
├── bin/ # Built binaries
│ └── bootstrap/
│ └── core/
│ └── MSBuild.dll # Built MSBuild executable
├── sdk-build-env.sh # Bootstrap script (Linux/macOS)
├── msbuild-build-env.bat # Bootstrap script (Windows)
└── packages/ # Built NuGet packages
documentation/
├── wiki/ # Developer documentation
├── specs/ # Technical specifications
└── *.md # Various documentation files
global.json: Pins .NET SDK version - never modify without explicit requestNuGet.config: Package source configuration - never modify without explicit request.editorconfig: Code formatting rulesDirectory.Build.props: Shared MSBuild properties across all projectsDirectory.Packages.props: Centralized package version managementMSBuild.slnx: Main solution file
Before completing any change:
- ✅ Full build completes successfully (
.\build.cmdor./build.sh) - ✅ Bootstrap environment activates correctly (
dotnet --versionshows preview) - ✅ Sample project builds:
dotnet build src/Samples/Dependency/Dependency.csproj - ✅ Relevant unit tests pass
- ✅
dotnet artifacts/bin/bootstrap/core/MSBuild.dll --helpworks
artifacts/directory contents - Generated during build.dotnet/directory contents - Local SDK location
See Key Configuration Files section for files that should not be modified without explicit request.
When making changes, check if related documentation exists in the documentation/ folder (including documentation/specs/) and update it to reflect your changes. Keep documentation in sync with code changes.
Because MSBuild is a critical part of the build process for a huge number of customers, we avoid breaking changes. Adding new errors or warnings, even when well-intentioned and pointing out things that are very likely to be wrong, is an unacceptable breaking change. Adding warnings is a breaking change because many production builds use /WarnAsError.
The exception to this policy is in new, opt-in behavior. In new, opt-in functionality, liberally emit warnings and errors--they can always be removed later.
When reviewing PRs, always consider whether the behavior change could be experienced as a break in existing builds and flag any new warnings or errors.
- Make your changes to source code
- Run the full build (WAIT for completion - takes 2-3 minutes):
- Windows:
.\build.cmd -v quiet - macOS/Linux:
./build.sh -v quiet
- Windows:
- Test your changes end-to-end using the bootstrap output:
dotnet build src/Samples/Dependency/Dependency.csproj - Run relevant individual tests, not the full test suite
- Commit your changes
These instructions are comprehensive and tested. Only search for additional information if:
- The instructions appear outdated or incorrect
- You encounter specific errors not covered here
- You need details about new features not yet documented
For most development tasks, following these instructions should be sufficient to build, test, and validate changes successfully.
When working on a task, if user input is required to complete the task or feedback is provided for guidance around a specific area of the code, evaluate that feedback/guidance and update this document to incorporate that feedback if it's missing. This document should be a live, evolving set of instructions.