Rozwiązywanie problemów z odwołaniami do zestawów
Jednym z najważniejszych zadań w programie MSBuild i procesie kompilacji platformy .NET jest rozwiązywanie odwołań do zestawów, które są wykonywane w ResolveAssemblyReference
zadaniu. W tym artykule opisano niektóre szczegóły dotyczące ResolveAssemblyReference
sposobu działania oraz sposób rozwiązywania problemów z błędami kompilacji, które mogą wystąpić, gdy ResolveAssemblyReference
nie można rozpoznać odwołania. Aby zbadać błędy odwołań do zestawów, możesz zainstalować przeglądarkę dzienników strukturalnych, aby wyświetlić dzienniki programu MSBuild. Zrzuty ekranu w tym artykule pochodzą z przeglądarki dzienników strukturalnych.
Celem programu ResolveAssemblyReference
jest użycie wszystkich odwołań określonych w .csproj
plikach (lub gdzie indziej) za pośrednictwem <Reference>
elementu i mapowanie ich na ścieżki do plików zestawów w systemie plików.
Kompilatory mogą akceptować tylko ścieżkę .dll
w systemie plików jako odwołanie, więc ResolveAssemblyReference
konwertuje ciągi takie jak mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
w plikach projektu na ścieżki, takie jak C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\mscorlib.dll
, które są następnie przekazywane do kompilatora za pośrednictwem przełącznika /r
.
ResolveAssemblyReference
Ponadto określa pełny zestaw (faktycznie przejściowe zamknięcie w kategoriach teorii grafu) wszystkich .dll
i .exe
odwołań cyklicznych, a dla każdego z nich określa, czy ma zostać skopiowany do katalogu wyjściowego kompilacji, czy nie. Nie wykonuje rzeczywistego kopiowania (obsługiwanego później, po rzeczywistym kroku kompilacji), ale przygotowuje listę elementów plików do skopiowania.
ResolveAssemblyReference
jest wywoływany ResolveAssemblyReferences
z obiektu docelowego:
Jeśli zauważysz, że kolejność ResolveAssemblyReferences
jest wykonywana przed Compile
elementem , i oczywiście CopyFilesToOutputDirectory
następuje po Compile
.
Uwaga
ResolveAssemblyReference
zadanie jest wywoływane w pliku Microsoft.Common.CurrentVersion.targets
standardowym .targets
w folderach instalacyjnych MSBuild. Możesz również przeglądać cele programu MSBuild dla zestawu .NET SDK w trybie online pod adresem https://github.com/dotnet/msbuild/blob/a936b97e30679dcea4d99c362efa6f732c9d3587/src/Tasks/Microsoft.Common.CurrentVersion.targets#L1991-L2140. Ten link pokazuje dokładnie, gdzie ResolveAssemblyReference
zadanie jest wywoływane w .targets
pliku.
ResolveAssemblyReference inputs
ResolveAssemblyReference
usługa jest kompleksowa w zakresie rejestrowania danych wejściowych:
Węzeł Parameters
jest standardowy dla wszystkich zadań, ale dodatkowo ResolveAssemblyReference
rejestruje własny zestaw informacji w obszarze Dane wejściowe (które są zasadniczo takie same jak w obszarze Parameters
, ale inaczej ustrukturyzowane).
Najważniejsze dane wejściowe to Assemblies
i AssemblyFiles
:
<ResolveAssemblyReference
Assemblies="@(Reference)"
AssemblyFiles="@(_ResolvedProjectReferencePaths);@(_ExplicitReference)"
Assemblies
używa zawartości Reference
elementu MSBuild w momencie, gdy ResolveAssemblyReference
jest wywoływany dla projektu. Wszystkie odwołania do metadanych i zestawów, w tym odwołania nuGet, powinny być zawarte w tym elemencie. Każde odwołanie zawiera bogaty zestaw metadanych dołączonych do niego:
AssemblyFiles
pochodzi z ResolveProjectReference
elementu wyjściowego elementu docelowego o nazwie _ResolvedProjectReferencePaths
. ResolveProjectReference
jest uruchamiany przed ResolveAssemblyReference
i konwertuje <ProjectReference>
elementy na ścieżki skompilowanych zestawów na dysku. W związku z tym AssemblyFiles
będą zawierać zestawy utworzone przez wszystkie przywołyzowane projekty bieżącego projektu:
Innym przydatnym wejściem jest parametr logiczny FindDependencies
, który pobiera jego wartość z _FindDependencies
właściwości :
FindDependencies="$(_FindDependencies)"
Tę właściwość false
można ustawić na w kompilacji, aby wyłączyć analizowanie zestawów zależności przechodnich.
ResolveAssemblyReference algorithm
Uproszczony algorytm zadania ResolveAssemblyReference
jest następujący:
- Dane wejściowe dziennika.
- Sprawdź zmienną
MSBUILDLOGVERBOSERARSEARCHRESULTS
środowiskową. Ustaw tę zmienną na dowolną wartość, aby uzyskać bardziej szczegółowe dzienniki. - Zainicjuj tabelę obiektów odwołań.
- Odczytaj plik pamięci podręcznej
obj
z katalogu (jeśli istnieje). - Oblicz zamknięcie zależności.
- Skompiluj tabele wyjściowe.
- Zapisz plik pamięci podręcznej w
obj
katalogu. - Zarejestruj wyniki.
Algorytm pobiera listę wejściowych zestawów (zarówno z metadanych, jak i odwołań do projektu), pobiera listę odwołań dla każdego zestawu, który przetwarza (odczytując metadane) i tworzy pełny zestaw (przejściowe zamknięcie) wszystkich zestawów, do których się odwołuje, i rozpoznaje je z różnych lokalizacji (w tym GAC, AssemblyFoldersEx itd.).
Przywoływany zestawy są dodawane do listy iteracyjnie, dopóki nie zostaną dodane żadne nowe odwołania. Następnie algorytm zatrzymuje się.
Bezpośrednie odwołania podane do zadania są nazywane odwołaniami podstawowymi. Zestawy pośrednie, które zostały dodane do zestawu z powodu odwołania przechodniego, są nazywane zależnością. Rekord dla każdego zestawu pośredniego śledzi wszystkie elementy podstawowe ("root"), które doprowadziły do jego dołączenia i odpowiednich metadanych.
Wyniki zadania ResolveAssemblyReference
ResolveAssemblyReference
Zawiera szczegółowe rejestrowanie wyników:
Rozwiązane zestawy są podzielone na dwie kategorie: odwołania podstawowe i zależności. Odwołania podstawowe zostały określone jawnie jako odwołania do tworzonego projektu. Zależności zostały wywnioskowane z odwołań przechodnich.
Ważne
ResolveAssemblyReference
odczytuje metadane zestawu w celu określenia odwołań do danego zestawu. Gdy kompilator języka C# emituje zestaw, dodaje tylko odwołania do zestawów, które są rzeczywiście potrzebne. Tak więc może się zdarzyć, że podczas kompilowania określonego projektu projekt może określić niepotrzebne odwołanie, które nie zostanie upieczone w zestawie. Możesz dodać odwołania do projektu, które nie są potrzebne. są ignorowane.
CopyLocal item metadata (Kopiowanie metadanych elementu lokalnego)
Odwołania mogą również zawierać CopyLocal
metadane. Jeśli odwołanie ma CopyLocal = true
wartość , zostanie później skopiowane do katalogu wyjściowego przez obiekt docelowy CopyFilesToOutputDirectory
. W tym przykładzie DataFlow
ustawiono CopyLocal
wartość true, ale Immutable
nie:
CopyLocal
Jeśli brakuje metadanych w całości, przyjmuje się, że domyślnie ma wartość true. Dlatego ResolveAssemblyReference
domyślnie próbuje skopiować zależności do danych wyjściowych, chyba że znajdzie powód, aby nie. ResolveAssemblyReference
rejestruje przyczyny, dla których wybrała konkretne odwołanie, które ma być CopyLocal
lub nie.
Wszystkie możliwe przyczyny CopyLocal
decyzji są wyliczane w poniższej tabeli. Warto znać te ciągi, aby móc wyszukiwać je w dziennikach kompilacji.
KopiujLokalny stan | opis |
---|---|
Undecided |
Stan lokalny kopiowania jest teraz niezdecydowany. |
YesBecauseOfHeuristic |
Odwołanie powinno mieć CopyLocal='true' , ponieważ nie było "nie" z jakiegokolwiek powodu. |
YesBecauseReferenceItemHadMetadata |
Odwołanie powinno mieć CopyLocal='true' , ponieważ jego element źródłowy ma wartość Private='true' |
NoBecauseFrameworkFile |
Odwołanie powinno mieć CopyLocal='false' , ponieważ jest to plik struktury. |
NoBecausePrerequisite |
Odwołanie powinno mieć CopyLocal='false' , ponieważ jest to plik wymagań wstępnych. |
NoBecauseReferenceItemHadMetadata |
Odwołanie powinno mieć CopyLocal='false' wartość , ponieważ Private atrybut jest ustawiony na wartość "false" w projekcie. |
NoBecauseReferenceResolvedFromGAC |
Odwołanie powinno mieć CopyLocal='false' miejsce, ponieważ zostało ono rozwiązane z GAC. |
NoBecauseReferenceFoundInGAC |
Starsze zachowanie, CopyLocal='false' gdy zestaw zostanie znaleziony w GAC (nawet wtedy, gdy został rozwiązany gdzie indziej). |
NoBecauseConflictVictim |
Odwołanie powinno mieć CopyLocal='false' , ponieważ utraciło konflikt między plikiem zestawu o tej samej nazwie. |
NoBecauseUnresolved |
Odwołanie zostało nierozwiązane. Nie można skopiować go do katalogu bin, ponieważ nie został znaleziony. |
NoBecauseEmbedded |
Odwołanie zostało osadzone. Nie należy kopiować go do katalogu bin, ponieważ nie zostanie załadowany w czasie wykonywania. |
NoBecauseParentReferencesFoundInGAC |
Właściwość copyLocalDependenciesWhenParentReferenceInGac jest ustawiona na false, a wszystkie nadrzędne elementy źródłowe zostały znalezione w GAC. |
NoBecauseBadImage |
Podany plik zestawu nie powinien być kopiowany, ponieważ jest to zły obraz, prawdopodobnie nie zarządzany, prawdopodobnie nie zestaw w ogóle. |
Metadane elementu prywatnego
Ważną częścią określania CopyLocal
jest Private
metadane we wszystkich podstawowych odwołaniach. Każde odwołanie (podstawowe lub zależność) zawiera listę wszystkich odwołań podstawowych (elementów źródłowych), które przyczyniły się do dodania tego odwołania do zamknięcia.
- Jeśli żadna z elementów źródłowych nie określi
Private
metadanych,CopyLocal
zostanie ustawiona wartośćTrue
(lub nie zostanie ustawiona wartość domyślna )True
- Jeśli którykolwiek z elementów źródłowych określ
Private=true
wartość , jest ustawiona na wartośćCopyLocal
True
- Jeśli żaden z zestawów źródłowych nie określi
Private=true
i co najmniej jeden określiPrivate=false
parametr ,CopyLocal
zostanie ustawiona wartośćFalse
Które odwołanie ma wartość False?
Ostatni punkt jest często używanym powodem CopyLocal
ustawiania wartości 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".
Program MSBuild nie informuje nas, które odwołanie ma ustawioną Private
wartość false, ale ustrukturyzowana przeglądarka dzienników dodaje Private
metadane do elementów, które zostały określone powyżej:
Upraszcza to badanie i informuje o tym, które odwołanie spowodowało, że zależność, o której mowa, ma być ustawiona za pomocą polecenia CopyLocal=false
.
Global Assembly Cache
Global Assembly Cache (GAC) odgrywa ważną rolę w określaniu, czy skopiować odwołania do danych wyjściowych. Jest to niefortunne, ponieważ zawartość GAC jest specyficzna dla maszyny i powoduje to problemy z powtarzalnymi kompilacjami (gdzie zachowanie różni się od innej maszyny zależnej od stanu komputera, na przykład GAC).
Wprowadzono ostatnie poprawki, aby ResolveAssemblyReference
złagodzić sytuację. Możesz kontrolować zachowanie tych dwóch nowych danych wejściowych w usłudze ResolveAssemblyReference
:
CopyLocalDependenciesWhenParentReferenceInGac="$(CopyLocalDependenciesWhenParentReferenceInGac)"
DoNotCopyLocalIfInGac="$(DoNotCopyLocalIfInGac)"
AssemblySearchPaths
Istnieją dwa sposoby dostosowywania listy wyszukiwań ścieżek ResolveAssemblyReference
podczas próby zlokalizowania zestawu. Aby w pełni dostosować listę, właściwość AssemblySearchPaths
można ustawić przed upływem czasu. Porządek ma znaczenie; jeśli zestaw znajduje się w dwóch lokalizacjach, ResolveAssemblyReference
zatrzymuje się po znalezieniu go w pierwszej lokalizacji.
Domyślnie istnieją dziesięć wyszukiwań ResolveAssemblyReference
lokalizacji (cztery, jeśli używasz zestawu .NET SDK), a każda z nich może zostać wyłączona, ustawiając odpowiednią flagę na false:
- Wyszukiwanie plików z bieżącego projektu jest wyłączone, ustawiając
AssemblySearchPath_UseCandidateAssemblyFiles
właściwość na false. - Przeszukiwanie właściwości ścieżki odwołania (z
.user
pliku) jest wyłączone przez ustawienieAssemblySearchPath_UseReferencePath
właściwości na false. - Użycie ścieżki wskazówek z elementu jest wyłączone przez ustawienie
AssemblySearchPath_UseHintPathFromItem
właściwości na false. - Użycie katalogu z docelowym środowiskiem uruchomieniowym msBuild jest wyłączone przez ustawienie
AssemblySearchPath_UseTargetFrameworkDirectory
właściwości na false. - Wyszukiwanie folderów zestawów z pliku AssemblyFolders.config jest wyłączone przez ustawienie
AssemblySearchPath_UseAssemblyFoldersConfigFileSearchPath
właściwości na false. - Przeszukiwanie rejestru jest wyłączone, ustawiając
AssemblySearchPath_UseRegistry
właściwość na false. - Wyszukiwanie starszych zarejestrowanych folderów zestawów jest wyłączone, ustawiając
AssemblySearchPath_UseAssemblyFolders
właściwość na false. - Szukanie w GAC jest wyłączone, ustawiając
AssemblySearchPath_UseGAC
właściwość na false. - Traktowanie odwołania Include jako prawdziwej nazwy pliku jest wyłączone przez ustawienie
AssemblySearchPath_UseRawFileName
właściwości false. - Sprawdzanie, czy folder wyjściowy aplikacji jest wyłączony, ustawiając
AssemblySearchPath_UseOutDir
właściwość na false.
Wystąpił konflikt
Typowa sytuacja polega na tym, że program MSBuild wyświetla ostrzeżenie dotyczące różnych wersji tego samego zestawu używanego przez różne odwołania. Rozwiązanie często polega na dodaniu przekierowania powiązania do pliku app.config.
Przydatnym sposobem zbadania tych konfliktów jest wyszukiwanie w przeglądarce dzienników strukturalnych MSBuild pod kątem "Wystąpił konflikt". Zawiera on szczegółowe informacje o tym, które odwołania są potrzebne do tego, które wersje danego zestawu.