2018-09-11 09:23:42 +08:00
|
|
|
|
Makefile 及其工作原理
|
2018-09-10 11:41:09 +08:00
|
|
|
|
======
|
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
> 用这个方便的工具来更有效的运行和编译你的程序。
|
|
|
|
|
|
2018-09-10 11:41:09 +08:00
|
|
|
|
![](https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/osdc_liberate%20docs_1109ay.png?itok=xQOLreya)
|
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
当你需要在一些源文件改变后运行或更新一个任务时,通常会用到 `make` 工具。`make` 工具需要读取一个 `Makefile`(或 `makefile`)文件,在该文件中定义了一系列需要执行的任务。你可以使用 `make` 来将源代码编译为可执行程序。大部分开源项目会使用 `make` 来实现最终的二进制文件的编译,然后使用 `make install` 命令来执行安装。
|
|
|
|
|
|
|
|
|
|
本文将通过一些基础和进阶的示例来展示 `make` 和 `Makefile` 的使用方法。在开始前,请确保你的系统中安装了 `make`。
|
2018-09-10 11:41:09 +08:00
|
|
|
|
|
|
|
|
|
### 基础示例
|
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
依然从打印 “Hello World” 开始。首先创建一个名字为 `myproject` 的目录,目录下新建 `Makefile` 文件,文件内容为:
|
|
|
|
|
|
2018-09-10 11:41:09 +08:00
|
|
|
|
```
|
|
|
|
|
say_hello:
|
|
|
|
|
echo "Hello World"
|
|
|
|
|
```
|
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
在 `myproject` 目录下执行 `make`,会有如下输出:
|
|
|
|
|
|
2018-09-10 11:41:09 +08:00
|
|
|
|
```
|
|
|
|
|
$ make
|
|
|
|
|
echo "Hello World"
|
|
|
|
|
Hello World
|
|
|
|
|
```
|
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
在上面的例子中,“say_hello” 类似于其他编程语言中的函数名。这被称之为<ruby>目标<rt>target</rt></ruby>。在该目标之后的是预置条件或依赖。为了简单起见,我们在这个示例中没有定义预置条件。`echo ‘Hello World'` 命令被称为<ruby>步骤<rt>recipe</rt></ruby>。这些步骤基于预置条件来实现目标。目标、预置条件和步骤共同构成一个规则。
|
2018-09-10 11:41:09 +08:00
|
|
|
|
|
|
|
|
|
总结一下,一个典型的规则的语法为:
|
|
|
|
|
|
|
|
|
|
```
|
2018-09-11 09:23:42 +08:00
|
|
|
|
目标: 预置条件
|
|
|
|
|
<TAB> 步骤
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
作为示例,目标可以是一个基于预置条件(源代码)的二进制文件。另一方面,预置条件也可以是依赖其他预置条件的目标。
|
2018-09-10 11:41:09 +08:00
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
final_target: sub_target final_target.c
|
|
|
|
|
Recipe_to_create_final_target
|
2018-09-11 09:23:42 +08:00
|
|
|
|
|
2018-09-10 11:41:09 +08:00
|
|
|
|
sub_target: sub_target.c
|
|
|
|
|
Recipe_to_create_sub_target
|
|
|
|
|
```
|
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
目标并不要求是一个文件,也可以只是步骤的名字,就如我们的例子中一样。我们称之为“伪目标”。
|
2018-09-10 11:41:09 +08:00
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
再回到上面的示例中,当 `make` 被执行时,整条指令 `echo "Hello World"` 都被显示出来,之后才是真正的执行结果。如果不希望指令本身被打印处理,需要在 `echo` 前添加 `@`。
|
2018-09-14 12:50:11 +08:00
|
|
|
|
|
2018-09-10 11:41:09 +08:00
|
|
|
|
```
|
|
|
|
|
say_hello:
|
|
|
|
|
@echo "Hello World"
|
|
|
|
|
```
|
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
重新运行 `make`,将会只有如下输出:
|
|
|
|
|
|
2018-09-10 11:41:09 +08:00
|
|
|
|
```
|
|
|
|
|
$ make
|
|
|
|
|
Hello World
|
|
|
|
|
```
|
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
接下来在 `Makefile` 中添加如下伪目标:`generate` 和 `clean`:
|
|
|
|
|
|
2018-09-10 11:41:09 +08:00
|
|
|
|
```
|
|
|
|
|
say_hello:
|
|
|
|
|
@echo "Hello World"
|
|
|
|
|
|
|
|
|
|
generate:
|
|
|
|
|
@echo "Creating empty text files..."
|
|
|
|
|
touch file-{1..10}.txt
|
|
|
|
|
|
|
|
|
|
clean:
|
|
|
|
|
@echo "Cleaning up..."
|
|
|
|
|
rm *.txt
|
|
|
|
|
```
|
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
随后当我们运行 `make` 时,只有 `say_hello` 这个目标被执行。这是因为`Makefile` 中的第一个目标为默认目标。通常情况下会调用默认目标,这就是你在大多数项目中看到 `all` 作为第一个目标而出现。`all` 负责来调用它他的目标。我们可以通过 `.DEFAULT_GOAL` 这个特殊的伪目标来覆盖掉默认的行为。
|
|
|
|
|
|
|
|
|
|
在 `Makefile` 文件开头增加 `.DEFAULT_GOAL`:
|
2018-09-10 11:41:09 +08:00
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
.DEFAULT_GOAL := generate
|
|
|
|
|
```
|
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
`make` 会将 `generate` 作为默认目标:
|
|
|
|
|
|
2018-09-10 11:41:09 +08:00
|
|
|
|
```
|
|
|
|
|
$ make
|
|
|
|
|
Creating empty text files...
|
|
|
|
|
touch file-{1..10}.txt
|
|
|
|
|
```
|
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
顾名思义,`.DEFAULT_GOAL` 伪目标仅能定义一个目标。这就是为什么很多 `Makefile` 会包括 `all` 这个目标,这样可以调用多个目标。
|
|
|
|
|
|
|
|
|
|
下面删除掉 `.DEFAULT_GOAL`,增加 `all` 目标:
|
2018-09-10 11:41:09 +08:00
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
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
|
|
|
|
|
```
|
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
运行之前,我们再增加一些特殊的伪目标。`.PHONY` 用来定义这些不是文件的目标。`make` 会默认调用这些伪目标下的步骤,而不去检查文件名是否存在或最后修改日期。完整的 `Makefile` 如下:
|
|
|
|
|
|
2018-09-10 11:41:09 +08:00
|
|
|
|
```
|
|
|
|
|
.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
|
|
|
|
|
```
|
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
`make` 命令会调用 `say_hello` 和 `generate`:
|
|
|
|
|
|
2018-09-10 11:41:09 +08:00
|
|
|
|
```
|
|
|
|
|
$ make
|
|
|
|
|
Hello World
|
|
|
|
|
Creating empty text files...
|
|
|
|
|
touch file-{1..10}.txt
|
|
|
|
|
```
|
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
`clean` 不应该被放入 `all` 中,或者被放入第一个目标中。`clean` 应当在需要清理时手动调用,调用方法为 `make clean`。
|
|
|
|
|
|
2018-09-10 11:41:09 +08:00
|
|
|
|
```
|
|
|
|
|
$ make clean
|
|
|
|
|
Cleaning up...
|
|
|
|
|
rm *.txt
|
|
|
|
|
```
|
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
现在你应该已经对 `Makefile` 有了基础的了解,接下来我们看一些进阶的示例。
|
2018-09-10 11:41:09 +08:00
|
|
|
|
|
|
|
|
|
### 进阶示例
|
|
|
|
|
|
|
|
|
|
#### 变量
|
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
在之前的实例中,大部分目标和预置条件是已经固定了的,但在实际项目中,它们通常用变量和模式来代替。
|
|
|
|
|
|
|
|
|
|
定义变量最简单的方式是使用 `=` 操作符。例如,将命令 `gcc` 赋值给变量 `CC`:
|
2018-09-10 11:41:09 +08:00
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
CC = gcc
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
这被称为递归扩展变量,用于如下所示的规则中:
|
2018-09-11 09:23:42 +08:00
|
|
|
|
|
2018-09-10 11:41:09 +08:00
|
|
|
|
```
|
|
|
|
|
hello: hello.c
|
|
|
|
|
${CC} hello.c -o hello
|
|
|
|
|
```
|
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
你可能已经想到了,这些步骤将会在传递给终端时展开为:
|
|
|
|
|
|
2018-09-10 11:41:09 +08:00
|
|
|
|
```
|
|
|
|
|
gcc hello.c -o hello
|
|
|
|
|
```
|
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
`${CC}` 和 `$(CC)` 都能对 `gcc` 进行引用。但如果一个变量尝试将它本身赋值给自己,将会造成死循环。让我们验证一下:
|
|
|
|
|
|
2018-09-10 11:41:09 +08:00
|
|
|
|
```
|
|
|
|
|
CC = gcc
|
|
|
|
|
CC = ${CC}
|
|
|
|
|
|
|
|
|
|
all:
|
|
|
|
|
@echo ${CC}
|
|
|
|
|
```
|
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
此时运行 `make` 会导致:
|
|
|
|
|
|
2018-09-10 11:41:09 +08:00
|
|
|
|
```
|
|
|
|
|
$ make
|
|
|
|
|
Makefile:8: *** Recursive variable 'CC' references itself (eventually). Stop.
|
|
|
|
|
```
|
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
为了避免这种情况发生,可以使用 `:=` 操作符(这被称为简单扩展变量)。以下代码不会造成上述问题:
|
|
|
|
|
|
2018-09-10 11:41:09 +08:00
|
|
|
|
```
|
|
|
|
|
CC := gcc
|
|
|
|
|
CC := ${CC}
|
|
|
|
|
|
|
|
|
|
all:
|
|
|
|
|
@echo ${CC}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 模式和函数
|
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
下面的 `Makefile` 使用了变量、模式和函数来实现所有 C 代码的编译。我们来逐行分析下:
|
|
|
|
|
|
2018-09-10 11:41:09 +08:00
|
|
|
|
```
|
|
|
|
|
# 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}
|
|
|
|
|
```
|
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
* 以 `#` 开头的行是评论。
|
|
|
|
|
* `.PHONY = all clean` 行定义了 `all` 和 `clean` 两个伪目标。
|
|
|
|
|
* 变量 `LINKERFLAG` 定义了在步骤中 `gcc` 命令需要用到的参数。
|
|
|
|
|
* `SRCS := $(wildcard *.c)`:`$(wildcard pattern)` 是与文件名相关的一个函数。在本示例中,所有 “.c”后缀的文件会被存入 `SRCS` 变量。
|
|
|
|
|
* `BINS := $(SRCS:%.c=%)`:这被称为替代引用。本例中,如果 `SRCS` 的值为 `'foo.c bar.c'`,则 `BINS`的值为 `'foo bar'`。
|
|
|
|
|
* `all: ${BINS}` 行:伪目标 `all` 调用 `${BINS}` 变量中的所有值作为子目标。
|
|
|
|
|
* 规则:
|
2018-09-10 11:41:09 +08:00
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
```
|
2018-09-10 11:41:09 +08:00
|
|
|
|
%: %.o
|
|
|
|
|
@echo "Checking.."
|
|
|
|
|
${CC} ${LINKERFLAG} $< -o $@
|
|
|
|
|
```
|
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
下面通过一个示例来理解这条规则。假定 `foo` 是变量 `${BINS}` 中的一个值。`%` 会匹配到 `foo`(`%`匹配任意一个目标)。下面是规则展开后的内容:
|
|
|
|
|
|
|
|
|
|
```
|
2018-09-10 11:41:09 +08:00
|
|
|
|
foo: foo.o
|
|
|
|
|
@echo "Checking.."
|
|
|
|
|
gcc -lm foo.o -o foo
|
|
|
|
|
```
|
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
如上所示,`%` 被 `foo` 替换掉了。`$<` 被 `foo.o` 替换掉。`$<`用于匹配预置条件,`$@` 匹配目标。对 `${BINS}` 中的每个值,这条规则都会被调用一遍。
|
|
|
|
|
* 规则:
|
2018-09-10 11:41:09 +08:00
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
```
|
2018-09-10 11:41:09 +08:00
|
|
|
|
%.o: %.c
|
|
|
|
|
@echo "Creating object.."
|
|
|
|
|
${CC} -c $<
|
|
|
|
|
```
|
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
之前规则中的每个预置条件在这条规则中都会都被作为一个目标。下面是展开后的内容:
|
|
|
|
|
|
|
|
|
|
```
|
2018-09-10 11:41:09 +08:00
|
|
|
|
foo.o: foo.c
|
|
|
|
|
@echo "Creating object.."
|
|
|
|
|
gcc -c foo.c
|
|
|
|
|
```
|
2018-09-11 09:23:42 +08:00
|
|
|
|
* 最后,在 `clean` 目标中,所有的二进制文件和编译文件将被删除。
|
2018-09-10 11:41:09 +08:00
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
下面是重写后的 `Makefile`,该文件应该被放置在一个有 `foo.c` 文件的目录下:
|
2018-09-10 11:41:09 +08:00
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
# Usage:
|
|
|
|
|
# make # compile all binary
|
|
|
|
|
# make clean # remove ALL binaries and objects
|
|
|
|
|
|
|
|
|
|
.PHONY = all clean
|
|
|
|
|
|
|
|
|
|
CC = gcc # compiler to use
|
|
|
|
|
|
|
|
|
|
LINKERFLAG = -lm
|
|
|
|
|
|
|
|
|
|
SRCS := foo.c
|
|
|
|
|
BINS := foo
|
|
|
|
|
|
|
|
|
|
all: foo
|
|
|
|
|
|
|
|
|
|
foo: foo.o
|
|
|
|
|
@echo "Checking.."
|
|
|
|
|
gcc -lm foo.o -o foo
|
|
|
|
|
|
|
|
|
|
foo.o: foo.c
|
|
|
|
|
@echo "Creating object.."
|
|
|
|
|
gcc -c foo.c
|
|
|
|
|
|
|
|
|
|
clean:
|
|
|
|
|
@echo "Cleaning up..."
|
|
|
|
|
rm -rvf foo.o foo
|
|
|
|
|
```
|
|
|
|
|
|
2018-09-11 09:23:42 +08:00
|
|
|
|
关于 `Makefile` 的更多信息,[GNU Make 手册][1]提供了更完整的说明和实例。
|
2018-09-10 11:41:09 +08:00
|
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
via: https://opensource.com/article/18/8/what-how-makefile
|
|
|
|
|
|
|
|
|
|
作者:[Sachin Patil][a]
|
|
|
|
|
选题:[lujun9972](https://github.com/lujun9972)
|
|
|
|
|
译者:[Zafiry](https://github.com/zafiry)
|
2018-09-11 09:23:42 +08:00
|
|
|
|
校对:[wxy](https://github.com/wxy)
|
2018-09-10 11:41:09 +08:00
|
|
|
|
|
|
|
|
|
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
|
|
|
|
|
|
|
|
|
[a]:https://opensource.com/users/psachin
|
|
|
|
|
[1]:https://www.gnu.org/software/make/manual/make.pdf
|