当前位置: 首页>后端>正文

CMake 一篇入坑

前言:

1.CMake是一个跨平台的安装编译工具,可以用简单的语句来描述所有平台的安装(编译过程)。
2.CMake可以说已经成为大部分C++开源项目标配

1.1 跨平台开发

假设您有一些跨平台项目,其中c++代码在不同的平台上共享。假设你在Windows上使用Visual Studio,在MAC上使用Xcode,在Linux上使用 Makefile:

CMake 一篇入坑,第1张
20221105171917.png

如果你想添加新的' bar.cpp '源文件,你会怎么做你必须把它添加到你使用的每一个工具中:

CMake 一篇入坑,第2张
20221105171950.png

为了保持环境的一致性,您必须手动多次执行类似的更新,(在本例中,图表上用红色标记的箭头)。显然这种方法容易出错,而且不灵活。

CMake通过在开发过程中增加额外的步骤来解决这一设计缺陷。你可以在CMakeLists.txt文件中描述你的项目,并使用CMake生成你目前感兴趣的跨平台CMake代码:

CMake 一篇入坑,第3张
20221105171953.png

相同的操作-添加新的 bar.cpp 文件,将在一步完成:

CMake 一篇入坑,第4张
20221105171958.png

注意,图表的底部没有改变,即 bar.cpp的变动不会重新牵涉到多端的修改,直接交给 CMakeLists.txt中间层处理了。

常用路径:

CMAKE_SOURCE_DIR: 顶级cmakelists.txt的文件夹目录。
CMAKE_BINRAY_DIR: 对应cmake的build的目录,主要是运行时生成的文件目录。
CMAKE_CURRENT_SOURCE_DIR: 一般来说,一个工程会有多个cmakelists.txt文件,对应当前文件目录。
CMAKE_CURRENT_BINARY_DIR: 对应build里的目录。
CMAKE_MODULE_PATH: api(include/find_package)包含别的cmake文件时的搜索目录。
CMAKE_PREFIX_PATH: api(find_libray/path)包含模块时的搜索目录。
CMAKE_INSTALL_PREFIX: 调用install相关函数,要生成/保存的根目录路径。

1.2语法特性介绍

基本语法格式:指令(参数1 参数2….)

1.参数使用括弧括起
2.参数之间使用空格或分号分开

指令是大小写无关的,参数和变量是大小写相关的

set(HELLo hello.cpp)  //设置变量名HELLo  
add_executable(hello main.cpp  hello.cpp)
ADD_EXECUTABLE(hello main.cpp ${HELLo})  //使用变量名${HELLo}

变量使用${}方式取值,但是在IF控制语句中是直接使用变量名

1.3重要指令和CMake常用变量

指令 [ ] 中为可选参数,通常不用填,除 add_library 外,实际中通常需要指定是动态/静态库。

1.3.1重要指令

cmake_minimum_required:指定CMake的最小版本要求
语法: cmake_minimum_required(VERSION versionNumber [FATAL_ERROR])

 # CMake最小版本要求为2.8.3
 cmake_minimum_required(VERSION 2.8.3) 

project:定义工程名称,并可指定工程支持的语言。
语法: project(projectname [CXX][C] [Java])

#指定工程名为HELLOWORLD
project(HELLOWORLD)

set: 显式的定义变量
语法: set(VAR [VALUE][CACHE TYPE DOCSTRING[FORCE]])

 #定义SRC变量,其值为sayhello.cpp hello.cpp
 set(SRC sayhello.cpp hello.cpp)

include_directories:向工程添加多个特定的头文件搜索路径--->相当于指定g++编译器的-l参数。
语法: include_directories([AFTER|BEFORE][SYSTEM] dir1 dir2 ...)

 #将/usr/include/mytest 和 ./include 添加到头文件搜索路径                              
 include_directories(/usr/include/mytest ./include)

link_directories:向工程添加多个特定的库文件搜索路径--->相当于指定g++编译器的-L参数。
语法: link_directories(dir1 dir2 ...)

#将/usr/lib/my1ibfo1der 和 ./1ib 添加到库文件搜索路径2ink_directories(/usr/ 
lib/my1ibfo1der ./lib)

add_library 生成库文件
语法: add_library(libname [SHARED|STATIC|MODULE] [EXCLUDE_FROM_ALL] source1 source2...sourceN)

#通过变量 SRC生成libhello.so共享库
add_library (hello SHARED ${SRC})

