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:
- Comment puis-je empaqueter la dll native sans visual studio essayant de l'ajouter dans la liste des références?
- 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?
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
- x86
- lib
- net40
- ManagedAssembly.dll
- net40
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).
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:
-
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 ). -
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." ). -
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épertoirex86
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".
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
:
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.
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
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
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.
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:
- solution Simple sans scripts encombrants avec des effets étranges qui sont difficiles à réinitialiser lors de la désinstallation du paquet.
- 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:
- 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)
- 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)
- 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)
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"
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