Freigeben über


Fehlerbehebung bei Assembly-Verweisen

Eine der wichtigsten Aufgaben in MSBuild und der .NET-Buildprozess ist das Auflösen von Assemblyverweisen, was in der ResolveAssemblyReference Aufgabe geschieht. In diesem Artikel werden einige Details zur Funktionsweise von ResolveAssemblyReference sowie zur Fehlerbehebung bei Build-Fehlern erläutert, die auftreten können, wenn ResolveAssemblyReference eine Referenz nicht aufgelöst werden kann. Um Assemblyverweisfehler zu untersuchen, sollten Sie den Strukturierten Protokoll-Viewer installieren, um MSBuild-Protokolle anzuzeigen. Die Screenshots in diesem Artikel stammen aus dem Strukturierten Protokoll-Viewer.

Der Zweck ResolveAssemblyReference besteht darin, alle in .csproj Dateien (oder an anderer Stelle) angegebenen Verweise über das <Reference> Element zu übernehmen und sie Pfaden zu Assemblydateien 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 erscheinen, in Pfade wie C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\mscorlib.dll, die dann über die /r Option an den Compiler übergeben werden.

ResolveAssemblyReference Bestimmt außerdem den vollständigen Satz (tatsächlich die transitive Schließung in Begriffen der Graphentheorie) aller .dll und .exe Verweise rekursiv und entscheidet für jeden von ihnen, ob er in das Buildausgabeverzeichnis kopiert werden soll oder nicht. Das eigentliche Kopieren (das später nach dem tatsächlichen Kompilierungsschritt behandelt wird) wird nicht ausgeführt, aber es bereitet eine Elementliste der zu kopierenden Dateien vor.

ResolveAssemblyReference wird vom ResolveAssemblyReferences Ziel aufgerufen:

Screenshot der Protokollanzeige, die zeigt, wann ResolveAssemblyReferences im Buildprozess aufgerufen wird.

Wenn Sie die Reihenfolge bemerken, ResolveAssemblyReferences geschieht vor Compile, und natürlich geschieht CopyFilesToOutputDirectory nach Compile.

Hinweis

ResolveAssemblyReference die Aufgabe wird in der Standarddatei .targetsMicrosoft.Common.CurrentVersion.targets in den MSBuild-Installationsordnern aufgerufen. Sie können sich auch die .NET SDK MSBuild-Ziele online ansehen unter https://github.com/dotnet/msbuild/blob/a936b97e30679dcea4d99c362efa6f732c9d3587/src/Tasks/Microsoft.Common.CurrentVersion.targets#L1991-L2140. Dieser Link zeigt genau an, wo die ResolveAssemblyReference Aufgabe in der .targets Datei aufgerufen wird.

Eingaben für ResolveAssemblyReference

ResolveAssemblyReference ist umfassend in der Protokollierung seiner Eingaben:

Screenshot mit Eingabeparametern für die Aufgabe ResolveAssemblyReference.

Der Parameters Knoten ist Standard für alle Aufgaben, protokolliert zusätzlich ResolveAssemblyReference seine eigenen Informationen unter "Eingaben" (die im Grunde identisch mit denen unter Parameters sind, aber anders strukturiert sind).

Die wichtigsten Eingaben sind Assemblies und AssemblyFiles:

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

Assemblies verwendet den Inhalt des Reference MSBuild-Elements im Moment, wenn ResolveAssemblyReference für das Projekt aufgerufen wird. Alle Metadaten- und Assemblyverweise, einschließlich Ihrer NuGet-Verweise, sollten in diesem Element enthalten sein. Jeder Verweis weist einen umfangreichen Satz von Metadaten auf:

Screenshot mit Metadaten in einem Assemblyverweis.

AssemblyFiles stammt aus dem ResolveProjectReference-Ausgabeelement des Ziels namens _ResolvedProjectReferencePaths. ResolveProjectReference wird vor ResolveAssemblyReference ausgeführt und konvertiert <ProjectReference> Elemente in die Pfade der erstellten Assemblys auf dem Datenträger. Daher enthalten die AssemblyFiles Assemblys, die von allen referenzierten Projekten des aktuellen Projekts erstellt wurden:

Screenshot mit

Eine weitere nützliche Eingabe ist der boolesche FindDependencies Parameter, der seinen Wert aus der _FindDependencies Eigenschaft verwendet:

FindDependencies="$(_FindDependencies)"

