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特有的库,编译直接崩了。
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)。你需要自己映射。我一般写个辅助函数来做这件事。
CMAKE_SYSTEM_VERSION返回的是内核版本,不是发行版版本。如果你想获取Ubuntu 22.04这样的信息,需要手动解析/etc/os-release文件。
24.5 综合实战:一个跨平台检测模块
下面我把上面讲的内容整合成一个完整的检测模块。你可以把它放在项目的cmake/目录下,在CMakeLists.txt中include进来。
# 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脚本就能做到「见人说人话,见鬼说鬼话」。我在实际项目中,通常会在项目根目录的CMakeLists.txt开头就做一次完整的平台检测,然后把结果缓存起来,后续所有子目录都能直接用。
include()引入。这样你的主CMakeLists.txt会干净很多,而且这个模块可以在多个项目间复用。
好了,平台检测就讲到这里。你把这些变量和用法记熟,跨平台构建就算入门了。下一节我们聊聊生成器表达式——那才是CMake真正灵活的地方。
公众号:蓝海资料掘金营,微信deep3321