Compartir a través de


Solución de problemas de referencias de ensamblado

Una de las tareas más importantes de MSBuild y el proceso de compilación de .NET es resolver las referencias de ensamblado, lo que sucede en la ResolveAssemblyReference tarea. En este artículo se explican algunos de los detalles de cómo ResolveAssemblyReference funciona y cómo solucionar errores de compilación que pueden producirse cuando ResolveAssemblyReference no se puede resolver una referencia. Para investigar los errores de referencia de ensamblado, es posible que desee instalar el Visor de registros estructurados para ver los registros de MSBuild. Las capturas de pantalla de este artículo se toman del Visor de registros estructurados.

El propósito de ResolveAssemblyReference es tomar todas las referencias especificadas en .csproj archivos (o en otro lugar) a través del <Reference> elemento y asignarlas a rutas de acceso a los archivos de ensamblado en el sistema de archivos.

Los compiladores solo pueden aceptar una .dll ruta de acceso en el sistema de archivos como referencia, por lo que ResolveAssemblyReference convierte cadenas como mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 las que aparecen en archivos de proyecto en rutas de acceso como C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\mscorlib.dll, que luego se pasan al compilador a través del /r modificador.

Además ResolveAssemblyReference, determina el conjunto completo (realmente el cierre transitivo en términos de teoría de grafos) de todas las referencias .dll y .exe recursivamente, y para cada una de ellas determina si se debe copiar al directorio de salida de la compilación o no. No realiza la copia real (que se controla más adelante, después del paso de compilación real), pero prepara una lista de elementos de los archivos que se van a copiar.

ResolveAssemblyReference se invoca desde el ResolveAssemblyReferences destino:

Captura de pantalla del visor de registros que muestra cuándo se llama a ResolveAssemblyReferences en el proceso de compilación.

Si observa que ResolveAssemblyReferences sucede antes de Compile, y por supuesto, CopyFilesToOutputDirectory ocurre después de Compile.

Nota:

ResolveAssemblyReferencela tarea se invoca en el archivo .targets estándar Microsoft.Common.CurrentVersion.targets en las carpetas de instalación de MSBuild. También puede examinar los objetivos de MSBuild del SDK de .NET en línea en https://github.com/dotnet/msbuild/blob/a936b97e30679dcea4d99c362efa6f732c9d3587/src/Tasks/Microsoft.Common.CurrentVersion.targets#L1991-L2140. Este vínculo muestra exactamente dónde se invoca la ResolveAssemblyReference tarea en el .targets archivo.

Las entradas de ResolveAssemblyReference

ResolveAssemblyReference proporciona un registro completo de sus entradas:

Captura de pantalla que muestra los parámetros de entrada para la tarea ResolveAssemblyReference.

El Parameters nodo es estándar para todas las tareas, pero además ResolveAssemblyReference registra su propio conjunto de información en Entradas (que es básicamente el mismo que en Parameters, pero estructurado de forma diferente).

Las entradas más importantes son Assemblies y AssemblyFiles:

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

Assemblies usa el contenido del elemento de MSBuild Reference en el momento en que ResolveAssemblyReference se invoca para el proyecto. Todas las referencias de metadatos y ensamblaje, incluidas las referencias de NuGet, deben estar contenidas en este elemento. Cada referencia tiene un amplio conjunto de metadatos adjuntos:

Captura de pantalla que muestra los metadatos en una referencia de ensamblado.

AssemblyFiles procede del elemento de salida del destino de ResolveProjectReference denominado _ResolvedProjectReferencePaths. ResolveProjectReference se ejecuta antes que ResolveAssemblyReference y convierte los elementos de <ProjectReference> en rutas de acceso de ensamblados compilados en disco. Por lo tanto, AssemblyFiles contendrá los ensamblados creados por todos los proyectos a los que se hace referencia del proyecto actual:

Captura de pantalla que muestra AssemblyFiles.

Otra entrada útil es el parámetro booleano FindDependencies , que toma su valor de la _FindDependencies propiedad :

FindDependencies="$(_FindDependencies)"

Puede establecer esta propiedad en false en la compilación para desactivar el análisis de ensamblados de dependencia transitiva.

El algoritmo ResolveAssemblyReference

El algoritmo simplificado para la ResolveAssemblyReference tarea es el siguiente:

  1. Entradas de registro.
  2. Compruebe la variable de entorno MSBUILDLOGVERBOSERARSEARCHRESULTS. Establezca esta variable en cualquier valor para obtener registros más detallados.
  3. Inicialice la tabla de objetos de referencias.
  4. Lea el archivo de caché del obj directorio (si está presente).
  5. Calcule el cierre de las dependencias.
  6. Compile las tablas de salida.
  7. Escriba el archivo de caché en el obj directorio.
  8. Registre los resultados.

El algoritmo toma la lista de entrada de ensamblados (tanto de metadatos como de referencias de proyecto), recupera la lista de referencias para cada ensamblado que procesa (leyendo metadatos) y crea un conjunto completo (cierre transitivo) de todos los ensamblados a los que se hace referencia y los resuelve desde varias ubicaciones (incluidos gaC, AssemblyFoldersEx, etc.).

