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

j'essaie de créer un paquet NuGet pour un assemblage .Net qui ne pinvoke vers un win32 natif. J'ai besoin d'empaqueter à la fois l'assemblée et la dll native avec l'Assemblée ajoutée aux références du projet (aucun problème à cette partie) et la dll native doit être copiée dans le répertoire de sortie du projet ou un autre répertoire relatif.

mes questions sont:

  1. Comment puis-je empaqueter la dll native sans visual studio essayant de l'ajouter dans la liste des références?
  2. Dois-je écrire une installation.ps1 pour copier la dll native? Si oui, comment puis-je accéder au contenu du paquet pour le copier?
87
demandé sur kjbartel 2013-10-20 18:50:37

9 réponses

en utilisant la cible Copy dans le fichier cible pour copier les bibliothèques nécessaires ne copiera pas ces fichiers vers d'autres projets qui font référence au projet, ce qui donne un DllNotFoundException . Cela peut être fait avec un fichier cible beaucoup plus simple, en utilisant un élément None , puisque MSBuild copiera tous les fichiers None vers 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>

ajouter le fichier cible au répertoire build du paquet nuget avec le fichier requis bibliothèques natives. Le fichier cible inclura tous les fichiers dll dans tous les répertoires enfants du répertoire build . Ainsi, pour ajouter une version x86 et x64 d'une bibliothèque native utilisée par une Any CPU Assemblée gérée, vous finiriez avec une structure de répertoire similaire à ce qui suit:

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

Le même Les répertoires x86 et x64 seront créés dans le répertoire de sortie du projet une fois construits. Si vous n'avez pas besoin de sous-répertoires, alors le ** et le %(RecursiveDir) peuvent être supprimés et à la place inclure les fichiers requis dans le répertoire build directement. Les autres fichiers de contenu peut également être ajoutés de la même façon.

les fichiers ajoutés en tant que None dans le fichier targets ne seront pas affichés dans le projet lors de leur ouverture dans Visual Studio. Si vous êtes je me demande pourquoi je n'utilise pas le dossier Content dans le nupkg, c'est parce qu'il n'y a aucun moyen de définir l'élément CopyToOutputDirectory sans utiliser un script powershell (qui ne sera exécuté qu'à L'intérieur de Visual Studio, pas à partir de l'invite de commande, sur des serveurs de construction ou dans D'autres IDEs, et qui est non supporté dans le projet.json / xproj DNX projets ) et je préfère utiliser un Link pour les fichiers plutôt que d'avoir une copie des fichiers dans le projet.

mise à Jour: Bien que cela devrait aussi fonctionner avec Content plutôt que None il semble qu'il y ait un bug dans msbuild donc les fichiers ne seront pas copiés pour référencer les projets plus d'une étape supprimée (par exemple proj1 -> proj2 -> proj3, proj3 ne récupérera pas les fichiers du paquet NuGet de proj1 mais proj2 le fera).

93
répondu kjbartel 2017-05-23 12:18:19

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

