O MSBuild não copia referências (arquivos DLL) se estiver usando dependências do projeto na solução

273

Tenho quatro projetos na minha solução do Visual Studio (todos direcionados ao .NET 3.5) - para o meu problema, apenas esses dois são importantes:

  1. MyBaseProject <- esta biblioteca de classes faz referência a um arquivo DLL de terceiros (elmah.dll)
  2. MyWebProject1 <- este projeto de aplicativo da web tem uma referência ao MyBaseProject

Adicionei a referência elmah.dll ao MyBaseProject no Visual studio 2008 clicando em "Adicionar referência ..." → guia "Procurar" → selecionando a opção "elmah.dll".

As propriedades da referência Elmah são as seguintes:

  • Aliases - global
  • Copiar local - verdadeiro
  • Cultura -
  • Descrição - Módulos e manipuladores de log de erros (ELMAH) para ASP.NET
  • Tipo de arquivo - montagem
  • Caminho - D: \ webs \ otherfolder \ _myPath \ __ tools \ elmah \ Elmah.dll
  • Resolvido - Verdadeiro
  • Versão em tempo de execução - v2.0.50727
  • Versão especificada - false
  • Nome forte - falso
  • Versão - 1.0.11211.0

No MyWebProject1 , adicionei a referência ao Projeto MyBaseProject por: "Adicionar referência ..." → guia "Projetos" → selecionando o "MyBaseProject". As propriedades desta referência são as mesmas, exceto os seguintes membros:

  • Descrição -
  • Caminho - D: \ webs \ CMS \ MyBaseProject \ bin \ Debug \ MyBaseProject.dll
  • Versão - 1.0.0.0

Se eu executar a compilação no Visual Studio, o arquivo elmah.dll será copiado para o diretório bin do MyWebProject1 , junto com o MyBaseProject.dll!

No entanto, se eu limpar e executar o MSBuild para a solução (via D: \ webs \ CMS> C: \ WINDOWS \ Microsoft.NET \ Framework \ v3.5 \ MSBuild.exe / t: ReBuild / p: Configuration = Debug MyProject.sln ) o elmah.dll está ausente no diretório bin do MyWebProject1 - embora a compilação em si não contenha nenhum aviso ou erro!

Eu já verifiquei que o .csproj do MyBaseProject contém o elemento privado com o valor "true" (que deve ser um alias para " copy local " no Visual Studio):

<Reference Include="Elmah, Version=1.0.11211.0, Culture=neutral, processorArchitecture=MSIL">
  <SpecificVersion>False</SpecificVersion>
  <HintPath>..\mypath\__tools\elmah\Elmah.dll</HintPath>
    **<Private>true</Private>**
</Reference>

(A marca particular não apareceu no xml do .csproj por padrão, embora o Visual Studio tenha dito "copiar local" como true. Eu mudei "copy local" para false - salvo - e configurei novamente como true - save!)

O que há de errado com o MSBuild? Como obtenho a referência (elmah.dll) copiada para a lixeira do MyWebProject1?

NÃO quero adicionar uma ação de cópia do postbuild ao comando postbuild de cada projeto! (Imagine que muitos projetos dependem do MyBaseProject!)

toebens
fonte
11
Gostaria muito de ter uma resposta mais clara sobre o porquê disso acontecer.
22412 David Faivre
1
Dê uma olhada na resposta dada aqui
Anuroopa Shenoy
1
alguma solução final com amostra de código fonte completa trabalhando nisso?
Kiquenet
2
Veja a resposta stackoverflow.com/a/21055664/21579 abaixo por @deadlydog. Excelente explicação e resolvi o problema para mim ... a resposta mais votada abaixo não está correta para o VS2012.
Jeff Widmer

Respostas:

153

Não sei por que é diferente ao criar entre o Visual Studio e o MsBuild, mas aqui está o que encontrei quando encontrei esse problema no MsBuild e no Visual Studio.

Explicação

