【CMake 教程】常用函数与构建案例解析(三)

发布于:2025-02-24 ⋅ 阅读:(19) ⋅ 点赞:(0)

一、CMake 常用函数简析

1. 条件判断 if() / elseif() / else()

在 CMake 脚本中,条件判断是控制逻辑的重要工具。if() 支持多种比较语句,包括数值、字符串、布尔值和变量存在性等。在条件满足时执行特定逻辑代码,下面是典型语法:

if(VARIABLE)
    # 当 VARIABLE 存在且为非空时执行
elseif(${VARIABLE} STREQUAL "value")
    # 当 VARIABLE 等于指定字符串 "value" 时执行
else()
    # 以上都不满足时执行
endif()

1.1 基本语句表达

  • if(<constant>)

    如果常量是 1ONYESTRUEY 或非零数字(包括浮点数),则为 True 。如果常量是 0OFFNOFALSENIGNORENOTFOUND空字符串或以后缀 -NOTFOUND 结尾,则为 False 。

  • if(<variable>)

    如果给定的变量被定义为非 false 常量的值,则为 True 。否则为 False ,包括变量未定义的情况。

  • if(<string>)

    引用的字符串始终计算为 false,除了下面这些情况:

    • 字符串的值是 true 常量之一
    • 策略 CMP0054 未设置为 NEW ,并且字符串的值恰好是受 CMP0054 行为影响的变量名。(本章末尾将对 CMP0054 策略进行介绍)

那么有个问题:

set(IS_STRING "This is string")
if(IS_STRING)
    message(OK)
endif()

请问会输出“OK”吗?

1.2 逻辑运算符

在 CMake 中,逻辑运算符被广泛应用于条件判断,帮助我们根据变量状态或构建条件调整逻辑流转。主要运算符如下:

运算符 含义 示例
NOT 逻辑非,取反 NOT IS_EMPTY_STRING
AND 逻辑与,所有条件为真 IS_TRUE AND NOT IS_FALSE
OR 逻辑或,任一条件为真 IS_TRUE OR IS_FALSE

我们直接通过代码来介绍:

option(IS_TRUE "This is true" ON)		# True
option(IS_FALSE "This is false" OFF)	# False
set(IS_EMPTY_STRING "")					# False
set(IS_STRING "This is a string")		# True

if(NOT IS_EMPTY_STRING)	# True
    message("IS_EMPTY_STRING is empty")
endif()

if(IS_STRING)	# True
    message("IS_STRING is: \"${IS_STRING}\"")
endif()

if(IS_TRUE AND NOT IS_FALSE)	# True
    message("IS_TRUE is true and IS_FALSE is false")
endif()

if(IS_FALSE OR NOT IS_TRUE)	# False
    message("IS_FALSE is true or IS_TRUE is false")
elseif(IS_TRUE)	# True
    message("IS_TRUE is true")
else()	# 这句到不了
    message("IS_FALSE is true")
endif()

1.3 存在检查

下面列举几种比较常用的检查:

1.3.1 if(DEFINED <name>|CACHE{<name>}|ENV{<name>})

如果定义了具有给定 <name>变量缓存变量环境变量,则为 True 。变量的值无关紧要

1.3.2 if(TARGET <target-name>)

用于判断指定目标是否已通过 add_executable()add_library()add_custom_target() 定义,并且无论目标是在哪个目录(包括子目录或父目录)中定义的都能检测到。

1.3.3 if(<variable|string> IN_LIST <variable>)(3.3版本后才加入的)

如果给定元素包含在命名列表变量中,则为 True 。

1.4 文件操作

操作不少,这里只列出常用的(至少我在生产级别项目中常用的),想了解更多可以到官方教程看。

1.4.1 if(EXISTS <path-to-file-or-directory>)

如果指定的文件或目录存在且可读,则返回 True 。Linux 下,~/ 不会被解析为主目录。

为了准确,尽可能使用绝对路径,例如:if(EXISTS ${CMAKE_BINARY_DIR}/Debug/VarApp.exe)

1.4.2 if(IS_DIRECTORY <path>)

如果 path 是目录,则为 True 。同样,path 传入绝对路径,避免出问题。

1.4.3 if(IS_ABSOLUTE <path>)

如果给定路径是绝对路径,则为 True 。注意以下特殊情况:

  • 空的 path 计算结果为 False。
  • 在 Windows 主机上,任何以驱动器号和冒号(例如 C: )、正斜杠或反斜杠开头的 path 都将计算为 True。这意味着像 C:no\base\dir 这样的路径将计算为 True,即使路径的非驱动器部分是相对的。
  • 在非 Windows 主机上,任何以波浪号开头的 path~ )都会被计算为 True。

