日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

發布時間:2023/12/4 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 如何创建一个基于 MSBuild Task 的跨平台的 NuGet 工具包 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

MSBuild 的 Task 為我們擴展項目的編譯過程提供了強大的擴展性,它使得我們可以用 C# 語言編寫擴展;利用這種擴展性,我們可以為我們的項目定制一部分的編譯細節。NuGet 為我們提供了一種自動導入 .props 和 .targets 的方法,同時還是一個 .NET 的包平臺;我們可以利用 NuGet 發布我們的工具并自動啟用這樣的工具。

制作這樣的一個跨平臺 NuGet 工具,我們能夠為安裝此工具的項目提供自動的但定制化的編譯細節——例如自動生成版本號,自動生成某些中間文件等。

本文更偏向于入門,只在幫助你一步一步地制作一個最簡單的 NuGet 工具包,以體驗和學習這個過程。然后我會在另一篇博客中完善其功能,做一個完整可用的 NuGet 工具。


關于創建跨平臺 NuGet 工具包的博客,我寫了兩篇。一篇介紹寫基于 MSBuild Task 的 dll,一篇介紹寫任意的命令行工具,可以是用于 .NET Framework 的 exe,也可以是基于 .NET Core 的 dll,甚至可以是使用本機工具鏈編譯的平臺相關的各種格式的命令行工具。內容是相似的但關鍵的坑不同。我分為兩篇可以減少完成單個任務的理解難度:

  • 如何創建一個基于 MSBuild Task 的跨平臺的 NuGet 工具包

  • 如何創建一個基于命令行工具的跨平臺的 NuGet 工具包

本文內容
  • 第零步:前置條件

  • 第一步:創建一個項目,用來寫工具的核心邏輯

  • 第二步:組織 NuGet 目錄

  • 第三步:編寫 Target

  • 第四部:調試

    • 讓我們的 Target 能夠正確找到我們新生成的 dll

    • 準備一個用于測試 Task 的測試項目

    • 讓我們自定義的 Task 開始工作,并能夠進入斷點

  • 第五步:發揮你的想象力

    • .targets 向 Task 傳參數

    • Task 向 .targets 返回參數

    • 在 Target 里編寫調試代碼

    • 在 Task 輸出錯誤或警告

    • 加入差量編譯支持

    • 本地測試 NuGet 包

  • 總結

    • 參考資料

第零步:前置條件

第一步:創建一個項目,用來寫工具的核心邏輯

為了方便制作跨平臺的 NuGet 工具,新建項目時我們優先選用 .NET Core Library 項目或 .NET Standard Library 項目。

緊接著,我們需要打開編輯此項目的 .csproj 文件,將目標框架改成多框架的,并填寫必要的信息。

<!-- Walterlv.NuGetTool.csproj --><Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><!-- 給一個初始的版本號。 --><Version>1.0.0-alpha</Version><!-- 使用 .NET Framework 4.7 和 .NET Core 2.0。 要點 1: - 加入 net47 的支持是為了能讓基于 .NET Framework 的 msbuild 能夠使用此工具編譯; - 加入 netcoreapp2.0 的支持是為了能讓基于 .NET Core 的 dotnet build (Roslyn) 能夠使用此工具編譯; - 當然 net47 太新了,只適用于 Visual Studio 2017 的較新版本,如果你需要照顧到更多用戶,建議使用 net46。 要點 2: 注意,我們使用 NuGet 包來依賴 Task 框架,但此 NuGet 包要求的最低 .NET Framework 版本為 4.6。 如果需要制作 .NET Framework 4.5 及以下版本,就必須改為引用以下程序集: - Microsoft.Build - Microsoft.Build.Framework - Microsoft.Build.Tasks.v4.0 - Microsoft.Build.Utilities.v4.0 --><TargetFrameworks>net47;netcoreapp2.0</TargetFrameworks><!-- 這個就是創建項目時使用的名稱。 --><AssemblyName>Walterlv.NuGetTool</AssemblyName><!-- 此值設為 true,才會在編譯之后生成 NuGet 包。 --><GeneratePackageOnBuild>true</GeneratePackageOnBuild><!-- 作者的 Id,如果要發布到 nuget.org,那么這里就是 NuGet 用戶 Id。 --><Authors>walterlv</Authors></PropertyGroup></Project>