Para um cenário de exemplo, digamos que temos o projeto X, o conjunto A e o conjunto B. O conjunto A faz referência ao conjunto B, portanto, o projeto X inclui uma referência a A e B. Além disso, o projeto X inclui o código que faz referência ao conjunto A (por exemplo, A. AlgumaFunção ()). Agora, você cria um novo projeto Y que faz referência ao projeto X.

Portanto, a cadeia de dependência fica assim: Y => X => A => B

O Visual Studio / MSBuild tenta ser inteligente e somente traz referências ao projeto Y que ele detecta como exigido pelo projeto X; isso é feito para evitar a poluição de referência no projeto Y. O problema é que, como o projeto X não contém realmente nenhum código que use explicitamente o assembly B (por exemplo, B.SomeFunction ()), o VS / MSBuild não detecta que B é necessário por X e, portanto, não o copia no diretório bin do projeto Y; copia apenas os conjuntos X e A.

Solução

Você tem duas opções para resolver esse problema, as quais resultarão na cópia do conjunto B para o diretório bin do projeto Y:

  1. Adicione uma referência ao conjunto B no projeto Y.
  2. Adicione código fictício a um arquivo no projeto X que usa o assembly B.

Pessoalmente, prefiro a opção 2 por alguns motivos.

  1. Se você adicionar outro projeto no futuro que faça referência ao projeto X, não precisará se lembrar de incluir também uma referência ao assembly B (como você faria com a opção 1).
  2. Você pode ter comentários explícitos dizendo por que o código fictício precisa estar lá e não para removê-lo. Portanto, se alguém excluir o código por acidente (digamos, com uma ferramenta de refatoração que procure por código não utilizado), você poderá ver facilmente no controle de origem que o código é necessário e restaurá-lo. Se você usar a opção 1 e alguém usar uma ferramenta de refatoração para limpar referências não utilizadas, você não terá nenhum comentário; você verá que uma referência foi removida do arquivo .csproj.

Aqui está um exemplo do "código fictício" que eu normalmente adiciono quando encontro essa situação.

    // DO NOT DELETE THIS CODE UNLESS WE NO LONGER REQUIRE ASSEMBLY A!!!
    private void DummyFunctionToMakeSureReferencesGetCopiedProperly_DO_NOT_DELETE_THIS_CODE()
    {
        // Assembly A is used by this file, and that assembly depends on assembly B,
        // but this project does not have any code that explicitly references assembly B. Therefore, when another project references
        // this project, this project's assembly and the assembly A get copied to the project's bin directory, but not
        // assembly B. So in order to get the required assembly B copied over, we add some dummy code here (that never
        // gets called) that references assembly B; this will flag VS/MSBuild to copy the required assembly B over as well.
        var dummyType = typeof(B.SomeClass);
        Console.WriteLine(dummyType.FullName);
    }
cão mortal
fonte
4
O que não está sendo discutido aqui é que existe uma diferença entre a cópia de produtos de construção em / bin de um projeto da Web e a cópia de rotina no diretório de saída de destino (por exemplo, / bin / x86 / Debug). O primeiro é feito pelo projeto referenciado quando ele é construído e o último é feito pelo projeto da Web dependente. Examinar Microsoft.Common.targets ajuda a entender isso. As cópias na web / bin não dependem do comportamento local da cópia - copie os impactos locais na cópia para o diretório de destino de saída que não faz parte da estrutura referenciada pela Cassini em execução via Debug.
user1164178
6
você pode explicar por que funcionou com o VS SEM adicionar esse "código fictício", mas não com o msbuild?
toebens
3
Ainda menos invasivo do que invocar uma função, você pode atribuir o tipo de uma classe contida dentro do assembly a uma variável dummy. Type dummyType = typeof(AssemblyA.AnyClass);
Arithmomaniac
14
A solução 2, como mostrada acima, funcionará, a menos que você tenha a configuração 'Otimizar código' marcada no Visual Studio. Nesse caso, ele ainda excluirá a dll. Eu adicionei mais uma linha para substituir sua "otimização". Console.WriteLine(dummyType.FullName);
JasonG
3
A solução 2 não funcionou para mim no Visual Studio 2017. Primeiro, pensei que era porque, no meu assembly X, eu estava usando apenas um enumde B e presumi que o enum estava sendo embutido. Adicionei código para usar diretamente um tipo de B e isso não ajudou. Também sempre foi o caso do meu X usar tipos em A que subclasse tipos em B, então não consigo entender como o compilador pensa que B não é exigido por X e pode ser ignorado. Isso é maluco.
Xharlie #
170

