除了现有的链接文件外,通常将目标文件添加到 MSBuild 中的 Link 任务

Generically adding object file to Link task in MSBuild in addition to existing linked files

TL;DR

除了正在进行的任何其他操作之外,如何将在正常构建过程之外创建的额外目标文件反馈回 (MSBuild) 构建过程?

/TL;DR

最好用一些背景信息来解释原因(警告:文字墙隐约可见)。

我们开发基于Qt5 框架的应用程序。该过程的一部分是使用 Qt 资源管理器将翻译 (.qm) 文件嵌入到资源 dll 中。这多年来一直很有效,但最近我们遇到了(显然是众所周知和可怕的)"C1060: Compiler is out of heap space" 错误。

发生这种情况是因为 Qt 的资源编译器 (RCC) 创建了一个越来越大的包含嵌入式资源的 .cpp 文件。在某些时候,该文件对于编译器来说太大而无法处理(我们达到了大约 60 MB 的限制)。

谷歌搜索问题通常会导致建议在项目的 .pro 文件中使用 "CONFIG += resources_big"(顺便说一句,未记录)。我们的问题是,我们不使用 .pro 文件,而是 Visual Studio 的 MSBuild。

一些进一步的谷歌搜索显示了 "CONFIG += resources_big" 的实际作用。

而不是使用通常的语法生成(巨大的).cpp 文件

rcc.exe my_resources.qrc -o qrc_my_resources.cpp

使用(同样未记录的)RCC 参数 -pass 1 和 -pass 2 应用三阶段方法。

首先创建一个特殊的 .cpp 文件,分配必要的 space

rcc.exe my_resources.qrc -pass 1 -o qrc_my_resources.cpp

然后您在这个特定的 .cpp 文件上显式调用编译器

cl.exe /c /Zl qrc_my_resources.cpp -Foqrc_my_resources.tmp

这将创建一个主要用零填充的目标文件。参数 /c 导致没有 linking 发生并且 /Zl 确保没有嵌入默认库调用(否则你稍后会得到 linker 错误)。

第三个也是最后一个阶段是再次调用 RCC 并让它用实际的二进制数据填充目标文件中的空 space。

rcc.exe my_resources.qrc -pass 2 -temp qrc_my_resources.tmp -o qrc_my_resources.obj

现在我们有了包含所有资源的最终目标文件,而不会导致可怕的 C1060 错误。但是,我们现在也遇到了一个问题,因为我们需要告诉 linker 除了 cl.exe 为所有未使用的文件提供的自动生成的目标文件列表之外,还需要使用我们的目标文件用 /c.

编译

更复杂的情况出现了,因为一名前雇员创建了一个非常复杂的 MSBuild 解决方案。专门为了自动处理 RCC'ing Qt 资源文件,他创建了以下属性(TemporaryFileName 和 ObjectFileName 元素是我添加的)。

<PropertyGroup>
  <QtToolsIntermediateDir>$(IntermediateOutputPath)GeneratedFiles</QtToolsIntermediateDir>
</PropertyGroup>
<ItemDefinitionGroup>
  <QtRCC>
    <OutputFileName>$(QtToolsIntermediateDir)\qrc_%(FileName).cpp</OutputFileName>
    <TemporaryFileName>$(IntermediateOutputPath)\qrc_%(FileName).tmp</TemporaryFileName>
    <ObjectFileName>$(IntermediateOutputPath)\qrc_%(FileName).obj</ObjectFileName>
  </QtRCC>
</ItemDefinitionGroup>

在一个特殊的 .targets 文件中,他定义了这样的自动资源编译

<Target Name="QtRCC"
        BeforeTargets="CustomBuild"
        DependsOnTargets="QtToolsEnsureOutputDir"
        Inputs="%(QtRCC.Dependencies)"
        Outputs="%(QtRCC.OutputFileName)">
  <Message Text="RCC'ing @(QtRCC)..."/>
  <Exec Command="$(MSBuildThisFileDirectory)tools\rcc.exe -name %22%(QtRCC.FileName)%22 -no-compress %22%(FullPath)%22 -o %(QtRCC.OutputFileName)"/>
</Target>

这会导致 "normal" 编译过程(产生一个巨大的 .cpp 文件)。

