Partager via


Résoudre les problèmes de références d’assembly

L’une des tâches les plus importantes dans MSBuild et le processus de génération .NET consiste à résoudre les références d’assembly, qui s’effectue dans la tâche ResolveAssemblyReference. Cet article explique en partie le fonctionnement de ResolveAssemblyReference et comment résoudre les échecs de génération qui peuvent se produire lorsque ResolveAssemblyReference ne parvient pas à résoudre une référence. Pour examiner les échecs de référence d’assembly, vous pouvez installer la visionneuse du journal structuré pour afficher les journaux MSBuild. Les captures d’écran de cet article sont extraites de la visionneuse du journal structuré.

L’objectif de ResolveAssemblyReference est prendre toutes les références spécifiées dans les fichiers .csproj (ou ailleurs) via l’élément <Reference> et de les mapper aux chemins d’accès aux fichiers d’assembly dans le système de fichiers.

Les compilateurs ne peuvent accepter comme référence qu’un chemin d’accès .dll sur le système de fichiers. ResolveAssemblyReference convertit des chaînes comme mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, qui apparaissent dans les fichiers projet, en chemins comme C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\mscorlib.dll, qui sont ensuite transmis au compilateur via le commutateur /r.

En outre, ResolveAssemblyReference détermine de manière récursive l’ensemble complet (en réalité, la fermeture transitive en termes de la théorie des graphes) de toutes les références .dll et .exe et, pour chacune d’elles, si elle doit être copiée ou non dans le répertoire de sortie de build. Il n’effectue pas la copie proprement dite (qui est gérée ultérieurement, après l’étape de compilation), mais elle prépare une liste de fichiers à copier.

ResolveAssemblyReference est appelé à partir de la cible ResolveAssemblyReferences :

Capture d’écran de la visionneuse de journaux montrant quand ResolveAssemblyReferences est appelé dans le processus de génération.

Si vous remarquez l’ordre, la tâche ResolveAssemblyReferences se produit avant Compile, et la tâche CopyFilesToOutputDirectory se produit évidemment après Compile.

Remarque

La tâche ResolveAssemblyReference est appelée dans le fichier .targets standard Microsoft.Common.CurrentVersion.targets dans les dossiers d’installation MSBuild. Vous pouvez également parcourir les cibles MSBuild du SDK .NET en ligne à l’adresse https://github.com/dotnet/msbuild/blob/a936b97e30679dcea4d99c362efa6f732c9d3587/src/Tasks/Microsoft.Common.CurrentVersion.targets#L1991-L2140. Ce lien indique exactement où la tâche ResolveAssemblyReference est appelée dans le fichier .targets.

Entrées ResolveAssemblyReference

ResolveAssemblyReference effectue un enregistrement très complet de ses entrées :

Capture d’écran montrant les paramètres d’entrée de la tâche ResolveAssemblyReference.

Le nœud Parameters est standard pour toutes les tâches, mais ResolveAssemblyReference enregistre en outre sous Inputs son propre ensemble d’informations (qui est sensiblement le même que sous Parameters, mais structuré différemment).

Les entrées les plus importantes sont Assemblies et AssemblyFiles :

    <ResolveAssemblyReference
        Assemblies="@(Reference)"
        AssemblyFiles="@(_ResolvedProjectReferencePaths);@(_ExplicitReference)"

Assemblies utilise le contenu de l’élément MSBuild Reference lorsque ResolveAssemblyReference est appelé pour le projet. Toutes les métadonnées et références d’assembly, y compris vos références NuGet, doivent être contenues dans cet élément. Un ensemble riche de métadonnées est associé à chaque référence :

Capture d’écran montrant les métadonnées d’une référence d’assembly.

AssemblyFiles provient de l’élément de sortie de la cible ResolveProjectReference appelé _ResolvedProjectReferencePaths. ResolveProjectReference s’exécute avant ResolveAssemblyReference et convertit les éléments <ProjectReference> en chemins d’accès aux assemblys générés sur le disque. Par conséquent, les AssemblyFiles contiendront les assemblys générés par tous les projets référencés du projet actuel :

Capture d’écran montrant les fichiers d’assembly (AssemblyFiles).

Une autre entrée utile est le paramètre booléen FindDependencies, qui prend sa valeur dans la propriété _FindDependencies :

FindDependencies="$(_FindDependencies)"

Vous pouvez définir cette propriété sur false dans votre build pour désactiver l’analyse des assemblys à dépendances transitives.

Algorithme ResolveAssemblyReference

L’algorithme simplifié pour la tâche ResolveAssemblyReference est le suivant :

  1. Enregistrez les entrées.
  2. Vérifiez la variable d’environnement MSBUILDLOGVERBOSERARSEARCHRESULTS. Définissez cette variable sur n’importe quelle valeur pour obtenir des journaux plus détaillés.
  3. Initialisez la table d’objets de référence.
  4. Lisez le fichier cache du répertoire obj (le cas échéant).
  5. Calculez la fermeture des dépendances.
  6. Générez les tables de sortie.
  7. Écrivez le fichier cache dans le répertoire obj.
  8. Enregistrez les résultats.

