在使用ROS2框架开发机器人应用时,对各个功能包Cmakelist.txt文件的更改尤为重要。本系列旨在总头开始介绍Cmakelist.txt各条语句的意义和内涵。
Cmake已经是高度集成的构建工具,其作用是在不同开发环境下生成makefile文件,以此来执行make指令,将一系列c++源码编译为可以运行的文件。所以,我们将先介绍c++的编译过程。
一、C++的编译过程
由c++源文件(即以.hpp和.cpp结尾的源文件)到可执行文件(在linux系统中默认以.out结尾)分为以下4个过程。
1、预处理:将#define定义的内容添加到代码中,处理#ifndef #endif等标记,完成删除注释等操作
2、编译:把C++代码编译为汇编语言代码
3、汇编:把汇编语言代码编译为二进制机器码
4、链接:将编译完成的二进制代码与诸如标准库等系统组件结合,使之能够运行
当我们执行如下指令时,编译器已经自动将这四个步骤执行完毕。
g++ mysource.cpp -o myexe.out
mysource.cpp为源文件,myexe.out为编译出的可执行文件。
二、动态链接库和静态链接库
链接这一步会把我们编写的二进制代码与其他组件结合,其他组件包括标准库,也包括他人完成的库文件。譬如,如果在如下的代码中使用matio库
#include <string>
#include "matio.h"
int main(void)
{
std::string path="/home/test.mat";
std::string name="value";
//待读取文件的地址和变量名称
mat_t* p_mat_info;
p_mat_info=Mat_Open(path.c_str(),MAT_ACC_RDONLY);
matvar_t* p_mat_var;
p_mat_var=Mat_VarRead(p_mat_info,name.c_str());
double* data=(double*)p_mat_var->data;
}
mat_t和matvar_t均为matio库定义的类型,我们自己编写的代码中并不不包含其具体定义和实现。但我们的编写的代码保留了这一份标记,在经历预处理、编译和汇编步骤后,经得到的二进制代码与系统中保存的matio库二进制代码结合,使最终的可执行文件可以利用matio库包含的功能。这便是编译,而其中库的二进制代码,一般被称为库文件。
依据链接的形式不同,库文件可以被分为静态链接库和动态链接库。
1、静态链接库:将所需的二进制代码片段拷贝到源代码编译得到的二进制文件中。
2、动态链接库:将所需的二进制代码的地址拷贝到源代码编译得到的二进制文件中。
两者的关系类似于普通函数和内联函数,按地址传递参数与按值传递参数。可以看出计算机中的概念时密切联系的!
在linux系统中,动态链接库以.so结尾,静态链接库以.a结尾。
在windows系统中,动态链接库以.dll结尾,静态链接库以.lib结尾。
进入linux系统的/lib目录,可以发现非常多的库文件,这些库文件都是系统中各个软件运行所需的必要保障。
三、make和Makefile的使用
对于简单的项目,比如单个c++文件,使用gcc/g++指令就可以实现由项目源文件到可执行文件的转变。但对于有多个cpp文件,多个hpp文件,使用了多个外部链接库的大项目,使用gcc/g++指令进行编译操作会复杂!
这里有笔者对于使用matio库的介绍:
Ubuntu平台上C语言利用matio库读取mat文件-CSDN博客
gcc test.c /usr/lib/libmatio.a -o name
可以看到,链接操作需要提供外部链接库的地址。如果一个项目使用了数十个,数百个链接库,每次编译就要输入非常长的指令。所以,make工具和makefile文件诞生了。makefile文件记录如何复杂项目的编译方式,make命令将其实现!
使用如下命令在ubuntu系统中安装make工具
sudo apt install ubuntu-make
如下博文介绍了make工具的使用方式,本文不再详细介绍:
windows环境安装make命令-CSDN博客
四、cmake和CMakeLists的使用
make工具和makefile文件有一定局限性:其语法相对复杂,平台通用性较差。所以,cmake工具应运而生!
cmake允许开发者编写一种与平台无关(跨平台)的CMakeLists.txt 文件来制定整个工程的编译流程,cmake 工具会解析CMakeLists.txt 文件的语法规则,再根据当前的编译平台,生成本地化的Makefile 和工程文件。
可以使用如下指令在ubuntu上安装cmake:
sudo apt-get install cmake
下面给出最简单的cmake使用示例:
1、创建一个目录,在其中创建一个c++文件,编写一段非常简单的c++代码
#include <iostream>
int main(void)
{
std::cout<<"Cmake is fun!";
return 0;
}
2、在相同目录下创建CMakeLists.txt文件并进行编辑
注意,文件需要别严格命名为CMakeLists.txt
project(MYproject)
add_executable(myexecutable mycode.cpp)
project语句规定了项目名称,MYproject即为项目名,可以随意替换。
add_executable语句生成了一个可执行文件,名称为myexecutable,并且是由源文件mycode.cpp生成的。这句话其实等价于在命令行直接输入
g++ mycode.cpp -o myexecutable
也可以使用如下方式在CMakeLists.txt文件中创建变量,实现语句的简化
project(MYproject)
set(SOURCE_CODE mycode.cpp)
add_executable(myexecutable ${SOURCE_CODE})
3、运行cmake工具进行项目构建
在项目所在目录输入以下指令,可以运行cmake工具。cmake工具会自动寻找CMakeLists.txt文件并生成Makefile文件!
cmake .
如图所示,cmake工具生成了一系列文件,包括最重要的Makefile和其他中间文件。
4、运行make工具生成可执行文件
在项目目录下输入以下指令,便可make工具自动寻找Makefile文件编译项目,得到所需要的可执行文件。
make
5、将编译结果与源文件分离
在某个文件夹里运行cmake指令,默认将生成的文件存在当前文件夹里。但如果cmake指令后面加上参数,可以将处理其他文件夹的源文件。
按照这个思路,我们在项目文件夹里创建src文件夹,build文件夹以及脚本,便可以实现分离!
其中,src文件里包含着源文件和CMakeList.txt,build文件夹为空,buildprocess.txt是我写的脚本,如下所示。
cd build
cmake ../src
make
即可在build文件里得到可执行文件!
五、ros2中的CMakeLists
ros2中,工作空间下包含4个文件夹:
其中,src文件夹储存源文件, build文件夹储存编译后的文件,install文件夹储存中间文件和资源文件。
src文件夹下有若干功能包,对于c++编写的功能包,目录下就包含了CMakeLists,如下所示:
当我们在工作空间下运行如下命令的时候
colcon build
ros2自带的构建工具会自动执行cmake指令和make指令,将源文件转化为可执行文件,并储存在build文件夹对应的位置中!