3、指定交叉编译器:CMAKE_C_COMPILER与CMAKE_CXX_COMPILER的配置

好,咱们接着往下走。上一章我们把工具链文件的基本骨架搭好了,也知道了怎么让 CMake 加载它。但说实话,那个文件还是个空壳子——最核心的东西还没填:编译器路径。

你想想看,交叉编译和本地编译最大的区别在哪?就是编译器不一样。本地编译你用 gcc,交叉编译你得用 arm-linux-gnueabihf-gcc。CMake 怎么知道用哪个?就是靠两个变量:CMAKE_C_COMPILER 和 CMAKE_CXX_COMPILER。

核心概念:CMAKE_C_COMPILER 指定 C 编译器,CMAKE_CXX_COMPILER 指定 C++ 编译器。这两个变量必须在 project() 命令之前设置,否则 CMake 会先用默认的本地编译器探测一遍,后面再改就晚了。

3.1 直接指定编译器路径

最直接的方式,就是在工具链文件里把路径写死。我个人习惯用 set() 命令加 CACHE PATH 的方式,这样用户还能在命令行覆盖。

# 方式一:直接写死路径
set(CMAKE_C_COMPILER   /usr/bin/arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER /usr/bin/arm-linux-gnueabihf-g++)

# 方式二:带 CACHE 变量(我推荐这个)
set(CMAKE_C_COMPILER   /usr/bin/arm-linux-gnueabihf-gcc   CACHE PATH "C compiler")
set(CMAKE_CXX_COMPILER /usr/bin/arm-linux-gnueabihf-g++   CACHE PATH "C++ compiler")

这里有个坑,我曾经踩过:路径里不能有空格。Windows 上交叉编译时,如果编译器装在 C:\Program Files\ 下面,CMake 会解析出错。解决办法是用 file(TO_CMAKE_PATH) 转换一下,或者干脆装到无空格的路径下。

3.2 查找交叉编译器路径

实际项目中,编译器路径往往不是固定的。不同的开发板 SDK,安装位置五花八门。我一般用两种方式来找:

方式一:环境变量 + find_program

让用户设置一个环境变量,比如 CROSS_COMPILER_PATH,然后在工具链文件里用 find_program 去搜。

# 先尝试从环境变量获取路径
if(DEFINED ENV{CROSS_COMPILER_PATH})
    set(TOOLCHAIN_PATH "$ENV{CROSS_COMPILER_PATH}")
else()
    # 默认路径,给个提示
    set(TOOLCHAIN_PATH "/opt/toolchains/arm-cortex_a9-linux-gnueabihf/bin")
    message(STATUS "CROSS_COMPILER_PATH not set, using default: ${TOOLCHAIN_PATH}")
endif()

# 查找编译器
find_program(CMAKE_C_COMPILER
    NAMES arm-linux-gnueabihf-gcc
    PATHS ${TOOLCHAIN_PATH}
    NO_DEFAULT_PATH
)

find_program(CMAKE_CXX_COMPILER
    NAMES arm-linux-gnueabihf-g++
    PATHS ${TOOLCHAIN_PATH}
    NO_DEFAULT_PATH
)

# 没找到就报错
if(NOT CMAKE_C_COMPILER)
    message(FATAL_ERROR "C compiler not found in ${TOOLCHAIN_PATH}")
endif()

注意那个 NO_DEFAULT_PATH,加上它之后,find_program 就不会去系统默认路径里搜了。为什么要加?因为系统路径里可能也有个 gcc,但那不是交叉编译器,搜到了反而坏事。

方式二:从 SDK 包中自动提取

有些 SDK 会把编译器放在固定的相对路径下。比如 TI 的 Processor SDK,编译器在 linux-devkit/sysroots/x86_64-arago-linux/usr/bin/arm-linux-gnueabihf-。我写过这样的逻辑:

# 假设用户设置了 SDK_ROOT
if(NOT SDK_ROOT)
    message(FATAL_ERROR "Please set SDK_ROOT to your SDK installation path")
endif()

