I.
CMake 基础 | 菜鸟教程
II.
III.
CMake Tutorial — CMake 4.0.0-rc2 Documentation
概念
cmake是一个跨平台、开源的工具家族,用于构建、测试、打包软件。
cmake本身并不实际用编译器和链接器,是指导其他构建工具来构建程序。
cmake帮助开发者花更少的时间在构建系统上,花更多的时间在写代码上。
cmake支持多种语言c、c++、CUDA、Fortran、python等。支持在构建过程中执行任意自定义命令。
很多第三方库在用cmake,对标shell脚本。
现代cmake
现代cmake从3.0开始。
老cmake语法繁杂冗长,现代cmake更加简洁直观。
现代cmake为开发者提供了面向构建目标的构建配置。
cmake很多语法是不太常规的与构建概念相关。
基础语法
CMakeLists.txt
CMakeLists.txt用于组织构建源程序的目录结构与构建过程息息相关。
通过add_subdirection命令将子目录添加到构建目录中,要求子目录也有CMakeLists.txt。
.cmake
.cmake分为脚本程序和模块程序。
脚本
-P参数运行.cmake脚本,不会配置成任何构建系统。
cmake -P a.cmake
模块
通过include引用.cmake模块。预制模块与环境检测、搜索使用第三方库有关。
注释
# 这是单行注释
#[[这是多行
注释]]
#[=[这是多行
注释]=]
调用命令
if、for统一采用命令形式调用,语法简单、理解简单。
命令名称不区分大小写,一般使用小写。
vczxh@DESKTOP MINGW64 ~/Desktop/新建文件夹
$ cat hello.cmake
message(a b c)
vczxh@DESKTOP MINGW64 ~/Desktop/新建文件夹
$ cmake -P hello.cmake
abc
set(var_a 你好)
set(var_b a)
message(${var_${var_b}}) #变量嵌套引用,输出:你好
message(${var_a})#输出:你好
message(${var_b})#输出:a
message(${var_c})#Error
变量
函数作用域
只在函数内部有效,在函数外部访问是个空值。
function(my_function)
set(local_variable "I am local")
message("Inside function: ${local_variable}")
endfunction()
my_function()
message("Outside function: ${local_variable}") #Outside function:
变量传递:通过函数参数创建变量。
function(add_numbers num1 num2 result)
math(EXPR ${result} "${num1}+${num2}")
endfunction()
set(num1 2)
set(num2 3)
add_numbers(${num1} ${num2} added_result)
message("The result of adding ${num1} and ${num2} is ${added_result}") #The result of adding 2 and 3 is
目录作用域
在目录下创建的变量,在包含的子目录下只能读访问。如果子目录重新定义变量则覆盖。
./CMakeLists.txt
set(global_variable "I am global")
add_subdirectory(src)
src/CMakeLists.txt
message("In src directory: ${global_variable}")
保留标识符
CMAKE_开头的名称。
预定义变量
cmake预先定义好的变量,关于构建环境、源文件目录、目标架构等。
CMAKE_SOURCE_DIR
CMAKE_SYSTEM_NAME
CMAKE_SYSTEM_PROCESSOR
CMAKE_MAKE_PROGRAM
CMAKE_GENERATOR
定义变量
cmake set定义变量的PARENT_SCOPE用法,
希望这个函数变量计算结果返回到函数外部使用。
function(compute_value result_variable)
set(local_value 42)
set(${result_variable} ${local_value} PARENT_SCOPE)
endfunction()
compute_value(my_result)
message("The result is: ${my_result}") #The result is: 42
缓存变量
定义
set(<variable_name> <value> CACHE <type> <docstring>)
set(变量名 值 CACHE 类型 描述)
根据用户选择决定一个功能,缓存变量具有全局的作用域。例如定义一个变量是否开启,可在cmake gui里配置:
# 定义一个布尔类型的缓存变量来控制功能是否开启
set(ENABLE_FEATURE ON CACHE BOOL "Enable a specific feature")
if(ENABLE_FEATURE)
# 如果变量为真,则添加相关的源文件或库
add_executable(my_app main.cpp feature.cpp)
else()
add_executable(my_app main.cpp)
endif()
BOOL
:用于表示布尔类型的变量。可以接受的值有ON
、OFF
、TRUE
、FALSE
、1
(表示TRUE
)和0
(表示FALSE
)。
用途:
- 构建类型选择:可以定义一个缓存变量来选择构建类型,如
Debug
、Release
等,而不是依赖于CMAKE_BUILD_TYPE
默认的设置。 - 第三方库路径指定:当使用外部第三方库时,通过缓存变量可以方便地指定库的路径,如
set(THIRD_PARTY_LIB_PATH "/path/to/lib" CACHE PATH "Path to third - party library")
。这样,用户可以根据自己的安装情况来灵活设置路径。
类型:
BOOL 类型
FILEPATH 类型
PATH 类型
STRING 类型
INTEGER 类型
赋值:
- 在CMakeCache.txt里修改
- cmake -D <变量>=<值>或cmake -D <变量>:<值>
使用:
${变量名}
$CACHE{变量名}
定义环境变量
set(ENV{变量名} 变量值)
如果一个环境变量指向一个库路径,在cmake里读这个环境变量。
- 在 Windows 上:
set MY_LIBRARY_PATH=C:\path\to\library
- 在 Unix/Linux/macOS 上:
export MY_LIBRARY_PATH=/path/to/library
# 读取环境变量 MY_LIBRARY_PATH
set(LIBRARY_PATH $ENV{MY_LIBRARY_PATH})
# 检查是否成功读取
message(STATUS "Library path: ${LIBRARY_PATH}")
# 使用该路径链接库
link_directories(${LIBRARY_PATH})
函数和宏的区别
宏,简单的替换
macro(print_message msg)
message(${msg})
endmacro()
print_message("hello world")
宏,没有自己的作用域
macro(set_variable)
set(var "hello world")
endmacro()
set_variable()
message(${var})
宏,不支持返回值,只是简单替换
函数,有变量作用域
function(print_message msg)
message(${msg})
endfunction()
print_message("hello world")
函数,有自己的作用域,超出范围未定义
function(set_variable)
set(var "hello world")
endfunction()
set_variable()
message(${var}) #CMake Error at hello.cmake:5 (message): message called with incorrect number of arguments
函数,可以在函数内设置作用域来模拟返回值作用
function(set_variable var)
set(${var} "hello world" PARENT_SCOPE)
endfunction()
set_variable(my_var)#my_var是形参
message(${my_var})
函数,调用时会对参数按类型和数量解析。宏,只是替换不会按类型解析。
函数和宏return的使用
function(fun val error_str)
if(val EQUAL 0)
set(${error_str} "no error" PARENT_SCOPE)
return()
elseif(val EQUAL 1)
set(${error_str} "error 1" PARENT_SCOPE)
endif()
endfunction()
fun(0 err)
message("error str=${err}")
fun(1 err)
message("error str=${err}")
宏的return使用,是代码的替换
列表set
1.设置普通变量
set(PROJECT_VERSION "1.0")
message("Project version is:${PROJECT_VERSION})
2.设置缓存变量
set(BUILD_SHARED_LIBS ON CACHE BOOL "build shared libraries")
message("build shared libraries:${BUILD_SHARED_LIBS}")
这是设置了一个bool类型的缓存变量值为ON,有解释字符串。
3.设置环境变量
set(ENV{MY_ENV_VALUE} "my_value")
message("MY_ENV_VALUE is${MY_ENV_VALUE})
4.设置列表变量
set(SOURCE_FILE
src/main.cpp
src/utils.cpp
src/io.cpp
)
#遍历
foreach(file ${SOURCE_FILE})
message("source file:${file}")
endforeach()
常用命令
math 基本算数运算
加减乘除
set(a 1)
set(b 2)
math(EXPR result "${a}+${b}")
message(STATUS "运算结果${result}")
范围检查CLAMP
set(value 15)
set(min 10)
set(max 20)
math(EXPR result "CLAMP(${value},${min},${max})")
message(STATUS "范围检查结果:${result}")
#CMake Error at hello.cmake:4 (math):
# math cannot parse the expression: "CLAMP(15,10,20)": syntax error,
# unexpected exp_NUMBER, expecting exp_CLOSEPARENT or exp_OR (12).
随机数生成
math(EXPR value "RAND(0,100)")
message(STATUS "随机数:${value}")
#CMake Error at hello.cmake:1 (math):
# math cannot parse the expression: "RAND(0,100)": syntax error, unexpected
# exp_NUMBER, expecting exp_CLOSEPARENT or exp_OR (11).
string 字符串操作
字符串常用操作有拼接、替换、查找、子串、大小写转换、去除首位空格、使用正则表达式分割为list字符串。
string(CONCAT str "hello" " world")
message("拼接后的字符串为:${str}")
string(REPLACE "world" "cmake" new_str ${str})
message("替换后的字符串为:${new_str}")
string(FIND "${str}" "world" index)
message("找到子串world的位置为:${index}")
string(SUBSTRING ${str} 7 5 sub_str)
message("子串为:${sub_str}")
string(TOUPPER "${str}" upper_str)
message("转为大写为:${upper_str}")
string(TOLOWER "${upper_str}" lower_str)
message("转为小写为:${lower_str}")
string(STRIP " hello world " stripped_str)
message("去除首位空格的字符串为:${stripped_str}")
string(REGEX MATCHALL "[^,]+" list_str "first,second,third")
foreach(item IN LISTS list_str)
message("分割后的元素为${item}")
endforeach()
list 列表
如文件列表、库列表。
list有添加、获取长度、获取索引元素、查找、删除项、反转、排序、子列表功能。
set(my_list "a" "b" "c")
list(APPEND my_list "d")
message(STATUS "添加后的列表为:${my_list}")
list(LENGTH my_list list_length)
message(STATUS "length=${list_length}")
list(GET my_list 1 item1)
message(STATUS "item 1=${item1}") #b
list(FIND my_list "c" found)
message(STATUS "item found index=${found}")
list(REMOVE_ITEM my_list "d")
message(STATUS "item remove=${my_list}")
list(REVERSE my_list)
message(STATUS "item reverse=${my_list}")
set(my_list "2" "3" "1" "4")
list(SORT my_list)
message(STATUS "item sort=${my_list}")
list(SUBLIST my_list 1 2 sub_list)
message(STATUS "sub list=${sub_list}")
file 文件操作
用于文件和目录的操作。
#文件读取
file(READ <PATH> <CONTENT>)
#文件写入
file(WRITE <PATH> <CONTENT>)
#追加
file(APPEND <PATH> <CONTENT>)
#创建目录
file(MAKE_DIRECTORY <DIR_NAME>)
#复制文件
FILE(COPY <SRC_PATH> DESTINATION <DEST_PATH>)
#重命名文件
FILE(RENAME <OLD_NAME> <NEW_NAME>)
#删除目录
FILE(REMOVE_DIRECTORY <DIR_NAME>)
#查找文件
FILE(GLOB <LIST_FIND> <FIND_NAME>)
FILE(GLOB <LIST_FIND> <FIND_NAME>)
FILE(GLOB txt_files "*.txt")
FILE(GLOB cpp_files "src/**/*.cpp")#查找src目录以及子目录下的cpp文件
cmake_path 路径操作
处理文件路径。
#父路径
set(my_path "/home/user/documents/report.pdf")
cmake_path(GET my_path PARENT_PATH parent_path)
message("父路径:${parent_path}")
CMAKE_PATH(GET <SRC_PATH> PARENT_PATH <PARENT_PATH>)
#标注为,获取父路径GET PARENT_PATH
#获取文件名GET FILENAME
#获取文件扩展名GET EXTENSION
#路径规范化NORMALIZE OUTPUT_VARIABLE
#路径拼接CMAKE_PATH(APPEND <BASE_PATH> <APPEND_PATH> OUTPUT_VARIABLE <OUTPUT_PATH>
#路径比较CMAKE_PATH(IS_EQUAL <PATH1> <PATH2> <RESULT>)
#是否为绝对路径CMAKE_PATH(IS_ABSOLUTE <PATH> <RESULT>)
#路径替换一部分CMAKE_PATH(REPLACE_FILENAME <PATH> <FILE_NAME> OUTPUT_VARIABLE <NEW_PATH>)
get_filename_component 路径操作
从文件路径中提取特定部分。
#提取目录
GET_FILENAME_COMPONENT(<RET_PATH> <PATH> DIRECTORY)
#提取文件名
GET_FILENAME_COMPONENT(<RET_NAME> <PATH> NAME)
#提取文件名不包括扩展名
GET_FILENAME_COMPONENT(<RET_NAME> <PATH> NAME_WE)
#提取文件扩展名包括点号
GET_FILENAME_COMPONENT(<RET_EXT_NAME> <PATH> EXT)
#转换为绝对路径
GET_FILENAME_COMPONENT(<RET_ABSOLUTE_PATH> <PATH> ABSOLUTE)
#将符号连接转换为实际绝对路径
GET_FILENAME_COMPONENT(<RET_REAL_PATH> <PATH> REALPATH)
config_file 配置模板文件
配置文件的生成。
configure_file(<input> <output>
[COPYONLY] [ESCAPE_QUOTES] [@ONLY]
[NEWLINE_STYLE [UNIX|DOS|WIN32|LF|CRLF]])
参数解释
- <input>:必需参数,指定输入文件的路径,可以是相对路径或者绝对路径。
- <output>:必需参数,指定输出文件的路径,可以是相对路径或者绝对路径。
- COPYONLY:可选参数,若指定该参数,configure_file 只会简单地复制文件,不会对文件中的变量进行替换。
- ESCAPE_QUOTES:可选参数,指定在替换变量时对引号进行转义处理。
- @ONLY:可选参数,指定只有以 @VAR@ 这种形式表示的变量才会被替换,而 ${VAR} 形式的变量不会被替换。
- NEWLINE_STYLE:可选参数,用于指定输出文件的换行符风格,可以是 UNIX(等价于 LF)、DOS(等价于 WIN32、CRLF)。
#复制配置文件
CONFIGURE_FILE(<SRC_FILE> <DEST_FILE>)
#仅复制不修改
CONFIGURE_FILE(<SRC_FILE> <DEST_FILE> COPYONLY)
#复制文件并修改换行符
CONFIGURE_FILE(<SRC_FILE> <DEST_FILE> NEWLINE_STYLE UNIX)
execute_process执行外部程序
# 执行单个命令
execute_process(
COMMAND ls -l
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
# 执行管道命令
execute_process(
COMMAND ls -l
COMMAND grep ".txt"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)#如果WORKING_DIRECTORY不指定,将使用cmake文件所在文件夹路径
#查看命令执行的返回值
EXECUTE_PROCESS(COMMAND <CMD> <ARG> RESULT_VARIABLE <RET>)
#标准输出和错误输出
EXECUTE_PROCESS(COMMAND ls -l OUTPUT_VARIABLE <OUT> ERROR_VARIABLE <ERROR>)
#将标准输入、标准输出、错误输出到指定文件
EXECUTE_PROCESS(COMMAND cat input.txt INPUT_FILE input.txt OUTPUT_FILE output.txt ERROR_FILE error.txt)
#标准输出和错误输出不显示在cmake执行中
EXECUTE_PROCESS(COMMAND ls -l OUTPUT_QUIET ERROR_QUIET)
#去除标准输出和标准错误输出末尾的空白字符
EXECUTE_PROCESS(COMMAND ls -l OUTPUT_VARIABLE output OUTPUT_STRIP_TRAILING_WHITESPACE)
#控制在执行过程中输出的显示方式none不显示,stdout显示,stderr显示出错,both都显示
EXECUTE_PROCESS(COMMAND ls -l COMMAND_ECHO both)
message 打印日志
打印信息。
MESSAGE([<MODE>] <STR>...)
<MODE>:可选参数包括STATUS,WARNING,AUTHOR_WARNING,SEND_ERROR,FATAL_ERROR.
<STR>是打印的字符串,多个会自动连接。
execute_process 执行程序
include 引用cmake程序
用于包含其他cmake文件,可以将大型cmake项目拆分成多个小的可管理的模块。
#包含本地现有的cmake文件
include(utils.cmake)
#避免包含不存在的cmake文件报错
include(utils.cmake OPTIONAL)
#如果包含成功返回绝对路径,否则返回错误信息
include(utils.cmake RESULT_VARIABLE ret)
if(ret)
message("successfully included:${ret}")
else()
message("failed included:${ret}")
endif()
cmake_language 执行代码片段
cmake语言元编程支持,允许用户以编程方式操作cmake各种元素。
#动态调用cmake命令
FUNCTION(MY_FUNC ARG)
MESSAGE("FUNC ARG=${ARG}")
ENDFUNCTION()
CMAKE_LANGUAGE(CALL MY_FUNC "ARG1")
#执行一个cmake字符串代码
SET(STR "MESSAGE(\"THIS IS A MESSAGE\")")
CMAKE_LANGUAGE(EVAL CODE ${STR})
#获取或设置cmake编译属性
#ADD_EXECUTABLE(MY_APP MAIN.C)
#CMAKE_LANGUAGE(PROPERTY MY_APP COMPILE_FLAGS "-Wall")
#用于获取cmake变量、属性等的值。
SET(MY_VAR "HELLO WORLD")
CMAKE_LANGUAGE(GET CACHE MY_VAR VALUE VAR_VALUE)
MESSAGE("VALUE OF MY_VAR IS:${VAR_VALUE}")
variable_watch 监控变量
以回调函数的形式使用监控变量的变化。
回调函数格式:
function(<callback function> <variable> <value> <access mode> <stack>)
endfunction()
# 定义回调函数
function(variable_change_callback variable value access_mode stack)
message(STATUS "Variable ${variable} has been ${access_mode} with value: ${value}")
message(STATUS "Call stack: ${stack}\n")
endfunction()
# 设置要监视的变量
set(my_variable "hello_world")
# 开始监视变量
variable_watch(my_variable variable_change_callback)
# 修改变量的值
set(my_variable "hello_camke")
# 删除变量
unset(my_variable)
使用cmake语法写的一个快速排序程序
# 定义快速排序函数
function(quick_sort input_list output_list)
set(list ${ARGN})
list(LENGTH list length)
if(length LESS_EQUAL 1)
set(${output_list} ${list} PARENT_SCOPE)
return()
endif()
# 选择基准元素,这里选择第一个元素
list(GET list 0 pivot)
set(left_list "")
set(right_list "")
# 分区操作
foreach(item IN LISTS list)
if(item LESS ${pivot})
list(APPEND left_list ${item})
elseif(item GREATER ${pivot})
list(APPEND right_list ${item})
endif()
endforeach()
# 递归排序左右子列表
quick_sort(left_list sorted_left_list ${left_list})
quick_sort(right_list sorted_right_list ${right_list})
# 合并排序后的列表
set(sorted_list ${sorted_left_list} ${pivot} ${sorted_right_list})
set(${output_list} ${sorted_list} PARENT_SCOPE)
endfunction()
# 测试快速排序
set(test_list 3 1 4 1 5 9 2 6 5 3 5)
quick_sort(test_list sorted_list ${test_list})
message(STATUS "Original list: ${test_list}")
message(STATUS "Sorted list: ${sorted_list}")
cmake构建初探
cmake项目的生命周期
- lib.c
int add(int a,int b){
return a+b;
}
- CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.19)
PROJECT(mylib VERSION 1.0.0)
ADD_LIBRARY(mylib STATIC lib.c)
INSTALL(TARGETS mylib)
SET(CPACK_PACKAGE_NAME "mylib")
INCLUDE(CPack)
- 使用visual studio2022构建
mkdir build
cd build
cmake ..
- *(Linux)使用Makefile构建在配置生成阶段指明Debug/Release
mkdir build_debug
cd build_debug
cmake -DCMAKE_BUILD_TYPE=Debug ..
- 在cmake命令中使用visual studio2022编译Release
cd build
cmake --build . --config Release
- 安装和打包到相对目录
cmake --install . --prefix ../install
-- Install configuration: "Release"
-- Installing: C:/Users/vczxh/Desktop/新建文件夹/build/../install/lib/mylib.lib
- 制作setup.exe安装包
cpack -G NSIS
CPack: Create package using NSIS
CPack: Install projects
CPack: - Install project: mylib []
CPack: Create package
CPack: - package: C:/Users/vczxh/Desktop/新建文件夹/build/mylib-1.0.0-win64.exe
generated.
- 双击运行mylib-1.0.0-win64.exe
默认安装到操作系统中,在C:\Program Files\mylib 1.0.0\lib\mylib.lib
- lib安装包的使用
- main.c
#include <stdio.h>
extern int add(int,int);
int main(){
printf("%d\n",add(1,2));
return 0;
}
- CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.19)
PROJECT(use_mylib VERSION 1.0.0)
FIND_LIBRARY(mylib_LIBRARY mylib PATH_SUFFIXES "mylib 1.0.0/lib")
MESSAGE("${mylib_LIBRARY}")
ADD_EXECUTABLE(main main.c)
TARGET_LINK_LIBRARIES(main ${mylib_LIBRARY})
- 编译并运行
mkdri build
cd build
cmake ..
输出
-- Selecting Windows SDK version 10.0.22621.0 to target Windows 10.0.19045.
C:/Program Files/mylib 1.0.0/lib/mylib.lib
-- Configuring done (0.1s)
-- Generating done (0.1s)
-- Build files have been written to: C:/Users/vczxh/Desktop/使用mylib/build
cmake --build . --config Release
输出
适用于 .NET Framework MSBuild 版本 17.11.9+a69bbaaf5
1>Checking Build System
Building Custom Rule C:/Users/vczxh/Desktop/使用mylib/CMakeLists.txt
main.c
main.vcxproj -> C:\Users\vczxh\Desktop\使用mylib\build\Release\main.exe
Building Custom Rule C:/Users/vczxh/Desktop/使用mylib/CMakeLists.txt
./Release/main.exe
输出3
构建目标和属性
二进制构建目标
在 CMake 中,二进制构建目标主要分为可执行文件(`executable`)和库文件(包括静态库 `static library`、共享库 `shared library` 和模块库 `module library`)。下面为你详细介绍不同类型二进制构建目标的示例。
### 1. 可执行文件构建目标(`add_executable`)
可执行文件是可以直接运行的程序,使用 `add_executable` 命令来创建。
#### 示例代码
cmake_minimum_required(VERSION 3.10)
project(ExecutableExample)
# 添加可执行文件,源文件为 main.cpp
add_executable(myapp main.cpp)
# 设置可执行文件的属性
set_target_properties(myapp PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
)
#### 代码解释
- `add_executable(myapp main.cpp)`:创建一个名为 `myapp` 的可执行文件,其源文件为 `main.cpp`。
- `set_target_properties`:用于设置目标的属性。这里将 `myapp` 的 C++ 标准设置为 C++17,并要求必须使用该标准。
### 2. 静态库构建目标(`add_library` 且类型为 `STATIC`)
静态库在链接时会被完整地复制到可执行文件中,使用 `add_library` 命令并指定类型为 `STATIC` 来创建。
#### 示例代码
cmake_minimum_required(VERSION 3.10)
project(StaticLibraryExample)
# 添加静态库,源文件为 lib.cpp
add_library(mystaticlib STATIC lib.cpp)
# 设置静态库的属性
set_target_properties(mystaticlib PROPERTIES
OUTPUT_NAME "my_static_lib"
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
)
# 添加可执行文件,源文件为 main.cpp
add_executable(myapp main.cpp)
# 链接静态库到可执行文件
target_link_libraries(myapp PRIVATE mystaticlib)
#### 代码解释
- `add_library(mystaticlib STATIC lib.cpp)`:创建一个名为 `mystaticlib` 的静态库,源文件为 `lib.cpp`。
- `set_target_properties`:设置静态库的属性。`OUTPUT_NAME` 用于指定库文件的输出名称,`ARCHIVE_OUTPUT_DIRECTORY` 用于指定库文件的输出目录。
- `target_link_libraries`:将 `mystaticlib` 静态库链接到 `myapp` 可执行文件。
### 3. 共享库构建目标(`add_library` 且类型为 `SHARED`)
共享库在运行时动态加载,使用 `add_library` 命令并指定类型为 `SHARED` 来创建。#### 示例代码
cmake_minimum_required(VERSION 3.10)
project(SharedLibraryExample)
# 添加共享库,源文件为 lib.cpp
add_library(mysharedlib SHARED lib.cpp)
# 设置共享库的属性
set_target_properties(mysharedlib PROPERTIES
VERSION 1.0.0
SOVERSION 1
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
)
# 添加可执行文件,源文件为 main.cpp
add_executable(myapp main.cpp)
# 链接共享库到可执行文件
target_link_libraries(myapp PRIVATE mysharedlib)
#### 代码解释
- `add_library(mysharedlib SHARED lib.cpp)`:创建一个名为 `mysharedlib` 的共享库,源文件为 `lib.cpp`。
- `set_target_properties`:设置共享库的属性。`VERSION` 用于指定库的版本号,`SOVERSION` 用于指定库的 API 版本号,`LIBRARY_OUTPUT_DIRECTORY` 用于指定库文件的输出目录。
- `target_link_libraries`:将 `mysharedlib` 共享库链接到 `myapp` 可执行文件。
### 4. 模块库构建目标(`add_library` 且类型为 `MODULE`)
模块库通常用于插件系统,使用 `add_library` 命令并指定类型为 `MODULE` 来创建。#### 示例代码
cmake_minimum_required(VERSION 3.10)
project(ModuleLibraryExample)
# 添加模块库,源文件为 plugin.cpp
add_library(myplugin MODULE plugin.cpp)
# 设置模块库的属性
set_target_properties(myplugin PROPERTIES
PREFIX ""
SUFFIX ".mylib"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugins"
)
# 添加可执行文件,源文件为 main.cpp
add_executable(myapp main.cpp)
#### 代码解释
- `add_library(myplugin MODULE plugin.cpp)`:创建一个名为 `myplugin` 的模块库,源文件为 `plugin.cpp`。
- `set_target_properties`:设置模块库的属性。`PREFIX` 用于指定库文件的前缀,这里设置为空;`SUFFIX` 用于指定库文件的后缀;`LIBRARY_OUTPUT_DIRECTORY` 用于指定库文件的输出目录。
- 可执行文件 `myapp` 可在运行时动态加载 `myplugin` 模块库。
通过这些示例,你可以了解如何在 CMake 中创建不同类型的二进制构建目标,并设置它们的属性。
模块库和静态库、动态库的区别:
在 CMake 以及软件开发中,模块库、静态库和动态库(共享库)是三种不同类型的库,它们在多个方面存在区别,下面为你详细介绍:
1. 定义和用途
静态库(Static Library)
定义:静态库是一组目标文件(.o 文件)的集合,在链接阶段,链接器会将静态库中被使用的代码完整地复制到可执行文件中。
用途:常用于需要将库代码完全集成到可执行文件中的场景,确保可执行文件在运行时不依赖外部的库文件,方便程序的分发和部署。例如,一些小型的工具程序可能会使用静态库来简化依赖管理。
动态库(共享库,Shared Library)
定义:动态库在编译时不会被复制到可执行文件中,而是在程序运行时动态加载。可执行文件只包含对动态库中函数和变量的引用信息。
用途:适用于多个程序共享同一份库代码的场景,可以减少磁盘空间的占用,并且方便库的更新和维护。例如,操作系统中的系统库通常都是动态库,多个应用程序可以共享这些库。
模块库(Module Library)
定义:模块库通常用于实现插件系统,它和动态库类似,也是在运行时加载,但它更强调可插拔性和独立性。模块库一般不会被系统默认的动态加载机制搜索,需要程序主动加载。
用途:常用于需要扩展功能的软件系统,通过加载不同的模块库来实现不同的功能。例如,一些图形编辑软件可能会通过加载不同的插件模块来支持不同的文件格式。
2. 链接和加载方式
静态库
链接:在编译链接阶段,链接器会将静态库中被调用的代码复制到可执行文件中,生成一个独立的可执行文件。
加载:由于代码已经包含在可执行文件中,所以在程序运行时不需要额外的加载步骤。
动态库
链接:在编译链接阶段,链接器只是记录可执行文件对动态库的依赖信息,不会将动态库的代码复制到可执行文件中。
加载:在程序启动时,操作系统的动态加载器会根据可执行文件中的依赖信息,将所需的动态库加载到内存中,并将可执行文件中的引用与动态库中的实际地址进行绑定。
模块库
链接:与动态库类似,在编译链接阶段只记录依赖信息。
加载:程序需要通过特定的 API(如 dlopen、LoadLibrary 等)在运行时主动加载模块库,并进行符号解析和绑定。
3. 文件大小和磁盘占用
静态库
由于静态库的代码会被复制到可执行文件中,所以可执行文件的体积会增大,磁盘占用也会相应增加。如果多个程序使用同一个静态库,会导致磁盘上存在多份相同的代码副本。
动态库
动态库的代码可以被多个程序共享,只需要在磁盘上保存一份,因此可以减少磁盘空间的占用。可执行文件的体积相对较小,因为它只包含对动态库的引用信息。
模块库
模块库的文件大小通常与动态库类似,因为它们的实现原理相近。模块库通常是按需加载,不会增加可执行文件的体积。
4. 更新和维护
静态库
如果静态库的代码发生更新,需要重新编译链接使用该静态库的所有可执行文件,否则可执行文件将继续使用旧版本的库代码。
动态库
动态库的更新相对方便,只需要替换磁盘上的动态库文件,不需要重新编译链接使用该动态库的可执行文件(前提是库的接口保持兼容)。
模块库
模块库的更新和维护也比较方便,只需要替换相应的模块库文件,程序可以在运行时动态加载新的模块库,实现功能的更新。
伪构建目标
在 CMake 中,伪构建目标(也称为伪目标)并不是真正生成可执行文件、库文件等二进制文件的目标,而是用于执行一些特定的任务,比如清理构建目录、运行测试、安装文件等。下面为你介绍几种常见的伪构建目标及其示例。
### 1. `clean` 目标
`clean` 是一个内置的伪构建目标,用于清理项目的构建文件,例如删除生成的可执行文件、库文件、中间编译文件等。#### 示例代码
cmake_minimum_required(VERSION 3.10)
project(CleanTargetExample)
# 添加可执行文件
add_executable(myapp main.cpp)
# 虽然 clean 目标是内置的,但可以通过自定义规则来扩展其功能
add_custom_target(clean_custom
COMMAND ${CMAKE_BUILD_TOOL} clean
COMMENT "Cleaning custom build files"
)
#### 代码解释
- `add_executable(myapp main.cpp)`:创建一个名为 `myapp` 的可执行文件。
- `add_custom_target(clean_custom ...)`:创建一个自定义的 `clean_custom` 伪目标,它会调用 `CMAKE_BUILD_TOOL`(通常是 `make` 或 `ninja`)执行 `clean` 操作,并输出一条注释信息。在执行 `make clean_custom` 或 `ninja clean_custom` 时,会触发清理操作。
### 2. `test` 目标
`test` 也是一个常见的伪构建目标,用于运行项目的测试用例。通常会结合 CTest(CMake 的测试工具)一起使用。#### 示例代码
cmake_minimum_required(VERSION 3.10)
project(TestTargetExample)
# 启用 CTest
enable_testing()
# 添加可执行文件
add_executable(myapp main.cpp)
# 添加测试用例
add_test(NAME MyAppTest COMMAND myapp)
# 创建 test 目标
add_custom_target(test
COMMAND ${CMAKE_CTEST_COMMAND}
DEPENDS myapp
COMMENT "Running tests"
)
#### 代码解释
- `enable_testing()`:启用 CTest 功能。
- `add_test(NAME MyAppTest COMMAND myapp)`:添加一个名为 `MyAppTest` 的测试用例,执行 `myapp` 可执行文件。
- `add_custom_target(test ...)`:创建一个 `test` 伪目标,它会调用 `CMAKE_CTEST_COMMAND`(CTest 的执行命令)来运行所有的测试用例。`DEPENDS myapp` 表示在运行测试之前需要先构建 `myapp` 可执行文件。
### 3. `install` 目标
`install` 目标用于将项目的文件(如可执行文件、库文件、头文件等)安装到指定的目录。#### 示例代码
cmake_minimum_required(VERSION 3.10)
project(InstallTargetExample)
# 添加可执行文件
add_executable(myapp main.cpp)
# 安装可执行文件
install(TARGETS myapp
RUNTIME DESTINATION bin
)
# 安装头文件
install(FILES myheader.h
DESTINATION include
)
#### 代码解释
- `install(TARGETS myapp RUNTIME DESTINATION bin)`:将 `myapp` 可执行文件安装到 `bin` 目录。
- `install(FILES myheader.h DESTINATION include)`:将 `myheader.h` 头文件安装到 `include` 目录。在执行 `make install` 或 `ninja install` 时,会触发安装操作。
### 4. 自定义伪目标
除了上述常见的伪目标,你还可以创建自定义的伪目标来执行特定的任务,比如代码格式化、代码检查等。#### 示例代码
cmake_minimum_required(VERSION 3.10)
project(CustomTargetExample)
# 添加可执行文件
add_executable(myapp main.cpp)
# 创建自定义伪目标,用于代码格式化
add_custom_target(format
COMMAND clang-format -i main.cpp
COMMENT "Formatting source code"
)
#### 代码解释
- `add_custom_target(format ...)`:创建一个名为 `format` 的自定义伪目标,它会调用 `clang-format` 工具对 `main.cpp` 文件进行格式化,并输出一条注释信息。在执行 `make format` 或 `ninja format` 时,会触发代码格式化操作。
通过这些示例,你可以了解如何在 CMake 中使用和创建伪构建目标来执行各种特定的任务。
子目录
### 子目录使用示例
#### 项目结构
project/
├── CMakeLists.txt # 主 CMakeLists.txt 文件
├── src/ # 源代码子目录
│ ├── CMakeLists.txt
│ ├── main.cpp
├── lib/ # 库代码子目录
│ ├── CMakeLists.txt
│ ├── mylib.cpp
│ ├── mylib.h
#### 主目录 `CMakeLists.txt`
cmake_minimum_required(VERSION 3.10)
project(SubdirectoryProject)
# 添加子目录
add_subdirectory(lib)
add_subdirectory(src)
此文件是整个项目的入口,它指定了 CMake 的最低版本和项目名称,并且通过 `add_subdirectory` 命令将 `lib` 和 `src` 子目录纳入构建体系。
#### `lib/CMakeLists.txt`
# 创建静态库
add_library(mylib STATIC mylib.cpp)
# 设置库的属性
set_target_properties(mylib PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
)
# 为库设置包含目录
target_include_directories(mylib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
该文件在 `lib` 子目录下,负责创建一个名为 `mylib` 的静态库,设置其 C++ 标准为 C++17 并要求强制使用该标准,同时将当前目录添加为公共包含目录,方便其他目标引用库的头文件。
#### `lib/mylib.h`
#ifndef MYLIB_H
#define MYLIB_H
void myFunction();
#endif
这是库的头文件,声明了一个函数 `myFunction`,使用头文件保护防止重复包含。
#### `lib/mylib.cpp`
#include <iostream>
#include "mylib.h"
void myFunction() {
std::cout << "Function from mylib is called." << std::endl;
}
此文件实现了 `mylib.h` 中声明的 `myFunction` 函数。
#### `src/CMakeLists.txt`
# 创建可执行文件
add_executable(myapp main.cpp)
# 设置可执行文件的属性
set_target_properties(myapp PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
)
# 链接 mylib 库到可执行文件
target_link_libraries(myapp PRIVATE mylib)
该文件在 `src` 子目录下,用于创建一个名为 `myapp` 的可执行文件,同样设置 C++ 标准为 C++17 并强制使用,然后将之前创建的 `mylib` 库链接到该可执行文件。
#### `src/main.cpp`
#include "mylib.h"
int main() {
myFunction();
return 0;
}
这是可执行文件的源文件,包含 `mylib.h` 头文件并调用 `myFunction` 函数。
### 子目录的文件要求
#### 1. `CMakeLists.txt` 文件
- **必需性**:这是子目录中最重要的文件,是 CMake 处理子目录的入口。每个包含构建逻辑的子目录都需要有一个 `CMakeLists.txt` 文件。
- **内容要求**:
- **构建目标定义**:可以使用 `add_executable`、`add_library` 等命令来定义可执行文件或库等构建目标。
- **属性设置**:通过 `set_target_properties` 等命令设置构建目标的属性,如编译标准、输出名称等。
- **依赖关系**:使用 `target_link_libraries`、`target_include_directories` 等命令设置目标之间的依赖关系和包含目录。
#### 2. 源文件和头文件
- **源文件(`.cpp`、`.c` 等)**:根据项目需求提供相应的源代码文件,用于实现具体的功能。这些文件会被编译成目标文件,最终链接到可执行文件或库中。
- **头文件(`.h`、`.hpp` 等)**:用于声明函数、类、结构体等,方便不同源文件之间的调用和共享。头文件应该使用头文件保护机制(如 `#ifndef`、`#define`、`#endif`)防止重复包含。
#### 3. 其他文件
- 子目录中还可以包含其他辅助文件,如资源文件、配置文件等,具体取决于项目的需求。但这些文件通常需要在 `CMakeLists.txt` 中进行相应的处理,例如使用 `install` 命令将资源文件安装到指定位置。
项目
以下将通过一个较为完整的 CMake 项目示例,详细介绍构建目标和属性的使用,项目包含可执行文件、静态库和共享库,同时会设置一些常见的目标属性。
### 项目结构
project/
├── CMakeLists.txt
├── src/
│ ├── main.cpp
│ └── CMakeLists.txt
├── lib/
│ ├── static_lib/
│ │ ├── static_lib.cpp
│ │ ├── static_lib.h
│ │ └── CMakeLists.txt
│ └── shared_lib/
│ ├── shared_lib.cpp
│ ├── shared_lib.h
│ └── CMakeLists.txt
### 详细代码及解释
#### 根目录 `CMakeLists.txt`
cmake_minimum_required(VERSION 3.10)
project(MyProject)
# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 添加子目录
add_subdirectory(lib/static_lib)
add_subdirectory(lib/shared_lib)
add_subdirectory(src)
- `cmake_minimum_required(VERSION 3.10)`:指定项目所需的最低 CMake 版本。
- `project(MyProject)`:定义项目名称。
- `set(CMAKE_CXX_STANDARD 17)` 和 `set(CMAKE_CXX_STANDARD_REQUIRED ON)`:设置项目使用 C++17 标准,并且要求必须使用该标准。
- `add_subdirectory`:将各个子目录纳入 CMake 的构建体系。
#### `lib/static_lib/CMakeLists.txt`
# 添加静态库
add_library(static_lib STATIC static_lib.cpp)
# 设置静态库的属性
set_target_properties(static_lib PROPERTIES
OUTPUT_NAME "my_static_lib"
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
)
# 设置包含目录
target_include_directories(static_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
- `add_library(static_lib STATIC static_lib.cpp)`:创建名为 `static_lib` 的静态库,源文件为 `static_lib.cpp`。
- `set_target_properties`:设置静态库的属性。`OUTPUT_NAME` 指定库的输出名称为 `my_static_lib`,`ARCHIVE_OUTPUT_DIRECTORY` 指定静态库的输出目录为构建目录下的 `lib` 文件夹。
- `target_include_directories`:将当前目录设置为公共包含目录,方便其他目标引用该库的头文件。
#### `lib/static_lib/static_lib.h`
#ifndef STATIC_LIB_H
#define STATIC_LIB_H
void staticFunction();
#endif
- 头文件,使用头文件保护防止重复包含,声明了 `staticFunction` 函数。
#### `lib/static_lib/static_lib.cpp`
#include <iostream>
#include "static_lib.h"
void staticFunction() {
std::cout << "Static function called." << std::endl;
}
- 实现了 `static_lib.h` 中声明的 `staticFunction` 函数。
#### `lib/shared_lib/CMakeLists.txt`
# 添加共享库
add_library(shared_lib SHARED shared_lib.cpp)
# 设置共享库的属性
set_target_properties(shared_lib PROPERTIES
VERSION 1.0.0
SOVERSION 1
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
)
# 设置包含目录
target_include_directories(shared_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
- `add_library(shared_lib SHARED shared_lib.cpp)`:创建名为 `shared_lib` 的共享库,源文件为 `shared_lib.cpp`。
- `set_target_properties`:设置共享库的属性。`VERSION` 指定库的版本号,`SOVERSION` 指定库的 API 版本号,`LIBRARY_OUTPUT_DIRECTORY` 指定共享库的输出目录为构建目录下的 `lib` 文件夹。
- `target_include_directories`:将当前目录设置为公共包含目录。
#### `lib/shared_lib/shared_lib.h`
#ifndef SHARED_LIB_H
#define SHARED_LIB_H
void sharedFunction();
#endif
- 头文件,声明了 `sharedFunction` 函数。
#### `lib/shared_lib/shared_lib.cpp`
#include <iostream>
#include "shared_lib.h"
void sharedFunction() {
std::cout << "Shared function called." << std::endl;
}
- 实现了 `shared_lib.h` 中声明的 `sharedFunction` 函数。
#### `src/CMakeLists.txt`
# 添加可执行文件
add_executable(myapp main.cpp)
# 链接静态库和共享库
target_link_libraries(myapp PRIVATE static_lib shared_lib)
# 设置可执行文件的属性
set_target_properties(myapp PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
- `add_executable(myapp main.cpp)`:创建名为 `myapp` 的可执行文件,源文件为 `main.cpp`。
- `target_link_libraries`:将 `static_lib` 和 `shared_lib` 链接到 `myapp` 可执行文件。
- `set_target_properties`:设置可执行文件的属性,`RUNTIME_OUTPUT_DIRECTORY` 指定可执行文件的输出目录为构建目录下的 `bin` 文件夹。
#### `src/main.cpp`
#include "static_lib.h"
#include "shared_lib.h"
int main() {
staticFunction();
sharedFunction();
return 0;
}
- 包含静态库和共享库的头文件,并调用它们的函数。
### 构建步骤
1. 在项目根目录下创建 `build` 目录并进入:
```sh
mkdir build
cd build
```
2. 运行 CMake 生成构建文件:
```sh
cmake ..
```
3. 编译项目:
```sh
make
```
4. 运行可执行文件:
```sh
./bin/myapp
```
通过这个示例,你可以看到如何使用 CMake 定义不同类型的构建目标(可执行文件、静态库、共享库),并设置它们的属性,同时处理目标之间的依赖关系。
属性get_property、set_property
更像是一个user_data.
在 CMake 中,`get_property` 和 `set_property` 是用于获取和设置属性的重要命令。属性可以关联到不同的作用域,如目标(target)、源文件(source file)、目录(directory)等。以下是针对不同作用域使用 `get_property` 和 `set_property` 的详细示例。
### 1. 目标作用域(Target Scope)
目标作用域的属性通常与构建目标(如可执行文件、库)相关。
cmake_minimum_required(VERSION 3.10)
project(TargetPropertyExample)
# 添加可执行文件
add_executable(myapp main.cpp)
# 设置目标属性
set_property(TARGET myapp PROPERTY COMPILE_FLAGS "-Wall -Wextra")
# 获取目标属性
get_property(compile_flags TARGET myapp PROPERTY COMPILE_FLAGS)
message(STATUS "Compile flags for myapp: ${compile_flags}")
**解释**:
- `add_executable(myapp main.cpp)`:创建一个名为 `myapp` 的可执行文件。
- `set_property(TARGET myapp PROPERTY COMPILE_FLAGS "-Wall -Wextra")`:为 `myapp` 目标设置 `COMPILE_FLAGS` 属性,这里设置的编译标志是 `-Wall -Wextra`,用于开启更多的编译警告。
- `get_property(compile_flags TARGET myapp PROPERTY COMPILE_FLAGS)`:从 `myapp` 目标获取 `COMPILE_FLAGS` 属性的值,并将其存储在 `compile_flags` 变量中。
- `message(STATUS ...)`:输出获取到的编译标志。
### 2. 源文件作用域(Source File Scope)
源文件作用域的属性与单个源文件相关。
这相当于增加了一个文件预处理器(宏)
cmake_minimum_required(VERSION 3.10)
project(SourceFilePropertyExample)
# 定义源文件列表
set(SOURCE_FILES main.cpp helper.cpp)
# 添加可执行文件
add_executable(myapp ${SOURCE_FILES})
# 为特定源文件设置属性
set_property(SOURCE helper.cpp PROPERTY COMPILE_DEFINITIONS DEBUG_MODE)
# 获取源文件属性
get_property(definitions SOURCE helper.cpp PROPERTY COMPILE_DEFINITIONS)
message(STATUS "Compile definitions for helper.cpp: ${definitions}")
**解释**:
- `set(SOURCE_FILES main.cpp helper.cpp)`:定义一个包含两个源文件的列表。
- `add_executable(myapp ${SOURCE_FILES})`:创建一个名为 `myapp` 的可执行文件,使用上述源文件列表。
- `set_property(SOURCE helper.cpp PROPERTY COMPILE_DEFINITIONS DEBUG_MODE)`:为 `helper.cpp` 源文件设置 `COMPILE_DEFINITIONS` 属性,这里定义了一个编译宏 `DEBUG_MODE`。
- `get_property(definitions SOURCE helper.cpp PROPERTY COMPILE_DEFINITIONS)`:从 `helper.cpp` 源文件获取 `COMPILE_DEFINITIONS` 属性的值,并将其存储在 `definitions` 变量中。
- `message(STATUS ...)`:输出获取到的编译定义。
文件属性,设置的意义,使用举例
### `PROPERTY` 设置文件属性的意义
在 CMake 中,使用 `set_property` 命令结合 `PROPERTY` 关键字为源文件设置属性具有重要的意义,主要体现在以下几个方面:
#### 1. 定制编译行为
通过设置源文件的属性,可以为特定的源文件定制编译选项。例如,为某些源文件添加特定的编译定义(`COMPILE_DEFINITIONS`)、编译标志(`COMPILE_FLAGS`)等,从而让这些源文件在编译时采用与其他源文件不同的行为。在上述代码中,为 `helper.cpp` 设置 `COMPILE_DEFINITIONS` 属性为 `DEBUG_MODE`,意味着在编译 `helper.cpp` 时会定义 `DEBUG_MODE` 这个预处理宏,这样在 `helper.cpp` 及其包含的头文件中就可以根据这个宏来执行不同的代码逻辑。
#### 2. 模块化管理
当项目规模较大时,不同的源文件可能有不同的需求。通过为源文件设置属性,可以实现对每个源文件的独立管理,使代码更加模块化。例如,某些源文件可能需要使用特定的 C++ 标准、开启特定的警告级别等,通过设置属性可以很方便地满足这些需求。
#### 3. 提高代码的可维护性
属性设置可以将特定源文件的编译相关信息集中管理,避免在多个地方重复设置相同的编译选项。当需要修改某个源文件的编译行为时,只需要修改其对应的属性设置即可,提高了代码的可维护性。
### 如何使用设置的属性
#### 在 C++ 代码中使用
当为源文件设置了 `COMPILE_DEFINITIONS` 属性后,可以在对应的源文件及其包含的头文件中使用 `#ifdef` 或 `#if defined` 来检查这个编译定义是否被定义,从而执行不同的代码逻辑。以下是结合上述 CMake 代码的示例:
**`helper.cpp` 文件**
#include <iostream>
// 检查 DEBUG_MODE 是否被定义
#ifdef DEBUG_MODE
void debugFunction() {
std::cout << "Debug mode is enabled in helper.cpp!" << std::endl;
}
#endif
void someFunction() {
#ifdef DEBUG_MODE
debugFunction();
#else
std::cout << "Normal mode in helper.cpp." << std::endl;
#endif
}
在这个示例中,如果 `DEBUG_MODE` 被定义(即 CMake 中为 `helper.cpp` 设置了该编译定义),那么在调用 `someFunction` 时会执行 `debugFunction` 并输出调试信息;否则,输出正常模式的信息。
#### 在 CMake 中进一步使用
在 CMake 中,设置的属性可以用于进一步的配置。例如,可以根据某个源文件的属性来决定是否添加额外的依赖项或链接库。以下是一个简单的示例:
cmake_minimum_required(VERSION 3.10)
project(SourceFilePropertyExample)
# 定义源文件列表
set(SOURCE_FILES main.cpp helper.cpp)
# 添加可执行文件
add_executable(myapp ${SOURCE_FILES})
# 为特定源文件设置属性
set_property(SOURCE helper.cpp PROPERTY COMPILE_DEFINITIONS DEBUG_MODE)
# 获取源文件属性
get_property(definitions SOURCE helper.cpp PROPERTY COMPILE_DEFINITIONS)
message(STATUS "Compile definitions for helper.cpp: ${definitions}")
# 根据属性决定是否添加额外的依赖项
list(FIND definitions "DEBUG_MODE" debug_index)
if(debug_index GREATER -1)
target_link_libraries(myapp PRIVATE debug_library)
endif()
在这个示例中,通过检查 `helper.cpp` 的 `COMPILE_DEFINITIONS` 属性中是否包含 `DEBUG_MODE`,来决定是否将 `debug_library` 链接到 `myapp` 可执行文件中。这样可以根据源文件的属性动态地调整项目的构建配置。
### 3. 目录作用域(Directory Scope)
目录作用域的属性适用于当前 CMake 目录及其子目录。
cmake_minimum_required(VERSION 3.10)
project(DirectoryPropertyExample)
# 设置目录属性
set_property(DIRECTORY PROPERTY CXX_STANDARD 17)
# 获取目录属性
get_property(cxx_standard DIRECTORY PROPERTY CXX_STANDARD)
message(STATUS "C++ standard for this directory: ${cxx_standard}")
# 添加可执行文件
add_executable(myapp main.cpp)
**解释**:
- `set_property(DIRECTORY PROPERTY CXX_STANDARD 17)`:为当前目录设置 `CXX_STANDARD` 属性,指定使用 C++17 标准。
- `get_property(cxx_standard DIRECTORY PROPERTY CXX_STANDARD)`:从当前目录获取 `CXX_STANDARD` 属性的值,并将其存储在 `cxx_standard` 变量中。
- `message(STATUS ...)`:输出获取到的 C++ 标准。
- `add_executable(myapp main.cpp)`:创建一个名为 `myapp` 的可执行文件,该可执行文件会继承目录的 `CXX_STANDARD` 属性。
### 4. 全局作用域(Global Scope)
全局作用域的属性在整个 CMake 项目中都有效。
cmake_minimum_required(VERSION 3.10)
project(GlobalPropertyExample)
# 设置全局属性
set_property(GLOBAL PROPERTY MY_GLOBAL_PROPERTY "This is a global property")
# 获取全局属性
get_property(global_prop GLOBAL PROPERTY MY_GLOBAL_PROPERTY)
message(STATUS "Global property value: ${global_prop}")
**解释**:
- `set_property(GLOBAL PROPERTY MY_GLOBAL_PROPERTY "This is a global property")`:设置一个全局属性 `MY_GLOBAL_PROPERTY`,其值为 `"This is a global property"`。
- `get_property(global_prop GLOBAL PROPERTY MY_GLOBAL_PROPERTY)`:从全局作用域获取 `MY_GLOBAL_PROPERTY` 属性的值,并将其存储在 `global_prop` 变量中。
- `message(STATUS ...)`:输出获取到的全局属性值。
通过这些示例,你可以看到如何使用 `get_property` 和 `set_property` 命令在不同作用域中获取和设置属性,从而实现对 CMake 项目更灵活的配置。
属性相关命令
设置目标编译参数,设置目标编译特性,设置头文件目录include_directories,设置目标头文件目录target_include_directories,设置链接库link_libraries,设置链接目录link_directories,设置目标链接参数target_link_options,设置目标源文件target_sources
以下为你详细介绍每个属性相关命令的使用示例:
### 1. 设置目标编译参数
使用 `set_target_properties` 或 `target_compile_options` 来设置目标的编译参数。
cmake_minimum_required(VERSION 3.10)
project(CompileParamsExample)
add_executable(myapp main.cpp)
# 使用 set_target_properties 设置编译参数
set_target_properties(myapp PROPERTIES
COMPILE_FLAGS "-Wall -Wextra"
)
# 或者使用 target_compile_options
target_compile_options(myapp PRIVATE "-Werror")
在上述代码中,`-Wall -Wextra` 开启了所有常见的警告信息,`-Werror` 则将所有警告都视为错误。`PRIVATE` 表示这些编译选项仅应用于 `myapp` 目标本身。
### 2. 设置目标编译特性
使用 `target_compile_features` 来设置目标所需的编译特性。
cmake_minimum_required(VERSION 3.10)
project(CompileFeaturesExample)
add_executable(myapp main.cpp)
# 设置 C++17 标准作为编译特性
target_compile_features(myapp PRIVATE cxx_std_17)
这里指定 `myapp` 目标需要使用 C++17 标准进行编译。`PRIVATE` 表明该特性仅用于 `myapp` 目标,不会传递给依赖它的其他目标。
### 3. 设置头文件目录 `include_directories`
`include_directories` 用于为当前 CMake 作用域内的所有目标设置头文件包含目录。
cmake_minimum_required(VERSION 3.10)
project(IncludeDirsExample)
# 设置头文件目录
include_directories("${CMAKE_SOURCE_DIR}/include")
add_executable(myapp main.cpp)
上述代码将项目源目录下的 `include` 目录添加到所有后续目标的头文件搜索路径中。
### 4. 设置目标头文件目录 `target_include_directories`
`target_include_directories` 用于为特定目标设置头文件包含目录。
cmake_minimum_required(VERSION 3.10)
project(TargetIncludeDirsExample)
add_executable(myapp main.cpp)
# 为 myapp 目标设置头文件目录
target_include_directories(myapp PRIVATE "${CMAKE_SOURCE_DIR}/include")
这里为 `myapp` 目标单独设置了头文件包含目录,`PRIVATE` 表示该目录仅对 `myapp` 目标有效。
### 5. 设置链接库 `link_libraries`
`link_libraries` 用于为当前 CMake 作用域内的所有目标设置链接库。
cmake_minimum_required(VERSION 3.10)
project(LinkLibrariesExample)
# 设置链接库
link_libraries(m)
add_executable(myapp main.cpp)
上述代码将数学库 `m` 链接到所有后续目标。
### 6. 设置链接目录 `link_directories`
`link_directories` 用于为当前 CMake 作用域内的所有目标设置库文件的搜索目录。
cmake_minimum_required(VERSION 3.10)
project(LinkDirectoriesExample)
# 设置链接目录
link_directories("${CMAKE_SOURCE_DIR}/lib")
add_executable(myapp main.cpp)
这里将项目源目录下的 `lib` 目录添加到所有后续目标的库文件搜索路径中。
### 7. 设置目标链接参数 `target_link_options`
`target_link_options` 用于为特定目标设置链接参数。
cmake_minimum_required(VERSION 3.10)
project(LinkOptionsExample)
add_executable(myapp main.cpp)
# 为 myapp 目标设置链接参数
target_link_options(myapp PRIVATE "-Wl,--as-needed")
`-Wl,--as-needed` 是一个链接器选项,用于确保只链接实际使用的库。`PRIVATE` 表示该链接选项仅应用于 `myapp` 目标。
### 8. 设置目标源文件 `target_sources`
`target_sources` 用于为特定目标添加源文件。
cmake_minimum_required(VERSION 3.10)
project(TargetSourcesExample)
add_executable(myapp "")
# 为 myapp 目标添加源文件
target_sources(myapp PRIVATE main.cpp helper.cpp)
上述代码为 `myapp` 目标添加了 `main.cpp` 和 `helper.cpp` 两个源文件。`PRIVATE` 表示这些源文件仅用于 `myapp` 目标。
综上所述,这些属性相关命令可以帮助你灵活地配置 CMake 项目中各个目标的编译、链接等属性。
include_directories和target_include_directories的区别
include_directories该命令会为当前 CMake 作用域内的所有目标(包括后续添加的目标)设置头文件包含目录。也就是说,一旦使用 include_directories 设置了头文件目录,在该命令之后通过 add_executable 或 add_library 创建的所有目标都会使用这些目录来搜索头文件。
target_include_directories此命令是为特定的目标设置头文件包含目录,只会影响指定的目标。你可以针对不同的目标分别设置不同的头文件包含目录,实现更细粒度的控制。
无须递归传递的例程,存在间接引用的例程
在 CMake 中,很多属性相关命令都可以设置传递性,通过不同的关键字(如 `PRIVATE`、`PUBLIC`、`INTERFACE`)来控制属性是否递归传递。下面为你分别给出设置目标编译参数、设置目标头文件目录、设置链接库这几个常见操作的无须递归传递和存在间接引用的例程。
### 1. 设置目标编译参数
#### 无须递归传递
```cmake
cmake_minimum_required(VERSION 3.10)
project(NonRecursiveCompileParams)
# 创建库
add_library(mylib STATIC mylib.cpp)
# 为 mylib 设置编译参数,不传递给依赖它的目标
target_compile_options(mylib PRIVATE -Wall)
# 创建可执行文件并链接库
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE mylib)
```
在这个例子中,`target_compile_options` 使用 `PRIVATE` 关键字为 `mylib` 设置了编译参数 `-Wall`。这意味着 `-Wall` 仅应用于 `mylib` 本身,不会传递给依赖 `mylib` 的 `myapp` 目标。
#### 存在间接引用
```cmake
cmake_minimum_required(VERSION 3.10)
project(IndirectCompileParams)
# 创建库 A
add_library(libA STATIC libA.cpp)
target_compile_options(libA PRIVATE -Wextra)
# 创建库 B,依赖于库 A
add_library(libB STATIC libB.cpp)
target_link_libraries(libB PRIVATE libA)
# 创建可执行文件,依赖于库 B
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE libB)
```
这里 `libA` 设置了 `-Wextra` 编译参数,`libB` 依赖于 `libA`,`myapp` 又依赖于 `libB`。由于使用了 `PRIVATE` 关键字,`-Wextra` 编译参数仅应用于 `libA`,不会通过 `libB` 间接传递给 `myapp`。
### 2. 设置目标头文件目录
#### 无须递归传递
```cmake
cmake_minimum_required(VERSION 3.10)
project(NonRecursiveIncludeDirs)
# 创建库
add_library(mylib STATIC mylib.cpp)
# 为 mylib 设置头文件目录,不传递给依赖它的目标
target_include_directories(mylib PRIVATE "${CMAKE_SOURCE_DIR}/include/mylib")
# 创建可执行文件并链接库
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE mylib)
```
使用 `PRIVATE` 关键字,`mylib` 的头文件目录 `${CMAKE_SOURCE_DIR}/include/mylib` 仅对 `mylib` 本身有效,不会传递给依赖它的 `myapp` 目标。
#### 存在间接引用
```cmake
cmake_minimum_required(VERSION 3.10)
project(IndirectIncludeDirs)
# 创建库 A
add_library(libA STATIC libA.cpp)
target_include_directories(libA PRIVATE "${CMAKE_SOURCE_DIR}/include/libA")
# 创建库 B,依赖于库 A
add_library(libB STATIC libB.cpp)
target_link_libraries(libB PRIVATE libA)
# 创建可执行文件,依赖于库 B
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE libB)
```
`libA` 的头文件目录 `${CMAKE_SOURCE_DIR}/include/libA` 因为使用了 `PRIVATE` 关键字,不会通过 `libB` 间接传递给 `myapp`。
### 3. 设置链接库
#### 无须递归传递
```cmake
cmake_minimum_required(VERSION 3.10)
project(NonRecursiveLinkLibraries)
# 创建库
add_library(mylib STATIC mylib.cpp)
# 为 mylib 链接数学库,不传递给依赖它的目标
target_link_libraries(mylib PRIVATE m)
# 创建可执行文件并链接库
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE mylib)
```
`target_link_libraries` 使用 `PRIVATE` 关键字为 `mylib` 链接了数学库 `m`,该链接不会传递给依赖 `mylib` 的 `myapp` 目标。
#### 存在间接引用
```cmake
cmake_minimum_required(VERSION 3.10)
project(IndirectLinkLibraries)
# 创建库 A
add_library(libA STATIC libA.cpp)
target_link_libraries(libA PRIVATE pthread)
# 创建库 B,依赖于库 A
add_library(libB STATIC libB.cpp)
target_link_libraries(libB PRIVATE libA)
# 创建可执行文件,依赖于库 B
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE libB)
```
`libA` 链接了 `pthread` 库,由于使用 `PRIVATE` 关键字,该链接不会通过 `libB` 间接传递给 `myapp`。
### 总结
通过使用 `PRIVATE` 关键字,可以确保属性(编译参数、头文件目录、链接库等)不会递归传递给依赖目标,即使存在间接引用也不会受影响。而 `PUBLIC` 关键字会使属性传递给依赖目标,`INTERFACE` 关键字用于只传递属性给依赖目标而不应用于当前目标本身。
自定义构建规则、目标
在 CMake 中,我们可以通过自定义构建规则和目标来满足项目中一些特殊的构建需求,比如生成特定格式的文件、执行脚本等。下面为你详细介绍自定义构建规则和目标的相关示例。
### 自定义构建规则(`add_custom_command`)
#### 示例 1:生成文件
假设我们需要通过一个 Python 脚本 `generate_data.py` 来生成一个数据文件 `data.txt`。
cmake_minimum_required(VERSION 3.10)
project(CustomCommandExample)
# 定义自定义命令
add_custom_command(
OUTPUT data.txt
COMMAND python3 ${CMAKE_SOURCE_DIR}/generate_data.py
DEPENDS ${CMAKE_SOURCE_DIR}/generate_data.py
COMMENT "Generating data.txt using Python script"
)
# 添加可执行文件
add_executable(myapp main.cpp)
# 让可执行文件依赖于生成的文件
add_dependencies(myapp data.txt)
**代码解释**:
- `add_custom_command`:
- `OUTPUT data.txt`:指定该命令的输出文件为 `data.txt`。
- `COMMAND python3 ${CMAKE_SOURCE_DIR}/generate_data.py`:执行 Python 脚本 `generate_data.py` 来生成 `data.txt`。
- `DEPENDS ${CMAKE_SOURCE_DIR}/generate_data.py`:指定该命令依赖于 `generate_data.py` 脚本,当脚本文件发生变化时,会重新执行该命令。
- `COMMENT "Generating data.txt using Python script"`:在构建过程中输出的提示信息。
- `add_dependencies`:将可执行文件 `myapp` 与生成的 `data.txt` 文件建立依赖关系,确保在构建 `myapp` 之前先生成 `data.txt`。
#### 示例 2:编译后执行清理操作
在编译可执行文件后,我们可能需要清理一些临时文件。
cmake_minimum_required(VERSION 3.10)
project(CustomCommandCleanExample)
# 添加可执行文件
add_executable(myapp main.cpp)
# 定义自定义命令,在可执行文件构建完成后执行清理操作
add_custom_command(
TARGET myapp
POST_BUILD
COMMAND rm -f temp_file.txt
COMMENT "Cleaning up temporary files"
)
**代码解释**:
- `TARGET myapp`:指定该自定义命令关联的目标为 `myapp`。
- `POST_BUILD`:表示该命令在目标构建完成后执行。
- `COMMAND rm -f temp_file.txt`:执行 `rm` 命令删除临时文件 `temp_file.txt`。
- `COMMENT "Cleaning up temporary files"`:构建过程中的提示信息。
### 自定义构建目标(`add_custom_target`)
#### 示例 1:运行测试脚本
假设我们有一个测试脚本 `run_tests.sh`,我们可以创建一个自定义目标来运行这个脚本。
cmake_minimum_required(VERSION 3.10)
project(CustomTargetTestExample)
# 添加可执行文件
add_executable(myapp main.cpp)
# 定义自定义目标
add_custom_target(
run_tests
COMMAND sh ${CMAKE_SOURCE_DIR}/run_tests.sh
DEPENDS myapp
COMMENT "Running tests"
)
**代码解释**:
- `add_custom_target`:
- `run_tests`:自定义目标的名称。
- `COMMAND sh ${CMAKE_SOURCE_DIR}/run_tests.sh`:执行测试脚本 `run_tests.sh`。
- `DEPENDS myapp`:指定该目标依赖于 `myapp`,确保在运行测试之前先构建 `myapp`。
- `COMMENT "Running tests"`:构建过程中的提示信息。
#### 示例 2:代码格式化
我们可以创建一个自定义目标来对项目中的源代码进行格式化。
cmake_minimum_required(VERSION 3.10)
project(CustomTargetFormatExample)
# 定义自定义目标
add_custom_target(
format
COMMAND clang-format -i ${CMAKE_SOURCE_DIR}/src/*.cpp ${CMAKE_SOURCE_DIR}/src/*.h
COMMENT "Formatting source code"
)
**代码解释**:
- `format`:自定义目标的名称。
- `COMMAND clang-format -i ${CMAKE_SOURCE_DIR}/src/*.cpp ${CMAKE_SOURCE_DIR}/src/*.h`:使用 `clang-format` 工具对 `src` 目录下的所有 `.cpp` 和 `.h` 文件进行格式化。
- `COMMENT "Formatting source code"`:构建过程中的提示信息。
通过上述示例,你可以看到如何使用 `add_custom_command` 和 `add_custom_target` 来创建自定义构建规则和目标,以满足项目中的特殊需求。
设置依赖
在 CMake 中,设置依赖关系是非常重要的,它可以确保在构建某个目标之前,其依赖的目标已经被正确构建。以下将从不同类型的依赖关系出发,给出具体的示例。
### 1. 目标间的依赖关系
#### 可执行文件依赖库
假设我们有一个库 `mylib` 和一个可执行文件 `myapp`,`myapp` 需要依赖 `mylib` 来运行。
cmake_minimum_required(VERSION 3.10)
project(DependencyExample)
# 添加静态库
add_library(mylib STATIC mylib.cpp)
# 添加可执行文件
add_executable(myapp main.cpp)
# 设置可执行文件依赖库
target_link_libraries(myapp PRIVATE mylib)
**代码解释**:
- `add_library(mylib STATIC mylib.cpp)`:创建一个名为 `mylib` 的静态库,源文件为 `mylib.cpp`。
- `add_executable(myapp main.cpp)`:创建一个名为 `myapp` 的可执行文件,源文件为 `main.cpp`。
- `target_link_libraries(myapp PRIVATE mylib)`:将 `myapp` 与 `mylib` 进行链接,这不仅建立了链接关系,还确保在构建 `myapp` 之前,`mylib` 已经被构建完成。`PRIVATE` 表示该依赖关系仅对 `myapp` 自身有效。
#### 多个目标的依赖链
假设有三个目标:`libA`、`libB` 和 `myapp`,`libB` 依赖 `libA`,`myapp` 依赖 `libB`。
cmake_minimum_required(VERSION 3.10)
project(MultiDependencyExample)
# 添加库 libA
add_library(libA STATIC libA.cpp)
# 添加库 libB,依赖于 libA
add_library(libB STATIC libB.cpp)
target_link_libraries(libB PRIVATE libA)
# 添加可执行文件,依赖于 libB
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE libB)
**代码解释**:
- 首先创建 `libA` 库。
- 然后创建 `libB` 库,并通过 `target_link_libraries` 将 `libB` 与 `libA` 链接,确保 `libB` 构建前 `libA` 已完成构建。
- 最后创建 `myapp` 可执行文件,并将其与 `libB` 链接,形成一个依赖链。
### 2. 自定义命令和目标的依赖关系
#### 可执行文件依赖自定义命令生成的文件
假设我们通过一个自定义命令生成一个配置文件 `config.txt`,可执行文件 `myapp` 需要依赖这个配置文件。
cmake_minimum_required(VERSION 3.10)
project(CustomCommandDependencyExample)
# 定义自定义命令生成配置文件
add_custom_command(
OUTPUT config.txt
COMMAND echo "This is a config file" > config.txt
COMMENT "Generating config.txt"
)
# 添加可执行文件
add_executable(myapp main.cpp)
# 设置可执行文件依赖生成的配置文件
add_dependencies(myapp config.txt)
**代码解释**:
- `add_custom_command`:定义一个自定义命令,其输出为 `config.txt`,通过 `echo` 命令生成该文件。
- `add_executable(myapp main.cpp)`:创建 `myapp` 可执行文件。
- `add_dependencies(myapp config.txt)`:建立 `myapp` 与 `config.txt` 的依赖关系,确保在构建 `myapp` 之前,`config.txt` 已经生成。
#### 自定义目标依赖其他目标
假设我们有一个自定义目标 `run_tests` 用于运行测试,它需要依赖可执行文件 `myapp` 先构建完成。
cmake_minimum_required(VERSION 3.10)
project(CustomTargetDependencyExample)
# 添加可执行文件
add_executable(myapp main.cpp)
# 定义自定义目标,依赖于 myapp
add_custom_target(
run_tests
COMMAND ./myapp --test
DEPENDS myapp
COMMENT "Running tests"
)
**代码解释**:
- `add_executable(myapp main.cpp)`:创建 `myapp` 可执行文件。
- `add_custom_target`:创建一个名为 `run_tests` 的自定义目标,其命令是运行 `myapp` 并传递 `--test` 参数。`DEPENDS myapp` 确保在运行 `run_tests` 之前,`myapp` 已经构建完成。
### 3. 外部项目的依赖关系
假设我们的项目依赖于一个外部的 CMake 项目 `external_project`。
cmake_minimum_required(VERSION 3.10)
project(ExternalProjectDependencyExample)
include(ExternalProject)
# 添加外部项目
ExternalProject_Add(
external_project
SOURCE_DIR ${CMAKE_SOURCE_DIR}/external_project
BINARY_DIR ${CMAKE_BINARY_DIR}/external_project-build
CONFIGURE_COMMAND ${CMAKE_COMMAND} ${CMAKE_SOURCE_DIR}/external_project
BUILD_COMMAND ${CMAKE_COMMAND} --build .
INSTALL_COMMAND ""
)
# 添加可执行文件
add_executable(myapp main.cpp)
# 设置可执行文件依赖外部项目
add_dependencies(myapp external_project)
**代码解释**:
- `include(ExternalProject)`:包含 CMake 的 `ExternalProject` 模块。
- `ExternalProject_Add`:添加一个外部项目 `external_project`,指定其源代码目录、构建目录、配置命令和构建命令等。
- `add_executable(myapp main.cpp)`:创建 `myapp` 可执行文件。
- `add_dependencies(myapp external_project)`:建立 `myapp` 与外部项目 `external_project` 的依赖关系,确保在构建 `myapp` 之前,外部项目已经构建完成。
生成表达式
创建构建目标的命令
在 CMake 中,生成表达式是一种强大的机制,它允许在生成构建系统时动态计算值。创建构建目标的命令(如 `add_executable`、`add_library` 等)结合生成表达式使用,可以实现更灵活的构建配置。以下是一些常见的创建构建目标命令结合生成表达式的例子。
### 1. `add_executable` 结合生成表达式设置编译选项
cmake_minimum_required(VERSION 3.10)
project(ExecutableWithGenExpr)
# 添加可执行文件
add_executable(myapp main.cpp)
# 根据构建类型设置编译选项
target_compile_options(myapp PRIVATE
"$<$<CONFIG:Debug>:-g -O0>"
"$<$<CONFIG:Release>:-O3>"
)
**解释**:
- `add_executable(myapp main.cpp)`:创建一个名为 `myapp` 的可执行文件,源文件为 `main.cpp`。
- `target_compile_options`:用于为目标(这里是 `myapp`)设置编译选项。
- 生成表达式 `$<$<CONFIG:Debug>:-g -O0>` 表示当构建类型为 `Debug` 时,添加 `-g -O0` 编译选项,用于生成调试信息并关闭优化。
- 生成表达式 `$<$<CONFIG:Release>:-O3>` 表示当构建类型为 `Release` 时,添加 `-O3` 编译选项,开启最高级别的优化。
### 2. `add_library` 结合生成表达式设置链接库
cmake_minimum_required(VERSION 3.10)
project(LibraryWithGenExpr)
# 添加共享库
add_library(mylib SHARED mylib.cpp)
# 根据平台设置链接库
target_link_libraries(mylib PRIVATE
"$<$<PLATFORM_ID:Linux>:pthread>"
"$<$<PLATFORM_ID:Windows>:ws2_32>"
)
**解释**:
- `add_library(mylib SHARED mylib.cpp)`:创建一个名为 `mylib` 的共享库,源文件为 `mylib.cpp`。
- `target_link_libraries`:用于为目标(这里是 `mylib`)设置链接库。
- 生成表达式 `$<$<PLATFORM_ID:Linux>:pthread>` 表示当平台为 Linux 时,链接 `pthread` 库,用于支持多线程编程。
- 生成表达式 `$<$<PLATFORM_ID:Windows>:ws2_32>` 表示当平台为 Windows 时,链接 `ws2_32` 库,用于支持网络编程。### 3. `add_executable` 结合生成表达式设置包含目录
cmake_minimum_required(VERSION 3.10)
project(IncludeDirWithGenExpr)
# 添加可执行文件
add_executable(myapp main.cpp)
# 根据构建类型设置包含目录
target_include_directories(myapp PRIVATE
"$<$<CONFIG:Debug>:${CMAKE_SOURCE_DIR}/debug_include>"
"$<$<CONFIG:Release>:${CMAKE_SOURCE_DIR}/release_include>"
)
**解释**:
- `add_executable(myapp main.cpp)`:创建一个名为 `myapp` 的可执行文件,源文件为 `main.cpp`。
- `target_include_directories`:用于为目标(这里是 `myapp`)设置包含目录。
- 生成表达式 `$<$<CONFIG:Debug>:${CMAKE_SOURCE_DIR}/debug_include>` 表示当构建类型为 `Debug` 时,添加 `debug_include` 目录到包含路径中。
- 生成表达式 `$<$<CONFIG:Release>:${CMAKE_SOURCE_DIR}/release_include>` 表示当构建类型为 `Release` 时,添加 `release_include` 目录到包含路径中。
通过这些例子可以看出,生成表达式可以让我们在创建构建目标时根据不同的条件动态地设置编译选项、链接库和包含目录等,从而实现更灵活的构建配置。
布尔型生成器表达式
在 CMake 中,布尔型生成器表达式用于根据布尔条件来动态地确定值,常见的布尔条件有构建配置(如 Debug、Release)、平台信息、目标类型等。以下是一些布尔型生成器表达式的使用示例。### 1. 根据构建配置添加编译选项
cmake_minimum_required(VERSION 3.10)
project(BooleanGenExprBuildConfig)
# 添加可执行文件
add_executable(myapp main.cpp)
# 当构建配置为 Debug 时添加 -g 编译选项
target_compile_options(myapp PRIVATE
"$<$<CONFIG:Debug>:-g>"
)
**解释**:
- `$<$<CONFIG:Debug>:-g>` 是一个布尔型生成器表达式。`<CONFIG:Debug>` 是一个条件,用于判断当前的构建配置是否为 `Debug`。如果是,则表达式的值为 `-g`,表示添加调试信息的编译选项;如果不是,则表达式的值为空。
- `target_compile_options` 用于为目标 `myapp` 添加编译选项。### 2. 根据目标类型设置链接库
cmake_minimum_required(VERSION 3.10)
project(BooleanGenExprTargetType)
# 添加共享库
add_library(mylib SHARED mylib.cpp)
# 添加静态库
add_library(mylib_static STATIC mylib.cpp)
# 当目标类型为 SHARED 时链接 m 库(数学库)
target_link_libraries(mylib PRIVATE
"$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,SHARED>:m>"
)
**解释**:
- `$<STREQUAL:$<TARGET_PROPERTY:TYPE>,SHARED>` 是一个布尔型生成器表达式。`$<TARGET_PROPERTY:TYPE>` 用于获取目标的类型,`STREQUAL` 用于比较两个值是否相等。这里是判断目标的类型是否为 `SHARED`。
- 如果目标类型是 `SHARED`,则表达式的值为 `m`,表示链接数学库;否则,表达式的值为空。
- `target_link_libraries` 用于为目标 `mylib` 添加链接库。### 3. 根据平台信息设置包含目录
cmake_minimum_required(VERSION 3.10)
project(BooleanGenExprPlatform)
# 添加可执行文件
add_executable(myapp main.cpp)
# 当平台为 Linux 时添加特定的包含目录
target_include_directories(myapp PRIVATE
"$<$<PLATFORM_ID:Linux>:${CMAKE_SOURCE_DIR}/linux_include>"
)
**解释**:
- `$<$<PLATFORM_ID:Linux>:${CMAKE_SOURCE_DIR}/linux_include>` 是一个布尔型生成器表达式。`<PLATFORM_ID:Linux>` 用于判断当前的平台是否为 Linux。
- 如果是 Linux 平台,则表达式的值为 `${CMAKE_SOURCE_DIR}/linux_include`,表示添加该目录到包含路径中;否则,表达式的值为空。
- `target_include_directories` 用于为目标 `myapp` 添加包含目录。### 4. 组合布尔条件
cmake_minimum_required(VERSION 3.10)
project(BooleanGenExprCombined)
# 添加可执行文件
add_executable(myapp main.cpp)
# 当构建配置为 Debug 且平台为 Linux 时添加特定的编译定义
target_compile_definitions(myapp PRIVATE
"$<$<AND:$<CONFIG:Debug>,$<PLATFORM_ID:Linux>>:DEBUG_ON_LINUX>"
)
上面的DEBUG_ON_LINUX是新建了一个预处理器(宏),这样使用:
#include <iostream>
int main() {
#ifdef DEBUG_ON_LINUX
std::cout << "Debug mode on Linux platform." << std::endl;
#else
std::cout << "Not in debug mode on Linux or not on Linux." << std::endl;
#endif
return 0;
}
**解释**:
- `$<$<AND:$<CONFIG:Debug>,$<PLATFORM_ID:Linux>>:DEBUG_ON_LINUX>` 是一个组合布尔型生成器表达式。`AND` 用于组合两个布尔条件,即判断当前构建配置是否为 `Debug` 且平台是否为 Linux。
- 如果两个条件都满足,则表达式的值为 `DEBUG_ON_LINUX`,表示添加该编译定义;否则,表达式的值为空。
- `target_compile_definitions` 用于为目标 `myapp` 添加编译定义。
通过这些示例,你可以看到布尔型生成器表达式在 CMake 中能够根据不同的条件灵活地配置构建过程。
字符串生成器表达式
在 CMake 中,字符串生成器表达式用于对字符串进行操作和转换,可根据不同条件动态生成字符串。以下是几种常见的字符串生成器表达式的使用示例。
1. `$<JOIN>` 表达式:连接字符串列表
`$<JOIN>` 用于将一个字符串列表连接成一个字符串,各元素之间可以指定分隔符。
cmake_minimum_required(VERSION 3.12)
project(StringJoinExample)
# 定义一个字符串列表
set(MY_LIST "apple" "banana" "cherry")
# 使用 $<JOIN> 表达式连接列表元素,用逗号分隔
set(JOINED_STRING "$<JOIN:${MY_LIST},,>")
# 添加可执行文件
add_executable(myapp main.cpp)
# 打印连接后的字符串
message(STATUS "Joined string: ${JOINED_STRING}")
**解释**:
- `set(MY_LIST "apple" "banana" "cherry")`:定义了一个包含三个元素的字符串列表。
- `$<JOIN:${MY_LIST},,>`:将 `MY_LIST` 列表中的元素用逗号连接起来,生成一个新的字符串。
- `message(STATUS ...)`:输出连接后的字符串。
2. `$<LOWER_CASE>` 和 `$<UPPER_CASE>` 表达式:大小写转换
这两个表达式分别用于将字符串转换为小写和大写。
cmake_minimum_required(VERSION 3.12)
project(StringCaseConversionExample)
# 定义一个字符串
set(MY_STRING "HelloWorld")
# 使用 $<LOWER_CASE> 表达式将字符串转换为小写
set(LOWER_STRING "$<LOWER_CASE:${MY_STRING}>")
# 使用 $<UPPER_CASE> 表达式将字符串转换为大写
set(UPPER_STRING "$<UPPER_CASE:${MY_STRING}>")
# 添加可执行文件
add_executable(myapp main.cpp)
# 打印转换后的字符串
message(STATUS "Lower case string: ${LOWER_STRING}")
message(STATUS "Upper case string: ${UPPER_STRING}")
**解释**:
- `set(MY_STRING "HelloWorld")`:定义了一个字符串。
- `$<LOWER_CASE:${MY_STRING}>`:将 `MY_STRING` 转换为小写形式。
- `$<UPPER_CASE:${MY_STRING}>`:将 `MY_STRING` 转换为大写形式。
- `message(STATUS ...)`:分别输出转换后的小写和大写字符串。
3. `$<IF>` 表达式:条件字符串选择
`$<IF>` 表达式根据布尔条件选择不同的字符串。
cmake_minimum_required(VERSION 3.12)
project(StringIfExample)
# 定义一个布尔变量
set(IS_DEBUG TRUE)
# 使用 $<IF> 表达式根据布尔条件选择字符串
set(SELECTED_STRING "$<IF:${IS_DEBUG},DebugMode,ReleaseMode>")
# 添加可执行文件
add_executable(myapp main.cpp)
# 打印选择的字符串
message(STATUS "Selected string: ${SELECTED_STRING}")
**解释**:
- `set(IS_DEBUG TRUE)`:定义了一个布尔变量。
- `$<IF:${IS_DEBUG},DebugMode,ReleaseMode>`:如果 `IS_DEBUG` 为 `TRUE`,则表达式的值为 `DebugMode`;否则为 `ReleaseMode`。
- `message(STATUS ...)`:输出选择的字符串。
### 4. `$<COMPARE>` 表达式:字符串比较
`$<COMPARE>` 表达式用于比较两个字符串或数字的大小关系。
cmake_minimum_required(VERSION 3.12)
project(StringCompareExample)
# 定义两个数字
set(NUM1 10)
set(NUM2 20)
# 使用 $<COMPARE> 表达式比较两个数字
set(COMPARE_RESULT "$<COMPARE:LESS,${NUM1},${NUM2}>")
# 添加可执行文件
add_executable(myapp main.cpp)
# 打印比较结果
message(STATUS "Compare result: ${COMPARE_RESULT}")
**解释**:
- `set(NUM1 10)` 和 `set(NUM2 20)`:定义了两个数字。
- `$<COMPARE:LESS,${NUM1},${NUM2}>`:比较 `NUM1` 是否小于 `NUM2`,如果是则表达式的值为 `1`,否则为 `0`。
- `message(STATUS ...)`:输出比较结果。
通过这些示例,你可以看到字符串生成器表达式在 CMake 中能够灵活地处理字符串,根据不同条件生成所需的字符串。
模块
引用功能模块
include(cmake_file)
不含扩展名.cmake文件名
常用的预制功能模块
CMake 提供了许多预制功能模块,这些模块可以帮助开发者简化项目配置和构建过程,以下是一些常用的预制功能模块及其使用示例。
1. `FindPackageHandleStandardArgs`
该模块用于标准化查找第三方库的包处理逻辑,能帮助开发者统一处理查找结果。**示例**:在查找 OpenCV 库时使用该模块。
cmake_minimum_required(VERSION 3.10)
project(OpenCVExample)
# 包含 FindPackageHandleStandardArgs 模块
include(FindPackageHandleStandardArgs)
# 查找 OpenCV 库
find_path(OpenCV_INCLUDE_DIR opencv2/opencv.hpp)
find_library(OpenCV_LIBRARY opencv_core)
# 使用 FindPackageHandleStandardArgs 处理查找结果
find_package_handle_standard_args(OpenCV
REQUIRED_VARS OpenCV_INCLUDE_DIR OpenCV_LIBRARY
)
if(OpenCV_FOUND)
message(STATUS "Found OpenCV: ${OpenCV_INCLUDE_DIR}, ${OpenCV_LIBRARY}")
include_directories(${OpenCV_INCLUDE_DIR})
add_executable(myapp main.cpp)
target_link_libraries(myapp ${OpenCV_LIBRARY})
else()
message(FATAL_ERROR "OpenCV not found")
endif()
2. `CheckCXXSourceCompiles`
用于检查一段 C++ 源代码是否能成功编译,可用于检测编译器是否支持某些特性。**示例**:检查编译器是否支持 C++11 的 `std::thread`。
cmake_minimum_required(VERSION 3.10)
project(CheckCXXSourceCompilesExample)
# 包含 CheckCXXSourceCompiles 模块
include(CheckCXXSourceCompiles)
# 定义要检查的源代码
set(SOURCE_CODE "
#include <thread>
int main() {
std::thread t([](){});
t.join();
return 0;
}
")
# 检查源代码是否能编译
check_cxx_source_compiles("${SOURCE_CODE}" CXX11_THREAD_SUPPORTED)
if(CXX11_THREAD_SUPPORTED)
message(STATUS "Compiler supports std::thread")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
add_executable(myapp main.cpp)
else()
message(FATAL_ERROR "Compiler does not support std::thread")
endif()
3. `ExternalProject`
该模块允许在 CMake 项目中下载、编译和安装外部项目。**示例**:下载并编译 Google Test 框架。
cmake_minimum_required(VERSION 3.10)
project(ExternalProjectExample)
# 包含 ExternalProject 模块
include(ExternalProject)
# 定义 Google Test 项目
ExternalProject_Add(googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.11.0
PREFIX ${CMAKE_CURRENT_BINARY_DIR}/googletest
INSTALL_COMMAND ""
)
# 获取 Google Test 的源目录和二进制目录
ExternalProject_Get_Property(googletest source_dir binary_dir)
# 添加可执行文件
add_executable(myapp main.cpp)
# 依赖 Google Test 项目
add_dependencies(myapp googletest)
# 包含 Google Test 的头文件
include_directories(${source_dir}/googletest/include)
# 链接 Google Test 库
target_link_libraries(myapp ${binary_dir}/libgtest.a ${binary_dir}/libgtest_main.a)
4. `GenerateExportHeader`
该模块用于为共享库生成导出头文件,方便在不同平台上正确导出和导入符号。**示例**:为一个共享库生成导出头文件。
cmake_minimum_required(VERSION 3.10)
project(GenerateExportHeaderExample)
# 包含 GenerateExportHeader 模块
include(GenerateExportHeader)
# 添加共享库
add_library(mylib SHARED mylib.cpp)
# 生成导出头文件
generate_export_header(mylib)
# 包含导出头文件所在目录
include_directories(${CMAKE_CURRENT_BINARY_DIR})
# 添加可执行文件并链接共享库
add_executable(myapp main.cpp)
target_link_libraries(myapp mylib)
5. `InstallRequiredSystemLibraries`
该模块用于安装项目运行所需的系统库,主要用于打包和分发项目。**示例**:
cmake_minimum_required(VERSION 3.10)
project(InstallRequiredSystemLibrariesExample)
# 包含 InstallRequiredSystemLibraries 模块
include(InstallRequiredSystemLibraries)
# 添加可执行文件
add_executable(myapp main.cpp)
# 安装可执行文件
install(TARGETS myapp DESTINATION bin)
# 安装所需的系统库
set(CPACK_PACKAGE_NAME "MyApp")
set(CPACK_PACKAGE_VERSION "1.0")
include(CPack)
include(CPack)
选中行解释:这行代码引入了 CMake 的 CPack 模块。CPack 是 CMake 提供的一个用于打包项目的工具,它可以将项目打包成多种格式,如 RPM、DEB、ZIP 等,方便在不同的操作系统和环境中进行分发和安装。
这些预制功能模块可以显著提高 CMake 项目的开发效率和可维护性,根据项目的具体需求合理使用它们可以让项目配置更加简洁和灵活。
查找模块,编写自定义查找模块
在 CMake 中,查找模块是非常实用的工具,它们能帮助我们定位和使用第三方库或工具。CMake 自带了许多查找模块,同时也允许用户自定义查找模块。下面为你介绍几种不同类型的查找模块使用示例。
1. 使用 CMake 自带的查找模块(以查找 OpenSSL 为例)
CMake 自带了许多用于查找常见第三方库的模块,例如查找 OpenSSL 库。
cmake_minimum_required(VERSION 3.10)
project(OpenSSLExample)
# 查找 OpenSSL 库
find_package(OpenSSL REQUIRED)
if(OpenSSL_FOUND)
message(STATUS "Found OpenSSL: ${OpenSSL_INCLUDE_DIR} ${OpenSSL_LIBRARIES}")
# 添加可执行文件
add_executable(myapp main.cpp)
# 包含 OpenSSL 头文件目录
target_include_directories(myapp PRIVATE ${OpenSSL_INCLUDE_DIR})
# 链接 OpenSSL 库
target_link_libraries(myapp PRIVATE ${OpenSSL_LIBRARIES})
else()
message(FATAL_ERROR "OpenSSL not found")
endif()
**解释**:
- `find_package(OpenSSL REQUIRED)`:调用 CMake 自带的 `FindOpenSSL.cmake` 查找模块来查找 OpenSSL 库。`REQUIRED` 表示如果找不到 OpenSSL 库,CMake 会报错并终止配置过程。
- `OpenSSL_FOUND`:是查找模块设置的一个布尔变量,用于表示是否找到了 OpenSSL 库。
- `OpenSSL_INCLUDE_DIR`:包含 OpenSSL 头文件的目录。
- `OpenSSL_LIBRARIES`:OpenSSL 库文件的路径。
2. 自定义查找模块(以查找自定义库为例)
有时候,我们可能需要查找一些非标准的第三方库,这时可以自定义查找模块。
#### 项目结构
project/
├── CMakeLists.txt
├── cmake/
│ └── FindMyLibrary.cmake
├── src/
│ └── main.cpp
└── lib/
└── mylibrary/
├── include/
│ └── mylibrary.h
└── lib/
└── libmylibrary.a
#### `CMakeLists.txt` 文件内容
cmake_minimum_required(VERSION 3.10)
project(MyLibraryExample)
# 添加自定义查找模块的搜索路径
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
# 查找自定义库
find_package(MyLibrary REQUIRED)
if(MyLibrary_FOUND)
message(STATUS "Found MyLibrary: ${MyLibrary_INCLUDE_DIR} ${MyLibrary_LIBRARIES}")
# 添加可执行文件
add_executable(myapp src/main.cpp)
# 包含自定义库头文件目录
target_include_directories(myapp PRIVATE ${MyLibrary_INCLUDE_DIR})
# 链接自定义库
target_link_libraries(myapp PRIVATE ${MyLibrary_LIBRARIES})
else()
message(FATAL_ERROR "MyLibrary not found")
endif()
```#### `cmake/FindMyLibrary.cmake` 文件内容
# 查找头文件
find_path(MyLibrary_INCLUDE_DIR
NAMES mylibrary.h
PATHS ${CMAKE_SOURCE_DIR}/lib/mylibrary/include
)
# 查找库文件
find_library(MyLibrary_LIBRARIES
NAMES mylibrary
PATHS ${CMAKE_SOURCE_DIR}/lib/mylibrary/lib
)
# 处理查找结果
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(MyLibrary
REQUIRED_VARS MyLibrary_INCLUDE_DIR MyLibrary_LIBRARIES
)
**解释**:
- `set(CMAKE_MODULE_PATH ...)`:将自定义查找模块所在的目录添加到 CMake 的查找模块搜索路径中。
- `find_package(MyLibrary REQUIRED)`:调用自定义的 `FindMyLibrary.cmake` 查找模块来查找自定义库。
- `FindMyLibrary.cmake`:自定义查找模块,负责查找自定义库的头文件和库文件,并使用 `FindPackageHandleStandardArgs` 模块来统一处理查找结果。
3. 查找系统工具(以查找 Python 为例)
除了查找库,CMake 还可以查找系统工具,例如 Python。
cmake_minimum_required(VERSION 3.10)
project(PythonExample)
# 查找 Python
find_package(Python3 COMPONENTS Interpreter Development REQUIRED)
if(Python3_FOUND)
message(STATUS "Found Python: ${Python3_EXECUTABLE}")
# 添加可执行文件
add_executable(myapp main.cpp)
# 包含 Python 头文件目录
target_include_directories(myapp PRIVATE ${Python3_INCLUDE_DIRS})
# 链接 Python 库
target_link_libraries(myapp PRIVATE ${Python3_LIBRARIES})
else()
message(FATAL_ERROR "Python not found")
endif()
**解释**:
- `find_package(Python3 COMPONENTS Interpreter Development REQUIRED)`:查找 Python 3 的解释器和开发组件。`COMPONENTS` 用于指定要查找的组件。
- `Python3_EXECUTABLE`:Python 解释器的可执行文件路径。
- `Python3_INCLUDE_DIRS`:Python 头文件的目录。
- `Python3_LIBRARIES`:Python 库文件的路径。
通过这些示例,你可以了解如何使用 CMake 自带的查找模块、自定义查找模块以及查找系统工具。
策略与向后兼容
cmake策略(CMP0115)
project/
├── CMakeLists.txt
├── src/
│ ├── file1.cpp
│ ├── file2.cpp
│ └── subdir/
│ └── file3.cpp
cmake_minimum_required(VERSION 3.24)
project(CMP0115PolicyCheckExample)
# 检查 CMP0115 策略是否存在
if (POLICY CMP0115)
message(STATUS "CMP0115 policy exists.")
# 获取当前 CMP0115 策略的设置
cmake_policy(GET CMP0115 current_policy_setting)
message(STATUS "Current CMP0115 policy setting: ${current_policy_setting}")
# 设置 CMP0115 策略为 NEW
cmake_policy(SET CMP0115 NEW)
message(STATUS "Set CMP0115 policy to NEW.")
# 使用 NEW 策略查找文件
file(GLOB new_style_files "src/*.cpp")
message(STATUS "Files found with NEW style: ${new_style_files}")
# 设置 CMP0115 策略为 OLD
cmake_policy(SET CMP0115 OLD)
message(STATUS "Set CMP0115 policy to OLD.")
# 使用 OLD 策略查找文件
file(GLOB old_style_files "src/*.cpp")
message(STATUS "Files found with OLD style: ${old_style_files}")
else()
message(WARNING "CMP0115 policy does not exist in this CMake version.")
endif()
# 添加可执行文件
add_executable(myapp ${old_style_files})
代码解释
- 策略检查:
if (POLICY CMP0115)
:检查当前 CMake 版本是否支持CMP0115
策略。如果支持,进入if
块执行后续操作;否则,执行else
块中的警告信息输出。
- 获取当前策略设置:
cmake_policy(GET CMP0115 current_policy_setting)
:获取当前CMP0115
策略的设置,并将其存储在current_policy_setting
变量中。
- 切换策略并查找文件:
- 分别将
CMP0115
策略设置为NEW
和OLD
,并使用file(GLOB)
命令查找文件,通过message
输出查找结果。
- 分别将
-- CMP0115 policy exists.
-- Current CMP0115 policy setting: NEW
-- Set CMP0115 policy to NEW.
-- Files found with NEW style:
-- Set CMP0115 policy to OLD.
-- Files found with OLD style: /your/project/path/src/file1.cpp;/your/project/path/src/file2.cpp
指定cmake最低版本要求
# 指定 CMake 最低版本要求为 3.10
cmake_minimum_required(VERSION 3.10)
# 项目名称
project(MyProject)
# 添加可执行文件
add_executable(myapp main.cpp)
管理策略行为
示例 1:设置单个策略
cmake_minimum_required(VERSION 3.10)
project(PolicySingleSetExample)
# 设置 CMP0042 策略为 NEW
cmake_policy(SET CMP0042 NEW)
# 检查策略是否已设置为 NEW
if(CMAKE_POLICY_CMP0042 STREQUAL "NEW")
message(STATUS "CMP0042 policy is set to NEW.")
else()
message(STATUS "CMP0042 policy is not set to NEW.")
endif()
# 其他 CMake 配置代码
add_executable(myapp main.cpp)
示例 2:保存和恢复策略设置
cmake_minimum_required(VERSION 3.10)
project(PolicyPushPopExample)
# 设置初始策略
cmake_policy(SET CMP0054 NEW)
message(STATUS "Initial CMP0054 policy: ${CMAKE_POLICY_CMP0054}")
# 保存当前策略设置
cmake_policy(PUSH)
# 临时更改策略
cmake_policy(SET CMP0054 OLD)
message(STATUS "Temporary CMP0054 policy: ${CMAKE_POLICY_CMP0054}")
# 恢复之前的策略设置
cmake_policy(POP)
message(STATUS "Restored CMP0054 policy: ${CMAKE_POLICY_CMP0054}")
# 其他 CMake 配置代码
add_executable(myapp main.cpp)
cmake零碎知识
cmake表示变量${},$(),$<>分别在什么情况下使用?
${}普通变量
SET(PROJECT_NAME "MyProject")
MESSAGE("THE PROJECT NAME IS: ${PROJECT_NAME}")
SET(SOURCE_FILES main.cpp utils.cpp)
ADD_EXECUTABLE(${PROJECT_NAME} ${SOURCE_FILES})
$()引用外部环境宏,例如visual studio的环境变量或项目属性宏
SET(OUTPUT_DIR "$(SolutionDir)bin")
SET_TARGET_PROPERTIES(MyApp PROPERTIES RUNTIME_OUTPUT_DIRECTORY
${OUTPUT_DIR})$<>生成器表达式
用途:生成器表达式是 CMake 中一种强大的特性,用于在生成构建系统文件时根据不同的构建配置或条件动态生成值。它允许你在构建时根据具体情况(如构建类型、目标平台等)来决定使用哪些编译选项、链接库等。
# 根据不同的构建配置设置不同的编译选项
set(CXX_FLAGS "$<$<CONFIG:Debug>:-g -O0>")
set(CXX_FLAGS "${CXX_FLAGS}$<$<CONFIG:Release>:-O3>")
# 设置目标的编译选项
set_target_properties(MyApp PROPERTIES COMPILE_FLAGS ${CXX_FLAGS})
# 根据目标类型设置不同的链接库
set(LINK_LIBS "$<$<TARGET_PROPERTY:TYPE,EXECUTABLE>:mylib1>")
set(LINK_LIBS "${LINK_LIBS}$<$<TARGET_PROPERTY:TYPE,SHARED_LIBRARY>:mylib2>")
target_link_libraries(MyApp ${LINK_LIBS})
场景总结:当你需要根据不同的构建配置(如 Debug、Release)、目标属性(如可执行文件、共享库)等动态地决定某些构建参数时,就需要使用生成器表达式 $<>。
综上所述,${} 用于引用 CMake 自身定义的变量,$() 用于引用外部环境宏,$<> 用于在构建时根据不同条件动态生成值。
使用cmake管理的qt6工程
cmake文件理解:
CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(QtWidgetsApplication2 LANGUAGES CXX)
include(qt.cmake)
#包含qt.cmake文件
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
#编译器使用c++17标准,必须支持c++17
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core)
#查找系统安装的qt库,先qt6,找不到考虑qt5,必须包含Core组件
find_package(Qt${QT_VERSION_MAJOR}
COMPONENTS
Core
Gui
Widgets
)
#找到qt库后,会自动为${QT_VERSION_MAJOR}变量赋值,接着继续查找此版本qt的Core/Gui/Widgets库。
qt_standard_project_setup()
#这是qt.cmake文件里的函数
set(PROJECT_SOURCES
main.cpp
QtWidgetsApplication2.ui
QtWidgetsApplication2.h
QtWidgetsApplication2.cpp
)
#将这几个文件加入到PROJECT_SOURCES(用户自定义变量,list)里
qt_add_executable(${PROJECT_NAME} ${PROJECT_SOURCES})
#这是qt.cmake文件里的函数,传了两个参数
set_target_properties(${PROJECT_NAME}
PROPERTIES
WIN32_EXECUTABLE TRUE
)
#设置项目属性win32可执行程序,其中WIN32_EXECUTABLE词是cmake固定词
target_link_libraries(${PROJECT_NAME}
PUBLIC
Qt::Core
Qt::Gui
Qt::Widgets
)
#链接库到项目,这三个库是前面找到的库
qt.cmake
if(QT_VERSION VERSION_LESS 6.3)
macro(qt_standard_project_setup)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
endmacro()
endif()
#定义一个宏qt_standard_project_setup,相当于函数
if(QT_VERSION VERSION_LESS 6.0)
macro(qt_add_executable name)
if(ANDROID)
add_library(name SHARED ${ARGN})
else()
add_executable(${ARGV})
endif()
endmacro()
endif()
#定义一个宏qt_add_executable,其中name是第一个参数,${ARGN}是第二个以及后面所有的参数,
#${ARGV}是所有参数包括第一个参数
编译运行:
I.使用visual studio2022打开CMakeLists.txt
II.命令行中指定CMAKE_PREFIX_PATH路径
mkdir build
cd build
cmake -DCMAKE_PREFIX_PATH="D:\Qt\6.8.0\msvc2022_64" ..
cmake --build . --config=Release
./Release/QtWidgetsApplication2.exe
//使前面的find_package();可以找到Qt6Config.cmake
//设置环境变量path添加D:\Qt\6.8.0\msvc2022_64\bin,编译完成运行时可以找到qt*.dll
III.追加qt6的CMAKE_PREFIX_PATH到环境变量
CMAKE_PREFIX_PATH = D:\Qt\6.8.0\msvc2022_64
C:\Users\vczxh>echo %CMAKE_PREFIX_PATH%
D:\Qt\6.8.0\msvc2022_64
mkdir build
cd build
cmake ..
cmake --build . -config Release
./Release/QtWidgetsApplication2.exe
注:在windows中可以运行一部分linux命令?是在git bash中运行的。echo %PATH%在cmd中运行的。
IV.在CMakeLists.txt文件里写死添加qt6的CMAKE_PREFIX_PATH,并在find_package();之前,在同一部署开发环境可以这样使用。其他步骤都一样。
LIST(APPEND CMAKE_PREFIX_PATH [["D:\Qt\6.8.0\msvc2022_64"]])
#注:[[ xxx ]]相当于c++11 R"(xxx)"
qt6使用qtmqtt
源码安装的qt6.8.0不自带qtmqtt库,是自带qtmqtt源码的,不需要git另外下载。
源码编译
cd d:/Qt/6.8.0/Src/qtmqtt
mkdir build
cmake ..
cmake --build . --config=Debug
cmake --build . --config=RelWithDebug
//没有Release版本
拷贝dll
编译生成的dll拷贝到D:\Qt\6.8.0\msvc2022_64\bin
拷贝include
编译生成的include/QtMqtt文件夹 拷贝到D:\Qt\6.8.0\msvc2022_64\include\
拷贝过去的一部分.h文件内容是绝对路径需要重新从src文件夹再拷贝一次:
D:\Qt\6.8.0\Src\qtmqtt\src\mqtt\ 文件夹的这几个文件
qmqttauthenticationproperties.h
qmqttclient.h
qmqttconnectionproperties.h
qmqttglobal.h
qmqttmessage.h
qmqttpublishproperties.h
qmqttsubscription.h
qmqttsubscriptionproperties.h
qmqtttopicfilter.h
qmqtttopicname.h
qmqtttype.h
//11个
覆盖拷贝到D:\Qt\6.8.0\msvc2022_64\include\
拷贝.lib .prl
编译生成的.lib .prl文件拷贝到D:\Qt\6.8.0\msvc2022_64\lib\
拷贝cmake文件夹
拷贝D:\Qt\6.8.0\Src\qtmqtt\build\lib\cmake\<两个文件夹>
到D:\Qt\6.8.0\msvc2022_64\lib\cmake\
拷贝mkspecs\modules\文件
拷贝D:\Qt\6.8.0\Src\qtmqtt\build\mkspecs\modules\<两个文件>
到D:\Qt\6.8.0\msvc2022_64\mkspecs\modules\
拷贝modules\Mqtt.json
拷贝D:\Qt\6.8.0\Src\qtmqtt\build\modules\Mqtt.json 到 D:\Qt\6.8.0\msvc2022_64\modules\
以上步骤也可以参考图形化教程:Qt6.7.0 安装官方MQTT库,Qt安装其他第三方库通用教程_qt6.7安装教程-优快云博客
测试demo
新建qt控制台程序,cmake工程
修改CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(QtConsoleApplication1 LANGUAGES CXX)
include(qt.cmake)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core)
find_package(Qt${QT_VERSION_MAJOR}
COMPONENTS
Core
Mqtt
)
#查找刚才编译的qtmqtt库
qt_standard_project_setup()
set(PROJECT_SOURCES
main.cpp
)
qt_add_executable(${PROJECT_NAME} ${PROJECT_SOURCES})
target_link_libraries(${PROJECT_NAME}
PUBLIC
Qt::Core
Qt::Mqtt
)
#链接到qtmqtt库,格式和qt自带的库一样
main.cpp
#include <QCoreApplication>
#include <QtMqtt/QMqttClient>
#include <QTimer>
#include <QDebug>
int main(int argc, char* argv[]) {
QCoreApplication a(argc, argv);
// 创建MQTT客户端
QMqttClient m_client;
m_client.setHostname("broker.emqx.io"); // 使用公共MQTT代理
m_client.setPort(1883);
// 连接信号槽
QObject::connect(&m_client, &QMqttClient::stateChanged, [&m_client]() {
qDebug() << "MQTT Client state changed:" << m_client.state();
if (m_client.state() == QMqttClient::Connected) {
qDebug() << "Connected to MQTT broker!";
// 订阅主题
m_client.subscribe(QMqttTopicFilter("test/topic"));
// 发布消息
m_client.publish(QMqttTopicName("test/topic"), "Hello, MQTT!");
}
});
QObject::connect(&m_client, &QMqttClient::messageReceived, [](const QByteArray& message, const QMqttTopicName& topic) {
qDebug() << "Received message from topic" << topic.name() << ":" << message;
});
// 连接到MQTT代理
m_client.connectToHost();
// 设置定时器,5秒后退出程序
QTimer::singleShot(5000, &a, &QCoreApplication::quit);
return a.exec();
}
编译运行demo
mkdir build
cmake ..
cmake --build . --config=Debug
./Debug/QtConsoleApplication1.exe
输出
vczxh@DESKTOP MINGW64 ~/Desktop/QtConsoleApplication1/build
$ ./Debug/QtConsoleApplication1.exe
MQTT Client state changed: QMqttClient::Connecting
MQTT Client state changed: QMqttClient::Connected
Connected to MQTT broker!
qt6使用ffmpeg库
到 https://github.com/BtbN/FFmpeg-Builds/releases 下载 ffmpeg-master-latest-win64-gpl-shared.zip
e.g. qwidget中播放本地mp4文件
新建qwidget程序。
CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(QtWidgetsApplication4 LANGUAGES CXX)
include(qt.cmake)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core)
find_package(Qt${QT_VERSION_MAJOR}
COMPONENTS
Core
Gui
Widgets
)
qt_standard_project_setup()
set(PROJECT_SOURCES
main.cpp
QtWidgetsApplication4.ui
QtWidgetsApplication4.h
QtWidgetsApplication4.cpp
)
qt_add_executable(${PROJECT_NAME} ${PROJECT_SOURCES})
set_target_properties(${PROJECT_NAME}
PROPERTIES
WIN32_EXECUTABLE TRUE
)
target_include_directories(${PROJECT_NAME} PRIVATE "d:/ffmpeg-master-latest-win64-gpl-shared/include") # 添加头文件路径
target_link_directories(${PROJECT_NAME} PRIVATE "d:/ffmpeg-master-latest-win64-gpl-shared/lib") # 添加库文件路径
target_link_libraries(${PROJECT_NAME}
PUBLIC
Qt::Core
Qt::Gui
Qt::Widgets
)
# 链接所需库(根据项目需求选择)
target_link_libraries(${PROJECT_NAME} PRIVATE
avcodec avformat avutil avdevice avfilter swscale swresample
)
main.cpp
#include "QtWidgetsApplication4.h"
#include <QtWidgets/QApplication>
#include "iostream"
#include <QApplication>
int main(int argc, char* argv[]) {
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
QtWidgetsApplication4.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QLabel>
#include <QVBoxLayout>
#include <QTimer>
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
}
#include "ui_QtWidgetsApplication4.h"
QT_BEGIN_NAMESPACE
QT_END_NAMESPACE
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow(QWidget* parent = nullptr);
~MainWindow();
private slots:
void playNextFrame();
private:
QLabel* m_videoLabel;
QTimer* timer;
AVFormatContext* formatContext = nullptr;
AVCodecContext* codecContext = nullptr;
const AVCodec* codec = nullptr;
int videoStreamIndex = -1;
SwsContext* swsContext = nullptr;
AVFrame* frame = nullptr;
AVFrame* frameRGB = nullptr;
uint8_t* buffer = nullptr;
void initFFmpeg(const QString& filePath);
void releaseFFmpeg();
Ui::QtWidgetsApplication4Class ui;
};
#endif // MAINWINDOW_H
QtWidgetsApplication4.cpp
#include "QtWidgetsApplication4.h"
MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent){
//ui.setupUi(this);
m_videoLabel = new QLabel(this);
QVBoxLayout* layout = new QVBoxLayout(this);
layout->addWidget(m_videoLabel);
this->setLayout(layout);
this->setFixedSize(800, 500);
m_videoLabel->setFixedSize(800, 500);
qDebug() << m_videoLabel->size();
QString filePath = "d:/1.mp4"; // 替换为实际的MP4文件路径
initFFmpeg(filePath);
timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &MainWindow::playNextFrame);
timer->start(40); // 约25帧每秒
}
MainWindow::~MainWindow() {
timer->stop();
releaseFFmpeg();
}
void MainWindow::initFFmpeg(const QString& filePath) {
avformat_network_init();
//av_register_all();
if (avformat_open_input(&formatContext, filePath.toStdString().c_str(), nullptr, nullptr) != 0) {
return;
}
if (avformat_find_stream_info(formatContext, nullptr) < 0) {
return;
}
for (unsigned int i = 0; i < formatContext->nb_streams; i++) {
if (formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoStreamIndex = i;
break;
}
}
if (videoStreamIndex == -1) {
return;
}
AVCodecParameters* codecParameters = formatContext->streams[videoStreamIndex]->codecpar;
codec = avcodec_find_decoder(codecParameters->codec_id);
if (codec == nullptr) {
return;
}
codecContext = avcodec_alloc_context3(codec);
if (avcodec_parameters_to_context(codecContext, codecParameters) < 0) {
return;
}
if (avcodec_open2(codecContext, codec, nullptr) < 0) {
return;
}
frame = av_frame_alloc();
frameRGB = av_frame_alloc();
int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, codecContext->width, codecContext->height, 1);
buffer = (uint8_t*)av_malloc(numBytes * sizeof(uint8_t));
av_image_fill_arrays(frameRGB->data, frameRGB->linesize, buffer, AV_PIX_FMT_RGB24,
codecContext->width, codecContext->height, 1);
swsContext = sws_getContext(codecContext->width, codecContext->height, codecContext->pix_fmt,
codecContext->width, codecContext->height, AV_PIX_FMT_RGB24,
SWS_BILINEAR, nullptr, nullptr, nullptr);
}
void MainWindow::releaseFFmpeg() {
sws_freeContext(swsContext);
av_free(buffer);
av_frame_free(&frameRGB);
av_frame_free(&frame);
avcodec_close(codecContext);
avformat_close_input(&formatContext);
}
void MainWindow::playNextFrame() {
AVPacket packet;
if (av_read_frame(formatContext, &packet) >= 0) {
if (packet.stream_index == videoStreamIndex) {
if (avcodec_send_packet(codecContext, &packet) < 0) {
av_packet_unref(&packet);
return;
}
while (avcodec_receive_frame(codecContext, frame) == 0) {
sws_scale(swsContext, (const uint8_t* const*)frame->data, frame->linesize, 0,
codecContext->height, frameRGB->data, frameRGB->linesize);
QImage image(frameRGB->data[0], codecContext->width, codecContext->height, QImage::Format_RGB888);
QPixmap pixmap = QPixmap::fromImage(image);
qDebug() << m_videoLabel->size();
m_videoLabel->setPixmap(pixmap.scaled(m_videoLabel->size(), Qt::KeepAspectRatio));
}
}
av_packet_unref(&packet);
} else {
timer->stop();
}
}
cmake命令显示详细运行步骤
生成更详细的MakeFile
cmake .. -DCMAKE_VERBOSE_MAKEFILE=ON
--trace跟踪执行过程
cmake --trace..
--trace-expand 进一步展开宏的调用
cmake --trace-expand ..
使用message
message(STATUS "INFO ...")
-Wdev输出更多警告
cmake -Wdev..