Добавить собственные файлы из пакета NuGet в выходной каталог проекта

126

Я пытаюсь создать пакет NuGet для сборки .Net, которая выполняет вызов собственной библиотеки DLL win32. Мне нужно упаковать как сборку, так и собственную dll со сборкой, добавленной в ссылки проекта (в этой части проблем нет), а собственная dll должна быть скопирована в выходной каталог проекта или какой-либо другой относительный каталог.

Мои вопросы:

  1. Как упаковать родную dll без попытки Visual Studio добавить ее в список ссылок?
  2. Должен ли я писать install.ps1 для копирования родной dll? Если да, то как я могу получить доступ к содержимому пакета для его копирования?
AlonFStackoverflow
источник
1
Существует поддержка библиотек, специфичных для среды выполнения / архитектуры, но отсутствует документация по функциям, и, похоже, она специфична для UWP. docs.microsoft.com/en-us/nuget/create-packages/…
Wouter

Ответы:

131

Использование Copyцели в целевом файле для копирования необходимых библиотек не приведет к копированию этих файлов в другие проекты, которые ссылаются на проект, в результате чего файл 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
  • Lib
    • net40
      • ManagedAssembly.dll

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

Файлы, добавленные Noneв качестве целевого файла, не будут отображаться в проекте при открытии в Visual Studio. Если вам интересно, почему я не использую Contentпапку в nupkg, это потому, что нет возможности установить CopyToOutputDirectoryэлемент без использования сценария PowerShell (который будет запускаться только внутри Visual Studio, а не из командной строки, на серверах сборки или в других IDE и не поддерживается в проектах DNX project.json / xproj ), и я предпочитаю использовать a Linkдля файлов, а не иметь дополнительную копию файлов в проекте.

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

kjbartel
источник
4
Сэр, вы гений! Работает как шарм. Спасибо.
MoonStom
Хотите знать, почему это условие '$(MSBuildThisFileDirectory)' != '' And HasTrailingSlash('$(MSBuildThisFileDirectory)')требуется? Я думал, что MSBuildThisFileDirectoryвсегда установлен. Когда это было бы не так?
kkm
@kkm Честно. Я не думаю, что это нужно. Я даже не могу вспомнить, откуда я это взял.
kjbartel
@kkm Изначально я модифицировал пакет Nuget System.Data.SQLite и, похоже, оставил его, когда удалил все остальное дерьмо, которое они включили. Исходный файл целей .
kjbartel
2
@SuperJMN Там есть подстановочные знаки. Вы не заметили **\*.dll? Это копирует все .dllфайлы во все каталоги. Вы можете легко **\*.*скопировать все дерево каталогов.
kjbartel
30

Недавно у меня была такая же проблема, когда я пытался создать пакет NuGet EmguCV, включая как управляемые сборки, так и неуправляемые общие лирарии (которые также должны были быть помещены в 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 в своем «окончательном» проекте.

buygrush
источник
4
« Однако это решение по-прежнему не будет копировать собственные двоичные файлы в выходной каталог другого проекта, ссылающегося на тот, который изначально включает пакет NuGet. Вам все равно придется ссылаться на пакет NuGet в своем« последнем »проекте». Это покажи мне стопор. Обычно это означает, что вам нужно добавить пакет nuget в несколько проектов (например, модульные тесты), иначе вы получите DllNotFoundExceptionошибку.
kjbartel
2
немного радикально переименовывать файлы и т. д. только из-за предупреждения.
вы можете удалить предупреждение, добавив его <NoWarn>NU5100</NoWarn>в файл проекта
Флориан Кох
29

Вот альтернатива, которая использует .targetsдля внедрения собственной библиотеки DLL в проект со следующими свойствами.

  • Build action знак равно None
  • Copy to Output Directory знак равно Copy if newer

Основное преимущество этого метода заключается в том, что собственная DLL копируется в bin/папку зависимых проектов транзитивно.

См. Макет .nuspecфайла:

Снимок экрана обозревателя пакетов NuGet

Вот .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 - спасибо :)
Zhaph - Бен Дугид
«(Но любопытно, что файл не отображается в Visual Studio)». - файлы проекта анализируются самим VS AFAIK, поэтому элементы, добавленные во внешние файлы .target (или те, которые динамически создаются при целевом выполнении), не отображаются.
kkm
Чем это отличается от другого более раннего ответа, кроме изменения с Contentна None?
kjbartel
3
вау ты быстрый. в любом случае, если вы захотите это сделать, вы можете хотя бы спросить: «Чем это отличается от моего ответа». это было бы справедливее, чем редактировать исходный вопрос, отвечать на него самому и затем продвигать свой ответ в комментариях других людей. не говоря уже о том, что мне лично этот конкретный ответ нравится больше, чем ваш - он
лаконичен, по делу
3
@MaksimSatsikau Вы можете посмотреть историю. Я отредактировал вопрос, чтобы было понятнее, затем ответил на вопрос. Этот ответ пришел через пару недель и фактически стал копией. Извините, если я нашел это грубым.
kjbartel
19

