双工具链构建系统:GCC ARM 和 LLVM 在同一项目中共存

Babel36acl 方法与工具 无~ 36 次阅读 预计阅读时间: 8 分钟 发布于 1 天前 最后更新于 1 小时前 1723 字


方法与工具 构建系统

双工具链构建系统:GCC ARM 和 LLVM 在同一项目中共存

同一个 STM32 项目,同时支持 GCC ARM None EABI 和 LLVM/Clang 两套工具链。不是"二选一",而是"两个都要"——开发用 GCC(稳定)、调优用 LLVM(更激进的优化和更清楚的警告)。

CMakePresets 架构

CMakePresets.json 定义构建预设,CMake 工具链文件决定用哪个编译器:

{
    "version": 3,
    "configurePresets": [
        {
            "name": "default",
            "hidden": true,           // 隐藏预设,只作为基类
            "generator": "Ninja",     // 统一生成器
            "binaryDir": "${sourceDir}/build/${presetName}",
            "toolchainFile": "${sourceDir}/cmake/gcc-arm-none-eabi.cmake"  // 默认GCC
        },
        {
            "name": "Debug",
            "inherits": "default",
            "cacheVariables": {
                "CMAKE_BUILD_TYPE": "Debug",
                "CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
            }
        },
        {
            "name": "Release",
            "inherits": "default",
            "cacheVariables": {
                "CMAKE_BUILD_TYPE": "Release"
            }
        }
    ],
    "buildPresets": [
        { "name": "Debug",   "configurePreset": "Debug" },
        { "name": "Release", "configurePreset": "Release" }
    ]
}

使用:

GCC Debug 构建
cmake --preset Debug
cmake --build --preset Debug --parallel 8

# 换 LLVM 时,手动指定不同的工具链文件
cmake -S . -B build/llvm-debug -G Ninja \
  -DCMAKE_TOOLCHAIN_FILE=cmake/llvm-arm-none-eabi.cmake \
  -DCMAKE_BUILD_TYPE=Debug \
  -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cmake --build build/llvm-debug --parallel 8

GCC 工具链文件

# cmake/gcc-arm-none-eabi.cmake
set(CMAKE_SYSTEM_NAME    Generic)
set(CMAKE_SYSTEM_PROCESSOR arm)

set(CMAKE_C_COMPILER    arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER  arm-none-eabi-g++)
set(CMAKE_OBJCOPY       arm-none-eabi-objcopy)

set(TARGET_FLAGS "-mcpu=cortex-m3")
set(CMAKE_C_FLAGS "${TARGET_FLAGS} -Wall -fdata-sections -ffunction-sections")
set(CMAKE_C_FLAGS_DEBUG   "-O0 -g3")
set(CMAKE_C_FLAGS_RELEASE "-Os -g0")

set(CMAKE_EXE_LINKER_FLAGS
    "${TARGET_FLAGS}
     -T ${CMAKE_SOURCE_DIR}/STM32F103XX_FLASH.ld
     --specs=nano.specs
     -Wl,-Map=${CMAKE_PROJECT_NAME}.map
     -Wl,--gc-sections
     -Wl,--print-memory-usage")

LLVM 工具链文件

LLVM 的配置有趣的地方:编译器是 clang,但链接器仍然是 arm-none-eabi-gcc(因为 LLVM 的 ld.lld 对 ARM 嵌入式链接脚本支持不如 GCC 的 GNU ld 完善):

# cmake/llvm-arm-none-eabi.cmake
set(CMAKE_C_COMPILER   clang)
set(CMAKE_CXX_COMPILER clang++)
set(CMAKE_LINKER       arm-none-eabi-gcc)  # 重要!用 GCC 做链接
set(CMAKE_OBJCOPY      llvm-objcopy)

set(CMAKE_C_COMPILER_TARGET   arm-none-eabi)
set(CMAKE_CXX_COMPILER_TARGET arm-none-eabi)

# 自动探测 GCC ARM 的 sysroot 和 libgcc 路径
execute_process(
    COMMAND arm-none-eabi-gcc -print-sysroot
    OUTPUT_VARIABLE ARM_NONE_EABI_SYSROOT
    OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(
    COMMAND arm-none-eabi-gcc -print-libgcc-file-name
    OUTPUT_VARIABLE ARM_NONE_EABI_LIBGCC)

set(CMAKE_SYSROOT "${ARM_NONE_EABI_SYSROOT}")
set(MCU_FLAGS -mcpu=cortex-m3 -mthumb -mfloat-abi=soft)
set(CMAKE_C_FLAGS_INIT "${MCU_FLAGS} -Wall -fdata-sections -ffunction-sections")
set(CMAKE_C_FLAGS_DEBUG   "-O0 -g3")
set(CMAKE_C_FLAGS_RELEASE "-Oz -g0")    # LLVM 用 -Oz 比 -Os 更激进

# 链接时回退到 GCC
set(CMAKE_C_LINK_EXECUTABLE
    "arm-none-eabi-gcc <LINK_FLAGS> <OBJECTS> -o <TARGET> <LINK_LIBRARIES>")

关键差异

维度 GCC ARM LLVM/Clang
编译器 arm-none-eabi-gcc clang --target=arm-none-eabi
链接器 arm-none-eabi-g++ arm-none-eabi-gcc(回退)
优化 -O0 / -Os -O0 / -Oz(更小)
sysroot 内置 从 GCC 探测
启动文件 GCC asm 兼容(同一份 startup.s)
编译数据库 CMAKE_EXPORT_COMPILE_COMMANDS 同上,clangd 兼容

LLVM 的 sysroot 探测

LLVM for ARM 需要 GCC ARM 的 C 运行时库(libgcc、libc_nano)。配置中通过 execute_process 在 CMake 配置阶段自动探测 GCC ARM 的安装路径:

execute_process(
    COMMAND arm-none-eabi-gcc -print-sysroot
    OUTPUT_VARIABLE ARM_NONE_EABI_SYSROOT)
execute_process(
    COMMAND arm-none-eabi-gcc -print-libgcc-file-name
    OUTPUT_VARIABLE ARM_NONE_EABI_LIBGCC)
get_filename_component(ARM_NONE_EABI_GCC_LIB_DIR
    "${ARM_NONE_EABI_LIBGCC}" DIRECTORY)

set(CMAKE_SYSROOT "${ARM_NONE_EABI_SYSROOT}")
# 链接时自动找到正确的 libgcc.a 路径

这避免了硬编码路径。只要 GCC ARM 工具链在 PATH 中,LLVM 就能自动找到它所需的运行时库。

GCC 用于日常开发和调试(稳定)。LLVM 用于发布构建和静态分析(更激进的优化 + clang-tidy / clang-analyzer)。两个工具链文件不到 70 行,但让项目的构建灵活度翻倍。

此作者没有提供个人介绍。
最后更新于 2026-05-30