通过MSBuild并行编译Delphi项目

14
我有一个脚本,可以像下面这样编译解决方案中的所有项目(约50个):
msbuild "myProjName.dproj" /t:build /p:config="Release" /fileLogger /flp:ErrorsOnly /nologo

这个方案能够正常工作,但编译时间非常长。为了加速构建过程,我一直在尝试利用现代多核机器的所有潜力,使用此处所解释的"/maxcpucount"开关:http://msdn.microsoft.com/library/bb651793.aspx

在我的4核CPU开发机上,编译时间大致相同,没有性能提升。

显然,当项目需要构建依赖项时,才能发挥其作用。其他"工人"将会并行地构建这些依赖项项目,就像主项目一样。 因此,我尝试在Delphi中构建一个项目组,并将所有项目添加到其中,然后在这个.groupproj上运行msbuild命令,但它仍然像以前一样慢。

你们有没有成功使用msbuild同时构建多个项目? 如果是,请给我提供一个解释。

谢谢!


每当我需要时,我总是通过编写脚本来构建,例如使用Python。 - David Heffernan
那确实是一个解决方法,但如果有一种“内置”的机制可以通过msbuild实现它,我更愿意不这样做。 - mathieu
3个回答

10

以下内容适用于RAD Studio XE4版本,但也可能适用于早期或更高版本。此外,在使用此方法时,.groupproj中定义的依赖关系将不会被执行。我尝试并行处理的.groupproj没有项目间的依赖关系,因此我不知道如何处理。

如果您使用BuildCleanMake目标使用MSBuild构建.groupproj文件,则构建不会并行运行,因为这些目标使用CallTarget任务执行其他目标,但是CallTarget不会并行执行其目标。

为了并行构建单独的项目,MSBuild项目必须使用单个MSBuild任务同时构建多个项目。必须像这样定义目标:

  <Target Name="Build">
    <MSBuild Projects="@(Projects)" BuildInParallel="true"/>
  </Target>
  <Target Name="Clean">
    <MSBuild Targets="Clean" Projects="@(Projects)" BuildInParallel="true"/>
  </Target>
  <Target Name="Make">
    <MSBuild Targets="Make" Projects="@(Projects)" BuildInParallel="true"/>
  </Target>

将这些内容添加到.groupproj文件中,然后删除其他的<Target>指令以及<Import>指令。(CodeGear.Group.Targets定义了一些目标,以便按正确顺序构建项目并在您要求仅构建项目子集时构建依赖项,但它会覆盖在.groupproj文件中定义的Build、Clean和Make目标。)请注意,这只允许您构建所有项目,而不仅是子集。

MSBuild 3.5中添加了BuildInParallel选项。 但是,由于.groupproj文件没有指定ToolsVersion属性,MSBuild将使用版本2.0中定义的MSBuild任务,该任务不支持BuildInParallel。有两种解决方法:

  1. ToolsVersion="3.5"(或更高版本)添加到.groupproj文件的根<Project>元素中。
  2. 使用命令行参数/toolsversion:3.5(或缩写形式/tv:3.5)运行MSBuild(/toolsversion将覆盖所有项目文件中指定的ToolsVersion)。

完成此操作后,请使用/maxcpucount(或/m)参数运行MSBuild,您的项目将可以并行构建。但是,RAD Studio无法正确处理转换后的项目组,因此您可能需要为文件命名不同的扩展名,以清楚地表明这不是标准的RAD Studio项目组(任何以proj结尾的扩展名均可)。

以下XSLT样式表执行上述转换:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
                exclude-result-prefixes="msbuild"
                xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
                xmlns:msbuild="http://schemas.microsoft.com/developer/msbuild/2003"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="//msbuild:Project">
    <xsl:copy>
      <xsl:attribute name="ToolsVersion">3.5</xsl:attribute>
      <xsl:apply-templates select="@* | node()"/>
      <Target Name="Build">
        <MSBuild Projects="@(Projects)" BuildInParallel="true"/>
      </Target>
      <Target Name="Clean">
        <MSBuild Targets="Clean" Projects="@(Projects)" BuildInParallel="true"/>
      </Target>
      <Target Name="Make">
        <MSBuild Targets="Make" Projects="@(Projects)" BuildInParallel="true"/>
      </Target>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="//msbuild:Target">
    <!-- Do not copy -->
  </xsl:template>

  <xsl:template match="//msbuild:Import">
    <!-- Do not copy -->
  </xsl:template>

  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

您可以使用MSBuild(4.0或更高版本:XslTransformation在MSBuild 4.0中添加)通过此项目文件应用此样式表(其中groupproj2parallel.xslt是上面的XSLT文件):

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="Build" Inputs="$(InputPaths)" Outputs="$(OutputPaths)">
    <XslTransformation
      XmlInputPaths="$(InputPaths)"
      XslInputPath="groupproj2parallel.xslt"
      OutputPaths="$(OutputPaths)" />
  </Target>
</Project>

你需要在命令行上明确指定/p:InputPaths="..." /p:OutputPaths="...",或者在MSBuild 任务的Properties参数中指定它们。(或者,您可以在项目文件中直接硬编码文件名。)


对于C#和Visual Basic项目提供的MSBuild目标定义通过使用项目文件中定义的<ProjectReference>项处理依赖项,而不是在解决方案文件中定义依赖项。 Delphi .dproj文件和C ++ Builder .cbproj文件不支持此功能,因为底层的CodeGear.Common.Targets没有重用Microsoft.Common.Targets中定义的<ProjectReference>机制。


3
有两种方法可以构建Delphi项目:MSBuildDCC32.exe。推荐使用MSBuild,因为项目文件(dprojgroupproj)将所有配置设置封装起来。
然而,与旧式的DCC32.exe相比,使用MSBuild会有额外的开销。此外,使用MSBuild构建Delphi项目组(.groupproj)对于多核CPU并没有任何好处。构建性能与单核CPU相同。
以下是在一个单一的groupproj中构建290个dproj文件的统计数据:
MSBuild a `groupproj` contains 290 `dproj` on 2C/4T CPU: ~100s
MSBuild a `groupproj` contains 290 `dproj` on 4C/8T CPU: ~100s

MSBuild 290 `dproj` run in multi-threads on 2C/4T CPU: ~121s
MSBuild 290 `dproj` run in multi-threads on 4C/8T CPU: ~50s

DCC 290 `dproj` run in multi-threads on 2C/4T CPU: ~37s
DCC 290 `dproj` run in multi-threads on 4C/8T CPU: ~24s

从阅读中可以得出结论,与DCC32相比,MSBuild引入了额外的开销。为了充分利用可用的CPU核心和线程,我们需要通过牺牲.DPROJ项目配置封装设计的便利来采用DCC32方式。
可以使用msbuild脚本并行构建Delphi groupproj,该脚本可在https://github.com/ccy/msbuild.delphi.parallel上找到。

非常棒的分析,您能提供用于这290个项目的多线程MSBuild和DCC编译的脚本吗? - mathieu

1

这应该是一个注释。 - David Heffernan
1
我刚刚安装了它。每个项目增加了约20%,这很不错。但与将任务分配到4或8个核心时所获得的相比,这微不足道。无论如何,还是谢谢! - mathieu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接