如何使用GNU Make

参考教程:
跟我一起写Makefile
GUN make 入门到精通
GNU make

前言:前段时间写OS的Lab2时,就已经使用了Makefile,不过较为简单,也只是一知半解。(不过前段时间听他们群里的说法Makefile也是旧日荣光了?)

概述

为什么要有Makefile

应对大型工程项目,makefile可以定义了整个编译流程以及各个目标文件与源文件之间的依赖关系,出现修改时,只会重新编译修改会影响的部分,从而降低了编译的时间,提升了编译的效率。

Makefile究竟是什么?

Makefile是整个工程的编译原则,定义了一系列的规则来指定文件编译顺序

Makefile给工程带来了“自动化编译”,一个make指令就可以使整个工程完全自动编译

区分GNU MakeMakefile

GNU Make is a tool which controls the generation of executables and other non-source files of a program from the program's source files.

Make gets its knowledge of how to build your program from a file called the makefile, which lists each of the non-source files and how to compute it from other files.

上述是来自于GNU官网对于两者的定义,可以直观看出,Make是批处理的工具,而这个工具所仰仗的信息来源于Makefile,所以写好Makefile文件使我们能很好利用Make工具的重要前提。

Makefile

基本规则

1.如果这个工程没有编译过,那么我们的所有c文件都要编译并被链接。
2.如果这个工程的某几个c文件被修改,那么我们只编译被修改的c文件,并链接目标程序。
3.如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的c文件,并链接目标程序。

默认情况下,make命令会在当前目录下按顺序寻找文件名为GNUmakefilemakefileMakefile的文件,最好使用Makefile这个文件名,最好不要用GNUmakefile,因为这个文件只能由GNUmake。当然也可以使用别的文件名来书写makefile,不过需要加上-f可选项进行指定。

基本格式

target: prerequisites
    recipe
    ...
    ...

结合上述基本规则,Makefile最核心的内容就是prerequisites中如果有一个以上的文件比target文件要新的话,recipe所定义的命令就会被执行。

target

可以是object file,也可以是一个可执行文件,还可以是一个标签

prerequisites

生成该target所要依赖的文件或其他target

recipe

target要执行的命令,每行一定要以Tab开头

工作流程

main: a.o b.o
	gcc -o main a.o b.o

a.o: a.c
	gcc -c a.c -o a.o

b.o: b.c
	gcc -c b.c -o b.o

clean:
	rm -rf a.o b.o main

使用上述makefile文件,目录结构为

a.c  b.c  makefile
frechen026@frechen026-virtual-machine:~/Desktop/MakeTest$ make
gcc -c a.c -o a.o
gcc -c b.c -o b.o
gcc -o main a.o b.o
frechen026@frechen026-virtual-machine:~/Desktop/MakeTest$ ./main
hello, a!
hello, b!!
frechen026@frechen026-virtual-machine:~/Desktop/MakeTest$ make
gcc -c b.c -o b.o
gcc -o main a.o b.o
frechen026@frechen026-virtual-machine:~/Desktop/MakeTest$ ./main
hello, a!
hello, b!
frechen026@frechen026-virtual-machine:~/Desktop/MakeTest$ make
make: 'main' is up to date.
frechen026@frechen026-virtual-machine:~/Desktop/MakeTest$ ./main
hello, a!
hello, b!

使用make指令可以看出执行了main这个target的内容,对b.c文件进行修改,发现只执行了gcc -c b.c -o b.o,未执行gcc -c a.c -o a.o,在不进行修改的情况下执行make,发现提示已是最新,无需更新。

make指令

默认的指令便是make,我们不需要在makefile文件中显式的指出(似乎也没法指出,毕竟target不能是个空吧)

make指令的工作流程:
1.找寻当前目录下Makefile文件
2.默认使用第一个target,找寻第一条target的依赖
3.处理依赖文件的target
4.根据目标target修改时间以及依赖的修改时间判断是否需要重新构建

参考教程跟我一起写Makefile,这里的表述我认为是存在一定问题的,未先去处理依赖文件如何获知依赖文件的最后更新时间以及是否存在?时间逻辑的先后应该是先去递归处理依赖文件,最后才是返回来处理目标target

官方的描述为:
In the example, this rule is for relinking edit; but before make can fully process this rule, it must process the rules for the files that edit depends on, which in this case are the object files. Each of these files is processed according to its own rule.

后续引入了其他makefile文件以及变量等概念后,make工作时的执行步骤为:
1.读入所有的Makefile
2.读入被include的其它Makefile
3.初始化文件中的变量
4.推导隐式规则,并分析所有规则
5.为所有的目标文件创建依赖关系链
6.根据依赖关系,决定哪些目标要重新生成
7.执行生成命令

其他target指令

make默认执行的就是第一个target以及其依赖的target,故无关联的target并不会进行执行,如我们所写makefile中的clean,但我们可以通过显式执行来执行clean,即make clean指令。

frechen026@frechen026-virtual-machine:~/Desktop/MakeTest$ make clean
rm -rf a.o b.o main

