Comment NuGet résout les dépendances de package

Lorsqu’un package est installé ou réinstallé, y compris dans le cadre d’un processus de restauration, NuGet installe également tous les packages supplémentaires dont dépend ce premier package.

Ces dépendances immédiates peuvent également avoir leurs propres dépendances, et les dépendances peuvent ainsi continuer jusqu’à la profondeur souhaitée. Cela génère ce que l’on appelle un graphique de dépendance, qui décrit les relations entre les packages à tous les niveaux.

Lorsque plusieurs packages partagent une même dépendance, le même ID de package peut apparaître plusieurs fois dans le graphique, potentiellement avec des restrictions de version différentes. Néanmoins, un projet ne peut utiliser qu’une seule version d’un package donné ; NuGet doit donc choisir laquelle. Le processus exact varie selon le format de gestion des packages utilisé.

Résolution des dépendances avec PackageReference

Lorsque des packages sont installés dans un projet au format PackageReference, NuGet ajoute des références à un graphique de packages plat dans le fichier correspondant, et résout les conflits à l’avance. Ce processus est appelé restauration transitive. Les processus de réinstallation et de restauration des packages reviennent donc à télécharger les packages répertoriés dans le graphique, ce qui permet d’obtenir des builds plus prévisibles, plus rapidement.

Vous pouvez également tirer parti des versions flottantes, telles que 2.8.*, pour éviter de modifier le projet afin d’utiliser la dernière version d’un package. Lorsque vous utilisez des versions flottantes, nous vous recommandons d’activer la fonctionnalité fichier de verrouillage pour garantir la répétabilité.

Quand le processus de restauration NuGet est exécuté avant une build, il résout d’abord les dépendances dans la mémoire, puis écrit le graphe résultant dans un fichier nommé project.assets.json.

Le fichier de ressources se trouve à l’emplacement MSBuildProjectExtensionsPath qui, par défaut, est le dossier « obj » du projet. MSBuild lit alors ce fichier et le convertit en un ensemble de dossiers pouvant contenir des références, puis les ajoute à l’arborescence de projets en mémoire.

Le fichier project.assets.json est temporaire et ne doit pas être ajouté au contrôle de code source. Ce fichier est répertorié par défaut dans .gitignore et .tfignore. Consultez Packages et contrôle de code source.

Règles de résolution des dépendances

La restauration transitive applique quatre règles principales pour résoudre les dépendances : la plus ancienne version applicable, les versions flottantes, la règle direct-dependency-wins et les dépendances cousines.

La plus ancienne version applicable

La règle de la version la plus ancienne applicable restaure la version la plus ancienne possible d’un package, comme défini par ses dépendances. Elle s’applique également aux dépendances de l’application et de la bibliothèque de classes, sauf si déclarée comme flottante.

Dans la figure suivante, par exemple, 1.0-beta est considéré comme une version plus ancienne que 1.0. De fait, NuGet choisit la version 1.0 :

Choosing the lowest applicable version

Dans la figure suivante, la version 2.1 n’est pas disponible dans le flux. Toutefois, étant donné que la restriction de version est >= 2.1, NuGet récupère la plus ancienne version suivante, dans ce cas 2.2 :

Choosing the next lowest version available on the feed

Lorsqu’une application spécifie un numéro de version exact (tel que 1.2) qui n’est pas disponible dans le flux, NuGet échoue avec une erreur lorsque vous tentez d’installer ou de restaurer le package :

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

Versions flottantes

Une version de dépendance flottante est spécifiée grâce au caractère *. Par exemple : 6.0.*. La spécification de cette version indique « Utiliser la version 6.0.x la plus récente ». 4.* signifie « Utiliser la version 4.x la plus récente ». L’utilisation d’une version flottante réduit les modifications apportées au fichier projet, tout en gardant à jour la dernière version d’une dépendance. Les versions flottantes ne peuvent être spécifiées qu’au niveau du projet seulement.

Lors de l’utilisation d’une version flottante, NuGet résout la version la plus récente d’un package qui correspond au modèle de version. Par exemple, 6.0.* obtient la version la plus récente d’un package qui commence par 6.0 :

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

Version Versions présentes sur serveur Résolution Motif Notes
* 1.1.0
1.1.1
1.2.0
1.3.0-alpha
1.2.0 Version stable la plus élevée.
1.1.* 1.1.0
1.1.1
1.1.2-alpha
1.2.0-alpha
1.1.1 Version stable la plus élevée qui respecte le modèle spécifié.
*-* 1.1.0
1.1.1
1.1.2-alpha
1.3.0-bêta
1.3.0-bêta Version la plus élevée, y compris les versions instables. Disponible dans Visual Studio version 16.6, NuGet version 5.6, kit SDK .NET Core version 3.1.300
1.1.*-* 1.1.0
1.1.1
1.1.2-alpha
1.1.2-bêta
1.3.0-bêta
1.1.2-bêta Version la plus élevée, respectant le modèle, comprenant les versions instables. Disponible dans Visual Studio version 16.6, NuGet version 5.6, kit SDK .NET Core version 3.1.300

