Share via


Selecionar os assemblies referenciados por projetos

Os assemblies são usados de duas maneiras diferentes durante uma compilação. O primeiro é a compliação em si, que permite que o código do consumidor do pacote seja compilado levando-se em conta as APIs no assembly e para o Intellisense apresentar sugestões. O segundo é o tempo de execução, onde o assembly é copiado para o diretório bin e é usado durante a execução do programa. Alguns autores de pacotes gostariam que apenas seus próprios assemblies (ou um subconjunto de seus assemblies) estivessem disponíveis para seus consumidores de pacotes em tempo de compilação, mas precisam fornecer todas as suas dependências para tempo de execução. Este documento analisa as formas de alcançar este resultado.

Nossa recomendação é ter um pacote por assembly e dependências de pacote para outros assemblies. Quando o NuGet restaura um projeto, ele faz a seleção de ativos e oferece suporte à inclusão, exclusão e criação de diferentes classes de ativos privadas. Para evitar que as dependências do seu pacote se tornem ativos de tempo de compilação para qualquer pessoa que use seu pacote, você pode tornar os ativos compile privados. No pacote gerado, isso fará com compile que seja excluído da dependência. Observe que os ativos privados padrão quando nenhum é fornecido são contentfiles;build;analyzers. Portanto, você deve usar PrivateAssets="compile;contentfiles;build;analyzers" em seu PackageReference ou ProjectReference.

<ItemGroup>
  <ProjectReference Include="..\OtherProject\OtherProject.csproj" PrivateAssets="compile;contentfiles;build;analyzers" />
  <PackageReference Include="SomePackage" Version="1.2.3" PrivateAssets="compile;contentfiles;build;analyzers" />
</ItemGroup>

Se você estiver criando um pacote a partir de um arquivo nuspec personalizado, em vez de permitir que o NuGet gere automaticamente um para você, seu nuspec deverá usar o atributo XML exclude.

<dependencies>
  <group targetFramework=".NETFramework4.8">
    <dependency id="OtherProject" version="3.2.1" exclude="Compile,Build,Analyzers" />
    <dependency id="SomePackage" version="1.2.3" exclude="Compile,Build,Analyzers" />
  </group>
</dependencies>

Há três razões pelas quais esta é a solução recomendada.

Em primeiro lugar, assemblies úteis geralmente são referenciados por novos assemblies/pacotes. Enquanto um assembly de utilitário pode ser destinado a ser usado apenas por um único pacote hoje, tornando tentador enviar ambos os assemblies em um único pacote, se um segundo pacote quiser usar o assembly de utilitário "privado" no futuro, o assembly de utilitário precisará ser movido para um novo pacote e o pacote antigo precisará ser atualizado para declará-lo como uma dependência, ou o pacote de utilitários precisará ser fornecido tanto no pacote existente quanto no novo. Se o assembly for fornecido em dois pacotes diferentes e um projeto fizer referência a ambos os pacotes, se houver versões diferentes do assembly do utilitário nos dois pacotes, o NuGet não poderá ajudar no gerenciamento de versões.

Em segundo lugar, poderá haver momentos em que os desenvolvedores que usam seu pacote também queiram usar APIs de suas dependências. Por exemplo, considere o pacote Microsoft.ServiceHub.Client versão 3.0.3078. Se você baixar o pacote e verificar o arquivo nuspec, poderá ver que ele lista dois pacotes começando com Microsoft.VisualStudio. como dependências, o que significa que ele precisa deles em tempo de execução, mas também exclui seus ativos de compilação. Isso significa que os projetos que usam Microsoft.ServiceHub.Client não terão as APIs do Visual Studio disponíveis no IntelliSense ou se eles compilarem o projeto, a menos que o projeto explicitamente instale esses pacotes. E essa é a vantagem que uma dependência de pacote com um ativo de exclusão tem. Para projetos que usam seu pacote, se eles quiserem usar suas dependências também, eles poderão adicionar uma referência ao pacote para tornar as APIs disponíveis para si mesmos.

Finalmente, alguns autores de pacotes ficaram confusos no passado sobre a seleção de assembly do NuGet para pacotes que oferecem suporte mais de uma estrutura de destino quando seu pacote também contém vários assemblies. Se o assembly principal oferecer suporte a diferentes estruturas de destino para o assembly do utilitário, talvez não seja óbvio em quais diretórios lib/ colocar todos os assemblies. Ao separar cada pacote pelo nome do assembly, é mais intuitivo em quais pastas lib/ cada assembly deve entrar. Observe que isso não significa ter pacotes Package1.net48 e Package1.net6.0. Significa ter lib/net48/Package1.dll e lib/net6.0/Package6.0 em Package1 e lib/netstandard2.0/Package2.dll e lib/net5.0/Package2.dll em Package2. Quando o Nuget restaura um projeto, ele faz a seleção de ativos de forma independente para os dois pacotes.

