NuGet paketinden proje çıktı dizinine yerel dosyalar ekleyin


126

Yerel bir win32 dll'ye pinvoke yapan bir .Net derlemesi için NuGet paketi oluşturmaya çalışıyorum. Hem derlemeyi hem de yerel dll'yi proje referanslarına eklenen derlemeyle paketlemem gerekiyor (bu bölümde sorun yok) ve yerel dll proje çıktı dizinine veya başka bir ilgili dizine kopyalanmalıdır.

Sorularım:

  1. Görsel stüdyosu referans listesine eklemeye çalışmadan yerel dll'yi nasıl paketleyebilirim?
  2. Yerel dll'yi kopyalamak için bir install.ps1 yazmam gerekir mi? Varsa, kopyalamak için paket içeriğine nasıl erişebilirim?

1
Çalışma zamanı / mimariye özgü kitaplıklar için destek var, ancak özellik belgeleri eksik ve UWP'ye özgü görünüyor. docs.microsoft.com/en-us/nuget/create-packages/…
Wouter

Yanıtlar:


131

Kullanma Copyhedefler dosyasında hedef gerekli kütüphaneleri bir sonuçlanan proje başvurmak diğer projeler için bu dosyaları kopyalamak olmaz kopyalamak için DllNotFoundException. NoneMSBuild tüm Nonedosyaları referans projelerine kopyalayacağından, bu çok daha basit bir hedef dosyasıyla, bir öğe kullanılarak yapılabilir .

<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>

Hedefler dosyasını buildgerekli yerel kitaplıklarla birlikte nuget paketinin dizinine ekleyin. Hedefler dosyası, dlldizinin tüm alt dizinlerindeki tüm dosyaları içerecektir build. Dolayısıyla, yönetilen bir derleme tarafından kullanılan yerel bir kitaplığın bir x86ve x64sürümünü eklemek Any CPUiçin aşağıdakine benzer bir dizin yapısına sahip olursunuz:

  • inşa etmek
    • x86
      • NativeLib.dll
      • NativeLibDependency.dll
    • x64
      • NativeLib.dll
      • NativeLibDependency.dll
    • MyNugetPackageID.targets
  • lib
    • net40
      • ManagedAssembly.dll

Aynı x86ve x64dizinler, inşa edildiğinde projenin çıktı dizininde oluşturulacaktır. Alt dizinlere ihtiyacınız yoksa, o zaman **ve %(RecursiveDir)kaldırılabilir ve bunun yerine gerekli dosyaları builddoğrudan dizine ekleyebilir . Diğer gerekli içerik dosyaları da aynı şekilde eklenebilir.

NoneHedefler dosyasında olduğu gibi eklenen dosyalar , Visual Studio'da açıldığında projede gösterilmez. ContentKlasörü nupkg'da neden kullanmadığımı merak ediyorsanız , bunun nedeni bir powershell betiği kullanmadanCopyToOutputDirectory öğeyi ayarlamanın bir yolu olmamasıdır (bu, komut isteminde, derleme sunucularında veya içinde değil, yalnızca Visual Studio içinde çalıştırılır) diğer IDE'ler ve project.json / xproj DNX projelerinde desteklenmiyor ) ve proje içindeki dosyaların ek bir kopyasına sahip olmak yerine dosyalara a kullanmayı tercih ediyorum .Link

Güncelleme: Bu , msbuild'de bir hata varmış gibi görünmek Contentyerine Nonebununla da çalışmalıdır, böylece dosyalar birden fazla adım kaldırılan projelere başvurmak için kopyalanmaz (örn. Proj1 -> proj2 -> proj3, proj3 dosyaları almaz proj1'in NuGet paketinden ancak proj2 olacaktır).


4
Efendim, siz bir dahisiniz! Tıkır tıkır çalışıyor. Teşekkürler.
MoonStom

Koşulun neden '$(MSBuildThisFileDirectory)' != '' And HasTrailingSlash('$(MSBuildThisFileDirectory)')gerekli olduğunu merak ediyor musunuz? Her MSBuildThisFileDirectoryzaman ayarlandığını düşündüm . Ne zaman durum böyle olmaz?
06'da kkm

@kkm Dürüst olmak gerekirse. Bunun gerekli olduğunu sanmıyorum. Bunu nereden aldığımı bile hatırlayamıyorum.
kjbartel