Eu apenas lido com isso assim. Vá para as propriedades da sua referência e faça o seguinte:

Set "Copy local = false"
Save
Set "Copy local = true"
Save

e é isso.

O Visual Studio 2010 não coloca inicialmente: <private>True</private> na marca de referência e a configuração "copy local" como false faz com que ela crie a marca. Depois disso, ele será definido como verdadeiro e falso de acordo.

andrew
fonte
10
Isso foi uma dádiva de Deus. Obrigado por isso!
Rebecca
22
Não funcionou para mim com o MSBuild 4 / VS2012. Ou seja, eu pude atualizar as referências para dizer, <Private>true</Private>mas parecia não ter efeito no MSBuild. No final, acabei de adicionar referências ao NuGet a projetos de nível inferior.
Michael Teper
2
Não funcionou para mim. Ele ainda não copia System.Net.Http.Formatting para a pasta bin.
Akira Yamamoto
4
Isso é equivalente a Have you tried turning it off and on again?, e funcionou!
guanome
6
Parece que o VS2015 ainda se comporta da mesma maneira: configurar 'Copiar local' para 'Falso' e voltar para 'Verdadeiro' nas DLLs referenciadas funciona.
flip
38

Se você não estiver usando o assembly diretamente no código, o Visual Studio, enquanto tenta ser útil, detecta que ele não é usado e não o inclui na saída. Não sei por que você está vendo um comportamento diferente entre o Visual Studio e o MSBuild. Você pode tentar definir a saída de construção como diagnóstico para ambos e comparar os resultados, ver onde eles divergem.

Quanto à sua referência elmah.dll, se você não a estiver referenciando diretamente no código, poderá adicioná-lo como um item ao seu projeto e definir a Ação de Construção como Contente o Diretório Copiar para Saída como Always.

John Hunter
fonte
3
+1 para sua cópia no comentário do Diretório de saída, se Elmah não estiver sendo usado no código, faz sentido copiar como conteúdo.
Kit Roed
4
Na verdade, ele ignora os assemblies que não são usados, mas uma coisa importante a ser observada: no VS 2010, o uso de assemblies nos dicionários de recursos XAML não é considerado o uso de montagem pelo VS, portanto não será copiado.
Alex Burtsev 8/10
Isso é melhor para um projeto de teste de unidade em que eu preciso da dll. Não quero adicionar uma dll que o projeto principal não precise apenas para executar os testes!
Lukos 7/07
14

Dê uma olhada em:

Este tópico do fórum do MSBuild que eu comecei

Você encontrará minha solução temporária / solução alternativa lá!

(O MyBaseProject precisa de algum código que faça referência a algumas classes (o que seja) do elmah.dll para que o elmah.dll seja copiado na lixeira do MyWebProject1!)

toebens
fonte
1
Droga - esta é a única solução para a qual eu tinha chegado - esperava que houvesse uma maneira melhor de fazer isso!
Nickspoon
2
Veja reponse de Andrew abaixo para uma solução menos hacky (sem ofensa!)
MPritchard
1
Para quem está vendo isso agora, a resposta de toebens no MSDN é essencialmente a mesma que a resposta de deadlydog , fornecida alguns anos depois.
jpaugh
8

Eu tive o mesmo problema.

