Linux里Makefile是什么?它是如何工作的?

2019-10-09 15:16:53王振洲

在 Makefile 文件开头增加 .DEFAULT_GOAL:

.DEFAULT_GOAL := generate

make 会将 generate 作为默认目标:

$ make
Creating empty text files...
touch file-{1..10}.txt

顾名思义,.DEFAULT_GOAL 伪目标仅能定义一个目标。这就是为什么很多 Makefile 会包括 all 这个目标,这样可以调用多个目标。
下面删除掉 .DEFAULT_GOAL,增加 all 目标:

all: say_hello generate
say_hello:
@echo "Hello World"
generate:
@echo "Creating empty text files..."
touch file-{1..10}.txt
clean:
@echo "Cleaning up..."
rm *.txt

运行之前,我们再增加一些特殊的伪目标。.PHONY 用来定义这些不是文件的目标。make 会默认调用这些伪目标下的步骤,而不去检查文件名是否存在或最后修改日期。完整的 Makefile 如下:

.PHONY: all say_hello generate clean
all: say_hello generate
say_hello:
@echo "Hello World"
generate:
@echo "Creating empty text files..."
touch file-{1..10}.txt
clean:
@echo "Cleaning up..."
rm *.txt

make 命令会调用 say_hello 和 generate:

$ make
Hello World
Creating empty text files...
touch file-{1..10}.txt

clean 不应该被放入 all 中,或者被放入第一个目标中。clean 应当在需要清理时手动调用,调用方法为 make clean

$ make clean
Cleaning up...
rm *.txt

现在你应该已经对 Makefile 有了基础的了解,接下来我们看一些进阶的示例。

进阶示例
变量
在之前的实例中,大部分目标和预置条件是已经固定了的,但在实际项目中,它们通常用变量和模式来代替。

定义变量最简单的方式是使用 = 操作符。例如,将命令 gcc 赋值给变量 CC:

CC = gcc

这被称为递归扩展变量,用于如下所示的规则中:

hello: hello.c
${CC} hello.c -o hello

你可能已经想到了,这些步骤将会在传递给终端时展开为:

gcc hello.c -o hello

${CC} 和 $(CC) 都能对 gcc 进行引用。但如果一个变量尝试将它本身赋值给自己,将会造成死循环。让我们验证一下:

CC = gcc
CC = ${CC}
all:
@echo ${CC}

此时运行 make 会导致:

$ make
Makefile:8: *** Recursive variable 'CC' references itself (eventually). Stop.

为了避免这种情况发生,可以使用 := 操作符(这被称为简单扩展变量)。以下代码不会造成上述问题:

CC := gcc
CC := ${CC}
all:
@echo ${CC}

模式和函数
下面的 Makefile 使用了变量、模式和函数来实现所有 C 代码的编译。我们来逐行分析下:

# Usage:
# make # compile all binary
# make clean # remove ALL binaries and objects
.PHONY = all clean
CC = gcc # compiler to use
LINKERFLAG = -lm
SRCS := $(wildcard *.c)
BINS := $(SRCS:%.c=%)
all: ${BINS}
%: %.o
@echo "Checking.."
${CC} ${LINKERFLAG} $< -o $@
%.o: %.c
@echo "Creating object.."
${CC} -c $<
clean:
@echo "Cleaning up..."
rm -rvf *.o ${BINS}