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

Alo*_*low 112 nuget nuget-package nuget-spec

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

我的问题是:

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

kjb*_*tel 119

使用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>
Run Code Online (Sandbox Code Playgroud)

将目标文件build与所需的本机库一起添加到nuget包的目录中.目标文件将包括dll目录的所有子目录中的所有文件build.因此,要添加x86x64通过使用本机库的版本Any CPU管理组件,你最终会得到类似以下的目录结构:

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

同样x86x64目录将在项目的输出目录生成时创建.如果你不需要子目录,则**%(RecursiveDir)可以被删除,而不是包括在所要求的文件build直接目录.其他所需的内容文件也可以以相同的方式添加.

None在Visual Studio中打开时,在目标文件中添加的文件将不会显示在项目中.如果你想知道我为什么不使用Content在nupkg文件夹,那是因为没有办法来设置CopyToOutputDirectory元素,而无需使用PowerShell脚本(这只会在Visual Studio中运行,而不是在命令提示符下,在构建服务器或其他IDE,并且在project.json/xproj DNX项目中不受支持),我更喜欢使用Link文件而不是项目中的文件的附加副本.

更新: 虽然这也应该可以使用,Content而不是None看起来msbuild中有一个错误,所以文件不会被复制到引用项目多个步骤被删除(例如proj1 - > proj2 - > proj3,proj3将无法获取文件来自proj1的NuGet包,但是proj2会).

  • 先生,你真是个天才!奇迹般有效。谢谢。 (3认同)
  • @SuperJMN那里有通配符.你没注意到`**\*.dll`吗?那就是复制所有目录中的所有`.dll`文件.您可以轻松地执行`**\*.*`来复制整个目录树. (2认同)
  • 真的吗?我不敢相信这是 NuGet 社区 5 年来最好的解决方案 (2认同)
  • @kjbartel 我实际上发现了这个问题 - 我在 .targets 文件的这一行中为资源指定了相同的名称: `&lt;NativeLibs Include="$(MSBuildThisFileDirectory)**\*.dll" /&gt;` 这导致了冲突。相反,您必须在不同的 nuget 包中给出不同的名称。例如:“&lt;LibraryANativeLibs Include=...&gt;”、“&lt;LibraryBNativeLibs Include=...&gt;” (2认同)

buy*_*ush 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>
    
    Run Code Online (Sandbox Code Playgroud)

上述.targets文件将注入目标项目文件中NuGet包的安装,并负责将本机库复制到输出目录.

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

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

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

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

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

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

  • "_但是,这个解决方案仍然不会将原生二进制文件复制到引用最初包含NuGet包的项目的另一个项目的输出目录.你仍然需要在"最终"项目中引用NuGet包._"这是对我来说是个噱头.它通常意味着你需要将nuget包添加到多个项目(例如单元测试),否则你会抛出`DllNotFoundException`. (4认同)
  • 由于警告,重命名文件等有点激烈. (2认同)

Ben*_*hon 27

下面是使用替代.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>
Run Code Online (Sandbox Code Playgroud)

这将插入MyNativeLib.dll原来项目的一部分(但奇怪的是文件在Visual Studio中不可见).

请注意在<Link>文件bin/夹中设置目标文件名的元素.

  • 哇你很快 无论如何,如果你选择这样做,你至少可以问'这与我的答案有什么不同'.imo比编辑原始问题更公平,自己回答,然后在其他人的评论中推广你的回答.更不用说我个人比你更喜欢这个特别的答案了 - 它简洁明了,更容易阅读 (3认同)
  • @MaksimSatsikau你可能想看看历史.我编辑了问题以使其更清楚,然后回答了问题.这个答案在几周之后出现,实际上是一个副本.对不起,如果我觉得粗鲁. (2认同)

小智 17

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

.targets文件名必须等于NuGet包标识

别的什么都不行.

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

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

添加自定义 <PackageName>.targets

  • 你节省了我的一整天! (3认同)

小智 13

这有点晚了,但我为此创建了一个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


gra*_*der 0

我无法解决你的具体问题,但我可以给你一个建议。

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

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

请参阅此处的参考:

在 NuGet 包中添加解决方案级项目

您必须编写一些 powershell voodoo 才能将本机 dll 的副本放入其主目录(同样,因为您不希望自动添加引用 voodoo 触发)

这是我编写的 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" 
Run Code Online (Sandbox Code Playgroud)