前言

驱动、内核等大型工程包含众多 .c .h 文件,如果手动一个个去编译这些文件是不现实的,通常的做法是使用 make 命令进行自动化编译。make 命令执行时,需要 makefile 文件,以告诉 make 命令需要怎样去编译和链接程序。

不过这些大型工程的 makefile 文件往往不止一个,且常常分散在各个层级。如果编译时报错,调试起来就比较麻烦了。联想到 C 语言 Debug 的方法,通常是在出现问题的语句前面添加打印,来判断变量的值是否符合预期,语句是否被执行等等。那么在 Makefile 中有没有类似的 Debug 方法呢?

控制 make 的函数

答案是有的,那就是控制 make 的函数(Functions That Control Make)。

make 提供了一些函数,可以检测 makefile 的运行时信息,来控制 make 的运行。

  1. info 函数
$(info <text ..>)

功能:打印信息

  1. error 函数
$(error <text ...>)

功能:产生一个致命的错误,终止 make 运行,<text …> 是错误信息。

  1. warning 函数
$(warning <text ...>)

功能:这个函数很像 error 函数,只是它并不会让 make 推出,只是输出一段警告信息,而 make 继续执行。

案例分析

期望:config.mk 作为 Makefile 的配置文件,ARM = y 表明想编译出 ARM 架构程序,即使用 arm-linux-gcc 进行编译。但是执行后,make 并没有按照预期使用 arm-linux-gcc 进行编译,而是使用默认的 cc 进行编译。问题出在哪呢?

先看代码

$ tree
.
├── config.mk
├── hello.c
└── Makefile

config.mk

ARM = y 

Makefile

include config.mk

ifeq ($(ARM), y)
CC = arm-linux-gcc
endif

all:
	$(CC) hello.c

hello.c

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
	printf("hello world\n");

	return EXIT_SUCCESS;
}

执行 make

$ make
cc hello.c

看到执行结果,很显然 CC 没有被正确赋值为 arm-linux-gcc,难道 ifeq ($(ARM), y) 条件不成立?测试一下

include config.mk

ifeq ($(ARM), y)
111
CC = arm-linux-gcc
endif

all:
	$(CC) hello.c

在 ifeq ($(ARM), y) 下面加入错误语句 111,结果执行没有报错,说明 ifeq ($(ARM), y) 条件确实是不成立的。但是我已经包含了 config.mk 这个文件了啊,而且 config.mk 里面定义了变量 ARM,并且赋值了 y。为什么 ifeq ($(ARM), y) 条件就是不成立呢?百思不得其解!!

这时候就可以启用我们的调试方法了,先上 info 函数

include config.mk

$(info "ARM=$(ARM)")

ifeq ($(ARM), y)
111
CC = arm-linux-gcc
endif

all:
	$(CC) hello.c

执行

$ make
"ARM=y "
cc hello.c

原来是 y 后面多了一个空格,导致条件判断不通过。可以看到,使用 info 函数进行 make 打印调试,可以很快速地定位问题。

其实这里 ifeq ($(ARM), y) 的写法不好,更好的写法是 ifeq ($(strip $(ARM)), y),这样,即便在 y 前后有空格,strip 函数也会帮助我们去除,增强脚本的健壮性。

include config.mk

$(info "ARM=$(ARM)")

ifeq ($(strip $(ARM)), y)
CC = arm-linux-gcc
endif

all:
	$(CC) hello.c

运行

$ make
"ARM=y "
arm-linux-gcc hello.c

可以看到,就算 y 后面有空格,由于使用 strip 函数的原因,帮我们把空格去除了,ifeq ($(strip $(ARM)), y) 条件也就成立了,最终使用了 arm-linux-gcc 进行编译。

回过头来,这一切还是要归功于 info 函数给我们调试 make 带来的便利性,才能有后面我们的解决方案和优化方案。

除了 info 函数,我们还可以使用 warning 函数,它比 info 函数会打印更多的信息:文件名称和行号。

不过上述两个函数只能打印调试信息,并不能控制 make 的运行,我们在 make 大型工程时,可能 make 流程已经不符合我们的预设了,但是 make 还在继续,这是我们只能手动停止,然后向上滚动查找出错位置。其实我们可以有更好的方法,那就是使用 error 函数,可以让 make 停止在非预期分支,比如上面示例中,我们想要使用 arm-linux-gcc 进行编译,如果 CC 没能被正确赋值为 arm-linux-gcc,我们就让 make 报错停止下来,这样非常方面我们定位问题。

include config.mk

# $(info "ARM=$(ARM)")
# $(error "ARM=$(ARM)")
$(warning "ARM=$(ARM)")

ifeq ($(ARM), y)
CC = arm-linux-gcc
else
$(error "ARM=$(ARM)")
endif

all:
	$(CC) hello.c

运行

$ make
Makefile:5: "ARM=y "
Makefile:10: *** "ARM=y "。 停止。