Додайте рідні файли з пакету NuGet до каталогу виводу проекту


126

Я намагаюся створити пакет NuGet для складання .Net, який робить піктограму для рідного win32 dll. Мені потрібно упакувати як збірку, так і нативну dll, при цьому збірка додається до посилань на проект (немає проблеми в цій частині), і нативний dll повинен бути скопійований у каталог вихідних проектів або в інший відносний каталог.

Мої запитання:

  1. Як я спакую рідний dll без візуальної студії, намагаючись додати його до списку посилань?
  2. Чи потрібно писати install.ps1 для копіювання рідного DLL? Якщо так, як я можу отримати доступ до вмісту пакета для його копіювання?

1
Існує підтримка бібліотек, що виконуються під час виконання / архітектури, але документація щодо функцій відсутня і вона, схоже, є специфічною для UWP. docs.microsoft.com/en-us/nuget/create-packages/…
Wouter

Відповіді:


131

Використання Copyцілі у файлі цілі для копіювання потрібних бібліотек не копіює ці файли до інших проектів, на які посилається проект, в результаті чого з'являється a DllNotFoundException. Це можна зробити за допомогою набагато більш простого файлу цілей, але, використовуючи Noneелемент, оскільки MSBuild буде копіювати всі Noneфайли на посилання на проекти.

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

Додайте файл-ціль до buildкаталогу пакунків nuget разом із необхідними власними бібліотеками. Файл цілі буде містити всі dllфайли у всіх дочірніх каталогах buildкаталогу. Отже, щоб додати x86та x64версію рідної бібліотеки, що використовується Any CPUкерованою збіркою, ви отримаєте структуру каталогу, схожу на наступне:

  • будувати
    • x86
      • NativeLib.dll
      • NativeLibDependency.dll
    • x64
      • NativeLib.dll
      • NativeLibDependency.dll
    • MyNugetPackageID.targets
  • ліб
    • net40
      • ManagedAssembly.dll

Ті самі x86та x64каталоги будуть створені у вихідному каталозі проекту, коли він буде створений. Якщо підкаталоги вам не потрібні, тоді **і ви %(RecursiveDir)можете видалити, і натомість включити потрібні файли buildбезпосередньо в каталог. Інші необхідні файли вмісту також можна додавати таким же чином.

Файли, додані як Noneу файлі цілі, не відображатимуться в проекті при відкритті в Visual Studio. Якщо вам цікаво, чому я не використовую Contentпапку в nupkg, це тому, що немає способу встановити CopyToOutputDirectoryелемент без використання скрипта powerhell (який буде запускатися тільки в Visual Studio, а не в командному рядку, на серверах побудови або в інших IDE і не підтримується в проектах DNX Project.json / xproj ), і я вважаю за краще використовувати Linkфайли a, а не мати додаткову копію файлів у проекті.

Оновлення: Хоча це також має працювати, Contentа не Noneздається, що у msbuild є помилка, тому файли не будуть скопійовані до посилань на проекти, більш ніж один крок видалено (наприклад, proj1 -> proj2 -> proj3, proj3 не отримає файли з пакета NuGet proj1, але proj2 буде).


4
Сер, ви геній! Працює як шарм. Дякую.
MoonStom

Цікаво, чому '$(MSBuildThisFileDirectory)' != '' And HasTrailingSlash('$(MSBuildThisFileDirectory)')потрібна умова ? Я думав, що MSBuildThisFileDirectoryзавжди встановлено. Коли цього не було б?
ккм

@kkm Чесно. Я не думаю, що це потрібно. Я навіть не можу згадати, звідки я його спочатку взяв.
kjbartel

@kkm Я спочатку змінив нульовий пакет System.Data.SQLite, і, здається, я залишив це позаду, коли я видалив усі інші лайно, які вони включали. Оригінальний файл цілі .
kjbartel

2
@SuperJMN Там є шаблони. Ви не помітили **\*.dll? Це копіювання всіх .dllфайлів у всі каталоги. Ви легко **\*.*можете скопіювати ціле дерево каталогів.
kjbartel

30

Нещодавно у мене була та сама проблема, коли я намагався створити пакет EmguCV NuGet, що включає керовані збірки та не керовані спільні лірійні файли (які також потрібно було розмістити у x86підкаталозі), які потрібно було автоматично скопіювати у вихідний каталог збірки після кожної збірки .

Ось таке рішення, яке я придумав, покладається лише на NuGet і MSBuild:

  1. Помістіть керовані збірки в /libкаталог пакунку (очевидна частина) та не керовані спільні бібліотеки та пов’язані з ними файли (наприклад, пакети .pdb) у /buildпідкаталозі (як описано в документах NuGet ).

  2. Перейменуйте всі не керовані *.dllзакінчення файлів на щось інше, наприклад, *.dl_щоб запобігти застосуванню NuGet про те, що передбачувані збірки розміщуються в неправильному місці ( "Проблема: Збірка за межами папки lib." ).

  3. Додайте власний <PackageName>.targetsфайл у /buildпідкаталог з таким, як наступний вміст (див. Нижче для опису):

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