然后,安裝如下 NuGet 包:

  • Microsoft.Build.Framework:?提供了編寫?ITask?的框架,有了這個才能寫?ITask

  • Microsoft.Build.Utilities.Core:?提供了?ITask?框架的基本實現,這樣才能用更少的代碼寫完?Task

要特別注意:由于我們是一個 NuGet 工具,不需要被其他項目直接依賴,所以此項目的依賴包不應該傳遞到下一個項目中。所以請將所有的 NuGet 包資產都聲明成私有的,方法是在 NuGet 包的引用后面加上?PrivateAssets="All"。想了解?PrivateAssets?的含義一起相關屬性,可以閱讀我的另一篇文章項目文件中的已知 NuGet 屬性(使用這些屬性,創建 NuGet 包就可以不需要 nuspec 文件啦) - 呂毅。

<ItemGroup><PackageReference Include="Microsoft.Build.Framework" Version="15.6.85" /><PackageReference Include="Microsoft.Build.Utilities.Core" Version="15.6.85" /><PackageReference Update="@(PackageReference)" PrivateAssets="All" /></ItemGroup>

接下來就是取名字的時間了!為?Class1?類改一個名字。這個類將成為我們這個 NuGet 工具包的入口類。

比如我們想做一個用 Git 提交信息來生成版本號的類,可以叫做 GitVersion;想做一個生成多語言文件的類,可以叫做 LangGenerator。在這里,為了示范而不是真正的實現功能,我取名為 DemoTool。

取好名字之后,讓這個類繼承自?Microsoft.Build.Utilities.Task:

// DemoTool.csusing Microsoft.Build.Utilities;namespace Walterlv.NuGetTool{public class DemoTool : Task{ public?override?bool?Execute()?{ return true;}}}

這時進行編譯,我們的 NuGet 包就會出現在項目的輸出目錄?bin\Debug?下了。

第二步:組織 NuGet 目錄

剛剛生成的 NuGet 包還不能真正拿來用。事實上你也可以拿去安裝,不過最終的效果只是加了一個毫無作用的引用程序集而已(順便還帶來一堆垃圾的間接引用)。

所以,我們需要進行“一番配置”,使得這個項目編譯成一個NuGet 工具,而不是一個依賴包。

現在,介紹一下 NuGet 預設的目錄(如果你想看,可以去解壓 .nupkg 文件):

// 根目錄,用來放 readme.txt 的(已經有人提 issue 要求加入 markdown 支持了)+ /// 用來放引用程序集 .dll,文檔注釋 .xml 和符號文件 .pdb 的+ lib/// 用來放那些與平臺相關的 .dll/.pdb/.pri 的+ runtimes/// 任意種類的文件,在這個文件夾中的文件會在編譯時拷貝到輸出目錄(保持文件夾結構)+ content/// 這里放 .props 和 .targets 文件,會自動被 NuGet 導入,成為項目的一部分(要求文件名與包名相同)+ build/// 這里也是放 .props 和 .targets 文件,會自動被 NuGet 導入,成為項目的一部分(要求文件名與包名相同)+ buildMultiTargeting/// PowerShell 腳本或者程序,在這里的工具可以在“包管理控制臺”(Package Manager Console) 中使用+ tools/

▲ 以上結構可以去官網翻閱原文?How to create a NuGet package - Microsoft Docs,不過我這里額外寫了一個預設目錄?buildMultiTargeting,官方文檔卻沒有說。

注意到我們的 csproj 文件中的?<TargetFrameworks>?節點嗎?如果指定為單個框架,則自動導入的是?build?目錄下的;如果指定為多個框架,則自動導入的是?buildMultiTargeting?目錄下的。

我們的初衷是做一個 NuGet 工具,所以我們需要選擇合適的目錄來存放我們的輸出文件。

我們要放一個?Walterlv.NuGetTool.targets?文件到?build?和?buildMultiTargeting?文件夾中,以便能夠讓我們定制編譯流程。我們要讓我們寫的 dll(也就是那個?Task)能夠工作,但是以上任何預定義的文件夾都不能滿足我們的要求,于是我們建一個自定義的文件夾,取名為?tasks,這樣 NuGet 便不會對我們的這個 dll 進行特殊處理,而將處理權全部交給我們。

于是我們自己的目錄結構為:

+ build/- Walterlv.NuGetTool.targets+ buildMultiTargeting/- Walterlv.NuGetTool.targets+ tasks/+ net47/- Walterlv.NuGetTool.dll+ netcoreapp2.0/- Walterlv.NuGetTool.dll- readme.txt