Observe também que os ativos de inclusão/exclusão de dependência são usados apenas por projetos que usam PackageReference. Qualquer projeto que instale seu pacote usando packages.config instalará suas dependências e terá suas APIs disponíveis também. packages.config só é compatível com modelos de projeto .NET Framework mais antigos do Visual Studio. Projetos estilo SDK, mesmo aqueles destinados ao .NET Framework, não oferecem suporte a packages.config e, portanto, oferecem suporte a ativos de inclusão/exclusão de dependência.

PackageReference e packages.config têm diferentes recursos disponíveis. Se você deseja oferecer suporte aos consumidores do pacote que usam PackageReference, packages.config ou ambos, altera a forma como você deve criar o pacote.

O destino do MSBuild Pack do NuGet não oferece suporte à inclusão automática de referências de projeto no pacote. Ele listará apenas os projetos referenciados como dependências de pacote. Há um problema no GitHub, em que os membros da comunidade compartilharam maneiras de alcançar esse resultado, que geralmente envolve o uso de metadados de item PackagePath do MSBuild para colocar arquivos em qualquer lugar do pacote, conforme descrito nos documentos sobre a inclusão de conteúdo em um pacote e o uso de SuppressDependenciesWhenPacking para evitar que as referências do projeto se tornem dependências do pacote. Também estão disponíveis ferramentas desenvolvidas pela comunidade que podem ser usadas como uma alternativa ao pacote oficial do NuGet e oferecem suporte a esse recurso.

Suporte a PackageReference

Quando um consumidor de pacote usa o PackageReference, o NuGet seleciona ativos de compilação e tempo de execução de forma independente, conforme descrito anteriormente.

Os ativos de compilação preferem ref/<tfm>/*.dll (por exemplo, ref/net6.0/*.dll), mas se isso não existir, eles voltarão para lib/<tfm>/*.dll (por exemplo, lib/net6.0/*.dll).

Os ativos de tempo de execução preferem runtimes/<rid>/lib/<tfm>/*.dll (por exemplo, runtimes/win11-x64/lib/net6.0/*.dll), mas se isso não existir, eles voltarão para lib/<tfm>/*.dll.

Como os assemblies em ref\<tfm>\ não são usados em runtime, eles podem ser assemblies somente de metadados para reduzir o tamanho do pacote.

Suporte a packages.config

Normalmente, os projetos que usam packages.config para gerenciar pacotes NuGet adicionam referências a todos os assemblies do diretório lib\<tfm>\. O diretório ref\ foi adicionado para dar suporte a PackageReference e, portanto, não é considerado ao usar packages.config. Para definir explicitamente quais assemblies são referenciados para projetos que usam packages.config, o pacote precisará usar o elemento <references> no arquivo nuspec. Por exemplo:

<references>
    <group targetFramework="net45">
        <reference file="MyLibrary.dll" />
    </group>
</references>

Os destinos do pacote MSBuild não oferecem suporte ao elemento <references>. Consulte os documentos sobre empacotamento usando um arquivo .nuspec ao usar o pacote MSBuild.

Observação

O projeto packages.config usa um processo chamado ResolveAssemblyReference para copiar os assemblies para o diretório de saída bin\<configuration>\. O assembly do projeto é copiado e, em seguida, o sistema de build examina o manifesto do assembly em busca de assemblies referenciados. Depois, ele copia esses assemblies e repete o processo recursivamente para todos os assemblies. Isso significa que, se qualquer um dos assemblies for carregado apenas por reflexão (Assembly.Load, MEF ou outra estrutura de injeção de dependência), ele não poderá ser copiado para o diretório de saída bin\<configuration>\ do seu projeto, apesar de estar em bin\<tfm>\. Isso também significa que isso só funciona para assemblies .NET, e não para código nativo chamado com P/Invoke.

Oferecendo suporte a PackageReference e packages.config.

Importante

Se um pacote contiver o elemento <references> do nuspec e não contiver assemblies em ref\<tfm>\, o NuGet anunciará os assemblies listados no elemento <references> nuspec como ativos de tempo de compilação e de execução. Isso significa que haverá exceções de runtime quando os assemblies referenciados precisarem carregar qualquer outro assembly no diretório lib\<tfm>\. Portanto, é importante usar o <references> do nuspec para suporte a packages.config, bem como duplicar assemblies na pasta ref/ para suporte a PackageReference. A pasta do pacote runtimes/ não precisa ser usada, ela foi adicionada à seção acima para fins de completude.

Exemplo

Meu pacote conterá três assemblies, MyLib.dll, MyHelpers.dll e MyUtilities.dll, direcionados ao .NET Framework 4.7.2. MyUtilities.dll contém classes que se destinam a ser usadas somente pelos outros dois assemblies, portanto, não quero disponibilizar essas classes no IntelliSense ou em tempo de compilação para projetos que usam meu pacote. Meu arquivo nuspec precisa conter os seguintes elementos XML:

<references>
    <group targetFramework="net472">
        <reference file="MyLib.dll" />
        <reference file="MyHelpers.dll" />
    </group>
</references>

Preciso garantir que o conteúdo do meu pacote seja:

lib\net472\MyLib.dll
lib\net472\MyHelpers.dll
lib\net472\MyUtilities.dll
ref\net472\MyLib.dll
ref\net472\MyHelpers.dll