L’algorithme prend la liste d’entrée des assemblys (à la fois à partir de métadonnées et de références de projet), récupère la liste des références pour chaque assembly qu’il traite (en lisant les métadonnées) et génère un ensemble complet (fermeture transitive) de tous les assemblys référencés, puis les résout à partir de différents emplacements (y compris le Global Assembly Cache [GAC], AssemblyFoldersEx, etc.).

Les assemblys référencés sont ajoutés à la liste de manière itérative jusqu’à ce qu’aucune nouvelle référence ne soit ajoutée. L’algorithme s’arrête ensuite.

Les références directes que vous avez fournies à la tâche sont appelées références principales. Les assemblys indirects ajoutés au jeu en raison d’une référence transitive sont appelés Dépendances. L’enregistrement de chaque assembly indirect effectue le suivi de tous les éléments principaux (« racine ») qui ont conduit à son inclusion ainsi que les métadonnées correspondantes.

Résultats de la tâche ResolveAssemblyReference

ResolveAssemblyReference fournit une journalisation détaillée des résultats :

Capture d’écran montrant les résultats de ResolveAssemblyReference dans la visionneuse du journal structuré.

Les assemblys résolus sont divisés en deux catégories : les références principales et les dépendances. Les références principales ont été spécifiées explicitement en tant que références du projet en cours de génération. Les dépendances ont été déduites des références de références de manière transitive.

Important

ResolveAssemblyReference lit les métadonnées d’assembly pour déterminer les références d’un assembly donné. Lorsque le compilateur C# émet un assembly, il ajoute uniquement des références aux assemblys qui sont réellement nécessaires. Par conséquent, il peut arriver que lorsque vous compilez un certain projet, celui-ci spécifie une référence inutile qui ne sera pas intégrée à l’assembly. Ajouter au projet des références inutiles n’est pas problématique, car elles sont ignorées.

Métadonnées de l’élément CopyLocal

Les références peuvent également posséder ou non les métadonnées CopyLocal. Si la référence possède le paramètre CopyLocal = true, elle sera copiée ultérieurement dans le répertoire de sortie par la cible CopyFilesToOutputDirectory. Dans cet exemple, CopyLocal est défini sur la valeur true pour DataFlow, mais pas pour Immutable :

Capture d’écran montrant les paramètres CopyLocal de certaines références.

Si les métadonnées CopyLocal sont manquantes, elles sont supposées être vraies par défaut. Par conséquent, ResolveAssemblyReference tente par défaut de copier les dépendances sur la sortie, sauf s’il trouve une raison de ne pas le faire. ResolveAssemblyReference enregistre les raisons pour lesquelles il a choisi qu’une référence précise possède ou non un paramètre CopyLocal.

Le tableau suivant décrit toutes les raisons possibles pour lesquelles une référence possède le paramètre CopyLocal. Il est utile de connaître ces chaînes pour pouvoir les rechercher dans les journaux de génération.

État de CopyLocal Description
Undecided L’état de CopyLocal n’est pas décidé.
YesBecauseOfHeuristic Le paramètre CopyLocal='true' devrait être défini pour la référence, car la valeur « no » n’est pas attribuée pour une raison quelconque.
YesBecauseReferenceItemHadMetadata Le paramètre CopyLocal='true' devrait être défini pour la référence, car son élément source possède le paramètre « Private=’true’ ».
NoBecauseFrameworkFile Le paramètre CopyLocal='false' devrait être défini pour la référence, car il s’agit d’un fichier framework.
NoBecausePrerequisite Le paramètre CopyLocal='false' devrait être défini pour la référence, car il s’agit d’un fichier prérequis.
NoBecauseReferenceItemHadMetadata Le paramètre CopyLocal='false' devrait être défini pour la référence, car l’attribut Private est défini sur « false » dans le projet.
NoBecauseReferenceResolvedFromGAC Le paramètre CopyLocal='false' devrait être défini pour la référence, car elle a été résolue à partir du GAC.
NoBecauseReferenceFoundInGAC Comportement hérité, CopyLocal='false' lorsque l’assembly est trouvé dans le GAC (même lorsqu’il a été résolu ailleurs).
NoBecauseConflictVictim Le paramètre CopyLocal='false' devrait être défini pour la référence, car elle a perdu un conflit avec un fichier d’assembly portant le même nom.
NoBecauseUnresolved La référence n’a pas été résolue. Elle ne peut pas être copiée dans le répertoire bin, car elle n’a pas été trouvée.
NoBecauseEmbedded La référence a été incorporée. Elle ne doit pas être copiée dans le répertoire bin, car elle ne sera pas chargée au moment de l’exécution.
NoBecauseParentReferencesFoundInGAC La propriété copyLocalDependenciesWhenParentReferenceInGac est définie sur « false », et tous les éléments sources parents ont été trouvés dans le GAC.
NoBecauseBadImage Le fichier d’assembly fourni ne doit pas être copié, car il s’agit d’une image incorrecte, peut-être non gérée, voire pas un assembly du tout.

