msbuild Documentation

The Microsoft Build Engine (MSBuild) is the build platform for .NET and Visual Studio.

5,542 stars C# C# 1 file ยท ~22,721 tokens #build#hacktoberfest#help-wanted#microsoft#msbuild#visual-studio
RAW Doc

1. Project Overview & Quickstart (dotnet/msbuild)

File: README.md

Microsoft.Build (MSBuild)

The Microsoft Build Engine is a platform for building applications. This engine, also known as MSBuild, provides an XML schema for a project file that controls how the build platform processes and builds software. Visual Studio uses MSBuild, but MSBuild can run without Visual Studio. By invoking msbuild.exe on your project or solution file, you can orchestrate and build products in environments where Visual Studio isn't installed.

For more information on MSBuild, see the MSBuild documentation on learn.microsoft.com.

The changelog has detailed information about changes made in different releases.

Building

Building MSBuild with Visual Studio 2022 on Windows

For the full supported experience, you will need to have Visual Studio 2022 or higher.

To get started on Visual Studio 2022:

  1. Install Visual Studio 2022. Select the following Workloads:
    • .NET desktop development
    • .NET Core cross-platform development
  2. Ensure long path support is enabled at the Windows level.
  3. Open a Developer Command Prompt for VS 2022 prompt.
  4. Clone the source code: git clone https://github.com/dotnet/msbuild
  5. Run .\build.cmd from the root of the repo to build the code. This also restores packages needed to open the projects in Visual Studio.
  6. Open MSBuild.slnx or MSBuild.Dev.slnf in Visual Studio 2022.

This newly-built MSBuild will be located at artifacts\bin\bootstrap\net472\MSBuild\Current\Bin\MSBuild.exe. It may not work for all scenarios, including C++ builds.

Building MSBuild in Unix (Mac & Linux)

MSBuild can be run on Unix systems that support .NET Core. Set-up instructions can be viewed on the wiki: Building Testing and Debugging on .Net Core MSBuild

Localization

You can turn on localized builds via the /p:LocalizedBuild=true command line argument. For more information on localized builds and how to make contributions to MSBuild's translations, see our localization documentation

Interested in contributing?

Before you contribute, please read through the contributing and developer guides to get an idea of what kinds of pull requests we accept.

Other ways to contribute

We encourage any contributions you decide to make to the repo!

MSBuild Components

  • MSBuild. Microsoft.Build.CommandLine is the entrypoint for the Microsoft Build Engine (MSBuild.exe).

  • Microsoft.Build. The Microsoft.Build namespaces contain types that provide programmatic access to, and control of, the MSBuild engine.

  • Microsoft.Build.Framework. The Microsoft.Build.Framework namespace contains the types that define how tasks and loggers interact with the MSBuild engine. For additional information on this component, see our Microsoft.Build.Framework wiki page.

  • Microsoft.Build.Tasks. The Microsoft.Build.Tasks namespace contains the implementation of all tasks shipping with MSBuild.

  • Microsoft.Build.Utilities. The Microsoft.Build.Utilities namespace provides helper classes that you can use to create your own MSBuild loggers and tasks.

License

MSBuild is licensed under the MIT license.


File: branding/README.md

Branding

This folder contains MSBuild brand images.

MSBuild-Logo.svg

A vector-based version of the logo that scales to any resolution.

MSBuild-Icon.png

A 40x40px version of the MSBuild logo.

MSBuild-Logo.png

A 256x256px version of the MSBuild logo.

MSBuild-NuGet-Icon.png

A 128x128px version of the MSBuild logo, intended for use in NuGet packages.

The URL https://aka.ms/msbuildicon redirects to this file on github.com. If the above image loads, then the forwarding link works. If not, please update the link.


File: documentation/Contributions/MSBuild-overview.md

Quick intro

MSBuild implements an actual language (... MSBuildian?). For syntax it uses XML. The XML elements and attributes represent keywords, variable names, and expressions in the language. The language is interpreted.

MSBuild has two data structures:

  • properties: single valued, scalars. Like string foo in C#
  • items: multi valued, arrays. Like string[] foo in C#. Except that each array has a name called an item-type, and each element may not only have a value, but also have associated key-value pairs known as metadata.

Typewise, everything is a string in MSBuild.

Executing logic is grouped in Targets. They are like functions in C#. Targets can contain a single type of statement, Tasks. Targets form dependencies between themselves and are executed via a stack, similar to stack based function execution in other languages.

Data structures (properties and items) can be declared either inside or outside targets. There is a single scope in MSBuild, you could call it the static scope. There's no automatic "target scopes". So if you declare a property or an item inside a target, it's still static and visible to the rest of the targets.

Language interpretation parts

The parser, which produces the AST: ProjectParser. The AST top node is the ProjectRootElement

Interpretation happens in two big separate phases.

The first interpretation phase is called project evaluation. It's done by the msbuild Evaluator. The result of evaluation is an object tree similar (but different) to the symbol trees done by compilers. The semantic top nodes are ProjectInstance and Project. You can sort of think of the ProjectRootElement as the syntax API and the Project / ProjectInstance as the semantic API. The big difference in MSBuild is that ProjectInstance/Project also contain the actual interpretation results of MSBuild's state (properties and items) that sits outside targets.

Evaluation does not execute the targets in a project. It only interprets and stores the results of logic outside targets.

The second phase of msbuild interpretation is target execution. This happens in the TargetBuilder. The TargetBuilder uses a stack to execute targets. The initial state is the state contained inside a given ProjectInstance. So targets execute in a stack based manner and mutate the global state inside a ProjectInstance.

What's the difference between Project and ProjectInstance? While both represent evaluated projects, they are intended for different use cases. Project objects are specialized in introspecting / analyzing MSBuild code and also in providing high level project editing operations. ProjectInstance objects are read only. So the objects in the Project tree point back to their corresponding ProjectRootElement AST elements. The objects in the ProjectInstance tree do not point back to the ProjectRootElement elements (so they have a much smaller memory footprint). For example, the Project tree is used by Visual Studio to analyze msbuild projects, and to reflect UI changes all the way down to the XML elements. The TargetBuilder only works with the lighter weight ProjectInstance tree, since it only needs to read state.

MSBuild APIs and components

  • Project / ProjectInstance: entrypoint APIs for working with MSBuild evaluations.
  • BuildManager: entrypoint API for executing targets in MSBuild.
      • Project build stack: RequestBuilder -> TargetBuilder -> TaskBuilder. The RequestBuilder is responsible for evaluating and running targets on a project. The TargetBuilder is responsible for running the target execution stack. The TaskBuilder is responsible for running individual tasks.
  • MSBuild nodes
    • MSBuild distributes work across multiple processes.
    • All processes live on the same machine.
    • Types of nodes:
      • A main node, called the "BuildManager node" or the "Scheduler node". It contains BuildManager instances and its main role is to coordinate the entire build. It's a big state machine that decides when to create other nodes and on which nodes to build projects.
      • Multiple worker nodes whose single responsibility is to build projects. A worker node builds only one project at a time (99% true).
    • The process hosting the single BuildManager node can also host one worker node (called the "inproc node"). This is done for perf reasons, to avoid serialization costs. When the BuildManager node decides to spawn a new Worker node, it creates a new process for it. These are called out of process nodes, or "oop nodes". So Worker nodes can either be inproc nodes (a single one), or oop nodes (multiple).

