虽然我们编译几个程序的指令很短,但是我们在一个大的项目中可能会存在成百上千个文件,那么手写指令可能需要很长很长时间,且这一种工作可能需要多次重复,那么可以使用Makefile来取代这项工作。
下面从四个版本的Makefile文件来由浅入深学习Makefile的使用:
首先,我们从最简单的Makefile文件的编写入手,我们有如下文件
getmin.c getmin.h main.c sort.c sort.h
那么编译生成可执行文件就可以用如下指令
gcc *.c -o app
我们也可以使用Makefile来完成这项任务,文件命名必须为 makefile
或 Makefile
,其基本规则如下:
目标:依赖文件1 依赖文件2 依赖文件3 ....
命令
注意这里命令前的不是空格,而是 Tab 键,那么Makefile就可编写如下
app:getmin.c sort.c main.c
gcc getmin.c sort.c main.c -o app
第一种写法有个问题是,如果一个 .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
即使该命令出现问题,后面的命令也会继续执行。