Вищевказаний .targetsфайл буде введений під час встановлення пакету NuGet у файлі цільового проекту і відповідає за копіювання рідних бібліотек у вихідний каталог.

  • <AvailableItemName Include="NativeBinary" /> додає новий проект "Зробити дію" для проекту (який також стає доступним у випадаючому меню "Дія дії" всередині Visual Studio).

  • <NativeBinary Include="...додає рідні бібліотеки, розміщені в /build/x86поточному проекті, і робить їх доступними до спеціальної цілі, яка копіює ці файли у вихідний каталог.

  • <TargetPath>x86</TargetPath>додає власні метадані до файлів і повідомляє користувальницькій цілі скопіювати рідні файли у x86підкаталог фактичного каталогу виводу.

  • <PrepareForRunDependsOn ...Блок додає призначена для користувача мета в список цілей складання залежить, см Microsoft.Common.targets файл для деталей.

  • Спеціальна мета, CopyNativeBinariesмістить два завдання з копіювання. Перший відповідає за копіювання будь-яких *.dl_файлів у вихідний каталог, змінюючи їх розширення назад до оригіналу *.dll. Другий просто копіює решту (наприклад, будь-які *.pdbфайли) в те саме місце. Це може бути замінено одним завданням копіювання та скриптом install.ps1, який повинен був перейменувати всі *.dl_файли *.dllпід час встановлення пакета.

Однак це рішення все ще не скопіює нативні бінарні файли у вихідний каталог іншого проекту, що посилається на той, який спочатку включає пакет NuGet. Ви все ще повинні посилатись на пакет NuGet у своєму "остаточному" проекті.


4
" Однак це рішення все ще не скопіює нативні бінарні файли до каталогу виводу іншого проекту, що посилається на той, який спочатку включає пакет NuGet. Ви все ще повинні посилатись на пакунок NuGet у вашому" остаточному "проекті. " Це показати пробку для мене. Зазвичай це означає, що вам потрібно додати пакет «nuget» до кількох проектів (наприклад, тестування одиниць), інакше вас DllNotFoundExceptionкинуть.
kjbartel

2
трохи драматично перейменувати файли тощо лише через попередження.

ви можете видалити попередження, додавши <NoWarn>NU5100</NoWarn>у файл свого проекту
Флоріан Кох

28

Ось альтернатива, яка використовує .targetsдля введення рідної DLL в проект із наступними властивостями.

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

Основна перевага цієї методики полягає в тому, що рідна DLL копіюється в bin/папку залежних проектів .

Дивіться макет .nuspecфайлу:

Захоплення екрана NuGet Package Explorer

Ось .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>

Це вставляє MyNativeLib.dllтак, ніби це було частиною оригінального проекту (але цікаво, що файл не видно у Visual Studio).

Зауважте <Link>елемент, який встановлює ім'я файлу призначення у bin/папці.


Працює частування з деякими файлами .bat та .ps1, які мені потрібно включити як частину сервісу Azure - спасибі :)
Джаф - Бен Дюгід

"(Але цікаво, що файл не видно у Visual Studio)." - файли проекту аналізуються самим VS AFAIK, тому елементи, додані у зовнішні файли .target (або ті, що динамічно створюються при цільовому виконанні), не відображаються.
ккм

Чим це відрізняється від інших попередніх відповідей, окрім зміни від « Contentдо» None?
kjbartel

3
вау, ти швидкий. у будь-якому випадку, якщо ви вирішили це зробити, ви могли б принаймні запитати «чим це відрізняється від моєї відповіді». що imo було б справедливішим, ніж редагувати оригінальне запитання, відповісти на нього самостійно, а потім просувати свою відповідь в коментарях інших людей. не кажучи вже про те, що мені особисто подобається ця конкретна відповідь краще, ніж ваша - це лаконічно, до речі і легше для читання
Максим Сацікау,

3
@MaksimSatsikau Ви можете подивитися на історію. Я відредагував це питання, щоб зробити його більш зрозумілим, потім відповів на питання. Ця відповідь надійшла через кілька тижнів і фактично була копією. Вибачте, якщо я виявив це грубість.
kjbartel

19

Якщо хтось інший натикається на це.

Ім'я .targetsфайлу повинно бути рівним ідентифікатору пакета NuGet

Нічого іншого не буде працювати.

Кредити йдуть на: https://sushihangover.github.io/nuget-and-msbuild-targets/

Я мав би прочитати більш ретельно, як це насправді було зазначено тут. Взяв мене віками ..

Додайте звичай <PackageName>.targets


3
ти врятуєш мій цілий день!
Чжен Юнь

