前言:
1.CMake是一个跨平台的安装编译工具,可以用简单的语句来描述所有平台的安装(编译过程)。
2.CMake可以说已经成为大部分C++开源项目标配
1.1 跨平台开发
假设您有一些跨平台项目,其中c++代码在不同的平台上共享。假设你在Windows上使用Visual Studio,在MAC上使用Xcode,在Linux上使用 Makefile:
如果你想添加新的' bar.cpp '源文件,你会怎么做你必须把它添加到你使用的每一个工具中:
为了保持环境的一致性,您必须手动多次执行类似的更新,(在本例中,图表上用红色标记的箭头)。显然这种方法容易出错,而且不灵活。
CMake通过在开发过程中增加额外的步骤来解决这一设计缺陷。你可以在CMakeLists.txt
文件中描述你的项目,并使用CMake生成你目前感兴趣的跨平台CMake代码:
相同的操作-添加新的 bar.cpp
文件,将在一步完成:
注意,图表的底部没有改变,即 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++项目:
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可执行文件
$ /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不会自动生成下图所指的目录。因此需要手动创建一个生成目录,防止零时文件扰乱工程。
编写 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 同学可直接点击编辑上的运行即可:
好了,本篇至此结束,以上内容由22年所写,如Jetbrains 已放弃对Clion的支持,请切换到最新编辑或者VS上使用~~