专栏首页walterlv - 吕毅的博客通过重写预定义的 Target 来扩展 MSBuild / Visual Studio 的编译过程

通过重写预定义的 Target 来扩展 MSBuild / Visual Studio 的编译过程

MSBuild 的编译过程提供了一些可以被重写的 Target,通过重写这些 Target 可以扩展 MSBuild 的编译过程。


重写预定义的 Target

有这些预定义的 Target 可以重写:

  • BeforeCompile, AfterCompile
  • BeforeBuild, AfterBuild
  • BeforeRebuild, AfterRebuild
  • BeforeClean, AfterClean
  • BeforePublish, AfterPublish
  • BeforeResolveReference, AfterResolveReferences
  • BeforeResGen, AfterResGen

你可以在 Microsoft.NET.Sdk 中找到各种富有创意的 Target 用来扩展,以上这些也是 Microsoft.NET.Sdk 的一部分,在那个文件夹的 Microsoft.Common.targets 或者 Microsoft.Common.CurrentVersion.targets 中。

而写法是这样的:

<Project>
    ...
    <Target Name="BeforeResGen">
        <!-- 这里可以写在生成资源之前执行的 Task 或者修改属性和集合。 -->
    </Target>
    <Target Name="AfterCompile">
        <!-- 这里可以写在 C# 文件以及各种资源文件编译之后执行的 Task 或者修改属性和集合。 -->
    </Target>
</Project>

是的,相比于你全新定义一个 Target 来说,你不需要去写 BeforeTargets 或者 AfterTargets。

那么以上那些 Target 都是什么时机呢?

BeforeCompile, AfterCompile

在 C# 文件以及各种资源文件被编译成 dll 的之前或之后执行。你可以在之前执行以便修改要编译的 C# 文件或者资源文件,你也可以在编译之后做一些其他的操作。

由于我们可以在 BeforeCompile 这个时机修改源码,所以我们很多关于代码级别的重新定义都可以在这个时机去完成。

BeforeBuild, AfterBuild

在整个编译之前或者之后执行。对于普通的编译来说,一般来说不会有比 BeforeBuild 更前以及比 AfterBuild 更后的时机了,不过如果有其他 Import 进来的 Target 或者通过 NuGet 自动引入进来的其他 Target 也使用了类似这样的时机,那么你就不一定比他们更靠前或者靠后。

BeforeRebuild, AfterRebuild

如果编译时采用了 /t:Rebuild 方案,也就是重新编译,那么 BeforeRebuild 和 AfterRebuild 就会被触发。一旦触发,会比前面更加提前和靠后。

执行顺序为:BeforeRebuild -> Clean -> Build -> AfterRebuild

BeforeClean, AfterClean

在清理开始和结束时执行。如果是重新编译,那么也会有 Clean 的过程。顺序见上面。

BeforePublish, AfterPublish

在发布之前执行和发布之后执行。对应到 Visual Studio 右键菜单中的发布按钮。

BeforeResolveReference, AfterResolveReferences

在程序集的引用被解析之前和之后执行。你可以通过重写这两个时机的 Target 来修改程序集的引用关系或者利用引用执行一些其他操作。

BeforeResGen, AfterResGen

在资源被生成之前和之后执行。

通过改写 DependsOn 的值扩展编译

有这些预定义的 DependsOn 可以改写:

  • BuildDependsOn
  • CleanDependsOn
  • CompileDependsOn

这几个属性的时机跟上面是一样的,你可以直接通过阅读上面一节中对应名字的 Target 的解释来获得这几个属性所对应的时机。

而这几个属性影响编译过程的写法是这样的:

<PropertyGroup>
    <BuildDependsOn>WalterlvDemoTarget1;$(BuildDependsOn);WalterlvDemoTarget1</BuildDependsOn>
</PropertyGroup>
<Target Name="WalterlvDemoTarget1">  
    <Message Text="正在运行 WalterlvDemoTarget1……"/>  
</Target>  
<Target Name="WalterlvDemoTarget1">  
    <Message Text="正在运行 WalterlvDemoTarget2……"/>  
</Target>

更推荐使用 DependsOn 属性的改写而不是像本文第一节那样直接重写 Target,是因为一个 Target 的重写很容易被不同的开发小伙伴覆盖。比如一个小伙伴在一处代码里面写了一个 Target,但另一个小伙伴不知道,在另一个地方也写了相同名字的 Target,那么这两个 Target 也会相互覆盖,导致其中的一个失效。

虽然同名的属性跟 Target 一样的会被覆盖,但是我们可以通过在改写属性的值的时候同时获取这个属性之前设置的值,可以把以前的值保留下来。

正如上面的例子那样,我们通过写了两个新的 Target 的名字,分别叠加到 $(BuildDependsOn) 这个属性原有值的两边,使得我们可以在编译前后执行两个不同的 Target。如果有其他的小伙伴使用了相同的方式去改写这个属性的值,那么它获取原有值的时候就会把这里已经赋过的值放入到它新的值的中间。也就是说,一个也不会丢。


参考资料

本文会经常更新,请阅读原文: https://blog.walterlv.com/post/extend-the-visual-studio-build-process.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 编写 MSBuild 内联编译任务(Task)用于获取当前编译环境下的所有编译目标(Target)

    我之前写过一些改变 MSBuild 编译过程的一些博客,包括利用 Microsoft.NET.Sdk 中各种自带的 Task 来执行各种各样的编...

    walterlv
  • 让 MSBuild Target 支持 Clean

    我们有时候会使用解决方案的清理(Clean)功能来解决一些项目编译过程中非常诡异的问题。这通常是一些 Target 生成了一些错误的中间文件,但又不...

    walterlv
  • 编写 Target 检测 MSBuild / dotnet build 此次编译是否是差量编译

    MSBuild 或 Roslyn 编译项目时均支持差量编译,毕竟为了性能。我在 每次都要重新编译?太慢!让跨平台的 MSBuild/dotnet bui...

    walterlv
  • iOS多Target开发相似App

    我们在iOS开发中可能会遇到同时开发多个类似项目的情况。这些项目大同小异,有诸多代码可以共用,如果每个项目都分别开发,这在后期的迭代中会十分繁琐。为了解决这个问...

    梧雨北辰
  • 编写 MSBuild 内联编译任务(Task)用于获取当前编译环境下的所有编译目标(Target)

    我之前写过一些改变 MSBuild 编译过程的一些博客,包括利用 Microsoft.NET.Sdk 中各种自带的 Task 来执行各种各样的编...

    walterlv
  • 让 MSBuild Target 支持 Clean

    我们有时候会使用解决方案的清理(Clean)功能来解决一些项目编译过程中非常诡异的问题。这通常是一些 Target 生成了一些错误的中间文件,但又不...

    walterlv
  • 工程管理篇 | APP环境分离的实现

    如何实现在同一台手机能同时安装同个应用的测试和生产版本?应用名称要有区分,图标也要有所区别。不要手动修改Bundle id和应用名称,也不要手动替换图标,更不要...

    進无尽
  • [剑指offer] 二进制中1的个数

    如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,原来在1后面的所有的0都会变成1(如果最右边的1后...

    尾尾部落
  • 每日算法系列【LeetCode 829】连续整数求和

    遍历所有的连续数字区间 (i, j) ,然后求和看等不等于 N 。这种方法时间复杂度是 ,显然不可行。

    godweiyang
  • Jenkins 自动化部署 持续集成

    Help for feature: Source files Files to upload to a server.

    一个会写诗的程序员

扫码关注云+社区

领取腾讯云代金券