add_compile_options-添加编译参数
语法: add_compile_options(<option> ...)

 #添加编译参数-wall -std=c++11   使用c11标准编译 -o 并优化代码
 add_compile_options(-wall -std=c++11 -o2)

add_executable:生成可执行文件
语法: add_executable(exename source1 source2 ... sourceN)

#编译main.cpp生成可执行文件main
add_executab1e(main main.cpp)

target_ link _libraries:为target添加需要链接的共享库--->相同于指定g++编译器-l参姒
语法: target_link_libraries(target library1<debug | optimized> library2...)

# 将he1lo动态库文件链接到可执行文件main
target_link_libraries(main hello)

add_subdirectory:向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置。
语法: add_subdirectory(source_dir [binary_dir][EXCLUDE_FROM_ALL])

#添加src子日录,src中需有一个CMakeLists.txt
add_subdirectory(src)

aux_source_directory:发现一个目录下所有的源代码文件并将列表存储在一个变量中,这个指令临时被用来自动构建源文件列表
语法: aux_source_directory(dir VARIABLE)

 #定义SRC变量,其值为当前目录下所有的源代码文件
 aux_source_directory(. SRC)

 #编译SRC变量所代表的源代码文件,生成main可执行文件
 add_executable(main ${SRC})
1.3.2 CMake常用变量

CMAKE_C_FLAGS gcc编译选项
CMAKE_CXX_FLAGS g++编译选项

 #在CMAKE_CXX_FLAGS编译选项后追加 -std=c++11
 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")

CMAKE_BUILD_TYPE 编译类型(Debug, Release)

 #设定编译类型为debug,调试时需要选择debug
 set(CMAKE_BUILD_TYPE Debug)
 #设定编译类型为release,发布时需要选择release
 set(CMAKE_BUILD_TYPE Release)

CMAKE_BINARY_DIR
PROJECT_BINARY_DIR
<projectname>_BINARY_DIR

1.这三个变量指代的内容是一致的。
2.如果是in source build,指的就是工程顶层目录。
3.如果是out-of-source编译,指的是工程编译发生的目录。
4.PROJECT_BINARY_DIR跟其他指令稍有区别,不过现在,你可以理解为他们是一致的。

CMAKE_SOURCE_DIR
PROJECT_SOURCE_DIR
<projectname>_SOURCE_DIR

1.这三个变量指代的内容是一致的,不论采用何种编译方式,都是工程顶层目录。
2.也就是在in source build时,他跟CMAKE_BINARY_DIR等变量一致。
3.PROJECT_SOURCE_DIR跟其他指令稍有区别,现在,你可以理解为他们是一致的。

CMAKE_C_COMPILER:指定C编译器
CMAKE_CXX_COMPILER:指定C++编译器
EXECUTABLE_OUTPUT_PATH:可执行文件输出的存放路径·
LIBRARY_OUTPUT_PATH:库文件输出的存放路径

1.4 CMake编译工程

CMake目录结构:项目主目录存在一个CMakeLists.txt文件两种方式设置编译规则:
1.包含源文件的子文件夹包含CMakeLists.txt文件,主目录的CMakeLists.txt通过add_subdirectory添加子目录即可。
2.包含源文件的子文件夹未包含CMakeLists.txt文件,子目录编译规则体现在主目录的CMakeLists.txt中。

1.4.1编译流程

在linux平台下使用CMake构建C/C++工程的流程如下:

1.手动编写CmakeLists.txt。
2.执行命令cmake PATH生成Makefile ( PATH是顶层CMakeLists.txt 所在的目录)。
3.执行命令make进行编译。

.      #表示当前目录
./     #表示当前目录
..     #表示上级目录
../    #表示上级目录
1.4.2两种构建方式

内部构建(in-source build):不推荐使用
内部构建会在同级目录下产生一大堆中间文件,这些中间文件并不是我们最终所需要的,和工程源文件放在一起会显得杂乱无章。

##内部构建
#在当前目录下,编译本目录的CMakeLists.txt,生成Makefile和其他文件
cmake .
#执行make命令,生成target
make

外部构建(out-of-source build):推荐使用
将编译输出文件与源文件放到不同目录中

##外部构建
# 1.在当前目录下,创建bui1d文件夹mkdir build
# 2.进入到 build文件夹
cd build

#3.编译上级目录的CMakeLists.txt,生成 Makefile和其他文件
cmake ..
#4.执行make命令,生成 target
make

2.简单实战

