虽然我们编译几个程序的指令很短,但是我们在一个大的项目中可能会存在成百上千个文件,那么手写指令可能需要很长很长时间,且这一种工作可能需要多次重复,那么可以使用Makefile来取代这项工作。




下面从四个版本的Makefile文件来由浅入深学习Makefile的使用:

第一种写法

首先,我们从最简单的Makefile文件的编写入手,我们有如下文件

getmin.c getmin.h main.c sort.c sort.h

那么编译生成可执行文件就可以用如下指令

gcc *.c -o app

我们也可以使用Makefile来完成这项任务,文件命名必须为 makefileMakefile,其基本规则如下:

目标:依赖文件1 依赖文件2 依赖文件3 ....
	命令

注意这里命令前的不是空格,而是 Tab 键,那么Makefile就可编写如下

app:getmin.c sort.c main.c
        gcc getmin.c sort.c main.c -o app

然后在命令行执行 make 命令即可


第二种写法

第一种写法有个问题是,如果一个 .c 文件被修改,那么 make 就需要重新从所有 .c 文件开始编译,那么可能会需要花费很长的时间。

最理想的做法是目标可执行文件只依赖 .o 文件,那么一个 .c 文件被修改,只需要将该 .c 文件编译生成 .o 文件即可,其他 .o 文件不需要重新编译了,那么 Makefile 文件的第二种写法就如下

app:getmin.o sort.o main.o
        gcc getmin.o sort.o main.o -o app

getmin.o:getmin.c
        gcc -c getmin.c

sort.o:sort.c
        gcc -c sort.c

main.o:main.c
        gcc -c main.c

这里不得不提到Makefile的另一项规则,Makefile 会把规则中的第一个目标作为终极目标,查看其依赖条件,对比其依赖条件是否存在或是否需要被更新,如果是,则会跳到下面相应的规则执行命令。比如 getmin.o 不存在或者 getmin.c 被更新了,那么其就会重新执行 gcc -c getmin.c 指令,然后再执行 gcc getmin.o sort.o main.o -o app 这样只编译了 getmin.c 文件,效率就提高了不少。


第三种写法

Makefile 还可以指定普通变量来替代一些文件或命令,也可以使用自动变量来指代规则中的目标、依赖条件等。

obj=getmin.o sort.o main.o
target=app
CC = gcc
$(target):$(obj)
        $(CC) $(obj) -o $(target)

%.o:%.c
        $(CC) -c $< -o $@

这里将 getmin.o sort.o main.o 赋值给变量 obj,将 app 赋值给变量target 变量在使用的时候使用 $(变量),除此之外还将 gcc 赋值给变量 CC

这里还可以使用通配符 %.o 指代所有 .o 文件,%.c 指代所有 .c 文件,当需要一个 .o 文件,比如 getmin.o其到 %.o:%.c 会自动生成相应规则 getmin.o:getmin.c

除此之外,这里还用 $< 指代依赖的第一项,$@ 指代目标。还可以用 $^ 指代所有依赖项。


第四种写法

上面的写法还有一个不够效率的地方就是,如果当前文件夹有很多文件,那么比如给变量 obj 赋值时就需要写很长的一段代码,这时就可以用 Makefile 里的函数来进行简化操作。

src=$(wildcard ./*.c)
obj=$(patsubst ./%.c, ./%.o, $(src))
target=app
CC = gcc
$(target):$(obj)
        $(CC) $(obj) -o $(target)

%.o:%.c
        $(CC) -c $< -o $@

clean:
        rm $(obj) $(target)

这里的 wildcard ./*.c 表示提取当前文件夹下所有 .c 文件生成变量,因为是生成变量,所以需要使用 $() 才能取得对应的值,我们将其赋值给 src

patsubst ./%c, ./%.o, $(src) 表示从 src 变量中提取的值,将所有 .o 变为 .c 赋值给变量,然后将其赋值给 obj 变量。

这里还用到了 clean 目的是清除所有 .o 文件与目标可执行文件,至于为什么要这样做,有一种情况是,.h 文件更新了,但按照 Makefile 的编写不需要重新编译更新目标可执行文件,所以我们需要 clean 以使得可以正常更新,当然还有其他情况这里暂不详细说明。

因为 clean 不是首要目标,所以需要再命令行中使用 make clean 来执行命令。如果没有 .o 文件和目标文件,可能会出现提示信息,如果不想显示,可以在 rm 后加上 -f

还有一种情况就是,如果当前文件夹存在 clean 文件,那么 make clean 会有问题,因为已经存在最新的 clean 文件了,所以我们要增加伪目标声明:

.PHONY:clean
clean:
	rm $(obj) $(target) -f

当然除了 clean 我们也可以添加其他的伪目标:

hello:
	echo "hello, makefile"

然后命令行执行 make hello 就能打印这段话。如果一个规则由多条命令组成,其中一条命令出现问题,之后的命令就不会执行,这时可以通过在前面加上 - 来继续执行后面的指令,比如:

-rm $(obj) $(target) -f

即使该命令出现问题,后面的命令也会继续执行。

Logo

更多推荐