NuGet 如何解析包依赖项
每当安装或重新安装包(包括作为 还原 过程的一部分进行安装)时,NuGet 也会安装第一个包所依赖的任何其他包。
然后,这些直接依赖项可能也有自己的依赖项,这些依赖项可以继续到任意深度。 这会生成所谓的 依赖项关系图,用于描述所有级别的包之间的关系。
当多个包具有相同的依赖项时,同一个包 ID 可以多次出现在图形中,可能具有不同的版本约束。 但是,项目中只能使用给定包的一个版本,因此 NuGet 必须选择使用哪个版本。 确切的过程取决于所使用的包管理格式。
使用 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 在尝试安装或还原包时会报错。
使用 * 字符指定浮动依赖项版本。 例如,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 会尝试遵循包作者的意图。 在下图中,包 A 的作者已从包 C 2.0.0 显式降级到包 C 1.0.0。
应用程序所有者可以选择将包 C 升级到高于 2.0.0 的版本,因此不会进一步降级包 C 的版本。在这种情况下,不会引发任何警告。
在图形中的不同子图中引用不同包版本时,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 的自己的直接依赖项,以便适用 直接依赖项优先 规则。
包提供稳定版本和预发行版本并不罕见。
解析依赖项关系图时,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
。 这些包的任何依赖项也会在同一列表中写入。 安装包后,NuGet 还可以修改 .csproj
文件、app.config
、web.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 规则不允许在图中混合使用稳定版本和预发布版本的依赖项。
如果依赖项以 [1.0.0, 2.0.0)
等范围表示,则图形中不允许预发行包。
使用 PackageReference 格式时,可以控制依赖项中的哪些资产流入顶级项目。 有关详细信息,请参阅 PackageReference。
当顶级项目本身是包时,还可以通过使用 include
和 exclude
属性以及 .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 页面,我们很乐意听取你关于你的体验的反馈。