MSBuild execution modes

  • Building from cmdline (type msbuild.exe)
  • Building from Visual Studio (VS) (pressing ctrl+shift+B, or the context menu Build -> Build Solution)
    • VS implements an object model for an MSBuild dependency graph
    • VS takes over the responsibility of handling the graph dependencies and constructs its own graph
    • It bypasses MSBuild's scheduler and "orders" MSBuild to build a single project at a time, ignoring its references
    • It builds the graph bottom up in reverse toposort order (starting from nodes without references and going up).
    • Fast up to date checks: VS hardcodes relevant project file inputs for each language SDK. (for example it knows that C# projects read global.json). It uses these hardcoded rules to skip building projects. (whereas quickbuild does this in a generic non hardcoded way by observing actual IO inputs and outputs)
  • Build from commandline with static graph (type msbuild.exe /graph)
    • this works just like QB and VS: MSBuild uses its static graph library to find the dependency graph then builds each project node from the graph in toposort order. Unlike QB, it does neither multi machine distribution nor caching

File: documentation/Contributions/README.md

Contributed documentation

This directory has documentation that was contributed by developers or users that hasn't been fully vetted for accuracy and correctness.

Explanations in this folder may be slightly or subtly wrong, but can still be very informative for developing an understanding of MSBuild or a specific problem.


File: documentation/Contributions/solution-parser.md

Solution Parser

Expander

The Expander class is used to expand a value from an expression, which can be a function invocation. These expressions (or embedded properties) have the form "$(Property)".

Expander handles intrinsic functions, which is a list of built-in functions.

MSBuild defines an initial set of properties like MSBuildFrameworkToolsPath32, MSBuildFrameworkToolsPath, etc.

Tools configuration can come from configuration file, registry and current exe path.

Building

Function BeginBuild prepares BuildManager to receive build requests, which:

  • Attaches debugger;
  • Checks that the current build manager's state is idle;
  • Initializes the logging service;
  • Initializes caches;
  • Registers packet handlers.

After this setup, MSBuild creates build request data from project files or project instances. Data for build requests are stored in BuildRequestData objects.

After build data for a request have been prepared, MSBuild executes the build. It may execute restore and/or a graph build instead depending on the configuration. It executes the build in the ExecuteBuild function, which pends the build request, creating an instance of BuildSubmission that represents the build submission.

All build submissions are stored in a dictionary in the BuildManager class. Then they are added to the work queue.

Execution

The work queue dispatches build submissions. Function IssueBuildSubmissionToSchedulerImpl
creates BuildRequestBlocker (blocker) thatโ€™s handled in the HandleNewRequest function that handles new requests coming from nodes. This function iterates over all build requests in blocker and, for a solution build, loads the solution to the configuration.

Solution file parser

Later, it parses the solution file to generate a solution wrapper using methods from the SolutionFile class. First, the parser parses the file header, which should only contain solution file format version.

After that, it parses all remaining lines. Each such line should start with one of the following strings:

  • Project( - calls ParseProject
  • GlobalSection(NestedProject) - calls ParseNestedProjects
  • GlobalSection(SolutionConfigurationPlatforms) - calls ParseSolutionConfiguration
  • GlobalSection(ProjectConfigurationPlatforms) - calls ProjectConfigurationPlatforms
  • VisualStudioVersion - calls ParseVisualStudioVersion

ParseProject parses the first line containing the Project Type GUID, Project name, Relative Path, and Project GUID. Each project type is hardcoded (as defined in SolutionFile). Then it parses all project dependencies. After a project has been parsed, itโ€™s added to the list of parsed projects.

GlobalSection(SolutionConfigurationPlatforms) parses the global configuration, such as Debug|Any CPU = Debug|Any CPU. It adds these configurations to the solution configuration list.

GlobalSection(ProjectConfigurationPlatforms) works pretty much the same as GlobalSection(ProjectConfigurationPlatforms), but it's the global configuration for projects. It adds these configurations to the raw project configuration list.

GlobalSection(NestedProjects) finds parent projects for each given project. It stores the parent GUID in nested projects.

After parsing the solution file, MSBuild processes the project configuration section, updating the project configuration section parsed from a previous solution file.

Solution project generation

Then MSBuild generates an MSBuild project file from the list of projects and dependencies collected from the solution file.

While generating the MSBuild wrapper project for a solution file, MSBuild first collects an ordered list of actual projects then creates a traversal project instance and adds some XML to it. Then it emits a solution metaproject (if it was specified) from that traversal project. It does not write this project to disk unless specified. Finally, it builds the metaproject.


File: documentation/deprecated/Building-Testing-and-Debugging-on-Mono-MSBuild.md

MSBuild can be successfully built on Windows, OS X 10.11, Ubuntu 14.04, and Ubuntu 16.04.

Mono maintains a fork of msbuild (for now) at https://github.com/mono/msbuild/. You can clone that and use the xplat-master branch or mono-2018-04 for the next release branch.

Unix

Required packages for OSX & Ubuntu

MSBuild requires a stable version of Mono to build itself.

Build process

make

If you encounter errors, see Something's wrong in my build

Tests

make test-mono

Installing

./install-mono-prefix.sh </your/mono/prefix>

Getting Mono MSBuild binaries without building the code

The best way to get Mono MSBuild for OSX/macOS is to get the official Mono package. After installing it, you can run msbuild.

For Linux, you can install mono and msbuild from here.

Debugging

See debugging options here.


File: documentation/specs/BuildCheck/BuildCheck.md

BuildCheck - Design Spec

Previously known as "warning waves" and "MSBuild Checks"

The feature is meant to help customers to improve and understand quality of their MSBuild scripts via rules violations reporting. It will allow MSBuild to gradually roll out additional rules, as users will be capable to configure their opt-in and severity of reports โ€“ preventing unwanted build breakages. And to equip powerusers to roll out their own quality checks โ€“ whether for general community or internal enterprise usage.

Terminology

  • Check โ€“ unit of execution (single implementing class), can host multiple rules.
  • Rule โ€“ Single violation type, with single unique code (โ€œBC1234: Redefining built-in targetโ€).
  • Report โ€“ Output from check informing about violating particular rule.
  • CodeFix โ€“ Violation remedy suggestion. Not yet applicable for MSBuild.
  • BuildCheck - Feature name. The infrastructure within MSBuild allowing pluggability and execution of checks and their Rules

North Star / Longer-term vision

MSBuild provides a rich object model (further just OM) exposing representation of the build scripts (unstructured and structured model of documents contributing to the build), build data (the definition and evaluated values of MSBuild primitives) and build execution (the eventing model of inputs, processing and outputs of the orchestrated execution) so that various quality checking rules can be authored. This includes static check rules (e.g. checking validity of condition expressions) as well as build execution rules (e.g. checking of referencing nonexistent files) and composition rules (e.g. unintended outputs overwrites checking). Identical OM is exposed from live build execution and via post-build log event sourcing โ€“ so that users can choose whether the build check will happen as part of the build or as a separate process.

Users are able to tune the behavior of the checks via .editorconfig which brings unified and standardized experience with other tooling (including built-in and third-party C# checks) leveraging .editorconfig files.

Powerusers are able to develop, test and publish their custom checks easily and contribute them back to community. The local development scenario doesnโ€™t require roundtrip through packaging.

A solid set of in-the-box checks is provided by MSBuild and the .NET SDK, extended each release, with high quality reports (pointing exact locations of issue, offering clear and actionable explanations, not repetitive for builds with multi-execution or/and multi-importing of a same script in single build context). The existing in-the-box checks are gradually enabled by default and their severity increased - in waves (likely tied to sdk releases) - aiming to constantly increase the quality of our customers build scripts. To avoid breaking customers builds, there will still be an explicit user gesture required to opt into running the check. This will be done either by configuring the checks with .editorconfig or auto-enabling the check based on the TFM of the project. There will be no difference between building with dotnet build and with MSBuild.exe, they will follow the same enablement rules with the set of enabled built-in checks derived from .editorconfig and TFM/props. Building in Visual Studio will eventually reach parity with command-line build as well.

Projects that don't use the .NET SDK and those that are not SDK-style at all are TBD. There is a possibility of using a property like MSBuildCheckLevel to enable some base checks we believe will add value everywhere.

The check has small impact on build duration with ability to disable check altogether which will remove all the performance costs associated with the check. The perf impact on representative projects is continuously monitored and documented by the MSBuild team.

Scope of initial iteration

Majority of following cases are included in appropriate context within the scenarios in User Experience section. Following is a quick overview.

In scope

  • Inbox (built-in) checks that run during the build execution.
  • Inbox checks that run when replaying binlog.
  • Custom authored checks, delivered via nuget.
  • Checks reports (errors, warnings, messages) are in logger output, VS error window.
  • Codes will be distinguishable from standard build warnings/error (to prevent easy mixups and attempts to configure standard msbuild warnings/errors via editorconfig), but otherwise the outputs are very similar.
  • Default opt-ins and levels for inbox checks set by sdk version (via $SdkAnalysisLevel) or other agreed mechanism for controlling increasing strictness between .NET versions.
  • Custom checks opted in via PackageReference of a particular nuget with the check.
  • Explicit overrides of enablement and check levels via .editorconfig file (with up to a per-project scope).
  • Specification of .editorconfig will be observed.
  • Simplified authoring experience via template and doc.
  • Single check can produce reports for multiple rules. However those need to be declared upfront.
  • Opt-in reporting of time spent via specific checks and infra overall.
  • Collect touched .editorconfigs into binlog embedded files.
  • Possibility to opt-out from check - the perf should not be impacted when done so.
  • Team collects performance impact numbers on a set of benchmark builds with the inbox checks enabled.

Non Goals, but subject for consideration

  • Custom check in a local project (source codes) or a binary.
  • Bulk configuration of multiple rules (based on prefixes).
  • Specifying scope of MSBuild imports that will be considered for check (so that e.g. data from sdk won't even be passed to check, if not requested).
  • Attempts to try to configure standard msbuild warnings/errors via .editorconfig should lead to fail fast errors.
  • Configuring BuildCheck levels when checking from binlog - beyond the collected editorconfigs
  • Structured information in VS error window (similarly to the Roslyn check reports - reports have titles, details, locations, searchable codes and exposed links leading to detailed documentation).

Out of scope

  • Instrumentation for telemetry.
  • Design time build check.
  • Localization support (for reports message formats, identifiers, etc.).
  • Custom checks have equal data access as the inbox checks. We'll aim to ship checks that use public BuildCheck API/OM surface. But for extra agility we might chose to implement and ship some checks using unexposed data.
  • All inbox checks reports have precise location(s) of issues (however for each individual check not providing precise location we should have a very strong reason, why location cannot be provided and why it still brings value even without precise location).
  • Opt-out of BuildCheck on code-level (something like C# #pragma warning disable, but within msbuild xml files).
  • Simplified authoring experience via dedicated reference assembly.
  • Restore phase check.
  • Turning check off/on based on target (e.g. multi-targeted builds, calling MSBuild task etc.).
  • Controlling/Configuring lifetime of checks - checks will currently be held alive, as single instance per check, for the whole duration of the build. But future versions might prevent some of the checks to survive beyond a scope of a single project built (means for sharing data would be provided).
  • Event Tracing for Windows (ETW) for checks.
  • Attributing .editorconfig configurations to .sln files. E.g.:
ini
# I expect this to apply to all projects within my solution, but not to projects which are not part of the solution
[ContosoFrontEnd.sln]
build_check.BC0101.Severity=warning
  • Attributing .editorconfig configurations to lower granularity than whole projects. E.g.:
ini
# I expect this to apply only to a scope of the imported file. Or possibly I expect this to apply to all projects importing this project.
[ContosoCommonImport.proj]
build_check.BC0101.Severity=warning
  • Respecting .editorconfig file in msbuild import locations (unless they are in the parent folders hierarchy of particular project file).
  • CodeFixes are not supported in V1

User Experience

Running / UX

Inbox Checks

Suggested list of checks to be shipped with V1: https://github.com/dotnet/msbuild/issues/9630#issuecomment-2007440323

The proposed initial configuration for those is TBD (as well based on initial test runs of the checks of chosen public repositories).

Live Build

BuildCheck will run as part of the build and execute inbox checks and custom checks based on the configuration. Users will have an option to completely opt-out from BuildCheck to run via an MSBuild property (could be set in a project file or passed on the commandline).

Findings - reports - of checks will be output as build messages/warnings/errors, and the message/warnings/error code should help distinguish BuildCheck produced reports from regular build errors/warnings.

BuildCheck reports will have power to fail the build (via errors or warnings), that would otherwise succeed without the BuildCheck. This is actually the main benefit of the feature - as it helps enforcing new rules, that are easily user configurable individually or as a whole feature - to prevent unwanted breakages of legacy builds not ready for improvements.

Binlog Replay mode

Users will have option to explicitly opt-in to run BuildCheck during the binlog replay mode:

bash
> dotnet build msbuild.binlog /check

Would there be any checks that are not possible to run during the replay mode (subject to internal design - this difference won't be exposed during custom checks authoring), replay mode will inform user about those via warnings.

Replay mode will by default consider .editorconfig files stored within the binlog and will run checks based on those. This would possibly lead to unintended double-reports โ€“ as binlog will have the runtime check reports stored, plus the replay-time check reports will be augmented. At the same time we might want to run some additional checks in the replay mode, that have not been enabled (or not even available) during the build time.

For this reason we will consider following modes (all are non-goals):

  • All binlog stored reports are skipped by default. We add option to request not skipping them (but they might need to be prefixed or otherwise distinguished from the 'fresh' reports).
  • Ability to specify skipping of the stored .editorconfig files
  • Ability to specify single replay-time .editorconfig file and itโ€™s precedence (only the specified, specified as most significant, specified as least significant)

We might as well consider specifying custom checks on a command line (as a non-goal) - so that unreferenced custom checks can be run against the binlog.

Configuration

There will be 3 mechanisms of configuring the checks and rules:

We will also consider respecting SdkAnalysisLevel to override the per-TFM defaults. Additionally, we may introduce a new "master switch" property, tentatively called RunMSBuildChecks, to make it possible to disable everything whole-sale. This would be used in scenarios like F5 in VS.

text
Skipping checks to speed up the build. You can execute 'Build' or 'Rebuild' command to run checks.

Here's the proposed release schedule:

  • .NET 9 - the feature is introduced and enabled in dotnet build and MSBuild.exe command-line builds. It is not enabled in VS just yet. No checks are enabled by default. It is not technically required to read the TFM or any other props during evaluation, though it would be nice to respect RunMSBuildChecks already in this release. .editorconfig can be the sole source of configuration.
  • .NET 10 - based on feedback and testing, we choose a set of checks to enable by default for projects targeting net10.0, and enable the feature as a whole in Visual Studio. Depending on how we feel about the perf characteristics of evaluation, especially the "double evaluation" mandated by discovering evaluation-tracking checks just-in-time, we may want to omit such checks from the default set.
  • .NET 11 and beyond - some more checks are enabled for projects targeting net11.0, the net10.0 does not change. Everything is mature and performant enough that we are able to auto-enable any check. The gradual tightening of rules enabled by default gets us closer to the long envisioned "strict mode", which will ultimately allow us to evolve MSBuild to be simpler and more performant.

For the .editorconfig file configuration, following will apply:

  • Only .editorconfig files collocated with the project file or up the folder hierarchy will be considered.
  • .editorconfig files placed along with explicitly or implicitly imported msbuild files wonโ€™t be considered.
  • .editorconfig files packaged within nuget packages within local nuget cache wonโ€™t be considered.

Non-Goals (but might be considered):

  • bulk configuration of multiple rules - based on checks/rules prefixes or/and categories.
  • attempts to try to configure standard msbuild warnings/errors via .editorconfig should lead to fail fast errors.
  • configuring check levels when checking from binlog - beyond the collected editorconfigs.
  • Aliasing the checks/rules, allowing to create multiple instances with different custom configuration (e.g. single check checking configurable list of forbidden properties prefixes can have 2 instance, each initialized with different list to check, each of the instance configurable for individual projects separately).

Out of scope for configuration:

  • opt-out of check on code-level (analogy to C# pragmas, but within msbuild xml files).
  • lower granularity of .editorconfig settings other than whole projects.
  • attributing configuration to a .sln file and expecting it will apply to all contained projects.
  • Support for multiple custom configurations within a single build for a single rule. (Not to be mixed with standardized configuration - which can be configured freely per project) If a custom configuration will be used, it will need to be specified identically in each explicit configurations of the rule. This is chosen so that there are no implicit requirements on lifetime of the check or check instancing โ€“ each check will be instantiated only once per build (this is however something that will very likely change in future versions โ€“ so authors are advised not to take hard dependency on single instance policy).

Sample configuration

ini
[*.csproj]
build_check.BC0101.severity=warning

build_check.COND0543.severity=none
build_check.COND0543.scope=project_file
build_check.COND0543.custom_switch=QWERTY

User Configurable Options

Initial version of BuildCheck plans a limited set of options configurable by user (via .editorconfig) by which users can override default configuration of individual check rules.

NOTE: The actual naming of the configuration options is yet to be determined.

Severity levels

Option Severity with following values will be available:

Severity EditorConfig option
Default default
None none
Suggestion suggestion
Warning warning
Error error

Severity levels are in line with roslyn analyzers severity levels. Default severity in .editorconfig will lead to using build-in severity from the check (so this can be used for clearing custom severity setting from higher level .editorconfig file). Default severity in the build-in code has same effect as if the code doesn't specify severity at all - an infrastruture default of None is considered.

Configuration will dictate transformation of the check report to particular build output type (message, warning or error).

Each rule has a severity, even if multiple rules are defined in a single check. The rule can have different severities for different projects within a single build session.

If all the rules from a single check have severity None - check won't be given any data for such configured part of the build (specific project or a whole build). If check have some rules enabled and some disabled - it will be still fed with data, but the reports will be post-filtered.

Configuring severity level

ini
[*.csproj]
build_check.BC0101.severity=warning

Scope of Check

Option EvaluationCheckScope (just scope in .editorconfig) with following possible options will be available:

EvaluationCheckScope (scope) EditorConfig option Behavior
ProjectFileOnly project_file Only the data from currently checked project will be sent to the check. Imports will be discarded.
WorkTreeImports work_tree_imports Only the data from currently checked project and imports from files not recognized to be in nuget cache or SDK install folder will be sent to the check. Other imports will be discarded.
ProjectWithAllImports all All data will be sent to the check.

Same rule can have EvaluationCheckScope configured to different values for different projects. If check has multiple rules (this is e.g. case of PropertyUsageCheck rules - BC0201, BC0202 and BC0203) - those can have the EvaluationCheckScope set to distinct values.

Currently the proper filtering of data is at the discretion of the Check - as the infrastructure might not be able to decide what can be considered in scope (e.g. in case of BC0203 - "Property declared, but never used" - the property writes (definitions) are scoped, but reads (usages) are not, while BC0201 "Usage of undefined property" needs to scope reads, but not writes (definitions). Identical input data need to be scoped differently based on the meaning of the Check).

Some checks might completely ignore the EvaluationCheckScope setting - as they can operate on data, that are sourced from build execution (as opposed from build evaluation) and hence the scoping is not possible. This is e.g. case of BC0102 "Double Writes" check.

Configuring evalution scope

ini
[*.csproj]
build_check.BC0101.scope=all

Checks and Rules Identification

TBD

  • Recommended and reserved prefixes
  • Short vs descriptive names
  • Rules categories
  • Ability to use prefixes during configuration

Custom Checks Authoring

Implementation

To author custom check, user will need to implement given contract (delivered in Microsoft.Build package). The contract will provide access to the exposed BuildCheck OM focused on build check.

Check declaration

Simplified proposal:

csharp
public abstract class Check : IDisposable
{
    /// 
    /// Friendly name of the check.
    /// Should be unique - as it will be used in the tracing stats, infrastructure error messages, etc.
    /// 
    public abstract string FriendlyName { get; }

    /// 
    /// Single or multiple rules supported by the check.
    /// 
    public abstract IReadOnlyList<CheckRule> SupportedRules { get; }

    /// 
    /// Optional initialization of the check.
    /// 
    /// 
    /// Custom data (not recognized by the infrastructure) passed from .editorconfig
    /// Currently the custom data has to be identical for all rules in the check and all projects.
    /// 
    public abstract void Initialize(ConfigurationContext configurationContext);

    /// 
    /// 
    /// 
    /// 
    public abstract void RegisterActions(IBuildCheckRegistrationContext context);

    public virtual void Dispose()
    { }
}

The context in RegisterActions call will enable subscriptions for data pumping from the infrastructure.

Sample of how registrations might look like:

csharp
public interface IBuildCheckRegistrationContext
{
    void RegisterEvaluatedPropertiesAction(Action> evaluatedPropertiesAction);

    void RegisterParsedItemsAction(Action> parsedItemsAction);

    // ...
}

The data provided in callbacks for registered actions will allow the check to submit reports for its rules. A single callback can lead to multiple reports being generated.

Any check will be allowed to produce reports only for Rules that it declared in itโ€™s SupportedRules definition.

Rules declaration

A single check can declare support of multiple rules โ€“ since it might produce reports for those on top of same input data โ€“ and for efficiency reasons a single processing of data might be needed.

Simplified proposal of definition of a single rule:

csharp
public class BuildExecutionCheckRule
{
    // Identification/Description fields
    // (To be defined more precisely by https://github.com/dotnet/msbuild/issues/9823)

    /// 
    /// The default configuration - overridable by the user via .editorconfig.
    /// If no user specified configuration is provided, this default will be used.
    /// 
    public BuildExecutionCheckConfiguration DefaultConfiguration { get; }
}

Each rule will supply its default configuration (mainly enablement and report severity) โ€“ those will apply if .editorconfig file will not set those settings explicitly. If the rule doesn't provide (some of) its defaults, a global hardcoded default is used (severity: message, enabled: false).

Standardized configuration declaration

Proposal of configuration specification:

csharp
/// 
/// Configuration for a build check.
/// Default values can be specified by the check in code.
/// Users can overwrite the defaults by explicit settings in the .editorconfig file.
/// Each rule can have its own configuration, which can differ per each project.
/// The <see cref="EvaluationCheckScope"/> setting must be same for all rules in the same check (but can differ between projects)
/// 
public class BuildExecutionCheckConfiguration
{
    /// 
    /// This applies only to specific events, that can distinguish whether they are directly inferred from
    ///  the current project, or from some import. If supported it can help tuning the level of detail or noise from check.
    ///
    /// If not supported by the data source - then the setting is ignored
    /// 
    public EvaluationCheckScope? EvaluationCheckScope { get; internal init; }

    /// 
    /// The default severity of the result for the rule. May be overridden by user configuration.
    ///
    /// If all rules within the check are `none`, the whole check will not be run.
    /// If some rules are `none` and some are not, the check will be run and reports will be post-filtered.
    /// 
    public BuildExecutionCheckResultSeverity? Severity { get; internal init; }
}

Values for this recognized contract, that are explicitly specified via .editorconfig files are passed both to the BuildCheck infrastructure as well as individual checks.

Custom configuration declaration

However if user will specify additional โ€“ unrecognized - values in .editorconfig file as part of a particular check configuration โ€“ those values will be extracted as key-value pairs and passed to the check initialization call (Initialize) via ConfigurationContext:

csharp
/// 
/// Holder of an optional configuration from .editorconfig file (not recognized by the infrastructure)
/// 
public class ConfigurationContext
{
    /// 
    /// Custom configuration data - per each rule that has some specified.
    /// 
    public IReadOnlyList<CustomConfigurationData> CustomConfigurationData { get; init; }

    /// 
    /// Configuration data from standard declarations
    /// 
    public IReadOnlyList BuildExecutionCheckConfig { get; init; }
}

This can allow creation of extendable checks โ€“ e.g. a check that will validate that properties defined within project do not start with any forbidden prefix, while actual prefixes to check are configurable โ€“ so the user of the check can tune the behavior to their needs.

More details on configuration are in Configuration section.

Compatibility

All the publicly exposed contracts will be available within Microsoft.Build.Experimental.BuildCheck namespace. The namespace is expressing that contracts are not guaranteed to be backward compatible (however breakage will be limited to necessary cases). The availability of particular set of BuildCheck API will be queryable via Feature Query API:

csharp
var availability = Features.CheckFeatureAvailability("BuildCheck.Beta");

This way the checks authors will be equipped to write highly-compatible checks even in a possibility of changing API.

Testing and Debugging

TBD

We aim to provide ability to locally test checks from local projects or assemblies without a need to roundtrip through packaging them. The exact way is yet to be determined.

At the same time we aim to provide mocks providing the BuildCheck context data โ€“ this work is however a non-goal.

Packaging

Several requirements are mandated for check packages to be properly recognized (Netstandard only, A call to designated property function will need to be part of the packaged build assets, dependencies will need to be packaged, binaries structure flattened). There might as well be couple of optional practices making the check package more resources savvy (E.g. defining the rule ids and enablement status within the mentioned property function - so that such information doesn't require loading and calling of the check type).

Also custom check package is a dependency is a purely development time harness - so it should be marked as DevelopmentDependency.

In order to simplify the packaging process (and meeting above mentioned requirements) a dotnet template will be provided producing proper package on pack action.

TBD - dotnet new sample on initiating the development.

Acquisition of custom checks

Apart from inbox checks (shipped together with msbuild), users will be able to plug-in packaged checks shipped as nugets (this will serve for community contributions, but possibly as a venue for off-cycle distribution of official checks).

In order to use an check package users just need to reference them via `` element as standard package reference.

xml

Only projects referencing the package will be able to run its checks. Enabling the rules from package on other projects wonโ€™t take any effect.


File: documentation/specs/BuildCheck/BuildCheck-Architecture.md

BuildCheck - Architecture and Implementation Spec

This is an internal engineering document. For general overview and user point of view - please check the BuildCheck - Design Spec.

Areas of Ownership

Area Owner
PM @baronfel
Advisory/Leadership @rainersigwald
Infrastructure @SimaTian
Configuration @JanProvaznik
Custom Checks @YuliiaKovalova
Inbox Checks @YuliiaKovalova
Replay Mode @surayya-MS
Tracing @surayya-MS
Perf Advisory @AR-May

Infrastructure and Execution

Data Source

The major source of data for BuildCheck will be the BuildEventArgs data - as it is already well established diagnostic source for MSBuild builds.

BuildCheck can source this data either offline from the binlog, or as a plugged logger during the live build execution. Choice was made to support both modes.

The actual OM exposed to users will be translating/mapping/proxying the underlying MSBuild OM and hence the implementation details and actual extent of the data (whether internal or public) will be hidden.

Sourcing unexposed data from within execution

For agility we'll be able to source internal data during the evaluation and/or execution directly from the build engine, without the BuildEventArgs exposure.

To simplify the switch between 'in-node direct data' and 'properly remoted data that can be exposed via public OM' we will internally expose execution data consuming interface - that will be implemented by LoggingService as well as BuildCheck infrastructure. This way we can have a simple toggle logic in LoggingService that will either forward data to BuildCheck infrastructure (in-proc) or translate them to appropriate BuildEventArgs and send them via logging infrastructure.

The following diagram ilustrates the case where data are being consumed directly in node - minimizing the performance and resources impact:

Once the implemented BuildCheck will solidify the idea of need for specific data, those can then be remoted via logging infrastructure and then publicly exposed after translation in BuildCheck central logger connector:

In both cases the sink for the data is actualy a LoggingContext - this is to ensure a valid BuildEventContext for all data that might be consumed by BuildCheck. BuildCheck needs to know the currently build project path (for proper configuration of the rules and proper reporting) - for this reasen we need to ensure passing of BuildEventContext with all data.

Sample in-node data case - evaluated project

One example of rich data that might be helpful for internal checks is Project. This OM is not currently being used during the standard build execution (ProjectInstance is used instead) - but we can conditionaly create and expose Project and satisfy the current internal consumers of ProjectInstance - spike of that is available in experimental branch.

Execution Modes

Replay Mode - so that users can choose to perform checks post build, without impacting the performance of the build. And so that some level of checking can be run on artifacts from builds produced by MSBuild from NET 9.0 / VS 17.12 or newer. The older versions won't be supported.

Live mode - this is what users are used to from compilation checks. Integrating into build execution will as well help driving adoption by opting-in users by default to some level of checking and hence exposing them to the feature.

Live Mode Hosting

Prerequisites: MSBuild Nodes Orchestration

The BuildCheck infrastructure will be prepared to be available concurrently within the scheduler node as well as in the additional worker nodes. There are 2 reasons for this:

  • BuildCheck will need to recognize custom check packages during the evaluation time - so some basic code related to BuildCheck will need to be present in the worker node.
  • Presence in worker node (as part of the RequestBuilder), will allow inbox checks to agile leverage data not available within BuildEventArgs (while data proven to be useful should over time be exposed to BuildEventArgs)

Replay Mode

Prerequisites: MSBuild Replaying a Binary Log

When replaying a binary log, we can apply BuildChecks with help of -check switch:

text
> msbuild msbuild.binlog -check

If BuildCheck is enabled, then the events from BinaryLogReplayEventSource and new events from BuildCheck are merged into the IEventSource, from which the loggers get events.

mermaid
flowchart TD
    replayEventSource[BinaryLogReplayEventSource replayEventSource] --> mergedEventSource[IEventSource mergedEventSource]
    replayEventSource[BinaryLogReplayEventSource replayEventSource] --> BuildCheckBuildEventHandler[BuildCheckBuildEventHandler]
    BuildCheckBuildEventHandler[BuildCheckBuildEventHandler] --> mergedEventSource[IEventSource mergedEventSource]
    mergedEventSource[IEventSource mergedEventSource] --> loggers
  1. The events from BinaryLogReplayEventSource replayEventSource are passed to the IEventSource mergedEventSource unchanged.
  2. The events from BinaryLogReplayEventSource replayEventSource are passed to BuildCheckBuildEventHandler in order to produce new events from BuildCheck.
  3. The BuildCheckBuildEventHandler uses the IEventSource mergedEventSource to invoke new events.

BuildCheckBuildEventHandler is an internal infrastructural class and serves as an entry point for producing new events from BuildCheck.

Handling the Distributed Model

We want to get some benefits (mostly inbox checks agility) from hosting BuildCheck infrastructure in worker nodes, but foremost we should prevent leaking the details of this model into public API and OM, until we are sure we cannot achieve all goals from just scheduler node from BuildEventArgs (which will likely never happen - as the build should be fully reconstructable from the BuildEventArgs).

How we'll internally handle the distributed model:

  • Each node will have just a single instance of infrastructure (IBuildCheckManager) available (registered via the MSBuild dependency injection container - IBuildComponentHost). This applies to a scheduler node with inproc worker node as well.
  • Scheduler node will have an MSBuild ILogger registered that will enable communicating information from worker nodes BuildCheck module to the scheduler node BuildCheck module - namely:
    • Acquisition module from worker node will be able to communicated to the scheduler node that it encountered PackageReference for particular check and that it should be loaded and instantiated in the main node.
    • Tracing module will be able to send perf stats from current worker node and aggregate all of those together in the main node.
    • Theoretical execution-data-only sourcing inbox check will be able to aggregate data from the whole build context (again - we should use this only for agility purposes, but shoot for check that needs presence only in scheduler node). The way to do that can be via being present in all worker nodes, sending a specific type of 'in progress result' BuildEventArgs and aggreggating those intermediary results in the single instance running in the main node.
  • Apart from the scenarios above - the BuildCheck infrastructure modules in individual nodes should be able to function independently (namely - load the inbox checks that should live in nodes; send the checks reports via logging infrastructure; load user configuration from .editorconfig and decide on need to enable/disable/configure particular checks).
  • The custom checks will be hosted only in the main node - and hence the distributed model will be fully hidden from them. This might be a subject for revision in future versions.
  • Communication from main to worker node between BuildCheck infra modules is not planned (this might be revisited - even for the V1).

Check Lifecycle

Planned model:

  • Checks factories get registered with the BuildCheck infrastructure (BuildCheckManager)
    • For inbox checks - this happens on startup.
    • For custom checks - this happens on connecting ILogger instance in scheduler node receives acquistion event (BuildCheckAcquisitionEventArgs). This event is being sent by worker node as soon as it hits a special marker (a magic property function call) during early evaluation. Loading is not processed by worker node as currently we want custom checks only in the main node (as they will be only given data proxied from BuildEventArgs). Loading in worker node in Evaluation context would result in double work as the custom Check needs to be loaded in the main node anyways.
      The BuildCheckAcquisitionEventArgs should be sent prior ProjectEvaluationStartedEventArgs (buffering will need to take place), or main node will need to replay some initial data after custom check is registered.
  • BuildCheckManager receives info about new project starting to be build
    • On scheduler node the information is sourced from ProjectEvaluationStartedEventArgs
    • On worker node this is received from RequestBuilder.BuildProject
  • BuildCheckManager calls Configuration module and gets information for all checks in it's registry
    • Checks with issues in configuration (communicated via BuildCheckConfigurationException) will issue an error and then be deregistered for the rest of the build.
    • Global configuration issue (communicated via BuildCheckConfigurationException) will issue an error and then entirely disable BuildCheck.
  • BuildCheckManager instantiates all newly enabled checks and updates configuration for all already instantiated checks.
  • At that point of time checks are prepared for receiving data and performing their work. MSBuild will start calling BuildCheckManager callbacks (mostly pumping BuildEventArgs), passed data will be translated into BuildCheck OM and passed to checks.
  • Checks may decide to report results of their findings (via BuildCheckDataContext.ReportResult), the infrastructure will then perform post-processing (filter out reports for Rules that are disabled, set the severity based on configuration) and send the result via the standard MSBuild logging infrastructure.
  • Checks result might hence be reported after project's final ProjectFinishedEventArgs
  • Final status of the build should not be reported (and BuildFinishedEventArgs logged) until all checks are done processing and their results are accounted for.

Configuration

Configuration implmentation details.

High-level logic

MSBuild engine always finds and parses relevant .editorconfig files to see which checks should be enabled, if any. In typical builds this operation will not be expensive compared to everything else happening as part of building a project. It's a new cost to all builds though, and in the unlikely case that it starts showing as a perf bottleneck, we can cache the data relevant to MSBuild in a separate intermediate file, in a process-wide in-memory cache invalidated by timestamp checks, and so on.

The rest of the configuration comes directly or indirectly from project files in the form of properties, which creates an interesting ordering issue. For the engine to know the final values of properties such as TargetFramework, it needs to evaluate the project. However, if it turns out that an checks should be enabled that is interested in tracing the evaluation, it is already too late. It's important to note that this issue exists only for a subset of checks. Checks interested in the result of evaluation, for example, are fine. The best way of handling this would be to simply evaluate again. Technically, we only need to finish Pass 1 of evaluation to know the value of properties and have the relevant property functions called, so the extra work can be limited to pass 0 and 1. Measurements show that 75% of evaluation is spent in passes 0 and 1. In the very worst case when an extra pass 0/1 runs for each project and lacks any kind of further optimization, single-process incremental build of OrchardCore has been measured to take about 5% longer. There are opportunities for optimizing this, for example by adding a marker to SDK targets files notifying MSBuild of the point after which TargetFramework is expected to be fixed so the engine can bail early.

Once TargetFramework is known, we can combine the default checks config with what came from .editorconfig, restart evaluation if an evaluation-tracing check just got enabled, and continue with the rest of the build. Unlike TargetFramework which has an additive effect on the enabled checks, a master switch like RunMSBuildChecks may instruct the engine to not run anything. It would be unfortunate if the user had to pay any extra cost when RunMSBuildChecks evaluates to false. So preferrably we don't do anything perf sensitive until we hit the point in evaluation when TargetFramework, RunMSBuildChecks, SdkAnalysisLevel, ... are all known and the set of checks to use is finalized.

Since we are unlikely to enable any checks by default in .NET 9, the focus in this release should be on optimizing the .editorconfig handling.

Acquisition

(For details on internals of processing acquisition by the infrastructure see Check Lifecycle)

BuildCheck employs two distinct types of checks: inbox and custom. As a result, the acquisition and distribution processes vary.
Inbox rules are integrated into the MSBuild repository, while custom checks can be packaged as NuGet packages and detected by MSBuild provided they adhere to a specific structure.

To streamline the creation of custom rules, a special template has been introduced.
To enable MSBuild to recognize a custom check, it must invoke the intrinsic function void RegisterBuildCheck(string path), which requires the path to the assembly as input. Therefore, it's essential for the user to ensure that the assembly exists in the specified location.
Additionally, the custom rule must use BuildCheckRule as a parent class; otherwise, MSBuild will not register it.

Examples of custom rules can be found in the end-to-end tests, showcasing various scenarios:

  1. Single Custom Rule Packaged as a NuGet Package: Demonstrates the implementation of a custom rule encapsulated within a NuGet package. It contains the process of creating, packaging, and integrating a single custom rule into a project.
  2. Project with Multiple Checks Referenced: Illustrates a project setup where multiple custom checks are referenced. This scenario is common in larger projects where multiple rule sets need to be enforced simultaneously.

Important Notes:

  • In these examples, pay attention to the presence of the CustomCheckName.props file. This file contains intrinsic function invocations necessary for successful rule registration. Understanding and correctly implementing these invocations are crucial for integrating custom rules into your projects effectively.
  • The examples do not include references to third-party assemblies that might be required in other custom checks. To load the custom instance in MSBuild, third-party assemblies have to be included in the NuGet package. The current implementation achieves this goal by using a special target: AddNuGetDlls.

Future Enhancements

As our library of custom rules expands, we plan to enrich this section with real-world production rule examples. These examples will showcase a diverse range of rules addressing common development challenges. Once these production rules are published and available on nuget.org, we will update this documentation accordingly.

Build OM for Checks Authoring

TBD - details for the initial inbox checks set to be amended by @ladipro


File: documentation/specs/BuildCheck/BuildCheck-feature-threat-model.md

Threat model of MSBuild BuildCheck feature

BuildCheck Feature Description

The infrastructure within MSBuild allowing pluggability and execution of
Checks and their Rules previously known as "warning waves" and
"MSBuild Checks".

The feature is meant to help customers to improve and understand quality of their MSBuild scripts via rules violations reporting. It will allow MSBuild to gradually roll out additional rules, as users will be capable to configure their opt-in and severity of reports โ€“ preventing unwanted build breakages. And to equip powerusers to roll out their own quality checks โ€“ whether for general community or internal enterprise usage.

Design
Spec

Architecture

Threats Identification

This feature does not alter existing nor create any new trust boundaries.

It is assumed to rely on only trusted sources, be managed by trusted operators, and operated on trusted machines.

For this document, we do not address any threats that result from violating these conditions.

Acquisition

Threat: Supply chain attack on custom check

Custom BuildCheck checks are executed during build. If bad external actors inject malicious code into it by supply chain attack or somehow else, such code can run on build machine, mostly build agent or develop box.

Mitigation

Detecting unsecure packages is not MSBuild responsibility and is currently out of scope of this feature.

Custom checks are delivered as regular nuget packages by MSBuild `` element.
Users is expected to implement process to detect and warn about known malicious custom checks.

Important Notice:

Users should not add untrusted build checks to their projects. The use of untrusted or unverified 3rd party checks can introduce security risks and vulnerabilities into the build process. Always ensure that any build checks integrated into your projects come from reputable sources and have been thoroughly vetted.

To ensure a secure and reliable build environment, the following steps should be taken:

Use Dedicated Security Tools:

Utilize specialized security tools and services to scan and monitor 3rd party check packages and their dependencies.

Regular Updates:

Ensure that all 3rd party packages and dependencies are regularly updated to the latest versions, which often include security patches and vulnerability fixes.

Vendor Documentation and Support:

Refer to the official documentation and support channels provided by the developers of the 3rd party check packages. They may offer guidance and tools for managing security and addressing vulnerabilities.

Internal Security Policies:

Implement internal policies and processes for the assessment and mitigation of security risks associated with using 3rd party packages. This can include regular security audits, dependency management protocols, and automated vulnerability scanning during the build process.

Execution

Threat: Supply chain attack by custom check

Custom BuildCheck checks are executed during build. If bad external actors inject malicious code into it by supply chain attack or somehow else, such code can run on build machine, mostly build agent or develop box, with intent to inject malicious behavior into build artifacts.

Mitigation

Detecting unsecure packages is not MSBuild responsibility and is currently out of scope of this feature.

Threat: Third-Party Vulnerabilities

Vulnerabilities in custom check or its dependencies.

Mitigation

Detecting unsecure packages is not MSBuild responsibility and is currently out of scope of this feature.

Configuration

Threat: Malicious configuration value

Although .editorconfig shall be part of trusted sources, and hence not malicious, .editorconfig is looked up in parent folders up to the root. This can allow attacked to store malicious editor config up in parent folders with intent of disabling an check or cause build malfunction for any reason.

Mitigation

This problem is identical to existing .editorconfig for Roslyn checks and since we share code for parsing it, we adopt same mitigation strategy, which is:

  • default template for editor config has root = true stopping parent config traversing
  • code is unit tested to verify and sanitize .editorconfig values

Threat: Intentional check ID conflict or misleading ID

Malicious actors can define check ID to be identical or like existing well known check ID to increase probability of executing malicious check code.

Mitigation

Detecting unsecure packages is not MSBuild responsibility and is currently out of scope of this feature.

Declaration

Threat: Malicious check registration property function

Threat actor can write malicious check registration property function in project files, with intent to run code from non-governed assemblies.

Mitigation

This threat is out of scope of this document, as this requires malicious modification of source code (repository) making these sources untrusted.


File: documentation/specs/BuildCheck/Codes.md

`BuildCheck` reports codes and their meaning

Report codes are chosen to conform to suggested guidelines. Those guidelines are currently in revew: https://github.com/dotnet/msbuild/pull/10088

Diagnostic Code Default Severity Default Scope Available from SDK Reason
BC0101 Warning N/A 9.0.100 Shared output path.
BC0102 Warning N/A 9.0.100 Double writes.
BC0103 Suggestion Project 9.0.100 Used environment variable.
BC0104 Warning N/A 9.0.200 ProjectReference is preferred to Reference.
BC0105 Warning N/A 9.0.200 Culture specific EmbeddedResource should specify Culture metadata.
BC0106 Warning N/A 9.0.200 CopyToOutputDirectory='Always' should be avoided.
BC0107 Warning N/A 9.0.200 TargetFramework and TargetFrameworks specified together.
BC0108 Warning N/A 9.0.300 TargetFramework or TargetFrameworks specified in non-SDK style project.
BC0201 Warning Project 9.0.100 Usage of undefined property.
BC0202 Warning Project 9.0.100 Property first declared after it was used.
BC0203 None Project 9.0.100 Property declared but never used.
BC0301 None Project 9.0.300 Building from Downloads folder.
BC0302 Warning N/A 9.0.300 Building using the Exec task.

Notes:

  • What does the 'N/A' scope mean? The scope of checks are only applicable and configurable in cases where evaluation-time data are being used and the source of the data is determinable and available. Otherwise the scope of whole build is always checked.
  • How can you alter the default configuration? Please check the Configuration section of the BuildCheck documentation
  • To enable verbose logging in order to troubleshoot issue(s), enable binary logging
    Cmd:
    cmd
    dotnet build -bl -check

BC0101 - Shared output path.

"Two projects should not share their OutputPath nor IntermediateOutputPath locations"

It is not recommended to share output path nor intermediate output path between multiple projects. Such practice can lead to silent overwrites of the outputs. Such overwrites will depend on the order of the build, that might not be guaranteed (if not explicitly configured) and hence it can cause nondeterministic behavior of the build.

If you want to produce outputs in a consolidated output folder - consider using the Artifacts output layout and/or Microsoft.Build.Artifacts SDK.

BC0102 - Double writes.

"Two tasks should not write the same file"

This is a similar problem as 'BC0101 - Shared output path' - however with higher granularity. It is not recomended that multiple tasks attempt to write to a single file - as such behavior might lead to nondeterminism of a build (as result can be dependent on the order of the tasks execution if those belong to independent projects) or/and to a lost updates.

If you want multiple tasks to update file in a one-by-one pipeline fashion, it is recommended to give each intermediate output a distinct name - preventing silent mixups if any of the tasks in the chain are skipped or removed.

BC0103 - Used environment variable.

"Environment variables should not be used as a value source for the properties"

Using environment variables as a data source in MSBuild is problematic and can lead to nondeterministic builds.
Relying on environment variables introduces variability and unpredictability, as their values can change between builds or environments.

This practice can result in inconsistent build outcomes and makes debugging difficult, since environment variables are external to project files and build scripts. To ensure consistent and reproducible builds, avoid using environment variables. Instead, explicitly pass properties using the /p option, which offers better control and traceability.

BC0104 - ProjectReference is preferred to Reference.

"A project should not be referenced via 'Reference' to its output, but rather directly via 'ProjectReference'."

It is not recommended to reference project outputs. Such practice leads to losing the explicit dependency between the projects. Build then might not order the projects properly, which can lead to randomly missing reference and hence undeterministic build.

If you need to achieve more advanced dependency behavior - check Controlling Dependencies Behavior document. If neither suits your needs - then you might need to disable this check for your build or for particular projects.

BC0105 - EmbeddedResource should specify Culture metadata.

"It is recommended to specify explicit 'Culture' metadata, or 'WithCulture=false' metadata with 'EmbeddedResource' item in order to avoid wrong or nondeterministic culture estimation."

EmbeddedResource item has a Culture and WithCulture metadata that are strongly recommended to be used - to prevent MSBuild to need to 'guess' the culture from the file extension - which may be dependent on the current OS/Runtime available cultures and hence it can lead to nondeterministic build.

Examples:

  • `` This indicates the culture to the MSBuild engine and the culture will be respected. No diagnostic (warning) is issued (see below for exceptions).
  • `` This indicates to the MSBuild engine that the file is culture neutral and the extension should not be treated as culture indicator. No diagnostic (warning) is issued.
  • `` MSBuild infers the culture from the extra extension ('xyz') and if it is known to System.Globalization.CultureInfo it is being used as the resource culture. The BC0105 diagnostic is emitted (if BuildCheck is enabled and BC0105 is not disabled)
  • `` MSBuild infers that the resource is culture neutral. No diagnostic (warning) is issued.


Note: In Full Framework version of MSBuild (msbuild.exe, Visual Studio) and in .NET SDK prior 9.0 a global or project specific property RespectAlreadyAssignedItemCulture needs to be set to 'true' in order for the explicit Culture metadata to be respected. Otherwise the explicit culture will be overwritten by MSBuild engine and if different from the extension - a MSB3002 warning is emitted ("MSB3002: Explicitly set culture "{0}" for item "{1}" was overwritten with inferred culture "{2}", because 'RespectAlreadyAssignedItemCulture' property was not set.")

BC0106 - CopyToOutputDirectory='Always' should be avoided.

"Avoid specifying 'Always' for 'CopyToOutputDirectory' as this can lead to unnecessary copy operations during build. Use 'PreserveNewest' or 'IfDifferent' metadata value, or set the 'SkipUnchangedFilesOnCopyAlways' property to true to employ more effective copying."

CopyToOutputDirectory metadata can take the values:

  • Never
  • Always
  • PreserveNewest
  • IfDifferent

Always is not recommended, as it causes the files to be copied in every build, even when the destination file content is identical to the source.

Before the introduction of IfDifferent, Always was needed to work around cases where the destination file could have changed between builds (e.g. an asset that can be changed during test run, but needs to be reset by the build). IfDifferent preserves this behavior without unnecessary copying.

In order to avoid the need to change copy metadata for a large number of items, it's now possible to specify the SkipUnchangedFilesOnCopyAlways property in order to flip all copy behavior of CopyToOutputDirectory=Always to behave identically to CopyToOutputDirectory=IfDifferent:

xml
<SkipUnchangedFilesOnCopyAlways>True</SkipUnchangedFilesOnCopyAlways>



    <None Include="File1.txt" CopyToOutputDirectory="Always" />
    <None Include="File2.txt" CopyToOutputDirectory="IfDifferent" />

Both items in above example are treated same and no BC0106 diagnostic is issued.

BC0107 - TargetFramework and TargetFrameworks specified together.

"'TargetFramework' (singular) and 'TargetFrameworks' (plural) properties should not be specified in the scripts at the same time."

When building a .NET project - you can specify target framework of the resulting output (for more info see the documentation).

When using TargetFrameworks property - you are instructing the build to produce output per each specified target framework.

If you specify TargetFramework you are instructing the build to produce a single output for that particualar target framework. TargetFramework gets precedence even if TargetFrameworks is specified - which might seem as if TargetFrameworks was ignored.

BC0107 doesn't apply if you explicitly choose to build a single target of multitargeted build:

text
dotnet build my-multi-target.csproj /p:TargetFramework=net10.0

BC0108 - TargetFramework or TargetFrameworks specified in SDK-less project.

"'TargetFramework' and 'TargetFrameworks' properties are not respected and should not be specified in projects not using .NET SDK."

'TargetFramework' or 'TargetFrameworks' control the project output targets in modern .NET SDK projects. The older SDK-less projects interprets different properties for similar mechanism (like 'TargetFrameworkVersion') and the 'TargetFramework' or 'TargetFrameworks' are silently ignored.

Make sure the Target Framework is specified appropriately for your project.

BC0201 - Usage of undefined property.

"A property that is accessed should be declared first."

This check indicates that a property was accessed without being declared (the declaration might have happen later - see BC0202 for such checking). Only accessing in the configured scope (by default it's the project file only) are checked.

There are couple cases which are allowed by the check:

  • Selfreferencing declaration is allowed - e.g.:
    <ChainProp>$(ChainProp)</ChainProp>

  • Checking the property for emptyness - e.g.:
    ``

  • Any usage of property in condition. This can be opted out via the configuration AllowUninitializedPropertiesInConditions - e.g.:

    ini
    [*.csproj]
    build_check.BC0201.severity=error
    build_check.BC0201.AllowUninitializedPropertiesInConditions=false
    build_check.BC0202.AllowUninitializedPropertiesInConditions=false

    BC0201 and BC0202 must have same value for the optional switch - as both operate on top of same data and same filtering.

BC0202 - Property first declared after it was used.

"A property should be declared before it is first used."

This check indicates that a property was accessed before it was declared. The default scope of this rule is the project file only. The scope captures the read and write operations as well. So this rule reports:

  • Uninitialized reads that happened anywhere during the build, while the uninitialized property was later defined within the scope of this check (e.g. project file).
  • Uninitialized reads that happened within the scope of check (e.g. project file), while later defined anywhere in the build

If BC0202 and BC0201 are both enabled - then BC0201 reports only the undefined reads that are not reported by this rule (so those that do not have late definitions).

BC0203 - Property declared but never used.

"A property that is not used should not be declared."

This check indicates that a property was defined in the observed scope (by default it's the project file only) and it was then not used anywhere in the build.

This is a runtime check, not a static analysis check - so it can have false positives - for this reasons it's currently not enabled by defaut.

Common cases of false positives:

  • Property not used in a particular build might be needed in a build with different conditions or a build of a different target (e.g. dotnet pack /check or dotnet build /t:pack /check accesses some additional properties as compared to ordinary dotnet build /check).
  • Property accessing is tracked for each project build request. There might be multiple distinct build requests for a project in a single build. Specific case of this is a call to the MSBuild task or CallTarget task that can request a result from a project build, while passing additional or different global properties and/or calling specific target. This happens often as part of common targets - e.g. for multi-targeted project build parallelization
  • Incremental build might skip execution of some targets, that might have been accessing properties of interest.

BC0301 - Building from Downloads folder.

"Downloads folder is untrusted for projects building."

Placing project files into Downloads folder (or any other folder that cannot be fully trusted including all parent folders up to a root drive) is not recomended, as unintended injection of unrelated MSBuild logic can occur.

Place your projects into trusted locations - including cases when you intend to only open the project in IDE.

BC0302 - Building using the Exec task.

"The 'Exec' task should not be used to build projects."

Building projects using the dotnet/msbuild/nuget CLI in the Exec task is not recommended, as it spawns a separate build process that the MSBuild engine cannot track. Please use the MSBuild task instead.

2. Official Technical Reference & Guides (dotnet/docs)

.NET Docs








This repository contains the conceptual documentation for .NET. The .NET documentation site is built from multiple repositories:

API reference documentation is published from the following repositories:

These API reference doc repositories are public. Only some accept issues and pull requests. Others are pass-through repositories because API reference is generated directly from the /// in the product source. Some folders in dotnet-api-docs use the product repository as the source of truth.

The C# language specification documentation comes from the following two repositories:

Our team's tasks are tracked in our project boards. You'll see monthly sprint projects, along with long-running projects for major documentation updates. The projects contain documentation issues across the repositories that build .NET docs. Issues are tracked in the relevant repositories. We have a large community using these resources. We make our best effort to respond to issues in a timely fashion. To create a new issue, click the "Open a documentation issue" button at the bottom of any of our published docs, or choose one of the available templates. The control at the bottom of each article automatically routes you to the correct repo and fills in some relevant information based on the article.

:purple_heart: Contribute

We welcome contributions to help us improve and complete the .NET docs. This is a very large repo, covering a large area. If this is your first visit, see the Contributor guide for information on working with us. Look for issues labeled help wanted :label: for ideas to get started.

Before submitting a PR with 3rd party dependencies, see our policy on 3rd party dependencies.

We work to merge or close PRs in a timely fashion. We regularly review and approve PRs. We encourage contributors to respond to comments in a similar timely fashion. Many times, reviews identify small changes such as spelling or grammar issues, or word choice. Maintainers can accept those suggestions and merge the PR. In other situations, maintainers ask for more significant changes before a PR is ready to merge. We expect contributors to make those changes. Maintainers will help by answering questions or pointing to other resources, if needed.

We consider PRs to be abandoned when they've had no activity (either commits or discussion) in the past 30 days. Team members will close PRs that have been abandoned. Contributors can reopen and continue to work.

:bookmark_tabs: Code of conduct

This project has adopted the code of conduct defined by the Contributor Covenant
to clarify expected behavior in our community. For more information, see the .NET Foundation: Code of Conduct.

:octocat: GitHub Action workflows

  • : Adds a comment to PRs that were not automated, but rather manually created that target the live branch.
  • : Automatically approves and auto-merges PRs originating from the dependabbot[bot].
  • : Creates a PR to generate the "What's new" article on the first of every month.
  • : The current status for the entire repositories Markdown linter status.
  • : Runs various Markdown verifications, beyond the linter, such as ensuring links and redirects are valid.
  • : Builds the site for the PR in context, and verifies the build reporting either, success, warnings, or error.
  • : Custom .NET build validation, locates code impacted by a PR, and builds.
  • : Runs monthly, creating issues on projects that target .NET versions that are out of support.
  • : Automatically updates the dependabot configuration weekly, but only if required.
  • : Automatically synchronizes issues with Quest (Azure DevOps).
  • : Manual bulk import of issues into Quest (Azure DevOps).