web-dev-qa-db-fra.com

Ajouter des fichiers natifs du package NuGet au répertoire de sortie du projet

J'essaye de créer le paquet NuGet pour un assemblage .Net qui ne fait pas appel à une dll win32 native. J'ai besoin d'emballer à la fois l'assembly et la dll native avec l'assembly ajouté aux références du projet (aucun problème pour cette partie) et la dll native doit être copiée dans le répertoire de sortie du projet ou dans un autre répertoire relatif.

Mes questions sont:

  1. Comment est-ce que j'emballe la DLL native sans Visual Studio essayant de l'ajouter à la liste de références?
  2. Dois-je écrire un fichier install.ps1 pour copier la dll native? Si oui, comment puis-je accéder au contenu du paquet pour le copier?
110
AlonFStackoverflow

L'utilisation de la cible Copy dans le fichier de cibles pour copier les bibliothèques requises ne copie pas ces fichiers dans d'autres projets qui référencent le projet, ce qui entraîne un DllNotFoundException. Cela peut être fait avec un fichier de cibles beaucoup plus simple, en utilisant un élément None, car MSBuild copiera tous les fichiers None dans les projets de référencement.

<Project xmlns="http://schemas.Microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <NativeLibs Include="$(MSBuildThisFileDirectory)**\*.dll" />
    <None Include="@(NativeLibs)">
      <Link>%(RecursiveDir)%(FileName)%(Extension)</Link>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>
</Project>

Ajoutez le fichier de cibles au répertoire build du package de nuget avec les bibliothèques natives requises. Le fichier de cibles inclura tous les fichiers dll de tous les répertoires enfants du répertoire build. Ainsi, si vous ajoutez les versions x86 Et x64 D'une bibliothèque native utilisée par un assemblage géré Any CPU, Vous obtiendrez une structure de répertoires semblable à celle-ci:

  • construire
    • x86
      • NativeLib.dll
      • NativeLibDependency.dll
    • x64
      • NativeLib.dll
      • NativeLibDependency.dll
    • MyNugetPackageID.targets
  • lib
    • net40
      • ManagedAssembly.dll

Les mêmes répertoires x86 Et x64 Seront créés dans le répertoire de sortie du projet lors de la construction. Si vous n'avez pas besoin de sous-répertoires, les fichiers ** Et %(RecursiveDir) peuvent être supprimés et inclure à la place les fichiers requis dans le répertoire build. D'autres fichiers de contenu requis peuvent également être ajoutés de la même manière.

