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:
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:
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 CopyToOutputDirectory
sans 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).
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:
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 ).
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.").
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".
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
:
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/
.
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
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=
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.
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:
Les inconvénients sont:
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"