Принципы разрешения зависимостей пакетов в NuGet

При любой установке или переустановке пакета, включая установку в рамках процесса восстановления, NuGet также устанавливает все дополнительные пакеты, от которых зависит этот первый пакет.

Эти прямые зависимости также могут иметь собственные зависимости, уходящие на произвольную глубину. В результате образуется так называемая схема зависимостей, описывающая связи между пакетами на всех уровнях.

Если несколько пакетов имеют одинаковую зависимость, один идентификатор пакета может отображаться на графе несколько раз, возможно, с другими ограничениями по версиям. Однако в проекте можно использовать только одну версию заданного пакета, поэтому 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:

Choosing the lowest applicable version

На следующем рисунке версия 2.1 недоступна в веб-канале, но так как ограничение по версии имеет значение >= 2.1, NuGet выбирает следующую наименьшую версию, которую может найти. В нашем случае — 2.2:

Choosing the next lowest version available on the feed

Если приложение указывает точный номер версии, например 1.2, который недоступен в веб-канале, NuGet завершается с ошибкой при попытке установить или восстановить этот пакет:

NuGet generates an error when an exact package version is not available

Гибкие версии

Групповая версия зависимости указывается с помощью символа *. Например, 6.0.*. Такая запись версии указывает, что нужно использовать самую последнюю версию 6.0.x. Если указано 4.*, нужно использовать последнюю версию 4.x. Использование групповой версии сокращает число изменений в файле проекта, применяя последнюю версию зависимости. Плавающие версии можно указать только на уровне проекта.

При использовании гибкой версии NuGet разрешает самую новую версию пакета, соответствующую заданному шаблону. Например, при значении 6.0.* используется новейшая версия пакета, начинающаяся с 6.0:

Choosing version 6.0.1 when a floating version 6.0.* is requested

Версия Версии на сервере Разрешение Причина Примечания.
* 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-альфа
1.2.0-alpha
1.1.1 Последняя стабильная версия, соответствующая указанному шаблону.
*-* 1.1.0
1.1.1
1.1.2-альфа
1.3.0-beta
1.3.0-beta Последняя версия (может быть нестабильной). Доступно в Visual Studio версии 16.6, NuGet версии 5.6, пакете SDK для .NET Core версии 3.1.300.
1.1.*-* 1.1.0
1.1.1
1.1.2-альфа
1.1.2-beta
1.3.0-beta
1.1.2-beta Последняя версия (может быть нестабильной), соответствующая шаблону. Доступно в Visual Studio версии 16.6, NuGet версии 5.6, пакете SDK для .NET Core версии 3.1.300.

Примечание.

Разрешение гибкой версии не учитывает то, указан ли пакет в списке. Разрешение плавающей версией будет выполняться локально, если условия могут быть удовлетворены пакетами в глобальной папке пакетов.

Прямые победы зависимостей

Если граф пакетов для приложения содержит разные версии пакета в одном подграфе, и одна из этих версий является прямой зависимостью в этом подграфе, эта версия будет выбрана для этого подграфа, а остальные будут игнорироваться. Такое поведение позволяет приложению переопределить любую конкретную версию пакета на графе зависимостей.

В приведенном ниже примере приложение зависит непосредственно от пакета B с ограничением >версии =2.0.0. Приложение также зависит от пакета A, который, в свою очередь, зависит от пакета B, но с ограничением >=1.0.0. Так как зависимость от пакета B 2.0.0 является прямой зависимостью приложения в графе, эта версия используется:

Application using the Direct dependency wins rule

Предупреждение

Правило winency Direct может привести к понижению версии пакета, что может привести к нарушению других зависимостей в графе. При понижении уровня пакета NuGet добавляет предупреждение для оповещения пользователя.

Это правило также приводит к повышению эффективности с большим граф зависимостей. Если более тесная зависимость в одном подграфе имеет более высокую версию, то NuGet игнорирует зависимость, а NuGet также игнорирует все оставшиеся зависимости от этой ветви графа.

Например, на схеме ниже, так как используется пакет C 2.0.0, NuGet игнорирует все ветви в этом подграфе, ссылающиеся на более раннюю версию пакета C:

When NuGet ignores a package in the graph, it ignores that entire branch

С помощью этого правила NuGet пытается выполнить намерение автора пакета. На приведенной ниже схеме автор пакета A явно снизился до пакета C 1.0.0 из пакета C 2.0.0.

When a package author explicitly downgrades, NuGet honors that.

Владелец приложения может обновить пакет C до версии более 2.0.0, поэтому более раннюю версию пакета C не будет ниже. В этом случае предупреждение не возникает.

When an application honor adds a direct dependency for a downgraded package, NuGet honors that.

Родственные зависимости

Если разные версии пакетов ссылаются на различные подграфы в графе приложения, NuGet использует самую низкую версию, которая удовлетворяет всем требованиям версии (как и с самыми низкими применимыми версиями и правилами с плавающей версией). На рисунке ниже, например, версия 2.0.0 пакета B удовлетворяет другому >ограничению =1.0.0 и таким образом используется:

Resolving cousin dependencies using the lower version that satisfies all constraints