1.5 比较

我直接把所有情况列出来吧,大家应该都能看懂:


if(<variable|string> MATCHES )

if(<variable|string> LESS <variable|string>)

if(<variable|string> GREATER <variable|string>)

if(<variable|string> EQUAL <variable|string>)

if(<variable|string> LESS_EQUAL <variable|string>)(在 3.7 版本中添加)

if(<variable|string> GREATER_EQUAL <variable|string>)(在 3.7 版本中添加)

if(<variable|string> STRLESS <variable|string>)

if(<variable|string> STRGREATER <variable|string>)

if(<variable|string> STREQUAL <variable|string>)

if(<variable|string> STRLESS_EQUAL <variable|string>)

if(<variable|string> STRGREATER_EQUAL <variable|string>)


要注意的是 if(<variable|string> MATCHES <regex>),大家在官方文档这里看支持的正则。

1.6 版本比较

用的很少,真想用就看官方文档吧。

3.24 版本中加入了 if(<variable|string> PATH_EQUAL <variable|string>),可以用来更准确地比较两个地址。

我没用过,不敢胡乱写,大家可以看官方文档

1.4 模拟运用场景

1.4.1 操作系统判断

判断当前操作系统,根据平台选择不同的编译选项。

if(CMAKE_BUILD_TYPE STREQUAL "Debug")
    if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
        message("On Windows")
    elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
        message("On Linux")
    else()
        message("Other system")
    endif()
endif()
1.4.2 模块启用控制

动态启用或禁用某些功能模块,通过变量开关来实现。

option(ENABLE_LOGS "Enable logging" ON)
if(ENABLE_LOGS)
    add_definitions(-DENABLE_LOGS)			# 添加全局的 log 输出宏
elseif(CMAKE_BUILD_TYPE STREQUAL "Debug")
    message(WARNING "Logging is disabled")	# Debug 模式不应该禁止log输出
endif()
1.4.3 外部依赖(或者叫外部文件)检查

检查第三方库文件是否存在。

set(LIB_PATH "/usr/lib/libz.so")
if(EXISTS ${LIB_PATH} AND NOT IS_DIRECTORY ${LIB_PATH})
    message(STATUS "Library found: ${LIB_PATH}")
else()
    message(FATAL_ERROR "Library not found")
endif()

message(FATAL_ERROR "Library not found") 这里将会使得 CMake 构建失败,属于自定义错误。

2. 循环 foreach()while()

2.1 foreach() 的三种用法

2.1.1 基于范围:foreach(<loop_var> RANGE <stop>)
foreach(i RANGE 5)
    message(STATUS "Current value: ${i}")
endforeach()
2.1.2 基于范围,指定起点和步长:foreach(<loop_var> RANGE <start> <stop> [<step>])

如果不指定 step,默认为1。

foreach(i RANGE 1 10 2)
    message(STATUS "Current step: ${i}")
endforeach()
2.1.3 基于列表或值的迭代:foreach(<loop_var> IN [LISTS [<lists>]] [ITEMS [<items>]])
set(FRUITS Apple Banana Cherry)
list(APPEND COLORS Blue Red Orange Pink)
foreach(fruit IN LISTS FRUITS)
    message(STATUS "Current fruit: ${fruit}")
endforeach()

foreach(fruit IN LISTS FRUITS COLORS)
    message(STATUS "Current fruit: ${fruit}")
endforeach()

2.2 while() 的用法

其实有点废话:

while(<condition>)
  <commands>
endwhile()

condition 部分与 if 是一样的,对着用就好。

2.3 break()continue()

不介绍了,可以用在 foreach()while() 中。

要是看不懂怎么用,基本上就告别开发了。。。

3. 消息输出 message()

我们也经常用到了,它最常用的方式就是输出内容到命令行。

message([<mode>] "message text" ...)

接下来介绍一下各个 mode

  • FATAL_ERROR:CMake 错误,停止处理和生成。
  • SEND_ERROR:CMake 错误,继续处理,但跳过生成。
  • WARNING:CMake 警告,继续处理。
  • STATUS:项目用户可能感兴趣的主要信息。这些信息应该简洁,不超过一行,但仍然具有信息量。

上面这几个就是常用的,其他的我是没用过,你们想用看官方文档吧。

4. 其他常用函数

