Warm tip: This article is reproduced from stackoverflow.com, please click
nuget xamarin.forms nuget-package

Correct way to distribute NuGet package with floating version

发布于 2020-03-27 10:28:04

I'm creating a NuGet package that depends on Xamarin.Forms. The package should work fine with any recent version of Forms, so I set it up like:

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

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

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

Build and publish locally to test...

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

At the time I ran these commands, Xamarin.Forms 4.1.0.555618 was the latest version.

I'm now trying to pull this package into an existing project which has a direct dependency on a different, older version of Xamarin.Forms:

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

...but it fails to add the package with this error:

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)

I was under the impression that the floating version specified in my package's PackageReference should have allowed this to work? Am I missing a step, or do I just misunderstand how floating versions work?

I've read through the MS article on package dependency resolution. I also tried searching on the error message and "floating version" but I'm only finding workarounds on the consumer side; I'd like to fix this on my packaging so the consumers don't have to jump through hoops.

Any help much appreciated…

Questioner
J. Perkins
Viewed
124
zivkan 2020-03-21 20:32

TL;DR version: Change your PackageReference to use Version=4.0.0, or the same version used by the project with the lowest version, instead of Version=4.*.

There's a misunderstanding between a project and a package. A project can be used to create a package, but they have different features. In particular, floating versions are a features only of PackageReference, which is how a project defines its package dependencies. 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.

It's not explicit about the nuspec not supporting wildcards either (a package contains a nuspec, not PackageReferences), but it isn't supported hence why your package has a dependency of >= 4.1.0.555618. Then as Matt pointed out in the comments, you're getting the downgrade warning because of the nearest wins rule (and NuGet treats the downgrade as a warning, but the .NET Core SDK elevates it to an error. I have no idea if Xamarin does as well or not). If you want your package to support >= 4.0.0, then you need to change the MyCompany.FormsExtras project's PackageReference for Xamarin.Forms to version 4.0.0 (although you should use the exact version of the lowest version available, otherwise every project that uses your package will have a performance hit when it can't find an exact match of your package's dependency), not 4.*.

I joined the NuGet team a long time after wildcards were implemented, and I made no effort of trying to find a design spec, so I'm totally guessing, but I believe the reason why packing a project that uses 4.* does not result in the package supporting >= 4.0.0 is because NuGet is making a best effort guess about what package versions are supported to minimise runtime failures for developers using the package.

To understand, consider the most extreme case, using a wildcard of *. Unless NuGet is going to somehow test your project with every version of your dependency to check which versions of the package it's actually compatible with (totally infeasible to do so, and even if it were it would making packing so very slow), the easiest two options are to either use >= 0.0.0 as that's spiritually equivalent to *, or use the version of the dependency that was resolved the last time the project was restored.

Using >= 0.0.0 is a problem because if the the very first version of a package might have breaking changes compared to the current version, or your project might be using APIs that are not available in earliest versions. Therefore, despite your project using *, it's not actually compatible with all versions of that dependency, so >= 0.0.0 might not work. The older, or the more versions, the package your project uses has, the less likely that the oldest versions of that package will work with your project.

Similarly, Semantic Versioning specifies that the minor version signifies non-breaking changes, but does contain new APIs. Your project that was packed into a package used 4.1.x of your dependency, and NuGet has no way of knowing if 1) the package strictly conforms to semantic versioning (my guess is very, very few do) and 2) if your project is using an API only available in 4.1.x and not 4.0.x. Given not all packages strictly conform to semantic versioning, it's unsafe to even change 4.1.* to 4.1.0.

Hopefully I've convinced you that NuGet's behaviour of how wildcards are handled when the project is packed into a package is the best approach. It's designed to maximise the percentage of packages that work "out of the box". If not, you should now understand how it works even if you don't agree it's the best implementation.