1
Ти вирішив тижневу проблему чимось іншим. Дякуємо вам і тій сторінці github.
Гленн Уотсон

13

Це трохи пізно, але я створив для цього специфічну специфіку пакету.

Ідея полягає у наявності додаткової спеціальної папки у вашому пакунку від nuget. Я впевнений, що ви вже знаєте Lib and Content. Створений мною пакунок шукає папку з назвою Output і скопіює все, що є, у папку виводу проектів.

Єдине, що вам потрібно зробити - це додати нугову залежність до пакету http://www.nuget.org/packages/Baseclass.Contrib.Nuget.Output/

Я написав про це запис у блозі: http://www.baseclass.ch/blog/Lists/Beitraege/Post.aspx?ID=6&mobile=0


Це круто! Однак це працює лише в поточному проекті. Якщо проект є "Бібліотекою класів" і ви хочете додати як залежність до "Веб-програми", наприклад, DLL-файли не будуватимуться у веб-додатку! Моє "швидке виправлення": створити NuGet для своєї бібліотеки і застосувати до бібліотеки класів, а також створити інший Nuget для залежностей (в цьому випадку dlls) і застосувати до WebApplication. Будь-яке найкраще рішення для цього?
Вагнер Леонарді

Здається, ви створили цей проект лише для .NET 4.0 (Windows). Ви плануєте оновити його для підтримки також портативних бібліотек класів?
Ані

1

Є чисте рішення C #, яке я вважаю досить простим у використанні, і мені не доведеться турбуватися з обмеженнями NuGet. Виконайте такі дії:

Включіть у свій проект рідну бібліотеку та встановіть для неї властивість Build Action Embedded Resource.

Вставте наступний код у клас, де ви PINvoke цю бібліотеку.

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

Викличте цей метод зі статичного конструктора наступним чином, UnpackNativeLibrary("win32");і він розпакує бібліотеку на диск безпосередньо перед необхідністю. Звичайно, ви повинні бути впевнені, що у вас є дозволи на запис на цю частину диска.


1

Це старе питання, але у мене є та сама проблема, і я знайшов поворот, який є трохи хитрим, але дуже простим та ефективним: створіть у папці Nuget стандартний контент наступну структуру з однією підпапкою для кожної конфігурації:

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

Коли ви запакуєте файл nuspec, ви отримаєте таке повідомлення для кожної рідної бібліотеки у папках налагодження та випуску:

Проблема: Збірка зовнішньої папки lib. Опис: Асамблея "Вміст \ Bin \ Налагодження \ ??????. Dll" не знаходиться у папці "lib", отже, вона не буде додана як посилання, коли пакет встановлений у проект. Рішення: Перемістіть його в папку "lib", якщо на неї слід посилатися.

Нам не потрібне таке "рішення", оскільки це лише наша мета: щоб рідні бібліотеки не додавались до посилань на NET Асамблеї.

Перевагами є:

  1. Просте рішення без громіздких сценаріїв із дивними ефектами, які важко скинути при видаленні пакета.
  2. Nuget керує власними бібліотеками, як і будь-яким іншим контентом під час встановлення та видалення.

Недоліками є:

  1. Вам потрібна папка для кожної конфігурації (але зазвичай їх лише дві: Налагодження та випуск; якщо у вас є інший вміст, який повинен бути встановлений у кожній конфігураційній папці, це або шлях)
  2. Рідні бібліотеки повинні бути продубльовані у кожній папці конфігурації (але якщо у вас є різні версії нативних бібліотек для кожної конфігурації, це або шлях)
  3. Попередження для кожного нативного dll у кожній папці (але, як я вже сказав, вони попереджають видаються творцеві пакета під час упаковки, а не користувачеві пакету під час встановлення VS)

0

Я не можу вирішити вашу точну проблему, але можу дати вам пропозицію.

Ваша ключова вимога: "І не реєструвати посилання автоматично" .....

Тож вам доведеться ознайомитись із "предметами рішення"

Дивіться посилання тут:

Додавання елементів рівня рішення в пакет NuGet

Вам доведеться написати вуду, щоб отримати копію вашого рідного dll в його будинок (знову ж таки, тому що ви НЕ хочете, щоб автоматичне додавання довідки вуду запускалося)

Ось файл ps1, який я написав ..... щоб помістити файли в папку сторонніх посилань.

Там вам достатньо, щоб ви зрозуміли, як скопіювати свій рідний dll в якийсь "будинок" ... без того, щоб починати з нуля.

Знову ж таки, це не прямий удар, але краще, ніж нічого.

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

Помістіть це папка вмісту

команда nuget pack [projfile].csprojзробить це автоматично для вас, якщо ви позначите файли як вміст.

потім відредагуйте файл проекту, як згадувалося тут, додаючи елемент ItemGroup & NativeLibs & None

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

працював на мене

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.