Обратите внимание, что пакеты не должны находиться на том же расстоянии, чтобы правило зависимостей двоюродных двоюродных зависимостей применялось. На схеме ниже выбран пакет D 2.0.0 в подграфе Package C и пакет D 3.0.0 выбран в подграфе пакета A. В подграфе приложения нет прямой зависимости от пакета D, поэтому применяется наименьшее применимое правило версии , и выбрана версия 3.0.0.

Resolving cousin dependencies using the lower version that satisfies all constraints at different distances

В некоторых случаях удовлетворить все требования к версии невозможно. Как показано ниже, если пакет A требует точно пакета B 1.0.0 и пакета C требует пакета B >=2.0.0,0, NuGet не может разрешить зависимости и дает ошибку.

Unresolvable dependencies due to an exact version requirement

В таких ситуациях потребитель верхнего уровня (приложение или пакет) должен добавить собственную прямую зависимость от пакета B, чтобы правило победы прямой зависимости зависимостей применялось.

Разрешение зависимостей для packages.config

При использовании packages.config зависимости проекта записываются в packages.config в виде плоского списка. Все зависимости этих пакетов также записываются в тот же список. При установке пакетов NuGet также может изменить файл .csproj, app.config, web.config и другие отдельные файлы.

При использовании packages.config NuGet пытается разрешить конфликты зависимостей во время установки каждого отдельного пакета. Таким образом, если устанавливается пакет A, зависящий от пакета B, а пакет B уже указан в packages.config в качестве зависимости для чего-то еще, NuGet сравнивает запрошенные версии пакета B и пытается найти ту из них, которая соответствует всем ограничениям по версиям. В частности, NuGet выбирает более низкую версию основной_номер.дополнительный_номер, которая удовлетворяет зависимостям.

По умолчанию NuGet 2.8 ищет самую раннюю версию исправления (см. заметки о выпуске NuGet 2.8). Вы можете управлять этим параметром с помощью атрибута DependencyVersion в NuGet.Config и параметра -DependencyVersion в командной строке.

Для более крупных графов процесс разрешения зависимостей packages.config усложняется. При каждой новой установке пакета требуется обход всего графа, а также повышается вероятность конфликтов версий. При возникновении конфликта установка останавливается, оставляя проект в неопределенном состоянии, что особенно касается потенциальных изменений в файле проекта. Эта проблема не актуальна при использовании других форматов управления пакетами.

Управление ресурсами зависимостей

При использовании формата PackageReference вы можете управлять тем, какие ресурсы из зависимостей переходят в проект верхнего уровня. Дополнительные сведения см. в разделе Управление ресурсами зависимости.

Если проект верхнего уровня сам является пакетом, вы также можете управлять этим потоком, используя атрибуты include и exclude для зависимостей, перечисленных в файле .nuspec. См. подраздел "Зависимости" в разделе Справочник по файлам NUSPEC.

Исключение ссылок

Существуют сценарии, когда проект может ссылаться на сборки с одинаковым именем несколько раз, что вызывает ошибки времени разработки и времени сборки. Рассмотрим проект, который содержит настраиваемую версию C.dll и ссылается на пакет C, который также содержит C.dll. В то же время проект зависит от пакета 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: удалите ссылку на пакет PackageC из файла .csproj, чтобы он ссылался только на нужную версию C.dll.

Обновление зависимостей при установке пакета

Если версия зависимости уже удовлетворена, зависимость не обновляется при установке других пакетов. Например, рассмотрим пакет A, который зависит от пакета B и указывает номер версии 1.0. Исходный репозиторий содержит версии 1.0, 1.1 и 1.2 пакета B. Если установить пакет A в проекте, который уже содержит версию 1.0 пакета B, то пакет B версии 1.0 все еще будет использоваться, так как это удовлетворяет ограничение по версии. Однако если бы пакет A запрашивал для пакета B версию 1.1 или более позднюю, был бы установлен пакет B версии 1.2.

Устранение ошибок с несовместимостью пакетов

При операции восстановления пакета вы можете увидеть ошибку "One or more packages are not compatible" (Один или несколько пакетов несовместимы) или сообщение о несовместимости пакета с целевой платформой проекта.

Эта ошибка возникает, когда один или несколько пакетов, на которые ссылается ваш проект, не сообщают о поддержке целевой платформы проекта. В результате пакет не содержит подходящую библиотеку DLL в своей папке lib для целевой платформы, совместимой с этим проектом. (Список см. в разделе Целевые платформы.)

Например, если проект предназначен для netstandard1.6 и вы пытаетесь установить пакет, содержащий библиотеки DLL, только в папках lib\net20 и \lib\net45, то для пакета и, возможно, его зависимостей могут отображаться следующие сообщения:

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 предусмотрена ссылка Contact Owners (Связаться с владельцами).

Совет

Альтернативное решение: NuGetSolver — это расширение Visual Studio, разработанное Microsoft DevLabs, предназначенное для устранения конфликтов зависимостей. Он автоматизирует процесс выявления и решения этих проблем. Дополнительные сведения см. на странице NuGetSolver в Visual Studio Marketplace, и мы хотели бы услышать отзывы о вашем опыте.