Verifique se a versão da estrutura do seu projeto é a mesma da versão da DLL que você colocou em referência.

No meu caso, meu cliente foi compilado usando o "Framework 4 Client" e a DLL estava no "Framework 4".

Andre Avelar
fonte
6

O problema que eu estava enfrentando era que eu tinha um projeto que depende de um projeto de biblioteca. Para construir, eu estava seguindo estas etapas:

msbuild.exe myproject.vbproj /T:Rebuild
msbuild.exe myproject.vbproj /T:Package

Isso, é claro, significava que estava faltando os arquivos DLL da minha biblioteca na lixeira e o mais importante no arquivo zip do pacote. Eu achei que isso funciona perfeitamente:

msbuild.exe myproject.vbproj /T:Rebuild;Package

Não tenho ideia do porquê desse trabalho ou por que não funcionou. Mas espero que ajude.

vangorra
fonte
Eu tive o mesmo problema ao criar uma solução inteira usando / t: Build do TeamCity em uma etapa e depois na próxima / t: Package on WebAPI project. As DLLs referenciadas por qualquer referência de projeto não foram incluídas. Isso foi corrigido usando o - / T: Rebuild; Package acima no WebAPI e, em seguida, incluiu essas DLLs.
Andy Hoyle
5

Eu só tive exatamente o mesmo problema e acabou sendo causado pelo fato de dois projetos na mesma solução referenciarem uma versão diferente da biblioteca de terceiros.

Depois de corrigir todas as referências, tudo funcionou perfeitamente.

Steven Engels
fonte
4

Como Alex Burtsev mencionou em um comentário, qualquer coisa que seja usada apenas em um dicionário de recursos XAML, ou no meu caso, qualquer coisa que seja usada apenas em XAML e não no código por trás, não é considerada 'em uso' pelo MSBuild.

Portanto, simplesmente atualizar uma referência fictícia para uma classe / componente no assembly em algum código por trás foi suficiente para convencer o MSBuild de que o assembly estava realmente em uso.

Scott
fonte
Esse foi exatamente o meu problema e a solução que funcionou. Passou muito tempo tentando descobrir isso. Obrigado Scott & @Alex Burstev #
karol
Que pena, isso estava me deixando louco. Sim , eu estava usando o FontAwesome.WPF e apenas dentro do XAML (por razões óbvias). Adicionar um método fictício ajudou. Obrigado! E sim, VS 2017 15,6 continua a ser afectado, por isso entrou com um bug: github.com/dotnet/roslyn/issues/25349
Sören Kuklau
3

Alterar a estrutura de destino do .NET Framework 4 Client Profile para o .NET Framework 4 corrigiu esse problema para mim.

Portanto, no seu exemplo: defina a estrutura de destino no MyWebProject1 como .NET Framework 4

Caqui Fuyu
fonte
3

Usando o esquema de deadlydog,

Y => X => A => B ,

meu problema era que quando construí Y, os conjuntos (A e B, todos os 15) de X não estavam aparecendo na pasta bin de Y.

Resolvi isso removendo a referência X de Y, salve, construa e adicione novamente a referência X (uma referência de projeto) e salve, construa e A e B começaram a aparecer na pasta bin de Y.

JZ
fonte
Depois de muitas horas pesquisando e tentando muitas das outras soluções, essa funcionou para mim.
precisa saber é o seguinte
2

Eu tive o mesmo problema e a dll era uma referência carregada dinamicamente. Para resolver o problema, adicionei um "using" com o namespace da dll. Agora a dll é copiada na pasta de saída.

Spamme
fonte
2

Isso requer adicionar um .targetsarquivo ao seu projeto e configurá-lo para ser incluído na seção de inclusões do projeto.

Veja minha resposta aqui para o procedimento.

Alex Essilfie
fonte
2