Los ensamblados a los que se hace referencia se agregan a la lista de forma iterativa hasta que no se agregan más referencias nuevas. A continuación, el algoritmo se detiene.

Las referencias directas que proporcionó a la tarea se denominan referencias principales. Los ensamblados indirectos que se agregaron al conjunto debido a una referencia transitiva se denominan Dependency. El registro de cada ensamblado indirecto realiza un seguimiento de todos los elementos principales ("raíz") que llevaron a su inclusión y sus metadatos correspondientes.

Resultados de la tarea ResolveAssemblyReference

ResolveAssemblyReference proporciona un registro detallado de los resultados:

Captura de pantalla que muestra los resultados de ResolveAssemblyReference en el visor de registros estructurados.

Los ensamblados resolvidos se dividen en dos categorías: referencias principales y dependencias. Las referencias principales se especificaron explícitamente como referencias del proyecto que se está compilando. Las dependencias se infirieron a partir de referencias de forma transitiva.

Importante

ResolveAssemblyReference lee los metadatos del ensamblado para determinar las referencias de un ensamblado determinado. Cuando el compilador de C# emite un ensamblado, solo agrega referencias a ensamblados que realmente son necesarios. Por lo tanto, puede ocurrir que, al compilar un proyecto determinado, el proyecto puede especificar una referencia innecesaria que no será incorporada en el ensamblaje. Es correcto agregar referencias al proyecto que no son necesarias; se omiten.

Metadatos del elemento CopyLocal

Las referencias también pueden tener los CopyLocal metadatos o no. Si la referencia tiene CopyLocal = true, más tarde el objetivo CopyFilesToOutputDirectory copiará al directorio de salida. En este ejemplo, DataFlow tiene CopyLocal establecido en verdadero, mientras Immutable no lo tiene.

Captura de pantalla que muestra la configuración de CopyLocal para algunas referencias.

CopyLocal Si falta el metadato por completo, se supone que es verdadero de forma predeterminada. Por lo tanto ResolveAssemblyReference , de forma predeterminada intenta copiar las dependencias en la salida a menos que encuentre una razón para no hacerlo. ResolveAssemblyReference registra las razones por las que eligió una referencia determinada para ser CopyLocal o no.

Todas las posibles razones de CopyLocal decisión se enumeran en la tabla siguiente. Resulta útil conocer estas cadenas para poder buscarlas en los registros de compilación.

Estado de CopyLocal Description
Undecided El estado local de copia no está decidido en este momento.
YesBecauseOfHeuristic La referencia debe tener CopyLocal='true' porque no fue 'no' por ninguna razón.
YesBecauseReferenceItemHadMetadata La referencia debe tener CopyLocal='true' porque su elemento de origen tiene Private='true'
NoBecauseFrameworkFile La referencia debe tener CopyLocal='false' porque es un archivo de marco.
NoBecausePrerequisite La referencia debe tener CopyLocal='false' porque es un archivo de requisitos previos.
NoBecauseReferenceItemHadMetadata La referencia debe tener CopyLocal='false' porque el Private atributo está establecido en "false" en el proyecto.
NoBecauseReferenceResolvedFromGAC La referencia debe tener CopyLocal='false' porque se resolvió desde la GAC.
NoBecauseReferenceFoundInGAC Comportamiento heredado, CopyLocal='false' cuando el ensamblado se encuentra en la GAC (incluso cuando se resolvió en otro lugar).
NoBecauseConflictVictim La referencia debe tener CopyLocal='false' porque perdió un conflicto debido a un archivo de ensamblaje con el mismo nombre.
NoBecauseUnresolved La referencia no se ha resuelto. No se puede copiar el archivo en el directorio bin porque no se encontró.
NoBecauseEmbedded La referencia se insertó. No se debe copiar en el directorio bin porque no se cargará en tiempo de ejecución.
NoBecauseParentReferencesFoundInGAC La propiedad copyLocalDependenciesWhenParentReferenceInGac se establece en false y todos los elementos de origen primarios se encontraron en la GAC.
NoBecauseBadImage El archivo de ensamblado proporcionado no debería copiarse porque es una imagen inválida, posiblemente no gestionada, o posiblemente no sea un ensamblado en absoluto.

Metadatos de elementos privados

Una parte importante de la determinación CopyLocal es los Private metadatos de todas las referencias principales. Cada referencia (principal o dependencia) tiene una lista de todas las referencias principales (elementos de origen) que han contribuido a que esa referencia se agregue a la clausura.

  • Si ninguno de los elementos de origen especifica Private metadatos, CopyLocal se establece en True (o no establecido, cuyo valor predeterminado es True)
  • Si alguno de los elementos de origen especifica Private=true, CopyLocal se establece en . True
  • Si ninguno de los ensamblados de origen especifica Private=true y al menos uno especifica Private=false, CopyLocal se establece en False.

¿Qué referencia configura Private en false?

