23、条件判断:if/elseif/else/endif的常用条件,如WIN32、UNIX、MSVC

条件判断,说白了就是让CMake根据当前环境“见人说人话,见鬼说鬼话”。

我在刚接触CMake时,总觉得这玩意儿不就是个构建工具嘛,搞这么复杂干嘛?后来接手一个跨平台项目,Windows上跑得好好的,换到Linux就崩了。嗯,从那以后,我再也不敢小看条件判断了。

一、条件判断的基本语法

CMake的条件判断,跟大多数语言差不多。结构是:

if(条件)
    # 条件为真时执行
elseif(另一个条件)
    # 另一个条件为真时执行
else()
    # 以上都不满足时执行
endif()

注意,endif()后面要加括号。我见过不少新手写成endif,结果CMake直接报错。这个坑,我当年也踩过。

核心要点:条件判断的括号不能省,ifelseifelseendif都要带括号。

二、最常用的系统判断条件

我个人习惯把系统判断条件分成三类:操作系统、编译器、构建类型。咱们一个一个说。

2.1 操作系统相关

条件 说明 典型场景
WIN32 Windows系统(包括32位和64位) 调用Windows API、链接WinSock库
UNIX 类Unix系统(Linux、macOS、BSD等) 使用POSIX接口、链接pthread库
APPLE macOS或iOS系统 使用CoreFoundation框架
CYGWIN Cygwin环境 特殊交叉编译场景

举个例子,我在项目中遇到过需要区分Windows和Linux的情况:

if(WIN32)
    target_link_libraries(myapp ws2_32)
    target_compile_definitions(myapp PRIVATE PLATFORM_WINDOWS)
elseif(UNIX)
    target_link_libraries(myapp pthread)
    target_compile_definitions(myapp PRIVATE PLATFORM_UNIX)
endif()

你想想看,如果不加这个判断,Windows上链接pthread库,那编译肯定过不去。

小提示:WIN32在64位Windows上也是真值。别被名字骗了,它不表示“32位系统”。

2.2 编译器相关

条件 说明 典型场景
MSVC Microsoft Visual C++编译器 处理MSVC特有的编译选项
MINGW MinGW编译器 Windows上使用GCC
CMAKE_COMPILER_IS_GNUCC GCC编译器(C语言) 设置GCC特有的警告选项
CMAKE_COMPILER_IS_GNUCXX GCC编译器(C++语言) 设置GCC特有的C++选项

我曾经在Windows上用MSVC编译一个项目,结果发现MSVC不支持-Wall这种GCC风格的警告选项。解决办法就是加个判断:

if(MSVC)
    target_compile_options(myapp PRIVATE /W4)
elseif(CMAKE_COMPILER_IS_GNUCXX)
    target_compile_options(myapp PRIVATE -Wall -Wextra)
endif()

注意:MSVCWIN32不是一回事。WIN32表示操作系统是Windows,MSVC表示编译器是Visual C++。你可以在Windows上用MinGW,这时候WIN32为真,但MSVC为假。

三、条件判断的进阶用法

3.1 变量是否定义

有时候我们需要检查某个变量是否被定义:

if(DEFINED MY_VARIABLE)
    message("MY_VARIABLE 已经被定义了")
else()
    message("MY_VARIABLE 还没定义")
endif()

这个在检查用户是否传入了某个选项时特别有用。

3.2 版本号比较

CMake支持直接比较版本号:

if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "7.0")
    target_compile_features(myapp PRIVATE cxx_std_17)
endif()

常用的比较操作符有:VERSION_LESSVERSION_GREATERVERSION_EQUALVERSION_LESS_EQUALVERSION_GREATER_EQUAL

3.3 逻辑运算

条件可以组合使用:

if(WIN32 AND MSVC)
    # Windows + MSVC 的特殊处理
endif()

if(UNIX OR APPLE)
    # Unix或macOS的处理
endif()

if(NOT MSVC)
    # 非MSVC编译器的处理
endif()

重要:逻辑运算符ANDORNOT必须大写。写成小写andornot,CMake会当成普通变量名处理。

四、知识体系图

下面这张图总结了条件判断的核心逻辑:

CMake条件判断知识体系 if() / elseif() / else() / endif() 操作系统判断 编译器判断 其他条件 WIN32 UNIX APPLE MSVC MINGW DEFINED VERSION 链接系统库 / 设置宏定义 设置编译器选项 / 特性 检查变量 / 比较版本 逻辑运算:AND / OR / NOT 组合使用

五、避坑指南

我总结几个常见的坑,都是我曾经踩过的:

  1. 大小写问题:CMake的条件关键字不区分大小写,但变量名区分。比如WIN32win32都行,但MY_VARmy_var是两回事。
  2. 引号问题:如果变量值可能为空,最好加引号。比如if("${MY_VAR}" STREQUAL "hello"),避免变量未定义时报错。
  3. elseif的写法:elseif(),不是else if()。中间没有空格。这个我写错过好几次。
  4. endif的括号:一定要写endif(),不能只写endif。老版本的CMake可能允许,但新版本已经废弃了这种写法。

曾经踩过的坑:我在一个项目里用了if(MSVC)来判断Windows平台,结果用户用MinGW编译时,这个条件为假,导致链接了一堆不兼容的库。后来我才明白,应该用if(WIN32)判断操作系统,用if(MSVC)判断编译器。两者不能混用。

六、实战建议

最后,给几个我个人习惯的建议:

  • 写条件判断时,先写最具体的条件,再写通用的条件。比如先判断MSVC,再判断WIN32
  • 复杂条件用ANDOR组合时,加括号明确优先级。虽然CMake的优先级是固定的,但加括号能让代码更易读。
  • 条件判断里尽量用CMake内置的变量,比如CMAKE_SYSTEM_NAMECMAKE_CXX_COMPILER_ID,这些比自定义变量更可靠。
  • 如果条件判断太多,考虑用cmake_policy或者generator expressions来简化。生成器表达式在复杂场景下比if/elseif更优雅。

条件判断是CMake的基石。掌握了它,你就能写出真正跨平台的构建脚本。下次遇到跨平台问题,先想想:是不是条件判断没写对?


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