使用英语阅读

通过


NuGet 如何解析包依赖项

每当安装或重新安装包(包括作为 还原 过程的一部分进行安装)时,NuGet 也会安装第一个包所依赖的任何其他包。

然后,这些直接依赖项可能也有自己的依赖项,这些依赖项可以继续到任意深度。 这会生成所谓的 依赖项关系图,用于描述所有级别的包之间的关系。

当多个包具有相同的依赖项时,同一个包 ID 可以多次出现在图形中,可能具有不同的版本约束。 但是,项目中只能使用给定包的一个版本,因此 NuGet 必须选择使用哪个版本。 确切的过程取决于所使用的包管理格式。

使用 PackageReference 的依赖关系解析

使用 PackageReference 格式将包安装到项目中时,NuGet 会在相应的文件中添加对平面包图形的引用,并提前解决冲突。 此过程称为 传递还原。 然后,重新安装或还原包是下载图表中列出的包的过程,以便于更快、更可预测的构建。

还可以利用浮点版本(如 2.8.*)来避免修改项目以使用包的最新版本。 使用浮动版本时,我们建议启用 锁定文件功能,以确保可重复性。

在生成之前运行 NuGet 还原进程时,它会先解析内存中的依赖项,然后将生成的图形写入名为 project.assets.json的文件。

资产文件位于 MSBuildProjectExtensionsPath,默认为项目的“obj”文件夹。 然后,MSBuild 读取此文件,并将其转换为一组文件夹,可在其中找到潜在的引用,然后将其添加到内存中的项目树。

project.assets.json 文件是临时的,不应添加到源代码管理中。 它默认在 .gitignore.tfignore中列出。 请参阅 包和源代码管理

依赖项解析规则

可传递恢复应用四个主要规则来解决依赖关系:最低适用版本浮动版本直接依赖优先,以及 表亲依赖关系

最低适用版本

最低可用版本规则根据依赖项定义恢复软件包的最低可能版本。 它也适用于应用程序或类库的依赖项,除非这些依赖关系被声明为未固定的

在下图中,例如,1.0-beta 被视为低于 1.0,因此 NuGet 选择 1.0 版本:

选择最低适用版本

在下一个图中,2.1 版在源上不可用,但由于版本约束是 >= 2.1 NuGet 选择它可以找到的下一个最低版本,在本例中为 2.2:

选择源 上可用的下一个最低版本

当应用程序指定一个源中不存在的确切版本号(如 1.2)时,NuGet 在尝试安装或还原包时会报错。

NuGet 在确切的包版本不可用时生成错误

浮动版本

使用 * 字符指定浮动依赖项版本。 例如,6.0.*。 此版本规范显示“使用最新的 6.0.x 版本”;4.* 意味着“使用最新的 4.x 版本”。使用浮动版本可减少对项目文件的更改,同时使依赖项的最新版本保持最新。 浮动版本只能在项目级别指定。

使用浮动版本时,NuGet 解析与版本模式匹配的包的最高版本,例如 6.0.* 获取以 6.0 开头的包的最高版本:

请求浮动版本 6.0.* 时选择版本 6.0.1

版本 服务器上存在的版本 分辨率 原因 笔记
* 1.1.0
1.1.1
1.2.0
1.3.0-alpha
1.2.0 最高稳定版本。
1.1.* 1.1.0
1.1.1
1.1.2-alpha
1.2.0-alpha
1.1.1 遵循指定模式的最高稳定版本。
*-* 1.1.0
1.1.1
1.1.2-alpha
1.3.0-beta
1.3.0-测试版 最高版本,包括不稳定的版本。 在 Visual Studio 版本 16.6、NuGet 版本 5.6、.NET Core SDK 版本 3.1.300 中可用
1.1.*-* 1.1.0
1.1.1
1.1.2-alpha
1.1.2-beta
1.3.0-beta
1.1.2-beta 遵循模式的最高版本,包括不稳定的版本。 在 Visual Studio 版本 16.6、NuGet 版本 5.6、.NET Core SDK 版本 3.1.300 中可用
1.2.0-rc.* 1.1.0
1.2.0-rc.1
1.2.0-rc.2
1.2.0
1.2.0 尽管这是一个包含预发行部分的版本范围,但如果它们与稳定版本部分匹配,则允许稳定版本。 鉴于 1.2.0 > 1.2.0-rc.2,我们选择了它。

备注

浮动版本解析不会考虑包是否被列出。 如果可以使用全局包文件夹中的包满足条件,则将本地处理浮动版本解析。

直接依赖项获胜