@kkm Başlangıçta System.Data.SQLite nuget paketini değiştirdim ve içerdikleri tüm diğer saçmalıkları kaldırdığımda bunu geride bıraktım. Orijinal hedefler dosyası .
kjbartel

2
@SuperJMN Orada joker karakterler var. Fark etmedin **\*.dllmi? Bu .dll, tüm dizinlerdeki tüm dosyaları kopyalamaktır . Bir **\*.*dizin ağacının tamamını kopyalamak için kolayca yapabilirsiniz .
kjbartel

30

Her derlemeden x86sonra derleme çıktı dizinine otomatik olarak kopyalanması gereken hem yönetilen derlemeleri hem de yönetilmeyen paylaşılan lirary'leri (ayrıca bir alt dizine yerleştirilmesi gerekiyordu) içeren bir EmguCV NuGet paketi oluşturmaya çalıştığımda da aynı sorunu yaşadım. .

İşte bulduğum, yalnızca NuGet ve MSBuild'e dayanan bir çözüm:

  1. Yönetilen derlemeleri /libpaketin dizinine (açık kısım) ve yönetilmeyen paylaşılan kitaplıkları ve ilgili dosyaları (örn. .Pdb paketleri) /buildalt dizine ( NuGet belgelerinde açıklandığı gibi ) yerleştirin.

  2. Yönetilmeyen tüm *.dlldosya sonlarını farklı bir *.dl_adla yeniden adlandırın ; örneğin , NuGet'in iddia edilen derlemelerin yanlış bir yere yerleştirilmesi ( "Sorun: lib klasörü dışında derleme." ) Hakkında sızlanmasını önlemek için .

  3. Alt dizine aşağıdaki içeriğe benzer bir özel <PackageName>.targetsdosya ekleyin /build(açıklama için aşağıya bakın):

    <?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>
    

Yukarıdaki .targetsdosya, hedef proje dosyasındaki NuGet paketinin kurulumuna enjekte edilecektir ve yerel kitaplıkların çıktı dizinine kopyalanmasından sorumludur.

  • <AvailableItemName Include="NativeBinary" /> proje için yeni bir öğe "Derleme Eylemi" ekler (bu, Visual Studio içindeki "Eylem Oluşturma" açılır menüsünde de bulunur).

  • <NativeBinary Include=".../build/x86mevcut projeye yerleştirilen yerel kitaplıkları ekler ve bu dosyaları çıktı dizinine kopyalayan özel hedef için erişilebilir hale getirir.

  • <TargetPath>x86</TargetPath>dosyalara özel meta veriler ekler ve özel hedefe yerel dosyaları x86gerçek çıktı dizininin alt dizinine kopyalamasını söyler .

  • <PrepareForRunDependsOn ...Blok yapı bağlıdır hedeflerin listesine özel hedef, bkz ekler Microsoft.Common.targets Ayrıntılar için dosyayı.

  • Özel hedef, CopyNativeBinariesiki kopyalama görevi içerir. Birincisi *.dl_, uzantılarını orijinaline geri döndürürken herhangi bir dosyayı çıktı dizinine kopyalamaktan sorumludur *.dll. İkincisi, geri kalanını (örneğin herhangi bir *.pdbdosyayı) aynı konuma kopyalar . Bu, tek bir kopyalama görevi ve paket kurulumu sırasında tüm dosyaları yeniden adlandırmak zorunda olan bir install.ps1 betiği ile değiştirilebilir .*.dl_*.dll

Ancak, bu çözüm yine de yerel ikili dosyaları, başlangıçta NuGet paketini içeren bir projeye başvuran başka bir projenin çıktı dizinine kopyalamayacaktır. Yine de "son" projenizde NuGet paketine başvurmanız gerekir.


4
" Ancak, bu çözüm yine de yerel ikili dosyaları, başlangıçta NuGet paketini içeren bir projeye başvuran başka bir projenin çıktı dizinine kopyalamayacaktır. Yine de" son "projenizde NuGet paketine başvurmanız gerekir. " Bu bir benim için tıpa göster. Genellikle nuget paketini birden çok projeye (birim testleri gibi) eklemeniz gerektiği anlamına gelir, aksi takdirde atılırsınız DllNotFoundException.
kjbartel

2
sadece uyarı nedeniyle dosyaları vb. yeniden adlandırmak biraz zor.

<NoWarn>NU5100</NoWarn>proje dosyanıza ekleyerek uyarıyı kaldırabilirsiniz
Florian Koch

29

İşte kullanan bir alternatiftir .targetsiçin projede yerli DLL enjekte aşağıdaki özelliklere sahip.

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

Bu tekniğin ana yararı, yerel DLL'nin geçişli bin/olarak bağımlı projeler klasörüne kopyalanmasıdır .

.nuspecDosyanın düzenine bakın :

NuGet Paket Gezgini'nin ekran görüntüsü

İşte .targetsdosya:

<?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>

Bu MyNativeLib.dll, orijinal projenin bir parçasıymış gibi ekler (ancak ilginç bir şekilde dosya Visual Studio'da görünmez).

Klasördeki <Link>hedef dosya adını belirleyen öğeye dikkat edin bin/.


Azure hizmetimin bir parçası olarak eklemem gereken bazı .bat ve .ps1 dosyalarıyla bir muamele yapıyor - teşekkürler :)
Zhaph - Ben Duguid

"(Ancak ilginç bir şekilde dosya Visual Studio'da görünmüyor)." - proje dosyaları VS'nin kendisi AFAIK tarafından ayrıştırılır, bu nedenle harici .target dosyalarında (veya hedef yürütmede dinamik olarak oluşturulanlar) eklenen öğeler gösterilmez.
kkm

' Dan' ye geçmek dışında bu önceki diğer cevaptan nasıl farklıdır ? ContentNone
kjbartel

3
vay hızlısın. her neyse, bunu yapmayı seçerseniz, en azından 'bunun cevabımdan ne farkı var' diye sorabilirsiniz. bu imo, orijinal soruyu düzenlemekten, kendi başınıza yanıtlamaktan ve ardından yanıtınızı diğer insanların yorumlarında tanıtmaktan daha adil olacaktır. kişisel olarak bu cevabı sizinkinden daha çok sevdiğimi söylememe gerek yok - kısa, özlü ve okuması kolay
Maksim Satsikau

3
@MaksimSatsikau Geçmişe bakmak isteyebilirsiniz. Soruyu daha net hale getirmek için düzenledim, sonra soruyu cevapladım. Bu cevap birkaç hafta sonra geldi ve aslında bir kopyaydı. Bunu kaba bulduysam özür dilerim.
kjbartel

19

Başka biri buna rastlarsa.

.targetsDosya adı GEREKİR Nuget Paketi Kimliği eşit

Başka hiçbir şey işe yaramayacak.

Krediler şu adrese gidin: https://sushihangover.github.io/nuget-and-msbuild-targets/

Aslında burada belirtildiği gibi daha ayrıntılı okumalıydım. Yaşlarımı aldı ..

Bir özel ekle <PackageName>.targets


3
bütün günümü kurtardın!
zheng yu

1
Haftalık bir sorunu başka bir şeyle düzelttiniz. Sana ve o github sayfasına teşekkürler.
Glenn Watson

13

Biraz geç ama bunun için tam bir nuget paketi oluşturdum.

Buradaki fikir, nuget paketinizde ek bir özel klasöre sahip olmaktır. Eminim zaten Lib ve Content'i biliyorsunuzdur. Oluşturduğum nuget paketi, Çıktı adlı bir Klasörü arar ve içindeki her şeyi proje çıktı klasörüne kopyalayacaktır.

Yapmanız gereken tek şey, http://www.nuget.org/packages/Baseclass.Contrib.Nuget.Output/ paketine bir nuget bağımlılığı eklemek.

Bununla ilgili bir blog yazısı yazdım: http://www.baseclass.ch/blog/Lists/Beitraege/Post.aspx?ID=6&mobile=0


Bu harika! Ancak, bu sadece mevcut projede çalışır. Proje bir "Sınıf Kitaplığı" ise ve örneğin bir "Web Uygulamasına" bağımlılık eklemek istiyorsanız, DLL'ler web uygulamasında oluşturulmayacaktır! Benim "hızlı düzeltme" şudur: Kitaplığınız için bir NuGet oluşturun ve Sınıf Kitaplığı'na başvurun ve bağımlılıklar için başka bir Nuget (bu durumda dll'ler) oluşturun ve WebApplication'a uygulayın. Bunun için en iyi çözüm var mı?
Wagner Leonardi

Bu projeyi yalnızca .NET 4.0 (Windows) için oluşturmuş görünüyorsunuz. Taşınabilir sınıf kitaplıklarını da destekleyecek şekilde güncellemeyi planlıyor musunuz?
Ani

1

Kullanımı oldukça kolay bulduğum saf bir C # çözümü var ve NuGet sınırlamaları ile uğraşmak zorunda değilim. Bu adımları takip et:

Yerel kitaplığı projenize dahil edin ve Build Action özelliğini olarak ayarlayın Embedded Resource.

Aşağıdaki kodu, bu kitaplığı PInvoke ettiğiniz sınıfa yapıştırın.

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());
    }
}

Bu yöntemi statik yapıcıdan aşağıdaki gibi çağırın UnpackNativeLibrary("win32");ve ihtiyacınız olmadan hemen önce kitaplığı diske açacaktır. Elbette, diskin o kısmına yazma izniniz olduğundan emin olmanız gerekir.


1

Bu eski bir soru, ancak şimdi aynı sorunu yaşıyorum ve biraz zor ama çok basit ve etkili bir geri dönüş buldum: Nuget standart İçerik klasöründe her konfigürasyon için bir alt klasörle aşağıdaki yapıyı oluşturun:

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

Nuspec dosyasını paketlediğinizde, Debug ve Release klasörlerindeki her yerel kitaplık için aşağıdaki iletiyi alırsınız:

Sorun: lib klasörünün dışında derleme. Açıklama: 'Content \ Bin \ Debug \ ??????. Dll' derlemesi 'lib' klasörünün içinde değildir ve bu nedenle paket bir projeye yüklendiğinde referans olarak eklenmeyecektir. Çözüm: Referans gösterilmesi gerekiyorsa 'lib' klasörüne taşıyın.

Böyle bir "çözüme" ihtiyacımız yok çünkü sadece amacımız bu: yerel kitaplıklar NET Assemblies referansları olarak eklenmiyor.

Avantajlar:

  1. Paket kaldırılırken sıfırlanması zor olan tuhaf efektlere sahip, hantal komut dosyaları içermeyen basit çözüm.
  2. Nuget, kurarken ve kaldırırken diğer içerikler gibi yerel kitaplıkları da yönetir.

Dezavantajlar:

  1. Her yapılandırma için bir klasöre ihtiyacınız vardır (ancak genellikle yalnızca iki tane vardır: Hata Ayıklama ve Yayınlama ve her bir yapılandırma klasörüne yüklenmesi gereken başka içeriğiniz varsa, bu iki yoldur)
  2. Yerel kitaplıklar her yapılandırma klasöründe çoğaltılmalıdır (ancak her yapılandırma için yerel kitaplıkların farklı sürümlerine sahipseniz, bu iki yoldur)
  3. Her klasördeki her yerel dll için uyarılar (ancak söylediğim gibi uyarılar, VS yükleme zamanında paket kullanıcısına değil paket anında paket oluşturucuya verilir)

0

Sorununuzu tam olarak çözemiyorum ama size bir öneri verebilirim.

Temel gereksiniminiz: "Ve referansı otomatik kaydettirmesin" .....

Bu nedenle, "çözüm öğelerine" aşina olmanız gerekecek

Buradaki referansa bakın:

NuGet paketine çözüm düzeyinde öğeler ekleme

Yerel dll'nizin kopyasını evine almak için biraz powershell voodoo yazmanız gerekecek (yine, çünkü otomatik ekle-referans vudu ateşlenmesini istemiyorsunuz)

İşte yazdığım bir ps1 dosyası ..... dosyaları üçüncü taraf referanslar klasörüne koymak için.

Orada, sıfırdan başlamak zorunda kalmadan yerel dll'nizi bir "ev" e nasıl kopyalayacağınızı anlamanız için yeterince bilgi var.

Yine, doğrudan bir vuruş değil, ama hiç yoktan iyidir.

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" 

-2

İçerik klasörü koy

nuget pack [projfile].csprojDosyaları içerik olarak işaretlerseniz komut bunu sizin için otomatik olarak yapacaktır.

sonra burada belirtildiği gibi proje dosyasını düzenleyerek ItemGroup & NativeLibs & None öğesini ekleyin

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

benim için çalıştı

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.