Если кто еще наткнется на это.

Имя .targetsфайла ДОЛЖНО совпадать с идентификатором пакета NuGet.

Все остальное не сработает.

Кредиты идут по адресу : https://sushihangover.github.io/nuget-and-msbuild-targets/

Я должен был прочитать более внимательно, поскольку это фактически отмечено здесь. Мне потребовалось много времени ..

Добавить обычай <PackageName>.targets

DirtyLittleHelper
источник
3
ты спасешь мой день!
zheng yu
1
Вы исправили недельную проблему с чем-то другим. Спасибо вам и этой странице на github.
Гленн Уотсон
13

Немного поздно, но я создал для этого пакет nuget.

Идея состоит в том, чтобы иметь дополнительную специальную папку в вашем пакете nuget. Я уверен, что вы уже знакомы с Lib и Content. Созданный мной пакет nuget ищет папку с именем Output и копирует все, что там есть, в папку вывода проекта.

Единственное, что вам нужно сделать, это добавить зависимость nuget к пакету 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 для зависимостей (в данном случае dll) и примените к WebApplication. Есть ли лучшее решение для этого?
Вагнер Леонарди
Похоже, вы создали этот проект только для .NET 4.0 (Windows). Планируете ли вы обновить его для поддержки переносимых библиотек классов?
Ani
1

Есть чистое решение C #, которое я считаю довольно простым в использовании, и мне не нужно беспокоиться об ограничениях NuGet. Следуй этим шагам:

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

Вставьте следующий код в класс, в котором вы вызываете эту библиотеку.

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, вы получите следующее сообщение для каждой собственной библиотеки в папках Debug и Release:

Проблема: сборка вне папки lib. Описание: сборка Content \ Bin \ Debug \ ??????. Dll не находится внутри папки lib и, следовательно, не будет добавлена ​​в качестве ссылки при установке пакета в проект. Решение: переместите его в папку "lib", если на него нужно ссылаться.

Нам не нужно такое «решение», потому что это и есть наша цель: чтобы собственные библиотеки не добавлялись как ссылки на NET Assemblies.

Преимущества:

  1. Простое решение без громоздких скриптов со странными эффектами, которые сложно сбросить при удалении пакета.
  2. Nuget управляет собственными библиотеками как любым другим содержимым при установке и удалении.

К недостаткам можно отнести:

  1. Вам нужна папка для каждой конфигурации (но обычно их всего две: Debug и Release, и если у вас есть другой контент, который должен быть установлен в каждой папке конфигурации, это либо выход)
  2. Собственные библиотеки должны быть продублированы в каждой папке конфигурации (но если у вас есть разные версии собственных библиотек для каждой конфигурации, это либо вариант)
  3. Предупреждения для каждой собственной библиотеки DLL в каждой папке (но, как я уже сказал, они выдаются создателю пакета во время упаковки, а не пользователю пакета во время установки VS)
SERWare
источник
0

Я не могу решить вашу проблему, но могу дать вам совет.

Ваше ключевое требование: «И пусть он не регистрирует ссылку автоматически» .....

Так что вам придется ознакомиться с «элементами решения»

См. Ссылку здесь:

Добавление элементов уровня решения в пакет NuGet

Вам нужно будет написать немного Powershell voodoo, чтобы получить копию вашей родной 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" 
granadaCoder
источник
-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>

работал на меня

Шэрон лосось
источник