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
则会
# make test
abc
make: abc: Command not found
make: [Makefile:2: test] Error 127 (ignored)
echo 123
123