Makefile:一个简单的入门
本文最后更新于:2022年10月13日 下午
为什么使用Makefile
Makefile可以使构建更方便、减少不必要的构建
想象以下情景,目标文件main的依赖关系如下:
那么可以使用如下命令行进行编译:
1 |
|
那么会出现以下状况:
- 即使是仅仅改变了某一个文件的一行代码,那么所有的.o文件都需要重新生成,然后通过链接得到目标文件,这个开销是巨大的
- 一个极端情景:一个大型的软件项目往往由上千个源文件组成,全部编译一遍需要几个小时
- 如果有多个.o文件,我们修改了某一个文件(比如.h或者.c文件),那么会影响多个.o文件,因此我们需要重新编译这些.o文件,那么管理他们的依赖是十分麻烦的
Makefile应运而生
Makefile格式
在目录下必须有一个Makefile文件,该文件名大小写敏感,里面内容类似下面:
1 |
|
其中:
- target是目标文件
- prerequisites是前置条件
- 如果要更新目标,那么需要更新所有前置条件
- target和prerequisites可以有多个,用空格分开
- command前面必须是tab,这些command将会被shell执行
- #是注释
一个简单的Makefile程序:
1 |
|
在下面的情况下,执行make会更新目标文件:
- 当前目录没有目标文件
- 前置条件需要更新
- 前置条件修改时间晚于目标文件(目标文件依赖于前置条件,而前置条件被修改了,那么目标文件需要更新)
在编译过程中,经常有一些过程文件,比如二进制文件,那么我们可以清除这些二进制文件,保留源文件,以便于下次make
make规则的一个示例:
1 |
|
- @表示这行语句不被输出,如果不加@那么会输出这行语句本身
- -表示这行语句的执行错误也要忽略。一般rm命令会加-,因为要删除的文件或许不存在
至此我们发现Makefile中已经有了很多目标文件了,比如main和clean,那么如果我们要指定构建target,那么只需要在命令行中输入make target即可;假设没有指定,那么会默认执行Makefile文件的第一个目标
如果文件夹中存在clean这个文件名,那么再次执行make clean并不会执行,因为它认为该文件已存在。为了防止这种情况发生,可以通过.PHONY声明clean是”伪目标”
1 |
|
Makefile中约定俗成的名字:
all,执行主要的编译工作,通常用作缺省目标。
install,执行编译后的安装工作,把可执行文件、配置文件、文档等分别拷到不同的安装目录。
clean,删除编译生成的二进制文件。
distclean,不仅删除编译生成的二进制文件,也删除其它生成的文件,例如配置文件和格式转换后的文档,执行make distclean之后应该清除所有这些文件,只留下源文件。
变量与规则
1 |
|
- 最终会打印出Huh?
- 这种赋值方式,在make遇到变量定义时不会立即展开
- 如果想要立即展开,可以使用
a:=b
的形式 - +=可以追加
Makefile中有一些特殊变量,比如:
- $@表示规则中的目标文件(target)
- $<表示规则中第一个prerequisites
- $?表示规则中所有比目标文件新的prerequisites
- $^规则中的所有prerequisites
使用这些变量,可以对Makefile进行改写
版本1:
1 |
|
版本2:
1 |
|
常见变量:
1 |
|
make -p查看Makefile的隐藏规则
依赖关系
gcc -M main.c
可以列出目标文件和源文件的依赖关系,包括系统头文件gcc -MM *.c
在上面的基础上不列出系统头文件
一个内核驱动的Makefile实例
- -j $(nrpoc) :指定处理器数量
- -C $(LINUX_KERNAL_PATH) :在内核源码路径执行Makefile
- M=$(CURRENT_PATH) modules :以内核为基础编译模块的时候,指定目录查找模块源码
- all:make的缺省目标
- @表示这行语句不被输出,如果不加@那么会输出这行语句本身
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25TARGET=rootkit
OBJ=$(TARGET).o
MODULE=$(TARGET).ko
obj-m+=$(OBJ)
EXTRA_CFLAGS+=-g -O0 # 编译参数
CURRENT_PATH:=$(shell pwd) #当前路径
LINUX_KERNAL:=$(shell uname -r) #内核版本号
LINUX_KERNAL_PATH:=/lib/modules/$(LINUX_KERNAL)/build
all:rootkit
rootkit:
make -j $(nrpoc)-C $(LINUX_KERNAL_PATH) M=$(CURRENT_PATH) modules
install:
# 安装模块
@sudo insmod $(CURRENT_PATH)/$(MODULE)
# 卸载模块
@sudo rmmod $(CURRENT_PATH)/$(MODULE)
clean:
make -C $(LINUX_KERNAL_PATH) M=$(CURRENT_PATH) clean
.PHONY:all install clean rootkit