当应用程序的包图包含同一子图中的包的不同版本,并且其中一个版本是该子图中的直接依赖项时,将为该子图选择该版本,其余版本将被忽略。 此行为允许应用程序重写依赖项关系图中的任何特定包版本。

在下面的示例中,应用程序直接依赖于版本约束为 >=2.0.0 的包 B。 应用程序还依赖于包 A,包 A 又依赖于包 B,并且要求 >=1.0.0 的约束。 由于包 B 2.0.0 上的依赖项直接依赖于图形中的应用程序,因此使用该版本:

根据直接依赖优先规则的应用程序

警告

直接依赖胜出规则可能会导致包版本的降级,从而可能会破坏依赖关系图中的其他依赖项。 当包被降级时,NuGet 会添加 警告,提醒用户注意

此规则还会导致使用大型依赖项关系图提高效率。 当同一子图中的更紧密的依赖项具有比另一个版本更高的版本时,NuGet 会忽略该依赖项,NuGet 也会忽略关系图的该分支上所有剩余的依赖项。

例如,在下图中,由于使用了包 C 2.0.0,NuGet 会忽略该子图中引用早期版本的 Package C 的任何分支:

NuGet 忽略图形中的包时,它会忽略整个分支

通过此规则,NuGet 会尝试遵循包作者的意图。 在下图中,包 A 的作者已从包 C 2.0.0 显式降级到包 C 1.0.0。

当包作者显式降级时,NuGet 会遵循这一点。

应用程序所有者可以选择将包 C 升级到高于 2.0.0 的版本,因此不会进一步降级包 C 的版本。在这种情况下,不会引发任何警告。

当应用程序为降级包添加直接依赖项时,NuGet 会遵守这一要求。

表亲依赖关系

在图形中的不同子图中引用不同包版本时,NuGet 使用满足所有版本要求的最低版本(与 最低适用版本浮动版本 规则一样)。 例如,在下图中,包 B 的版本 2.0.0 满足其他 >=1.0.0 的约束,因此被使用:

使用满足所有约束的较低版本解析表亲依赖项

请注意,包不需要在相同的距离下才能应用表亲依赖项规则。 在下图中,包 D 2.0.0 在包 C 子图中选择,包 D 3.0.0 在包 A 的子图中选择。在应用程序子图中,没有对包 D 的直接依赖项,因此应用了 最低适用版本 规则,并且选择了版本 3.0.0。

使用较低版本解析表亲依赖项,该版本满足不同距离的所有约束

在某些情况下,无法满足所有版本要求。 如下所示,如果包 A 需要包 B 1.0.0 且包 C 要求包 B >=2.0.0,则 NuGet 无法解析依赖项并给出错误。

的依赖项无法解决,原因是确切的版本要求

在这些情况下,顶层消费者(应用程序或软件包)应添加对包 B 的自己的直接依赖项,以便适用 直接依赖项优先 规则。

PackageReference 的版本范围和预发行版本

包提供稳定版本和预发行版本并不罕见。 解析依赖项关系图时,NuGet 决定是否考虑基于单个规则的包的预发行版:If the project or any packages within the graph request a prerelease version of a package, then include both prerelease or stable versions, otherwise consider stable versions only.

在实践中,在最低适用规则下,这意味着:

版本范围 可用版本 所选版本
[1.0.0, 2.0.0) 1.2.0-beta.1、1.2.0、 1.2.0
[1.0.0, 2.0.0-0) 1.2.0-beta.1、1.2.0、 1.2.0-beta.1
[1.0.0, 2.0.0) 1.2.0-beta.1、2.0.0-beta.3 无,NU1103 已启动。
[1.0.0,2.0.0-rc) 1.2.0-beta.1、2.0.0-beta.3 1.2.0-beta.1

packages.config 的依赖解析

使用 packages.config时,项目的依赖项将作为平面列表写入 packages.config。 这些包的任何依赖项也会在同一列表中写入。 安装包后,NuGet 还可以修改 .csproj 文件、app.configweb.config和其他单个文件。

使用 packages.config,NuGet 会尝试解决每个包的安装过程中的依赖项冲突。 也就是说,如果包 A 正在安装并依赖于包 B,并且包 B 已作为其他项的依赖项列在 packages.config 中,NuGet 会比较请求的包 B 版本,并尝试查找满足所有版本约束的版本。 具体而言,NuGet 选择满足依赖项的较低 major.minor 版本。