Les fichiers ajoutés en tant que None dans le fichier de cibles ne seront pas affichés dans le projet lorsqu’ils seront ouverts dans Visual Studio. Si vous vous demandez pourquoi je n’utilise pas le dossier Content dans nupkg, c’est qu’il n’ya aucun moyen de définir l’élément CopyToOutputDirectorysans utiliser de script PowerShell (lequel ne sera exécuté que dans Visual Studio, et non à partir de la commande Invite, sur des serveurs de génération ou dans d'autres IDE, et est non pris en charge dans les projets DNX project.json/xproj ) et je préfère utiliser un Link aux fichiers plutôt que d'avoir une copie supplémentaire des fichiers dans le projet.

Mise à jour: Bien que cela devrait également fonctionner avec Content plutôt que None, il semble qu'il existe un bogue dans msbuild afin que les fichiers ne sera pas copié dans les projets de référence avec plus d'une étape supprimée (par exemple, proj1 -> proj2 -> proj3, proj3 n'obtiendra pas les fichiers du paquet NuGet de proj1 mais proj2).

117
kjbartel

J'ai eu récemment le même problème lorsque j'ai essayé de construire un paquet EmguCV NuGet comprenant à la fois des assemblys gérés et des répertoires partagés non gérés (qui devaient également être placés dans un sous-répertoire x86) qui devait être copié automatiquement dans le répertoire de sortie de la construction après chaque construction.

Voici une solution que j'ai proposée, qui ne repose que sur NuGet et MSBuild:

  1. Placez les assemblys gérés dans le répertoire /lib du paquet (partie évidente) et les bibliothèques partagées non gérées et les fichiers associés (par exemple, les paquets .pdb) dans le /build sous-répertoire (comme décrit dans le NuGet docs ).

  2. Renommez tous les fichiers non gérés *.dll les fins de fichier, par exemple *.dl_ pour empêcher NuGet de se plaindre du fait que des assemblages présumés sont placés un mauvais endroit ( "Problème: Assembly en dehors du dossier lib.").

  3. Ajoutez un fichier personnalisé <PackageName>.targets dans le sous-répertoire /build avec le contenu suivant (voir ci-dessous pour une description):

    <?xml version="1.0" encoding="utf-8"?>
    <Project ToolsVersion="4.0" xmlns="http://schemas.Microsoft.com/developer/msbuild/2003">
      <ItemGroup>
        <AvailableItemName Include="NativeBinary" />
      </ItemGroup>
      <ItemGroup>
        <NativeBinary Include="$(MSBuildThisFileDirectory)x86\*">
          <TargetPath>x86</TargetPath>
        </NativeBinary>
      </ItemGroup>
      <PropertyGroup>
        <PrepareForRunDependsOn>
          $(PrepareForRunDependsOn);
          CopyNativeBinaries
        </PrepareForRunDependsOn>
      </PropertyGroup>
      <Target Name="CopyNativeBinaries" DependsOnTargets="CopyFilesToOutputDirectory">
        <Copy SourceFiles="@(NativeBinary)"
              DestinationFiles="@(NativeBinary->'$(OutDir)\%(TargetPath)\%(Filename).dll')"
              Condition="'%(Extension)'=='.dl_'">
          <Output TaskParameter="DestinationFiles" ItemName="FileWrites" />
        </Copy>
        <Copy SourceFiles="@(NativeBinary)"
              DestinationFiles="@(NativeBinary->'$(OutDir)\%(TargetPath)\%(Filename).%(Extension)')"
              Condition="'%(Extension)'!='.dl_'">
          <Output TaskParameter="DestinationFiles" ItemName="FileWrites" />
        </Copy>
      </Target>
    </Project>
    

Le fichier ci-dessus .targets sera injecté lors de l'installation du package NuGet dans le fichier de projet cible et est responsable de la copie des bibliothèques natives dans le répertoire de sortie.

  • <AvailableItemName Include="NativeBinary" /> Ajoute un nouvel élément "Action de construction" pour le projet (qui devient également disponible dans le menu déroulant "Action de construction" de Visual Studio).

  • <NativeBinary Include="... Ajoute les bibliothèques natives placées dans /build/x86 au projet en cours et les rend accessibles à la cible personnalisée qui copie ces fichiers dans le répertoire de sortie.

  • <TargetPath>x86</TargetPath> Ajoute des métadonnées personnalisées aux fichiers et indique à la cible personnalisée de copier les fichiers natifs dans le sous-répertoire x86 du répertoire de sortie actuel.

  • Le bloc <PrepareForRunDependsOn ... Ajoute la cible personnalisée à la liste des cibles dont dépend la construction, voir le fichier Microsoft.Common.targets pour plus de détails.

  • La cible personnalisée, CopyNativeBinaries, contient deux tâches de copie. Le premier est responsable de la copie de tous les fichiers *.dl_ dans le répertoire de sortie tout en remettant leur extension sur l'original *.dll. Le second copie simplement le reste (par exemple, n'importe quel fichier *.pdb) au même emplacement. Cela pourrait être remplacé par une tâche de copie unique et un script install.ps1 qui devait renommer tous les fichiers *.dl_ en *.dll pendant l'installation du paquet.

Cependant, cette solution ne pouvait toujours pas copier les fichiers binaires natifs dans le répertoire de sortie d'un autre projet faisant référence à celui qui incluait initialement le package NuGet. Vous devez également faire référence au paquet NuGet dans votre projet "final".

30
buygrush

Voici une alternative qui utilise le .targets À injecter le natif DLL dans le projet avec les propriétés suivantes.

  • Build action = None
  • Copy to Output Directory = Copy if newer

Le principal avantage de cette technique est que le fichier natif DLL est copié dans le dossier bin/ De projets dépendants de manière transitoire.

Voir la disposition du fichier .nuspec:

Screen capture of NuGet Package Explorer

Voici le fichier .targets:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.Microsoft.com/developer/msbuild/2003">
    <ItemGroup>
        <None Include="$(MSBuildThisFileDirectory)\..\MyNativeLib.dll">
            <Link>MyNativeLib.dll</Link>
            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
        </None>
    </ItemGroup>
</Project>

Cela insère le MyNativeLib.dll Comme s'il faisait partie du projet d'origine (mais curieusement, le fichier n'est pas visible dans Visual Studio).

Notez l'élément <Link> Qui définit le nom du fichier de destination dans le dossier bin/.

26
Benoit Blanchon

Si quelqu'un d'autre tombe par là.

Le .targets nom de fichier DOIT égal à l'identifiant de paquet NuGet

Tout le reste ne fonctionnera pas.

Les crédits vont à: https://sushihangover.github.io/nuget-and-msbuild-targets/

J'aurais dû lire plus attentivement, comme c'est noté ici. M'a pris des âges ..

Ajouter un personnalisé <PackageName>.targets

15
DirtyLittleHelper

C'est un peu tard mais j'ai créé un paquet de pépites spécialement pour ça.

L'idée est d'avoir un dossier spécial supplémentaire dans votre paquet Nuget. Je suis sûr que vous connaissez déjà Lib et Content. Le paquet de nugets que j'ai créé cherche un dossier nommé Output et copiera tout ce qu'il contient dans le dossier de sortie du projet.

La seule chose que vous devez faire est d’ajouter une dépendance de nuget au paquet http://www.nuget.org/packages/Baseclass.Contrib.Nuget.Output/

J'ai écrit un billet de blog à ce sujet: http://www.baseclass.ch/blog/Lists/Beitraege/Post.aspx?ID=6&mobile=

12
Daniel Romero

Il existe une solution C # pure que je trouve assez facile à utiliser et je n’ai pas à me soucier des limitations de NuGet. Suivez ces étapes:

Incluez la bibliothèque native dans votre projet et définissez sa propriété Build Action sur Embedded Resource.

Collez le code suivant dans la classe où vous invoquez cette bibliothèque.

private static void UnpackNativeLibrary(string libraryName)
{
    var Assembly = Assembly.GetExecutingAssembly();
    string resourceName = $"{Assembly.GetName().Name}.{libraryName}.dll";

    using (var stream = Assembly.GetManifestResourceStream(resourceName))
    using (var memoryStream = new MemoryStream(stream.CanSeek ? (int)stream.Length : 0))
    {
        stream.CopyTo(memoryStream);
        File.WriteAllBytes($"{libraryName}.dll", memoryStream.ToArray());
    }
}

Appelez cette méthode à partir du constructeur statique comme suit UnpackNativeLibrary("win32"); et elle décompactera la bibliothèque sur disque juste avant que vous en ayez besoin. Bien sûr, vous devez vous assurer que vous disposez des autorisations d'écriture sur cette partie du disque.

1
Ondrej Janacek

C'est une vieille question, mais j'ai le même problème maintenant et j'ai trouvé un redressement un peu compliqué mais très simple et efficace: créez dans le dossier Contenu standard Nuget la structure suivante avec un sous-dossier pour chaque configuration:

/Content
 /bin
   /Debug
      native libraries
   /Release
      native libraries

Lorsque vous compressez le fichier nuspec, vous recevez le message suivant pour chaque bibliothèque native des dossiers Debug et Release:

Problème: Assembly en dehors du dossier lib. Description: l'assembly 'Content\Bin\Debug\??????. Dll' ne se trouve pas dans le dossier 'lib' et ne sera donc pas ajouté comme référence lorsque le package sera installé dans un projet. Solution: déplacez-le dans le dossier 'lib' s'il doit être référencé.

Nous n’avons pas besoin d’une telle "solution" car c’est juste notre objectif: que les bibliothèques natives ne soient pas ajoutées en tant que références NET Assemblies.

Les avantages sont:

  1. Solution simple sans scripts encombrants avec des effets étranges difficiles à réinitialiser lors de la désinstallation du paquet.
  2. Nuget gère les bibliothèques natives comme n'importe quel autre contenu lors de l'installation et de la désinstallation.

Les inconvénients sont:

  1. Vous avez besoin d'un dossier pour chaque configuration (mais en général, il n'y en a que deux: Debug et Release, et si vous avez un autre contenu à installer dans chaque dossier de configuration, vous avez le choix.)
  2. Les bibliothèques natives doivent être dupliquées dans chaque dossier de configuration (mais si vous avez des versions différentes des bibliothèques natives pour chaque configuration, vous avez le choix.)
  3. Les avertissements pour chaque dll native dans chaque dossier (mais comme je l'ai dit, ils sont envoyés au créateur du paquet au moment de l'emballage, pas à l'utilisateur du paquet au moment de l'installation de VS)
1
SERWare

Je ne peux pas résoudre votre problème exact, mais je peux vous faire une suggestion.

Votre exigence clé est: "Et ne pas l'enregistrer automatiquement" .....

Vous devrez donc vous familiariser avec les "éléments de solution"

Voir référence ici:

Ajout d'éléments de niveau solution dans un package NuGet

Vous devrez écrire un peu de voodoo PowerShell pour obtenir la copie de votre DLL native dans sa maison (encore une fois, parce que vous ne voulez pas que voodoo auto-add-reference tire)

Voici un fichier ps1 que j'ai écrit ..... pour placer les fichiers dans un dossier de références tierces.

Il y en a assez pour que vous sachiez comment copier votre dll native vers une "maison" ... sans avoir à partir de zéro.

Encore une fois, ce n'est pas un coup direct, mais c'est mieux que rien.

param($installPath, $toolsPath, $package, $project)
if ($project -eq $null) {
$project = Get-Project
}

Write-Host "Start Init.ps1" 

<#
The unique identifier for the package. This is the package name that is shown when packages are listed using the Package Manager Console. These are also used when installing a package using the Install-Package command within the Package Manager Console. Package IDs may not contain any spaces or characters that are invalid in an URL.
#>
$separator = " "
$packageNameNoVersion = $package -split $separator | select -First 1

Write-Host "installPath:" "${installPath}"
Write-Host "toolsPath:" "${toolsPath}"
Write-Host "package:" "${package}"
<# Write-Host "project:" "${project}" #>
Write-Host "packageNameNoVersion:" "${packageNameNoVersion}"
Write-Host " "

<# Recursively look for a .sln file starting with the installPath #>
$parentFolder = (get-item $installPath)
do {
        $parentFolderFullName = $parentFolder.FullName

        $latest = Get-ChildItem -Path $parentFolderFullName -File -Filter *.sln | Select-Object -First 1
        if ($latest -ne $null) {
            $latestName = $latest.name
            Write-Host "${latestName}"
        }

        if ($latest -eq $null) {
            $parentFolder = $parentFolder.parent    
        }
}
while ($parentFolder -ne $null -and $latest -eq $null)
<# End recursive search for .sln file #>


if ( $parentFolder -ne $null -and $latest -ne $null )
{
    <# Create a base directory to store Solution-Level items #>
    $thirdPartyReferencesDirectory = $parentFolder.FullName + "\ThirdPartyReferences"

    if ((Test-Path -path $thirdPartyReferencesDirectory))
    {
        Write-Host "--This path already exists: $thirdPartyReferencesDirectory-------------------"
    }
    else
    {
        Write-Host "--Creating: $thirdPartyReferencesDirectory-------------------"
        New-Item -ItemType directory -Path $thirdPartyReferencesDirectory
    }

    <# Create a sub directory for only this package.  This allows a clean remove and recopy. #>
    $thirdPartyReferencesPackageDirectory = $thirdPartyReferencesDirectory + "\${packageNameNoVersion}"

    if ((Test-Path -path $thirdPartyReferencesPackageDirectory))
    {
        Write-Host "--Removing: $thirdPartyReferencesPackageDirectory-------------------"
        Remove-Item $thirdPartyReferencesPackageDirectory -Force -Recurse
    }

    if ((Test-Path -path $thirdPartyReferencesPackageDirectory))
    {
    }
    else
    {
        Write-Host "--Creating: $thirdPartyReferencesPackageDirectory-------------------"
        New-Item -ItemType directory -Path $thirdPartyReferencesPackageDirectory
    }

    Write-Host "--Copying all files for package : $packageNameNoVersion-------------------"
    Copy-Item $installPath\*.* $thirdPartyReferencesPackageDirectory -recurse
}
else
{
        Write-Host "A current or parent folder with a .sln file could not be located."
}


Write-Host "End Init.ps1" 
0
granadaCoder