将NuGet包中的本机文件添加到项目输出目录

时间:2013-10-20 14:50:38

标签: nuget nuget-package nuget-spec

我正在尝试为.Net程序集创建NuGet程序包,该程序集会对本机win32 dll执行pinvoke。 我需要打包程序集和本机dll,并将程序集添加到项目引用中(此部分没有问题),本机dll应该复制到项目输出目录或其他相关目录中。

我的问题是:

  1. 如果没有visual studio尝试将其添加到引用列表中,如何打包本机dll?
  2. 我是否必须编写install.ps1来复制本机dll?如果是这样,我如何访问包内容进行复制呢?

9 个答案:

答案 0 :(得分:112)

使用目标文件中的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>

将目标文件与所需的本机库一起添加到nuget包的build目录中。目标文件将包含dll目录的所有子目录中的所有build个文件。因此,要添加x86托管程序集使用的本机库的x64Any CPU版本,最终将得到类似于以下内容的目录结构:

  • 构建
    • 86
      • NativeLib.dll
      • NativeLibDependency.dll
    • 64
      • NativeLib.dll
      • NativeLibDependency.dll
    • MyNugetPackageID.targets
  • LIB
    • net40
      • ManagedAssembly.dll

构建时,将在项目的输出目录中创建相同的x86x64目录。如果您不需要子目录,则可以删除**%(RecursiveDir),而是直接在build目录中包含所需的文件。其他所需的内容文件也可以以相同的方式添加。

在Visual Studio中打开时,在目标文件中添加为None的文件不会显示在项目中。如果您想知道我为什么不在nupkg中使用Content文件夹,因为无法设置CopyToOutputDirectory元素without using a powershell script(它只能在Visual Studio中运行,而不是从命令提示符,构建服务器或其他IDE中运行,并且是not supported in project.json / xproj DNX projects)而我更喜欢对文件使用Link而不是额外的项目中文件的副本。

<强>更新 虽然这也应该与Content而不是None一起使用,但似乎msbuild中存在一个错误,因此文件不会被复制到引用项目的多个步骤被删除(例如proj1 - &gt; proj2 - &gt; proj3,proj3无法从proj1的NuGet包中获取文件,但proj2将会这样做。

答案 1 :(得分:30)

当我尝试构建EmguCV NuGet包时,我最近遇到了同样的问题,包括托管程序集和非托管共享库(也必须放在 x86 子目录中)每次构建后都必须自动复制到构建输出目录。

这是我提出的解决方案,仅依赖于NuGet和MSBuild:

  1. 将托管程序集放在程序包的 /lib 目录(显而易见的部分)和非托管的共享库和相关文件(例如.pdb软件包)中 /build 子目录(如NuGet docs中所述)。

  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>
    
  4. 上述 .targets 文件将在目标项目文件中的NuGet包安装中注入,并负责将本机库复制到输出目录。

    • <AvailableItemName Include="NativeBinary" />为项目添加了一个新项目“Build Action”(也可以在Visual Studio的“Build Action”下拉列表中找到)。

    • <NativeBinary Include="...将放置在 /build/x86 中的本机库添加到当前项目中,并使其可供自定义目标访问,该目标将这些文件复制到输出目录。 / p>

    • <TargetPath>x86</TargetPath>向文件添加自定义元数据,并告知自定义目标将本机文件复制到实际输出目录的 x86 子目录。

    • <PrepareForRunDependsOn ...块将自定义目标添加到构建所依赖的目标列表中,有关详细信息,请参阅Microsoft.Common.targets文件。

    • 自定义目标CopyNativeBinaries包含两个复制任务。第一个负责将任何 *.dl_ 文件复制到输出目录,同时将其扩展名更改回原来的 *.dll 。第二个只是将其余的(例如任何 *.pdb 文件)复制到同一位置。这可以由单个副本任务和install.ps1脚本替换,该脚本在程序包期间必须将所有 *.dl_ 文件重命名为 *.dll 安装。

    但是,此解决方案仍然不会将本机二进制文件复制到引用最初包含NuGet包的项目的另一个项目的输出目录中。您仍然需要在“最终”项目中引用NuGet包。

答案 2 :(得分:26)

以下是使用.targets 在项目中注入本机DLL的替代方法,具有以下属性。

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

此技术的主要好处是本机DLL可以传递地复制到依赖项目bin/文件夹中。

查看.nuspec文件的布局:

Screen capture of 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/文件夹中设置目标文件名。

答案 3 :(得分:12)

有点晚了,但我为此创建了一个nuget包exaclty。

我们的想法是在您的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

答案 4 :(得分:12)

如果其他人偶然发现了这一点。

.targets文件名必须等于NuGet包ID

其他任何事情都无法奏效。

积分转到: https://sushihangover.github.io/nuget-and-msbuild-targets/

我应该仔细阅读,因为这里实际上已经注意到了。花了我很多时间..

  

添加自定义<PackageName>.targets

答案 5 :(得分: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");,它将在您需要之前将库解压缩到磁盘。当然,您需要确保您对磁盘的该部分具有写入权限。

答案 6 :(得分:1)

这是一个老问题,但我现在遇到了同样的问题,我找到了一个有点棘手但又非常简单有效的转变:在Nuget标准Content文件夹中创建以下结构,每个配置都有一个子文件夹:

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

打包nuspec文件时,您将在Debug和Release文件夹中收到每个本机库的以下消息:

  

问题:lib文件夹外的程序集。描述:装配   &#39;内容\ BIN \调试\ ?????? DLL&#39。不在&#39; lib&#39;文件夹和   因此,在安装软件包时,它不会被添加为参考   进入一个项目。解决方案:将其移入&#39; lib&#39;文件夹,如果它应该   被引用。

我们不需要这样的&#34;解决方案&#34;因为这只是我们的目标:本地库不作为.NET程序集引用添加。

优点是:

  1. 简单的解决方案,没有繁琐的脚本,在解包卸载时很难重置奇怪的效果。
  2. Nuget在安装和卸载时将本地库管理为任何其他内容。
  3. 缺点是:

    1. 每个配置都需要一个文件夹(但通常只有两个:Debug和Release,如果你有其他内容必须安装在每个配置文件夹中,这就是要走的路)。
    2. 必须在每个配置文件夹中复制本机库(但如果每个配置都有不同版本的本机库,则可以采用这种方式)。
    3. 每个文件夹中每个本机dll的警告(但正如我所说,它们会在打包时向包创建者发出警告,而不是在VS安装时发送给包用户)

答案 7 :(得分:0)

我无法解决您的确切问题,但我可以给您一个建议。

您的关键要求是:“并且不要自动注册参考”.....

所以你必须熟悉“解决方案项目”

参见此处的参考资料:

Adding solution-level items in a NuGet package

你必须写一些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" 

答案 8 :(得分:-2)

把它放在内容文件夹

如果您将文件标记为内容,命令nuget pack [projfile].csproj将自动为您执行此操作。

然后编辑项目文件,如此处所述添加ItemGroup&amp; NativeLibs&amp;无元素

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

为我工作