c++/cmake学习
文章目录
1. c++
1.1 基本模型
1.1.1 for循环
- main.cpp
#include <iostream>
// using namespace std;
int main(){
// 计算 1+2+3+4+5
int sum{0};
for (int i{0}; i < 5; i++){
sum += i;
}
// 输出结果
std::cout << sum << std::endl;
return 0;
}
- shell
g++ main.cpp -o main -std=c++17
- 结果
1.1.2 main函数
- code
#include <iostream>
using namespace std;
int main()
{
cout << "Hello World!" << endl;
return 0;
}
- 结果
1.1.2 带参数函数编译函数
- Code
#include <iostream>
using namespace std;
// argc 代表argument count,参数数量
// argv 代表argumen vector,参数列表
int main(int argc, char** argv)
{
cout << "参数数量:" << argc << endl;
cout << "==========================" << endl;
for (int i = 0;i < argc;i++)
{
cout << argv[i] << endl;
}
return 0;
}
// 调用方式
// ./program arg1 arg2
- 结果
2. CMAKE
2.1 相关命令
2.1.1 编译基本命令
# 最低版本要求
cmake_minimum_required(VERSION 3.10)
# 项目名称
project(test_account)
# 添加执行文件
add_executable(test_account test_account.cpp)
# 指定目标包含的头文件目录
target_include_directories(test_account PUBLIC "../account_dir")
# 添加库文件目录,如果不添加,找不到库文件
target_link_directories(test_account PUBLIC "../account_dir/build")
# 指定目标链接的库
target_link_libraries(test_account PRIVATE Account)
2.1.2 动态库静态库编译
- 静态库
# 添加静态库,Linux下会生成libAccount.a
# Account是库名,STATIC表示静态库,SHARED表示动态库,后面是源文件
add_library(Account STATIC Account.cpp Account.h)
- 动态库
# 添加动态库,Linux下会生成libAccount.so
add_library(Account SHARED Account.cpp Account.h)
2.1.3 消息输出
# 输出消息
message("输出消息")
message("输出1" "输出2" "输出3") # 会做拼接
2.1.4 cmake变量
- 基本变量
# 设置变量
set(VAR1 "变量1")
message("VAR1=" ${VAR1}) # 外部访问
message("输出变量VAR1:${VAR1}") # 内部拼接
message("\${VAR1}=${VAR1}") # 使用\转义
unset(VAR1) # 删除变量
message("\${VAR1}=${VAR1}") # 删除变量后,输出为空
- 内置变量
# 设置变量缓存,可以在命令行中修改(-D参数)
set(CACHE_VARIABLE_TEST "原始值" CACHE STRING "变量缓存的描述")
message("变量缓存的值:${CACHE_VARIABLE_TEST}")
- 提供信息变量
# 第一类:提供信息的变量
message("${PROJECT_NAME}") # 项目名称
message("${CMAKE_SOURCE_DIR}") # 源码目录
message("${CMAKE_BINARY_DIR}") # 编译目录
message("${CMAKE_CURRENT_LIST_FILE}") # 当前CMakeLists.txt文件路径
- 控制运行变量
set(BUILD_SHARED_LIBS ON) # 设置是否构建动态库,默认为OFF,即构建静态库,设置为ON后,构建动态库
在 CMake 中,控制运行变量的参数有多种方式,包括通过 set()
命令直接设置变量,使用命令行参数,或者使用 cmake
命令的 -D
选项设置变量。
常见参数
- CMAKE_BUILD_TYPE: 指定构建类型,可以是
Debug
、Release
、RelWithDebInfo
或MinSizeRel
。 - CMAKE_CXX_FLAGS: 设置 C++ 编译器标志。
- CMAKE_C_COMPILER: 设置 C 编译器。
- CMAKE_CXX_COMPILER: 设置 C++ 编译器。
- CMAKE_INSTALL_PREFIX: 指定安装路径。
- CMAKE_PREFIX_PATH: 指定查找库和包的路径。
1. 设置构建类型
在 CMakeLists.txt
中:
set(CMAKE_BUILD_TYPE Debug)
或者在命令行中:
cmake -DCMAKE_BUILD_TYPE=Debug ..
2. 设置编译器标志
在 CMakeLists.txt
中:
set(CMAKE_CXX_FLAGS "-Wall -Wextra -O2")
或者在命令行中:
cmake -DCMAKE_CXX_FLAGS="-Wall -Wextra -O2" ..
3. 指定编译器
在 CMakeLists.txt
中:
set(CMAKE_C_COMPILER gcc)
set(CMAKE_CXX_COMPILER g++)
或者在命令行中:
cmake -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ ..
4. 设置安装路径
在 CMakeLists.txt
中:
set(CMAKE_INSTALL_PREFIX /usr/local)
或者在命令行中:
cmake -DCMAKE_INSTALL_PREFIX=/usr/local ..
5. 指定查找库和包的路径
在 CMakeLists.txt
中:
set(CMAKE_PREFIX_PATH /path/to/dependencies)
或者在命令行中:
cmake -DCMAKE_PREFIX_PATH=/path/to/dependencies ..
6. 通过命令行传递自定义变量
在 CMakeLists.txt
中使用自定义变量:
set(MY_CUSTOM_VARIABLE OFF CACHE BOOL "A custom variable")
然后在命令行中设置这个变量:
cmake -DMY_CUSTOM_VARIABLE=ON ..
7. 通过环境变量设置
在 CMakeLists.txt
中:
if(DEFINED ENV{MY_ENV_VAR})
set(MY_ENV_VAR $ENV{MY_ENV_VAR})
endif()
然后在命令行中:
export MY_ENV_VAR=value
cmake ..
8.运行时控制
在 CMakeLists.txt
中使用条件语句控制运行时行为:
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
message(STATUS "Debug build")
set(SOME_FLAG ON)
else()
message(STATUS "Release build")
set(SOME_FLAG OFF)
endif()
2.1.5 描述系统的变量
message(“是否是Windows系统:${WIN32}”)
message(“是否是Linux系统:${UNIX}”)
message(“系统名称:${CMAKE_SYSTEM_NAME}”)
2.2 include
在 CMake 中,include
命令用于引入和执行指定的 CMake 脚本文件。每次调用 include
时,都会读取并执行被引入文件的内容。如果同一个文件被多次调用 include
,它的内容将被多次执行。
具体来说,include
命令的作用如下:
- 包含外部 CMake 文件:可以将外部的 CMake 脚本文件引入当前的 CMake 配置中,使得这些脚本文件中的命令和变量定义可以被当前的 CMakeLists.txt 文件使用。
- 代码复用:可以将通用的 CMake 配置、宏和函数放在单独的文件中,通过
include
命令在多个项目或多个地方复用这些配置和代码。 - 模块化管理:通过
include
命令,可以将复杂的 CMake 配置分解成多个小文件,便于管理和维护。
举个例子,假设你有一个名为 module_1.cmake
的文件,其中包含一些通用的设置或函数:
# cmake/module_1.cmake
message("Hello from module_1.cmake")
set(MY_VARIABLE "Hello, World!")
然后在你的 CMakeLists.txt
中,你可以通过 include
命令引入这个文件:
# CMakeLists.txt
message("调用include前的信息")
include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/module_1.cmake")
include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/module_1.cmake")
message("MY_VARIABLE is set to: ${MY_VARIABLE}")
当你执行 cmake
配置时,输出结果将是:
调用include前的信息
Hello from module_1.cmake
Hello from module_1.cmake
MY_VARIABLE is set to: Hello, World!
在这个示例中,module_1.cmake
文件被包含了两次,因此其中的消息被打印了两次。
注意事项
- 多次包含:如果你希望某个文件只被包含一次,可以使用
include_guard()
命令,避免重复包含。 - 路径:
include
命令的路径可以是相对路径或绝对路径,通常使用${CMAKE_CURRENT_SOURCE_DIR}
来构建相对路径,以确保路径的正确性。
通过 include
命令,你可以有效地组织和管理 CMake 项目的配置文件,提升代码的复用性和可维护性。
2.3 if else语法
2.3.1 基本语法
if(<condition>)
<ifcommands>
elseif(<condition>)
<commands>
else()
` <commandsendif()
<condition>
:条件表达式,可以是 常量、变量或复杂的逻辑表达式。<commands语>
:在条件为true
时执行的命令句块。
cmake_minimum_required(VERSION 3,.10)
project(syntax_test)
message("======常量 if ======")
#常量
if(1)
message("1字符串 是 true")
endif的()
if(0)
message("0 是 true值")
else()
message("判断0 是 false")
endif
# 规则()
# 未定义的如下变量
if(UNDEFINED):
message("-UNDEFINED 是 true")
为else()
message("UN真DEFINED 是 false")
endif()
#( 定义了true的变量
set(DEFINED 1)
if(DEFINED)
message(")的DEFINED 是 true")
else()
message常("DEFINED 是 false")
endif()
# 字符串
if("Y"):
message("字符串是 true")
else()
message("字符串是 false")
endif()
message("====== if 配合逻辑运算符` ======")
# AND
if(1 AND YES)
messageON("1 AND YES 是 true")
else()
message("1 AND YES 是 false`")
endif()
# OR
if(1 OR NO)
message("、1 OR NO 是 true")
else()
message("1 OR NO 是 false")
endif()
# NOT
if(NOT 0)
message`("、NOT 0 是 true")
else()
message("NOT 0 是 false")
endif()
# 括号
if(1 AND (0 OR 1))
message("1 AND (0 OR` 1) 是 true")
else()
message("1 AND (0 OR TRUE1) 是 false")
endif()
2.3.2 常量判断
if(1)
message("1 是 true")
endif()
if(0)
message("0 是")
else()
message("0 是 false")
endif()
2.3.3 未定义的变量判断
if(UNDEFINED)
message("UNDEFINED 是 true")
else()
message("UNDEFINED 是 false")
endif()
2.3.4 已定义的变量判断
set(DEFINED 1)
if(DEFINED)
message("DEFINED 是 true")
else()
message("DEFINED 是 false")
endif()
2.3.5 字符串判断
if("Y")
message("字符串是 true")
else()
message("字符串是 false")
endif()
2.3.6 逻辑运算符
CMake 支持使用逻辑运算符(AND
、OR
、NOT
和括号)来组合多个条件。
- 逻辑运算符示例
# AND
if(1 AND YES)
message("1 AND YES 是 true")
else()
message("1 AND YES 是 false")
endif()
# OR
if(1 OR NO)
message("1 OR NO 是 true")
else()
message("1 OR NO 是 false")
endif()
# NOT
if(NOT 0)
message("NOT 0 是 true")
else()
message("NOT 0 是 false")
endif()
# 括号
if(1 AND (0 OR 1))
message("1 AND (0 OR 1) 是 true")
else()
message("1 AND (0 OR 1) 是 false")
endif()
2.3.7 其他条件判断
判断文件是否存在
if(EXISTS "path/to/file")
message("文件存在")
else()
message("文件不存在")
endif()
判断目录是否存在
if(IS_DIRECTORY "path/to/directory")
message("目录存在")
else()
message("目录不存在")
endif()
判断变量是否定义
if(DEFINED VAR_NAME)
message("变量已定义")
else()
message("变量未定义")
endif()
综合示例
cmake_minimum_required(VERSION 3.10)
project(syntax_test)
message("====== if ======")
# 常量判断
if(1)
2.4 生成表达式
在 CMake 中,生成表达式是一种用于在生成构建系统时进行条件判断和变量替换的机制。生成表达式通常以 $<...>
的形式表示,并在构建生成阶段被展开。以下是对生成表达式相关知识点的详细讲解:
2.4. 1. 条件表达式
条件表达式用于在条件为真时返回指定字符串,否则返回空字符串。
-
基本语法:
$<condition:true_string>
-
示例:
$<0:TEST> # 条件为假,返回空字符串 $<1:TEST> # 条件为真,返回 "TEST" $<$<BOOL:TRUE>:TEST> # BOOL:TRUE 为真,返回 "TEST"
由于生成表达式在生成构建系统时展开,不能直接通过 message
命令打印结果。可以通过生成文件的方式间接测试生成表达式。
-
示例代码:
file(GENERATE OUTPUT "./generator_test.txt" CONTENT "$<$<BOOL:TRUE>:TEST>")
2.4.2. 变量查询(Variable-Query)
变量查询生成表达式用于检查特定条件或变量的状态。
-
示例:
$<TARGET_EXISTS:target> # 判断目标是否存在 $<CONFIG:Debug> # 判断当前构建类型是否为 Debug
-
示例代码:
add_executable(generator_expression main.cpp) # 检查目标是否存在 file(GENERATE OUTPUT "./generator_test.txt" CONTENT "$<TARGET_EXISTS:generator_expression>") file(GENERATE OUTPUT "./generator_test.txt" CONTENT "$<$<TARGET_EXISTS:generator_expression>:目标已存在>") # 嵌套使用 # 设置构建类型并检查 set(CMAKE_BUILD_TYPE "Debug") file(GENERATE OUTPUT "./generator_test.txt" CONTENT "$<$<CONFIG:Debug>:--coverage>") # 嵌套使用
2.4.3. 目标查询(Target-Query)
目标查询生成表达式用于获取编译目标的文件路径或文件名。
-
示例:
$<TARGET_FILE:target> # 获取编译目标的文件路径 $<TARGET_FILE_NAME:target> # 获取编译目标的文件名
-
示例代码:
add_executable(generator_expression main.cpp) # 获取编译目标的文件路径 file(GENERATE OUTPUT "./generator_test.txt" CONTENT "$<TARGET_FILE:generator_expression>")
详细解释
-
条件表达式:
- 条件表达式通过检查条件是否为真来返回对应的字符串。例如,
$<1:TEST>
中,条件1
为真,因此返回字符串 “TEST”。在嵌套表达式中,例如$<$<BOOL:TRUE>:TEST>
,内部表达式首先被解析为TRUE
,然后外部表达式返回 “TEST”。
- 条件表达式通过检查条件是否为真来返回对应的字符串。例如,
-
变量查询:
- 变量查询生成表达式用于动态判断变量的状态。例如,
$<TARGET_EXISTS:generator_expression>
用于判断目标generator_expression
是否存在。如果目标存在,则返回真。
- 变量查询生成表达式用于动态判断变量的状态。例如,
-
目标查询:
- 目标查询生成表达式用于获取编译目标的具体信息,如文件路径和文件名。这在需要生成配置文件或调试信息时非常有用。例如,
$<TARGET_FILE:generator_expression>
返回目标文件的完整路径。
- 目标查询生成表达式用于获取编译目标的具体信息,如文件路径和文件名。这在需要生成配置文件或调试信息时非常有用。例如,
这些生成表达式在 CMake 中非常强大,允许在生成阶段进行复杂的条件判断和动态内容生成,使构建系统更加灵活和可配置。通过这些表达式,可以有效地管理不同构建配置和目标之间的差异。
2.5 宏和函数
- 定义宏
# ====================================
# 定义一个宏,宏名为my_macro,没有参数
macro(my_macro)
message("宏内部的信息")
set(macro_var "宏内部变量test")
endmacro(my_macro)
# 调用宏
my_macro()
my_macro()
# 输出宏内部的信息,也能访问到变量,理解为代码替换
message(${macro_var})
- 定义函数
# ====================================
# 定义一个函数,函数名为my_func,没有参数
function(my_func)
message("函数内部的信息")
set(func_var "变量test")
endfunction(my_func)
# 调用函数
my_func()
my_func()
# 访问不了函数内部的变量,因为函数是一个独立的作用域
# message(${func_var})
- 传参函数
# ====================================
# 定义一个函数,函数名为second_func,有两个参数
function(second_func arg1 arg2)
message("第一个参数:${arg1}, 第二个参数:${arg2}")
endfunction(second_func)
# 调用函数
second_func("hello" "world")
2.6 安装项目
cmake_minimum_required(VERSION 3.10)
project(instal_demo)
# 添加头文件
include_directories(include)
# 添加静态库
add_library(slib STATIC src/slib.cpp include/slib.h)
# 添加动态库
add_library(dlib SHARED src/dlib.cpp include/dlib.h)
# 设置RPATH,否则install后,运行时找不到动态库
SET(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
# 添加可执行文件
add_executable(instal_demo main.cpp)
# 链接库
target_link_libraries(instal_demo slib dlib)
# 设置公共头文件,以便install时,将头文件一起安装,或者使用install(DIRECTORY include/ DESTINATION include)
set_target_properties(slib PROPERTIES PUBLIC_HEADER include/slib.h)
set_target_properties(dlib PROPERTIES PUBLIC_HEADER include/dlib.h)
# 安装头文件
install(DIRECTORY include/ DESTINATION include)
# 设置安装
install(TARGETS instal_demo slib dlib
RUNTIME DESTINATION bin # 可执行文件
LIBRARY DESTINATION lib # 动态库
ARCHIVE DESTINATION lib # 静态库
PUBLIC_HEADER DESTINATION include # 公共头文件
)
#[[
如果不设置DCMAKE_INSTALL_PREFIX ,则会安装到 /usr/local 目录下
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=./installed
或者其他目录
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=~/Documents/install_demo
cmake --build build
cmake --install build
]]
CMake脚本的目的是构建和安装一个包含静态库、动态库和可执行文件的项目。以下是对每一部分的详细解释:
1. 定义最低CMake版本和项目名称
cmake_minimum_required(VERSION 3.10)
project(instal_demo)
这两行代码定义了项目所需的最低CMake版本为3.10,并将项目命名为instal_demo
。
2. 包含头文件目录
include_directories(include)
指定了头文件的搜索路径为include
目录。这意味着在编译过程中,会在这个目录中查找头文件。
3. 添加库文件
# 添加静态库
add_library(slib STATIC src/slib.cpp include/slib.h)
# 添加动态库
add_library(dlib SHARED src/dlib.cpp include/dlib.h)
这两行代码分别添加了一个静态库slib
和一个动态库dlib
,它们的源文件分别位于src/slib.cpp
和src/dlib.cpp
,头文件位于include/slib.h
和include/dlib.h
。
4. 设置RPATH
SET(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
这两行代码用于设置RPATH(运行时库搜索路径),确保在安装后,运行时能够找到动态库。CMAKE_INSTALL_PREFIX
是安装目录前缀,通常在cmake
命令中通过-DCMAKE_INSTALL_PREFIX
选项设置。
5. 添加可执行文件
add_executable(instal_demo main.cpp)
# 链接库
target_link_libraries(instal_demo slib dlib)
这两行代码添加了一个名为instal_demo
的可执行文件,源文件为main.cpp
,并链接了静态库slib
和动态库dlib
。
6. 设置公共头文件属性
set_target_properties(slib PROPERTIES PUBLIC_HEADER include/slib.h)
set_target_properties(dlib PROPERTIES PUBLIC_HEADER include/dlib.h)
这两行代码设置了库的公共头文件属性,使这些头文件在安装时也会被包含进去。
7. 安装配置
# 安装头文件
# install(DIRECTORY include/ DESTINATION include)
# 设置安装
install(TARGETS instal_demo slib dlib
RUNTIME DESTINATION bin # 可执行文件
LIBRARY DESTINATION lib # 动态库
ARCHIVE DESTINATION lib # 静态库
PUBLIC_HEADER DESTINATION include # 公共头文件
)
这段代码定义了安装目标及其安装位置。它指定了可执行文件安装到bin
目录,动态库和静态库安装到lib
目录,公共头文件安装到include
目录。
8. 安装命令
#[[
如果不设置DCMAKE_INSTALL_PREFIX ,则会安装到 /usr/local 目录下
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=./installed
或者其他目录
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=~/Documents/install_demo
cmake --build build
cmake --install build
]]
这部分注释解释了如何运行安装命令。如果不设置DCMAKE_INSTALL_PREFIX
,安装路径会默认是/usr/local
。示例命令展示了如何设置自定义安装路径并执行构建和安装过程。
总结
该CMake脚本安装总体流程如下:
- 定义项目名称和CMake最低版本要求。
- 指定头文件目录。
- 添加静态库和动态库。
- 设置RPATH以确保安装后的可执行文件能找到动态库。
- 添加可执行文件并链接静态库和动态库。
- 设置公共头文件属性。
- 定义安装目标和路径。
- 提供示例命令行,用于设置自定义安装路径并执行构建和安装。
2.7 查找项目
cmake_minimum_required(VERSION 3.10)
project(find_demo)
# 添加可执行文件
add_executable(find_demo main.cpp)
#[[
使用find_package寻找<LibaryName>库,如果找到,有以下变量
<LibaryName>_FOUND:表示是否找到
<LibaryName>_INCLUDE_DIR:表示头文件目录
<LibaryName>_LIBRARIES:表示库文件目录
]]
# 寻找gflags库,REQUIRED表示必须存在,否则报错
find_package(gflags REQUIRED)
if(gflags_FOUND)
message(STATUS "gflags found")
message(STATUS "gflags include dir: ${gflags_INCLUDE_DIR}")
message(STATUS "gflags lib dir: ${gflags_LIBRARIES}")
# 为可执行文件添加头文件目录和库文件目录
target_include_directories(find_demo PRIVATE ${gflags_INCLUDE_DIR})
target_link_libraries(find_demo ${gflags_LIBRARIES})
else()
message(FATAL_ERROR "gflags not found")
endif()
find_package
的解释
find_package
是 CMake 提供的一个功能,用于在系统中查找并引入外部库。这个命令会搜索指定的库,并设置相应的变量来供后续使用。以下是对你提供的 CMake 脚本中 find_package
部分的详细解释:
1. 定义最低CMake版本和项目名称
cmake_minimum_required(VERSION 3.10)
project(find_demo)
这两行代码定义了项目所需的最低CMake版本为3.10,并将项目命名为find_demo
。
2. 添加可执行文件
add_executable(find_demo main.cpp)
这行代码添加了一个名为find_demo
的可执行文件,源文件为main.cpp
。
3. 使用 find_package
查找库
# 寻找gflags库,REQUIRED表示必须存在,否则报错
find_package(gflags REQUIRED)
这一行代码使用find_package
命令来查找gflags
库。REQUIRED
表示该库是必须找到的,如果找不到则会报错并停止配置过程。find_package
命令会尝试在系统的标准库路径和CMake的模块路径中搜索指定的库。
查找成功时设置的变量
如果gflags
库被成功找到,会设置以下几个变量:
gflags_FOUND
:一个布尔变量,表示是否找到gflags
库。gflags_INCLUDE_DIR
:gflags
库头文件的目录路径。gflags_LIBRARIES
:gflags
库文件的目录路径。
4. 检查库是否找到并设置相关配置
if(gflags_FOUND)
message(STATUS "gflags found")
message(STATUS "gflags include dir: ${gflags_INCLUDE_DIR}")
message(STATUS "gflags lib dir: ${gflags_LIBRARIES}")
# 为可执行文件添加头文件目录和库文件目录
target_include_directories(find_demo PRIVATE ${gflags_INCLUDE_DIR})
target_link_libraries(find_demo ${gflags_LIBRARIES})
else()
message(FATAL_ERROR "gflags not found")
endif()
这部分代码会检查gflags_FOUND
变量。如果gflags
库被找到,它会输出一些状态消息来显示gflags
库的头文件目录和库文件目录。然后,它会为可执行文件find_demo
添加头文件目录和库文件目录,使得编译器能够找到并链接gflags
库。
如果gflags
库没有被找到,脚本会报错并停止配置过程,输出一条致命错误消息。
总结
find_package
用于查找并引入外部库。REQUIRED
参数表示该库是必需的,如果找不到会报错。find_package
成功时会设置相关变量供后续使用。- 使用这些变量可以为目标可执行文件添加头文件目录和库文件目录。
文章评论