Problembehandlung für Assemblyverweise
Eine der wichtigsten Aufgaben in MSBuild und im .NET-Buildprozess ist das Auflösen von Assemblyverweisen. Dies geschieht in der Aufgabe ResolveAssemblyReference
. Dieser Artikel erläutert Details der Funktionsweise von ResolveAssemblyReference
und die Problembehandlung für Buildfehler, die auftreten, wenn ResolveAssemblyReference
einen Verweis nicht auflösen kann. Zur Untersuchung von Assemblyverweisfehlern empfiehlt es sich, die strukturierte Protokollanzeige zu installieren, um MSBuild-Protokolle anzuzeigen. Die Screenshots in diesem Artikel stammen aus der strukturierten Protokollanzeige.
Der Zweck von ResolveAssemblyReference
besteht darin, über das <Reference>
-Element alle in .csproj
-Dateien (oder an anderer Stelle) angegebenen Verweise Assemblydateipfaden im Dateisystem zuzuordnen.
Die Compiler können nur einen .dll
-Pfad im Dateisystem als Verweis akzeptieren. Daher konvertiert ResolveAssemblyReference
Zeichenfolgen wie mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
, die in Projektdateien vorkommen, in Pfade wie C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\mscorlib.dll
, die dann über den Switch /r
an den Compiler übergeben werden.
ResolveAssemblyReference
bestimmt außerdem den vollständigen Satz (transitiver Abschluss aus der Graphentheorie) aller .dll
- und .exe
-Verweise rekursiv sowie für jeden von ihnen, ob er in das Buildausgabeverzeichnis kopiert werden soll. Der eigentliche Kopiervorgang wird erst nach dem tatsächlichen Kompilierungsschritt ausgeführt, aber es wird eine Liste mit den Dateien vorbereitet, die kopiert werden sollen.
ResolveAssemblyReference
wird vom ResolveAssemblyReferences
-Ziel aus aufgerufen:
Wie Sie der Reihenfolge entnehmen können, wird ResolveAssemblyReferences
vor Compile
und CopyFilesToOutputDirectory
natürlich nach Compile
ausgeführt.
Hinweis
Die Aufgabe ResolveAssemblyReference
wird in der .targets
-Standarddatei Microsoft.Common.CurrentVersion.targets
in den MSBuild-Installationsordnern aufgerufen. Sie können auch die MSBuild-Ziele des .NET SDK online durchsuchen: https://github.com/dotnet/msbuild/blob/a936b97e30679dcea4d99c362efa6f732c9d3587/src/Tasks/Microsoft.Common.CurrentVersion.targets#L1991-L2140. Dieser Link zeigt genau, wo die Aufgabe ResolveAssemblyReference
in der .targets
-Datei aufgerufen wird.
ResolveAssemblyReference-Eingaben
Die Eingaben für ResolveAssemblyReference
werden umfassend protokolliert:
Der Knoten Parameters
ist standardmäßig für alle Aufgaben vorhanden. Zusätzlich protokolliert ResolveAssemblyReference
jedoch eigene Informationen unter „Eingaben“. Diese sind im Grunde mit den Informationen unter Parameters
identisch, aber anders strukturiert.
Die wichtigsten Eingaben sind Assemblies
und AssemblyFiles
:
<ResolveAssemblyReference
Assemblies="@(Reference)"
AssemblyFiles="@(_ResolvedProjectReferencePaths);@(_ExplicitReference)"
Assemblies
verwendet den Inhalt des MSBuild-Elements Reference
in dem Moment, in dem ResolveAssemblyReference
für das Projekt aufgerufen wird. Alle Metadaten und Assemblyverweise, einschließlich Ihrer NuGet-Verweise, müssen in diesem Element enthalten sein. Jeder Verweis verfügt über umfangreiche Metadaten:
AssemblyFiles
stammt aus dem als _ResolvedProjectReferencePaths
bezeichneten Ausgabeelement des ResolveProjectReference
-Ziels. ResolveProjectReference
wird vor ResolveAssemblyReference
ausgeführt und konvertiert <ProjectReference>
-Elemente in Pfade von erstellten Assemblys auf dem Datenträger. Daher enthält AssemblyFiles
die Assemblys, die von allen referenzierten Projekten des aktuellen Projekts erstellt wurden:
Eine weitere nützliche Eingabe ist der boolesche Parameter FindDependencies
, dessen Wert aus der Eigenschaft _FindDependencies
stammt:
FindDependencies="$(_FindDependencies)"
Sie können diese Eigenschaft in Ihrem Build auf false
festlegen, um die Analyse transitiver Abhängigkeitsassemblys zu deaktivieren.
ResolveAssemblyReference-Algorithmus
Der vereinfachte Algorithmus für die Aufgabe ResolveAssemblyReference
umfasst Folgendes:
- Protokollieren der Eingaben
- Überprüfen der Umgebungsvariablen
MSBUILDLOGVERBOSERARSEARCHRESULTS
. Legen Sie diese Variable auf einen beliebigen Wert fest, um ausführlichere Protokolle zu erhalten. - Initialisieren des Verweistabellenobjekts
- Lesen der Cachedatei aus dem Verzeichnis
obj
(sofern vorhanden) - Berechnen des Schließens von Abhängigkeiten
- Erstellen der Ausgabetabellen
- Schreiben der Cachedatei in das
obj
-Verzeichnis - Protokollieren der Ergebnisse
Der Algorithmus verwendet die Eingabeliste von Assemblys (sowohl aus Metadaten als auch aus Projektverweisen), ruft die Liste der Verweise für jede Assembly ab, die verarbeitet wird (durch Lesen von Metadaten), und erstellt einen vollständigen Satz (transitiver Abschluss) aller referenzierten Assemblys. Anschließend löst er sie von verschiedenen Orten aus auf (einschließlich GAC, AssemblyFoldersEx usw.).
Referenzierte Assemblys werden der Liste iterativ hinzugefügt, bis keine weiteren neuen Verweise mehr hinzukommen. Anschließend wird der Algorithmus beendet.
Direkte Verweise, die Sie für die Aufgabe angegeben haben, werden als primäre Verweise bezeichnet. Indirekte Assemblys, die dem Satz aufgrund eines transitiven Verweises hinzugefügt wurden, werden als Abhängigkeit bezeichnet. Im Datensatz für jede indirekte Assembly werden alle primären Elemente (Stammelemente), die zu ihrer Aufnahme geführt haben, sowie die entsprechenden Metadaten nachverfolgt.
Ergebnisse der Aufgabe „ResolveAssemblyReference“
ResolveAssemblyReference
bietet eine detaillierte Protokollierung der Ergebnisse:
Aufgelöste Assemblys werden in zwei Kategorien unterteilt: primäre Verweise und Abhängigkeiten. Primäre Verweise wurden explizit als Verweise auf das zu erstellende Projekt angegeben. Abhängigkeiten wurden transitiv von Verweisen von Verweisen abgeleitet.
Wichtig
ResolveAssemblyReference
liest Assemblymetadaten, um die Verweise einer bestimmten Assembly zu bestimmen. Wenn der C#-Compiler eine Assembly ausgibt, werden dadurch nur Verweise auf Assemblys hinzugefügt, die tatsächlich benötigt werden. Wenn Sie also ein bestimmtes Projekt kompilieren und das Projekt einen nicht benötigten Verweis angibt, wird dieser nicht in die Assembly integriert. Es ist kein Problem, einem Projekt Verweise hinzuzufügen, die nicht benötigt werden. Sie werden einfach ignoriert.
CopyLocal-Elementmetadaten
Verweise können optional auch über die CopyLocal
-Metadaten verfügen. Wenn der Verweis über CopyLocal = true
verfügt, wird er später vom CopyFilesToOutputDirectory
-Ziel in das Ausgabeverzeichnis kopiert. In diesem Beispiel ist CopyLocal
für DataFlow
auf „true“ festgelegt. Bei Immutable
ist dies dagegen nicht der Fall:
Wenn die CopyLocal
-Metadaten vollständig fehlen, wird standardmäßig von „true“ ausgegangen. ResolveAssemblyReference
versucht daher standardmäßig, Abhängigkeiten in die Ausgabe zu kopieren, es sei denn, es spricht etwas dagegen. ResolveAssemblyReference
erfasst die Gründe dafür, dass für einen bestimmten Verweis CopyLocal
verwendet wurde (oder nicht).
Alle möglichen Gründe für die CopyLocal
-Entscheidung sind in der folgenden Tabelle aufgeführt. Es ist hilfreich, sich mit diesen Zeichenfolgen vertraut zu machen, um in Buildprotokollen nach ihnen suchen zu können.
CopyLocal-Zustand | Beschreibung |
---|---|
Undecided |
Der Zustand für lokales Kopieren steht derzeit nicht fest. |
YesBecauseOfHeuristic |
Der Verweis muss den Wert CopyLocal='true' aufweisen, da es keinen Grund für „Nein“ gab. |
YesBecauseReferenceItemHadMetadata |
Der Verweis muss den Wert CopyLocal='true' aufweisen, da für das Quellelement „Private='true'“ festgelegt ist. |
NoBecauseFrameworkFile |
Der Verweis muss den Wert CopyLocal='false' aufweisen, da es sich um eine Frameworkdatei handelt. |
NoBecausePrerequisite |
Der Verweis muss den Wert CopyLocal='false' aufweisen, da es sich um eine erforderliche Datei handelt. |
NoBecauseReferenceItemHadMetadata |
Der Verweis muss den Wert CopyLocal='false' aufweisen, da das Attribut Private im Projekt auf „false“ festgelegt ist. |
NoBecauseReferenceResolvedFromGAC |
Der Verweis muss den Wert CopyLocal='false' aufweisen, da er über den GAC aufgelöst wurde. |
NoBecauseReferenceFoundInGAC |
Legacyverhalten: CopyLocal='false' , wenn die Assembly im GAC gefunden wird (auch wenn sie an anderer Stelle aufgelöst wurde). |
NoBecauseConflictVictim |
Der Verweis muss den Wert CopyLocal='false' aufweisen, da er einen Konflikt mit einer Assemblydatei gleichen Namens verloren hat. |
NoBecauseUnresolved |
Der Verweis wurde nicht aufgelöst. Er kann nicht in das Verzeichnis „bin“ kopiert werden, da er nicht gefunden wurde. |
NoBecauseEmbedded |
Der Verweis wurde eingebettet. Er darf nicht in das Verzeichnis „bin“ kopiert werden, da er zur Laufzeit nicht geladen wird. |
NoBecauseParentReferencesFoundInGAC |
Die Eigenschaft copyLocalDependenciesWhenParentReferenceInGac ist auf „false“ festgelegt, und alle übergeordneten Quellelemente wurden im GAC gefunden. |
NoBecauseBadImage |
Die bereitgestellte Assemblydatei darf nicht kopiert werden, da es sich um ein schlechtes Image handelt, das möglicherweise nicht verwaltet oder gar keine Assembly ist. |
Metadaten für private Elemente
Ein wichtiger Teil der Bestimmung von CopyLocal
sind die Private
-Metadaten für alle primären Verweise. Jeder Verweis (primär oder Abhängigkeit) verfügt über eine Liste aller primären Verweise (Quellelemente), die zu diesem Verweis beigetragen haben, der dem Abschluss hinzugefügt wird.
- Wenn keines der Quellelemente
Private
-Metadaten angibt, wirdCopyLocal
aufTrue
festgelegt (oder nicht festgelegt, was standardmäßig dem WertTrue
entspricht). - Wenn eines der Quellelemente
Private=true
angibt, wirdCopyLocal
aufTrue
festgelegt. - Wenn keine der Quellassemblys
Private=true
angibt und mindestens eine der QuellassemblysPrivate=false
angibt, wirdCopyLocal
aufFalse
festgelegt.
Von welchem Verweis wurde „Privat“ auf „false" festgelegt?
Der letzte Punkt ist ein gängiger Grund dafür, dass CopyLocal
auf „false“ festgelegt wird: 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 gibt keine Auskunft darüber, bei welchem Verweis Private
auf „false“ festgelegt ist, aber die strukturierte Protokollansicht fügt den obigen Elementen, bei denen dieser Wert angegeben war, Private
-Metadaten hinzu:
Dies vereinfacht Untersuchungen und zeigt genau, welcher Verweis dazu geführt hat, dass für die betreffende Abhängigkeit CopyLocal=false
festgelegt wurde.
Globaler Assemblycache
Der globale Assemblycache (Global Assembly Cache, GAC) spielt eine wichtige Rolle bei der Bestimmung, ob Verweise in die Ausgabe kopiert werden sollen. Leider ist der Inhalt des GAC computerspezifisch, was zu Problemen bei reproduzierbaren Builds führt, bei denen sich das Verhalten auf unterschiedlichen Computern abhängig vom Computerzustand (Beispiel: GAC) unterscheidet.
Es wurden kürzlich Korrekturen für ResolveAssemblyReference
vorgenommen, um dies zu verbessern. Sie können das Verhalten durch zwei neue Eingaben für ResolveAssemblyReference
steuern:
CopyLocalDependenciesWhenParentReferenceInGac="$(CopyLocalDependenciesWhenParentReferenceInGac)"
DoNotCopyLocalIfInGac="$(DoNotCopyLocalIfInGac)"
AssemblySearchPaths
Es gibt zwei Möglichkeiten, die Liste der Pfade anzupassen, die von ResolveAssemblyReference
durchsucht werden, um eine Assembly zu finden. Um die Liste vollständig anzupassen, kann die Eigenschaft AssemblySearchPaths
vorab festgelegt werden. Die Reihenfolge ist wichtig. Wenn sich eine Assembly an zwei Orten befindet, wird ResolveAssemblyReference
beendet, nachdem die Assembly am ersten Ort gefunden wurde.
Standardmäßig gibt es zehn Speicherortssuchen ResolveAssemblyReference
(vier, wenn Sie das .NET SDK verwenden), und jedes kann deaktiviert werden, indem das entsprechende Flag auf "false" festgelegt wird:
- Das Durchsuchen von Dateien aus dem aktuellen Projekt wird deaktiviert, indem die Eigenschaft
AssemblySearchPath_UseCandidateAssemblyFiles
auf „false“ festgelegt wird. - Die Suche nach der Verweispfadeigenschaft (aus einer
.user
-Datei) wird deaktiviert, indem die EigenschaftAssemblySearchPath_UseReferencePath
auf „false“ festgelegt wird. - Die Verwendung des Hinweispfads aus dem Element wird deaktiviert, indem die Eigenschaft
AssemblySearchPath_UseHintPathFromItem
auf „false“ festgelegt wird. - Die Verwendung des Verzeichnisses mit der Zielruntime von MSBuild wird deaktiviert, indem die Eigenschaft
AssemblySearchPath_UseTargetFrameworkDirectory
auf „false“ festgelegt wird. - Das Durchsuchen von Assemblyordnern aus „AssemblyFolders.config“ wird deaktiviert, indem die Eigenschaft
AssemblySearchPath_UseAssemblyFoldersConfigFileSearchPath
auf „false“ festgelegt wird. - Die Suche nach der Registrierung wird deaktiviert, indem die Eigenschaft
AssemblySearchPath_UseRegistry
auf „false“ festgelegt wird. - Das Durchsuchen von registrierten Legacy-Assemblyordnern wird deaktiviert, indem die Eigenschaft
AssemblySearchPath_UseAssemblyFolders
auf „false“ festgelegt wird. - Die Suche im GAC wird deaktiviert, indem die Eigenschaft
AssemblySearchPath_UseGAC
auf „false“ festgelegt wird. - Die Behandlung der Einschließung des Verweises als echter Dateiname wird deaktiviert, indem die Eigenschaft
AssemblySearchPath_UseRawFileName
auf „false“ festgelegt wird. - Die Überprüfung des Ausgabeordners der Anwendung wird deaktiviert, indem die Eigenschaft
AssemblySearchPath_UseOutDir
auf „false“ festgelegt wird.
Es ist ein Konflikt aufgetreten.
Es kommt vor, dass MSBuild eine Warnung mit einem Hinweis auf verschiedene Versionen der gleichen Assembly ausgibt, die von verschiedenen Verweisen verwendet werden. Die Lösung umfasst häufig das Hinzufügen einer Bindungsumleitung zur Datei „app.config“.
Eine praktische Möglichkeit zur Untersuchung derartiger Konflikte besteht darin, in der strukturierten Protokollansicht von MSBuild nach „Es ist ein Konflikt aufgetreten.“ zu suchen. Dort finden Sie ausführliche Informationen dazu, von welchen Verweisen welche Versionen der betreffenden Assembly benötigt wurden.