跳转至

Makefile

原始内容来自 rstacruz/cheatsheets 有改动。

变量赋值

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 最后指定的值。:=是赋予当前位置的值。?=是如果该变量没有被赋值,才被赋值为等号后面的值。

x = foo
y = $(x) bar
z := $(x) bar
x = xyz

上面例子中x,y,z最终值分别为xyz,xyz bar, foo bar

魔术变量

out.o: src.c src.h src.c
  $@   # "out.o" (目标对象)
  $<   # "src.c" (第一个前置依赖)
  $^   # "src.c src.h" (所有的前置依赖)
  $+   # "src.c src.h src.c" (类似$^,不同于$^地方是它不会去重)
  $?   # 所有比目标对象新的依赖的集合
  $(@D) # "." (目标对象中的目录部分,没有目录部分那么对应是.)

命令前缀

前缀 描述
- 忽略错误
@ 不打印命令输出
+ 忽略错误,但会打印错误信息
build:
    @echo "compiling"
    -gcc $< $@

-include .depend

查找文件

js_files  := $(wildcard test/*.js) # 匹配所有test目录下的js文件
all_files := $(shell find images -name "*") # 查找images目录下所有文件

函数

# 替换相关
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的规则中的目标可以不止一个,其支持多目标,有可能我们的多个目标同时依赖于一个文件,并且其生成的命令大体类似。于是我们就能把其合并起来。

bigoutput littleoutput : text.g
    generate text.g -$(subst output,,$@) > $@

其中, -$(subst output,,$@) 中的 $ 表示执行一个Makefile的函数,函数名为subst,后面的为参数。上述规则等价于:

bigoutput : text.g
    generate text.g -big > bigoutput
littleoutput : text.g
    generate text.g -little > littleoutput

静态模式

静态模式可以更加容易地定义多目标的规则。

<targets ...> : <target-pattern> : <prereq-patterns ...>
    <commands>
    ...
  • targets定义了一系列的目标文件,可以有通配符。是目标的一个集合。

  • target-pattern是指明了targets的模式,也就是的目标集模式。

  • prereq-patterns是目标的依赖模式,它对target-pattern形成的模式再进行一次依赖目标的定义。

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”)。于是,上面的规则展开后等价于下面的规则:

foo.o : foo.c
    $(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
    $(CC) -c $(CFLAGS) bar.c -o bar.o

再看一个例子:

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”的内容。

隐含规则与模式规则

隐含规则

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 的目标。也就是说,它等同于下面的两条规则:

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 。后缀规则不允许任何的依赖文件,如果有依赖文件的话,那就不是后缀规则,那些后缀统统被认为是文件名。

.c.o:
    $(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<

模式规则

我们可以使用模式规则来定义一个隐含规则,模式规则中,至少在规则的目标定义中要包含 % ,否则,就是一般的规则。目标中的 % 定义表示对文件名的匹配。

%.o: %.c
  $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@ # 把所有的 .c 文件都编译成 .o 文件

条件判断

foo: $(objects)
ifeq ($(CC),gcc)
  $(CC) -o foo $(objects) $(libs_for_gcc)
else
  $(CC) -o foo $(objects) $(normal_libs)
endif

自动生成依赖性

在Makefile中,我们的依赖关系可能会需要包含一系列的头文件,我们可以借助编译器来生成依赖关系。

cc -M main.c # -M选项输出依赖关系
gcc -MM main.c # -MM 选项输出依赖关系,输出的依赖关系中会去掉标准库中的头文件
cc -MM src/chap8/udpserv01.c -I./src/include # 使用 -I 参数指定自定义头文件位置

包含其他 Makefile

-include foo.make

make会在当前目录下首先寻找,如果当前目录下没有找到,那么,make还会在下面的几个目录下找:

  1. 如果make执行时,有 -I 或 --include-dir 参数,那么make就会在这个参数所指定的目录下去寻找。

  2. 接下来按顺序寻找目录 /include (一般是 /usr/local/bin )、 /usr/gnu/include 、 /usr/local/include 、 /usr/include 。

环境变量 .INCLUDE_DIRS 包含当前 make 会寻找的目录列表。你应当避免使用命令行参数 -I 来寻找以上这些默认目录,否则会使得 make “忘掉”所有已经设定的包含目录,包括默认目录。

命令行选项

make
  -e, --environment-overrides # 允许环境变量覆盖 Makefile 的变量
  -B, --always-make # 强制重新编译所有目标
  -s, --silent # 不打印任何信息
  -j, --jobs=N   # 并行编译,N 表示同时编译的线程数
  -f, --file=FILE # 指定 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 $@

进一步阅读