Risolvere i problemi relativi ai riferimenti agli assembly
Una delle attività più importanti di MSBuild e del processo di compilazione .NET consiste nella risoluzione dei riferimenti agli assembly, che si verifica nell'attività ResolveAssemblyReference
. Questo articolo illustra alcuni dettagli sul funzionamento ResolveAssemblyReference
e come risolvere gli errori di compilazione che possono verificarsi quando ResolveAssemblyReference
non è in grado di risolvere un riferimento. Per analizzare gli errori di riferimento agli assembly, è possibile installare Structured Log Viewer per visualizzare i log di MSBuild. Gli screenshot di questo articolo sono tratti dal Visualizzatore log strutturato.
Lo scopo di ResolveAssemblyReference
consiste nell'accettare tutti i riferimenti specificati nei .csproj
file (o altrove) tramite l'elemento ed eseguirne il <Reference>
mapping ai percorsi dei file di assembly nel file system.
I compilatori possono accettare solo un .dll
percorso nel file system come riferimento, quindi ResolveAssemblyReference
converte stringhe come mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
presenti nei file di progetto in percorsi come C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\mscorlib.dll
, che vengono quindi passati al compilatore tramite l'opzione /r
.
Determina inoltre ResolveAssemblyReference
il set completo (in realtà la chiusura transitiva in termini di teoria del grafo) di tutti i .dll
riferimenti e .exe
in modo ricorsivo e per ognuno di essi determina se deve essere copiato nella directory di output di compilazione o meno. Non esegue la copia effettiva (gestita in un secondo momento, dopo il passaggio di compilazione effettivo), ma prepara un elenco di elementi di file da copiare.
ResolveAssemblyReference
viene richiamato dalla ResolveAssemblyReferences
destinazione:
Se si nota l'ordinamento, ResolveAssemblyReferences
avviene prima Compile
di e, naturalmente, CopyFilesToOutputDirectory
si verifica dopo Compile
.
Nota
ResolveAssemblyReference
l'attività viene richiamata nel file Microsoft.Common.CurrentVersion.targets
standard .targets
nelle cartelle di installazione di MSBuild. È anche possibile esplorare le destinazioni di MSBuild di .NET SDK online all'indirizzo https://github.com/dotnet/msbuild/blob/a936b97e30679dcea4d99c362efa6f732c9d3587/src/Tasks/Microsoft.Common.CurrentVersion.targets#L1991-L2140. Questo collegamento mostra esattamente dove viene richiamata l'attività ResolveAssemblyReference
nel .targets
file.
Input ResolveAssemblyReference
ResolveAssemblyReference
è completo sulla registrazione degli input:
Il Parameters
nodo è standard per tutte le attività, ma registra anche ResolveAssemblyReference
il proprio set di informazioni in Input (che è fondamentalmente uguale Parameters
a in , ma strutturato in modo diverso).
Gli input più importanti sono Assemblies
e AssemblyFiles
:
<ResolveAssemblyReference
Assemblies="@(Reference)"
AssemblyFiles="@(_ResolvedProjectReferencePaths);@(_ExplicitReference)"
Assemblies
usa il contenuto dell'elemento Reference
MSBuild al momento in cui ResolveAssemblyReference
viene richiamato per il progetto. Tutti i metadati e i riferimenti all'assembly, inclusi i riferimenti NuGet, devono essere contenuti in questo elemento. A ogni riferimento è associato un set completo di metadati:
AssemblyFiles
proviene dall'elemento ResolveProjectReference
di output di destinazione denominato _ResolvedProjectReferencePaths
. ResolveProjectReference
viene eseguito prima ResolveAssemblyReference
e converte gli elementi in percorsi di assembly compilati <ProjectReference>
su disco. AssemblyFiles
Conterrà quindi gli assembly compilati da tutti i progetti a cui si fa riferimento del progetto corrente:
Un altro input utile è il parametro booleano FindDependencies
, che accetta il valore dalla _FindDependencies
proprietà :
FindDependencies="$(_FindDependencies)"
È possibile impostare questa proprietà su false
nella compilazione per disattivare l'analisi degli assembly di dipendenza transitiva.
Algoritmo ResolveAssemblyReference
L'algoritmo semplificato per l'attività ResolveAssemblyReference
è il seguente:
- Input del log.
- Controllare la variabile di
MSBUILDLOGVERBOSERARSEARCHRESULTS
ambiente. Impostare questa variabile su qualsiasi valore per ottenere log più dettagliati. - Inizializzare la tabella dell'oggetto references.
- Leggere il file della cache dalla
obj
directory (se presente). - Calcolare la chiusura delle dipendenze.
- Compilare le tabelle di output.
- Scrivere il file della cache nella
obj
directory . - Registrare i risultati.
L'algoritmo accetta l'elenco di input degli assembly (sia dai metadati che dai riferimenti al progetto), recupera l'elenco di riferimenti per ogni assembly che elabora (leggendo i metadati) e compila un set completo (chiusura transitiva) di tutti gli assembly a cui si fa riferimento e li risolve da varie posizioni (inclusi gac, AssemblyFoldersEx e così via).
Gli assembly a cui si fa riferimento vengono aggiunti all'elenco in modo iterativo finché non vengono aggiunti altri nuovi riferimenti. Quindi l'algoritmo si arresta.
I riferimenti diretti forniti all'attività sono denominati riferimenti primari. Gli assembly indiretti aggiunti al set a causa di un riferimento transitivo sono denominati Dependency. Il record per ogni assembly indiretto tiene traccia di tutti gli elementi primari ("radice") che ne hanno portato all'inclusione e ai relativi metadati corrispondenti.
Risultati dell'attività ResolveAssemblyReference
ResolveAssemblyReference
fornisce la registrazione dettagliata dei risultati:
Gli assembly risolti sono suddivisi in due categorie: Riferimenti primari e Dipendenze. I riferimenti primari sono stati specificati in modo esplicito come riferimenti del progetto da compilare. Le dipendenze sono state dedotte dai riferimenti dei riferimenti in modo transitivo.
Importante
ResolveAssemblyReference
legge i metadati dell'assembly per determinare i riferimenti di un determinato assembly. Quando il compilatore C# genera un assembly, aggiunge solo riferimenti agli assembly effettivamente necessari. Pertanto, potrebbe verificarsi che quando si compila un determinato progetto, il progetto potrebbe specificare un riferimento non necessario che non verrà inserito nell'assembly. È ok aggiungere riferimenti al progetto che non sono necessari; vengono ignorati.
Metadati dell'elemento CopyLocal
I riferimenti possono anche avere o meno i CopyLocal
metadati. Se il riferimento ha CopyLocal = true
, verrà copiato in un secondo momento nella directory di output in base alla CopyFilesToOutputDirectory
destinazione. In questo esempio, DataFlow
è CopyLocal
impostato su true, mentre Immutable
non:
Se i CopyLocal
metadati mancano completamente, si presuppone che sia true per impostazione predefinita. Quindi ResolveAssemblyReference
, per impostazione predefinita, tenta di copiare le dipendenze nell'output, a meno che non trovi un motivo per cui non. ResolveAssemblyReference
registra i motivi per cui ha scelto o meno un riferimento CopyLocal
specifico.
Nella tabella seguente vengono enumerati tutti i possibili motivi per CopyLocal
la decisione. È utile conoscere queste stringhe per poterle cercare nei log di compilazione.
CopyLocal state | Descrizione |
---|---|
Undecided |
Lo stato locale di copia è indecidato al momento. |
YesBecauseOfHeuristic |
Il riferimento dovrebbe essere CopyLocal='true' dovuto perché non era "no" per nessun motivo. |
YesBecauseReferenceItemHadMetadata |
Il riferimento deve avere CopyLocal='true' perché l'elemento di origine ha Private='true' |
NoBecauseFrameworkFile |
Il riferimento deve avere CopyLocal='false' perché si tratta di un file framework. |
NoBecausePrerequisite |
Il riferimento deve avere CopyLocal='false' perché è un file prerequisito. |
NoBecauseReferenceItemHadMetadata |
Il riferimento deve avere CopyLocal='false' perché l'attributo Private è impostato su 'false' nel progetto. |
NoBecauseReferenceResolvedFromGAC |
Il riferimento deve avere CopyLocal='false' perché è stato risolto dalla GAC. |
NoBecauseReferenceFoundInGAC |
Comportamento legacy, CopyLocal='false' quando l'assembly viene trovato nella GAC (anche quando è stato risolto altrove). |
NoBecauseConflictVictim |
Il riferimento deve avere CopyLocal='false' perché ha perso un conflitto tra un file di assembly con lo stesso nome. |
NoBecauseUnresolved |
Il riferimento non è stato risolto. Non è possibile copiarlo nella directory bin perché non è stato trovato. |
NoBecauseEmbedded |
Il riferimento è stato incorporato. Non deve essere copiato nella directory bin perché non verrà caricato in fase di esecuzione. |
NoBecauseParentReferencesFoundInGAC |
La proprietà copyLocalDependenciesWhenParentReferenceInGac è impostata su false e tutti gli elementi di origine padre sono stati trovati nella GAC. |
NoBecauseBadImage |
Il file di assembly fornito non deve essere copiato perché è un'immagine non valida, probabilmente non gestita, probabilmente non è un assembly. |
Metadati dell'elemento privato
Una parte importante della determinazione CopyLocal
è costituita dai Private
metadati di tutti i riferimenti primari. Ogni riferimento (primario o dipendenza) ha un elenco di tutti i riferimenti primari (elementi di origine) che hanno contribuito a tale riferimento da aggiungere alla chiusura.
- Se nessuno degli elementi di origine specifica
Private
i metadati,CopyLocal
viene impostatoTrue
su (o non impostato, che per impostazione predefinita èTrue
) - Se uno degli elementi di origine specifica
Private=true
,CopyLocal
è impostato suTrue
- Se nessuno degli assembly di origine specifica
Private=true
e almeno uno specificaPrivate=false
,CopyLocal
è impostato suFalse
Quale riferimento imposta Private su false?
L'ultimo punto è un motivo spesso usato per CopyLocal
essere impostato su 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 non indica quale riferimento è impostato su Private
false, ma il visualizzatore di log strutturato aggiunge Private
metadati agli elementi specificati in precedenza:
Ciò semplifica le indagini e indica esattamente quale riferimento ha causato l'impostazione della dipendenza in questione con CopyLocal=false
.
Global Assembly Cache
La Global Assembly Cache (GAC) svolge un ruolo importante per determinare se copiare i riferimenti all'output. Ciò è sfortunato perché il contenuto della GAC è specifico del computer e questo comporta problemi per le compilazioni riproducibili (dove il comportamento è diverso in base allo stato del computer, ad esempio la GAC).
Sono state apportate recenti correzioni per ResolveAssemblyReference
alleviare la situazione. È possibile controllare il comportamento da questi due nuovi input in ResolveAssemblyReference
:
CopyLocalDependenciesWhenParentReferenceInGac="$(CopyLocalDependenciesWhenParentReferenceInGac)"
DoNotCopyLocalIfInGac="$(DoNotCopyLocalIfInGac)"
AssemblySearchPaths
Esistono due modi per personalizzare l'elenco di ricerche nei percorsi ResolveAssemblyReference
nel tentativo di individuare un assembly. Per personalizzare completamente l'elenco, è possibile impostare la proprietà AssemblySearchPaths
in anticipo. L'ordine è importante; se un assembly si trova in due posizioni, ResolveAssemblyReference
si arresta dopo averlo trovato nella prima posizione.
Per impostazione predefinita, sono disponibili dieci ricerche in posizioni ResolveAssemblyReference
(quattro se si usa .NET SDK) e ognuna può essere disabilitata impostando il flag pertinente su false:
- La ricerca di file dal progetto corrente è disabilitata impostando la
AssemblySearchPath_UseCandidateAssemblyFiles
proprietà su false. - La ricerca della proprietà del percorso di riferimento (da un
.user
file) è disabilitata impostando laAssemblySearchPath_UseReferencePath
proprietà su false. - L'uso del percorso hint dell'elemento è disabilitato impostando la
AssemblySearchPath_UseHintPathFromItem
proprietà su false. - L'uso della directory con il runtime di destinazione di MSBuild è disabilitato impostando la
AssemblySearchPath_UseTargetFrameworkDirectory
proprietà su false. - La ricerca di cartelle di assembly da AssemblyFolders.config è disabilitata impostando la
AssemblySearchPath_UseAssemblyFoldersConfigFileSearchPath
proprietà su false. - La ricerca nel Registro di sistema è disabilitata impostando la
AssemblySearchPath_UseRegistry
proprietà su false. - La ricerca di cartelle di assembly registrate legacy è disabilitata impostando la
AssemblySearchPath_UseAssemblyFolders
proprietà su false. - La ricerca nella GAC è disabilitata impostando la
AssemblySearchPath_UseGAC
proprietà su false. - Il trattamento dell'elemento Include del riferimento come nome file reale è disabilitato impostando la
AssemblySearchPath_UseRawFileName
proprietà su false. - Il controllo della cartella di output dell'applicazione è disabilitato impostando la
AssemblySearchPath_UseOutDir
proprietà su false.
Si è verificato un conflitto
Una situazione comune è che MSBuild genera un avviso relativo a versioni diverse dello stesso assembly usato da riferimenti diversi. La soluzione comporta spesso l'aggiunta di un reindirizzamento di binding al file app.config.
Un modo utile per analizzare questi conflitti consiste nel cercare in MSBuild Structured Log Viewer "Si è verificato un conflitto". Vengono visualizzate informazioni dettagliate sui riferimenti necessari alle versioni dell'assembly in questione.