士兵突击需求:
1.士兵张三有一把枪,士兵可以开火
3.士兵可以给枪装填子弹
4.枪:能够发射子弹
5.枪:能够装填子弹―—增加子弹数量*

代码设计类枪类和士兵类。我这里由于家用电脑没装vs code,只装了 Clion 编译器,且配置了c/c++环境。
这里演示用Clion开发工具。

新建cmakeTest C++项目:

CMake 一篇入坑,第5张
20221105233637.png

include:目录存放我们的头文件
src: 头文件的实现放在src 下

按照我上面的结构创建,暂时先不管 CMakeLists.txt里内容。

枪类头文件: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;
};

枪类实现文件: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>

class Solider{

public:

    //构造,由外部传参数
    Solider(std::string name);

    void addBulletToGun(int num);

    void addGun(Gun* gun);

    bool fire();

private:
    std::string _name;
    Gun* _ptr_gun;

public:

    //析构
    ~Solider();

};

士兵头文件接口实现 Soldier.cpp

#include "Soldier.h"

Solider::Solider(std::string name) {
    this->_name = name;
    this->_ptr_gun = nullptr;
}

void  Solider::addGun(Gun *gun) {

    this->_ptr_gun = gun;
}

void  Solider::addBulletToGun(int num) {

    this->_ptr_gun->addBullet(num);
}

bool Solider::fire() {

    return this->_ptr_gun->shoot();

}

Solider::~Solider() {

    if(this->_ptr_gun== nullptr){
        return;
    }
    delete this->_ptr_gun;
    this->_ptr_gun== nullptr;
}

入口文件 main.cpp中编写:

#include <iostream>
#include "Gun.h"
#include "Soldier.h"

void test(){
    Solider zhangsan("张三");
    zhangsan.addGun(new Gun("M419"));
    zhangsan.addBulletToGun(-1);

    //开火
    zhangsan.fire();
}

int main() {
    test();
    system("pause");
    return 0;
}

这里我们先不利用编译器工具直接编译,等会在配置 CMakeLists.txt编译,这里我们用原始的 g++ 命令方式编译可执行文件。

cd 到 cmakeTest的根目录;执行g++ main.cpp src/Gun.cpp src/Soldier.cpp -Iinclude -o mygexe 生成 mygexe可执行文件

CMake 一篇入坑,第6张
20221105235237.png
 $ /f/ProjectWork/Cpp/cmakeTest/mygexe.exe
 没子弹了
 Press any key to continue . . .

hangsan.addBulletToGun(-1) 由于程序里做了限制 子弹小于等于0时,提示没子弹了。

修改 hangsan.addBulletToGun(100) 重新在终端执行 g++ 命令 重新生成,然后再执行mygexe.exe

$ /f/ProjectWork/Cpp/cmakeTest/mygexe.exe
发射成功
Press any key to continue . . .

我这里使用的是windows在终端可直接执行,Linux下 执行 ./mygexe.exe

上面是通过命令的方式,下面使用 CMakeLists.txt。使用Cmake的两种方式如 1.4.2节中说的建议是外部构建。如果是使用Clion 编译器的同学可以忽略,VS Code 的同学注意看下,因为VS Code不会自动生成下图所指的目录。因此需要手动创建一个生成目录,防止零时文件扰乱工程。

CMake 一篇入坑,第7张
20221106000540.png

编写 CMakeLists.txt文件:直接

#cmake 需要的版本
cmake_minimum_required(VERSION 3.17)

#工程名称
project(cmakeTest)

#c++ 14编译
set(CMAKE_CXX_STANDARD 14)

#添加头文件路径 方便导包
include_directories(${PROJECT_SOURCE_DIR}/include)

#将士兵和枪编译成可执行文件cmakeTest
add_executable(cmakeTest main.cpp include/Gun.h src/Gun.cpp include/Soldier.h src/Soldier.cpp)

cd 到项目根目录:mkdir build 创建 build

执行:cmake ..
然后:make

ps:vs code 上一定要先装 c++ ,cmake 插件。
编译成功即可看到 生成的 cmakeTest 可执行文件。

如果是Clion 同学可直接点击编辑上的运行即可:

CMake 一篇入坑,第8张
20221106001854.png

好了,本篇至此结束,以上内容由22年所写,如Jetbrains 已放弃对Clion的支持,请切换到最新编辑或者VS上使用~~


https://www.xamrdz.com/backend/3ke1942735.html

相关文章: