Tambahkan file asli dari paket NuGet ke direktori output proyek

126

Saya mencoba membuat paket NuGet untuk .Net assembly yang melakukan pinvoke ke win32 dll. Saya perlu mengemas baik rakitan dan dll asli dengan rakitan ditambahkan ke referensi proyek (tidak ada masalah pada bagian ini) dan dll asli harus disalin ke direktori keluaran proyek atau beberapa direktori relatif lainnya.

Pertanyaan saya adalah:

  1. Bagaimana saya mengemas dll asli tanpa studio visual mencoba menambahkannya ke dalam daftar referensi?
  2. Apakah saya harus menulis install.ps1 untuk menyalin dll asli? Jika demikian, bagaimana saya dapat mengakses konten paket untuk menyalinnya?
AlonFStackoverflow
sumber
1
Ada dukungan untuk pustaka runtime / arsitektur tertentu, tetapi dokumentasi fitur kurang dan tampaknya spesifik UWP. docs.microsoft.com/en-us/nuget/create-packages/…
Wouter

Jawaban:

131

Menggunakan Copytarget dalam file target untuk menyalin pustaka yang diperlukan tidak akan menyalin file-file itu ke proyek lain yang mereferensikan proyek, menghasilkan a DllNotFoundException. Ini dapat dilakukan dengan file target yang lebih sederhana, menggunakan Noneelemen, karena MSBuild akan menyalin semua Nonefile ke proyek referensi.

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

Tambahkan file target ke builddirektori paket nuget bersama dengan pustaka asli yang diperlukan. File target akan mencakup semua dllfile di semua direktori anak dari builddirektori. Jadi untuk menambahkan x86dan x64versi pustaka asli yang digunakan oleh Any CPUrakitan terkelola Anda akan berakhir dengan struktur direktori yang mirip dengan berikut:

  • membangun
    • x86
      • NativeLib.dll
      • NativeLibDependency.dll
    • x64
      • NativeLib.dll
      • NativeLibDependency.dll
    • MyNugetPackageID.targets
  • Lib
    • net40
      • ManagedAssembly.dll

Direktori yang sama x86dan x64akan dibuat di direktori output proyek ketika dibangun. Jika Anda tidak memerlukan subdirektori maka file **dan %(RecursiveDir)dapat dihapus dan sebaliknya sertakan file yang diperlukan dalam builddirektori secara langsung. File konten lain yang diperlukan juga dapat ditambahkan dengan cara yang sama.

File yang ditambahkan seperti Nonedalam file target tidak akan ditampilkan dalam proyek ketika dibuka di Visual Studio. Jika Anda bertanya-tanya mengapa saya tidak menggunakan Contentfolder di nupkg itu karena tidak ada cara untuk mengatur CopyToOutputDirectoryelemen tanpa menggunakan skrip PowerShell (yang hanya akan dijalankan di dalam Visual Studio, bukan dari command prompt, pada build server atau di IDE lain, dan tidak didukung dalam proyek DNX project.json / xproj ) dan saya lebih suka menggunakan a Linkke file daripada memiliki salinan tambahan file dalam proyek.

Pembaruan: Meskipun ini juga harus bekerja dengan Contentdaripada Nonetampaknya ada bug di msbuild sehingga file tidak akan disalin ke proyek referensi lebih dari satu langkah dihapus (misalnya proj1 -> proj2 -> proj3, proj3 tidak akan mendapatkan file dari paket NuGet proj1 tetapi proj2 akan).

kjbartel
sumber
4
Pak, Anda jenius! Bekerja seperti pesona. Terima kasih.
MoonStom
Ingin tahu mengapa syarat itu '$(MSBuildThisFileDirectory)' != '' And HasTrailingSlash('$(MSBuildThisFileDirectory)')diperlukan? Saya pikir itu MSBuildThisFileDirectoryselalu diatur. Kapan itu tidak terjadi?
kkm
@ km Jujur. Saya pikir itu tidak diperlukan. Aku bahkan tidak ingat dari mana asalnya.
kjbartel
@ kkm Saya awalnya memodifikasi paket nuget System.Data.SQLite dan sepertinya saya meninggalkannya ketika saya menghapus semua omong kosong lainnya yang mereka sertakan. File target asli .
kjbartel
2
@ SupJJNN Ada wildcard di sana. Apakah kamu tidak memperhatikan **\*.dll? Itu menyalin semua .dllfile di semua direktori. Anda dapat dengan mudah melakukannya **\*.*untuk menyalin seluruh pohon direktori.
kjbartel
30

Saya baru-baru ini memiliki masalah yang sama ketika saya mencoba untuk membangun paket EmguCV NuGet termasuk rakitan terkelola dan lirari bersama yang tidak dikelola (yang juga harus ditempatkan dalam x86subdirektori) yang harus disalin secara otomatis ke direktori output build setelah setiap build .

Inilah solusi yang saya buat, yang hanya mengandalkan NuGet dan MSBuild:

  1. Tempatkan rakitan terkelola di /libdirektori paket (bagian yang jelas) dan pustaka bersama yang tidak dikelola serta file terkait (mis. Paket .pdb) di /buildsubdirektori (seperti yang dijelaskan dalam dokumen NuGet ).

  2. Ganti nama semua *.dllujung file yang tidak dikelola menjadi sesuatu yang berbeda, misalnya *.dl_untuk mencegah NuGet mengeluh tentang dugaan rakitan yang ditempatkan di tempat yang salah ( "Masalah: Rakitan folder di luar lib." ).

  3. Tambahkan <PackageName>.targetsfile khusus di /buildsubdirektori dengan sesuatu seperti konten berikut (lihat keterangan di bawah):

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

File di atas .targetsakan disuntikkan pada instalasi paket NuGet dalam file proyek target dan bertanggung jawab untuk menyalin pustaka asli ke direktori output.

  • <AvailableItemName Include="NativeBinary" /> menambahkan item baru "Build Action" untuk proyek (yang juga tersedia di dropdown "Build Action" di dalam Visual Studio).

  • <NativeBinary Include="...menambahkan pustaka asli yang ditempatkan di /build/x86proyek saat ini dan membuatnya dapat diakses oleh target kustom yang menyalin file-file itu ke direktori output.

  • <TargetPath>x86</TargetPath>menambahkan custom metadata ke file dan memberi tahu target kustom untuk menyalin file asli ke x86subdirektori dari direktori output aktual.

  • The <PrepareForRunDependsOn ...blok menambahkan target kustom ke daftar target membangun tergantung pada, lihat Microsoft.Common.targets file untuk rincian.

  • Target kustom CopyNativeBinaries,, berisi dua tugas salin. Yang pertama bertanggung jawab untuk menyalin *.dl_file apa pun ke direktori output sambil mengubah ekstensi mereka kembali ke aslinya *.dll. Yang kedua hanya menyalin sisanya (misalnya *.pdbfile apa saja ) ke lokasi yang sama. Ini dapat diganti dengan satu tugas salin dan skrip install.ps1 yang harus mengganti nama semua *.dl_file *.dllselama instalasi paket.

Namun, solusi ini masih tidak akan menyalin binari asli ke direktori keluaran proyek lain yang merujuk pada yang awalnya termasuk paket NuGet. Anda masih harus merujuk paket NuGet di proyek "final" Anda juga.

buygrush
sumber
4
" Namun, solusi ini masih tidak akan menyalin binari asli ke direktori keluaran proyek lain yang merujuk pada yang awalnya menyertakan paket NuGet. Anda masih harus merujuk paket NuGet dalam proyek" final "Anda juga. " Ini adalah tunjukkan stopper untukku. Biasanya berarti Anda perlu menambahkan paket nuget ke beberapa proyek (seperti tes unit) jika tidak Anda akan DllNotFoundExceptiondibuang.
kjbartel
2
agak drastis untuk mengganti nama file dll hanya karena peringatan itu.
Anda dapat menghapus peringatan dengan menambahkan <NoWarn>NU5100</NoWarn>ke file proyek Anda
Florian Koch
29

Berikut adalah alternatif yang menggunakan .targetsuntuk menyuntikkan DLL asli di proyek dengan properti berikut.

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

Manfaat utama dari teknik ini adalah bahwa DLL asli disalin ke bin/folder proyek tergantung secara transitif.

Lihat tata letak .nuspecfile:

Pengambilan layar Explorer Paket NuGet

Ini .targetsfile tersebut:

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

Ini memasukkan MyNativeLib.dllseolah-olah itu adalah bagian dari proyek asli (tapi anehnya file tersebut tidak terlihat di Visual Studio).

Perhatikan <Link>elemen yang menetapkan nama file tujuan di bin/folder.

Benoit Blanchon
sumber
Bekerja memperlakukan dengan beberapa file .bat dan .ps1 yang harus saya sertakan sebagai bagian dari layanan Azure saya - terima kasih :)
Zhaph - Ben Duguid
"(Tapi anehnya file tersebut tidak terlihat di Visual Studio)." - file proyek diurai oleh VS sendiri AFAIK, sehingga item yang ditambahkan dalam file .target eksternal (atau yang dibuat secara dinamis dalam eksekusi target) tidak ditampilkan.
kkm
Bagaimana ini berbeda dengan jawaban sebelumnya selain mengubah dari Contentke None?
kjbartel
3
wow kamu cepat. Lagi pula, jika Anda memilih untuk melakukannya, Anda setidaknya bisa bertanya 'bagaimana ini berbeda dari jawaban saya'. bahwa imo akan lebih adil daripada mengedit pertanyaan asli, menjawabnya sendiri dan kemudian mempromosikan jawaban Anda dalam komentar orang lain. belum lagi saya secara pribadi menyukai jawaban khusus ini lebih baik daripada jawaban Anda - ringkas, to the point, dan lebih mudah dibaca
Maksim Satsikau
3
@MaksimSatsikau Anda mungkin ingin melihat riwayatnya. Saya mengedit pertanyaan untuk membuatnya lebih jelas, lalu menjawab pertanyaan itu. Jawaban ini datang beberapa minggu kemudian dan secara efektif merupakan salinan. Maaf jika saya menemukan itu kasar.
kjbartel
19

Jika ada orang lain yang tersandung di ini.

Nama .targetsfile HARUS sama dengan ID Paket NuGet

Apa pun tidak akan berhasil.

Kredit pergi ke: https://sushihangover.github.io/nuget-and-msbuild-targets/

Saya harus membaca lebih teliti seperti yang sebenarnya dicatat di sini. Butuh waktu lama bagi saya ..

Tambahkan kebiasaan <PackageName>.targets

DirtyLittleHelper
sumber
3
Anda menghemat seluruh hari saya!
zheng yu
1
Anda memperbaiki masalah selama seminggu dengan sesuatu yang lain. Terima kasih untuk Anda dan halaman github itu.
Glenn Watson
13

Agak terlambat tapi saya sudah membuat paket nuget untuk itu.

Idenya adalah untuk memiliki folder khusus tambahan dalam paket nuget Anda. Saya yakin Anda sudah tahu Lib dan Konten. Paket nuget yang saya buat mencari Folder bernama Output dan akan menyalin semua yang ada di sana ke folder keluaran proyek.

Satu-satunya hal yang harus Anda lakukan adalah menambahkan ketergantungan nuget ke paket http://www.nuget.org/packages/Baseclass.Contrib.Nuget.Output/

Saya sudah menulis posting blog tentang hal itu: http://www.baseclass.ch/blog/Lists/Beitraege/Post.aspx?ID=6&mobile=0

Daniel Romero
sumber
Itu luar biasa! Namun, ini hanya berfungsi di proyek saat ini. Jika proyek adalah "Perpustakaan Kelas" dan Anda ingin menambahkan sebagai ketergantungan ke "Aplikasi Web" untuk misalnya, DLL tidak akan dibangun dalam aplikasi web! "Perbaikan cepat" saya adalah: membuat NuGet untuk perpustakaan Anda, dan mendaftar ke Perpustakaan Kelas, dan membuat Nuget lain untuk dependensi (dll dalam hal ini) dan berlaku untuk Aplikasi Web. Ada solusi terbaik untuk ini?
Wagner Leonardi
Anda tampaknya telah membuat proyek ini hanya untuk .NET 4.0 (Windows). Apakah Anda berencana untuk memperbaruinya untuk mendukung perpustakaan kelas portabel juga?
Ani
1