Sie können diese Eigenschaft auf false in Ihrem Build setzen, um die Analyse transitiver Abhängigkeitsasssemblies auszuschalten.

ResolveAssemblyReference-Algorithmus

Der vereinfachte Algorithmus für die ResolveAssemblyReference Aufgabe lautet wie folgt:

  1. Protokolleingaben.
  2. Überprüfen Sie die Umgebungsvariable MSBUILDLOGVERBOSERARSEARCHRESULTS . Legen Sie diese Variable auf einen beliebigen Wert fest, um detailliertere Protokolle abzurufen.
  3. Initialisieren Sie die Tabelle des Verweisesobjekts.
  4. Lesen Sie die Cachedatei aus dem obj Verzeichnis (sofern vorhanden).
  5. Berechnen des Schließens von Abhängigkeiten.
  6. Erstellen Sie die Ausgabetabellen.
  7. Schreiben Sie die Cachedatei in das obj Verzeichnis.
  8. Protokollieren Sie die Ergebnisse.

Der Algorithmus verwendet die Eingabeliste von Assemblys (sowohl aus Metadaten als auch Projektverweise), ruft die Liste der Verweise für jede Assembly ab, die verarbeitet wird (durch Lesen von Metadaten) und erstellt einen vollständigen Satz (transitives Schließen) aller referenzierten Assemblys und löst sie von verschiedenen Speicherorten (einschließlich GAC, AssemblyFoldersEx usw.) auf.

Referenzierte Assemblys werden der Liste iterativ hinzugefügt, bis keine neuen Verweise hinzugefügt werden. Anschließend stoppt der Algorithmus.

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. Der Eintrag für jede indirekte Assembly zeichnet alle primären („Stamm“) Elemente auf, die zu ihrer Aufnahme geführt haben, und die entsprechenden Metadaten.

Ergebnisse der ResolveAssemblyReference-Aufgabe

ResolveAssemblyReference bietet eine detaillierte Protokollierung der Ergebnisse:

Screenshot mit ResolveAssemblyReference-Ergebnissen im strukturierten Protokoll-Viewer.

Gelöste Assemblys sind 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.

Von Bedeutung

ResolveAssemblyReference liest Assemblymetadaten, um die Verweise einer bestimmten Assembly zu bestimmen. Wenn der C#-Compiler eine Assembly ausgibt, werden nur Verweise auf Assemblys hinzugefügt, die tatsächlich benötigt werden. Wenn Sie also ein bestimmtes Projekt kompilieren, kann das Projekt einen nicht benötigten Verweis angeben, der nicht in die Assembly integriert wird. Es ist OK, Verweise auf Projekt hinzuzufügen, die nicht benötigt werden; sie werden ignoriert.

CopyLocal-Objektmetadaten

Verweise können auch über die CopyLocal Metadaten verfügen oder nicht. Wenn der Verweis CopyLocal = true vorhanden ist, wird er später vom CopyFilesToOutputDirectory-Ziel in das Ausgabeverzeichnis kopiert. In diesem Beispiel ist DataFlow auf CopyLocal true gesetzt, während dies bei Immutable nicht der Fall ist.

Screenshot der CopyLocal-Einstellungen für einige Verweise.

Wenn die CopyLocal Metadaten vollständig fehlen, wird davon ausgegangen, dass sie standardmäßig "true" ist. Versucht ResolveAssemblyReference standardmäßig, Abhängigkeiten in die Ausgabe zu kopieren, es sei denn, es gibt einen Grund dagegen. ResolveAssemblyReference zeichnet die Gründe auf, warum ein bestimmter Verweis ausgewählt wurde CopyLocal oder nicht.

Alle möglichen Gründe für die CopyLocal Entscheidung werden in der folgenden Tabelle aufgelistet. Es ist hilfreich, diese Zeichenfolgen zu kennen, um in Buildprotokollen nach diesen Zeichenfolgen zu suchen.

