Flutter - Xcode16 还原编译速度

1,875 阅读6分钟

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

一、前言

在之前发布的【Flutter - iOS编译加速】一文中,我们提到升级至 Xcode16 之后,iOS 的编译速度慢到令人发指,随后探索发现是 xcrun cc snapshot_assembly.S snapshot_assembly.o 这一汇编耗时变长了。而就在几天前,有人在相关的 issue 中留言了他篡改使用 Xcode 15 的 cc 来提升编译速度的步骤,详情可见 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【可选】简短的显示名称

注:在 DisplayName 和 ShortDisplayName 都不设置时,名字会显示为 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 设置为 .xctoolchain 的 Identifier,如: 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 的工具链中,cc 是 clang 的替身

而不同版本的 clang 对同一份 .S 进行汇编,还是有可能生成内容不一样的 .o 的。不过我自己对比 Xcode 16 和 Xcode 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 技术,还有 AI、Android,iOS,Python 等文章, 可能有你想要了解的技能知识点哦~