Ada solusi C # murni yang saya temukan agak mudah digunakan dan saya tidak perlu repot dengan keterbatasan NuGet. Ikuti langkah ini:

Sertakan pustaka asli di proyek Anda dan setel properti Build Action-nya ke Embedded Resource.

Rekatkan kode berikut ke dalam kelas tempat Anda PInvoke perpustakaan ini.

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

Panggil metode ini dari konstruktor statis seperti berikut ini UnpackNativeLibrary("win32");dan itu akan membongkar pustaka ke disk tepat sebelum Anda membutuhkannya. Tentu saja, Anda perlu memastikan bahwa Anda memiliki izin menulis ke bagian disk tersebut.

Ondrej Janacek
sumber
1

Ini adalah pertanyaan lama, tetapi saya memiliki masalah yang sama sekarang, dan saya menemukan perubahan haluan yang sedikit rumit tetapi sangat sederhana dan efektif: buat di folder Konten standar Nuget struktur berikut dengan satu subfolder untuk setiap konfigurasi:

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

Saat Anda mengemas file nuspec, Anda akan menerima pesan berikut untuk setiap pustaka asli di folder Debug dan Rilis:

Masalah: Perakitan di luar folder lib. Deskripsi: Rakitan 'Konten \ Bin \ Debug \ ??????. Dll' tidak ada di dalam folder 'lib' dan karenanya tidak akan ditambahkan sebagai referensi ketika paket diinstal ke proyek. Solusi: Pindahkan ke folder 'lib' jika harus dirujuk.

Kami tidak memerlukan "solusi" seperti itu karena ini hanya tujuan kami: bahwa perpustakaan asli tidak ditambahkan sebagai rujukan NET Assemblies.

Keuntungannya adalah:

  1. Solusi sederhana tanpa skrip rumit dengan efek aneh yang sulit diatur ulang saat penghapusan paket.
  2. Nuget mengelola perpustakaan asli sebagai konten lain ketika menginstal dan menghapus instalan.

Kerugiannya adalah:

  1. Anda memerlukan folder untuk setiap konfigurasi (tetapi biasanya hanya ada dua: Debug dan Rilis, dan jika Anda memiliki konten lain yang harus diinstal di setiap folder konfigurasi, ini adalah salah satu cara untuk pergi)
  2. Pustaka asli harus diduplikasi di setiap folder konfigurasi (tetapi jika Anda memiliki versi pustaka asli yang berbeda untuk setiap konfigurasi, ini adalah cara yang baik untuk pergi)
  3. Peringatan untuk setiap dll asli di setiap folder (tapi seperti yang saya katakan, peringatan itu dikeluarkan untuk pembuat paket pada waktu paket, bukan kepada pengguna paket pada waktu instalasi VS)
SERWare
sumber
0

Saya tidak bisa menyelesaikan masalah Anda, tetapi saya bisa memberikan saran.

Persyaratan utama Anda adalah: "Dan jangan mendaftar secara otomatis referensi" .....

Jadi, Anda harus terbiasa dengan "item solusi"

Lihat referensi di sini:

Menambahkan item tingkat solusi dalam paket NuGet

Anda harus menulis beberapa voodoo PowerShell untuk mendapatkan salinan dll asli Anda ke dalam rumahnya (sekali lagi, karena Anda TIDAK ingin voodoo auto-add-referensi menyala)

Ini adalah file ps1 yang saya tulis ..... untuk meletakkan file di folder referensi pihak ketiga.

Ada cukup banyak di sana bagi Anda untuk mengetahui cara menyalin dll asli Anda ke beberapa "rumah" ... tanpa harus mulai dari awal.

Sekali lagi, ini bukan serangan langsung, tetapi lebih baik daripada tidak sama sekali.

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" 
granadaCoder
sumber
-2

Masukkan itu adalah folder konten

perintah nuget pack [projfile].csprojakan melakukannya untuk Anda secara otomatis jika Anda akan menandai file sebagai konten.

kemudian edit file proyek seperti yang disebutkan di sini menambahkan elemen ItemGroup & NativeLibs & None

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

bekerja untukku

Sharon Salmon
sumber