使用浮动版本分发 NuGet 包的正确方法

Correct way to distribute NuGet package with floating version

我正在创建一个依赖于 Xamarin.Forms 的 NuGet 包。该包应该适用于任何最新版本的 Forms,因此我将其设置为:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <PackageId>MyCompany.FormsExtras</PackageId>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Xamarin.Forms" Version="4.*" />
  </ItemGroup>
</Project>

在本地构建并发布以测试...

$ dotnet pack -c Release -p:Version=0.9.0
$ nuget add bin/Release/MyCompany.FormsExtras.0.9.0.nupkg -source ~/Dropbox/Packages/

当时我运行这些命令,Xamarin.Forms4.1.0.555618是最新版本

我现在正试图将这个包拉入一个现有项目,该项目直接依赖于 Xamarin.Forms 的不同旧版本:

  <ItemGroup>
    <PackageReference Include="Xamarin.Forms" Version="4.0.0.425677" />
  </ItemGroup>

...但添加包失败并出现此错误:

Detected package downgrade: Xamarin.Forms from 4.1.0.555618 to 4.0.0.425677. Reference the package directly from the project to select a different version. 
 MyCompany.ToDo.Forms -> MyCompany.FormsExtras 0.9.0 -> Xamarin.Forms (>= 4.1.0.555618) 
 MyCompany.ToDo.Forms -> Xamarin.Forms (>= 4.0.0.425677)

我的印象是我的包 PackageReference 中指定的浮动版本应该允许这个工作?我错过了一步,还是我误解了浮动版本的工作原理?

我已通读 MS article on package dependency resolution。我也尝试搜索错误消息和 "floating version" 但我只在消费者方面找到解决方法;我想在我的包装上解决这个问题,这样消费者就不必再费力了。

非常感谢任何帮助……

TL;DR 版本:将您的 PackageReference 更改为使用 Version=4.0.0,或与最低版本的项目使用的相同版本,而不是 Version=4.*

项目和包之间存在误解。一个项目可以用来创建一个包,但它们有不同的特性。特别是,浮动版本是 PackageReference 的一个特性,这是项目定义其包依赖项的方式。 The docs say:

When using the PackageReference format, NuGet also supports using a wildcard notation, *, for Major, Minor, Patch, and pre-release suffix parts of the number. Wildcards are not supported with the packages.config format.

没有明确说明 nuspec 也不支持通配符(包包含 nuspec,而不是 PackageReferences),但它不受支持,因此您的包具有 >= 4.1.0.555618 的依赖性。然后正如 Matt 在评论中指出的那样,由于最近的获胜规则,您收到降级警告(并且 NuGet 将降级视为警告,但 .NET Core SDK 将其提升为错误。我不知道 Xamarin 是否是否一样)。如果您希望您的包支持 >= 4.0.0,那么您需要将 MyCompany.FormsExtras 项目的 PackageReference for Xamarin.Forms 更改为版本 4.0.0(尽管您应该使用确切的可用的最低版本,否则每个使用你的包的项目在找不到与你的包的依赖项完全匹配时都会受到性能影响),而不是 4.*.

我在实现通配符后很长时间才加入 NuGet 团队,并且我没有努力寻找设计规范,所以我完全是猜测,但我相信打包使用 4.* 不会导致包支持 >= 4.0.0 是因为 NuGet 正在尽最大努力猜测支持哪些包版本,以最大限度地减少使用包的开发人员的运行时故障。

为了理解,考虑最极端的情况,使用通配符 *。除非 NuGet 打算以某种方式使用每个版本的依赖项测试您的项目,以检查它实际兼容的包版本(这样做完全不可行,即使这样做也会使打包变得非常慢),最简单的两个选项是使用 >= 0.0.0,因为它在精神上等同于 *,或者使用上次恢复项目时解决的依赖项版本。

使用 >= 0.0.0 是一个问题,因为如果包的第一个版本与当前版本相比可能有重大更改,或者您的项目可能正在使用不可用的 API在最早的版本中。因此,尽管您的项目使用 *,但它实际上并不与该依赖项的所有版本兼容,因此 >= 0.0.0 可能无法正常工作。您的项目使用的包越旧或版本越多,该包的最旧版本与您的项目一起使用的可能性就越小。

类似地,语义版本控制指定次要版本表示 non-breaking 更改,但确实包含新的 API。您打包到包中的项目使用了 4.1.x 您的依赖项,而 NuGet 无法知道 1) 包是否严格符合语义版本控制(我的猜测是非常非常少)和 2) 如果您项目使用的 API 仅适用于 4.1.x,不适用于 4.0.x。鉴于并非所有包都严格符合语义版本控制,即使将 4.1.* 更改为 4.1.0.

也是不安全的

希望我已经让您相信,NuGet 在将项目打包到包中时如何处理通配符的行为是最好的方法。它旨在最大限度地提高可用软件包的百分比 "out of the box"。如果没有,即使您不同意它是最好的实现,您现在也应该了解它是如何工作的。