我将其替换为上述三阶段方法

  <Exec Command="$(MSBuildThisFileDirectory)tools\rcc.exe -name %22%(QtRCC.FileName)%22 -no-compress %22%(FullPath)%22 -pass 1 -o %(QtRCC.OutputFileName)"/>
  <Exec Command="cl.exe -c -Zl %(QtRCC.OutputFileName) -Fo%(QtRCC.TemporaryFileName)"/>
  <Exec Command="$(MSBuildThisFileDirectory)tools\rcc.exe -name %22%(QtRCC.FileName)%22 -no-compress %22%(FullPath)%22 -pass 2 -temp %(QtRCC.TemporaryFileName) -o %(QtRCC.ObjectFileName)"/>

这完全符合预期,但同样存在上述同样的问题。也就是说,我现在有一个目标文件,除了所有自动生成的 link 目标之外,我还需要 linker 到 link。

已经找到了一种解决方案,但这是我真正想避免的一种。此解决方案是在每个可能使用 Qt 资源文件的项目文件的 Link 任务的 AdditionalDependencies 部分手动添加包含资源的目标文件。

<Link>
  <AdditionalDependencies>$(IntermediateOutputPath)\qrc_my_resources.obj;Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;Qt5PrintSupport.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>

我非常希望将它放在一个通用的地方,例如上述的 .targets 或 .props 文件。那可能吗?如果是这样,怎么做?

我知道您提到的问题,对我来说最简单的解决方案是使用 QT 的 MS Visual Studio AddOn 中提供的 Qt 属性 sheet。

基本上大家都会告诉你在项目的.pro文件里加上CONFIG += resources_big”,但是你用的不是.pro文件,而是Visual Studio的MSBuild.

CMake 中可用的所有选项在 MSBuild 中都可用,但未记录。

安装 Qt Visual Studio 扩展,完成后,进入 %APPDATA%/Local/QtMsBuild 并复制项目外部目录中的所有 属性 sheet .在你的项目中使用那些 属性 sheet 像这样:

 <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Retail|x64'">
   <QtRcc>
      <ExecutionDescription>Rcc'ing %(Identity)...</ExecutionDescription>
      <OutputFile>$(ProjectDir)\GeneratedFiles\qrc_%(Filename).cpp</OutputFile>
      <!-- Enable verbose mode '-verbose' -->
      <Verbose>true</Verbose>
 <!-- Pass Number for big resources '-pass' will make obj smaller each time-->
      <PassNumber>10</PassNumber>
      <!-- Disable all compression '-no-compress' (false by default, should be false in final product) -->
      <NoCompression>false</NoCompression>
      <!-- Output a binary file for use as a dynamic source '--binary' -->
      <BinaryOutput>false</BinaryOutput>
      <!-- Run work in parallel processes or not, this was buggy last time I tried. No big time improvement, keep it off -->
      <ParallelProcess>false</ParallelProcess>
      <!-- Compress input files by <level> '-compress <level>'  
        -- The level is a int from 1 to 9 and represent the zlib compression levels 
          #define Z_NO_COMPRESSION         0
          #define Z_BEST_SPEED             1
          #define Z_BEST_COMPRESSION       9
          #define Z_DEFAULT_COMPRESSION  (-1)
        -->
      <Compression>level3</Compression>
      <!--  Threshold to consider compressing files '-threshold <level>' 
            Specifies a threshold (in bytes) to use when compressing files. 
            If the file is smaller than the threshold, it will not be compressed, independent of what the compression level is.
            Keep this unset, it is not used and be default behavior
      <CompressThreshold>3</CompressThreshold>
      -->
      <InitFuncName>QmlInitialization</InitFuncName>
    </QtRcc>

我建议创建一个小示例项目并从 UI 添加 Qt 选项,然后查看生成的 vcxproj 中的语法。您将为自己的 Qt 项目创建文档。

有道理吗?

最后一件事:使用我提供的解决方案,您可以使用 QmlCacheGenerate 选项轻松编译用于缓存的 QML:

示例 .vcxproj

<ItemGroup Condition="'$(Configuration)|$(Platform)'=='Retail|x64'">
    <QtRcc Include="$(QtResourceFile)" >
      <QmlCacheGenerate>true</QmlCacheGenerate>
    </QtRcc>
</ItemGroup>

顺便说一句,-pass 参数不是通道的 ID,而是编译资源时要执行的通道数。如果你最终得到一个 10Mb 的 cpp,执行 10 遍编译 10Mb 文件

干杯。

-- 纪尧姆普兰特 高级程序员