製作一個 dll 引用的 NuGet 包簡直是一鍵完成,無論是不是多框架項目;製作 dotnet-tools 也是如此。但如果需要自定義一些編譯步驟,那麼就需要在製作 NuGet 包時做很多的特殊處理了。
本文介紹製作適用於多框架項目的 NuGet 工具包時應該注意的問題。
本文內容
背景知識
NuGet 包內的文件夾結構
回顧一下 NuGet 包的文件夾結構:
+ /
+ lib/
+ ref/
+ runtimes/
+ content/
+ build/
+ buildMultiTargeting/
+ buildTransitive
+ tools/
由於涉及到自定義 NuGet 包的代碼都寫在 build
buildMultiTargeting
和 buildTransitive
中,其他都不涉及到 NuGet 包在編譯期間會做的事情,另外,buildTransitive
是用來處理包傳遞過程中的編譯過程的,所以我們本文只說也只需要說 build
和 buildMultiTargeting
。
這裏面的代碼都是用 Target
寫出來的,如果你對此不瞭解,建議閱讀這些博客:
- 理解 C# 項目 csproj 文件格式的本質和編譯流程 - walterlv
- 從零開始製作 NuGet 源代碼包(全面支持 .NET Core / .NET Framework / WPF 項目) - walterlv
製作有自定義功能的 NuGet 包
我之前寫過一些關於如何製作各種高級功能的 NuGet 包的博客:
- 如何創建一個基於命令行工具的跨平臺的 NuGet 工具包 - walterlv
- 如何創建一個基於 MSBuild Task 的跨平臺的 NuGet 工具包 - walterlv
- 從零開始製作 NuGet 源代碼包(全面支持 .NET Core / .NET Framework / WPF 項目) - walterlv
按照上面的博客製作出來的 NuGet 包其實是適用於單框架項目和多框架項目的,甚至也適用於傳統的非 SDK 風格的項目。
關於單框架和多框架項目,就是項目文件中這裏的差別:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- 單框架項目 -->
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- 多框架項目 -->
<TargetFrameworks>netcoreapp3.1;net48</TargetFrameworks>
</PropertyGroup>
</Project>
但是,有的小夥伴希望探索一些更高級的用法,所以可能會遇到在多框架項目中,NuGet 包自定義的功能不執行的問題。
接下來,我們瞭解一下在單框架和多框架下 NuGet 包執行上的不同。
執行時機
我們打出這樣的兩種 NuGet 包,一種是僅包含 build
文件夾而不包含 buildMultiTargeting
文件夾;一種是包含 build
文件夾和 buildMultiTargeting
文件夾。
我們的目標項目一種是單框架項目;一種是多框架項目。
於是我們可以得到這樣的四種不同的組合情況:
- 僅含
build
文件夾的 NuGet 包裝到單框架項目中 - 僅含
build
文件夾的 NuGet 包裝到多框架項目中 - 包含
build
和buildMultiTargeting
文件夾的 NuGet 包裝到單框架項目中 - 包含
build
和buildMultiTargeting
文件夾的 NuGet 包裝到多框架項目中
1. 僅含 build
文件夾的 NuGet 包裝到單框架項目中
在這種情況下,build
文件夾中的 .props
和 .targets
文件在目標項目編譯時正常執行。
2. 僅含 build
文件夾的 NuGet 包裝到多框架項目中
在這種情況下,build
文件夾中的 .props
和 .targets
文件,會分別在目標項目編譯每個框架的時候執行一次。
例如這種項目:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- 多框架項目 -->
<TargetFrameworks>netcoreapp3.1;net48</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Walterlv.NullableAttributes.Source" Version="0.15.0" />
</ItemGroup>
</Project>
那麼,在編譯 netcoreapp3.1
框架的時候會執行一次 Walterlv.NullableAttributes.Source 包中 build
文件夾中的編譯任務;在編譯 net48
框架的時候又會執行一次 Walterlv.NullableAttributes.Source 包中 build
文件夾中的編譯任務。
3. 包含 build
和 buildMultiTargeting
文件夾的 NuGet 包裝到單框架項目中
在這種情況下,buildMultiTargeting
中的任何編譯任務相當於不存在。編譯過程與情況 1 是完全一樣的。
4. 包含 build
和 buildMultiTargeting
文件夾的 NuGet 包裝到多框架項目中
從 NuGet 5.x 版本開始在這種情況下,build
中的內容和 buildMultiTargeting
中的編譯任務會同時參與編譯。
依然舉例這樣的目標項目(不過使用了含 buildMultiTargeting
的 NuGet 包):
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- 多框架項目 -->
<TargetFrameworks>netcoreapp3.1;net48</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Walterlv.NullableAttributes.Source" Version="2.1.1" />
</ItemGroup>
</Project>
編譯一開始,會將 buildMultiTargeting
中的編譯任務加入執行。在編譯 netcoreapp3.1
框架的時候會執行一次 Walterlv.NullableAttributes.Source 包中 build
文件夾中的編譯任務;在編譯 net48
框架的時候又會執行一次 Walterlv.NullableAttributes.Source 包中 build
文件夾中的編譯任務。而這兩個單獨框架的編譯結束後,buildMultiTargeting
中的任務纔會結束。
也就是說,這兩個編譯任務文件夾中的編譯任務是都會執行的。但是:
兩者參與編譯的 Targets 不一樣。
下表中列出了在你沒有編寫任何擴展的任務或者干預已有 Target 執行的情況下,默認可以依賴的 Target(指的是可以通過 BeforeTargets="xx"
或 AfterTargets="xx"
的方式擴展編譯任務:
可依賴的 Target | build | buildMultiTargeting |
---|---|---|
BeforeCompile | ✔ | ❌ |
Compile | ✔ | ❌ |
CoreCompile | ✔ | ❌ |
AfterCompile | ✔ | ❌ |
BeforeBuild | ✔ | ❌ |
Build | ✔ | ✔ |
AfterBuild | ✔ | ❌ |
BeforeRebuild | ❌ | ❌ |
Rebuild | ❌ | ✔(如果強行執行) |
AfterRebuild | ❌ | ❌ |
BeforeClean | ✔(如果強行執行) | ❌ |
Clean | ✔(如果強行執行) | ✔(如果強行執行) |
AfterClean | ✔(如果強行執行) | ❌ |
注:強制執行說的是一般編譯時不會執行,你需要在命令中指定執行這個 Target。也對應到 Visual Studio 裏的“重新編譯”和“清理”的功能。
爲了更好理解上表,這裏給出一個例子。下面的代碼如果在 build
文件夾中則會在編譯過程輸出一堆星號,而如果在 buildMultiTargeting
文件夾中則不會執行。而無論目標項目是否是多框架的。但換成 AfterBuild
則會兩個文件夾中都輸出。
<Target Name="WalterlvDemoTarget" AfterTargets="Build">
<Message Text="****************************************************************" />
</Target>
當然,不要被這個第 4 種情況帶歪了!如果你的 NuGet 包依然只有一個 build
文件夾,那麼上面的所有 Targets 都是會執行的。
參考資料
- Create a NuGet package using nuget.exe CLI - Microsoft Docs
- Allow package authors to define build assets transitive behavior · NuGet/Home Wiki
我的博客會首發於 https://blog.walterlv.com/,而 CSDN 會從其中精選發佈,但是一旦發佈了就很少更新。
如果在博客看到有任何不懂的內容,歡迎交流。我搭建了 dotnet 職業技術學院 歡迎大家加入。
本作品採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。歡迎轉載、使用、重新發布,但務必保留文章署名呂毅(包含鏈接:https://walterlv.blog.csdn.net/),不得用於商業目的,基於本文修改後的作品務必以相同的許可發佈。如有任何疑問,請與我聯繫。