那么,如何改造我們的項目才能夠生成這樣的 NuGet 目錄結構呢?

我們先在 Visual Studio 里建好文件夾:

隨后去編輯項目的 .csproj 文件,在最后的?</Project>?前面添加下面這些項:

<!-- Walterlv.NuGetTool.csproj --><ItemGroup><None Include="Assets\build\**" Pack="True" PackagePath="build\" /><None Include="Assets\buildMultiTargeting\**" Pack="True" PackagePath="buildMultiTargeting\" /><None Include="Assets\readme.txt" Pack="True" PackagePath="" /></ItemGroup>

None?表示這一項要顯示到 Visual Studio 解決方案中(其實對于不認識的文件,None?就是默認值);Include?表示相對于項目文件的路徑(支持通配符);Pack?表示這一項要打包到 NuGet;PackagePath?表示這一項打包到 NuGet 中的路徑。(如果你想了解更多 csproj 中的 NuGet 屬性,可以閱讀我的另一篇文章:項目文件中的已知 NuGet 屬性(使用這些屬性,創建 NuGet 包就可以不需要 nuspec 文件啦) - 呂毅)

后來的我們

主演:井柏然 / 周冬雨 / 田壯壯

貓眼電影演出 廣告 購買

這樣的一番設置,我們的?build、buildMultiTargeting?和?readme.txt?準備好了,但是?tasks?文件夾還沒有。由于我們是把我們生成的 dll 放到?tasks?里面,第一個想到的當然是修改輸出路徑——然而這是不靠譜的,因為 NuGet 并不識別輸出路徑。事實上,我們還可以設置一個屬性?<BuildOutputTargetFolder>,將值指定為?tasks,那么我們就能夠將我們的輸出文件打包到 NuGet 對應的?tasks?文件夾下了。

至此,我們的 .csproj 文件看起來像如下這樣(為了減少行數,我已經去掉了注釋):

<!-- Walterlv.NuGetTool.csproj --><Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><Version>1.0.0-alpha</Version><AssemblyName>Walterlv.NuGetTool</AssemblyName><GeneratePackageOnBuild>true</GeneratePackageOnBuild><!-- ↓ 新增的屬性 --><BuildOutputTargetFolder>tasks</BuildOutputTargetFolder><!-- ↓ 新增的屬性 --><NoPackageAnalysis>true</NoPackageAnalysis><!-- ↓ 新增的屬性 --><DevelopmentDependency>true</DevelopmentDependency><Authors>walterlv</Authors></PropertyGroup><ItemGroup><PackageReference Include="Microsoft.Build.Framework" Version="15.6.85" /><PackageReference Include="Microsoft.Build.Utilities.Core" Version="15.6.85" /><!-- ↓ 在第一步中不要忘了這一行 --><PackageReference Update="@(PackageReference)" PrivateAssets="All" /></ItemGroup><ItemGroup><Folder Include="Assets\tasks\" /></ItemGroup><ItemGroup><!-- ↓ 新增的三項 --><None Include="Assets\build\**" Pack="True" PackagePath="build\" /><None Include="Assets\buildMultiTargeting\**" Pack="True" PackagePath="buildMultiTargeting\" /><None Include="Assets\readme.txt" Pack="True" PackagePath="" /></ItemGroup></Project>

