Cmake教程

Posted Dontla

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Cmake教程相关的知识,希望对你有一定的参考价值。

被引用:【B站视频教程笔记】基于VSCode和CMake实现C/C++开发 | Linux篇(gcc/g++)(安装、配置、使用详细教程)(VSCode教程)(CMake教程)(精!)

另外,这是github上的cmake cookbook的主页:CMake Cookbook

文章目录

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文件
两种方式设置编译规则:

  1. 包含源文件的子文件夹包含CMakeLists.txt文件,主目录的CMakeLists.txt通过add_subdirectory
    添加子目录即可;
  2. 包含源文件的子文件夹未包含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 常用变量和常用环境变量查表手册—整理 .

以上是关于Cmake教程的主要内容,如果未能解决你的问题,请参考以下文章

cmake基础教程(40)生成器表达式

cmake基础教程(40)生成器表达式

B站视频教程笔记基于VSCode和CMake实现C/C++开发 | Linux篇(gcc/g++)(安装配置使用详细教程)(VSCode教程)(CMake教程)(精!)

VIM 代码片段插件 ultisnips 使用教程

markdown 打字稿...编码说明,提示,作弊,指南,代码片段和教程文章

使用CMake检查列表是否包含特定条目的最佳方法