Como faço para que os projetos .NET Core copiem as referências do NuGet para a saída da compilação?

110

Estou tentando escrever um sistema de plug-in com o .NET Core e um dos meus requisitos é poder distribuir a DLL do plug-in junto com suas dependências para o usuário instalar.

No entanto, não consigo descobrir como incluir minhas dependências do NuGet como um artefato de construção e gerá-las na pasta de construção, sem precisar usar dotnet publishcomo um hack. Existe alguma maneira de especificar isso no arquivo .csproj (arquivo de projeto)?

chyyran
fonte
2
Por que usar dotnet publishseria um hack? Inclua o comando em seu arquivo csproj como um script de pós-compilação.
Austin Drenski
3
dotnet publishjoga todo o framework na pasta de publicação, já que estou escrevendo um plugin, a maioria dos arquivos não são necessários, pois o framework já estaria carregado pelo programa bootstrapper. Estou procurando algo semelhante a como as compilações funcionam no .NET Framework.
chyyran
E incluir <CopyToOutputDirectory>Always</CopyToOutputDirectory>em seu csproj em cada uma das dlls que você deseja mover não resolve? Talvez combinado com um <link>nó?
Austin Drenski
7
<PackageReference/>não suporta <CopyToOutputDirectory>.
chyyran
1
No entanto, a "estrutura inteira" vem do NuGet .. e se você optar por copiar todos os assemblies do NuGet para a saída da compilação, você obterá todos eles ..
Martin Ullrich

Respostas:

179

Você pode adicionar isso a um <PropertyGroup>arquivo csproj dentro de seu arquivo para forçar a cópia de assemblies NuGet para a saída da compilação:

<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

No entanto, observe que a saída de construção ( bin/Release/netcoreapp*/*) não deve ser portátil e distribuível, a saída de dotnet publishé. Mas, no seu caso, copiar os assemblies para a saída do build é provavelmente muito útil para fins de teste. Mas observe que você também pode usar a DependencyContextapi para resolver as DLLs e seus locais que fazem parte do gráfico de dependência do aplicativo em vez de enumerar um diretório local.

Martin Ullrich
fonte
7
Isso faz com que copiem todas as dlls, não apenas as dlls Nuget
Mohammad Dayyan
2
Core 2 Estou recebendo todas as DLLs da Microsoft também. Não sei por que, mas antes eu estava recebendo apenas NuGet, mas ele parou de fazer isso? irritante
Piotr Kula
4
@MartinUllrich Você pode explicar melhor DependencyContext? Como posso usá-lo para localizar uma DLL que não está no diretório do aplicativo? Onde está, afinal?
ygoe
2
não funciona para mim núcleo do asp.net não copiando System.ValueTuple.dll
Ali Yousefi
1
@AliYousefie system.valuietuple para projetos de framework .net não deve mais vir do NuGet em versões recentes de framework .net e ferramentas de construção
Martin Ullrich
10

Você pode usar PostBuildEvent para automatizar a implantação do módulo na construção.

Para obter assemblies NuGet na pasta de compilação, adicione csproj do seu módulo

<PropertyGroup>
    <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>

Defina quais arquivos de módulo você deseja onde usando Incluir / Excluir (modifique o caminho conforme necessário)

<ItemGroup>
    <ModuleFiles
      Include="$(TargetDir)*.dll"
      Exclude="$(TargetDir)System*.dll;$(TargetDir)Microsoft*.dll"
      DestinationPath="$(SolutionDir)src\MyProject\Modules\MyModule\%(Filename)%(Extension)">
    </ModuleFiles>
</ItemGroup>

Redefina sua pasta de construção para o padrão e adicione PostbuildEvent

<Target Name="PublishModule" AfterTargets="PostBuildEvent" Inputs="@(ModuleFiles)" Outputs="@(ModuleFiles->'%(DestinationPath)')">
    <WriteLinesToFile File="$(SolutionDir)src\[YOURAPP]\app_offline.htm" />
    <Copy SourceFiles="@(ModuleFiles)" DestinationFiles="@(ModuleFiles->'%(DestinationPath)')" />
    <Delete Files="$(SolutionDir)src\[YOURAPP]\app_offline.htm" />
</Target>

Estou incluindo app_offline para reciclar o aplicativo se ele já estiver em execução para evitar erros de arquivo em uso.

Xeevis
fonte
Em meu projeto, tenho uma dependência da biblioteca Nuget "Microsoft.Extensions.Logging.Log4Net.AspNetCore" e ela não faz parte do NetCore, então essa abordagem não funcionará
sad_robot
4

Adicionando

<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

não funcionou, mas adicionar isso ao arquivo .csproj do Framework:

<RestoreProjectStyle>PackageReference</RestoreProjectStyle>

fez.

Mike Brunner
fonte
Isso funcionou bem para mim quando estava fazendo referência a bibliotecas .net Standard 2.0 de um projeto .Net Framework 4.7.2. Nada mais consertou.
Grungondola
3

Eu "resolvi" (criei uma solução alternativa) de maneira mais simples.

Na pós-construção

dotnet publish "$(ProjectFileName)" --no-build -o pub
xcopy "$(ProjectDir)pub\3rdPartyProvider.*.dll" "$(OutDir)"

pub é a pasta onde você deseja que seu material publicado vá para teste

NOTA: dependendo de qual versão dotnet.exevocê usa, o comando --no-buildpode não estar disponível.

Por exemplo, não disponível na v2.0.3; e disponível em v2.1.402. Eu sei que o VS2017 Update4 tinha v2.0.3. E Update8 tem 2.1.x

Atualizar:

A configuração acima funcionará no ambiente de depuração básico, mas para colocá-la no ambiente de servidor / produção de compilação é necessário mais. Neste exemplo particular que tive que resolver, construímos Release|x64e Release|x86separadamente. Então, eu expliquei ambos. Mas para suportar o dotnet publishcomando post build , eu primeiro adicionei RuntimeIdentifierao arquivo de projeto.

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
  <OutputPath>..\..\lib\</OutputPath>
  <RuntimeIdentifier>win-x64</RuntimeIdentifier>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
  <OutputPath>..\..\lib\</OutputPath>
  <RuntimeIdentifier>win-x86</RuntimeIdentifier>
</PropertyGroup>

Por que eu precisava e por que você pode escapar sem ele? Eu precisava disso porque meu programa de compilação está definido para interceptar o aviso MSB3270 e falhar na compilação se ele aparecer. Este aviso diz, "ei, alguns arquivos em suas dependências estão no formato errado". Mas você se lembra do objetivo deste exercício? Precisamos extrair DLLs de dependência de pacote. E, em muitos casos, não importa se esse aviso está lá, porque a pós-compilação não importa. Novamente, este é o meu programa de construção que se preocupa. Portanto, adicionei apenas RuntimeIdentifier2 configurações que utilizo durante a construção de produção.

Postagem completa

if not exist "$(ProjectDir)obj\$(ConfigurationName)" mkdir "$(ProjectDir)obj\$(ConfigurationName)"
xcopy  "$(ProjectDir)obj\$(PlatformName)\$(ConfigurationName)" "$(ProjectDir)obj\$(ConfigurationName)" /E /R /Y

if $(ConfigurationName) == Release (
    dotnet publish "$(ProjectFileName)" --runtime win-$(PlatformName) --no-build -c $(ConfigurationName) -o pub --no-restore --no-dependencies
) else (
    dotnet publish "$(ProjectFileName)" --no-build -c $(ConfigurationName) -o pub --no-restore --no-dependencies
)

xcopy "$(ProjectDir)pub\my3rdPartyCompany.*.dll" "$(OutDir)" /Y /R

Explicação: o dotnet publish está procurando obj\Debugou obj\Release. Não o temos durante a construção porque a construção cria obj\x64\Releaseou obj\x86\Release. As linhas 1 e 2 atenuam esse problema. Na linha 3, digo dotnet.exepara usar configuração específica e tempo de execução de destino. Caso contrário, quando este é o modo de depuração, não me importo com as coisas do tempo de execução e avisos. E na última linha, simplesmente pego minhas dlls e as copio para a pasta de saída. Tarefa concluída.

TS
fonte
O parâmetro "-c Release" é necessário para o comando "dotnet publish" se o projeto não tiver configuração de depuração (como no meu caso). Usei este lote como evento pós-compilação: dotnet publish "$(ProjectFileName)" -c Release --no-build -o bin\pub xcopy "$(ProjectDir)pub\PostSharp.dll" "$(OutDir)"
Xtro
0

Em conjunto com a resposta acima: Isso está funcionando muito bem na linha de comando do evento Pós-compilação: no Visual Studio. Ele executa um loop em uma seleção de dlls (System * .dll e Microsoft .dll) * e, em seguida, ignora a exclusão de dlls específicas. System.Data.SqlClient.dll e System.Runtime.Loader.dll

for %%f in ($(OutDir)System*.dll $(OutDir)Microsoft*.dll) do if not %%f == $(OutDir)System.Data.SqlClient.dll if not %%f == $(OutDir)System.Runtime.Loader.dll del %%f
Robalo
fonte