You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

275 lines
9.0 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Makefile
原始内容来自 [rstacruz/cheatsheets](https://github.com/rstacruz/cheatsheets/blob/master/makefile.md) 有改动。
## 变量赋值
```makefile
foo = "bar" # foo 当前值为 bar允许后面进行会修改
bar = $(foo) foo # Makefile 展开,确定 foo 值之后才能决定最后 bar 的值
dum := $(foo) foo # dum值为 bar foo
foo := "boo" # 此时 foo 值被修改为 boo
foo ?= /usr/local # 如果 foo 没有赋值过,才会进行赋值
bar += world # 追加
foo != echo fooo # 执行shell 命令后foo 值为 fooo
# 因为 foo 最终值确定为 fooo所以最终 bar 值为fooo foo world。
```
`=` 用于创建一个可变的变量,后面可以重新赋值这个变量,最终值是整个 Makefile 最后指定的值。`:=`是赋予当前位置的值。`?=`是如果该变量没有被赋值,才被赋值为等号后面的值。
```makefile
x = foo
y = $(x) bar
z := $(x) bar
x = xyz
```
上面例子中x,y,z最终值分别为`xyz`,`xyz bar`, `foo bar`
## 魔术变量
```makefile
out.o: src.c src.h src.c
$@ # "out.o" (目标对象)
$< # "src.c" (第一个前置依赖)
$^ # "src.c src.h" (所有的前置依赖)
$+ # "src.c src.h src.c" (类似$^,不同于$^地方是它不会去重)
$? # 所有比目标对象新的依赖的集合
$(@D) # "." (目标对象中的目录部分,没有目录部分那么对应是.)
```
## 命令前缀
| 前缀 | 描述 |
| --- | --- |
| `-` | 忽略错误 |
| `@` | 不打印命令输出 |
| `+` | 忽略错误,但会打印错误信息 |
```makefile
build:
@echo "compiling"
-gcc $< $@
-include .depend
```
## 查找文件
```makefile
js_files := $(wildcard test/*.js) # 匹配所有test目录下的js文件
all_files := $(shell find images -name "*") # 查找images目录下所有文件
```
## 函数
```makefile
# 替换相关
file = $(SOURCE:.cpp=.o) # 替换 .cpp 为 .o
outputs = $(files:src/%.coffee=lib/%.js) # 替换 .coffee 为 .js
outputs = $(patsubst %.c, %.o, $(wildcard *.c)) # 替换 *.c 为 *.o
assets = $(patsubst images/%, assets/%, $(wildcard images/*)) # 替换 images/ 为 assets/
# 其他函数
$(strip $(string_var)) # 移除字符串两端的空格
$(filter %.less, $(files)) # 匹配所有以 .less 结尾的文件
$(filter-out %.less, $(files)) # 匹配所有不是以 .less 结尾的文件
$(subst ee,EE,feet on the street) # 替换 ee 为 EE
```
`patsubst <pattern>,<replacement>,<text>` 是将 text 中的所有匹配 pattern 的部分替换成 replacement。
## 多目标规则
Makefile的规则中的目标可以不止一个其支持多目标有可能我们的多个目标同时依赖于一个文件并且其生成的命令大体类似。于是我们就能把其合并起来。
```makefile
bigoutput littleoutput : text.g
generate text.g -$(subst output,,$@) > $@
```
其中, `-$(subst output,,$@)` 中的 $ 表示执行一个Makefile的函数函数名为subst后面的为参数。上述规则等价于
```makefile
bigoutput : text.g
generate text.g -big > bigoutput
littleoutput : text.g
generate text.g -little > littleoutput
```
### 静态模式
静态模式可以更加容易地定义多目标的规则。
```makefile
<targets ...> : <target-pattern> : <prereq-patterns ...>
<commands>
...
```
- targets定义了一系列的目标文件可以有通配符。是目标的一个集合。
- target-pattern是指明了targets的模式也就是的目标集模式。
- prereq-patterns是目标的依赖模式它对target-pattern形成的模式再进行一次依赖目标的定义。
```makefile
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
```
上面的例子中,指明了我们的目标从$object中获取 %.o 表明要所有以 .o 结尾的目标,也就是 foo.o bar.o ,也就是变量 $object 集合的模式,而依赖模式 %.c 则取模式 %.o 的 % ,也就是 foo bar ,并为其加下 .c 的后缀,于是,我们的依赖目标就是 foo.c bar.c 。而命令中的 $< 和 $@ 则是自动化变量, $< 表示第一个依赖文件, $@ 表示目标集也就是“foo.o bar.o”。于是上面的规则展开后等价于下面的规则
```makefile
foo.o : foo.c
$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
$(CC) -c $(CFLAGS) bar.c -o bar.o
```
再看一个例子:
```makefile
files = foo.elc bar.o lose.o
$(filter %.o,$(files)): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
emacs -f batch-byte-compile $<
```
`$(filter %.o,$(files))`表示调用Makefile的filter函数过滤“$files”集只要其中模式为“%.o”的内容。
## 隐含规则与模式规则
### 隐含规则
```makefile
foo : foo.o bar.o
cc o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)
```
这个Makefile中并没有写下如何生成 foo.o 和 bar.o 这两目标的规则和命令。因为make的“隐含规则”功能会自动为我们自动去推导这两个目标的依赖目标和生成命令。在上面的那个例子中make调用的隐含规则是把 .o 的目标的依赖文件置成 .c 并使用C的编译命令 `cc c $(CFLAGS) foo.c` 来生成 foo.o 的目标。也就是说,它等同于下面的两条规则:
```makefile
foo.o : foo.c
cc c foo.c $(CFLAGS)
bar.o : bar.c
cc c bar.c $(CFLAGS)
```
另外对于没有 Makefile 的时候,对于一个 foo.c 文件,我们可以 make foo 时,会自动运行`cc foo.c -o foo`。
### 老式风格的“后缀规则”
后缀规则是一个比较老式的定义隐含规则的方法。后缀规则会被模式规则逐步地取代。因为模式规则更强更清晰。为了和老版本的Makefile兼容GNU make同样兼容于这些东西。后缀规则有两种方式“双后缀”和“单后缀”。
双后缀规则定义了一对后缀:目标文件的后缀和依赖目标(源文件)的后缀。如 .c.o 相当于 %o : %c 。单后缀规则只定义一个后缀,也就是源文件的后缀。如 .c 相当于 % : %.c 。后缀规则不允许任何的依赖文件,如果有依赖文件的话,那就不是后缀规则,那些后缀统统被认为是文件名。
```makefile
.c.o:
$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<
```
### 模式规则
我们可以使用模式规则来定义一个隐含规则,模式规则中,至少在规则的目标定义中要包含 % ,否则,就是一般的规则。目标中的 % 定义表示对文件名的匹配。
```makefile
%.o: %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@ # 把所有的 .c 文件都编译成 .o 文件
```
## 条件判断
```makefile
foo: $(objects)
ifeq ($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(normal_libs)
endif
```
## 自动生成依赖性
在Makefile中我们的依赖关系可能会需要包含一系列的头文件我们可以借助编译器来生成依赖关系。
```bash
cc -M main.c # -M选项输出依赖关系
gcc -MM main.c # -MM 选项输出依赖关系,输出的依赖关系中会去掉标准库中的头文件
cc -MM src/chap8/udpserv01.c -I./src/include # 使用 -I 参数指定自定义头文件位置
```
## 包含其他 Makefile
```makefile
-include foo.make
```
make会在当前目录下首先寻找如果当前目录下没有找到那么make还会在下面的几个目录下找
1. 如果make执行时有 -I 或 --include-dir 参数那么make就会在这个参数所指定的目录下去寻找。
2. 接下来按顺序寻找目录 <prefix>/include (一般是 /usr/local/bin )、 /usr/gnu/include 、 /usr/local/include 、 /usr/include 。
环境变量 .INCLUDE_DIRS 包含当前 make 会寻找的目录列表。你应当避免使用命令行参数 -I 来寻找以上这些默认目录,否则会使得 make “忘掉”所有已经设定的包含目录,包括默认目录。
## 命令行选项
```sh
make
-e, --environment-overrides # 允许环境变量覆盖 Makefile 的变量
-B, --always-make # 强制重新编译所有目标
-s, --silent # 不打印任何信息
-j, --jobs=N # 并行编译N 表示同时编译的线程数
-f, --file=FILE # 指定 Makefile 文件
```
## 参考示例
```makefile
CC=cc
CFLAGS+=-Wall -Werror -Wformat=2 -g
LDFLAGS=-I./src/include -L./src/lib
COMPILER_VERSION=$(shell $(CC) --version)
ifneq '' '$(findstring clang, $(COMPILER_VERSION))'
CFLAGS += -Qunused-arguments
endif
lib := src/lib
bin := udpserv01 udpcli01
all: $(bin)
lib_objects :=$(patsubst %.c, %.o, $(wildcard $(lib)/*.c))
$(lib_objects): %.o : %.c
$(CC) -c $(CFLAGS) $(LDFLAGS) $< -o $@
.PHONY: udpserv01
udpserv01: src/chap8/udpserv01.c $(lib_objects)
$(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@
.PHONY: udpcli01
udpcli01: $(lib_objects) src/chap8/udpcli01.c dg_cli.o
$(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@
```
## 进一步阅读
- [isaacs's Makefile](https://gist.github.com/isaacs/62a2d1825d04437c6f08)
- [Your Makefiles are wrong](https://tech.davis-hansson.com/p/make/)
- [Manual](https://www.gnu.org/software/make/manual/html_node/index.html)
- [跟我一起写Makefile](https://seisman.github.io/how-to-write-makefile/index.html)