修改自:https://www.toutiao.com/a6599850731023368707/
当你需要在一些源文件改变后运行或更新一个任务时,通常会用到 make 工具。make 工具需要读取一个名为 Makefile(或 makefile)的文件,该文件中定义了一系列需要执行的任务。你可以使用 make 来将源代码编译为可执行程序。大部分开源项目会使用 make 来实现最终的二进制文件的编译,然后使用 make install 命令来执行安装。
本文将通过一些基础和进阶的示例来展示 make 和 Makefile 的使用方法。在开始前,请确保你的系统中安装了 make。
依然从打印 “Hello World” 开始。首先创建一个名字为 myproject 的目录,在目录下新建 Makefile 文件,文件内容为:
x
say_hello:
echo "Hello World"
在 myproject 目录下执行 make,会有如下输出:
xxxxxxxxxx
echo "Hello World"
Hello World
在上面的例子中,“say_hello” 类似于其它编程语言中的函数名,在这里被称为目标(target)。在该目标之后的是预置条件或依赖。为了简单起见,我们在这个示例中没有定义预置条件。echo "Hello World" 命令被称为步骤(recipe),它基于预置条件来实现目标。目标、预置条件和步骤共同构成一个规则。
总结一下,一个典型的规则的语法为:
xxxxxxxxxx
目标: 预置条件
<TAB> 步骤
预置条件可以是文件,也可以是其它目标:
xxxxxxxxxx
final_target: sub_target final_target.c
Recipe_to_create_final_target
sub_target: sub_target.c
Recipe_to_create_sub_target
目标可以是文件,也可以是步骤的名字(称之为伪目标)。
再回到上面的示例中,当 make 被执行时,指令 echo "Hello World" 也被打印出来了,之后才是真正的执行结果。如果不希望指令本身被打印,需要在 echo 前添加 @。
say_hello:
@echo "Hello World"
接下来在 Makefile 中添加如下伪目标:generate 和 clean:
xxxxxxxxxx
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 这个目标被执行。这是因为 Makefile 中的第一个目标是默认目标。我们可以通过 .DEFAULT_GOAL 这个特殊的伪目标来覆盖掉该默认行为。
在 Makefile 文件的开头增加 .DEFAULT_GOAL:
xxxxxxxxxx
.DEFAULT_GOAL := generate
make 会将 generate 作为默认目标:
xxxxxxxxxx
# make
Creating empty text files...
touch file-{1..10}.txt
下面删除掉 .DEFAULT_GOAL,增加 all 目标:
xxxxxxxxxx
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 如下:
xxxxxxxxxx
.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:
xxxxxxxxxx
# make
Hello World
Creating empty text files...
touch file-{1..10}.txt
clean 不应该被放到 all 中,或者被放到第一个目标中。clean 应当在需要清理时手动调用,调用方法为 make clean:
# make clean
Cleaning up...
rm *.txt
现在你应该已经对 Makefile 有了基础的了解,接下来我们看一些进阶的示例。
Makefile 中的变量只能是字符串类型。Makefile 中的变量有四种赋值方式:
简单赋值(:= ):只对当前语句中的变量有效
比如:
xxxxxxxxxx
.PHONY: test
x := foo
y := $(x)b
x := new
test:
@echo "x = $(x)"
@echo "y = $(y)"
输出:
xxxxxxxxxx
# make
x = new
y = foob
递归赋值(=):所有与目标变量相关的变量都受影响
比如:
xxxxxxxxxx
.PHONY: test
x = foo
y = $(x)b
x = new
test:
@echo "x = $(x)"
@echo "y = $(y)"
输出:
xxxxxxxxxx
# make
x = new
y = newb
条件赋值(?=):如果变量未定义,则定义它;否则该语句无效
追加赋值(+=):原变量用空格隔开的方式追加一个新值
.PHONY: test
x := foo
x += bar
test:
@echo "x = $(x)"
输出:
# make
x = foo bar
使用 ${变量名} 和 $(变量名) 都可以对变量进行引用。
下面的 Makefile 使用变量、模式和函数来实现所有 C 代码的编译,我们来逐行分析下:
x
# 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}
以 # 开始的行是注释
.PHONY = all clean 用于定义 all 和 clean 两个伪目标
变量 LINKERFLAG 用于定义在步骤中 gcc 命令需要用到的参数
$(wildcard PATTERN ...) 会被展开为已经存在的、用空格分开的、匹配模式的所有文件的列表。在本示例中,所有扩展名为 “.c” 的文件都会被存入 SRCS 变量
BINS := $(SRCS:%.c=%) 被称为替代引用。本例中,如果 SRCS 的值为 'foo.c bar.c',则 BINS 的值为 'foo bar'
规则:
x
%: %.o
@echo "Checking.."
${CC} ${LINKERFLAG} $< -o $@
其中:
假定 foo 是 ${BINS} 中的一个值,那么该规则会被展开为:
xxxxxxxxxx
foo: foo.o
@echo "Checking.."
gcc -lm foo.o -o foo
对于 ${BINS} 中的每个值,这条规则都会被调用一遍。
关于 Makefile 的更多信息, GNU Make 手册提供了更完整的说明和实例。