Cmake教程
Posted Dontla
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Cmake教程相关的知识,希望对你有一定的参考价值。
被引用:【B站视频教程笔记】基于VSCode和CMake实现C/C++开发 | Linux篇(gcc/g++)(安装、配置、使用详细教程)(VSCode教程)(CMake教程)(精!)
另外,这是github上的cmake cookbook的主页:CMake Cookbook
文章目录
- CMake的使用
- 语法特性
- CMake重要指令
- cmake_minimum_required(指定CMake最小版本)
- project(定义工程名称)
- set(定义变量)
- include_directories(头文件搜索路径)
- target_include_directories(为特定项添加头文件搜索路径)(可能带PRIVATE, PUBLIC, INTERFACE等参数)
- link_directories(库搜索路径)
- add_library(生成库文件)
- add_compile_options(添加编译参数)
- add_executable(生成可执行文件)
- link_libraries(因为依赖关系没有 target_link_libraries 那么清晰,官方建议使用新的 target_link_libraries)
- target_link_libraries(为target添加需要链接的库文件)
- add_subdirectory(添加存放源文件子目录)(子目录中必须有CMakeLists.txt文件)
- aux_source_directory(临时构建源文件列表)(跟add_executable搭配使用?)
- find_package 找库,引入依赖库,从外部项目查找和加载设置,通过`if(库名_FOUND)`能判断是否找到
- add_definitions(代码外部控制,外部控制代码的开启和关闭,不对源码造成伤害?)
- CMake常用变量
- CMake编译工程
- 【CMake代码实践】(士兵与枪)
- CMake调试
- 总结
- 附录:cmake 常用变量和常用环境变量查表手册---整理 .
CMake的使用
用CMake,比你写makefile方便的多
比如增加了一个bar.cpp文件,我们只需要修改CMakeLists.txt文件就可以了
语法特性
CMake重要指令
cmake_minimum_required(指定CMake最小版本)
project(定义工程名称)
set(定义变量)
include_directories(头文件搜索路径)
target_include_directories(为特定项添加头文件搜索路径)(可能带PRIVATE, PUBLIC, INTERFACE等参数)
参见:CMake 中的 include_directories 和 target_include_directories 有什么区别?
link_directories(库搜索路径)
add_library(生成库文件)
add_compile_options(添加编译参数)
add_executable(生成可执行文件)
link_libraries(因为依赖关系没有 target_link_libraries 那么清晰,官方建议使用新的 target_link_libraries)
link_libraries 与 target_link_libraries 的区别:
target_link_libraries 要在 add_executable 之后
link_libraries 要在 add_executable 之前
参考文章:cmake 的link_libraries和target_link_libraries
target_link_libraries(为target添加需要链接的库文件)
写法示例:
比如(以下写法(包括备注中的)都可以):
TARGET_LINK_LIBRARIES(myProject hello),连接libhello.so库
TARGET_LINK_LIBRARIES(myProject libhello.a)
TARGET_LINK_LIBRARIES(myProject libhello.so)
再如:
TARGET_LINK_LIBRARIES(myProject libeng.so) #这些库名写法都可以。
TARGET_LINK_LIBRARIES(myProject eng)
TARGET_LINK_LIBRARIES(myProject -leng)
参考文章:target_link_libraries 和link_libraries区别
add_subdirectory(添加存放源文件子目录)(子目录中必须有CMakeLists.txt文件)
aux_source_directory(临时构建源文件列表)(跟add_executable搭配使用?)
查找当前目录下的所有源文件并保存在SRC变量中(不会递归子文件夹)
find_package 找库,引入依赖库,从外部项目查找和加载设置,通过if(库名_FOUND)
能判断是否找到
Module模式与Config模式
通过上文我们了解了通过Cmake引入依赖库的基本用法。知其然也要知其所以然,find_package对我们来说是一个黑盒子,那么它是具体通过什么方式来查找到我们依赖的库文件的路径的呢。
到这里我们就不得不聊到find_package的两种模式,一种是Module模式,也就是我们引入curl库的方式。另一种叫做Config模式,也就是引入glog库的模式。下面我们来详细介绍着两种方式的运行机制。
在Module模式中,cmake需要找到一个叫做Find<LibraryName>.cmake的文件。这个文件负责找到库所在的路径,为我们的项目引入头文件路径和库文件路径。
cmake搜索这个文件的路径有两个,一个是上文提到的cmake安装目录下的share/cmake-<version>/Modules目录,另一个使我们指定的CMAKE_MODULE_PATH的所在目录。
如果Module模式搜索失败,没有找到对应的Find<LibraryName>.cmake文件,则转入Config模式进行搜索。它主要通过<LibraryName>Config.cmake or <lower-case-package-name>-config.cmake这两个文件来引入我们需要的库。
以我们刚刚安装的glog库为例,在我们安装之后,它在/usr/local/lib/cmake/glog/目录下生成了glog-config.cmake文件,而/usr/local/lib/cmake/<LibraryName>/正是find_package函数的搜索路径之一。
(find_package的搜索路径是一系列的集合,而且在linux,windows,mac上都会有所区别,需要的可以参考官方文档find_package)
由以上的例子可以看到,对于原生支持Cmake编译和安装的库通常会安装Config模式的配置文件到对应目录,这个配置文件直接配置了头文件库文件的路径以及各种cmake变量供find_package使用。
而对于非由cmake编译的项目,我们通常会编写一个Find<LibraryName>.cmake,通过脚本来获取头文件、库文件等信息。通常,原生支持cmake的项目库安装时会拷贝一份XXXConfig.cmake到系统目录中,因此在没有显式指定搜索路径时也可以顺利找到。
示例:
find_package(GLOG)
add_executable(glogtest glogtest.cc)
if(GLOG_FOUND)
# 由于glog在连接时将头文件直接链接到了库里面,所以这里不用显示调用target_include_directories
target_link_libraries(glogtest glog::glog)
else(GLOG_FOUND)
message(FATAL_ERROR ”GLOG library not found”)
endif(GLOG_FOUND)
add_definitions(代码外部控制,外部控制代码的开启和关闭,不对源码造成伤害?)
参见:CMakeLists中的add_definitions()函数
CMake常用变量
CMAKE_C_FLAGS gcc编译选项
CMAKE_CXX_FLAGS g++编译选项
(只会在原先的编译状态下追加或修改)
CMAKE_BUILD_TYPE 编译类型(Debug,Release)
CMAKE_BINARY_DIR、PROJECT_BINARY_DIR、<projectname>_BINARY_DIR
CMAKE_SOURCE_DIR、PROJECT_SOURCE_DIR、<projectname>_SOURCE_DIR
呀,老总不让看这个,让先看rtsp了,那就先看rtsp吧
基于VSCode和CMake实现C/C++开发 | Linux篇
20210923,继续!
CMAKE_C_COMPILER(指定C编译器)
CMAKE_CXX_COMPILER(指定C++编译器)
EXECUTABLE_OUTPUT_PATH(可执行文件输出的存放路径)
LIBRARY_OUTPUT_PATH(库文件输出的存放路径)
CMake编译工程
CMake目录结构:项目主目录存在一个CMakeLists.txt文件
两种方式设置编译规则:
- 包含源文件的子文件夹包含CMakeLists.txt文件,主目录的CMakeLists.txt通过add_subdirectory
添加子目录即可; - 包含源文件的子文件夹未包含CMakeLists.txt文件,子目录编译规则体现在主目录的
CMakeLists.txt中;
编译流程
(CMake可以帮助我们生成makefile)
在 linux 平台下使用 CMake 构建C/C++工程的流程如下:
- 手动编写 CMakeLists.txt。
- 执行命令 cmake PATH 生成 Makefile ( PATH 是顶层CMakeLists.txt 所在的目录 )。
- 执行命令 make 进行编译。
# important tips
. # 表示当前目录
./ # 表示当前目录
.. # 表示上级目录
../ # 表示上级目录
两种构建方式
内部构建(in-source build):不推荐使用
内部构建会在同级目录下产生一大堆中间文件,这些中间文件并不是我们最终所需要的,和工程源
文件放在一起会显得杂乱无章。
## 内部构建
# 在当前目录下,编译本目录的CMakeLists.txt,生成Makefile和其他文件
cmake .
# 执行make命令,生成target
make
外部构建(out-of-source build):推荐使用
将编译输出文件与源文件放到不同目录中
## 外部构建
# 1. 在当前目录下,创建build文件夹
mkdir build
# 2. 进入到build文件夹
cd build
# 3. 编译上级目录的CMakeLists.txt,生成Makefile和其他文件
cmake ..
# 4. 执行make命令,生成target
make
【实战】外部构建示例
yg@ubuntu:~/arnold_test/20210922_swap_test$ tree
.
├── build
├── CMakeLists.txt
├── include
│ └── swap.h
├── main.cpp
└── src
└── swap.cpp
3 directories, 4 files
main.cpp
#include <iostream>
#include "swap.h"
using namespace std;
int main()
{
int a = 1;
int b = 2;
cout << "交换前:a = " << a << ", " << "b = " << b << endl;
swap(a, b);
cout << "交换后:a = " << a << ", " << "b = " << b << endl;
cout << "success!" << endl;
return 0;
}
swap.cpp
#include "swap.h"
void swap(int &a, int &b)
{
int temp;
temp = a;
a = b;
b =temp;
}
swap.h
#pragma once
extern void swap(int &a, int &b);
CMakeLists.txt
cmake_minimum_required(VERSION 3.5.1)
project(SWAP)
include_directories(include)
# 不能写在add_executable后面,宏定义要写前面
aux_source_directory(src SRC)
add_executable(main_cmake main.cpp ${SRC})
运行cmake指令生成makefile文件,并执行make指令:
yg@ubuntu:~/arnold_test/20210922_swap_test$ cd build
yg@ubuntu:~/arnold_test/20210922_swap_test/build$ cmake ..
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/yg/arnold_test/20210922_swap_test/build
yg@ubuntu:~/arnold_test/20210922_swap_test/build$ make
Scanning dependencies of target main_cmake
[ 33%] Building CXX object CMakeFiles/main_cmake.dir/main.cpp.o
[ 66%] Building CXX object CMakeFiles/main_cmake.dir/src/swap.cpp.o
[100%] Linking CXX executable main_cmake
[100%] Built target main_cmake
yg@ubuntu:~/arnold_test/20210922_swap_test/build$ ls
CMakeCache.txt CMakeFiles cmake_install.cmake main_cmake Makefile
yg@ubuntu:~/arnold_test/20210922_swap_test/build$ ./main_cmake
交换前:a = 1, b = 2
交换后:a = 2, b = 1
success!
yg@ubuntu:~/arnold_test/20210922_swap_test/build$
【CMake代码实践】(士兵与枪)
一个士兵对象,一个枪对象,士兵对象里有枪属性,并且有装填子弹和发射子弹方法,枪对象里也有装填子弹和发射子弹方法
yg@ubuntu:~/arnold_test/20210923_vscode_soldier_gun$ tree
.
├── build
├── CMakeLists.txt
├── include
│ ├── Gun.h
│ └── Soldier.h
├── main.cpp
└── src
├── Gun.cpp
└── Soldier.cpp
3 directories, 6 files
main.cpp
#include "Gun.h"
#include "Soldier.h"
using namespace std;
void test()
{
Soldier sanduo("xusanduo");
sanduo.addGun(new Gun("AK47"));
sanduo.addBulletToGun(20);
sanduo.fire();
}
int main()
{
test();
return 0;
}
Soldier.cpp
#include "Soldier.h"
using namespace std;
Soldier::Soldier(string name)
{
this->_name = name;
this->_ptr_gun = nullptr;
}
void Soldier::addGun(Gun *ptr_gun){
this->_ptr_gun = ptr_gun;
}
void Soldier::addBulletToGun(int num)
{
this->_ptr_gun->addBullet(num);
}
bool Soldier::fire()
{
return(this->_ptr_gun->shoot());
}
Soldier::~Soldier()
{
if (this->_ptr_gun==nullptr)
{
return;
}
delete this->_ptr_gun;
this->_ptr_gun=nullptr;
}
Gun.cpp
#include "Gun.h"
#include "iostream"
using namespace std;
void Gun::addBullet(int bullet_num)
{
this->_bullet_count += bullet_num;
}
bool Gun::shoot()
{
if(this->_bullet_count<=0)
{
cout << "没子弹了!" << endl;
return false;
}
this->_bullet_count -= 1;
cout << "成功发射一枚子弹!" << endl;
return true;
}
Soldier.h
#pragma once
#include <string>
#include "Gun.h"
using namespace std;
class Soldier
{
private:
string _name;
Gun *_ptr_gun;
public:
Soldier(string name);
~Soldier();
void addGun(Gun *ptr_gun);
void addBulletToGun(int num);
bool fire();
};
Gun.h
#pragma once
#include <string>
using namespace std;
class Gun
{
public:
Gun(string type){
this->_bullet_count = 0;
this->_type = type;
}
void addBullet(int bullet_num);
bool shoot();
private:
int _bullet_count;
string _type;
};
CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(SOLDIERFIRE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O2 -Wall -std=c++11")
include_directories(${CMAKE_SOURCE_DIR}/include)
aux_source_directory(src SRC)
add_executable(my_cmake_exe main.cpp ${SRC})
编译运行结果:
yg@ubuntu:~/arnold_test/20210923_vscode_soldier_gun/build$ cmake ..
-- Configuring done
-- Generating done
-- Build files have been written to: /home/yg/arnold_test/20210923_vscode_soldier_gun/build
yg@ubuntu:~/arnold_test/20210923_vscode_soldier_gun/build$ makeScanning dependencies of target my_cmake_exe
[ 25%] Building CXX object CMakeFiles/my_cmake_exe.dir/main.cpp.o
[ 50%] Linking CXX executable my_cmake_exe
[100%] Built target my_cmake_exe
yg@ubuntu:~/arnold_test/20210923_vscode_soldier_gun/build$ ./my_cmake_exe
成功发射一枚子弹!
CMake调试
配置launch.json
点击调试按钮,点击launch.json文件
点击C++(GDB/LLDB),如果有C++-运行和调试活动文件
,就继续点(我这里没有)
然后弹出launch.json文件
然后修改这个地方为我们上次生成的可执行文件
运行调试
然后我们打开main.cpp文件,在16行那打个断点,然后按F5调试运行
然后我们可以按F11单步调试,这跟Visual Studio调试的快捷键是一样的
但是发现调试遇到点故障,,不同的地方乱进
我们得修改下CMakeLists.txt文件并重新编译
删除编译的 -g(生成带调试信息的可执行文件)和-O2(编译代码优化)选项,因为这可能会影响我们的调试文件,同时设置CMAKE_BUILD_TYPE为Debug
然后再次运行调试,可以正常调试了,但感觉这调试功能远不如Visual Studio方便啊!
配置launch.json中的preLaunchTask,同时配置task.json(做自动化调试)
在launch.json中,添加这两行代码(虽然不知道干啥用的!)
然后点击上方终端-配置默认生成任务
然后点击使用模板创建tasks.json文件
随便点一个others
然后把里面都删掉
在里面填入以下信息:
{
"version": "2.0.0",
"options": {
"cwd": "${workspaceFolder}/build" // "cwd"表示当前工作目录:current working directory;workspaceFolder表示工作空间文件夹
},
"tasks": [
{
"type": "shell",
"label": "cmake", // 该任务的标签是:"cmake"
"command": "cmake", // cmake任务生成makefile文件
"args": [ // cmake命令后所跟的参数,".."表示在父目录
".."
]
},
{
"label": "make", // 该任务的标签是:"label",根据makefile文件进行编译
"group": {
"kind": "build", // 当前任务所属的组是build组
"isDefault": true
},
"command": "make", // 在Windows下实际执行的命令是:mingw32-make
"args": [ // mingw32-make命令后面所跟的参数
]
},
{
"label": "Build", //这个名为"Build"的task是launch.json执行前所预先执行的任务
"dependsOrder": "sequence",//按列出的顺序执行任务依赖项
"dependsOn":[ // 并且这个任务又依赖"cmake"和"make"这两个任务
"cmake",
"make"
]
}
]
}
然后在launch.json的"preLaunchTask"处填入Build
然后就可以做自动化调试了
自动化调试测试
我们在main.cpp的17行加入一行打印信息,然后直接按F5调试程序,发现程序自动执行了cmake …和make指令,并开启调试,在终端输出了我们添加的打印信息
视频课程完结!
总结
因为我做的项目比较大,有点担心VScode的性能不够好,有点想转向QT creator或者C-Lion,涉及到交叉编译器的设置还不是很懂,准备先熟悉一下vscode的code runner插件
附录:cmake 常用变量和常用环境变量查表手册—整理 .
以上是关于Cmake教程的主要内容,如果未能解决你的问题,请参考以下文章
B站视频教程笔记基于VSCode和CMake实现C/C++开发 | Linux篇(gcc/g++)(安装配置使用详细教程)(VSCode教程)(CMake教程)(精!)