Makefile指南
0、前言
内核编译以及工具开发过程中,经常会涉及到Makefile文件,这一次详细来看一下Makefile相关的内容。
官方手册:Learn Makefiles
很好的学习文章:跟我一起写Makefile
1、入门
1.1-Makefile的作用
编译一个最简单的.c文件通常会使用到
gcc main.c
但是在大型复杂项目在构建的时候,需要的依赖文件比较多且复杂,这个时候只用gcc简单的进行编译已经无法实现了。另外Makefile还会用于帮助决定大型程序的哪些部分需要重新编译,这样只有一个模块文件修改时,就没必要重编整个工程。
而Makefile依赖的工具则是make,他会寻找当前路径下的Makefile文件,并执行相应的命令。Makefile则告诉make命令需要怎么样的去编译和链接程序。
1.2-Makefile的基础规则
一个Makefile的最基础组成如下
target ... : prerequisites ...
recipe
...
...
这其中:
- target:可以是一个object file(目标文件),也可以是一个可执行文件,还可以是一个标签(label)。
- prerequisites:生成该target所依赖的文件,可以理解为原材料。
- recipe:生成这个target要执行的命令(任意的shell命令)。
如果原材料中有代码变更的话,就会执行相应的的命令,重新构建目标。
1.3-例子
c: a.o b.o
gcc -o c a.o b.o
a.o:a.c d.h
gcc a.c
b.o: b.c d.h
gcc b.c
上边的例子可以看到,我们会最终编译出一个c的二进制文件,而c是由a.c与b.c两个c文件组成的,因此需要将他们编译出的.o文件进行链接,而这两个.o文件还是与d.h有关,所有当时d.h有变化时,则会重编整个工程。
2、规则
例如我们之前的举例,最基础的规则如下
foo.o: foo.c defs.h # foo模块
cc -c -g foo.c
在规则中,使用\进行换行,使用*进行通配。
2.1-伪目标
如果目标后没有依赖文件,则相当于一个lable
clean :
rm *.o
在执行make clean后则会执行对应的命令,也就是清理所有的.o文件。
另外也可以使用下列方式指定特定目标为伪目标
.PHONY : clean
clean :
rm *.o
2.2-多目标
makefile中可以多个目标依赖一样的文件,下列这两个种方式是等价的
a b : c.o d.o
...
a : c.o d.o
b : c.o d.o
2.3-自动生成依赖
在Makefile中,我们可以利用GCC的-M选项生成依赖关系,然后通过-include指令将这些依赖关系包含到Makefile中。这样,当源文件或头文件发生变化时,Makefile会自动重新编译相应的目标文件。例如
# 定义变量
CC = gcc
CFLAGS = -Wall -g
SRCS = $(wildcard *.c)
OBJS = $(patsubst %.c,%.o,$(SRCS))
DEPS = $(patsubst %.c,%.d,$(SRCS))
TARGET = my_program
# 默认目标
all: $(TARGET)
# 链接目标文件,生成可执行文件
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $(TARGET) $(OBJS)
# 编译源文件,生成目标文件和依赖文件
%.o: %.c
$(CC) $(CFLAGS) -MMD -c $*.c -o $*.o
# 包含自动生成的依赖文件
-include $(DEPS)
3、变量
3.1-基础变量
在Makefile中,变量的语法与shell类似,比如
- 定义编译的文件
object = a.o b.o
c: $(object)
gcc -o c $(object)
- 定义命令
cc = gcc
c: a.o b.o
$(cc) -o c a.o b.o
3.2-追加变量
可以使用+=的方式追加变量
object = a.o b.o
object += c.o
这个时候object = a.o b.o c.o。
3.3-传参
如果Makefile中没有定义变量的话,可以通过make DESTDIR=xxx的时候传入变量,
install -d $(DESTDIR)/usr/local/sbin/
类似上述命令组中会安装在xxx/usr/local/sbin/上。
3.4-替换
下列操作中,会把变量 $(sources) 所有 .c 的字串都替换成 .d
sources = foo.c bar.c
include $(sources:.c=.d)
3.5-自动变量
$@:表示当前规则的目标文件。在命令行中,我们可以用$@引用目标文件,而无需显式地指定目标文件名。
$^:表示当前规则的所有依赖文件,以空格分隔。在命令行中,我们可以用$^引用所有依赖文件,而无需显式地指定它们。
$<:表示当前规则的第一个依赖文件。在命令行中,我们可以用$<引用第一个依赖文件,而无需显式地指定它。
4、if判断
最基础的语法是
<conditional-directive>
<text-if-true>
else
<text-if-false>
endif
Makefile中有四种if语法及<conditional-directive>
- 是否相等
ifeq (<arg1>, <arg2>)
- 是否不相等
ifneq (<arg1>, <arg2>)
- 是否为空
ifdef arg1
- 是否不为空
ifndef arg1
上述的参数可以是变量,也可以是字符串,类似于判断 $(CC) 变量是否 gcc
ifeq ($(CC),gcc)
5、命令
Makefile中可以直接执行bash命令,但是命令必须以tab开头。
5.1-显示命令
makefile可以通过@来屏蔽输出命令本身。比如makefie中存在这样一个命令
test:
echo 123
@echo 456
运行这个脚本的效果如下
echo 123
123
456
另外,也可以通过make -s来屏蔽命令输出,用make -n只输出命令,不执行命令(用于调试)。
5.2-忽略报错
在makefile中,如果命令执行失败,则会退出makefile,不继续执行,而-命令则可以忽略报错继续执行。
test:
abc
echo 123
这时
# make test
abc
make: abc: Command not found
make: *** [Makefile:2: test] Error 127
而添加了-
test:
-abc
echo 123
则会
Para escoger camisetas de fútbol según el ajuste, es práctico verificar el espacio necesario para llevar otra prenda debajo. Para evitar errores, merece la pena revisar las condiciones de cambio cuando el ajuste no sea el esperado.
# make test
abc
make: abc: Command not found
make: [Makefile:2: test] Error 127 (ignored)
echo 123
123