您所描述的“并行增量构建”已经在(某种)中构建,但您真正需要的是并行部分构建。
让我先解释一下这些概念,然后我将具体介绍它在您的场景中是如何工作的。您需要了解两件事;增量建设和部分建设。我刚刚在http://sedodream.com/2010/09/23/MSBuildYouveHeardOfIncrementalBuildingButHaveYouHeardOfPartialBuilding.aspx 上写了一篇讨论此问题的博文,但我会将相关部分粘贴在这里。
增量构建的概念是您应该只构建过时的东西。为了支持这一点,MSBuild 具有 Target 元素上的属性、输入和输出。使用这些属性,您可以指定进入目标的文件(通过输入属性),以及您期望从目标中出来的文件(通过输出属性)。执行此操作后,MSBuild 会将输入的时间戳与输出进行比较,如果所有输出都是最新的(即输入较旧),则将跳过目标。看看下面这个非常简单的项目文件。
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Files Include="src\01.txt;src\02.txt;src\03.txt;src\04.txt;src\05.txt;"/>
</ItemGroup>
<PropertyGroup>
<Dest>dest\</Dest>
</PropertyGroup>
<Target Name="CopyFiles"
Inputs="@(Files)"
Outputs="@(Files->'$(Dest)%(Filename)%(Extension)')">
<Message Text="CopyFiles" />
<Copy SourceFiles="@(Files)"
DestinationFiles="@(Files->'$(Dest)%(Filename)%(Extension)')"/>
</Target>
<Target Name="DeleteTwoFiles">
<Message Text="DeleteTwoFiles" />
<Delete Files="$(dest)01.txt;$(dest)02.txt"/>
</Target>
</Project>
在这个项目文件中,我们有两个目标; CopyFiles 和 DeleteTwoFiles。暂时忽略 DeleteTwoFiles。另请注意,我正在执行此构建的目录有一个文件夹 src,其中包含文件项中列出的文件。在 CopyFiles 目标上,我指定了输入和输出。输入只是@(Files),这是目标正在操作的文件。输出包含表达式@(Files->'$(Dest)%(Filename)%(Extension)')。这与 Copy 语句中的表达式相同。如果 Dest 文件夹为空并且我执行 CopyFiles 目标,则结果如下所示。
所以正如预期的那样,文件被复制了,所以一切都很好。现在如果我再次执行它会发生什么?输出如下所示
所以你可以看到目标被跳过了,消息语句“CopyFiles”没有被执行,副本也没有被执行。因此,简而言之,这是增量构建。
现在,使用包含所有文件的 dest 文件夹,您认为我执行命令 msbuild.exe PartialBuilding01.proj /t:DeleteTwoFiles;CopyFiles 会发生什么?此命令将首先从输出目录中删除两个文件,然后再次调用 CopyFiles 目标。让我们看看下面的结果。
当 CopyFiles 目标被执行时,您会看到语句“Building target ‘CopyFiles’ partial, ...”。当执行目标时,MSBuild 检查输入和输出,确定文件 01.txt 和 02.txt 已过期(因为它们不存在于目标中)但 03.txt、04.txt和 05.txt 是最新的。因此,MSBuild 为 CopyFiles 提供了仅包含 01.txt 和 02.txt 的 Files 项的值,并让它完成它的工作。
现在这在很多方面都与您的问题有关,有些方面并不像您希望的那样直接。首先,MSBuild 将逐步构建您的项目,因此如果您的项目是最新的,则不会再次构建它。问题是,为了让 MSBuild 确定您的项目是最新的,它必须加载项目运行默认目标(通常是 Build),然后目标本身会发现没有工作要做。这东西本身需要时间。因此,如果您有大量项目,或者项目中有大量文件,那么您可以自己处理事情。您需要一种方法来确定您的项目是否是最新的,并在您的输入和输出属性中正确表达这一点。完成此操作后,您应该能够跳过构建最新的项目。
问题的核心是你如何制作正确的输入/输出。如果你能想出办法做到这一点,那么你就会得到你想要的。你如何制作这将取决于你的场景,但我可以看到这样的东西:
- 在每个项目构建后,将文件拖放到特定于该项目的已知位置
- 在构建项目之前扫描其目录,找到最新的文件,然后将项目文件的时间戳更新为该值
- 然后您可以将项目文件作为输入值,将标记文件作为输出
- 然后调用你的目标
在这种情况下,您假设所有依赖项都完全包含在项目目录下的文件中(这可能不正确)。我并不是说这是理想的解决方案,而是一个解决方案。
===============================================
编辑:根据以下问题进行更新。
您需要将项目放入一个项目(尽管不是必需的)中,例如 ProjectFiles,然后使用 @(ProjectFiles) 作为输入。对于输出,这就是我所说的困难部分。您必须想办法知道(或通过您自己的流程向您表明)项目是最新的。没有为此内置任何内容。
关注增量构建与干净构建。在一个完美的世界中,增量和干净的构建是相同的。但有时情况并非如此。对于许多项目来说都是如此。如果您开始在构建过程中添加一堆目标并将它们设置为进行增量构建,但您没有正确实施,那么您可能会在目标确实过时时跳过目标。一个很好的例子是,当您创建一个目标时,将 Inputs 设置为文件列表,然后将 Outputs 设置为已创建文件的列表。如果您不扩展清理过程以删除那些创建的文件,那么下次重建时(假设您没有更改文件)目标将被跳过,而该目标应该在之前的重建中被清理。