Fazer referência a assemblies que não são usados ​​durante a compilação não é a prática correta. Você deve aumentar seu arquivo de construção para copiar os arquivos adicionais. Usando um evento pós-compilação ou atualizando o grupo de propriedades.

Alguns exemplos podem ser encontrados em outro post

verbedr
fonte
2

Outro cenário em que isso aparece é se você estiver usando o tipo de projeto "Site" mais antigo no Visual Studio. Para esse tipo de projeto, não é possível referenciar .dlls que estão fora de sua própria estrutura de diretórios (pasta atual e inativa). Portanto, na resposta acima, digamos que sua estrutura de diretórios fique assim:

insira a descrição da imagem aqui

Onde ProjectX e ProjectY são diretórios pai / filho, e ProjectX faz referência a A.dll, que por sua vez faz referência a B.dll e B.dll está fora da estrutura de diretórios, como em um pacote Nuget na raiz (Pacotes) e, em seguida, A. DLL será incluída, mas B.dll não.

Focker
fonte
0

Eu tive um problema semelhante hoje, e essa certamente não é a resposta para sua pergunta. Mas gostaria de informar a todos e, possivelmente, fornecer uma centelha de insight.

Eu tenho um aplicativo ASP.NET. O processo de compilação está definido para limpar e compilar.

Eu tenho dois scripts de Jenkins CI . Um para produção e outro para preparação. Implantei meu aplicativo no teste e tudo funcionou bem. Implantado na produção e estava faltando um arquivo DLL que foi referenciado. Este arquivo DLL estava apenas na raiz do projeto. Não está em nenhum repositório NuGet. A DLL foi definida como do not copy.

O script do IC e o aplicativo eram os mesmos entre as duas implantações. Ainda após a limpeza e implantação no ambiente de armazenamento temporário, o arquivo DLL foi substituído no local de implantação do aplicativo ASP.NET ( bin/). Este não foi o caso do ambiente de produção.

Acontece que em uma ramificação de teste, eu adicionei uma etapa ao processo de compilação para copiar esse arquivo DLL para o bindiretório. Agora, a parte que demorou um pouco para descobrir. O processo do IC não estava se limpando. A DLL foi deixada no diretório de trabalho e estava sendo empacotada acidentalmente com o arquivo .zip do ASP.NET. O ramo de produção nunca teve o arquivo DLL copiado da mesma maneira e nunca foi implantado acidentalmente.

TLDR; Verifique e saiba o que seu servidor de compilação está fazendo.

Jason Portnoy
fonte
0

Verifique se os dois projetos estão na mesma versão .net também verifique a propriedade local da cópia, mas isso deve ser trueo padrão

john.kernel
fonte
0

Usando o Visual Studio 2015, adicionando o parâmetro adicional

/ deployonbuild = false

para a linha de comando msbuild corrigiu o problema.

Giles Roberts
fonte
-1

Acabei de encontrar um problema muito semelhante. Ao compilar usando o Visual Studio 2010, o arquivo DLL foi incluído nobin pasta. Mas ao compilar usando o MSBuild, o arquivo DLL de terceiros não foi incluído.

Muito frustrante. A maneira que resolvi foi incluir a referência do NuGet ao pacote no meu projeto da web, mesmo que eu não a esteja usando diretamente lá.

Gary Brunton
fonte
Esta é uma duplicata da resposta principal.
Giles Roberts
-3

Incluir todos os arquivos DLL referenciados das suas projectreferences no projeto Website nem sempre é uma boa ideia, especialmente quando você está usando injeção de dependência : seu projeto da Web só deseja adicionar uma referência ao arquivo / projeto DLL da interface, e não a qualquer DLL de implementação concreta Arquivo.

Como se você adicionar uma referência diretamente a um arquivo / projeto DLL de implementação, não poderá impedir que seu desenvolvedor chame um "novo" em classes concretas do arquivo / projeto DLL de implementação, em vez de através da interface. Você também declarou um "código fixo" em seu site para usar a implementação.

Hung Nguyen
fonte