《CMake 构建实战-项目开发卷》分享1—编译构建基础
导言
本书第一章《构建之旅》,从命令行编译源程序,从程序由单一文件构成,到多个文件构成,再到程序连接到静态库文件,程序链接到动态库文件,非常详细的描述了目标构建的关系、构建书序。最后引入 Boost 库,构建一个依赖于第三方库的程序。
回首我的嵌入式开发历史,吃了多少编译的亏,吭哧吭哧解决了多少编译相关的错误,但是没有形成一个系统的知识架构,非常遗憾。感谢本书作者为我补全了编译相关的一些知识。
浅谈Makefile
最早接触 Makefile 是在 Linux 环境下做嵌入式开发。当时很恐惧,Makefile 中一行一行的代码列出依赖关系,什么.o文件依赖什么.c 文件,什么 target 又依赖什么 OBJS,一头雾水,就连 $@ 和 $< 符号也来欺负人。网上找了一些教程,了解到 Makefile 规则文件是由一系列面向目标的规则构成的,描述依赖关系以及目标是由什么命令执行生成的。已经好长时间没写过Makefile了,生疏了,目前只能看懂,稍微修改一下。
当初以为Makefile 只能调用 gcc/g++ 等编译器命令,实际上可以调用任何系统注册的命令,可以执行一些编译前后的特殊动作,例如 bin 文件生成、调用外部工具给 bin 文件加密、签名等。
对此,我的一点心得是:Makefile 中的一系列构建目标通常都是为了生成 elf 文件,但是对于嵌入式开发,可能附加一些编译后的动作,例如 elf 文件生成 hex 文件、bin 文件,甚至是生成反汇编文件。也有一些厂商有一些特殊功能,例如在编译后需要自动把 bin 文件打包成支持 OTA 的 bin 文件格式,甚至给 bin 文件加上 CRC 、签名等,都是可以的。只需要在新增一个伪目标,调用外部工具即可。
浅谈编译器输出文件以及踩过的坑
linux 下编译工具生成的目标文件一般都是 .o 文件,而在 windows 下编译工具生成的目标文件一般都是 .obj 文件。对于一般嵌入式开发的小伙伴来说可能不在意这个细节,但是我在这个小细节上踩过坑。
曾经接手一个前同事的项目,基于CMake+GCC 编译,同事现在虚拟机上搭建Ubuntu编译环境,编译正常。我接手后新增了一个需求:跨平台编译,同时支持Windows和Linux环境下的 CMake+GCC 编译。我现在Windows电脑上安装了必要的工具,如CMake, Make, Ninja, Arm-none-eabi-gcc ,然后编译,总是报错,提示某个函数定义找不到。我查看了编译日志,此函数所在的文件参与了编译,但是链接过程找不到这个函数定义。让人摸不着头脑。后来我无意中查看链接脚本,原来链接脚本中把 *.o 放到某个段中,但是这个通配符在 Windows 环境下失效了,仔细一看 arm-none-eabi-gcc 在 Windows 平台输出的目标文件名后缀为 *.obj 而不是 *.o,所以链接脚本找不对应的函数是合乎情理的。我更改链接脚本中的 *.o 为 *.obj,成功解决问题。
浅谈编译依赖和产物
编译C/C++程序,无论是IDE还是Makefile 抑或是 CMakeLists.txt,其输入大致分为以下几种:
- 源文件路径;
- 头文件搜索路径:当前项目的头文件路径,外部库的头文件路径;
- 库文件;
- 库文件搜索路径;
- 编译选项,例如宏定义、编译器参数、链接器参数;
- 输出文件,即有可执行文件又包含由可执行文件加工生成的其他文件,编译生成的库文件;
编译报错,最常见的问题无非是源文件缺失,头文件找不到,某个函数未定义,编译参数错误。按照上面的几条一一对照总能解决。