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 se produisent dans la ResolveAssemblyReference tâche. Cet article explique quelques détails sur le fonctionnement de ResolveAssemblyReference, et comment résoudre les échecs de build qui peuvent survenir lorsque ResolveAssemblyReference n’est pas en mesure de résoudre une référence. Pour examiner les échecs de référence d’assembly, vous pouvez installer Structured Log Viewer pour afficher les journaux MSBuild. Les captures d’écran de cet article sont extraites de la visionneuse de journaux structurés.

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

Les compilateurs ne peuvent accepter qu’un .dll chemin d’accès sur le système de fichiers en tant que référence, par conséquent 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 passés au compilateur via l'option /r.

Détermine également ResolveAssemblyReference l’ensemble complet (en fait la fermeture transitive en termes de théorie du graphique) de toutes .dll et .exe de références récursives, et pour chacun d’eux détermine s’il doit être copié dans le répertoire de sortie de build ou non. Il ne fait pas la copie réelle (qui est gérée ultérieurement, après l’étape de compilation réelle), mais elle prépare une liste d’éléments de fichiers à copier.

ResolveAssemblyReference est appelé à partir de la ResolveAssemblyReferences cible :

Capture d’écran du visionneur de logs montrant quand ResolveAssemblyReferences est appelé dans le processus de génération.

Si vous remarquez l’ordre, ResolveAssemblyReferences se produit avant Compile, et bien sûr, CopyFilesToOutputDirectory se produit après Compile.

Note

ResolveAssemblyReferencela tâche 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 ResolveAssemblyReference tâche est appelée dans le .targets fichier.

Entrées de ResolveAssemblyReference

ResolveAssemblyReference est complet dans la journalisation de ses entrées.

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

