Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Uma das tarefas mais importantes no MSBuild e no processo de compilação do .NET é resolver referências de assembly, o que acontece na ResolveAssemblyReference tarefa. Este artigo explica alguns detalhes de como ResolveAssemblyReference funciona e como resolver falhas de compilação que podem ocorrer quando ResolveAssemblyReference não é possível resolver uma referência. Para investigar falhas de referência de assembly, poderá querer instalar o Visualizador de Logs Estruturados para ver os registos do MSBuild. As capturas de ecrã deste artigo são retiradas do Visualizador de Registos Estruturados.
O objetivo de ResolveAssemblyReference é capturar todas as referências especificadas em ficheiros .csproj (ou noutro local) através do item <Reference> e mapeá-las para caminhos de ficheiros de assemblagem no sistema de ficheiros.
Os compiladores só podem aceitar um .dll caminho do sistema de ficheiros como referência, por isso ResolveAssemblyReference converte cadeias, como mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, que aparecem nos ficheiros do projeto em caminhos como C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\mscorlib.dll, que depois são enviados para o compilador através do /r switch.
Além disso, ResolveAssemblyReference determina o conjunto completo (na verdade, o fecho transitivo em termos de teoria dos grafos) de todas as .dll e .exe referências recursivamente, e para cada uma delas determina se deve ser copiada para o diretório de saída da compilação ou não. Não faz a cópia propriamente dita (que é tratada mais tarde, após a fase de compilação), mas prepara uma lista de ficheiros para copiar.
ResolveAssemblyReference é invocado a partir do alvo ResolveAssemblyReferences.
Se reparares na ordenação, ResolveAssemblyReferences acontece antes de Compile, e claro, CopyFilesToOutputDirectory acontece depois de Compile.
Observação
ResolveAssemblyReferencea tarefa é invocada no ficheiro padrão .targets nas pastas de instalação do MSBuildMicrosoft.Common.CurrentVersion.targets. Também pode navegar online pelos alvos do MSBuild do SDK .NET em https://github.com/dotnet/msbuild/blob/a936b97e30679dcea4d99c362efa6f732c9d3587/src/Tasks/Microsoft.Common.CurrentVersion.targets#L1991-L2140. Este link mostra exatamente onde a ResolveAssemblyReference tarefa é invocada no .targets ficheiro.
Entradas ResolveAssemblyReference
ResolveAssemblyReference é abrangente quanto ao registo das suas entradas:
O Parameters nó é padrão para todas as tarefas, mas adicionalmente ResolveAssemblyReference regista o seu próprio conjunto de informação em Inputs (que é basicamente igual ao em Parameters, mas estruturado de forma diferente).
Os inputs mais importantes são Assemblies e AssemblyFiles:
<ResolveAssemblyReference
Assemblies="@(Reference)"
AssemblyFiles="@(_ResolvedProjectReferencePaths);@(_ExplicitReference)"
Assemblies utiliza o conteúdo do Reference item MSBuild no momento em que ResolveAssemblyReference é invocado para o projeto. Todos os metadados e referências de assembly, incluindo as suas referências NuGet, devem estar contidos neste item. Cada referência tem um conjunto rico de metadados associados:
AssemblyFiles provém do ResolveProjectReference item de saída do alvo chamado _ResolvedProjectReferencePaths.
ResolveProjectReference executa antes ResolveAssemblyReference e converte <ProjectReference> itens em caminhos de assemblies construídos no disco. Assim, o AssemblyFiles irá conter os conjuntos construídos por todos os projetos referenciados do projeto atual:
Outra entrada útil é o parâmetro booleano FindDependencies , que retira o seu valor da _FindDependencies propriedade:
FindDependencies="$(_FindDependencies)"
Pode definir esta propriedade na false sua build para desligar a análise de assemblies de dependências transitivas.
Algoritmo "ResolveAssemblyReference"
O algoritmo simplificado para a ResolveAssemblyReference tarefa é o seguinte:
- Registo de entradas.
- Verifica a
MSBUILDLOGVERBOSERARSEARCHRESULTSvariável de ambiente. Defina esta variável para qualquer valor para obter registos mais detalhados. - Inicialize o objeto da tabela de referências.
- Leia o ficheiro de cache a partir do
objdiretório (se estiver presente). - Calcule o encerramento das dependências.
- Constrói as tabelas de saída.
- Escreve o ficheiro de cache no
objdiretório. - Regista os resultados.
O algoritmo recolhe a lista de entrada de assemblies (tanto de metadados como de referências de projeto), recupera a lista de referências de cada assembly que processa (lendo metadados) e constrói um conjunto completo (fecho transitivo) de todos os assemblies referenciados, resolvendo-os a partir de vários locais (incluindo o GAC, AssemblyFoldersEx, entre outros).
As assembleias referenciadas são adicionadas iterativamente à lista até que não sejam adicionadas mais novas referências. Depois o algoritmo pára.
As referências diretas que forneceu para a tarefa são chamadas de referências primárias. Montagens indiretas que foram adicionadas ao conjunto devido a uma referência transitiva são chamadas de Dependências. O registo de cada assemblagem indireta mantém o registo de todos os itens primários ("raiz") que levaram à sua inclusão e dos respetivos metadados.
Resultados da tarefa ResolveAssemblyReference
ResolveAssemblyReference fornece registo detalhado dos resultados:
Os conjuntos de assemblies resolvidos dividem-se em duas categorias: referências primárias e dependências. As referências primárias eram especificadas explicitamente como referências ao projeto em construção. As dependências foram inferidas a partir de referências de referências de forma transitiva.
Importante
ResolveAssemblyReference lê os metadados da assembly para determinar as referências de uma determinada assembly. Quando o compilador C# emite uma assembleia, só adiciona referências a assemblies que são realmente necessárias. Assim, pode acontecer que, ao compilar um determinado projeto, este especifique uma referência desnecessária que não será incorporada na assembly. É aceitável adicionar referências ao projeto que não são necessárias; São ignorados.
Metadados do CopyLocal item
As referências também podem ter os CopyLocal metadados ou não. Se a referência tiver CopyLocal = true, ela será depois copiada para o diretório de saída pelo alvo CopyFilesToOutputDirectory. Neste exemplo, DataFlow definiu CopyLocal como verdadeiro, enquanto Immutable não:
Se os CopyLocal metadados estiverem completamente ausentes, assume-se que são verdadeiros por defeito. Por defeito, ResolveAssemblyReference tenta copiar dependências para a saída, a menos que encontre uma razão para não o fazer.
ResolveAssemblyReference regista as razões pelas quais escolheu ou não uma determinada referência CopyLocal .
Todas as possíveis razões para CopyLocal a decisão estão enumeradas na tabela seguinte. É útil conhecer estas strings para poder procurá-las nos logs de compilação.
| Copiar estado local | Description |
|---|---|
Undecided |
O estado da cópia local está atualmente por decidir. |
YesBecauseOfHeuristic |
A referência devia ter CopyLocal='true' porque não havia razão para ser 'não'. |
YesBecauseReferenceItemHadMetadata |
A Referência deve ter CopyLocal='true' porque o seu item de origem tem Privado='true' |
NoBecauseFrameworkFile |
A referência deve ter CopyLocal='false' porque é um ficheiro de framework. |
NoBecausePrerequisite |
A referência deve ter CopyLocal='false' porque é um ficheiro pré-requisito. |
NoBecauseReferenceItemHadMetadata |
A referência deve ter CopyLocal='false' porque o atributo Private está definido como 'falso' no projeto. |
NoBecauseReferenceResolvedFromGAC |
A referência deve ter CopyLocal='false' porque foi resolvida pelo GAC. |
NoBecauseReferenceFoundInGAC |
Comportamento legado, CopyLocal='false' quando o componente é encontrado no GAC (mesmo quando foi resolvido noutro local). |
NoBecauseConflictVictim |
A referência deve conter CopyLocal='false' porque perdeu um conflito com um ficheiro de assembly do mesmo nome. |
NoBecauseUnresolved |
A referência não foi resolvida. Não pode ser copiado para o diretório bin porque não foi encontrado. |
NoBecauseEmbedded |
A referência foi incorporada. Não deve ser copiado para a pasta bin porque não será carregado em tempo de execução. |
NoBecauseParentReferencesFoundInGAC |
A propriedade copyLocalDependenciesWhenParentReferenceInGac está definida como falsa e todos os itens de origem originais foram encontrados no GAC. |
NoBecauseBadImage |
O ficheiro de montagem fornecido não deve ser copiado porque é uma imagem má, possivelmente não gerida, possivelmente nem sequer uma assembleia. |
Metadados privados de itens
Uma parte importante da determinação CopyLocal são os Private metadados de todas as referências primárias. Cada referência (primária ou dependente) tem uma lista de todas as referências primárias (itens de origem) que contribuíram para que essa referência fosse adicionada ao fecho.
- Se nenhum dos itens de origem especificar
Privatemetadados,CopyLocalé definido comoTrue(ou não definido, que por defeito éTrue) - Se algum dos itens fonte especificar
Private=true,CopyLocalé definido comoTrue - Se nenhuma das assemblagens de origem especificar
Private=truee pelo menos uma especificarPrivate=false,CopyLocalé definido comoFalse
Qual referência configurou Privado para falso?
O último ponto é uma razão frequentemente usada para CopyLocal estar definido como 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".
O MSBuild não nos diz qual referência está definida Private como falsa, mas o visualizador de registos estruturados adiciona Private metadados aos itens que tinham especificado acima:
Isto simplifica as investigações e indica-lhe exatamente qual referência causou a definição da dependência em questão com CopyLocal=false.
Cache de Assembleia Global
O Global Assembly Cache (GAC) desempenha um papel importante ao determinar se deve copiar referências para o output. Isto é lamentável porque o conteúdo do GAC é específico de cada máquina, o que resulta em problemas para compilações reproduzíveis (onde o comportamento difere consoante a máquina dependendo do estado da máquina, como o GAC).
Foram feitas correções recentes no ResolveAssemblyReference para aliviar a situação. Pode controlar o comportamento com estas duas novas entradas para ResolveAssemblyReference:
CopyLocalDependenciesWhenParentReferenceInGac="$(CopyLocalDependenciesWhenParentReferenceInGac)"
DoNotCopyLocalIfInGac="$(DoNotCopyLocalIfInGac)"
AssemblySearchPaths
Existem duas formas de personalizar a lista de caminhos que as pesquisas ResolveAssemblyReference utilizam ao tentar localizar um assembly. Para personalizar totalmente a lista, a propriedade AssemblySearchPaths pode ser definida antecipadamente. A ordem importa; se um conjunto estiver em dois locais, ResolveAssemblyReference interrompe a execução após encontrá-lo no primeiro local.
AssemblySearchPaths é uma lista delimitada por ponto e vírgula. Suporta um conjunto de marcadores incorporados (por exemplo, {HintPathFromItem} e {GAC}) que se expandem para locais reais durante a resolução.
Por defeito, projetos que não são ao estilo SDK usam a seguinte ordem de caminho de pesquisa:
- Ficheiros de montagem de candidatos (
{CandidateAssemblyFiles}) - Propriedade
ReferencePath($(ReferencePath)) - Percursos de referência a partir de
<Reference>itens ({HintPathFromItem}) - O diretório do framework de destino (
{TargetFrameworkDirectory}) - Pastas de montagem a partir de
AssemblyFolders.config($(AssemblyFoldersConfigFileSearchPath)) - O registo (
{Registry:...}) - Assemblies registadas herdadas (
{AssemblyFolders}) - A Cache de Montagem Global (GAC) (
{GAC}) - Trate o
<Reference Include="...">valor como um nome real de ficheiro ({RawFileName}) - O diretório de saída (
$(OutDir))
O SDK .NET configura um padrão AssemblySearchPaths menor (excluindo, por defeito, as pesquisas no GAC/registo/diretório de saída):
{CandidateAssemblyFiles}{HintPathFromItem}{TargetFrameworkDirectory}{RawFileName}
Para ver o valor efetivo da sua build, inspecione a SearchPaths entrada registada por ResolveAssemblyReference (por exemplo, no MSBuild Structured Log Viewer) ou processe previamente o projeto com msbuild /pp.
Cada entrada pode ser desativada definindo a flag relevante para false:
- A pesquisa de ficheiros do projeto atual é desativada ao definir a
AssemblySearchPath_UseCandidateAssemblyFilespropriedade como false. - A pesquisa na propriedade de caminho de referência (a partir de um
.userficheiro) é desativada ao definir aAssemblySearchPath_UseReferencePathpropriedade como false. - O uso do caminho de dicas do item é desativado ao definir a
AssemblySearchPath_UseHintPathFromItempropriedade como false. - Para desativar o uso do diretório com o runtime alvo do MSBuild, defina a propriedade
AssemblySearchPath_UseTargetFrameworkDirectorycomo false. - Pesquisar diretórios de assembly a partir de AssemblyFolders.config é desativado ao definir a propriedade
AssemblySearchPath_UseAssemblyFoldersConfigFileSearchPathcomo False. - Pesquisar no registo é desativado ao definir a
AssemblySearchPath_UseRegistrypropriedade para falso. - A pesquisa em pastas de assembly registadas antigas é desativada quando se define a propriedade
AssemblySearchPath_UseAssemblyFolderspara false. - Procurar no GAC é desativado quando a propriedade
AssemblySearchPath_UseGACé definida como false. - Tratar o Include da referência como um nome real de ficheiro é desativado ao definir a
AssemblySearchPath_UseRawFileNamepropriedade como false. - A verificação da pasta de saída da aplicação é desativada ao definir a propriedade
AssemblySearchPath_UseOutDirpara false.
Houve um conflito
Uma situação comum é que o MSBuild emite um aviso sobre diferentes versões do mesmo conjunto serem usadas por diferentes referências. A solução envolve frequentemente adicionar um redirecionamento de ligação ao ficheiro app.config.
Uma forma útil de investigar estes conflitos é pesquisar no MSBuild Structured Log Viewer por "Houve um conflito". Mostra-lhe informações detalhadas sobre quais referências necessitam de quais versões do assembly em questão.