24、平台检测:WIN32/APPLE/UNIX/LINUX等平台变量、编译器ID检测、架构检测、操作系统版本

跨平台构建,说白了就是让你的代码能在不同操作系统上顺利编译。我刚开始做跨平台项目时,最头疼的就是——同一份代码,在Windows上跑得好好的,到了macOS上就报错,到了Linux上又换一种报错方式。后来我学乖了:先搞清楚你在哪,再决定怎么干。

CMake提供了非常丰富的平台检测机制。今天我们就来把这些工具捋一遍。你掌握了这些,跨平台就不再是玄学。

24.1 平台变量:WIN32、APPLE、UNIX、LINUX

CMake在配置阶段会自动设置一些变量,告诉你当前是什么操作系统。这些变量是布尔值,可以直接用在条件判断里。

变量名 含义 典型平台
WIN32 Windows系统(32位或64位) Windows、MinGW、MSVC
APPLE Apple系统(macOS、iOS等) macOS、iOS、tvOS
UNIX 类Unix系统(包括Linux、macOS) Linux、macOS、FreeBSD
LINUX Linux系统 Ubuntu、CentOS、Debian
ANDROID Android系统 Android NDK
CYGWIN Cygwin环境 Windows + Cygwin
MSYS MSYS环境 Windows + MSYS2

举个例子,你想在Windows上链接ws2_32库,在Linux上链接pthread,可以这样写:

if(WIN32)
    target_link_libraries(myapp PRIVATE ws2_32)
endif()

if(UNIX AND NOT APPLE)
    target_link_libraries(myapp PRIVATE pthread)
endif()

注意一个小坑:APPLE为真时,UNIX也为真。因为macOS本质上是Unix-like系统。所以如果你只想针对Linux,必须写成UNIX AND NOT APPLE。我当年第一次写跨平台代码时,就因为这个条件写错了,导致macOS上链接了Linux特有的库,编译直接崩了。

核心原则:先判断大范围(UNIX),再缩小范围(APPLE、LINUX),最后处理特殊情况(ANDROID、CYGWIN)。

24.2 编译器ID检测

不同编译器对C++标准的支持程度不同,警告选项也不同。CMake提供了CMAKE_<LANG>_COMPILER_ID变量来检测编译器。

编译器ID 对应编译器
MSVC Microsoft Visual C++
GNU GCC(GNU Compiler Collection)
Clang Clang / LLVM
AppleClang Apple 版本的 Clang(macOS自带)
Intel Intel C++ Compiler
MSYS MSYS2 环境下的 GCC

使用方式很简单:

if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
    target_compile_options(myapp PRIVATE /W4 /utf-8)
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
    target_compile_options(myapp PRIVATE -Wall -Wextra -Wpedantic)
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
    target_compile_options(myapp PRIVATE -Wall -Wextra -Wpedantic)
endif()

我个人习惯把编译器检测和平台检测结合起来用。比如在Windows上用MSVC时,需要处理Unicode问题;在Windows上用MinGW时,又得注意路径分隔符。分开处理会更清晰。

小技巧:可以用CMAKE_CXX_COMPILER_VERSION获取编译器版本号。比如GCC 7以下不支持C++17,你可以用版本号做更精细的控制。

24.3 架构检测

你的代码是编译给x86还是ARM?是32位还是64位?这些信息CMake也能告诉你。

常用的架构变量:

  • CMAKE_SYSTEM_PROCESSOR:目标系统的处理器架构(如x86_64、aarch64、armv7l)
  • CMAKE_SIZEOF_VOID_P:指针大小,等于4表示32位,等于8表示64位

举个例子:

if(CMAKE_SIZEOF_VOID_P EQUAL 8)
    message(STATUS "64-bit build")
else()
    message(STATUS "32-bit build")
endif()

if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|ARM64")
    message(STATUS "ARM64 architecture")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64")
    message(STATUS "x86_64 architecture")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "i386|i686")
    message(STATUS "x86 32-bit architecture")
endif()

这里有个坑:CMAKE_SYSTEM_PROCESSOR的值在不同平台上格式不一样。比如x86_64在Linux上是"x86_64",在Windows上可能是"AMD64"。所以用MATCHES做正则匹配比用STREQUAL更稳妥。

我记得有一次帮客户排查问题,他们的代码在ARM服务器上编译不过。一查发现是用了x86特有的内联汇编。后来加了架构检测,在ARM上自动切换到C语言实现,问题就解决了。

24.4 操作系统版本检测

有时候你还需要知道操作系统的具体版本。比如某个API只在Windows 10以上才支持,或者某个库只在Ubuntu 20.04以上才有预编译包。

CMake提供了以下变量:

  • CMAKE_SYSTEM_NAME:操作系统名称(Windows、Linux、Darwin等)
  • CMAKE_SYSTEM_VERSION:操作系统版本号
  • CMAKE_SYSTEM:完整的系统描述字符串

示例:

if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
    if(CMAKE_SYSTEM_VERSION VERSION_GREATER_EQUAL "10.0.0")
        message(STATUS "Windows 10 or later")
    else()
        message(STATUS "Windows 8.1 or earlier")
    endif()
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
    # 读取 /etc/os-release 获取发行版信息
    file(STRINGS "/etc/os-release" OS_RELEASE REGEX "^ID=")
    string(REGEX REPLACE "^ID=(.*)" "\\1" LINUX_DISTRO "${OS_RELEASE}")
    message(STATUS "Linux distribution: ${LINUX_DISTRO}")
endif()

注意:CMAKE_SYSTEM_VERSION在Windows上是内核版本号(如10.0.19041),不是Windows的营销版本号(如Windows 10 21H2)。你需要自己映射。我一般写个辅助函数来做这件事。

注意:在Linux上,CMAKE_SYSTEM_VERSION返回的是内核版本,不是发行版版本。如果你想获取Ubuntu 22.04这样的信息,需要手动解析/etc/os-release文件。

24.5 综合实战:一个跨平台检测模块

下面我把上面讲的内容整合成一个完整的检测模块。你可以把它放在项目的cmake/目录下,在CMakeLists.txtinclude进来。

# cmake/PlatformDetect.cmake
# 平台检测模块

# 1. 操作系统检测
if(WIN32)
    set(PLATFORM_OS "Windows")
elseif(APPLE)
    set(PLATFORM_OS "Apple")
elseif(LINUX)
    set(PLATFORM_OS "Linux")
elseif(ANDROID)
    set(PLATFORM_OS "Android")
else()
    set(PLATFORM_OS "Unknown")
endif()

# 2. 编译器检测
if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
    set(PLATFORM_COMPILER "MSVC")
    set(PLATFORM_COMPILER_VERSION ${CMAKE_CXX_COMPILER_VERSION})
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
    set(PLATFORM_COMPILER "GCC")
    set(PLATFORM_COMPILER_VERSION ${CMAKE_CXX_COMPILER_VERSION})
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
    set(PLATFORM_COMPILER "Clang")
    set(PLATFORM_COMPILER_VERSION ${CMAKE_CXX_COMPILER_VERSION})
endif()

# 3. 架构检测
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
    set(PLATFORM_ARCH "64-bit")
else()
    set(PLATFORM_ARCH "32-bit")
endif()

if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|ARM64")
    set(PLATFORM_ARCH "${PLATFORM_ARCH} ARM")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64")
    set(PLATFORM_ARCH "${PLATFORM_ARCH} x86")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "i386|i686")
    set(PLATFORM_ARCH "${PLATFORM_ARCH} x86")
endif()

# 4. 输出检测结果
message(STATUS "Platform: ${PLATFORM_OS}")
message(STATUS "Compiler: ${PLATFORM_COMPILER} ${PLATFORM_COMPILER_VERSION}")
message(STATUS "Architecture: ${PLATFORM_ARCH}")
message(STATUS "System version: ${CMAKE_SYSTEM_VERSION}")

CMakeLists.txt中使用:

include(cmake/PlatformDetect.cmake)

# 根据检测结果设置编译选项
if(PLATFORM_OS STREQUAL "Windows" AND PLATFORM_COMPILER STREQUAL "MSVC")
    add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
endif()

if(PLATFORM_ARCH MATCHES "ARM")
    add_compile_options(-march=armv8-a)
endif()

这个模块我一直在用,基本覆盖了90%的跨平台场景。你把它加到项目里,每次配置时都能看到清晰的平台信息,排查问题会方便很多。

24.6 知识体系总览

下面这张图帮你梳理了平台检测的核心逻辑:

CMake 平台检测知识体系 平台检测 操作系统检测 WIN32 / APPLE / UNIX LINUX / ANDROID / CYGWIN 编译器ID检测 MSVC / GNU / Clang AppleClang / Intel 架构检测 CMAKE_SYSTEM_PROCESSOR CMAKE_SIZEOF_VOID_P 综合判断:条件组合 + 版本检测 最终输出:编译选项 / 链接库 / 宏定义

说白了,平台检测就是三个维度:你在哪个操作系统上、用哪个编译器、目标架构是什么。把这三个维度搞清楚,你的CMake脚本就能做到「见人说人话,见鬼说鬼话」。我在实际项目中,通常会在项目根目录的CMakeLists.txt开头就做一次完整的平台检测,然后把结果缓存起来,后续所有子目录都能直接用。

我的建议:把平台检测逻辑单独抽成一个cmake模块文件,用include()引入。这样你的主CMakeLists.txt会干净很多,而且这个模块可以在多个项目间复用。

好了,平台检测就讲到这里。你把这些变量和用法记熟,跨平台构建就算入门了。下一节我们聊聊生成器表达式——那才是CMake真正灵活的地方。


公众号:蓝海资料掘金营,微信deep3321