默认情况下,NuGet 2.8 查找最低修补程序版本(请参阅 NuGet 2.8 发行说明)。 可以通过 NuGet.Config 中的 DependencyVersion 属性和命令行上的 -DependencyVersion 开关来控制此设置。

解决依赖项的 packages.config 过程对于较大的依赖项关系图来说会变得复杂。 每个新包安装都需要遍历整个图,并增加版本冲突的可能性。 发生冲突时,安装过程将停止,这将导致项目处于不确定的状态,特别是对项目文件本身可能做出的修改。 使用其他包管理格式时,这不是问题。

具有 packages.config 的版本范围和预发行版本

packages.config 规则不允许在图中混合使用稳定版本和预发布版本的依赖项。 如果依赖项以 [1.0.0, 2.0.0)等范围表示,则图形中不允许预发行包。

管理依赖项资产

使用 PackageReference 格式时,可以控制依赖项中的哪些资产流入顶级项目。 有关详细信息,请参阅 PackageReference

当顶级项目本身是包时,还可以通过使用 includeexclude 属性以及 .nuspec 文件中列出的依赖项来控制此流。 请参阅.nuspec 参考 - 依赖项

排除引用

在某些情况下,项目中可能会多次引用具有相同名称的程序集,从而生成设计时和生成时错误。 考虑一个项目,该项目包含 C.dll的自定义版本,并引用了包含 C.dll的包 C。 同时,项目还依赖于包 B,该包还依赖于包 C 和 C.dll。 因此,NuGet 无法确定要使用的 C.dll,但不能只删除项目对包 C 的依赖项,因为包 B 也依赖于它。

若要解决此问题,必须直接引用所需的 C.dll(或使用引用正确版本的另一个包),并将包 C 设置为依赖项,以排除其所有资源。 具体作如下,具体取决于使用的包管理格式:

  • PackageReference:在依赖项中添加 ExcludeAssets="All"

    <PackageReference Include="PackageC" Version="1.0.0" ExcludeAssets="All" />
    
  • packages.config:从 .csproj 文件中删除对 PackageC 的引用,使其仅引用所需的 C.dll 版本。

包安装过程中的依赖项更新

如果已满足依赖项版本,则其他包安装期间不会更新依赖项。 例如,考虑依赖于包 B 的包 A,并为版本号指定 1.0。 源存储库包含包 B 的版本 1.0、1.1 和 1.2。如果 A 安装在已包含 B 版本 1.0 的项目中,则 B 1.0 仍使用,因为它满足版本约束。 但是,如果包 A 需要 B 的 1.1 或更高版本,那么将安装 B 1.2。

解决不兼容的包错误

在包还原作期间,可能会看到错误“一个或多个包不兼容...”或者包“与项目的目标框架不兼容”。

如果项目中引用的一个或多个包未指示它们支持项目的目标框架,则会发生此错误;也就是说,包在其 lib 文件夹中不包含适合与项目兼容的目标框架的 DLL。 (请参阅 目标框架 以获得列表。)

例如,如果项目面向 netstandard1.6,并且你尝试仅安装 lib\net20\lib\net45 文件夹中包含 DLL 的包,则会看到包及其依赖项的消息,如下所示:

Restoring packages for myproject.csproj...
Package ContosoUtilities 2.1.2.3 is not compatible with netstandard1.6 (.NETStandard,Version=v1.6). Package ContosoUtilities 2.1.2.3 supports:
  - net20 (.NETFramework,Version=v2.0)
  - net45 (.NETFramework,Version=v4.5)
Package ContosoCore 0.86.0 is not compatible with netstandard1.6 (.NETStandard,Version=v1.6). Package ContosoCore 0.86.0 supports:
  - 11 (11,Version=v0.0)
  - net20 (.NETFramework,Version=v2.0)
  - sl3 (Silverlight,Version=v3.0)
  - sl4 (Silverlight,Version=v4.0)
One or more packages are incompatible with .NETStandard,Version=v1.6.
Package restore failed. Rolling back package changes for 'MyProject'.

若要解决不兼容问题,请执行以下作之一:

  • 将项目重定向到要使用的包支持的框架。
  • 请与包的作者联系,并与其协作,添加对所选框架的支持。 nuget.org 上的每个包列表页都有一个 联系所有者 链接,以便实现此目的。

提示

替代解决方案:NuGetSolver 是由 Microsoft DevLabs 开发的 Visual Studio 扩展,旨在帮助解决依赖项冲突。 它自动执行识别和解决这些问题的过程。 有关更多详细信息,请访问 Visual Studio Marketplace 上的 NuGetSolver 页面,我们很乐意听取你关于你的体验的反馈。