深入了解Linux —— make和makefile自动化构建工具

什么是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.cChange时间晚于目标文件codeChange时间还是不能够执行;

那现在我们就能够得出结论,只有当依赖文件的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.ocode.o依赖于code.scode.s依赖于code.icode.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=codeSRC=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

相关推荐
A小辣椒2 天前
TShark:Wireshark CLI 功能
linux
A小辣椒2 天前
TShark:基础知识
linux
AlfredZhao2 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao2 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334663 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪3 天前
linux 拷贝文件或目录到指定的位置
linux
大树883 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠3 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush43 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5203 天前
Linux 11 动态监控指令top
linux