如何编写基于 Microsoft.NET.Sdk 的跨平台的 MSBuild Target(附各种自带的 Task)

我之前写过一篇 理解 C# 项目 csproj 文件格式的本质和编译流程,其中,Target 节点就是负责编译流程的最关键的节点。但因为篇幅限制,那篇文章不便详说。于是,我在本文说说 Target 节点。


Target 的节点结构

<Target> 内部几乎有着跟 <Project> 一样的节点结构,内部也可以放 PropertyGroupItemGroup,不过还能放更加厉害的 Task。按照惯例,我依然用思维导图将节点结构进行了总结:

Target 的节点结构
▲ 上面有绿线和蓝线区分,仅仅是因为出现了交叉,怕出现理解歧义

<Hash><WriteCodeFragment> 都是 Task。我们可以看到,Task 是多种多样的,它可以占用一个 xml 节点。而本例中,WriteCodeFragment Task 就是生成代码文件,并且将生成的文件作为一项 Compile 的 Item 和 FileWrites 的 Item。在 理解 C# 项目 csproj 文件格式的本质和编译流程 中我们提到 ItemGroup 的节点,其作用由 Target 指定。所有 Compile 会在名为 CoreCompileTarget 中使用,而 FileWrites 在 Microsoft.NET.Sdk 的多处都生成了这样的节点,不过目前从我查看到的全部 Microsoft.NET.Sdk 中,发现内部并没有使用它。

Target 执行的时机和先后顺序

既然 <Target> 内部节点很大部分跟 <Project> 一样,那区别在哪里呢?<Project> 里的 <PropertyGroup><ItemGroup> 是静态的状态,如果使用 Visual Studio 打开项目,那么所有的状态将会直接在 Visual Studio 的项目文件列表和项目属性中显示;而 <Target> 内部的 <PropertyGroup><ItemGroup> 是在编译期间动态生成的,不会在 Visual Studio 中显示;不过,它为我们提供了一种在编译期间动态生成文件或属性的能力。

总结起来就是——Target 是在编译期间执行的

不过,同样是编译期间,那么多个 Target,它们之间的执行时机是怎么确定的呢?

一共有五个属性决定了 Target 之间的执行顺序:

通过指定这些属性,我们的 Target 能够被 MSBuild 自动选择合适的顺序进行执行。例如,当我们希望自定义版本号,那么就需要赶在我们此前提到的 GenerateAssemblyInfo 之前执行。

Microsoft.NET.Sdk 为我们提供的现成可用的 Task

有 Microsoft.NET.Sdk 的帮助,我们可以很容易地编写自己的 Target,因为很多功能它都帮我们实现好了,我们排列组合一下就好。

提供的 Task 还有更多,如果上面不够你写出想要的功能,可以移步至官方文档翻阅:MSBuild Task Reference - Visual Studio - Microsoft Docs

使用自己写的 Task

我有另外的一篇文章来介绍如何创建一个基于 MSBuild Task 的跨平台的 NuGet 工具包 - 吕毅。如果希望自己写 Ta

差量编译

如果你认为自己写的 Target 执行比较耗时,那么就可以使用差量编译。我另写了一篇文章专门来说 Target 的差量编译:每次都要重新编译?太慢!让跨平台的 MSBuild/dotnet build 的 Target 支持差量编译 - 吕毅


参考资料

本文会经常更新,请阅读原文: https://walterlv.github.io/post/write-msbuild-target.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 吕毅 (包含链接: https://walterlv.github.io ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系