在NuGet包中将内容文件设置为“复制本地:始终”

67

我通过在后期构建事件中运行此命令从一个项目生成 NuGet 包。变量 %conf% 被设置为正确的配置(debug 或 release),%1 代表项目名称(例如,“MyCompany.MyProject”)。

nuget pack -Prop Configuration=%conf% "%1.csproj" -exclude *.sql -IncludeReferencedProjects

这个软件包仅供我们自己使用,不会在 nuget 上发布。它只存在于我们的私有代码库中。

在项目中有一个文件被设置为 生成操作: 内容拷贝到本地: 总是。(我的 Visual Studio 是法语版,所以我不能100%确定翻译是否准确)。让我们把它命名为 importantfile.xml

在生成的软件包中,最终得到了这样一种结构:

- content
    - importantfile.xml
- lib
    -net45 (.NetFramework,Version=v4.5)
        -MyCompany.MyProject.dll

没问题,我希望importantfile.xml能够随着程序包一起部署,毕竟这个文件很重要!

当我把程序包安装到另一个项目时,importantfile.xml会被部署在项目的根目录下。那很好。但它没有设置为copy local: always

我需要在安装程序包的这个项目中将importantfile.xml设置为copy local: always

如何实现?

注:

我可以在安装包后立即将copy local: always设置在文件上,这没什么大不了的。如果以后更新程序包时能保留此属性,我可以接受这种方式,但事实并非如此。更新程序包时,copy local会被重置为never(如此处所述)。

项目文件夹中有一个nuspec文件,以下是它的内容:

<?xml version="1.0"?>
<package >
  <metadata>
    <id>$id$</id>
    <version>$version$</version>
    <title>$title$</title>
    <authors>$author$</authors>
    <owners>$author$</owners>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>$description$</description>
    <copyright>Copyright 2014</copyright>
    <tags>some random tags</tags>
  </metadata>
</package>

为什么你想一直使用CopyToOutputDirectory呢?在我的经验中,始终这个选项从来都不是正确的选择;它会破坏增量构建,并导致每次都需要重新构建。 - Tim Sparkles
5个回答

165

除了使用 PowerShell 脚本之外,另一种方法是使用与软件包 ID 相同名称的 MSBuild targets 或 props 文件

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <None Include="$(MSBuildThisFileDirectory)importantfile.xml">
      <Link>importantfile.xml</Link>
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
  </ItemGroup>
</Project>

在 nuspec 文件中,不要将必需的文件添加到 Content 目录中,而是将它们和 targets 文件一起添加到 Build 目录中。

  • Build
    • importantfile.xml
    • MyPackage.targets
  • lib
    • net45
      • MyAssembly.dll

如果您需要为不同的架构提供不同的内容,则还可以在 Build 下添加架构文件夹,并带有各自的 targets 文件。

使用 targets 文件而不是带有 NuGet Content 目录的 PowerShell 脚本的好处:

  • 在 Visual Studio 项目中不会显示所需的内容文件
  • 内容文件链接到每个引用 NuGet 包的项目目录中,而不是复制到其中(防止出现多个副本,并使行为与从 NuGet 包中获取的程序集/库相同)
  • PowerShell 脚本仅适用于 Visual Studio 并且在从命令行运行 NuGet 时不运行(生成服务器、其他 IDE 和其他 OS),这种方法将在任何地方都有效
  • PowerShell 安装脚本在 NuGet 3.x project.json 系统中不受支持

17
这个解决方案比被接受的答案好得多。 - Peuczynski
4
我喜欢这个解决方案,并在我的场景中使用它。但是需要注意的一点是,如果你希望用户可能要修改 importantfile.xml,那么这个解决方案并不适合你——该文件仅存在于 packages 文件夹中。然而,如果你不希望用户修改此文件,那么这是一个很好的解决方案 :) - RB.
2
我在使用Nuget 2.12.0.817和VS2013时无法使其工作,直到我将目标文件重命名为与包ID匹配。这种命名约定在Nuget 2.5发布说明中有说明 https://docs.nuget.org/ndocs/release-notes/nuget-2.5 - Joe
1
这对我有用。使用Nuget Explorer添加.target文件来处理Joe的观点,它按预期工作了。 - Farrukh Waheed
1
如果你不太明白需要做什么(就像我一样),请参考我找到的这个答案,它提供了基于此答案的逐步说明。 - Sal
显示剩余9条评论

41

我知道你们已经有一个可行的解决方案,但是对我没用,所以我想分享一下我从 NLog.config NuGet package 的 install.ps1 文件中获取的内容(github 地址在 这里)。

注意:这不是我的代码,这只是来自 NLog.config nuget 包中的 install.ps1 内容,我只是分享了这个知识。

这对我来说似乎更加直接,我希望能帮助其他可能遇到此问题的人。

你可以在 BuildAction 这里 找到可接受的 int 值,以及在 这里 找到 CopyToOutputDirectory 可接受的值。

如果链接再次失效,请点击enter image description here

字段 prjBuildActionCompile 1
文件已编译。

prjBuildActionContent 2
将该文件包含在 Content 项目输出组中(请参阅部署应用程序、服务和组件)。

prjBuildActionEmbeddedResource 3
将该文件作为资源包含在主生成的程序集或卫星程序集中。

prjBuildActionNone 0
不执行任何操作。

param($installPath, $toolsPath, $package, $project)

$configItem = $project.ProjectItems.Item("NLog.config")

# set 'Copy To Output Directory' to 'Copy if newer'
$copyToOutput = $configItem.Properties.Item("CopyToOutputDirectory")

# Copy Always Always copyToOutput.Value = 1
# Copy if Newer copyToOutput.Value = 2  
$copyToOutput.Value = 2

# set 'Build Action' to 'Content'
$buildAction = $configItem.Properties.Item("BuildAction")
$buildAction.Value = 2

1
你值得一声赞!已添加。 - Rhyous

7
我已经创建了一个程序,可以将我的构建文件夹中的文件复制到输出文件夹(bin/debug或bin/release)。对我来说运作得非常好。
Nuspec文件:
<package>
  <files>
    <file src="\bin\Release\*.dll" target="lib" />
    <file src="\bin\Release\x64\*.dll" target="build\x64" />
    <file src="\bin\Release\x86\*.dll" target="build\x86" />
    <file src="MyProject.targets" target="build\" />    
  </files>
</package>

MyProject.targets

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

2
你可以使用PowerShell和NuGet提供的Install.ps1 hook。
请参阅文档
通过PowerShell,你需要“搜索”包含你的importantfile.xml属性的内容元素。当脚本找到它时,它必须将<CopyToOutputDirectory>Always</CopyToOutputDirectory>作为子元素添加进去。
    <Content Include="importantfile.xml">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>

您可以在此处找到一些PowerShell代码片段(链接)。只需查看.ps1文件即可。

您可以尝试以下操作(未经测试)。该文件必须命名为Install.ps1并复制到tools文件夹中:

param($installPath, $toolsPath, $package, $project)

# Load project XML.
$doc = New-Object System.Xml.XmlDocument
$doc.Load($project.FullName)
$namespace = 'http://schemas.microsoft.com/developer/msbuild/2003'

# Find the node containing the file. The tag "Content" may be replace by "None" depending of the case, check your .csproj file.
$xmlNode = Select-Xml "//msb:Project/msb:ItemGroup/msb:Content[@Include='importantfile.xml']" $doc -Namespace @{msb = $namespace}


#check if the node exists.
if($xmlNode -ne $null)
{
    $nodeName = "CopyToOutputDirectory"

    #Check if the property already exists, just in case.
    $property = $xmlNode.Node.SelectSingleNode($nodeName)
    if($property -eq $null)
    {
        $property = $doc.CreateElement($nodeName, $namespace)
        $property.AppendChild($doc.CreateTextNode("Always"))
        $xmlNode.Node.AppendChild($property)

        # Save changes.
        $doc.Save($project.FullName)
    }
}

卸载包时,您还应检查是否完全删除了所有内容。

Jonhhy5的注释

通过update-package更新包时,Visual Studio警告项目在“环境”外被修改。这是由$doc.Save($project.FullName)引起的。如果在命令完全终止之前单击重新加载,有时会导致错误。诀窍是将对话框保留在那里,直到进程完成,然后重新加载项目。


我看过这份文档,发现它非常不完整。那正是我试图做的,虽然你的代码比我的更加简洁。 - Johnny5
真的吗?你缺少什么? - timmkrause
1
你的意思是文档中缺少了吗?那么,最起码应该记录一下由 param($installPath, $toolsPath, $package, $project) 初始化的变量。虽然问题中的情况比较简单,但如果我能够搜索 $package 中的内容文件就更好了,但是关于提供这个变量的信息却没有任何说明。 - Johnny5
真实故事。但是通过Select-Xml查找内容文件并不能让你感到高兴? - timmkrause
3
请注意,在V3中,Install.ps1(和Uninstall.ps1)的支持已经被移除。Init.ps1仍然得到支持。 - Sam Rueby
显示剩余4条评论

0

我写了一个小工具叫做NuGetLib,可以在构建后自动将文件添加到NuGet包中。

  1. 创建一个名为tools的文件夹,并放入你的Install.ps1脚本
  2. 构建你的nugetPackage
  3. 将tools文件夹添加到已构建的nugetPackage

https://dev59.com/7Kbja4cB1Zd3GeqPgXGh#47134733


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接