【问题标题】:Why is there a need for a separate item in my MSBuild file?为什么我的 MSBuild 文件中需要单独的项目?
【发布时间】:2017-03-19 04:51:09
【问题描述】:

有很多文章(如thisthis)展示了如何添加要发布的文件,他们都说要在发布配置文件(.pubxml)中添加这样的内容:

<Target Name="CustomCollectFiles">
  <ItemGroup>
    <_CustomFiles Include="..\Extra Files\**\*" />
    <FilesForPackagingFromProject  Include="%(_CustomFiles.Identity)">
      <DestinationRelativePath>Extra Files\%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>
    </FilesForPackagingFromProject>
  </ItemGroup>
</Target>

为什么需要新的_CustomFiles 项目?为什么不简单地&lt;FilesForPackagingFromProject Include="..\Extra Files\**\*"&gt;?我试过了,由于某种原因,这会导致项目中的每个文件最终都在部署的 Extra Files 文件夹中。有人可以解释一下这种行为吗?

【问题讨论】:

    标签: visual-studio msbuild


    【解决方案1】:

    既然你问为什么这是必需的,我将不得不深入研究这段代码的含义来解释你所看到的。 &lt;Message /&gt;是我们的朋友!

    %的含义

    让我们先看看 % 在&lt;Message&gt; 任务中的使用是什么意思:

    <ItemGroup>
      <_CustomFiles Include="..\Extra Files\**\*" />
    </ItemGroup>
    
    <Message Text="File: %(_CustomFiles.Identity)" />
    

    当你运行它时,你会得到以下输出:

    File: ..\Extra Files\file1.txt
    File: ..\Extra Files\file2.txt
    File: ..\Extra Files\file3.txt
    ...
    File: ..\Extra Files\etc.txt
    

    基本上,Message 任务为项目组中的每个项目运行一次,因为我们使用了 %。

    项目组中有什么?

    在我们对项目组进行任何更改之前,让我们先看看它。当此任务开始时,FilesForPackagingFromProject 中已经包含所有文件,以及各种元数据属性,包括 DestinationRelativePath。让我们通过将这个添加到我们的任务中来查看它:

    <Message Text="File: %(FilesForPackagingFromProject.Identity) -> %(FilesForPackagingFromProject.DestinationRelativePath)" />
    

    这个输出:

    File: ..\obj\TempBuildDir\PrecompiledApp.config -> PrecompiledApp.config
    File: ..\obj\TempBuildDir\Web.config -> Web.config
    File: ..\obj\TempBuildDir\App_Themes\theme.css -> App_Themes\theme.css
    ...
    

    重要的是要意识到这个项目组一开始就不是空的。您正在尝试向其添加项。

    工作代码

    当你在一个元素中有子元素时,它们会在每次迭代中应用一次,所以现在让我们看看工作代码:

    <FilesForPackagingFromProject Include="%(_CustomFiles.Identity)">
      <DestinationRelativePath>Extra Files\%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>
    </FilesForPackagingFromProject>
    
    <Message Text="File: %(FilesForPackagingFromProject.Identity) -> %(FilesForPackagingFromProject.DestinationRelativePath)" />
    

    对于_CustomFiles 中的每个项目,我们将其包含在FilesForPackagingFromProject 项目组中,并将DestinationRelativePath 元数据属性设置为适当 RecursiveDir/Filename 值 - 基本上是适用于正在查看的当前元素的元素。让我们看看这个输出:

    File: ..\obj\TempBuildDir\PrecompiledApp.config -> PrecompiledApp.config
    File: ..\obj\TempBuildDir\Web.config -> Web.config
    File: ..\obj\TempBuildDir\App_Themes\theme.css -> App_Themes\theme.css
    ...
    File: ..\Extra Files\file1.txt -> Extra Files\file1.txt
    File: ..\Extra Files\file2.txt -> Extra Files\file2.txt
    File: ..\Extra Files\file3.txt -> Extra Files\file3.txt
    ...
    File: ..\Extra Files\etc.txt -> Extra Files\etc.txt
    

    只包含一个文件

    如果你只想包含一个文件,你可以这样做:

    <FilesForPackagingFromProject Include="..\Extra Files\file1.txt">
      <DestinationRelativePath>Extra Files\file1.txt</DestinationRelativePath>
    </FilesForPackagingFromProject>
    

    这没有% 可以在任何地方扩展,所以它完全符合您的预期:它在输出中包含一个文件。

    损坏的代码

    现在让我们尝试包含一个文件,但不要硬编码路径,而是使用原始代码中的 % 表达式:

    <FilesForPackagingFromProject Include="..\Extra Files\file1.txt">
      <DestinationRelativePath>Extra Files\%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>
    </FilesForPackagingFromProject>
    
    <Message Text="File: %(FilesForPackagingFromProject.Identity) -> %(FilesForPackagingFromProject.DestinationRelativePath)" />
    

    这里有%,所以东西被扩展了,但是因为在项目组元素中没有%,所以扩展的工作方式不同,东西变成了梨形:

    File: ..\obj\TempBuildDir\PrecompiledApp.config -> PrecompiledApp.config
    File: ..\obj\TempBuildDir\Web.config -> Web.config
    File: ..\obj\TempBuildDir\App_Themes\theme.css -> App_Themes\theme.css
    ...
    File: ..\Extra Files\file1.txt -> Extra Files\PrecompiledApp.config
    File: ..\Extra Files\file1.txt -> Extra Files\Web.config
    File: ..\Extra Files\file1.txt -> Extra Files\theme.css
    

    因此,它不会将file1.txt 添加到项目组一次,而是遍历整个集合并为其中已有的每个文件添加一次file1.txtRecursiveDir 未在此上下文中设置,而 Filename/Extension 是组中每个文件的原始文件名。

    希望您现在可以看到,这将为您的整个部署中的每个文件创建一个文件,但在一个扁平树中,值得注意的是,内容将是 file1.txt 的内容,而不是原始文件。

    当您包含通配符而不是仅包含一个文件时,与通配符匹配的每个文件都会发生相同的情况。

    如何解决这个问题

    坚持使用%(_CustomFiles) 修复。希望您现在会明白为什么它是必要的以及它是如何做的。我相信这就是你应该这样做的方式:这里是 another question 关于它的答案,推荐这种方法。

    【讨论】:

      猜你喜欢
      • 2023-03-15
      • 2017-01-18
      • 1970-01-01
      • 2013-02-02
      • 2011-10-20
      • 1970-01-01
      相关资源
      最近更新 更多