MSBuild 总结

背景介绍

最近又要开始搞 Visual Studio 的项目了,可能还要把之前一些老构建流程的项目迁移过来,所以在看看 MSBuild 的相关资料,整理一下学习笔记。

这次的话会写得简单一些,因为有些基础概念已经都掌握了。

官方文档见 MSBuild - MSBuild

基本概念

参考 MSBuild Concepts - MSBuild

  • Property:约等于 Variable 的概念
  • Item:会展示在 IDE 中的文件
  • Target:执行构建工作流的节点
  • Task:实际执行的动作,一个 Target 可以包含多个 Tasks

Property

$(<name>) 来使用。

主要是要知道有些内建的 Properties:MSBuild Reserved and Well-known Properties - MSBuild

然后是环境变量也能当成 Property 用:How to: Use Environment Variables in a Build - MSBuild

可以用一些特定的函数对 Property 进行计算:Property Functions - MSBuild

Item

@(<name>) 来使用 Item,用 %(<name>) 来使用其 Metadata。

Metadata 是 Attach 到 Item 上的属性,有一些内建的 Metadata 可用:MSBuild Well-known Item Metadata - MSBuild

可以用一些特定的函数对 Metadata 进行计算:Item Functions - MSBuild

可以用 MSBuild Transforms - MSBuild 这样一种语法对 Item 列表基于 Metadata 来进行一些变换(类似于 Mapping 操作)。

Target

默认的 Targets 有这些 MSBuild Targets - MSBuild

如何确定 Target 的执行顺序见 Target Build Order - MSBuild

这里比较容易引起困惑的是 DependsOnTargets 和 AfterTargets,他们的区别见 DependsOnTargets Vs AfterTargets · Issue #2994 · MicrosoftDocs/visualstudio-docs

<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.

空的 proj 就没有 Target,但是一般我们都用 vcxproj 什么的,通过 SDK 就给注入了一堆 target。

Task

内置的 Task 有这些 MSBuild Task Reference - MSBuild(定义于 Microsoft.Common.CurrentVersion.Targets)

.targets file Description
Microsoft.Common.targets Defines 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.targets Defines 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.targets Defines 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" />

C++ 的好像是 Microsoft.Cpp.targets 看起来好像跟 Microsoft.Commons.targets 没关系(看这里的可能更靠谱 C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\Microsoft.Common.tasks)。

如果需要自定义 Task 可以参考 Task Writing - MSBuildCreate a custom task - MSBuild。如果只需要嵌入非常简单的小段代码,可以考虑 MSBuild Inline Tasks - MSBuild

Directory.Build.props

递归引用的小技巧

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

<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

这里面搞了几个比较有用的扩展:

  1. Microsoft.Build.Traversal 一般用来搞一个 dirs.proj,递归的引用子目录中的其他项目。这样方便在一个特别大的仓库中组织几百个项目。最终可以用 SlnGen 工具生成 sln 文件使用 IDE 进行开发。这么搞的原因是大型 sln 文件性能比较差。
  2. Microsoft.Build.CentralPackageVersions 中心化配置依赖版本,这样就能将整个仓库中所有项目使用的依赖版本对齐了,互相引用和链接的时候也不会出什么依赖版本不一致的问题。
  3. Microsoft.Build.NoTargets Build 过程为空,这样方便在编译时期处罚一些自定义的任务,比如说 robocopy 什么的。
  4. Microsoft.Build.Artifacts 把项目产物放到一个集中的地方,方便比如说 Azure DevOps Pipeline 编译完了之后把所有产物收集起来(Azure Artifacts)。

其他

XML 文件 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.

但是看现在 SDK 的项目自动都把 Schema 抹掉了,也不知道是为啥。

一般把扩展拆成 .props 和 .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

说这个主要是写 nuget 包的时候会用到。

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.

根根据项目类型自定义配置

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

<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>

proj 文件生成 sln 文件

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

编译 .NET Framework 目标工程

可以用这个 https://github.com/Microsoft/dotnet/tree/master/releases/reference-assemblies 而不安装 .NET Framework SDK

C++ SDK

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

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