Freigeben über


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:

Screenshot: Protokollanzeige, die zeigt, wann „ResolveAssemblyReferences“ im Buildprozess aufgerufen wird

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:

Screenshot: Eingabeparameter für die Aufgabe „ResolveAssemblyReference“

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:

Screenshot: Metadaten in einem Assemblyverweis

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:

Screenshot: AssemblyFiles

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:

  1. Protokollieren der Eingaben
  2. Überprüfen der Umgebungsvariablen MSBUILDLOGVERBOSERARSEARCHRESULTS. Legen Sie diese Variable auf einen beliebigen Wert fest, um ausführlichere Protokolle zu erhalten.
  3. Initialisieren des Verweistabellenobjekts
  4. Lesen der Cachedatei aus dem Verzeichnis obj (sofern vorhanden)
  5. Berechnen des Schließens von Abhängigkeiten
  6. Erstellen der Ausgabetabellen
  7. Schreiben der Cachedatei in das obj-Verzeichnis
  8. 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:

Screenshot: Ergebnisse von „ResolveAssemblyReference“ in der strukturierten Protokollanzeige

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:

Screenshot: CopyLocal-Einstellungen für einige Verweise

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, wird CopyLocal auf True festgelegt (oder nicht festgelegt, was standardmäßig dem Wert True entspricht).
  • Wenn eines der Quellelemente Private=true angibt, wird CopyLocal auf True festgelegt.
  • Wenn keine der Quellassemblys Private=true angibt und mindestens eine der Quellassemblys Private=false angibt, wird CopyLocal auf False 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:

Screenshot: „Privat“ mit dem Wert „false“ in der strukturierten Protokollanzeige

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 Eigenschaft AssemblySearchPath_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.