e脉通网站,做普通网站公司吗,国外的有趣设计网站,做钢丝绳外贸的网站声明此博客为转载
CMake基础 文章目录 CMake基础一、准备知识1.1 C的编译过程1.2 静态链接库和动态链接库1.3 为什么需要CMake1.3.1 g 命令行编译1.3.2 CMake简介 二、CMake基础知识2.1 安装2.2 第一个CMake例子2.3 语法基础2.3.1 指定版本2.3.2 设置项目2.3.3 添加可执行文件… 声明此博客为转载
CMake基础 文章目录 CMake基础一、准备知识1.1 C的编译过程1.2 静态链接库和动态链接库1.3 为什么需要CMake1.3.1 g 命令行编译1.3.2 CMake简介 二、CMake基础知识2.1 安装2.2 第一个CMake例子2.3 语法基础2.3.1 指定版本2.3.2 设置项目2.3.3 添加可执行文件目标2.3.4 生成静态库并链接2.3.5 生成动态库并连接2.3.6 CMake 中的 PUBLIC、PRIVATE、INTERFACE2.3.7 变量2.3.8 include引入其他代码2.3.9 条件控制2.3.10 CMake分步编译2.3.11 生成器表达式2.3.12 函数和宏2.3.13 设置安装2.3.14 寻找依赖 find_package 三、opencv CMake示例 一、准备知识
1.1 C的编译过程
使用g等编译工具从源码生成最终的可执行文件一般有这几步预处理Preprocess、编译Compile、汇编assemble、链接link。 输入g --help可以看到对应命令 -E Preprocess only; do not compile, assemble or link.
-S Compile only; do not assemble or link.
-c Compile and assemble, but do not link.
-o file Place the output into file.以下面程序为例
#include iostreamint main() {std::cout Hello World! std::endl;return 0;
}第一步预处理 C中预处理指令以 # 开头。在预处理阶段会对#define进行宏展开处理#if#else等条件编译指令递归处理#include。这一步需要我们添加所有头文件的引用路径。 # 将xx.cpp源文件预处理成xx.i文件文本文件
g -E main.cpp -o main.i第二步编译 检查代码的规范性和语法错误等检查完毕后把代码翻译成汇编语言文件。 # 将xx.i文件编译为xx.s的汇编文件文本文件
g -S main.i -o main.s第三步汇编 基于汇编语言文件生成二进制格式的目标文件。 # 将xx.s文件汇编成xx.o的二进制目标文件
g -c main.s -o main.o第四步链接 将目标代码与所依赖的库文件进行关联或者组装合成一个可执行文件 # 将xx.o二进制文件进行链接最终生成可执行程序
g main.o -o main1.2 静态链接库和动态链接库
所谓静态和动态其区别是链接的阶段不一样。 静态链接库名称一般是lib库名称.a.a代表archive library其链接发生在编译环节。一个工程如果依赖一个静态链接库其输出的库或可执行文件会将静态链接库*.a打包到该工程的输出文件中可执行文件或库因此生成的文件比较大但在运行时也就不再需要库文件了。 而动态链接库的链接发生在程序的执行过程中其在编译环节仅执行链接检查而不进行真正的链接这样可以节省系统的开销。动态库一般后缀名为*.so.so代表shared objectLinuxlib库名称.so macOSlib库名称.dylib。动态链接库加载后在内存中仅保存一份拷贝多个程序依赖它时不会重复加载和拷贝这样也节省了内存的空间。 以下图为例 工程A和B依赖静态链接库 static libraryA和B在运行时内存中会有多份static library 工程A和B依赖动态链接库 shared libraryA和B在运行时内存中只有一份 shared libraryshared共享。
以上只是非常简单的一个解释以区分动态链接库和静态链接库。更多底层的知识需要单独进行深入讲解。
1.3 为什么需要CMake
1.3.1 g 命令行编译
当我们编译附件中1.hello_world时我们可以运行
g main.cpp -o main当我们需要引入外部库时如附件中的2.external_libs需要引入gflagsGoogle开源的命令行参数处理库我们则需要运行
# 安装gflags
sudo apt-get install libgflags-dev libgflags2.2 // -lgflags表示链接gflags库-o main表示输出文件名为main
g main.cpp -lgflags -o main # 或者# 安装pkg-config
sudo apt-get install pkg-config// pkg-config是一个工具用于查找和管理安装在系统上的库文件--cflags --libs gflags表示查找gflags库的头文件和库文件的路径-o main表示输出文件名为maing main.cpp pkg-config --cflags --libs gflags -o main # 测试输出
./main --age 31 --name alice有些时候有一些常用库我们也不用手动添加头文件或链接库路径通常g能在默认查询路径中找到他们。当我们的项目文件变得多起来引入的外部库也多起来时命令行编译这种方式就会变得十分臃肿也不方便调试和编辑。通常在测试单个文件时会使用命令行进行编译但不推荐在一个实际项目中使用命令行编译。
1.3.2 CMake简介
在实际工作中推荐使用CMake构建C项目CMake是用于构建、测试和软件打包的开源跨平台工具
特性
自动搜索可能需要的程序、库和头文件的能力独立的构建目录如build可以安全清理支持复杂的自定义命令下载、生成各种文件自定义配置可选组件从简单的文本文件CMakeLists.txt自动生成工作区和项目的能力在主流平台上自动生成文件依赖项并支持并行构建几乎支持所有的IDE
二、CMake基础知识
2.1 安装
ubuntu上请执行
sudo apt install cmake -y或者编译安装
# 以v3.25.1版本为例
git clone -b v3.25.1 https://github.com/Kitware/CMake.git
cd CMake
# 你使用--prefix来指定安装路径或者去掉--prefix,安装在默认路径。
./bootstrap --prefix安装路径 make sudo make install# 验证
cmake --version2.2 第一个CMake例子
附件位置3.first_cmake
# 第一步配置-S 指定源码目录-B 指定构建目录
cmake -S . -B build
# 第二步生成--build 指定构建目录
cmake --build build
# 运行
./build/first_cmakevs code插件
安装twxs.cmake做代码提示安装ms-vscode.cmake-tools界面操作。
2.3 语法基础
2.3.1 指定版本
以附件3.first_cmake/CMakeLists.txt为例
# CMake 最低版本号要求
cmake_minimum_required(VERSION 3.10)# first_cmake是项目名称VERSION是版本号DESCRIPTION是项目描述LANGUAGES是项目语言
project(first_cmake VERSION 1.0.0 DESCRIPTION 项目描述LANGUAGES CXX) # 添加一个可执行程序first_cmake是可执行程序名称main.cpp是源文件
add_executable(first_cmake main.cpp)命令cmake_minimum_required来指定当前工程所使用的CMake版本不区分大小写的通常用小写。VERSION是这个函数的一个特殊关键字版本的值在关键字之后。CMake中的命令大多和cmake_minimum_required相似不区分大小写并有很多关键字来引导命令的参数输入类似函数传参。
2.3.2 设置项目
以附件3.first_cmake/CMakeLists.txt为例
project(ProjectName VERSION 1.0.0 DESCRIPTION 项目描述LANGUAGES CXX) 在CMakeLists.txt的开头都会使用project来指定本项目的名称、版本、介绍、与使用的语言。在project中第一个ProjectName例子中用的是first_cmake不需要参数其他关键字都有参数。
2.3.3 添加可执行文件目标
以附件3.first_cmake/CMakeLists.txt为例
add_executable(first_cmake main.cpp)这里我们用到add_executable其中第一个参数是最终生成的可执行文件名以及在CMake中定义的Target名。我们可以在CMake中继续使用Target的名字为Target的编译设置新的属性和行为。命令中第一个参数后面的参数都是编译目标所使用到的源文件。
2.3.4 生成静态库并链接
附件位置4.static_lib_test
A.生成静态库
#account_dir/CMakeLists.txt# 最低版本要求
cmake_minimum_required(VERSION 3.10)# 项目信息
project(Account)# 添加静态库Linux下会生成libAccount.a
add_library(Account STATIC Account.cpp Account.h)# 编译静态库后会在build下生成 build/libAccount.a 静态库文件
account_dir/
├── Account.cpp
├── Account.h
├── build
│ └── libAccount.a
└── CMakeLists.txt这里我们用到add_library, 和add_executable一样Account为最终生成的库文件名lib库名称.a第二个参数是用于指定链接库为动态链接库SHARED还是静态链接库STATIC后面的参数是需要用到的源文件。
B.链接
# test_account/CMakeLists.txt# 最低版本要求
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)# 编译后目录如下
4.static_lib_test/
├── account_dir
│ ├── Account.cpp
│ ├── Account.h
│ ├── build
│ │ └── libAccount.a
│ └── CMakeLists.txt
└── test_account ├── build│ └── test_account├── CMakeLists.txt└── test_account.cpp我们通过add_library和add_executable定义了Target我们可以通过Target的名称为其添加属性例如
# 指定目标包含的头文件目录
target_include_directories(test_account PUBLIC ../account_dir)
# 添加库文件目录如果不添加找不到库文件
target_link_directories(test_account PUBLIC ../account_dir/build)
# 指定目标链接的库
target_link_libraries(test_account PRIVATE Account)通过target_include_directories我们给test_account添加了头文件引用路径../account_dir。上面的关键词PUBLIC,PRIVATE用于说明目标属性的作用范围更多介绍参考下节。通过target_link_libraries将前面生成的静态库libAccount.a链接给对象test_account但此时还没指定库文件的目录CMake无法定位库文件再通过target_link_directories添加库文件的目录即可。
2.3.5 生成动态库并连接
附件位置5.dynamic_lib_test
A.生成动态库
#account_dir/CMakeLists.txt# 添加动态库Linux下会生成libAccount.so
add_library(Account SHARED Account.cpp Account.h)# 编译动态库后会在build下生成 build/libAccount.so 动态库文件
account_dir/
├── Account.cpp
├── Account.h
├── build
│ └── libAccount.so
└── CMakeLists.txtB.链接
操作不变。
# ldd查看依赖的动态库
libAccount.so /home/enpei/Documents/course_cpp_tensorrt/course_5/src/5.dynamic_lib_test/test_account/../account_dir/build/libAccount.so (0x00007fb692cf1000)当然也可以用一个CMakeLists.txt来一次性编译参考附件6.build_together
#6.build_together/CMakeLists.txt# 最低版本要求
cmake_minimum_required(VERSION 3.10)# 项目信息
project(test_account)# 添加动态库
add_library(Account SHARED ./account_dir/Account.cpp ./account_dir/Account.h)# 添加可执行文件
add_executable(test_account ./test_account/test_account.cpp)# 添加头文件
target_include_directories(test_account PUBLIC ./account_dir)
# 添加链接库
target_link_libraries(test_account Account)2.3.6 CMake 中的 PUBLIC、PRIVATE、INTERFACE
CMake中经常使用target_...()类似的命令一般这样的命令支持通过PUBLIC、PRIVATE、INTERFACE关键字来控制传播。
以target_link_libraries(A B)为例从理解的角度来看
PRIVATE 依赖项B仅链接到目标 A如果有C 链接了AC不会链接BINTERFACE 依赖项B并不链接到目标A如果有C 链接了AC会链接BPUBLIC 依赖项B链接到目标 A如果有C 链接了AC也会链接B
其实就是对象属性的传递打个散烟的比方
PRIVATE 就是自己抽不给别人抽INTERFACE 就是自己不抽给别人抽PUBLIC 就是自己抽也给别人抽
从使用的角度来说如果有C链接了目标A
如果B仅用于A的实现且不在头文件中提供给C使用使用PRIVATE如果B不用于A的实现仅在头文件中作为借口给C使用使用INTERFACE如果B既用于A的实现也在头文件中提供给C使用使用PUBLIC
举例
# 创建库
add_library(C c.cpp)
add_library(D d.cpp)
add_library(B b.cpp)# C是B的PUBLIC依赖项
target_link_libraries(B PUBLIC C)
# D是B的PRIVATE依赖项
target_link_libraries(B PRIVATE D)# 添加可执行文件
add_executable(A a.cpp)# 将B链接到A
target_link_libraries(A B)因为C是B的PUBLIC依赖项所以C会传播到A因为D是B的PRIVATE依赖性所以D不会传播到A
2.3.7 变量
附件位置7.message_var_demo
像其他编程语言一样我们应该将CMake理解为一门编程语言。我们也需要设定变量来储存我们的选项信息。有时候我们通过变量来判断我们在什么平台上通过变量来判断我们需要编译哪些Target也通过变量来决定添加哪些依赖。
2.3.8 include引入其他代码
附件位置8.include_demo
2.3.9 条件控制
附件位置9.if_demo
正如前面所讲应该把CMake当成编程语言除了可以设置变量以外CMake还可以写条件控制。
if(variable)# 为true的常量ON、YES、TRUE、Y、1、非0数字
else()# 为false的常量OFF、NO、FALSE、N、0、空字符串、NOTFOUND
endif()可以和条件一起使用的关键词有
NOT, TARGET, EXISTS (file), DEFINED等
STREQUAL, AND, OR, MATCHES (regular expression), VERSION_LESS, VERSION_LESS_EQUAL等2.3.10 CMake分步编译
附件位置10.steps_demo
# 查看所有目标
$ cmake -S . -B build
$ cd build
$ cmake --build . --target helpThe following are some of the valid targets for this Makefile:
... all (the default if no target is provided)
... clean
... depend
... rebuild_cache
... edit_cache
... steps_demo
... main.o
... main.i
... main.s# 1.预处理
$ cmake --build . --target main.i
# 输出Preprocessing CXX source to CMakeFiles/steps_demo.dir/main.cpp.i
# 可以打开滑到底部# 2.编译
$ cmake --build . --target main.s
# 输出汇编代码Compiling CXX source to assembly CMakeFiles/steps_demo.dir/main.cpp.s# 3.汇编
$ cmake --build . --target main.o
# 输出二进制文件Building CXX object CMakeFiles/steps_demo.dir/main.cpp.o# 链接
$ cmake --build .
Scanning dependencies of target steps_demo
[ 50%] Linking CXX executable steps_demo
[100%] Built target steps_demo# 运行
./steps_demo2.3.11 生成器表达式
附件位置11.generator_expression
生成器表达式简单来说就是在CMake生成构建系统的时候根据不同配置动态生成特定的内容。有时用它可以让代码更加精简我们介绍几种常用的。 需要注意的是生成表达式被展开是在生成构建系统的时候所以不能通过解析配置CMakeLists.txt阶段的message命令打印可以用类似file(GENERATE OUTPUT ./generator_test.txt CONTENT $$BOOL:TRUE:TEST)生成文件的方式间接测试。 在其最一般的形式中生成器表达式是$...尖括号中间可以是如下几种类型
条件表达式变量查询Variable-Query目标查询Target-Query输出相关的表达式
# 1.条件表达式$condition:true_string当condition为真时返回true_string否则返回空字符串
$0:TEST
$1:TEST
$$BOOL:TRUE:TEST# 2.变量查询Variable-Query
$TARGET_EXISTS:target判断目标是否存在
$CONFIG:Debug判断当前构建类型是否为Debug# 3.目标查询Target-Query
$TARGET_FILE:target获取编译目标的文件路径
$TARGET_FILE_NAME:target获取编译目标的文件名4.输出相关表达式用于在不同的环节使用不同参数比如需要在install和build环节分别用不同的参数我们可以这样写
add_library(Foo ...)
target_include_directories(FooPUBLIC$BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}$INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}
)其中$BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}仅在build环节生效;而$INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}仅在install环节生效。通过设定不同阶段不同的参数我们可以避免路径混乱的问题。
2.3.12 函数和宏
附件位置12.function_macro
# 定义一个宏宏名为my_macro没有参数
macro(my_macro)message(宏内部的信息)set(macro_var 宏内部变量test)
endmacro(my_macro)# 定义一个函数函数名为second_func有两个参数
function(second_func arg1 arg2)message(第一个参数${arg1}, 第二个参数${arg2})
endfunction(second_func)2.3.13 设置安装
附件位置13.install_demo
当需要发布项目时你需要指定项目文件的安装路径。下面的代码片段中使用install安装demo_test并分别将可执行文件安装在bin中动态链接库和静态链接库都安装在lib公共头文件安装在include。这里的路径都将添加${CMAKE_INSTALL_PREFIX}作为前缀如果不设置CMAKE_INSTALL_PREFIX则会安装到/usr/local 目录下。实现安装的功能在你需要发布你项目给其他人使用时非常有用。
# 设置安装
install(TARGETS demo_testRUNTIME DESTINATION bin # 可执行文件LIBRARY DESTINATION lib # 动态库ARCHIVE DESTINATION lib # 静态库PUBLIC_HEADER DESTINATION include # 公共头文件
)2.3.14 寻找依赖 find_package
对于大部分支持了CMake的项目来说均可以通过find_package找到对应的依赖库参考附件14.find_demo
# 使用find_package寻找LibaryName库如果找到一般都会有以下变量库作者设置
LibaryName_FOUND表示是否找到
LibaryName_INCLUDE_DIR表示头文件目录
LibaryName_LIBRARIES表示库文件目录假设我们编写了一个新的函数库我们希望别的项目可以通过find_package对它进行引用我们有两种办法
编写一个FindLibraryName.cmake适用于导入非cmake安装的项目参考附件15.custom_find使用install安装生成LibraryNameConfig.cmake文件适用于导入自己开发的cmake项目参考附件16.custom_install_demo
三、opencv CMake示例
附件位置17.demo_opencv/
安装OpenCVsudo apt install libopencv-dev
依赖和链接OpenCV与常规的添加依赖并没有太多不同同时OpenCV提供了cmake find package的功能因此我们可以通过find_package方便的定位opencv在系统中的位置和需要添加的依赖。
find_package(OpenCV REQUIRED)message(OPENCV INCLUDE DIRS: ${OpenCV_INCLUDE_DIRS})
message(OPENCV LINK LIBRARIES: ${OpenCV_LIBS})如果cmake找到了OpenCV配置cmake后命令行会有如下输出
OPENCV INCLUDE DIRS: /usr/include/opencv4
OPENCV LINK LIBRARIES: opencv_calib3d;opencv_core;opencv_dnn;opencv_features2d;opencv_flann;opencv_highgui;opencv_imgcodecs;opencv_imgproc;opencv_ml;opencv_objdetect;opencv_photo;opencv_stitching;opencv_video;opencv_videoio;opencv_aruco;opencv_bgsegm;opencv_bioinspired;opencv_ccalib;opencv_datasets;opencv_dnn_objdetect;opencv_dnn_superres;opencv_dpm;opencv_face;opencv_freetype;opencv_fuzzy;opencv_hdf;opencv_hfs;opencv_img_hash;opencv_line_descriptor;opencv_optflow;opencv_phase_unwrapping;opencv_plot;opencv_quality;opencv_reg;opencv_rgbd;opencv_saliency;opencv_shape;opencv_stereo;opencv_structured_light;opencv_superres;opencv_surface_matching;opencv_text;opencv_tracking;opencv_videostab;opencv_viz;opencv_ximgproc;opencv_xobjdetect;opencv_xphoto