前言
makefile 关系到了整个工程的编译规则。一个工程中的源文件不计其数,并且按类型、功能、模块分别放在若干个目录中,makefile 定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile 就像一个 Shell 脚本一样,其中也可以执行操作系统的命令。
本文记录了 Makefile 的使用。
一、Makefile 介绍
Makefile 是一个名为 GNU-Make 软件所需要的脚本文件,该脚本文件可以指导 Make 软件控制 arm-gcc 等工具链去编译工程文件最终得到可执行文件,几乎所有的 Linux 发行版都内置了 GNU-Make 软件,VScode 等多种 IDE 也内置了 Make 程序。说白了,Makefile 就是用来管理项目的。
你见到的 xxx.mk 文件或者 Makefile 都统称为 Makefile 脚本文件。命名只能为 makefile 或者是 Makefile,因为只有这两种命名方式才能被 make 命令识别。
Makefile 脚本文件的语法学习可以参考:跟我一起写Makefile 陈皓
二、示例源码
这里放出供下面 Makefile 基础规则中用来测试的源码。
1、hello.c
#include <stdio.h> #include "head.h" int main(int argc, char *argv[]) { int a = 10; int b = 5; printf("%d+%d=%d\n", a, b, add(a, b)); printf("%d-%d=%d\n", a, b, sub(a, b)); printf("%d/%d=%d\n", a, b, div(a, b)); printf("%dx%d=%d\n", a, b, mul(a, b)); return 0; }
2、add.c
int add(int a, int b) { return a+b; }
3、sub.c
int sub(int a, int b) { return a-b; }
4、mul.c
int mul(int a, int b) { return a*b; }
5、div.c
int div(int a, int b) { return a/b; }
6、head.h
#ifndef _HEAD_H_ #define _HEAD_H_ int add(int , int ); int sub(int , int ); int div(int , int ); int mul(int , int ); #endif
三、Makefile 基础规则
想要掌握 makefile,首先需要了解两个概念,⼀个是目标(target),另⼀个就是依赖
(dependency)。目标就是指要干什么,或说运行 make 后生成什么,而依赖是告诉 make 如何去做以实现目标。在 Makefile 中,目标和依赖是通过规则(rule)来表达的。
makefile 的依赖是从上至下的,换句话说就是目标文件是第一句里的目标, 如果不满足执行依赖,就会继续向下执行。如果满足了生成目标的依赖,就不会再继续向下执行了。make 会自动寻找规则里需要的材料文件,执行规则下面的行为生成规则中的目标。
Makefile 三要素:
工作原理:
1、一个规则
1个规则
目标:依赖条件 (一个 tab 缩进)命令
①、目标的时间必须晚于依赖条件的时间,否则,更新目标。
②、依赖条件如果不存在,找寻新的规则去产生依赖条件。
下面来一步一步升级 makefile
利用上述代码实现第一个版本的 Makefile
Makefile 第一版
a.out : hello.c add.c sub.c mul.c div.c gcc hello.c add.c sub.c mul.c div.c -o a.out -I ./
执行 make :
此时,修改 add.c 为下面
add.c
int add(int a, int b) { return a+b+1; }
此时,再使用 make,发现了问题
可以看到,只修改 add.c,但是编译的时候,其他.c 文件也重新编译了,这不太科学。明明只改了一个,全部都重新编译了。
于是将 makefile 改写如下:
Makefile 第二版
a.out : hello.o add.o sub.o mul.o div.o gcc hello.o add.o sub.o mul.o div.o -o a.out hello.o : hello.c gcc -c hello.c -o hello.o -I ./ add.o : add.c gcc -c add.c -o add.o sub.o : sub.c gcc -c sub.c -o sub.o mul.o : mul.c gcc -c mul.c -o mul.o div.o : div.c gcc -c div.c -o div.o
执行 make 指令如下:
此时,再将 add.c 改为最开始的代码:
add.c
int add(int a, int b) { return a+b; }
执行 make 指令如下:
可以看到,只重新编译了修改过的 add.c 和最终目标
makefile 检测原理:修改文件后,文件的修改时间发生变化,会出现目标文件的时间早于作为依赖材料的时间,出现这种情况的文件会重新编译。修改 add.c 后, add.o 的时间就早于 add.c , a.out 的时间也早于 add.o 的时间了,于是重新编译这俩文件了。
关于 makefile 指定目标问题,先修改 makefile 如下:
Makefile 第三版
hello.o : hello.c gcc -c hello.c -o hello.o -I ./ add.o : add.c gcc -c add.c -o add.o sub.o : sub.c gcc -c sub.c -o sub.o mul.o : mul.c gcc -c mul.c -o mul.o div.o : div.c gcc -c div.c -o div.o a.out : hello.o add.o sub.o mul.o div.o gcc hello.o add.o sub.o mul.o div.o -o a.out
只是将 a.out 放在了文件末尾
执行 make,如下:
这是因为, makefile 默认第一个目标文件为终极目标,生成就跑路,这时候可以用 ALL 来指定终极目标。
ALL:指定 makefile 的终极目标。
指定目标的 makefile 如下:
Makefile 第四版
ALL : a.out hello.o : hello.c gcc -c hello.c -o hello.o -I ./ add.o : add.c gcc -c add.c -o add.o sub.o : sub.c gcc -c sub.c -o sub.o mul.o : mul.c gcc -c mul.c -o mul.o div.o : div.c gcc -c div.c -o div.o a.out : hello.o add.o sub.o mul.o div.o gcc hello.o add.o sub.o mul.o div.o -o a.out
执行: