为什么我的MSBuild文件中需要单独的项目?

时间:2017-03-09 19:59:53

标签: visual-studio msbuild

有许多文章(如thisthis)展示了如何添加要发布的文件,他们都说要将这样的内容添加到发布配置文件(.pubxml):< / p>

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

为什么需要新的_CustomFiles项目?为什么不简单<FilesForPackagingFromProject Include="..\Extra Files\**\*">?我尝试过,由于某种原因,这会导致项目中的每个文件都在已部署的Extra Files文件夹中。有人能解释一下这种行为吗?

1 个答案:

答案 0 :(得分:3)

由于您询问为什么这是必需的,因此我将深入了解此代码的含义,以解释您所看到的内容。 <Message />是我们的朋友!

%

的含义

让我们首先在<Message>任务中使用它来表示%的含义:

<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.txt一次。在此上下文中未设置RecursiveDir,而Filename/Extension是组中每个文件的原始文件名。

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

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

如何解决此问题

坚持%(_CustomFiles)修正。希望您现在能够了解它为什么必要以及它如何做到这一点。我相信这就是你应该这样做的方式:这里another question关于它,并给出了推荐这种方法的答案。