在 macOS 上使用 Keil 的 armclang 工具链

发布于2026年4月21日 -

因为手上这个处理器比较特殊,编译时没法随便换工具链,必须得用 ARM 提供的 armclang。之前我的做法一直很朴素:开虚拟机,在里面装 Keil,然后把整套编译流程放进去跑。能用是能用,但总归不够顺手。

后来突然想到,之前买过 CrossOver,但早都丢到垃圾箱了。群友能用 Wine 跑起来,我也应该能用它跑起来,于是翻出了 License,最后在 GPT 的帮助下,还真跑通了。

装 Keil 不是什么大问题,编单个文件也没问题,一旦开始编译复杂的项目,就遇到麻烦了:

  • ninja 是在 macOS 上运行的
  • 它生成和传递的路径,天然是 Unix 风格
  • 而实际被调用的 armclang.exe 是 Windows 程序
  • Windows 程序只认 Windows 风格路径

这意味着,构建系统吐出来的参数,不能直接原样交给 CrossOver 里的编译器。中间必须有一层翻译,把路径从 macOS 风格转成 Windows 风格,不然很多参数都会直接失效。

于是我 vibe code 了一个 wrap 文件来做这件事:cx-winpath-wrap

它的基本功能很简单:拦住参数,识别里面的路径,然后翻译成 Windows 能理解的形式,再把参数交给真正的 ARM 工具链。

编译问题解决了,但还有个大坑:dependency 文件的处理。因为 ninja 是否需要重新编译,很多时候依赖的就是编译器生成的 dependency 信息。这个链路如果处理不好,就会出现一种很烦的情况:明明源码没变,但构建系统总觉得依赖不对,于是每次都重新编译。

经过 GPT 反复尝试(感觉是因为它确实不擅长写 bash),这个问题也搞定了。

有了路径转换脚本之后,后面的思路就比较直接了:在本地放几个同名脚本,专门负责把调用转发给 CrossOver 里的 Windows 可执行文件。

例如 armclang 可以写成这样:

#!/bin/bash
exec "$HOME/.local/bin/cx-winpath-wrap" \
  'C:\users\crossover\AppData\Local\Keil_v5\ARM\ARMCLANG\bin\armclang.exe' \
  "$@"

其他几个工具也一样处理,比如:

  • armar
  • armlink
  • fromelf

包装脚本准备好之后,就可以直接在 toolchain.cmake 里指定这些入口:

set(CMAKE_C_COMPILER "$ENV{HOME}/.local/bin/armclang")
set(CMAKE_AR "$ENV{HOME}/.local/bin/armar")
set(CMAKE_LINKER "$ENV{HOME}/.local/bin/armlink")
set(FROMELF "$ENV{HOME}/.local/bin/fromelf")

这样整个调用链就串起来了:

  • cmake 生成构建规则
  • ninja 在 macOS 上执行
  • 本地包装脚本接管编译命令
  • wrap 负责路径和 dependency 的转换
  • CrossOver 里的 armclang 工具链真正完成编译和链接