Voici une solution que J'ai trouvé, qui repose uniquement sur NuGet et MSBuild:

  1. Placer les assemblages gérés dans le répertoire /lib du paquet (partie évidente) et dans les bibliothèques partagées non gérées et les fichiers connexes (par exemple .pdb packages) dans le sous-répertoire /build (tel que décrit dans le NuGet docs ).

  2. Renommer tous les non-géré *.dll fichier des terminaisons quelque chose de différent, par exemple *.dl_ " pour empêcher NuGet de se plaindre de prétendus assemblages placés à un mauvais endroit ( " problème: assemblage à l'extérieur du dossier lib." ).

  3. ajouter une coutume <PackageName>.targets fichier dans le sous-répertoire /build avec quelque chose comme 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 "1519220920 .targets sera injecté sur une installation du paquet NuGet dans le fichier cible du projet et est responsable de copier les bibliothèques natives dans le répertoire de sortie.

  • <AvailableItemName Include="NativeBinary" /> ajoute un nouvel élément " Build Action "pour le projet (qui devient également disponible dans le menu déroulant" Build Action " à L'intérieur de Visual Studio).

  • <NativeBinary Include="... Ajoute les bibliothèques natives placées dans /build/x86 au projet actuel 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 dit à la cible personnalisée de copier les fichiers natifs dans le sous-répertoire x86 du répertoire de sortie réel.

  • le bloc <PrepareForRunDependsOn ... ajoute la cible personnalisée à la liste des cibles dont la construction dépend, voir le Microsoft.Commun.cibles pour plus de détails.

  • La cible personnalisée, CopyNativeBinaries , contient deux tâches de copie. Le premier est responsable de copier tous les fichiers *.dl_ dans le répertoire de sortie tout en changeant leur extension vers le l'original *.dll . La seconde copie simplement le reste (par exemple les fichiers *.pdb ) au même endroit. Ceci pourrait être remplacé par une tâche en copie unique et une installation .ps1 script qui a dû renommer tous les fichiers *.dl_ en fichiers *.dll pendant l'installation du paquet.

cependant, ce la solution ne copierait toujours pas les binaires natifs dans le répertoire de sortie d'un autre projet faisant référence à celui qui inclut initialement le paquet NuGet. Vous devez encore faire référence au paquet NuGet dans votre projet" final".

29
répondu buygrush 2015-04-07 20:11:50

Voici une alternative qui utilise le .targets pour injecter la DLL native 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 la DLL native est copiée dans le bin/ dossier de projets dépendants de manière transitive.

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>

ceci insère le MyNativeLib.dll comme s'il faisait partie du projet original (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 bin/ dossier.

20
répondu Benoit Blanchon 2015-06-05 13:54:40

c'est un peu tard mais j'ai créé un paquet nuget exactement pour ça.

L'idée est d'avoir un dossier spécial dans votre package nuget. Je suis sûr que vous connaissez déjà Lib et Content. Le paquet nuget que j'ai créé recherche un dossier nommé Output et copiera tout ce qui s'y trouve dans le dossier projects output.

la seule chose que vous devez faire est d'ajouter une dépendance 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=0

12
répondu Daniel Romero 2013-11-30 14:09:59

si quelqu'un d'autre trébuche là-dessus.

le .targets nom du fichier doit doit être égal au numéro D'Identification du paquet

rien d'autre ne marchera.

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

j'aurais dû lire plus attentivement comme il est noté ici. M'a pris les âges..

Ajouter un custom <PackageName>.targets

9
répondu DirtyLittleHelper 2017-03-31 15:06:26

il y a une solution pure C# que je trouve assez facile à utiliser et je n'ai pas à me soucier des limitations NuGet. Suivez ces étapes:

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

coller le code suivant dans la classe où vous PInvoke 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 du constructeur statique comme suit UnpackNativeLibrary("win32"); et il va défaire la bibliothèque au disque juste avant que vous en ayez besoin. Bien sûr, vous devez être sûr que vous avez les permissions d'écriture sur cette partie du disque.

1
répondu Ondrej Janacek 2016-04-11 15:26:32

c'est une vieille question, mais j'ai le même problème maintenant, et j'ai trouvé un retournement qui est un peu délicat mais très simple et efficace: créer dans le dossier Nuget Standard Content la structure suivante avec un sous-dossier pour chaque configuration:

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

lorsque vous empaquetez le fichier nuspec, vous recevrez le message suivant pour chaque bibliothèque native dans les dossiers de débogage et de publication:

Thème: Assemblée en dehors du dossier lib. Description: l'assemblage 'Content\Bin \ Debug\??????.dll "n'est pas dans le dossier" lib " et par conséquent, il ne sera pas ajouté comme référence lorsque le paquet est installé. dans un projet. Solution: déplacez-le dans le dossier' lib ' s'il le faut être référencé.

nous n'avons pas besoin d'une telle" solution " parce que c'est juste notre but: que les bibliothèques natives ne soient pas ajoutées comme références D'assemblages de réseaux.

les avantages sont:

  1. solution Simple sans scripts encombrants avec des effets étranges qui sont difficiles à réinitialiser lors de la désinstallation du paquet.
  2. Nuget gère les bibliothèques natives comme tout autre contenu lors de l'installation et de la désinstallation.

Les inconvénients sont les suivants:

  1. vous avez besoin d'un dossier pour chaque configuration (mais habituellement il n'y en a que deux: Debug et Release, et si vous avez d'autres contenus qui doit être installé dans chaque dossier de configuration, c'est la voie à suivre)
  2. bibliothèques Natives doivent être dupliqué dans chaque dossier de configuration (mais si vous avez les différentes versions des bibliothèques natives pour chaque configuration, c'est la voie à suivre)
  3. les avertissements pour chaque dll natif dans chaque dossier (mais comme je l'ai dit, ils sont émis au créateur du paquet à l'heure du paquet, pas à l'utilisateur du paquet à L'Heure de L'installation VS)
1
répondu SERWare 2016-11-08 17:14:11

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

votre exigence principale est la suivante : "et ne pas faire enregistrer automatiquement la référence".....

vous devez donc vous familiariser avec les" éléments de solution "

voir référence ici:

ajout D'éléments au niveau de la solution dans un emballage NuGet

vous devrez écrire un peu de vaudou powershell pour obtenir la copie de votre dll native dans sa maison (encore une fois, parce que vous ne voulez pas l'auto-add-référence voodoo à feu)

voici un fichier ps1 que j'ai écrit....pour mettre des fichiers dans un dossier de référence tiers.

il y a assez là pour que vous compreniez comment copier votre dll natif dans quelque"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
répondu granadaCoder 2017-05-23 12:03:02

Mettre le contenu du dossier

la commande nuget pack [projfile].csproj le fera pour vous automatiquement si vous cochez les fichiers que de contenu.

puis éditer le fichier de projet comme mentionné ici en ajoutant ItemGroup & NativeLibs & None element

<ItemGroup>
    <NativeLibs Include="$(MSBuildThisFileDirectory)**\*.dll" />
    <None Include="@(NativeLibs)">
      <Link>%(RecursiveDir)%(FileName)%(Extension)</Link>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
</ItemGroup>

a travaillé pour moi

-1
répondu Sharon Salmon 2017-10-16 10:44:16