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
:
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 :
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 :
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 :
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 :
- Enregistrez les entrées.
- Vérifiez la variable d’environnement
MSBUILDLOGVERBOSERARSEARCHRESULTS
. Définissez cette variable sur n’importe quelle valeur pour obtenir des journaux plus détaillés. - Initialisez la table d’objets de référence.
- Lisez le fichier cache du répertoire
obj
(le cas échéant). - Calculez la fermeture des dépendances.
- Générez les tables de sortie.
- Écrivez le fichier cache dans le répertoire
obj
. - 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 :
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
:
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 surTrue
(ou n’est pas défini, ce qui entraîne une définition par défaut surTrue
). - Si l’un des éléments sources spécifie
Private=true
,CopyLocal
est défini surTrue
. - Si aucun des assemblys sources ne spécifie
Private=true
et qu’au moins un spécifiePrivate=false
,CopyLocal
est défini surFalse
.
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 :
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.