5 min read

Makefile指南

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.cb.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类似,比如

  1. 定义编译的文件
object = a.o b.o
c: $(object)
	gcc -o c $(object)
  1. 定义命令
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>

  1. 是否相等
ifeq (<arg1>, <arg2>)
  1. 是否不相等
ifneq (<arg1>, <arg2>)
  1. 是否为空
ifdef arg1
  1. 是否不为空
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