既然你问为什么这是必需的,我将不得不深入研究这段代码的含义来解释你所看到的。 <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 关于它的答案,推荐这种方法。