Métadonnées de l’élément Private

Les métadonnées Private de toutes les références principales sont essentielles pour déterminer la valeur de CopyLocal. Chaque référence (principale ou dépendance) contient une liste de toutes les références principales (éléments sources) qui ont permis à cette référence d’être ajoutée à la fermeture.

  • Si aucun des éléments sources ne spécifie les métadonnées Private, CopyLocal est défini sur True (ou n’est pas défini, ce qui entraîne une définition par défaut sur True).
  • Si l’un des éléments sources spécifie Private=true, CopyLocal est défini sur True.
  • Si aucun des assemblys sources ne spécifie Private=true et qu’au moins un spécifie Private=false, CopyLocal est défini sur False.

Quelle référence définit Private sur false ?

Le dernier point est une raison souvent utilisée pour expliquer pourquoi CopyLocal est défini sur false : This reference is not "CopyLocal" because at least one source item had "Private" set to "false" and no source items had "Private" set to "true"..

MSBuild n’indique pas quelle référence a défini Private sur false, mais la visionneuse du journal structuré ajoute les métadonnées Private aux éléments qui l’ont spécifié ci-dessus :

Capture d’écran montrant Private défini sur false dans la visionneuse du journal structuré.

Cela simplifie les recherches et vous indique exactement quelle référence a provoqué la définition du paramètre CopyLocal=false pour la dépendance concernée.

Global Assembly Cache

Le Global Assembly Cache (GAC) est essentiel pour déterminer si les références doivent être copiées ou non dans la sortie. Cela est regrettable, car le contenu du GAC est spécifique à l’ordinateur, et cela entraîne des problèmes pour les builds reproductibles (où le comportement diffère d’une machine à l’autre en raison de l’état de l’ordinateur, tel que le GAC).

Pour remédier à la situation, des correctifs récents ont été apportés à ResolveAssemblyReference. Vous pouvez contrôler ce comportement à l’aide ces deux nouvelles entrées pour ResolveAssemblyReference :

    CopyLocalDependenciesWhenParentReferenceInGac="$(CopyLocalDependenciesWhenParentReferenceInGac)"
    DoNotCopyLocalIfInGac="$(DoNotCopyLocalIfInGac)"

AssemblySearchPaths

Il existe deux façons de personnaliser la liste des chemins d’accès ciblés par ResolveAssemblyReference pour tenter de localiser un assembly. Pour personnaliser entièrement la liste, la propriété AssemblySearchPaths peut être définie à l’avance. L’ordre est important. Si un assembly se trouve à deux emplacements, ResolveAssemblyReference s’arrête dès qu’il le trouve au premier emplacement.

Par défaut, ResolveAssemblyReference effectue des recherches dans dix emplacements (quatre si vous utilisez le SDK .NET), et chacun peut être désactivé en définissant l’indicateur approprié sur false :

  • La recherche de fichiers à partir du projet actuel est désactivée en définissant la propriété AssemblySearchPath_UseCandidateAssemblyFiles sur false.
  • La recherche de la propriété de chemin d’accès de référence (à partir d’un fichier .user) est désactivée en définissant la propriété AssemblySearchPath_UseReferencePath sur false.
  • L’utilisation du HintPath de l’élément est désactivée en définissant la propriété AssemblySearchPath_UseHintPathFromItem sur false.
  • L’utilisation du répertoire avec le runtime cible de MSBuild est désactivée en définissant la propriété AssemblySearchPath_UseTargetFrameworkDirectory sur false.
  • La recherche de dossiers d’assemblys à partir d’AssemblyFolders.config est désactivée en définissant la propriété AssemblySearchPath_UseAssemblyFoldersConfigFileSearchPath sur false.
  • La recherche dans le registre est désactivée en définissant la propriété AssemblySearchPath_UseRegistry sur false.
  • La recherche de dossiers d’assemblys inscrits hérités est désactivée en définissant la propriété AssemblySearchPath_UseAssemblyFolders sur false.
  • La recherche dans le GAC est désactivée en définissant la propriété AssemblySearchPath_UseGAC sur false.
  • Considérer l’option Include de la référence comme un véritable nom de fichier est désactivé en définissant la propriété AssemblySearchPath_UseRawFileName sur false.
  • La vérification du dossier de sortie de l’application est désactivée en définissant la propriété AssemblySearchPath_UseOutDir sur false.

Un conflit a été détecté

Il arrive souvent que MSBuild affiche un avertissement concernant l’utilisation par plusieurs références de différentes versions du même assembly. Il convient en général d’ajouter une redirection de liaison vers le fichier app.config.

Un moyen utile d’examiner ces conflits consiste à rechercher la mention « There was a conflict » (Un conflit a été détecté) dans la visionneuse du journal structuré MSBuild. Elle affiche des informations détaillées sur les versions de l’assembly concerné requises par chaque référence.