如何避免在MSBuild中重复?

时间:2011-01-26 22:22:09

标签: msbuild dry

我不介意在必要时偶尔重复某些事情,但在MSBuild中我真的不知道如何永远避免重复。它通常意义上不提供“功能”;目标只能通过CallTarget进行一次调用,而<Import>仅适用于Project级。

这是一个我试图去“重复”的具体例子:

<Target Name="Tgt1">
  <PropertyGroup><Conf1>Twiddle</Conf1><Conf2>Thing</Conf2></PropertyGroup>

  <PropertyGroup><xxxxxxxxxxExePath>$(xxxxxxxBuildRoot)\$(Conf1)Console-xxxxxxxxed</xxxxxxxxorExePath></PropertyGroup>
  <MSBuild Projects="$(BuildSingleProj)" Targets="Build;Merge"
           Properties="Configuration=$(Conf1)$(Conf2);Platform=$(Platform);CompiledFileName=$(CompiledFileName);ProjectName=$(ProjectName);SolutionFile=$(SolutionFile);Root=$(Root);Caller=$(MSBuildProjectFullPath)"/>
  <MakeDir Directories="$(xxxxxxxxorExePath)" />
  <WriteLinesToFile File="$(xxxxxxxxorExePath)\xxxxxxx.IsPortable.txt" />
  <WriteLinesToFile File="$(xxxxxxxxorExePath)\xxxxxxx.Global.Settings.xml" Lines="@(xxxxxxxLicense)" Overwrite="true" />
  <Exec Command='$(xxxxxxxxorExePath)\xxxxxxx.exe -a "$(xxxxxxxBuildRoot)\$(Conf1)$(Conf2)-Merged\xxxxxxx.exe" "$(xxxxxxxBuildRoot)\$(Conf1)$(Conf2)-xxxxxxxxed\xxxxxxx.exe"'/>
</Target>

我有四个这样的目标,Tgt1Tgt2Tgt3Tgt4。这四个目标之间存在差异的唯一是第一行,即定义Conf1Conf2的行。

我所知道的唯一或多或少可行的重复数据删除思想是将共享代码移动到新目标并通过MSBuild任务调用它。不幸的是,这需要手动传入 loooooong 属性字符串,而且此任务使用了相当多的(我计算了11个属性和1个项目组)。

另一个要求是我可以使用这些目标的任意子集来调用脚本,例如: \t:Tgt2,Tgt3

是否有任何合理的替代方法可以复制/粘贴这段代码 - 而不涉及复制庞大的属性列表?

2 个答案:

答案 0 :(得分:8)

这是使用Batching的完美方案。

您需要使用适当的元数据创建自定义Items,然后创建一个目标以引用新项目。

您可以将每个项目包装在自己的目标中,如下所示:

<Target Name="Tgt1">
  <ItemGroup>
    <BuildConfig Include="Tgt1">
      <Conf1>Twiddle</Conf1>
      <Conf2>Thing</Conf2>
    </BuildConfig>
  </ItemGroup>
</Target>

<Target Name="Tgt2">
  <ItemGroup>
    <BuildConfig Include="Tgt2">
      <Conf1>Twaddle</Conf1>
      <Conf2>Thing 1</Conf2>
    </BuildConfig>
  </ItemGroup>
</Target>

<Target Name="Tgt3">
  <ItemGroup>
    <BuildConfig Include="Tgt3">
      <Conf1>Tulip</Conf1>
      <Conf2>Thing 2</Conf2>
    </BuildConfig>
  </ItemGroup>
</Target>

然后你需要一个能够执行所有工作的核心目标:

<Target Name="CoreBuild" Outputs="%(BuildConfig.Identity)">
  <Message Text="Name  : %(BuildConfig.Identity)" />
  <Message Text="Conf1 : %(BuildConfig.Conf1)" />
  <Message Text="Conf2 : %(BuildConfig.Conf2)" />
</Target>

Outputs="%(BuildConfig.Identity)"添加到目标将确保您在目标级别而不是在任务级别进行批处理。

只要最后一个目标是您的核心目标,您就可以通过传递任意目标组合从msbuild执行此操作。例如,执行此命令MSBuild.exe test.msbulid /t:Tgt1,Tgt3,CoreBuild将为您提供以下输出:

Name  : Tgt1
Conf1 : Twiddle
Conf2 : Thing

Name  : Tgt3
Conf1 : Tulip
Conf2 : Thing 2

答案 1 :(得分:5)

DRY不是MSBuild的宗旨。有人说,在任何情况下重复自己是不好的,当它合理可避免时。 Aaron给出的关于批处理的答案是一个很好的答案。这是防止重复的一种方法。

我想指出的一点是,在更高的层次上,您似乎将MSBuild视为一种过程语言(即具有可以调用的函数,而不是其他函数)。虽然MSBuild比声明更具说明性。如果你正在创建MSBuild脚本并且你有心态'创建函数X以便我可以在Y点调用它,那么你就进入了一个痛苦的世界。相反,您应该将MSBuild视为阶段。例如;收集文件,编译,发布等等。当你以这种方式想到它时,它总是有意义的是为什么目标在执行一次后被跳过(你在试验期间明显观察到

在我和MSBuild一起工作之后,我已经发现它可以真正成为PITA以通用/超级可重用的方式做事。它可以完成,但我会保留.targets文件的那种类型的努力,你知道肯定会重复使用很多次。现在有一天而不是经历,我更务实,我在完全黑客的脚本之间的某个地方和按照我以前的方式做事。我有一组我重复使用的脚本,但除了那些我尝试并保持简单。其中一个重要原因是有很多人如何了解MSBuild的基础知识,但很少有人对它有深入的了解。创建好的通用脚本需要深入了解MSBuild,所以当你离开项目时,进入你的人不知道你在做什么(如果你是承包商,那么也许好吗?大声笑

无论如何,我在批量处理时获得了大量资源:http://sedotech.com/Resources#Batching