El último punto es una razón que se suele usar para establecer CopyLocal en falso: 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 no nos indica qué referencia se ha establecido Private en false, pero el visor de registros estructurado agrega Private metadatos a los elementos que lo tenían especificado anteriormente:

Captura de pantalla que muestra el ajuste Private configurado en false en el visor de registros estructurados.

Esto simplifica las investigaciones e indica exactamente qué referencia provocó que la dependencia en cuestión se establezca con CopyLocal=false.

Caché global de ensamblados

El Global Assembly Cache (GAC) desempeña un papel importante en determinar si copiar las referencias al resultado. Esto es lamentable porque el contenido de la GAC es específico de la máquina y esto da lugar a problemas para compilaciones reproducibles (donde el comportamiento difiere en diferentes equipos dependientes del estado de la máquina, como la GAC).

Se han realizado correcciones recientes en ResolveAssemblyReference para aliviar la situación. Puede controlar el comportamiento mediante estas dos nuevas entradas para ResolveAssemblyReference:

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

AssemblySearchPaths

Hay dos maneras de personalizar la lista de rutas de acceso que ResolveAssemblyReference busca al tratar de localizar un ensamblaje. Para personalizar completamente la lista, la propiedad AssemblySearchPaths se puede establecer con antelación. El orden es importante; si un ensamblaje está en dos ubicaciones, ResolveAssemblyReference se detiene después de encontrarlo en la primera ubicación.

AssemblySearchPaths es una lista delimitada por punto y coma. Admite un conjunto de marcadores de posición integrados (por ejemplo, {HintPathFromItem} y {GAC}) que se expanden a ubicaciones reales durante la resolución.

De forma predeterminada, los proyectos que no son de estilo SDK usan el siguiente orden de ruta de búsqueda:

  1. Archivos candidatos de ensamblaje ({CandidateAssemblyFiles})
  2. La ReferencePath propiedad ($(ReferencePath))
  3. Rutas de pistas de elementos <Reference> ({HintPathFromItem})
  4. Directorio de la plataforma de destino ({TargetFrameworkDirectory})
  5. Carpetas de ensamblado de AssemblyFolders.config ($(AssemblyFoldersConfigFileSearchPath))
  6. Registro ({Registry:...})
  7. Carpetas de ensamblaje registradas heredadas ({AssemblyFolders})
  8. Caché global de ensamblados (GAC) ({GAC})
  9. Tratar el <Reference Include="..."> valor como un nombre de archivo real ({RawFileName})
  10. Directorio de salida ($(OutDir))

El SDK de .NET configura un valor predeterminado más pequeño AssemblySearchPaths (excluyendo las búsquedas en directorios de GAC, registro y salida de forma predeterminada):

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

Para ver el valor efectivo de tu compilación, inspecciona la entrada SearchPaths registrada por ResolveAssemblyReference (por ejemplo, en el Visor de Registros Estructurados de MSBuild) o preprocesa el proyecto con msbuild /pp.

Cada entrada se puede deshabilitar estableciendo la marca falsepertinente en :

  • La búsqueda de archivos del proyecto actual está deshabilitada estableciendo la AssemblySearchPath_UseCandidateAssemblyFiles propiedad en false.
  • La búsqueda de la propiedad de ruta de acceso de referencia (desde un .user archivo) está deshabilitada estableciendo la propiedad AssemblySearchPath_UseReferencePath en false.
  • El uso de la ruta de acceso de sugerencia del elemento está deshabilitado estableciendo la propiedad AssemblySearchPath_UseHintPathFromItem en false.
  • El uso del directorio con el entorno de ejecución de destino de MSBuild está deshabilitado estableciendo la AssemblySearchPath_UseTargetFrameworkDirectory propiedad en false.
  • La búsqueda de carpetas de ensamblados desde AssemblyFolders.config está deshabilitada estableciendo la AssemblySearchPath_UseAssemblyFoldersConfigFileSearchPath propiedad en false.
  • La búsqueda en el Registro está deshabilitada estableciendo la AssemblySearchPath_UseRegistry propiedad en false.
  • La búsqueda de carpetas de ensamblado registradas heredadas está deshabilitada estableciendo la AssemblySearchPath_UseAssemblyFolders propiedad en false.
  • El buscar en la GAC se deshabilita estableciendo la propiedad AssemblySearchPath_UseGAC en falso.
  • Al tratar el Include de la referencia como un nombre de archivo real, se desactiva estableciendo la propiedad AssemblySearchPath_UseRawFileName en false.
  • La comprobación de la carpeta de salida de la aplicación está deshabilitada estableciendo la AssemblySearchPath_UseOutDir propiedad en false.

Hubo un conflicto

Una situación común es que MSBuild proporciona una advertencia acerca de diferentes versiones del mismo ensamblado que están siendo utilizadas por diferentes referencias. La solución suele implicar agregar una redirección de enlace al archivo app.config.

Una manera útil de investigar estos conflictos es buscar en el Visor de registros estructurados de MSBuild para "Hubo un conflicto". Muestra información detallada sobre qué referencias necesitaban las versiones del ensamblado en cuestión.