一、CMake 常用函數簡析
1. 條件判斷 if() / elseif() / else()
在 CMake 腳本中,條件判斷是控制邏輯的重要工具。if()
支持多種比較語句,包括數值、字符串、布爾值和變量存在性等。在條件滿足時執行特定邏輯代碼,下面是典型語法:
if(VARIABLE)# 當 VARIABLE 存在且為非空時執行
elseif(${VARIABLE} STREQUAL "value")# 當 VARIABLE 等于指定字符串 "value" 時執行
else()# 以上都不滿足時執行
endif()
1.1 基本語句表達
-
if(<constant>)
如果常量是
1
、ON
、YES
、TRUE
、Y
或非零數字(包括浮點數),則為 True 。如果常量是0
、OFF
、NO
、FALSE
、N
、IGNORE
、NOTFOUND
、空字符串或以后綴-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") # Trueif(NOT IS_EMPTY_STRING) # Truemessage("IS_EMPTY_STRING is empty")
endif()if(IS_STRING) # Truemessage("IS_STRING is: \"${IS_STRING}\"")
endif()if(IS_TRUE AND NOT IS_FALSE) # Truemessage("IS_TRUE is true and IS_FALSE is false")
endif()if(IS_FALSE OR NOT IS_TRUE) # Falsemessage("IS_FALSE is true or IS_TRUE is false")
elseif(IS_TRUE) # Truemessage("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;
#elsereturn pthread_create(thread, NULL, func, arg);
#endif
}void join_thread(thread_t thread)
{
#ifdef _WIN32WaitForSingleObject(thread, INFINITE);CloseHandle(thread);
#elsepthread_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標準為C99add_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 教程】常用函數與構建案例解析(三) 結束,希望大家提提意見,歡迎指正!