Le Parameters nœud est standard pour toutes les tâches, mais enregistre en outre ResolveAssemblyReference son propre ensemble d’informations sous Entrées (qui est essentiellement identique à 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 Reference MSBuild au moment où il ResolveAssemblyReference est appelé pour le projet. Toutes les références de métadonnées et d’assembly, y compris vos références NuGet, doivent être contenues dans cet élément. Chaque référence a un ensemble complet de métadonnées attachées à celui-ci :

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

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

Capture d’écran montrant AssemblyFiles.

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

FindDependencies="$(_FindDependencies)"

Vous pouvez définir cette propriété false sur dans votre build pour désactiver l’analyse des assemblys de dépendance transitive.

Algorithme ResolveAssemblyReference

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

  1. Journalisation des 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 l'objet table des références.
  4. Lisez le fichier cache à partir du obj répertoire (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 obj répertoire.
  8. Journaliser les résultats.

L’algorithme prend la liste d’entrée d’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 et les résout à partir de différents emplacements (y compris le GAC, AssemblyFoldersEx, etc.).

Les assemblages référencés sont ajoutés à la liste de façon 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 assemblages indirects ajoutés à l'ensemble en raison d'une référence transitive sont appelés dépendances. L'enregistrement de chaque assembly indirect consigne tous les éléments primaires (« racine ») qui ont conduit à son inclusion ainsi que leurs 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 de journal structuré.

Les assemblages 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 transitivement.

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, le projet peut spécifier une référence inutiles qui ne sera pas intégrée à l’assembly. Il est ok d’ajouter des références au projet qui ne sont pas nécessaires ; ils sont ignorés.

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

Les références peuvent également avoir les CopyLocal métadonnées ou non. Si la référence a CopyLocal = true, elle sera copiée ultérieurement dans le répertoire de sortie par la CopyFilesToOutputDirectory cible. Dans cet exemple, DataFlow a CopyLocal défini sur vrai, tandis que Immutable ne l'est pas :

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

Si les CopyLocal métadonnées sont manquantes entièrement, elles sont supposées être vraies par défaut. Ainsi, ResolveAssemblyReference essaie par défaut de copier les dépendances en sortie, sauf s'il trouve une raison de ne pas le faire. ResolveAssemblyReference enregistre les raisons pour lesquelles cela a choisi une référence particulière pour être CopyLocal ou non.

Toutes les raisons possibles justifiant CopyLocal la décision sont énumérées dans le tableau suivant. Il est utile de connaître ces chaînes pour pouvoir les rechercher dans les journaux de compilation.

État CopyLocal Descriptif
Undecided L’état local de copie est indécis pour le moment.
YesBecauseOfHeuristic La référence devrait avoir CopyLocal='true' parce qu’elle n’était pas « non » pour une raison quelconque.
YesBecauseReferenceItemHadMetadata La référence doit avoir CopyLocal='true' parce que son élément source a Private='true'
NoBecauseFrameworkFile La référence doit avoir CopyLocal='false' parce qu’il s’agit d’un fichier framework.
NoBecausePrerequisite La référence doit avoir CopyLocal='false' , car il s’agit d’un fichier requis.
NoBecauseReferenceItemHadMetadata La référence doit avoir CopyLocal='false' , car l’attribut Private est défini sur « false » dans le projet.
NoBecauseReferenceResolvedFromGAC La référence doit avoir CopyLocal='false' parce qu’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 La référence doit avoir CopyLocal='false' parce qu’elle a perdu le conflit avec un fichier d’assembly ayant 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. Il ne doit pas être copié dans le répertoire bin, car il ne sera pas chargé 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, éventuellement pas gérée, éventuellement pas d’un assembly du tout.

Métadonnées d’élément privé

Une partie importante de la détermination CopyLocal sont les Private métadonnées sur toutes les références principales. Chaque référence (principale ou de dépendance) inclut une liste de toutes les références principales (éléments source) qui ont contribué à l'ajout de cette référence à l'ensemble fermé.

  • Si aucun des éléments sources ne spécifie les Private métadonnées, CopyLocal est réglé sur True (ou n'est pas défini, auquel cas il prend par défaut la valeur 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 a défini Private sur false ?

Le dernier point est une raison souvent utilisée pour CopyLocal avoir la valeur 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 ne nous dit pas quelle référence a défini Private sur false, mais la visionneuse du journal structuré ajoute des métadonnées Private aux éléments pour lesquels cela a été spécifié ci-dessus :

Image montrant Private réglé à false dans la visionneuse de journaux structurés.

Cela simplifie les enquêtes et vous indique exactement quelle référence a provoqué la définition de la dépendance en question CopyLocal=false.

Cache d'assemblage global

Le Global Assembly Cache (GAC) joue un rôle important dans la décision de copier ou non les références vers la sortie. Cela est malheureux, 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 sur une autre machine dépendante de l’état de l’ordinateur, tel que le GAC).

Des correctifs récents ont été apportés à ResolveAssemblyReference pour atténuer la situation. Vous pouvez contrôler le comportement par ces deux nouvelles entrées pour ResolveAssemblyReference:

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

AssemblySearchPaths

Il existe deux façons de personnaliser la liste de recherche des chemins d’accès ResolveAssemblyReference lors du processus de localisation d’un assembly. Pour personnaliser entièrement la liste, la propriété AssemblySearchPaths peut être définie à l’avance. L’ordre est important ; si un assemblage se trouve à deux emplacements, ResolveAssemblyReference s’arrête après l'avoir trouvé au premier emplacement.

AssemblySearchPaths est une liste délimitée par des points-virgules. Il prend en charge un ensemble d’espaces réservés intégrés (par exemple, {HintPathFromItem} et {GAC}) qui s’étendent vers des emplacements réels pendant la résolution.

Par défaut, les projets de style non SDK utilisent l’ordre de chemin de recherche suivant :

  1. Fichiers d’assembly candidats ({CandidateAssemblyFiles})
  2. Propriété ReferencePath ($(ReferencePath))
  3. Chemins des indices provenant des éléments <Reference> ({HintPathFromItem})
  4. Répertoire du framework cible ({TargetFrameworkDirectory})
  5. Dossiers d’assembly de AssemblyFolders.config ($(AssemblyFoldersConfigFileSearchPath))
  6. Registre ({Registry:...})
  7. Dossiers d'assemblage hérités enregistrés ({AssemblyFolders})
  8. Cache d’assemblages global (Global Assembly Cache, GAC) ({GAC})
  9. Traitez la <Reference Include="..."> valeur comme un nom de fichier réel ({RawFileName})
  10. Répertoire de sortie ($(OutDir))

Le Kit de développement logiciel (SDK) .NET définit une valeur par défaut AssemblySearchPaths plus petite (à l’exception du GAC/du Registre/des recherches de répertoires de sortie par défaut) :

  • {CandidateAssemblyFiles}
  • {HintPathFromItem}
  • {TargetFrameworkDirectory}
  • {RawFileName}

Pour afficher la valeur effective de votre build, inspectez l’entrée SearchPaths enregistrée par ResolveAssemblyReference (par exemple, dans MSBuild Structured Log Viewer) ou prétraitez le projet avec msbuild /pp.

Chaque entrée peut être désactivée en définissant l’indicateur approprié sur false:

  • La recherche de fichiers à partir du projet actuel est désactivée en définissant la AssemblySearchPath_UseCandidateAssemblyFiles propriété sur false.
  • La recherche de la propriété de chemin d’accès de référence (à partir d’un .user fichier) est désactivée en définissant la AssemblySearchPath_UseReferencePath propriété sur false.
  • L'utilisation du chemin d'indication 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 AssemblySearchPath_UseTargetFrameworkDirectory propriété sur false.
  • La recherche de dossiers d’assembly à partir de AssemblyFolders.config est désactivée en définissant la AssemblySearchPath_UseAssemblyFoldersConfigFileSearchPath propriété sur false.
  • La recherche dans le Registre est désactivée en définissant la AssemblySearchPath_UseRegistry propriété sur false.
  • La recherche dans les dossiers de bibliothèques enregistrés anciens 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 AssemblySearchPath_UseGAC propriété sur false.
  • Le traitement de l’élément Include de la référence en tant que nom de fichier réel est désactivé en définissant la valeur de 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 AssemblySearchPath_UseOutDir propriété sur false.

Il y avait un conflit

Une situation courante est que MSBuild fournit un avertissement sur différentes versions du même assembly utilisées par différentes références. La solution implique souvent l’ajout d’une redirection de liaison au fichier app.config.

Un moyen utile d’examiner ces conflits consiste à rechercher dans MSBuild Structured Log Viewer pour « Il y a eu un conflit ». Il vous montre des informations détaillées sur les références indiquant quelles versions spécifiques de l’assembly sont nécessaires.