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

wez*_*ten 5 msbuild visual-studio

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

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

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

Rom*_*kov 8

既然您要问为什么需要这样做,我将不得不深入研究此代码的含义以解释您所看到的内容。<Message />是我们的朋友!

的意思 %

我们先来看看在<Message>任务中使用 % 是什么意思:

<ItemGroup>
  <_CustomFiles Include="..\Extra Files\**\*" />
</ItemGroup>

<Message Text="File: %(_CustomFiles.Identity)" />
Run Code Online (Sandbox Code Playgroud)

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

File: ..\Extra Files\file1.txt
File: ..\Extra Files\file2.txt
File: ..\Extra Files\file3.txt
...
File: ..\Extra Files\etc.txt
Run Code Online (Sandbox Code Playgroud)

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

项目组中有什么?

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

<Message Text="File: %(FilesForPackagingFromProject.Identity) -> %(FilesForPackagingFromProject.DestinationRelativePath)" />
Run Code Online (Sandbox Code Playgroud)

这输出:

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

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

工作代码

当您在具有 % 的元素中有子元素时,它们会应用于每次迭代一次,所以现在让我们看一下工作代码:

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

<Message Text="File: %(FilesForPackagingFromProject.Identity) -> %(FilesForPackagingFromProject.DestinationRelativePath)" />
Run Code Online (Sandbox Code Playgroud)

对于 中的每个项目_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
Run Code Online (Sandbox Code Playgroud)

只包含一个文件

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

<FilesForPackagingFromProject Include="..\Extra Files\file1.txt">
  <DestinationRelativePath>Extra Files\file1.txt</DestinationRelativePath>
</FilesForPackagingFromProject>
Run Code Online (Sandbox Code Playgroud)

这不会%在任何地方扩展,因此它完全符合您的期望:它在输出中包含一个文件。

损坏的代码

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

<FilesForPackagingFromProject Include="..\Extra Files\file1.txt">
  <DestinationRelativePath>Extra Files\%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>
</FilesForPackagingFromProject>

<Message Text="File: %(FilesForPackagingFromProject.Identity) -> %(FilesForPackagingFromProject.DestinationRelativePath)" />
Run Code Online (Sandbox Code Playgroud)

这里有%所以事情得到扩展,但因为这%在项目组元素中没有 a ,扩展的工作方式不同,事情变得梨形:

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

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

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

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

如何解决这个问题

坚持%(_CustomFiles)修复。希望你现在会明白为什么它是必要的,以及它是如何做的。我确实相信这就是你应该这样做的方式:这是另一个关于它的问题,答案是推荐这种方法。