制作一个 dll 引用的 NuGet 包简直是一键完成,无论是不是多框架项目;制作 dotnet-tools 也是如此。但如果需要自定义一些编译步骤,那么就需要在制作 NuGet 包时做很多的特殊处理了。
本文介绍制作适用于多框架项目的 NuGet 工具包时应该注意的问题。
回顾一下 NuGet 包的文件夹结构:
+ /
+ lib/
+ ref/
+ runtimes/
+ content/
+ build/
+ buildMultiTargeting/
+ buildTransitive
+ tools/
由于涉及到自定义 NuGet 包的代码都写在 build
buildMultiTargeting
和 buildTransitive
中,其他都不涉及到 NuGet 包在编译期间会做的事情,另外,buildTransitive
是用来处理包传递过程中的编译过程的,所以我们本文只说也只需要说 build
和 buildMultiTargeting
。
这里面的代码都是用 Target
写出来的,如果你对此不了解,建议阅读这些博客:
我之前写过一些关于如何制作各种高级功能的 NuGet 包的博客:
按照上面的博客制作出来的 NuGet 包其实是适用于单框架项目和多框架项目的,甚至也适用于传统的非 SDK 风格的项目。
关于单框架和多框架项目,就是项目文件中这里的差别:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1TargetFramework>
PropertyGroup>
Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1;net48TargetFrameworks>
PropertyGroup>
Project>
但是,有的小伙伴希望探索一些更高级的用法,所以可能会遇到在多框架项目中,NuGet 包自定义的功能不执行的问题。
接下来,我们了解一下在单框架和多框架下 NuGet 包执行上的不同。
我们打出这样的两种 NuGet 包,一种是仅包含 build
文件夹而不包含 buildMultiTargeting
文件夹;一种是包含 build
文件夹和 buildMultiTargeting
文件夹。
我们的目标项目一种是单框架项目;一种是多框架项目。
于是我们可以得到这样的四种不同的组合情况:
build
文件夹的 NuGet 包装到单框架项目中build
文件夹的 NuGet 包装到多框架项目中build
和 buildMultiTargeting
文件夹的 NuGet 包装到单框架项目中build
和 buildMultiTargeting
文件夹的 NuGet 包装到多框架项目中build
文件夹的 NuGet 包装到单框架项目中在这种情况下,build
文件夹中的 .props
和 .targets
文件在目标项目编译时正常执行。
build
文件夹的 NuGet 包装到多框架项目中在这种情况下,build
文件夹中的 .props
和 .targets
文件,会分别在目标项目编译每个框架的时候执行一次。
例如这种项目:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1;net48TargetFrameworks>
PropertyGroup>
<ItemGroup>
<PackageReference Include="Walterlv.NullableAttributes.Source" Version="0.15.0" />
ItemGroup>
Project>
那么,在编译 netcoreapp3.1
框架的时候会执行一次 Walterlv.NullableAttributes.Source 包中 build
文件夹中的编译任务;在编译 net48
框架的时候又会执行一次 Walterlv.NullableAttributes.Source 包中 build
文件夹中的编译任务。
build
和 buildMultiTargeting
文件夹的 NuGet 包装到单框架项目中在这种情况下,buildMultiTargeting
中的任何编译任务相当于不存在。编译过程与情况 1 是完全一样的。
build
和 buildMultiTargeting
文件夹的 NuGet 包装到多框架项目中从 NuGet 5.x 版本开始在这种情况下,build
中的内容和 buildMultiTargeting
中的编译任务会同时参与编译。
依然举例这样的目标项目(不过使用了含 buildMultiTargeting
的 NuGet 包):
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1;net48TargetFrameworks>
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 都是会执行的。
参考资料
我的博客会首发于 https://blog.walterlv.com/,而 CSDN 会从其中精选发布,但是一旦发布了就很少更新。
如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:https://walterlv.blog.csdn.net/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。