CopyLocal-Zustand Description
Undecided Der lokale Kopierstatus ist derzeit unentschieden.
YesBecauseOfHeuristic Der Verweis sollte haben CopyLocal='true' , weil er aus irgendeinem Grund nicht "nein" war.
YesBecauseReferenceItemHadMetadata Der Verweis sollte CopyLocal='true' beinhalten, da sein Quellelement "Private='true'" hat.
NoBecauseFrameworkFile Der Verweis sollte aufweisen CopyLocal='false' , da es sich um eine Frameworkdatei handelt.
NoBecausePrerequisite Der Verweis sollte CopyLocal='false' enthalten, da es sich um eine erforderliche Datei handelt.
NoBecauseReferenceItemHadMetadata Der Verweis sollte CopyLocal='false' aufweisen, da das Private Attribut im Projekt auf "false" festgelegt ist.
NoBecauseReferenceResolvedFromGAC Der Verweis sollte CopyLocal='false' aufweisen, da er vom GAC aufgelöst worden ist.
NoBecauseReferenceFoundInGAC Legacy-Verhalten, CopyLocal='false' wenn die Assembly im GAC gefunden wird (auch wenn sie an anderer Stelle aufgelöst wurde).
NoBecauseConflictVictim Der Verweis sollte CopyLocal='false' enthalten, da ein Konflikt mit einer gleichnamigen Assembly-Datei verloren wurde.
NoBecauseUnresolved Der Verweis war nicht gelöst. Sie kann nicht in das Bin-Verzeichnis kopiert werden, da es nicht gefunden wurde.
NoBecauseEmbedded Der Verweis wurde eingebettet. Sie sollte nicht in das Bin-Verzeichnis kopiert werden, da sie zur Laufzeit nicht geladen wird.
NoBecauseParentReferencesFoundInGAC Die Eigenschaft copyLocalDependenciesWhenParentReferenceInGac ist auf "false" festgelegt, und alle übergeordneten Quellelemente wurden im GAC gefunden.
NoBecauseBadImage Die bereitgestellte Assembly-Datei sollte nicht kopiert werden, da es sich um ein schlechtes Abbild handeln könnte, möglicherweise nicht verwaltet wird und möglicherweise gar keine Assembly ist.

Metadaten für private Elemente

Ein wichtiger Bestandteil der Bestimmung von CopyLocal sind die Private Metadaten aller primären Verweise. Jeder Verweis (primärer oder abhängiger Verweis) verfügt über eine Liste aller primären Verweise (Quellelemente), die zu dessen Aufnahme in den Abschluss beigetragen haben.

  • Wenn keines der Quellelemente Metadaten Private spezifiziert, wird CopyLocal auf True festgelegt (oder nicht festgelegt ist, was standardmäßig auf True festgelegt wird).
  • Wenn eines der Quellelemente Private=true angibt, wird CopyLocal auf True gesetzt.
  • Wenn keine der Quellassemblys das Private=true angibt und mindestens eine das Private=false angibt, wird CopyLocal auf False gesetzt.

Welche Referenz setzt private auf false?

Der letzte Punkt ist ein häufig verwendeter Grund dafür, CopyLocal auf "false" gesetzt zu werden: 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 teilt uns nicht mit, welcher Verweis Private auf 'false' gesetzt hat, aber der strukturierte Protokoll-Viewer fügt die Private Metadaten zu den Elementen hinzu, für die es oben angegeben wurde.

Screenshot, der zeigt, dass

Dies vereinfacht Untersuchungen und informiert Sie genau, welcher Verweis dazu geführt hat, dass die betreffende Abhängigkeit mit CopyLocal=false festgelegt wurde.

Globaler Assemblycache

Der globale Assemblycache (Global Assembly Cache, GAC) spielt eine wichtige Rolle bei der Bestimmung, ob Verweise auf die Ausgabe kopiert werden sollen. Dies ist bedauerlich, weil der Inhalt des GAC maschinenspezifisch ist und dies zu Problemen bei reproduzierbaren Builds führt (wo sich das Verhalten auf unterschiedlichen Computern unterscheidet, die vom Computerzustand abhängig sind, z. B. das GAC).

Kürzlich wurden Korrekturen an ResolveAssemblyReference vorgenommen, um die Situation zu lindern. Sie können das Verhalten über diese beiden neuen Eingaben zu ResolveAssemblyReference steuern:

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

AssemblySearchPaths

Es gibt zwei Möglichkeiten, die Liste der Pfade, in denen ResolveAssemblyReference nach einer Assembly sucht, anzupassen. Um die Liste vollständig anzupassen, kann die Eigenschaft AssemblySearchPaths vorab festgelegt werden. Die Reihenfolge ist wichtig; wenn sich eine Assembly an zwei Speicherorten befindet, ResolveAssemblyReference endet der Vorgang, nachdem sie am ersten Speicherort gefunden wurde.

AssemblySearchPaths ist eine durch Semikolons getrennte Liste. Sie unterstützt eine Reihe integrierter Platzhalter (z. B. {HintPathFromItem} und {GAC}), die während des Auflösens auf tatsächliche Positionen erweitert werden.

Standardmäßig verwenden Projekte im Nicht-SDK-Stil die folgende Suchpfadreihenfolge:

  1. Kandidatenassemblydateien ({CandidateAssemblyFiles})
  2. Die ReferencePath Eigenschaft ($(ReferencePath))
  3. Hinweispfade aus <Reference> Elementen ({HintPathFromItem})
  4. Das Zielframeworkverzeichnis ({TargetFrameworkDirectory})
  5. Assembly-Ordner von AssemblyFolders.config ($(AssemblyFoldersConfigFileSearchPath))
  6. Die Registrierung ({Registry:...})
  7. Ältere registrierte Assemblyordner ({AssemblyFolders})
  8. Globaler Assemblycache (GAC) ({GAC})
  9. Behandeln des <Reference Include="..."> Werts als echter Dateiname ({RawFileName})
  10. Das Ausgabeverzeichnis ($(OutDir))

Das .NET SDK legt einen kleineren Standardwert für AssemblySearchPaths fest (ohne standardmäßige Durchsuchung von GAC, Registrierung und Ausgabeverzeichnis).

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

Um den effektiven Wert für Ihren Build zu sehen, inspizieren Sie die protokollierte Eingabe von SearchPaths durch ResolveAssemblyReference (z. B. im MSBuild Structured Log Viewer), oder verarbeiten Sie das Projekt mit msbuild /pp vor.

Jeder Eintrag kann deaktiviert werden, indem die entsprechende Kennzeichnung auf false gesetzt wird.

  • Das Durchsuchen von Dateien aus dem aktuellen Projekt ist deaktiviert, indem die AssemblySearchPath_UseCandidateAssemblyFiles Eigenschaft auf "false" festgelegt wird.
  • Das Durchsuchen der Verweispfadeigenschaft (aus einer .user Datei) ist deaktiviert, indem man die AssemblySearchPath_UseReferencePath Eigenschaft auf "false" festlegt.
  • Die Verwendung des Hinweispfads aus dem Element ist deaktiviert, indem die Eigenschaft AssemblySearchPath_UseHintPathFromItem auf false festgelegt wird.
  • Die Verwendung des Verzeichnisses mit der Ziellaufzeit von MSBuild ist deaktiviert, indem die AssemblySearchPath_UseTargetFrameworkDirectory Eigenschaft auf "false" festgelegt wird.
  • Das Durchsuchen von Assemblyordnern aus AssemblyFolders.config ist deaktiviert, indem die AssemblySearchPath_UseAssemblyFoldersConfigFileSearchPath Eigenschaft auf "false" festgelegt wird.
  • Die Suche in der Registrierung ist deaktiviert, indem die AssemblySearchPath_UseRegistry Eigenschaft auf "false" festgelegt wird.
  • Das Durchsuchen von älteren registrierten Assemblyordnern ist deaktiviert, indem die AssemblySearchPath_UseAssemblyFolders Eigenschaft auf "false" festgelegt wird.
  • Die Suche im GAC ist deaktiviert, indem die AssemblySearchPath_UseGAC Eigenschaft auf "false" festgelegt wird.
  • Die Behandlung des "Include"-Elements eines Verweises als echter Dateiname wird deaktiviert, indem die AssemblySearchPath_UseRawFileName-Eigenschaft auf "false" gesetzt wird.
  • Das Überprüfen des Ausgabeordners der Anwendung ist deaktiviert, indem die AssemblySearchPath_UseOutDir Eigenschaft auf "false" festgelegt wird.

Es gab einen Konflikt

Eine häufige Situation ist, dass MSBuild eine Warnung ausgibt, weil verschiedene Verweise unterschiedliche Versionen derselben Assembly verwenden. Die Lösung umfasst häufig das Hinzufügen einer Bindungsumleitung zur app.config Datei.

Eine nützliche Möglichkeit, diese Konflikte zu untersuchen, besteht darin, in DER MSBuild Structured Log Viewer nach "Es gab einen Konflikt" zu suchen. Es zeigt Ihnen detaillierte Informationen darüber, welche Verweise benötigt werden, welche Versionen der betreffenden Assembly erforderlich sind.