前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >.NET 项目自定义 MSBuild Task

.NET 项目自定义 MSBuild Task

作者头像
jgrass
发布2024-12-25 18:34:38
发布2024-12-25 18:34:38
8500
代码可运行
举报
文章被收录于专栏:蔻丁杂记蔻丁杂记
运行总次数:0
代码可运行

🍉 1 MSBuild Task

利用 MSBuild Task, 可以在编译阶段,完成很多自定义的操作。比如最常见的,就是在编译完成之后,复制一些额外的文件到输出目录中。

对于这些简单任务,可以使用 MSBuild 自带的 Task。

详见:

MSBuild 任务参考 - MSBuild | Microsoft Learn

了解 MSBuild 任务如何执行生成操作 - MSBuild | Microsoft Learn

如果自带的 Task 不能满足需求,可以使用 Exec 任务 ,来执行自定义脚本。

Exec 任务在 Windows 上调用 cmd.exe,在其他操作系统上调用 sh,而不是直接调用进程。

这个的灵活性就会非常大了,自定义脚本里面可以完成很多事情。

如果觉得自定义脚本还是不够灵活,就可以考虑自定义 Task 了,也就是本文的笔记内容。

🍉 2 一些弯路

在考虑自定义 Task 之前,其实想通过 Roslyn 分析器来借道完成一些编译时期望完成的操作。但 Roslyn Analyzer 对 API 使用的限制很严格,代码必须是 Pure 的,不能访问和操作任何外部的东西。

也就是不能使用 IO 相关的 API,想要在这里读写本地文件是不可以的。

🍉 3 自定义 MSBuild Task

使用 MSBuild 代码编写自己的任务 - MSBuild | Microsoft Learn

第一步,先看效果。不考虑使用 nuget 包发布的情况,只考虑当前项目使用。

3.1 新建 Task 项目

我打算做一点 git hook 相关的事情,这里就取名为 Jgrass.GitHookMsbuildTask,新建一个 C# 类库项目,csproj 文件如下。

代码语言:javascript
代码运行次数:0
复制
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>    <TargetFramework>netstandard2.0</TargetFramework>    <ImplicitUsings>enable</ImplicitUsings>    <Nullable>enable</Nullable>    <LangVersion>latest</LangVersion>    <OutputType>Library</OutputType>  </PropertyGroup>
  <ItemGroup>    <PackageReference Include="Microsoft.Build.Framework" Version="17.12.6" />    <PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.12.6" />  </ItemGroup>
</Project>

1 TargetFramework 需要是 netstandard2.0

这里使用 netstandard2.1 都不行,可能跟 Roslyn 也是只支持 netstandard2.0 原因一样?期望后续的 VS 版本能跟上 .NET 高版本的节奏。

关于 netstandard 的一些相关信息:

2 引入 Microsoft.Build.Framework Microsoft.Build.Utilities.Core

代码语言:javascript
代码运行次数:0
复制
  <PackageReference Include="Microsoft.Build.Framework" Version="17.12.6" />  <PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.12.6" />

3.2 新建一个 Task

继承自 Microsoft.Build.Utilities.Task 即可。

代码语言:javascript
代码运行次数:0
复制
using Microsoft.Build.Framework;namespace Jgrass.GitHookMsbuildTask;
public class LargeFileInterceptTask : Microsoft.Build.Utilities.Task{    public override bool Execute()    {        Log.LogMessage(MessageImportance.High, "Normal Message");        Log.LogWarning("Warning Message");        Log.LogError("Error Message");
        return false; // Task 执行失败,会让引用了此 Task 的项目编译失败。    }}

3.3 在目标项目中使用

Task 项目和目标项目在同一个大的仓库中,这里可以使用相对路径的方式直接引用。

代码语言:javascript
代码运行次数:0
复制
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>    <OutputType>Exe</OutputType>    <TargetFramework>net8.0</TargetFramework>    <ImplicitUsings>enable</ImplicitUsings>    <Nullable>enable</Nullable>  </PropertyGroup>
  <UsingTask TaskName="LargeFileInterceptTask" AssemblyFile="$(MSBuildProjectDirectory)\..\Jgrass.GitHookMsbuildTask\bin\$(Configuration)\netstandard2.0\Jgrass.GitHookMsbuildTask.dll" />
  <Target Name="RunGitHookTask" AfterTargets="Build">    <LargeFileInterceptTask />  </Target>
</Project>

编译项目就能看到效果,这里会编译失败,是因为 Task 返回 false 引起的。

如果这个 Task 只是在当前项目中使用,这样基本上就能达到目的了。如果要通过 nuget 作为一个通用的 Task 发布,就会复杂亿丢丢。

PS 如果在修改代码之后,在编译 Task 项目时,发现输出目录的 dll 被占用,直接结束掉 msbuild.exe 进程。推荐使用 PowerToys 的 File Locksmith 工具。

🍉 4 将自定义 Task 打包成 nuget 包

基本流程参照吕毅的博客,所以里面设计到的基本概念就不详细介绍了,我这里做了一点简化。

如何创建一个基于 MSBuild Task 的跨平台的 NuGet 工具包 - walterlv

这个解决方案分为三个项目

  • Jgrass.GitHookMsbuildTask

Task 实现项目,TargetFramework 为 netstandard2.0,支持输出 nuget 包供外部使用。

