Makefile是什么?初学者的Make指南

这是一篇英文博文翻译,第一次看到对初学者如此友好的,清晰明了的make指南,想让更多人看到。原文地址:GNU Make in Detail for Beginners

什么是 Make?

​ 一个较大的项目往往包含几千行代码,数个源文件,储存在不同的子目录下。除此之外,还可能包含多个组件。这些组件之间存在着复杂的内部依赖。举个例子:

为了编译组件X,我们首先需要编译Y

为了编译组件Y,我们首先需要编译Z

……

​假设我们对X做了一点点修改,却需要手动从Z开始一个个编译,不仅麻烦耗时,还容易出错。

​这时候 make 就来解决这个问题啦。我们可以为项目创建Makefile文件(小写的makefile也可以),其中规定了组件之间的依赖,因此在编译的时候会按照满足组件间依赖关系的顺序。

当只对项目中的某个组件A修改了一点点时,make只会重新编译被修改的组件A和任何依赖组件A的其它组件。

​除了组件之间的依赖关系,Makefile文件还能够描述项目结构,源文件位置,编译参数,输出位置等内容。当执行make命令时,就会按照当前目录下的Makefile来编译项目。

​Makefile的编写类似shell脚本,在之后会详细介绍

来一个 HelloWorld 的例子吧

​我们用C语言编写一个 “Hello World” 项目,并用make来编译它,在Makefile里设置编译后的可执行文件位置。以下是项目代码

module.h

# include <stdio.h>
void sample_func()

module.c

# include "module.h"
void sample_func() {
    
    
  printf("Hello World!\n");
}

main.c

# include "module.h"
void sample_func();
int main() {
    
    
  sample_func();
  return 0;
}

项目目录:

HelloWorld

├── Makefile

├── main.c

├── module.c

└── module.h

那么如果要手动编译HelloWorld项目,生成一个能输出 Hello World! 的可执行程序,该怎么做呢?

依次在当前目录输入以下命令:

gcc -I . -c main.c # 得到 main.o
gcc -I . -c module.c # 得到 module.o
gcc main.o module.o -o target_bin # 得到目标二进制文件

gcc在执行编译的时候,总共需要4步

1、预处理,生成 .i 的文件[预处理器cpp]

2、将预处理后的文件转换成汇编语言, 生成文件 .s [编译器egcs]

3、有汇编变为目标代码(机器代码)生成 .o 的文件[汇编器as]

4、连接目标代码, 生成可执行程序 [链接器ld]

gcc-I 参数指定了 #include "file" 的首先查找路径,-I .即先查找当前目录,-c 参数表示只激活预处理,编译,和汇编,也就是他只把程序做成obj文件。

为 HelloWorld 编写 Makefile

约定成俗的,Makefile中的变量名都是大写的,一些常用的变量也有约定成俗的表示,比如 CC = gcc,之后要访问时可以用 $(CC)${CC}

注释用#开头,以上两点和shell脚本相同

Makefile的一般格式是:

target: dependency1 dependency2
		action1
		action2
		...

那么上面HelloWorld项目的Makefile是:

all: main.o module.o
		gcc main.o module.o -o target_bin
mian.o: main.c module.h
		gcc -I . -c main.c
module.o: module.c module.h
		gcc -I . -c module.c
clean:
		rm -rf *.o
		rm target_bin

在这个Makefile中,我们有四个target:

  • all 是一个特殊的target关键字。我们需要一个终极target来生成最终的可执行文件,

  • main.o 是一个文件名target,依赖于main.cmoudle.h

  • module.o 是一个文件名target,依赖于 module.cmodule.h

  • clean 是一个特殊的target关键字,没有依赖。该命令用于清除项目编译的结果

    接下来我们只需要在当先目录下输入 make 命令就可以了 ,make 命令后面可以跟一个target参数(Makefile中定义过的),表示编译当前target组件,比如 make module.o,也可以什么参数都不加,那么 make 就会默认编译Makefile中的第一个target。在我们的Makefile中,就是all,并且最终生成可执行文件target_bin

Make 运行的时候发生了什么?

make 命令被调用时,它会查找当前目录下名为makefile或Makefile的文件。它从语法上分析找到的Makefile文件,构建依赖树。之后make检查目标target的依赖,检查这些依赖的target是否存在,如果存在,则判断这些依赖的target是不是最新的(依赖target是否比目标target新,通过检查文件的时间戳),否则重新编译。

详细来讲,当target是一个文件名target时,make比较target文件和其依赖文件的时间戳,如果它的依赖文件是另一个target,那么make就检查该target的依赖的时间戳。这将会是一个沿着依赖树的递归检查。如果make发现了某个比目标target新的文件A,所有在依赖树中受A影响的分支都会被重新编译,从树的底层开始,更新依赖文件。

这就是make 节省时间的原因,只重新编译修改过的文件

如果targte不是一个文件名,比如 clean, all等特殊target,make就不会检查时间戳,直接执行。

在执行每个target时,make会打印出当前target的action。这里划重点,每一个action都是在一个分离的子shell中执行的,如果某个action改变了shell的环境,这个改变只会在当前shell生效。举个例子,某个action中调用了cd anotherDir 命令,当前目录就会变为anotherDir,但只会对当前行/action生效,在下一行/action中,当前目录又会变回来。

运行我们的 Makefile

是时候跑一下我们写的Makefile了:

don@yaoyao HelloWorld % make
gcc -I . -c main.c
gcc -I . -c module.c
gcc main.o module.o -o target_bin

don@yaoyao HelloWorld % ./target_bin
Hello World!

如之前所说,当make命令没有参数是,会默认使用Makefile文件中的第一个target,也就是all。target all的依赖是 module.omain.o,当我们第一次运行make时这些文件还不存在,因此make会先执行target module.omain.o。在用相应命令获得module.omain.o后,最终执行target all的命令,得到目标可执行文件target_bin

如何我们立马又运行了make命令,没有更改源文件,我们可以看到只有target all对应的命令被执行:

don@yaoyao HelloWorld % make
gcc main.o module.o -o target_bin

在上述命令运行期间,make检查每个target的依赖的时间戳,对比当前target和其依赖的时间戳。因为我们什么都没改,因此不执行任何target的命令。但是因为all不是一个文件名,make无法比较文件的时间戳,直接执行了对应命令。

好了,我们现在更改一下module.c文件,添加一条printf("\nfirstupdate");,修改如下:

module.c

# include "module.h"
void sample_func() {
    
    
  printf("Hello World!\n");
  printf("\nfirstupdate");
}

然后make:

don@yaoyao HelloWorld % make
gcc -I . -c module.c
gcc main.o module.o -o target_bin

由于module.o的依赖(即module.c)被修改,target module.o 的时间戳比依赖module.c旧,因此make重新运行target module.o对应的命令,也就是gcc -I . -c module.c

最后,我们可以用make clean 来清除生成的文件:

don@yaoyao HelloWorld % make clean
rm -rf *.o
rm target_bin

don@yaoyao HelloWorld % ls
Makefile	main.c		module.c	module.h

你的 Makefile 还可以有以下操作

待更新。。。

猜你喜欢

转载自blog.csdn.net/weixin_44160754/article/details/109731335