# 构造编译器路径
set(TOOLCHAIN_BIN_DIR "${SDK_ROOT}/linux-devkit/sysroots/x86_64-arago-linux/usr/bin")
set(CMAKE_C_COMPILER   "${TOOLCHAIN_BIN_DIR}/arm-linux-gnueabihf-gcc")
set(CMAKE_CXX_COMPILER "${TOOLCHAIN_BIN_DIR}/arm-linux-gnueabihf-g++")

这种方式的好处是,用户只需要设置一个 SDK_ROOT,剩下的路径自动拼出来。但前提是你得熟悉 SDK 的目录结构。

3.3 验证编译器是否可用

路径配好了,不代表编译器就能用。我见过太多人配完路径就以为万事大吉,结果 cmake 一跑就报错。所以,验证这一步不能省。

CMake 在 project() 阶段会自动做一次编译器检测。它会尝试用你指定的编译器编译一个简单的测试程序,如果编译失败,CMake 会直接报错退出。但有时候错误信息不够直观,我习惯在工具链文件里加一段主动检测:

# 主动检测编译器是否可用
include(CheckCSourceCompiles)
include(CheckCXXSourceCompiles)

# 写一个简单的测试程序
set(CMAKE_REQUIRED_FLAGS "-Wall -Wextra")

check_c_source_compiles("
    #include <stdio.h>
    int main() {
        printf(\"Hello from cross compiler\\n\");
        return 0;
    }
" C_COMPILER_WORKS)

if(NOT C_COMPILER_WORKS)
    message(FATAL_ERROR "C compiler check failed: ${CMAKE_C_COMPILER}")
endif()

# C++ 同理
check_cxx_source_compiles("
    #include <iostream>
    int main() {
        std::cout << \"Hello from cross C++ compiler\" << std::endl;
        return 0;
    }
" CXX_COMPILER_WORKS)

小技巧:如果检测失败,可以用 execute_process 直接运行编译器,看看它输出的错误信息。比如:

execute_process(COMMAND ${CMAKE_C_COMPILER} --version
    RESULT_VARIABLE result
    OUTPUT_VARIABLE output
    ERROR_VARIABLE error)
if(NOT result EQUAL 0)
    message(FATAL_ERROR "Compiler not executable: ${error}")
endif()

3.4 常见问题与避坑

问题 现象 解决办法
编译器路径写错 CMake 报 "CMAKE_C_COMPILER not set" 用 file(TO_CMAKE_PATH) 标准化路径,或者用 find_program 自动搜索
编译器版本不匹配 编译通过但链接时报错,比如找不到 crt0.o 检查工具链的 sysroot 是否设置正确,后面会讲 CMAKE_SYSROOT
权限问题 编译器文件没有执行权限 chmod +x 给编译器加上执行权限
32位 vs 64位 64位系统上运行32位交叉编译器,报 "No such file or directory" 安装 32 位兼容库:sudo apt install lib32z1 lib32ncurses5

我曾经遇到过一个特别隐蔽的问题:编译器路径里包含软链接,而软链接指向的目录在 cmake 运行时被卸载了。结果 cmake 检测时能通过(因为软链接还在),但实际编译时却报错。所以,我建议在工具链文件里用 get_filename_component(REAL_PATH ... REALPATH) 把路径解析成真实路径再使用。

3.5 本章知识体系

下面这张图,把编译器配置的整个流程串起来了。你可以对照着看,每一步都做了什么。

交叉编译器配置流程 交叉编译器配置流程 1. 确定编译器名称 2. 查找编译器路径 环境变量 / find_program SDK 自动提取 3. 设置 CMAKE_C/CXX_COMPILER 4. 验证编译器可用性 5. 通过?继续 / 失败?报错 注意:所有配置必须在 project() 命令之前完成,否则 CMake 会使用默认编译器

说白了,配置交叉编译器就这五步。但每一步都有细节,尤其是路径查找和验证环节,最容易出问题。我个人建议,在工具链文件里把验证逻辑写完整,宁可多花几分钟检测,也不要等编译到一半才发现编译器不对。