第44例:平台检测——CMAKE_SYSTEM_NAME 判断 Windows/Linux/macOS
跨平台开发,说白了就是「一套代码,到处编译」。但不同操作系统之间,API 不同、库路径不同、编译器行为也不同。CMake 怎么知道当前在哪个平台上?靠的就是 CMAKE_SYSTEM_NAME 这个内置变量。
我个人习惯在项目一开始就做平台检测,而不是等到编译报错了再回头改。你想想看,如果代码里写了 Windows 特有的 Sleep(),拿到 Linux 上编译,那错误信息能让你排查半天。提前判断平台,把条件分支写好,后面就省心了。
CMAKE_SYSTEM_NAME 是什么?
CMake 在配置阶段会自动检测当前操作系统,并把结果存到 CMAKE_SYSTEM_NAME 里。它的值是一个字符串,常见的有:
| 操作系统 | CMAKE_SYSTEM_NAME 值 |
|---|---|
| Windows | Windows |
| Linux | Linux |
| macOS | Darwin |
| Android | Android |
| iOS | iOS |
| FreeBSD | FreeBSD |
注意 macOS 的值是 Darwin,不是 macOS 也不是 OSX。我记得刚用 CMake 时,第一反应写了个 if(CMAKE_SYSTEM_NAME STREQUAL "macOS"),结果死活不生效。后来查文档才发现是 Darwin——嗯,这个坑我替你们踩过了。
基本用法:if 判断平台
最直接的方式就是用 if() 命令做字符串比较。来看一个典型例子:
cmake_minimum_required(VERSION 3.10)
project(PlatformCheck)
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
message(STATUS "当前平台:Windows")
# Windows 特有的设置
set(PLATFORM_LIBS ws2_32)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
message(STATUS "当前平台:Linux")
# Linux 特有的设置
set(PLATFORM_LIBS pthread)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
message(STATUS "当前平台:macOS")
# macOS 特有的设置
set(PLATFORM_LIBS "")
else()
message(WARNING "未知平台:${CMAKE_SYSTEM_NAME}")
endif()
message(STATUS "平台相关库:${PLATFORM_LIBS}")
这段代码会在配置阶段打印出当前平台,并设置对应的链接库。我在项目中经常用这种模式来区分 pthread(Linux)、ws2_32(Windows)和 CoreFoundation(macOS)等系统库。
更简洁的写法:使用平台简写变量
CMake 还提供了一些简写变量,不用每次都写字符串比较:
WIN32:在 Windows 上为TRUE(包括 64 位)UNIX:在 Linux、macOS、BSD 等类 Unix 系统上为TRUEAPPLE:在 macOS、iOS 等 Apple 系统上为TRUE
举个例子:
if(WIN32)
message("这是 Windows 系统")
elseif(APPLE)
message("这是 Apple 系统")
elseif(UNIX)
message("这是类 Unix 系统(非 Apple)")
endif()
我个人更推荐用 CMAKE_SYSTEM_NAME 做精确判断,因为 UNIX 和 APPLE 的范围比较宽,有时候会误判。比如你想专门给 Linux 写逻辑,用 if(UNIX) 会把 macOS 也带进来,这时候就得再加一层判断。
实战:根据平台设置编译选项
跨平台项目里,不同平台的编译器选项往往不一样。比如 Windows 上常用 /W4 开启警告,Linux 和 macOS 上用 -Wall -Wextra。下面是一个完整的例子:
cmake_minimum_required(VERSION 3.10)
project(CrossPlatformDemo CXX)
# 根据平台设置编译选项
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
# MSVC 风格
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /EHsc")
# 定义 Windows 平台宏
add_definitions(-DPLATFORM_WINDOWS)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
# GCC/Clang 风格
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pthread")
add_definitions(-DPLATFORM_LINUX)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -stdlib=libc++")
add_definitions(-DPLATFORM_MACOS)
endif()
# 添加源文件
add_executable(myapp main.cpp)
# 平台相关的链接库
target_link_libraries(myapp PRIVATE ${PLATFORM_LIBS})
这里我用了 add_definitions() 来定义预处理宏,这样在 C++ 代码里就可以用 #ifdef PLATFORM_WINDOWS 来写平台相关的代码了。不过要注意,add_definitions() 会影响整个项目,如果只想对某个目标生效,建议用 target_compile_definitions()。
避坑指南:交叉编译时的 CMAKE_SYSTEM_NAME
我曾经在做一个嵌入式项目时,用 CMake 交叉编译 ARM Linux 程序。在工具链文件里手动设置了 set(CMAKE_SYSTEM_NAME Linux),结果 CMake 检测到宿主机是 Windows,就自动把 CMAKE_SYSTEM_NAME 改回了 Windows。
为什么会这样?因为交叉编译时,你必须显式地在工具链文件里设置 CMAKE_SYSTEM_NAME,并且要放在 project() 命令之前。否则 CMake 会认为你在为本机编译,覆盖掉你的设置。
正确的做法:
# 工具链文件 toolchain.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
# 然后在 CMakeLists.txt 中
cmake_minimum_required(VERSION 3.10)
# 这里 project() 会读取上面设置的 CMAKE_SYSTEM_NAME
project(CrossProject)
# 此时 CMAKE_SYSTEM_NAME 就是 Linux,而不是宿主机的 Windows
平台检测的完整知识结构
下面这张图总结了平台检测的核心逻辑和常用变量:
常用平台检测模式总结
根据我多年的项目经验,下面几种模式最常用:
- 精确匹配:
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")—— 适合需要精确区分平台的场景 - 范围匹配:
if(WIN32)或if(UNIX)—— 适合大类区分,比如 Windows vs 非 Windows - 组合判断:
if(UNIX AND NOT APPLE)—— 适合「Linux 但不包括 macOS」的场景 - 交叉编译检测:
if(CMAKE_CROSSCOMPILING)—— 配合CMAKE_SYSTEM_NAME使用
message(STATUS "CMAKE_SYSTEM_NAME = ${CMAKE_SYSTEM_NAME}"),配置时就能看到当前平台,方便调试。
project() 命令之前依赖 CMAKE_SYSTEM_NAME 做复杂逻辑。因为 project() 会触发编译器检测,可能会修改这个变量。如果你需要在 project() 之前判断平台,建议用 CMAKE_HOST_SYSTEM_NAME(宿主机的系统名)。
好了,这一例的内容就这些。平台检测是跨平台 CMake 的基石,掌握了 CMAKE_SYSTEM_NAME,后面再处理平台相关的库路径、编译器选项、预处理宏,就都顺理成章了。