Build System

MSBuild Summary

A summary of MSBuild basic concepts and extension methods

Published

Background #

Recently I have been working on Visual Studio projects again, and I may also need to migrate some projects from older build processes, so I have been reading MSBuild-related materials and organizing my study notes.

This time I will keep it simpler, because I have already grasped some of the basic concepts.

See the official documentation: MSBuild - MSBuild

Basic Concepts #

Reference: MSBuild Concepts - MSBuild

  • Property: roughly equivalent to the concept of a Variable
  • Item: files that are displayed in the IDE
  • Target: a node that executes the build workflow
  • Task: the action that actually executes; one Target can contain multiple Tasks

Property #

Use $(<name>) to reference one.

The main thing to know is that there are some built-in Properties: MSBuild Reserved and Well-known Properties - MSBuild

Environment variables can also be used as Properties: How to: Use Environment Variables in a Build - MSBuild

You can use certain functions to evaluate Properties: Property Functions - MSBuild

Item #

Use @(<name>) to reference an Item, and use %(<name>) to reference its Metadata.

Metadata is a property attached to an Item. There is some built-in Metadata available: MSBuild Well-known Item Metadata - MSBuild

You can use certain functions to evaluate Metadata: Item Functions - MSBuild

You can use a syntax such as MSBuild Transforms - MSBuild to perform transformations on an Item list based on Metadata (similar to a mapping operation).

Target #

The default Targets are listed here: MSBuild Targets - MSBuild

For how to determine the Target execution order, see Target Build Order - MSBuild

One point that can easily cause confusion is DependsOnTargets versus AfterTargets. See the difference here: DependsOnTargets Vs AfterTargets · Issue #2994 · MicrosoftDocs/visualstudio-docs

plain text
<Target Name="x" DependsOnTargets="y" /> means:

If something wants to run x, y must run first.

<Target Name="a" AfterTargets="b" /> means:

If something runs b, then run a after it.

They differ in a couple of ways. BeforeTargets and AfterTargets are
generally used for *extending* the build: "I have a custom target I
want to run after ResolveReferences". The target that's being
hooked onto doesn't need to know anything about the new target.

In contrast, DependsOnTargets is the basic building block of the
target-dependency graph. When authoring a target, use
DependsOnTargets to ensure that all of your inputs (both file and
MSBuild item/property) have already been brought up to date.

An empty proj has no Target, but generally we use things like vcxproj, where a bunch of targets are injected through the SDK.

Task #

The built-in Tasks are listed here: MSBuild Task Reference - MSBuild (defined in Microsoft.Common.CurrentVersion.Targets)

.targets fileDescription
Microsoft.Common.targetsDefines the steps in the standard build process for Visual Basic and C# projects.
Imported by the Microsoft.CSharp.targets and Microsoft.VisualBasic.targets files, which include the following statement: <Import Project="Microsoft.Common.targets" />
Microsoft.CSharp.targetsDefines the steps in the standard build process for Visual C# projects.
Imported by Visual C# project files (.csproj), which include the following statement: <Import Project="$(MSBuildToolsSpeed)\Microsoft.CSharp.targets" />
Microsoft.VisualBasic.targetsDefines the steps in the standard build process for Visual Basic projects.
Imported by Visual Basic project files (.vbproj), which include the following statement: <Import Project="$(MSBuildToolsSpeed)\Microsoft.VisualBasic.targets" />

For C++, it seems to be Microsoft.Cpp.targets, which appears to have nothing to do with Microsoft.Commons.targets (looking at this file may be more reliable: C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\Microsoft.Common.tasks).

If you need to customize a Task, refer to Task Writing - MSBuild and Create a custom task - MSBuild. If you only need to embed a very simple small piece of code, consider MSBuild Inline Tasks - MSBuild.

Directory.Build.props #

A Small Tip for Recursive References #

https://docs.microsoft.com/zh-cn/visualstudio/msbuild/how-to-use-project-sdk?view=vs-2022

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

MSBuildSdks #

https://github.com/microsoft/MSBuildSdks

https://docs.microsoft.com/zh-cn/visualstudio/msbuild/how-to-use-project-sdk?view=vs-2022

There are several useful extensions in it:

  1. Microsoft.Build.Traversal is generally used to create a dirs.proj that recursively references other projects in subdirectories. This makes it convenient to organize hundreds of projects in a particularly large repository. Finally, you can use the SlnGen tool to generate an sln file and develop with an IDE. The reason for doing this is that large sln files have relatively poor performance.
  2. Microsoft.Build.CentralPackageVersions centrally configures dependency versions, so the dependency versions used by all projects in the entire repository can be aligned, and there will not be issues caused by inconsistent dependency versions when referencing and linking each other.
  3. Microsoft.Build.NoTargets leaves the Build process empty, making it convenient to trigger custom tasks during build time, such as robocopy and similar tasks.
  4. Microsoft.Build.Artifacts places project artifacts in a centralized location, which is convenient for scenarios such as collecting all artifacts after an Azure DevOps Pipeline build completes (Azure Artifacts).

Other #

XML File Schema #

https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-project-file-schema-reference?view=vs-2022

The schema link in an MSBuild project file is not required in Visual Studio 2017 and later. If present, it should be http://schemas.microsoft.com/developer/msbuild/2003 regardless of the version of Visual Studio.

However, it looks like SDK-style projects now automatically remove the Schema, and I do not know why.

Extensions Are Generally Split into Two Files: .props and .targets #

https://docs.microsoft.com/en-us/visualstudio/msbuild/customize-your-build?view=vs-2022#choose-between-adding-properties-to-a-props-or-targets-file

This is mainly useful when writing NuGet packages.

When using explicit imports, you can import from a .props or .targets file at any point. Here is the widely used convention:

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

Customize Configuration Based on Project Type #

https://docs.microsoft.com/en-us/visualstudio/msbuild/customize-your-build?view=vs-2022#custom-configuration-based-on-project-language

xml
<PropertyGroup Condition="'$(MSBuildProjectExtension)' == '.vbproj'">
   <!-- Put VB-only property definitions here -->
</PropertyGroup>
<PropertyGroup Condition="'$(MSBuildProjectExtension)' == '.fsproj'">
   <!-- Put F#-only property definitions here -->
</PropertyGroup>
<PropertyGroup Condition="'$(MSBuildProjectExtension)' == '.csproj'">
   <!-- Put C#-only property definitions here -->
</PropertyGroup>

Generate an sln File from a proj File #

https://microsoft.github.io/slngen/

Build Projects Targeting .NET Framework #

You can use this https://github.com/Microsoft/dotnet/tree/master/releases/reference-assemblies without installing the .NET Framework SDK.

C++ SDK #

https://www.nuget.org/packages/Microsoft.Windows.SDK.NET.Ref/

https://www.nuget.org/packages/Microsoft.Windows.SDK.CPP/