Remarque

La résolution de version flottante ne prend pas en compte si un package est répertorié ou non. La résolution de version flottante est résolue localement si les conditions peuvent être satisfaites avec les packages dans le dossier de package global.

Victoire des dépendances directes

Lorsque le graphe de package d’une application contient différentes versions d’un package au sein d’un même sous-graphe et que l’une de ces versions est une dépendance directe dans ce sous-graphique, cette version sera choisie pour ce sous-graphe et les autres seront ignorées. Ce comportement permet à une application de remplacer n’importe quelle version de package dans le graphique de dépendance.

Dans l’exemple ci-dessous, l’application dépend directement du Package B ayant une contrainte de version >= 2.0.0. L’application dépend également du Package A, qui, lui, dépend du Package B. Toutefois, sa contrainte de version est >= 1.0.0. Étant donné que la dépendance du Package B 2.0.0 est une dépendance directe à l’application dans le graphe, la version utilisée est :

Application using the Direct dependency wins rule

Avertissement

La règle de victoire du package direct peut entraîner le passage à une version antérieure du package, et rompre les autres dépendances du graphe. Lorsqu’un package est rétrogradé à une version inférieure, NuGet ajoute un avertissement pour alerter l’utilisateur.

Cette règle permet également d'améliorer l'efficacité dans le cas d'un graphe des éléments dépendants important. Lorsqu’une dépendance plus proche dans le même sous-graphe a une version supérieure, NuGet ignore cette dépendance, et NuGet ignore également toutes les dépendances restantes sur cette branche du graphe.

Dans le diagramme ci-dessous par exemple, comme le Package C 2.0.0 est utilisé, NuGet ignore toutes les branches de ce sous-graphe qui font référence à une version plus ancienne du Package C :

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

Grâce à cette règle, NuGet tente d’honorer l’intention de l’auteur du package. Dans le diagramme ci-dessous, l’auteur du package A a été explicitement passé à une version antérieure. Il est donc passé du package C 1.0.0 au package C 2.0.0.

When a package author explicitly downgrades, NuGet honors that.

Le propriétaire de l’application peut choisir de mettre à niveau le package C vers une version supérieure à 2.0.0, ce qui n’entraîne pas de rétrogradation supplémentaire de la version pour le package C. Dans ce cas, aucun avertissement n’est déclenché.

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

Dépendances cousines

Lorsque des versions de package différentes sont référencées dans différents sous-graphes, NuGet utilise la plus ancienne version qui répond à toutes les exigences de version (comme avec la règle de la plus ancienne version applicable et la règle de version flottante). Dans l’image ci-dessous par exemple, la version 2.0.0 du Package B respecte l’autre restriction >= 1.0.0, et est donc utilisée :

Resolving cousin dependencies using the lower version that satisfies all constraints

Notez que les packages n’ont pas besoin d’être à la même distance pour que la règle de dépendances cousines s’applique. Dans le diagramme ci-dessous, le package D 2.0.0 est choisi dans le sous-graphe du package C et le package D 3.0.0 est choisi dans le sous-graphe du package A. Dans le sous-graphe d’application, il n’existe aucune dépendance directe avec le package D. Par conséquent, la règle de version applicable la plus basse est appliquée et la version 3.0.0 est choisie.

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

Dans certains cas, il n’est pas possible de répondre à toutes les conditions des versions. Comme indiqué ci-dessous, si le Package A nécessite exactement le Package B 1.0.0, et si le Package C nécessite le Package B version >= 2.0.0, NuGet ne peut pas résoudre les dépendances et génère une erreur.

Unresolvable dependencies due to an exact version requirement

Dans ce cas, le consommateur de niveau supérieur (l’application ou le package) doit ajouter sa propre dépendance directe au Package B pour que la règle de la victoire de la dépendance directe soit appliquée.

Résolution des dépendances avec packages.config

Avec packages.config, les dépendances d’un projet sont écrites dans packages.config sous forme d’une liste plate. Toutes les dépendances de ces packages sont écrites dans une même liste. Lorsque les packages sont installés, NuGet peut également modifier le fichier .csproj, app.config, web.config, et d’autres fichiers.

Avec packages.config, NuGet tente de résoudre les conflits de dépendance lors de l’installation de chaque package. Autrement dit, si le Package A est en cours d’installation et dépend du Package B, et si ce dernier est déjà répertorié dans packages.config comme une dépendance d’un autre élément, NuGet compare les versions du Package B demandé et tente de trouver une version respectant toutes les restrictions de version. Plus précisément, NuGet sélectionne la version la plus ancienne majeure.mineure qui répond aux dépendances.