效果如上所示,故make指令也可以显式为make加上首条target,在此即为make main,效果如下所示

frechen026@frechen026-virtual-machine:~/Desktop/MakeTest$ make main
gcc -c a.c -o a.o
gcc -c b.c -o b.o
gcc -o main a.o b.o

因此我们可以通过显式方法来执行其余target,其余target的工作流程也与make相同。

不难发现,有些target如上述的clean,并非是文件名。我们可以使用虚假的目标PHONY,将目标标记为不指向文件,当然PHONY同样适用于文件。

语法格式:

.PHONY: target1 target2 ...
main: a.o b.o
	gcc -o main a.o b.o

a.o: a.c
	gcc -c a.c -o a.o

b.o: b.c
	gcc -c b.c -o b.o

clean:
	rm -rf a.o b.o main

.PHONY: main clean

值得注意的一点是:PHONY目标总是被认为是过期的,因此将总是运行这些targetrecipe,递归的也将总是运行包含PHONY依赖的target

还是上述的makefile,每次执行make,发现都会执行main下的recipe

frechen026@frechen026-virtual-machine:~/Desktop/MakeTest$ make
gcc -o main a.o b.o
frechen026@frechen026-virtual-machine:~/Desktop/MakeTest$ make
gcc -o main a.o b.o

a.o置为PHONY,取消mainPHONY,执行make

frechen026@frechen026-virtual-machine:~/Desktop/MakeTest$ make
gcc -c a.c -o a.o
gcc -o main a.o b.o
frechen026@frechen026-virtual-machine:~/Desktop/MakeTest$ make
gcc -c a.c -o a.o
gcc -o main a.o b.o

由于main的依赖targetPHONY的,不仅是a.o需要重编译,main受其牵连也要如此。

清空目录的规则

每个Makefile中都应该写一个清空目标文件( .o )和可执行文件的规则,这不仅便于重编译,也很利于保持文件的清洁。这是一个“修养”。

确实如此!

question

一个令我感到头疼的问题是所谓目标target和依赖更新时间的先后,如果目标target是文件,那这不难理解,文件都有最后一次写入时间。但若是目标target根本就不是文件或是一个根本不存在的文件,类似于上述的伪目录

处处透露出合理,却在某些时候想不通真是一件让人难过的事。这两天虽然看了看提问的智慧,可当自己想描述一个问题时却发现还是十分困难,果然,提问是门艺术。

表述为不存在的文件或者伪目标应该都是“过期的”,而依赖也是在检测自己是否是“过期的”,如果过期则执行相关的重新编译,感觉这种说法更加利于理解一些。

书写规范

变量使用

将需要重复大段使用的内容整合为一个变量,便于修改以及复用

我们可以修改makefile文件,将a.o b.o列为变量

objects = a.o b.o

main: $(objects)
	gcc -o main a.o b.o

a.o: a.c
	gcc -c a.c -o a.o

b.o: b.c
	gcc -c b.c -o b.o

clean:
	rm -rf a.o b.o main

.PHONY: clean

执行效果一样。

makefile中定义的变量,声明时要给予初值,而在使用时要在变量名前加上$,最好使用()或者{}将变量名括起来。

隐式规则

是一种惯例,让make自己去推导会发生什么,这些约定俗成的内容在我们不显式指出并规定时就会按照默认进行。

可以将上述makefile进一步精简

objects = a.o b.o

main: $(objects)
	gcc -o main a.o b.o

clean:
	rm -rf a.o b.o main

.PHONY: clean

执行make指令发现

frechen026@frechen026-virtual-machine:~/Desktop/MakeTest$ make
cc    -c -o a.o a.c
cc    -c -o b.o b.c
gcc -o main a.o b.o

GNU Make工具已经猜测出生成a.o b.o所需执行的指令

当然,还有很多隐式的一些规则,不加以罗列,但对于一些不是很清楚的隐式规则可能发生时,我们应该想方设法避免他,即使他真的十分简洁。

注释

写好注释应该是每个好的程序员应该有的素养,makefile也可以书写注释,其注释采用#,一般编辑器下应该都可以使用快捷键ctrl + /

伪目标

上面已经提及过相关内容,“伪目标”并不是一个文件,只是一个标签而已。为了避免“伪标签”和文件重名的情况,我们可以使用特使标记.PHONY来显式的指明一个目标是“伪目标”。

伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为“默认目标”,只要将其放在第一个。一个示例就是,如果你的Makefile需要一口气生成若干个可执行文件,但你只想简单地敲一个make完事,并且,所有的目标文件都写在一个Makefile中,那么你可以使用“伪目标”这个特性。

所以我们可以借助“伪目标”简化shell命令,以简单的语句执行复杂的shell语句,前提是你的makefile写的足够好。

后话

当然,makefile还有很多细节值得去学习,例如函数、多文件等等,不过总感觉记录下来过于繁琐,也可能是不知道如何记录。所以碰到实际的问题还是要多参考参考上述的几篇教程…

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值