什么是make
/makefile
在之前写代码的过程中,我们都是对一个文件进行编译链接(
gcc编译
),但是如果一个项目中,源代码文件非常的多,我们总不能一个一个的进行编译链接,这也太麻烦了;所以现在就来学习make
/makefile
实现自动化构建
make
是一个命令工具,是一个解释makefile
在指令的命令工具,大多数的IDE
都存在这个命令。
makefile
成为一种在工程方面的编译方法。
makefile
是一个文件,make
是一个命令;二者搭配使用来完成项目的自动化构建makefile
的好处就是自动化构建,写好makefile
文件以后就只需make
命令就可以完成项目工程的自动化构建,提点高了软件开发的效率
其实会不会写makefile
文件,也侧面反映了其是否具备完成大型工程的能力
在一个工程中源代码文件,按照类型、功能、模块分别放到了若干个目录下,makefile
定义一系列的规则来指定哪些文件需要先编译、哪些文件需要后编译,哪些文件需要重新编译,甚至进行更加复杂的操作。
makefile
基本使用
首先先来看一个makefile
如何使用(以及如何编写)
现在有一个已经写好的makefile
文件(这里简单使用一下)
我们在当前目录下编写一个code.c
文件,并且写上一个简单的代码
现在就来使用make
命令
我们看到,使用make
命令就自动编译code.c
文件形成code
可执行程序了。
现在就来看一下makefile
中内容有和含义呢?
makefile
code:code.c
gcc code.c -o code
.PHONY:clean
clean:
rm -f code
1. 依赖关系和依赖方法
对于上述makefile
文件,在介绍之前我们先来想要个问题,就是依赖关系
和依赖方法
- 依赖关系:所谓依赖关系就表明两个事物(这里指目标文件和依赖文件)之间关系(就像现实生活中两个人之间的关系一样)
- 依赖方法:有了依赖关系,那应该做什么呢?,依赖方法就表明(这里表示要如何生成目标文件);(就像现实生活中,你需要向朋友寻求帮助,有了你们是朋友这一关系,接下来就要表明你要做什么了)
了解了依赖关系
和依赖方法
,接下来看目标文件和依赖文件
目标文件和依赖文件,其实就是两个有着依赖关系的文件(依赖文件可以有多个),如上图就表示
code
依赖code.c
文件而依赖关系就表明了,如何由依赖文件列表生成
目标文件
。
这样我们就很好理解了,上述makefile
文件中,code
作为目标文件、code.c
作为依赖文件、通过依赖关系gcc code.c -o code
来生成目标文件。
2. 伪目标.PHONY
对于上述makefile
,我们连续执行多次make
就会发现,不能执行
但是我们如果连续指向多次make clean
,它并没有出错
到这里一个可能想到是.PHONY
的作用了;那这到底是什么呢?又为什么被称为伪目标呢?
伪目标:
.PHONY
修饰,伪目标它总是被执行的;像clean
这种的目标文件,一般都设置成伪目标
对于这个,可以解释为:
伪目标
clean
不依赖于任何文件,执行make clean
生成clean
目标文件就会执行依赖方法rm -f code
;但是依赖方法并没有生成clean
文件,而是删除code
文件,做清理工作。
这里虽然说clean
是一个目标文件,但在依赖方法中并没有生成clean
文件。(也许是因为没有生成clean
目标文件,才称为伪目标
)
3. ACM
时间
对于上述的伪目标
能够一直被执行,而其他就不能够连续执行;又是如何实现的呢?
先来了解什么是ACM
时间
Access
:文件最近访问的时间Modify
:文件内容最近修改的时间Change
:文件属性最近修改的时间
那我们如果想要看一个文件的ACM
时间,就要用到新的指令stat
了。
那又是根据哪个时间来判断是否执行呢? 答案是Modify
文件内容修改时间
我们可以看到上图中
目标文件
的Modify
时间是晚于依赖文件
的,所以就不能为执行。
仔细观察可以看出来,上图Change
时间目标文件
也晚于依赖文件
啊,这样不能说明比较的是Modify
时间啊
现在接着来看
我们可以看到,依赖文件code.c
的Change
时间晚于目标文件code
的Change
时间还是不能够执行;
那现在我们就能够得出结论,只有当依赖文件的Modify
时间晚于目标文件的Modify
时间时(在编译过后,依赖文件内容有所修改),才能够被执行。
这里.PHONY
之所以能一直被执行,其原因就是它忽视了对比时间。
4. 推导过程
在上述过程中,我们写的依赖文件都是在当前目录中直接存在的;拿如果依赖文件并没有直接存在,而是依赖于其他依赖文件呢?
这里就根据编译过程,生成编译过程中所有的临时文件,来写makefile
makefile
code:code.o
gcc code.o -o code
code.o:code.s
gcc -c code.s -o code.o
code.s:code.i
gcc -S code.i -o code.s
code.i:code.c
gcc -E code.c -o code.i
.PHONY:clean
clean:
rm -f *.i *.s *.o code
如上述,code
依赖于code.o
、code.o
依赖于code.s
、code.s
依赖于code.i
、code.i
依赖于code.c
。这些除了code.c
以为的文件都没有直接存在于当前目录下。
这样依然可以运行,并且都生成了这些文件;这足以说明应该存在可以推导的过程。
扩展语法------更加通用的makefile
上面所描述的
makefile
它只能适用于应该文件,拿对于多个文件也是无济于事(还要重新写makefile
)这就一点太麻烦了,现在就来学习更多
makefile
语法,让makefile
变得更加适用。
1. 创建变量
在
makefile
中也可以创建变量,和C语言中指针那样
makefile
BIN=code
SRC=cpde.c
OBJ=code.o
CC=gcc
RM=rm -f
$(BIN):$(OBJ)
gcc $(OBJ) -o $(BIN)
$(OBJ):$(SRC)
gcc -c $(SRC) -o $(OBJ)
.PHONY:clean
clean:
$(RM) $(OBJ) $(BIN)
上述中例如BIN=code
、SRC=code.c
这样的就是创建了变量,(它们就像`C语言中的指针);
在需要访问这些变量的值的时候需要用到$()
(就比如$(BIN)
访问BIN
变量的值)
这里还有优化一下,使用$^
和$@
$^
:在依赖方法中使用,指依赖关系中的依赖文件
$@
:在依赖方法中使用,指依赖关系在的目标文件
所有就可以这样来写:
makefile
BIN=code
SRC=cpde.c
OBJ=code.o
CC=gcc
RM=rm -f
$(BIN):$(OBJ)
gcc $^ -o $@
$(OBJ):$(SRC)
gcc -c $^ -o $@
.PHONY:clean
clean:
$(RM) $(OBJ) $(BIN)
2. 编译当前目录下的多个文件
说了这么多,这还是只能编译一个文件,现在就来看如何编译当前目录下的多个文件
要想编译多个文件,那我们就不能给变量这样赋值了
makefile
SRC=code.c
这样就写死了,只能操作一个文件;现在我们需要的是,识别到当前目录下所以的.c
文件
获取当前目录下所有的.c
文件
方法有两种:
$(shell ls *.c)
:在makefile
中执行shell
命令,获取当前目录所有的.c
文件。$(wildcard *.c)
:使用wildcard
函数,获取所有的.c
文件。
将所有的.c
修改成.o
这里,我们并不是直接生成code
可执行程序的,而是生成对应的.o
文件,再链接形成的可执行程序;所以我们就需要获取所有.c
文件对于的.o
文件名;(使用makefile
语法即可)
makefile
OBJ=$(SRC:.c=.o)
这个语法就是将SRC
(获取当前目录下所有.c
文件)中的.c
替换成.o
;这样我们就获取到了所有.c
文件对应的.o
文件名。
通配符%
和逐个执行$<
上面经常写到*.c
,它就表示所有以.c
结尾的文件;那在写makefile
的使用,有时候也要用到通配符,但是我们没有使用*
,而是使用%
。
我们想要让它编译多个文件就不能像之前那样写了,(因为这里我们要生成所有.c
文件对应的.o
文件。
这样我们在写由
.c
生成.o
文件时,就能直接使用通配符%
,这样就可以自动匹配
在匹配结束之后,目标文件依赖多个文件;就不能使用$^
直接取依赖文件列表了,而是使用%<
将依赖文件列表中多个文件一个一个执行。
到这里,我们就能基本完成编译当前目录下所有.c
文件的makefile
了。
makefile
BIN=code
SRC=cpde.c
OBJ=code.o
CC=gcc
RM=rm -f
$(BIN):$(OBJ)
gcc $^ -o $@
$(OBJ):$(SRC)
gcc -c $^ -o $@
.PHONY:clean
clean:
$(RM) $(OBJ) $(BIN)
3. 测试和优化makefile
对于上述的makefile
,我们进行一些优化
makefile
BIN=code
SRC=cpde.c
OBJ=code.o
CC=gcc
RM=rm -f
COM=-c
LINK=-o
$(BIN):$(OBJ)
$(CC) $^ $(LINK) $@
%.o:%.c
$(CC) $(COM) $<
.PHONY:clean
clean:
$(RM) $(OBJ) $(BIN)
这里优化,新增了
COM
编译选项、LINK
链接选项;并且都使用变量,更将通用。
在进行测试之前,我们要先让当前目录有多个文件,这里介绍应该语法
makefile
touch code{1..20}.c
该指令作用是创建
code1.c、code2.c、code3.c......code20.c
(20个文件)。
现在该目录下新建了20个文件,运行一试
可以看到,它会显示出来编译的过程,如果不想要看到可以在语句前加上@
。
makefile
BIN=code
SRC=cpde.c
OBJ=code.o
CC=gcc
RM=rm -f
COM=-c
LINK=-o
$(BIN):$(OBJ)
@$(CC) $^ $(LINK) $@
%.o:%.c
@$(CC) $(COM) $<
.PHONY:clean
clean:
$(RM) $(OBJ) $(BIN)
这样我们不知道它编译的进度了,我们就可以手动回显一些内容(在依赖关系中直接添加输出到显示器文件的语句即可)
makefile
BIN=code
SRC=cpde.c
OBJ=code.o
CC=gcc
RM=rm -f
COM=-c
LINK=-o
$(BIN):$(OBJ)
@$(CC) $^ $(LINK) $@
@echo "link...$^"
%.o:%.c
@$(CC) $(COM) $<
@echo "compile...$^"
.PHONY:clean
clean:
$(RM) $(OBJ) $(BIN)
注意:这里在
echo
语句前也要加@
不让它回显,否则就会回显出来echo
语句的内容。
执行结果展示
到这里makefile
部分就结束了,后续有要深入了解的语法知识,在后续过程在继续学习。
我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2oul0hvapjsws