Par défaut, NuGet 2.8 recherche la version corrective la plus ancienne (voir Notes de publication NuGet 2.8). Vous pouvez contrôler ce paramètre via l’attribut DependencyVersion de NuGet.Config, et le commutateur -DependencyVersion sur la ligne de commande.

Le processus packages.config de résolution des dépendances devient complexe pour les graphiques de dépendance plus volumineux. Chaque nouvelle installation de package nécessite le parcours de l’ensemble du graphique, ce qui entraîne un risque de conflit de versions. En cas de conflit, l’installation est arrêtée, ce qui laisse le projet dans un état indéterminé, avec des modifications potentielles du fichier projet. Ce n’est pas un problème en cas d’utilisation d’autres formats de gestion des packages.

Gestion des ressources de dépendance

Le format PackageReference permet de choisir les ressources des dépendances qui seront acheminées dans le projet de niveau supérieur. Pour plus d’informations, consultez la section PackageReference.

Lorsque le projet de niveau supérieur est un package, vous pouvez contrôler ce flux en utilisant les attributs include et exclude avec les dépendances répertoriées dans le fichier .nuspec. Consultez Informations de référence sur le fichier .nuspec - Dépendances.

Exclusion de références

Il existe des scénarios dans lesquels les assemblys qui portent le même nom peuvent être référencés plusieurs fois dans un même projet, ce qui génère des erreurs au moment de la conception et de la génération. Imaginez un projet qui contienne une version personnalisée de C.dll, et qui référence le Package C contenant également C.dll. En même temps, le projet dépend également du Package B qui dépend du Package C et de C.dll. Par conséquent, NuGet ne peut pas déterminer quel C.dll utiliser. Toutefois, vous ne pouvez pas supprimer la dépendance du projet au Package C, car le Package B en dépend également.

Pour résoudre ce problème, vous devez référencer directement le C.dll que vous souhaitez (ou utiliser un autre package qui référence celui qui convient), puis ajouter une dépendance au Package C qui exclut toutes ses ressources. Pour cela, suivez les étapes ci-dessous, selon le format de gestion des packages utilisé :

  • PackageReference : ajoutez ExcludeAssets="All" dans la dépendance :

    <PackageReference Include="PackageC" Version="1.0.0" ExcludeAssets="All" />
    
  • packages.config : supprimez la référence au Package C dans le fichier .csproj pour qu’il référence uniquement la version du C.dll souhaitée.

Mises à jour des dépendances pendant l’installation du package

Si une condition de version de la dépendance est déjà satisfaite, celle-ci n’est pas mise à jour lors de l’installation des autres packages. Par exemple, imaginons le Package A qui dépend du Package B et qui spécifie 1.0 comme numéro de version. Le référentiel source contient les versions 1.0, 1.1 et 1.2 du Package B. Si le Package A est installé dans un projet qui contient déjà le Package B version 1.0, ce dernier reste utilisé, car la contrainte de version est satisfaite. Toutefois, si le Package A avait demandé la version 1.1 ou ultérieure du Package B, le Package B 1.2 serait installé.

Résolution des erreurs de package incompatible

Pendant une opération de restauration de package, vous pouvez rencontrer une erreur vous informant qu’un ou plusieurs packages ne sont pas compatibles ou qu’un package n’est pas compatible avec la version cible du .NET Framework du projet.

Cette erreur se produit lorsqu’un ou plusieurs des packages référencés dans votre projet n’indiquent pas qu’ils prennent en charge la version cible de .NET Framework du projet. Autrement dit, aucune DLL correspondant à une version cible de .NET Framework ne se trouve dans le dossier lib du package (consultez Versions cibles de .NET Framework pour obtenir la liste des versions cibles).

Par exemple, si un projet cible netstandard1.6 et que vous essayez d’installer un package qui ne contient des DLL que dans les dossiers lib\net20 et \lib\net45, des messages de ce type apparaîtront pour le package et, éventuellement, pour ses éléments dépendants :

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'.

Pour résoudre les incompatibilités, effectuez l’une des opérations suivantes :

  • Reciblez votre projet vers un framework pris en charge par les packages que vous souhaitez utiliser.
  • Contactez l’auteur des packages et ajoutez ensemble la prise en charge du framework de votre choix. Chaque page qui répertorie des packages sur nuget.org comprend le lien Contact Owners qui permet de contacter les propriétaires des packages.

Conseil

Solution alternative : NuGetSolver est une extension Visual Studio développée par Microsoft DevLabs, conçue pour faciliter la résolution des conflits de dépendances. Il automatise le processus d’identification et de résolution de ces problèmes. Pour plus d’informations, visitez la page NuGetSolver sur visual Studio Marketplace et nous aimerions entendre vos commentaires sur votre expérience.