注意到我同時還在文件中新增了另外兩個屬性配置?NoPackageAnalysis?和?DevelopmentDependency。由于我們沒有?lib?文件夾,所以 NuGet 會給出警告,NoPackageAnalysis?將阻止這個警告。DevelopmentDependency?是為了說明這是一個開發依賴,設置為 true 將阻止包作為依賴傳遞給下一個項目。(事實上這又是官方的一個騙局!因為新版本的 NuGet 竟然去掉了這個功能!,已經被吐槽了,詳見:PackageReference should support DevelopmentDependency metadata · Issue #4125 · NuGet/Home)。關于這些屬性更詳細的解釋,依然可以參見:項目文件中的已知 NuGet 屬性(使用這些屬性,創建 NuGet 包就可以不需要 nuspec 文件啦) - 呂毅。

現在再嘗試編譯一下我們的項目,去輸出目錄下解壓查看 nupkg 文件,你就能看到期望的 NuGet 文件夾結構了;建議一個個點進去看,你可以看到我們準備好的空的?Walterlv.NuGetTool.targets?文件,也能看到我們生成的?Walterlv.NuGetTool.dll。

第三步:編寫 Target

.targets 文件是對項目功能進行擴展的關鍵文件,由于安裝 NuGet 包會自動導入包中的此文件,所以它幾乎相當于我們功能的入口。

現在,我們需要徒手編寫這個文件了。

<!-- Assets\build\Walterlv.NuGetTool.targets --><Project><PropertyGroup><!-- 我們使用 $(MSBuildRuntimeType) 來判斷編譯器是 .NET Core 的還是 .NET Framework 的。 然后選用對應的文件夾。--><NuGetWalterlvTaskFolder Condition=" '$(MSBuildRuntimeType)' == 'Core'">$(MSBuildThisFileDirectory)..\tasks\netcoreapp2.0\</NuGetWalterlvTaskFolder><NuGetWalterlvTaskFolder Condition=" '$(MSBuildRuntimeType)' != 'Core'">$(MSBuildThisFileDirectory)..\tasks\net47\</NuGetWalterlvTaskFolder></PropertyGroup><UsingTask TaskName="Walterlv.NuGetTool.DemoTool" AssemblyFile="$(NuGetWalterlvTaskFolder)\Walterlv.NuGetTool.dll" /><Target Name="WalterlvDemo" BeforeTargets="CoreCompile"><DemoTool /></Target></Project>

targets 的文件結構與 csproj 是一樣的,你可以閱讀我的另一篇文章?理解 C# 項目 csproj 文件格式的本質和編譯流程 - 呂毅?了解其結構。

上面的文件中,我們指定?Target?的執行時機為?CoreCompile?之前,也就是編譯那些 .cs 文件之前。在這個時機,我們可以修改要編譯的 .cs 文件。如果想了解更多關于?Target?執行時機或順序相關的資料,可以閱讀:Target Build Order。

別忘了我們還有一個?buildMultiTargeting?文件夾,也要放一個幾乎一樣功能的 targets 文件;不過我們肯定不會傻到復制一個一樣的。我們在?buildMultiTargeting?文件夾里的 targets 文件中寫以下內容,這樣我們的注意力便可以集中在前面的 targets 文件中了。

<!-- Assets\buildMultiTargeting\Walterlv.NuGetTool.targets --><Project><!-- 直接 Import 我們在 build 中寫的那個 targets 文件。 NuGet 留下了為多框架項目提供特殊擴展的方案,其實有時候也是很有用的。--><Import Project="..\build\Walterlv.NuGetTool.targets" /></Project>

第四部:調試

嚴格來說,寫到這里,我們的跨平臺 NuGet 工具已經寫完了。在以上狀態下,你只需要編譯一下,就可以獲得一個跨平臺的基于 MSBuild Task 的 NuGet 工具。只是——你肯定會非常郁悶——心里非常沒譜,這工具到底有沒有工作起來!有沒有按照我預期的進行工作!如果遇到了 Bug 怎么辦!

于是現在我們來掌握一些調試技巧,這樣才方便我們一步步完善我們的功能嘛!額外插一句:以上第一到第三步幾乎都是結構化的步驟,其實非常適合用工具來自動化完成的。

讓我們的 Target 能夠正確找到我們新生成的 dll

你應該注意到,我們的 targets 文件在?Assets\build?目錄下,而我們的?Assets?文件夾下并沒有真實的?tasks?文件夾(里面是空的)。于是我們希望在調試狀態下,dll 能夠指向輸出目錄下。于是我們修改 targets 文件添加配置:

<!-- Assets\build\Walterlv.NuGetTool.targets --><Project><PropertyGroup Condition=" $(IsInDemoToolDebugMode) == 'True' "><NuGetWalterlvTaskFolder Condition=" '$(MSBuildRuntimeType)' == 'Core'">$(MSBuildThisFileDirectory)..\..\bin\$(Configuration)\netcoreapp2.0\</NuGetWalterlvTaskFolder><NuGetWalterlvTaskFolder Condition=" '$(MSBuildRuntimeType)' != 'Core'">$(MSBuildThisFileDirectory)..\..\bin\$(Configuration)\net47\</NuGetWalterlvTaskFolder></PropertyGroup><PropertyGroup Condition=" $(IsInDemoToolDebugMode) != 'True' "><NuGetWalterlvTaskFolder Condition=" '$(MSBuildRuntimeType)' == 'Core'">$(MSBuildThisFileDirectory)..\tasks\netcoreapp2.0\</NuGetWalterlvTaskFolder><NuGetWalterlvTaskFolder Condition=" '$(MSBuildRuntimeType)' != 'Core'">$(MSBuildThisFileDirectory)..\tasks\net47\</NuGetWalterlvTaskFolder></PropertyGroup><UsingTask TaskName="Walterlv.NuGetTool.DemoTool" AssemblyFile="$(NuGetWalterlvTaskFolder)\Walterlv.NuGetTool.dll" /><Target Name="WalterlvDemo" BeforeTargets="CoreCompile"><DemoTool /></Target></Project>

這樣,我們就擁有了一個可以供用戶設置的屬性?<IsInDemoToolDebugMode>?了。

準備一個用于測試 Task 的測試項目

接著,我們在解決方案中新建一個調試項目?Walterlv.Debug(我選用了 .NET Standard 2.0 框架)。然后在它的 csproj 中?<Import>?我們剛剛的 .targets 文件,并設置?<IsInDemoToolDebugMode>?屬性為?True:

<!-- Walterlv.Debug.csproj --><Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><TargetFramework>netstandard2.0</TargetFramework><IsInDemoToolDebugMode>True</IsInDemoToolDebugMode></PropertyGroup><Import Project="..\Walterlv.NuGetTool\Assets\build\Walterlv.NuGetTool.targets" /></Project>

當準備好基本的調試環境之后,我們的解決方案看起來是下面這樣的樣子:

讓我們自定義的 Task 開始工作,并能夠進入斷點

最簡單能夠讓 DemoTool 這個自定義的 Task 進入斷點的方式當然是加上?Debugger.Launch();?了,就像這樣:

// DemoTool.csusing System.Diagnostics;using Microsoft.Build.Utilities;namespace Walterlv.NuGetTool{public class DemoTool : Task{ public?override?bool?Execute()?{ // 新增了啟動調試器的代碼。Debugger.Launch(); return true;}}}

這樣,一旦此函數開始執行,Windows 將顯示一個選擇調試器的窗口,我們選擇當前打開的 Visual Studio 即可。

當然,也有一些比較正統的方法,為了使這篇文章盡可能簡單,我只附一張圖,如果有需要,可以自己去嘗試:

現在,我們去 Walterlv.Debug 目錄下輸入?msbuild?命令,在輸出到如下部分的時候,就會進入我們的斷點了:

這下,我們的調試環境就全部搭建好了,你可以發揮你的想象力在 Task 里面隨意揮灑你的代碼!

當然,只要你記得去掉?Debugger.Launch();,或者加上?#if DEBUG?這樣的條件編譯,那么隨時打包就是一個可以發布的跨平臺 NuGet 工具包了。

提示:一旦調試環境搭建好,你可能會遇到編譯 Walterlv.NuGetTool 項目時,發現 dll 被占用的情況,這時,打開任務管理器結束掉 msbuild.exe 進行即可。

第五步:發揮你的想象力

想象力是沒有限制的,不過如果不知道 Task 能夠為我們提供到底什么樣的功能,也是無從下手的。這一節我會說一些 Task 在 C# 代碼和 .targets 文件中的互相操作。

.targets 向 Task 傳參數

.targets 向 Task 傳參數只需要寫一個屬性賦值的句子就可以了:

<!-- Assets\build\Walterlv.NuGetTool.targets --><Target Name="WalterlvDemo" BeforeTargets="CoreCompile"><DemoTool IntermediateOutputPath="$(IntermediateOutputPath)" /></Target>

這里,$(IntermediateOutputPath)?是 msbuild 編譯期間會自動設置的全局屬性,代表此項目編譯過程中臨時文件的存放路徑(也就是我們常見的 obj 文件夾)。當然,使用?dotnet build?或者?dotnet msbuild?也是有這樣的全局屬性的。我們為?<DemoTool>節點也加了一個屬性,名為?IntermediateOutputPath。

在 DemoTool 的 C# 代碼中,只需要寫一個字符串屬性即可接收這樣的傳參。

// DemoTool.cspublic class DemoTool : Task{public string IntermediateOutputPath { get; set; } public?override?bool?Execute()?{Debugger.Launch(); var intermediateOutputPath = IntermediateOutputPath; return true;}}


▲ 在斷點中我們能夠看到傳進來的參數的值

你可以盡情發揮你的想象力,傳入更多讓人意想不到的參數,實現不可思議的功能。更多 MSBuild 全局參數,可以參考我的另一篇文章項目文件中的已知屬性(知道了這些,就不會隨便在 csproj 中寫死常量啦) - 呂毅。

Task 向 .targets 返回參數

如果只是傳入參數,那么我們頂多只能干一些不痛不癢的事情,或者就是兩者互相約定了一些常量。什么?你說直接去改源代碼?那萬一你的代碼不幸崩潰了,項目豈不被你破壞了!(當然,你去改了源碼,還會破壞 MSBuild 的差量編譯。)

我們新定義一個屬性,但在屬性上面標記?[Output]?特性。這樣,這個屬性就會作為輸出參數傳到 .targets 里了。

// DemoTool.csusing System.Diagnostics;using System.IO;using Microsoft.Build.Framework;using Microsoft.Build.Utilities;namespace Walterlv.NuGetTool{public class DemoTool : Task{ public string IntermediateOutputPath { get; set; }[Output] public string AdditionalCompileFile { get; set; } public?override?bool?Execute()?{Debugger.Launch(); var intermediateOutputPath = IntermediateOutputPath; var additional = Path.Combine(intermediateOutputPath, "DoubiClass.cs");AdditionalCompileFile = Path.GetFullPath(additional);File.WriteAllText(AdditionalCompileFile, @"using System; namespace Walterlv.Debug { public class Doubi { public string Name { get; } private Doubi(string name) => Name = name; public static Doubi Get() => new Doubi(""呂毅""); } }"); return true;}}}

然后,我們在 .targets 里接收這個輸出參數,生成一個屬性:

<!-- Assets\build\Walterlv.NuGetTool.targets --><Target Name="WalterlvDemo" BeforeTargets="CoreCompile"><DemoTool IntermediateOutputPath="$(IntermediateOutputPath)"><Output TaskParameter="AdditionalCompileFile" PropertyName="WalterlvDemo_AdditionalCompileFile" /></DemoTool><ItemGroup><Compile Include="$(WalterlvDemo_AdditionalCompileFile)" /></ItemGroup></Target>

這樣,我們生成的 Walterlv.Debug 調試項目在編譯完成之后,還會額外多出一個“逗比”類。而且——我們甚至能夠直接在 Walterlv.Debug 項目的中使用這個編譯中生成的新類。

使用編譯生成的新類既不會報錯,也不會產生警告下劃線,就像原生寫的類一樣。

如果你要在編譯期間替換一個類而不是新增一個類,例如將 Class1.cs 更換成新類,那么需要將其從編譯列表中移除:

<!-- Assets\build\Walterlv.NuGetTool.targets --><ItemGroup><Compile Remove="Class1.cs" /><Compile Include="$(WalterlvDemo_AdditionalCompileFile)" /></ItemGroup>

需要注意:編譯期間才生成的項(<ItemGroup>)或者屬性(<PropertyGroup>),需要寫在?<Target>?節點的里面。如果寫在外面,則不是編譯期間生效的,而是始終生效的。當寫在外面時,要特別留意可能某些屬性沒有初始化完全,你應該只使用那些肯定能確認存在的屬性或文件。

在 Target 里編寫調試代碼

雖然說以上的每一個步驟我都是一邊實操一邊寫的,但即便如此,本文都寫了 500 多行了,如果你依然能夠不出錯地完成以上每一步,那也是萬幸了!Task 里我能還能用斷點調試,那么 Target 里面怎么辦呢?

我們可以用?<Message>?節點來輸出一些信息:

<!-- Assets\build\Walterlv.NuGetTool.targets --><Target Name="WalterlvDemo" BeforeTargets="CoreCompile"><DemoTool IntermediateOutputPath="$(IntermediateOutputPath)"><Output TaskParameter="AdditionalCompileFile" PropertyName="WalterlvDemo_AdditionalCompileFile" /></DemoTool><Message Text="臨時文件的路徑為:$(WalterlvDemo_AdditionalCompileFile)" /><ItemGroup><Compile Include="$(WalterlvDemo_AdditionalCompileFile)" /></ItemGroup></Target>

在 Task 輸出錯誤或警告

我們繼承了?Microsoft.Build.Utilities.Task,此類有一個?Log?屬性,可以用來輸出信息。使用?LogWarning?方法可以輸出警告,使用?LogError?可以輸出錯誤。如果輸出了錯誤,那么就會導致編譯不通過。

加入差量編譯支持

如果你覺得你自己寫的?Task?執行非常耗時,那么建議加入差量編譯的支持。關于加入差量編譯,可以參考我的另一篇文章每次都要重新編譯?太慢!讓跨平臺的 MSBuild/dotnet build 的 Target 支持差量編譯。

本地測試 NuGet 包

在發布 NuGet 包之前,我們可以先在本地安裝測試。由于我們在?C:\Users\lvyi\Desktop\Walterlv.NuGetTool\Walterlv.NuGetTool\bin\Debug輸出路徑下已經有了打包好的 nupkg 文件,所以可以加一個本地 NuGet 源。

我們找一個其他的項目,然后在 Visual Studio 中設置 NuGet 源為我們那個 NuGet 工具項目的輸出路徑。

這時安裝,編譯完之后,我們就會發現我們的項目生成的 dll 中多出了一個“逗比(Doubi)”類,并且可以在那個項目中編寫使用?Doubi?的代碼了。

總結

不得不說,制作一個跨平臺的基于 MSBuild Task 的 NuGet 工具包還是比較麻煩的,我們總結一下:

  • 準備項目的基本配置(設置各種必要的項目屬性,安裝必要的 NuGet 依賴)

  • 建立好 NuGet 的文件夾結構

  • 編寫 Task 和 Target

  • 新增功能、調試和測試

  • 如果你在實踐的過程中遇到了各種問題,歡迎在下面留言,一般我會在一天之內給予回復。

    如果在閱讀這篇文章時存在一些概念理解上的問題,或者不知道如何擴展本文的功能,可能需要閱讀下我的另一些文章:

    • 理解 C# 項目 csproj 文件格式的本質和編譯流程 - 呂毅

    • 項目文件中的已知屬性(知道了這些,就不會隨便在 csproj 中寫死常量啦) - 呂毅

    • 項目文件中的已知 NuGet 屬性(使用這些屬性,創建 NuGet 包就可以不需要 nuspec 文件啦) - 呂毅

    當然,還有一些正在編寫,過一段時間可以閱讀到。


    參考資料

    • NuGet pack and restore as MSBuild targets - Microsoft Docs

    • Bundling .NET build tools in NuGet

    • Shipping a cross-platform MSBuild task in a NuGet package

    • MSBuild Reserved and Well-Known Properties

    • build process - How does MSBuild check whether a target is up to date or not? - Stack Overflow

    • How to: Build Incrementally

    • How To: Implementing Custom Tasks – Part I – MSBuild Team Blog

    • Overwrite properties with MSBuild - Stack Overflow

    • How to Access MSBuild properties inside custom task

    • visual studio - How to get property value of a project file using msbuild - Stack Overflow

    • davidfowl/NuGetPowerTools: A bunch of powershell modules that make it even easier to work with nuget

    • [MSBuild and Skipping target “?

      " because it has no outputs - Stack Overflow](https://stackoverflow.com/questions/27377095/msbuild-and-skipping-target-targetname-because-it-has-no-outputs)
    • WriteCodeFragment Task

    • Don’t include dependencies from packages.config file when creating NuGet package - Stack Overflow

    • NuGet 2.7 Release Notes - Microsoft Docs

    • PackageReference should support DevelopmentDependency metadata · Issue #4125 · NuGet/Home

    • debugging - How to debug MSBuild Customtask - Stack Overflow

    本文會經常更新,請閱讀原文:?https://walterlv.github.io/post/create-a-cross-platform-msbuild-task-based-nuget-tool.html?,以避免陳舊錯誤知識的誤導,同時有更好的閱讀體驗。

    原文地址: https://walterlv.github.io/post/create-a-cross-platform-msbuild-task-based-nuget-tool.html


    .NET社區新聞,深度好文,歡迎訪問公眾號文章匯總 http://www.csharpkit.com

    Reward

    長按二維碼向我轉賬

    受蘋果公司新規定影響,微信 iOS 版的贊賞功能被關閉,可通過二維碼轉賬支持公眾號。

    總結

    以上是生活随笔為你收集整理的如何创建一个基于 MSBuild Task 的跨平台的 NuGet 工具包的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。