Flutter - Xcode16 还原编译速度

1,216 阅读6分钟

欢迎关注微信公众号:FSA全栈行动 👋

一、前言

在之前发布的【Flutter - iOS编译加速】一文中,我们提到升级至 Xcode16 之后,iOS 的编译速度慢到令人发指,随后探索发现是 xcrun cc snapshot_assembly.S snapshot_assembly.o 这一汇编耗时变长了。而就在几天前,有人在相关的 issue 中留言了他篡改使用 Xcode 15cc 来提升编译速度的步骤,详情可见 github.com/dart-lang/s…

我在他的基础上做了优化与封装,只需两句命令即可还原编译速度,在开始详细介绍之前,先展示一下两台构建机优化前后的编译时长记录。

构建机优化前(min)Release + 二进制依赖(min)Release + 二进制依赖 + 还原编译速度(min)
i725+14+11+
M416+8+4+

M4 只要四分多钟,真香~

二、调整

以下是他提供的修改步骤

cp -r /Applications/Xcode-15.4.0.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain ~/Library/Developer/Toolchains
cd ~/Library/Developer/Toolchains
mv XcodeDefault.xctoolchain Xcode15.4.xctoolchain
/usr/libexec/PlistBuddy -c "Add CompatibilityVersion integer 2" Xcode15.4.xctoolchain/ToolchainInfo.plist
/usr/libexec/PlistBuddy -c "Set Identifier clang.Xcode15.4" Xcode15.4.xctoolchain/ToolchainInfo.plist
  1. Xcode 15.4 内部的默认工具链复制到 ~/Library/Developer/Toolchains 目录。
  2. 将当前工作目录切换到 ~/Library/Developer/Toolchains 目录。
  3. 将复制过来的 XcodeDefault.xctoolchain 重命名为 Xcode15.4.xctoolchain,方便区分。
  4. 修改 .xctoolchain/ToolchainInfo.plist 文件,添加 CompatibilityVersion,并将其值设置为整数类型 2,修改 Identifier 的值为 clang.Xcode15.4
- Future<RunResult> cc(List<String> args) => _run('cc', args);
+ Future<RunResult> cc(List<String> args) => _run('--toolchain', <String>[
+   'clang.Xcode15.4',
+   'cc',
+   ...args,
+ ]);

修改 flutter_tools 源码,将 cc 修改为 --toolchain 来使用 clang.Xcode15.4 下的 cc

三、详解

默认的工具链路径是 Xcode 中的 /Applications/Xcode.app/Contents/Developer/Toolchains,不过也可以将工具链放到 ~/Library/Developer/Toolchains 目录下,这样就可以在不修改 Xcode 应用本身的情况下,使用和管理不同的工具链版本。

接着是修改 .xctoolchain/ToolchainInfo.plist 文件,里面可以设置的一些字段如下:

字段说明
CFBundleIdentifier唯一标识
CompatibilityVersion适配版本,适配 Xcode 时必为 2
DisplayName【可选】显示名称
ShortDisplayName【可选】简短的显示名称

注:在 DisplayNameShortDisplayName 都不设置时,名字会显示为 CFBundleIdentifier

关于 CompatibilityVersion 的说明,在网上基本是搜不到的,只有如下这个注释,Xcode 8 及以上,使用 2,否则使用 1

# Xcode 8 requires CompatibilityVersion 2
set(COMPAT_VERSION 2)
if(XCODE_VERSION VERSION_LESS 8.0.0)
  # Xcode 7.3 (the first version supporting external toolchains) requires
  # CompatibilityVersion 1
  set(COMPAT_VERSION 1)
endif()

摘自: github.com/llvm/llvm-p…

四、改进

直接修改 flutter_tools 源码并写死 clang.Xcode15.4 太过于粗暴,如果我们为了安全起见,只想打测试包的时候还原编译速度,而打上架包保持原样就不好调整了,所以这里我对他的修改进行了优化。

首先来介绍一下 TOOLCHAINS 这个环境变量,它可以影响 /usr/bin/ 下的命令调用,如 /usr/bin/xcrun

注:Developer Directory/Applications/Xcode.app/Contents/Developer 或者 /Library/Developer/CommandLineTools,可以通过 xcode-select --print-path 进行检查

如果我们没有设置 TOOLCHAINS,根据上述流程图,在调用 /usr/bin/xcrun 时,会根据 Developer Directory 搜索该命令,如果找到同名命令,则执行该命令。

xcrun --find cc

# /Applications/Xcode-16.2.0.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc

如果我们将 TOOLCHAINS 设置为 .xctoolchainIdentifier,如: clang.Xcode15.4

export TOOLCHAINS=clang.Xcode15.4

那么根据上述流程图,则是在 Xcode15.4.xctoolchain 中找到 cc

xcrun --find cc
/Users/lxf/Library/Developer/Toolchains/Xcode15.4.xctoolchain/usr/bin/cc

根据这一特性,我做了如下调整:

调整 cc 方法,当有配置 CONDOR_TOOLCHAINS 环境变量时,将值取出并赋值给 TOOLCHAINS

//   Future<RunResult> cc(List<String> args) => _run('cc', args);
  Future<RunResult> cc(List<String> args) {
    final String condorToolchains = platform.environment['CONDOR_TOOLCHAINS'] ?? '';
    final Map<String, String> environment = <String, String>{
      if (condorToolchains.isNotEmpty) "TOOLCHAINS": condorToolchains,
    };
    _run('--find', <String>['cc'], environment: environment).then((RunResult result) {
      printStatus(
        '\n[condor] find cc: ${result.stdout}\n',
      );
    });
    return _run('cc', args, environment: environment);
  }

_run 方法新增 environment 参数,用于设置环境变量。

//   Future<RunResult> _run(String command, List<String> args) {
//     return _processUtils.run(
//       <String>[...xcrunCommand(), command, ...args],
//       throwOnError: true,
//     );
//   }
  Future<RunResult> _run(String command, List<String> args, {Map<String, String>? environment}) {
    return _processUtils.run(
      <String>[...xcrunCommand(), command, ...args],
      throwOnError: true,
      environment: environment,
    );
  }

五、Condor

上述步骤还是比较繁琐的,所以这里我将其进行了封装,只需要执行两句命令即可。

1、安装与更新 condor

Homebrew

如果你是首次安装,则执行如下命令

brew tap LinXunFeng/tap && brew install condor

如果不是首次安装,则需要执行如下命令进行更新

brew update && brew reinstall condor

Pub Global

如果你习惯使用 Pub,或者你的电脑是 Intel 芯,则可以执行如下命令进行安装或更新

dart pub global activate condor_cli

2、拷贝 xctoolchain

condor optimize-build xctoolchain-copy --xcode Xcode-15.4.0

--xcode 参数请使用 Xcode 15/Applications/ 下的名字,如果你电脑上没有 Xcode 15,建议使用 github.com/XcodesOrg/X… 进行安装。

这一步会做如下几个操作

  1. /Applications/Xcode-15.4.0.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain 拷贝至 ~/Library/Developer/Toolchains/Xcode-15.4.0.xctoolchain
  2. Xcode-15.4.0.xctoolchain/ToolchainInfo.plist 中的 Identifier 设置为 Xcode-15.4.0
  3. 添加 CompatibilityVersion 并设置为 2

3、cc 重写向

该命令会对 flutter_tools 源码进行修改,使其具备重定向 cc 的能力而已,在有配置 CONDOR_TOOLCHAINS 环境变量时才会生效,否则则使用默认的 cc

# 使用默认 flutter,则不需要传 flutter 参数
condor optimize-build redirect-cc

# 如果你想指定 fvm 下的指定 Flutter 版本
condor optimize-build redirect-cc --flutter fvm spawn 3.24.5

4、应用

后续你只需要 export CONDOR_TOOLCHAINS=Xcode-15.4.0 就可以在 Xcode 16 上感受到 Xcode 15 的编译速度了 🥳

如打包前 export

export CONDOR_TOOLCHAINS=Xcode-15.4.0
flutter clean
flutter build ipa

如果你想验证,可以加上 --verbose,并将输出保存到 result.txt

flutter build ipa --verbose > result.txt

命令执行完毕后打开 result.txt,搜索 condor 即可。

或者如果你不需要按需配置,也可以直接在 Run Script 里设置 CONDOR_TOOLCHAINS 环境变量。

验证也很简单,如下图所示,选择当前的 Build 任务,搜索 condor 即可。

六、是否有影响

Xcode 的工具链中,ccclang 的替身

而不同版本的 clang 对同一份 .S 进行汇编,还是有可能生成内容不一样的 .o 的。不过我自己对比 Xcode 16Xcode 15 生成的 .o 并没有什么不同。

对比 .o 文件,我们可以使用系统自带的 cmp 命令,cmp 是一个用于比较文件的命令行工具,它可以逐字节比较二进制文件。如下所示

cmp /Users/lxf/cc15/snapshot_assembly.o /Users/lxf/cc16/snapshot_assembly.o

cmp 命令执行完成,退出代码为 0,并且没有输出。这表明 cmp 命令没有发现两个文件之间有任何不同之处。因此可以证明这两个 .o 文件的内容是相同的。

即,基于 Xcode 16 来说并没有影响,这种方式生成的 .o 可以用于上架包,如果还是不放心,可以在打上架包时,不设置 CONDOR_TOOLCHAINS 环境变量即可。

七、资料

如果文章对您有所帮助, 请不吝点击关注一下我的微信公众号:FSA全栈行动, 这将是对我最大的激励. 公众号不仅有 Flutter 技术,还有 AIAndroidiOSPython 等文章, 可能有你想要了解的技能知识点哦~