【把Linux“聊”明白】自动化构建-make/Makefile详解

自动化构建-make/Makefile详解

友情专栏:【把Linux"聊"明白】


文章目录


前言

在Linux开发环境中,我们经常需要编译和构建复杂的项目。手动一个个编译源文件不仅效率低下,而且容易出错。这时候,make和Makefile就成为了每个Linux开发者必须掌握的利器。本文将带你从零开始,深入理解make和Makefile的工作原理,并通过实战案例展示如何编写高效的自动化构建脚本。


一、make/Makefile是什么?

简单来说,make是一条命令,Makefile是一个文件 ,两个搭配使用,完成项目自动化构建。

详解:

一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。

makefile带来的好处就是⸺"自动化编译",一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。

make是一个命令工具,是一个解释makefile中指令的命令工具。

二、make/Makefile的基本概念

首先,建立一个Makefile文件

shell 复制代码
touch Makefile # Makefile/makefile都可以,我们习惯于Makefile

然后,保证你有个mypro.c的c文件,然后对Makefile写入:

shell 复制代码
mypro:mypro.c
		gcc mypro.c -o mypro # 前面的空格是一个tab的结果

依赖关系和依赖方法说明

即mypro依赖于mypro.c,而gcc myproc.c -o myproc 正是这种依赖方法。

在对Makefile写入上述依赖关系与依赖方法之后,我们就可以直接使用make命令了。

上面我们只是完成了Makefile的冰山一角,有个基础的认识。

我们可以用Makefile快速生成可执行,那有没有快速清理项目的呢?

项目清理

我们可以在已经有的Makefile再进行增加:

shell 复制代码
mypro:mypro.c
		gcc mypro.c -o mypro 

.PHONY:clean
clean:  # 可以没有依赖关系
		rm -f myproc

有了上面的Makefile,我们就可以进行项目清理了:

像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过,我们可以显示要make执行。即命令⸺make clean,以此来清除所有的目标文件,以便重编译。

.PHONY **
但是一般我们这种clean的目标问文件,我们将它设置为伪目标,用.PHONY修饰,伪目标的特性是
总是被执行的**。

我们也可以对第一组依赖关系用.PHONY修饰,来看看效果:

未修饰之前:

修饰之后:


有这些理解,我们来看看什么叫做总是被执行?

什么叫做总是被执行?

我们要知道,在编译的时候,默认老代码(以前编译过)是不会别重新编译的,很好理解,但是编译器是如何知道你是老代码还是新代码/更改过的代码。

我们知道:文件 = 内容 + 属性 ,我们可以用stat来查看文件的属性。


Modify: 内容变更,时间更新
Change:属性变更,时间更新
Access:常指的是文件最近一次被访问的时间。在Linux的早期版本中,每当文件被访问时,其atime都会更新。但这种机制会导致大量的IO操作。具体更新原则,不做过多解释

有了上面的了解,可以,对于每个文件的属性中都有其''时间''属性,我们改变了文件的内容,它的Modify时间一定会变,所以通过Modify时间来进行对比判断它是老代码还是新代码/更改过的代码。和谁对比呢,就和对应的二进制文件中的相对应的时间属性进行对比。

但是,有了.PHONY,它不管老代码还是新代码/更改过的代码,都会重新编译,可见:
.PHONY会让make忽略源文件和可执行目标文件的M时间对比

三、 推导过程

我们知道,C语言程序从.c文件到可执行文件会经过预处理、编译、汇编、链接,现在我们可以依次在Makefile中实现它:

shell 复制代码
  1 mypro:mypro.o                                                                                                                   
  2     gcc mypro.o -o mypro
  3 mypro.o:mypro.s
  4     gcc -c mypro.s -o mypro.o
  5 mypro.s:mypro.i
  6     gcc -S mypro.i -o mypro.s
  7 mypro.i:mypro.c
  8     gcc -E mypro.c -o mypro.i
  9                                                                                                  
 10 .PHONY:clean
 11 clean:
 12     rm -f *.i *.s *.o mypro  

执行make:

可见这样写也是可以的,那是怎么推导的呢?

当执行make命令时:

详细图解:

接下来,我们来总结分析一下make 是如何工作的 ,我们只 make 命令之后。那么:

  1. make 会在当前目录下找名字叫 "Makefile""makefile" 的文件。

  2. 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,它会找到 mypro 这个文件,并把这个文件作为最终的目标文件。

  3. 如果 mypro 这个文件不存在,或者 mypro 所依赖的 mypro.o 文件的修改时间要比 mypro 这个文件新,那么,它就会执行后面所定义的命令来生成 mypro 这个文件。

  4. 如果 mypro 所依赖的 mypro.o 文件不存在,那么 make 会在当前文件中找目标为 mypro.o 文件的依赖性,如果找到则再根据那个规则生成 mypro.o 文件。(这有点像一个堆栈的过程)

  5. 当然,你的 .c 文件和 .h 文件是存在的啦,于是 make 会生成 mypro.o 文件,然后再用 mypro.o 文件完成 make 的终极任务,也就是生成可执行文件 mypro 了。

  6. 这就是整个 make 的依赖性,make 会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。

  7. 在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么 make 就会直接退出,并报错。而对于后定义的命令的错误,或是编译不成功,make 根本不理。

  8. make 只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不存在,那么对不起,我就不工作啦。