  • Jgrass.GitHookMsbuildTask.Debugger

Task 的 Debug 项目,使用相对路径直接引用,用于开发时的调试。

Jgrass.GitHookMsbuildTask.Sample

Task 的使用示例项目,通过引用 nuget 包的形式引用 Task.

4.1 Task 实现项目配置

Jgrass.GitHookMsbuildTask.csproj

代码语言:javascript
代码运行次数:0
复制
// Jgrass.GitHookMsbuildTask.csproj<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>    <TargetFramework>netstandard2.0</TargetFramework>    <ImplicitUsings>enable</ImplicitUsings>    <Nullable>enable</Nullable>    <LangVersion>latest</LangVersion>    <OutputType>Library</OutputType>    <DevelopmentDependency>true</DevelopmentDependency>    <Version>0.0.7-alpha</Version>    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>    <BuildOutputTargetFolder>tasks</BuildOutputTargetFolder>    <NoPackageAnalysis>true</NoPackageAnalysis>  </PropertyGroup>
  <ItemGroup>    <PackageReference Include="Microsoft.Build.Framework" Version="17.12.6" />    <PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.12.6" />    <PackageReference Update="@(PackageReference)" PrivateAssets="All" />  </ItemGroup>
  <ItemGroup>    <Folder Include="Assets\tasks\" />  </ItemGroup>
  <ItemGroup>    <None Include="Assets\build\**" Pack="True" PackagePath="build\" />    <None Include="Assets\readme.md" Pack="True" PackagePath="" />  </ItemGroup>
</Project>

解决方案中的 Assets 文件结构如下

这里有个注意点,Jgrass.GitHookMsbuildTask.targets 文件的名称,必须与 Jgrass.GitHookMsbuildTask 项目的名称是一致的。

也可能 .targets 文件名是必须与 dll 的输出文件名一致?或者必须与 AssemblyName 的设置一致?或者是必须与 nuget 包的名称一致? 这里没有做进一步的探索,总之,要注意这个名称问题,不能随便取。不然其它项目在使用 nuget 包引用时,不会自动加载这个 .targets 文件。

Jgrass.GitHookMsbuildTask.targets 的内容

代码语言:javascript
代码运行次数:0
复制
<Project>    <PropertyGroup Condition=" $(IsInGitHookTaskDebugMode) == 'true' ">        <NuGetTaskFolder>$(MSBuildThisFileDirectory)..\..\bin\$(Configuration)\netstandard2.0\</NuGetTaskFolder>    </PropertyGroup>
    <PropertyGroup Condition=" $(IsInGitHookTaskDebugMode) != 'true' ">        <NuGetTaskFolder >$(MSBuildThisFileDirectory)..\tasks\netstandard2.0\</NuGetTaskFolder>    </PropertyGroup>
    <UsingTask TaskName="LargeFileInterceptTask" AssemblyFile="$(NuGetTaskFolder)Jgrass.GitHookMsbuildTask.dll" />
    <Target Name="GitHookTask" AfterTargets="Build">        <LargeFileInterceptTask />    </Target>
</Project>

4.1 Debugger 项目

Jgrass.GitHookMsbuildTask.Debugger 项目的配置

代码语言:javascript
代码运行次数:0
复制
// Jgrass.GitHookMsbuildTask.Debugger.csproj<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>    <OutputType>Exe</OutputType>    <TargetFramework>net8.0</TargetFramework>    <ImplicitUsings>enable</ImplicitUsings>    <Nullable>enable</Nullable>    <IsInGitHookTaskDebugMode>true</IsInGitHookTaskDebugMode>  </PropertyGroup>
  <Import Project="..\Jgrass.GitHookMsbuildTask\Assets\build\Jgrass.GitHookMsbuildTask.targets" />
</Project>

设置 IsInGitHookTaskDebugModetrue, 使用 Import 直接导入相对路径下的 .targets 文件。

要调试的时候,记得开启 Debugger.Launch()

代码语言:javascript
代码运行次数:0
复制
public class LargeFileInterceptTask : Microsoft.Build.Utilities.Task{    public override bool Execute()    {#if DEBUG        Debugger.Launch();#endif
        Log.LogMessage(MessageImportance.High, "Normal Message");        Log.LogWarning("Warning Message");        Log.LogError("Error Message");
        return false; // Task 执行失败,会让引用了此 Task 的项目编译失败。    }}

编译 Jgrass.GitHookMsbuildTask.Debugger 项目就会触发调试入口了。

4.2 Task 使用演示

以下是 Jgrass.GitHookMsbuildTask.Sample 的配置,很简单,就是通过 PackageReference 引入普通的 nuget 包。

本地测试时,需要将 nuget 包所在路径,添加为 nuget 包源。

代码语言:javascript
代码运行次数:0
复制
// Jgrass.GitHookMsbuildTask.Sample.csproj<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>    <OutputType>Exe</OutputType>    <TargetFramework>net8.0</TargetFramework>    <ImplicitUsings>enable</ImplicitUsings>    <Nullable>enable</Nullable>    <LangVersion>latest</LangVersion>  </PropertyGroup>
  <ItemGroup>    <PackageReference Include="Jgrass.GitHookMsbuildTask" Version="0.0.7-alpha" />  </ItemGroup>
</Project>

编译项目,如果看到如下输出,就说明成功啦~

🍉 5 其它

项目源码:https://gitee.com/Jasongrass/demo-msbuild-task

前面说 LargeFileInterceptTask 中,打算实现一个 git hook 的功能,具体怎么实现,以后再说吧。想法的源头来自这里:git 禁止大文件提交到仓库中

参考资料

原文链接: https://cloud.tencent.com/developer/article/2481580

本作品采用 「署名 4.0 国际」 许可协议进行许可,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024年11月24日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 🍉 1 MSBuild Task
  • 🍉 2 一些弯路
  • 🍉 3 自定义 MSBuild Task
    • 3.1 新建 Task 项目
    • 3.2 新建一个 Task
    • 3.3 在目标项目中使用
  • 🍉 4 将自定义 Task 打包成 nuget 包
    • 4.1 Task 实现项目配置
    • 4.1 Debugger 项目
    • 4.2 Task 使用演示
  • 🍉 5 其它
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档