【问题标题】:Parallel compilation of delphi projects through MSBuild通过MSBuild并行编译delphi项目
【发布时间】:2023-04-07 15:23:01
【问题描述】:

我有一个脚本可以编译我的解决方案的所有项目(大约 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 编写构建脚本
  • 这将是一种解决方法,是的,但如果 msbuild 有一个“内置”机制来实现它,我不希望这样做。

标签: delphi msbuild delphi-xe6


【解决方案1】:

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

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

为了并行构建单独的项目,MSBuild 项目必须使用单个MSBuild task 来一次构建多个项目。目标必须像这样定义:

  <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,然后删除其他 &lt;Target&gt; 指令以及 &lt;Import&gt; 指令。 (CodeGear.Group.Targets 定义了一些目标,以便在您要求仅构建项目的子集时以正确的顺序构建项目并构建依赖关系,但它覆盖了在.groupproj。)请注意,这只允许您构建所有项目,而不仅仅是一个子集。

BuildInParallel was added in MSBuild 3.5. 但是,由于 .groupproj 文件未指定 ToolsVersion 属性,MSBuild 将使用不支持 BuildInParallel 的 2.0 版中定义的 MSBuild 任务。有两种方法可以解决此问题:

  1. ToolsVersion="3.5"(或更高版本)添加到 .groupproj 文件的根 &lt;Project&gt; 元素中。
  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>

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

<?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="..." 明确指定InputPathsOutputPaths,或者在MSBuild 任务的Properties 参数上指定它们。 (或者,您可以在项目文件中硬编码文件名。)


MSBuild 为 C# 和 Visual Basic 项目提供的目标定义通过使用项目文件中定义的 &lt;ProjectReference&gt; 项来处理依赖项,而不是在解决方案文件中定义依赖项。 Delphi .dproj 文件和 C++ Builder .cbproj 文件不支持这一点,因为底层 CodeGear.Common.Targets 不会重用在 Microsoft.Common.Targets 中为 &lt;ProjectReference&gt; 定义的机制。

【讨论】:

    【解决方案2】:

    有两种方法可以构建 Delphi 项目:MSBuildDCC32.exe。建议使用MSBuild,因为项目文件(dprojgroupproj)封装了所有配置设置。

    但是,与普通的旧 DCC32.exe 相比,使用 MSBuild 有更多开销。此外,使用MSBuild 构建Delphi Project Group (.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
    

    从阅读中,我们可以得出结论,MSBuildDCC32 相比引入了额外的开销。为了充分利用可用的CPU内核和线程,DCC32.DPROJ牺牲项目配置封装设计的便利性的方法。

    msbuild 用于并行构建 Delphi groupproj 的脚本可在 https://github.com/ccy/msbuild.delphi.parallel 获得

    【讨论】:

    • 很好的分析,能否提供你用于这290个项目的多线程MSBuild和DCC编译的脚本?
    【解决方案3】:

    有点离题:您可以尝试 IDE 修复包的 fastdcc 部分以获得更快的构建: http://andy.jgknet.de/blog/ide-tools/ide-fix-pack/ 例如,我将构建时间从 1 分钟缩短到 22 秒!

    【讨论】:

    • 这应该是一条评论
    • 我刚刚安装了它。每个项目我获得了大约 20% 的收益,这很好。但这与将任务拆分为 4 或 8 个核心所获得的收益相比微不足道。还是谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-12
    • 1970-01-01
    相关资源
    最近更新 更多