四、"高级版"的 Makefile

有了上面一大串的铺垫,我们上面的Makefile文件只是一个较为简单的Makefile,只适合有一个源文件的时候使用,接下来,我要在此基础上在进行升级(适用于多个源文件的情况):

代码

makefile 复制代码
BIN=proc.exe        # 定义变量:最终生成的可执行文件名
CC=gcc              # 定义变量:使用的编译器

#SRC=$(shell ls *.c)     # 方式一:用 shell 命令获取所有 .c 文件名
SRC=$(wildcard *.c)      # 方式二:用 make 自带函数 wildcard 获取所有 .c 文件

OBJ=$(SRC:.c=.o)         # 把所有 .c 文件替换成对应的 .o 文件(目标文件列表)

LFLAGS=-o                # 链接选项(用于生成可执行文件)
FLAGS=-c                 # 编译选项(用于生成目标文件)
RM=rm -f                 # 定义删除命令

# === 构建目标规则 ===
$(BIN):$(OBJ)
	@$(CC) $(LFLAGS) $@ $^         # 第一个@:不回显命令
	@echo "linking ... $^ to $@"

# === 模式规则:编译每个 .c 文件成 .o 文件 ===
%.o:%.c
	@$(CC) $(FLAGS) $<
	@echo "compiling ... $< to $@"

# === 清理规则 ===
.PHONY:clean
clean:
	$(RM) $(OBJ) $(BIN)

# === 测试输出规则(非编译) ===
.PHONY:test
test:
	@echo $(SRC)
	@echo $(OBJ)

逐行讲解

定义变量

makefile 复制代码
BIN=proc.exe
CC=gcc
SRC=$(wildcard *.c)
OBJ=$(SRC:.c=.o)
  • BIN:最终生成的可执行文件名
  • CC:指定编译器为 gcc
  • SRC:自动收集当前目录下所有 .c 文件
  • OBJ:把 .c 扩展名替换为 .o,即生成目标文件列表

例如,当前目录中有:

复制代码
main.c util.c

则:

复制代码
SRC = main.c util.c
OBJ = main.o util.o

编译并链接

makefile 复制代码
$(BIN):$(OBJ)
	@$(CC) $(LFLAGS) $@ $^

解释:

  • 目标:proc.exe
  • 依赖:main.o util.o
  • 命令:使用 gcc 链接所有 .o 文件生成 proc.exe
  • $@:代表目标(这里是 proc.exe
  • $^:代表所有依赖文件(这里是 main.o util.o

模式规则

makefile 复制代码
%.o:%.c
	@$(CC) $(FLAGS) $<

这是 模式匹配规则

  • % 代表任意匹配部分(相同名字的 .c.o
  • $<:代表第一个依赖文件(即 .c 文件)
  • 命令会自动把每个 .c 编译成 .o

等价于:

makefile 复制代码
main.o: main.c
	gcc -c main.c

util.o: util.c
	gcc -c util.c

清理规则

makefile 复制代码
.PHONY: clean
clean:
	$(RM) $(OBJ) $(BIN)
  • .PHONY:声明伪目标(表示 clean 不是文件名)
  • $(RM):删除命令,这里是 rm -f
  • 作用:清除生成的目标文件和可执行文件。

执行:

bash 复制代码
make clean

即可清空中间文件。

测试规则

makefile 复制代码
.PHONY: test
test:
	@echo $(SRC)
	@echo $(OBJ)

输出当前检测到的 .c.o 文件,用于调试验证。

总结

这个 Makefile 利用了 变量 + 通配符 + 模式规则,实现了自动化、多文件可扩展的编译流程,可以认真学习学习。


如果本文对您有启发:

点赞 - 让更多人看到这篇硬核技术解析 !

收藏 - 实战代码随时复现

关注 - 获取Linux系列深度更新
您的每一个[三连]都是我们持续创作的动力!✨

相关推荐
XH1.2 小时前
学习RT-thread(项目一:基于RT-thread的multi_button控制灯闪烁)
stm32·单片机·学习
wei_shuo2 小时前
全场景自动化 Replay 技术:金仓 KReplay 如何攻克数据库迁移 “难验证“ 难题
数据库·自动化·king base
fy zs2 小时前
linux文件系统和软硬连接
linux·centos
QT 小鲜肉3 小时前
【个人成长笔记】将Try Ubuntu里面配置好的文件系统克隆在U盘上(创建一个带有持久化功能的Ubuntu Live USB系统)
linux·开发语言·数据库·笔记·ubuntu
Wu Liuqi3 小时前
【大模型学习4】大语言模型(LLM)详解
人工智能·学习·语言模型·大模型
CarmenHu3 小时前
RAFT微调学习笔记
笔记·学习
QiZhang | UESTC3 小时前
JAVA算法练习题day67
java·python·学习·算法·leetcode
d111111111d4 小时前
STM32外设学习--ADC模数转换器--笔记
笔记·stm32·单片机·嵌入式硬件·学习
AC是你的谎言4 小时前
网络层和数据链路层
linux·网络·学习·智能路由器