还有两个比较重要的函数:file()string(),主要作用如下:

  • file():创建文件夹、读取/写入文件、遍历目录等。
  • string():处理字符串(替换、拼接和分割等)。

由于它们的作用比较泛且杂,我打算在后面用到再说。

现在完全介绍这两个函数并不能很好的起到学习 CMake 的作用,因为脱离了实践!

这里是 file() 的官方文档

这里是 string() 的官方文档

二、构建案例:跨平台多线程项目

我们通过一个实例来熟悉 CMake 在跨平台方面的使用。

项目结构:

ThreadSimple
├── CMakeLists.txt
├── main.c
├── thread_wrapper.c	# 线程封装实现
└── thread_wrapper.h	# 线程封装头文件

thread_wrapper.h 内容:

#ifndef THREAD_WRAPPER_H
#define THREAD_WRAPPER_H

// 跨平台县城宏定义
#ifdef _WIN32
#include <windows.h>

typedef HANDLE thread_t;        // Windows 线程类型
typedef DWORD thread_return_t;  // Windows 线程函数返回类型
typedef LPVOID thread_param_t;  // Windows 线程参数类型
#else
#include <pthread.h>

typedef pthread_t thread_t;     // POSIX 线程类型
typedef void *thread_return_t;  // POSIX 线程函数返回类型
typedef void *thread_param_t;   // POSIX 线程参数类型
#endif

/// 通用线程函数指针
typedef thread_return_t (*thread_func_t)(thread_param_t arg);

/// 创建线程
int create_thread(thread_t *thread, thread_func_t func, void *arg);

/// 等待线程执行完成
void join_thread(thread_t thread);

#endif  //! THREAD_WRAPPER_H

thread_wrapper.c 内容:

#include "thread_wrapper.h"

int create_thread(thread_t *thread, thread_func_t func, void *arg)
{
#ifdef _WIN32
    *thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)func, (LPVOID)arg, 0, NULL);
    return *thread == NULL ? -1 : 0;
#else
    return pthread_create(thread, NULL, func, arg);
#endif
}

void join_thread(thread_t thread)
{
#ifdef _WIN32
    WaitForSingleObject(thread, INFINITE);
    CloseHandle(thread);
#else
    pthread_join(thread, NULL);
#endif
}

main.c 内容:

#include <stdio.h>
#include "thread_wrapper.h"

#define THREAD_COUNT 5  // 线程数量

/// 线程函数
thread_return_t thread_function(thread_param_t arg)
{
    int thread_num = *((int *)arg);

    printf("Thread %d is running\n", thread_num);

    // 模拟线程任务
    for (int i = 0; i < 5; i++) {
        printf("Thread %d: %d\n", thread_num, i);
    }

    return 0;
}

int main()
{
    thread_t threads[THREAD_COUNT];  // 线程句柄
    int thread_args[THREAD_COUNT];   // 线程参数

    // 创建线程
    for (int i = 0; i < THREAD_COUNT; i++) {
        thread_args[i] = i + 1;
        if (create_thread(&threads[i], thread_function, &thread_args[i]) != 0) {
            printf("Error creating thread %d\n", i + 1);
            return -1;
        }
    }

    // 等待线程完成
    for (int i = 0; i < THREAD_COUNT; i++) {
        join_thread(threads[i]);
    }

    printf("All threads finished.\n");
    return 0;
}

从源码上看,我们需要在合适的平台上添加 pthread 库作为依赖。

直接上 CMakeLists.txt 代码:

cmake_minimum_required(VERSION 3.21)

project(ThreadSimple LANGUAGES C)    # 指定项目名称和语言

set(CMAKE_C_STANDARD 99)            # 指定C标准为C99

add_executable(${PROJECT_NAME})

target_sources(${PROJECT_NAME} PRIVATE main.c thread_wrapper.c)

# 只有在非Windows平台才链接pthread库
if(NOT WIN32)
    target_link_libraries(${PROJECT_NAME} PRIVATE pthread)
endif()

WIN32 变量只有在 Windows 系统下才存在。

project() 中使用了 LANGUAGES C,也就意味着只用到了C语言,CMake 不会去查找以及使用C++编译器

大家也发现,我并没有在 add_executable() 函数内添加源文件,而是使用了 target_sources() 函数,target_sources() 的用法和第一章讲的 target_link_libraries 非常相似,可以借鉴。

Linux 执行结果:

在这里插入图片描述

Windows 执行结果:

在这里插入图片描述


【CMake 教程】常用函数与构建案例解析(三) 结束,希望大家提提意见,欢迎指正!