语法

targets: prerequisites
	command
	command
	command

prerequisites 是先决条件, 类似依赖

定义 target

hello:
	echo "Hello, World"

运行 make 后执行 echo

可以在 : 后写这个 target 的 prerequisites

hello: hello.txt
	cat hello.txt

如果没有这个文件就会报错

这样就可以构建一个依赖链条, make 会按照依赖顺序来处理

blah: blah.o
	cc blah.o -o blah # Runs third

blah.o: blah.c
	cc -c blah.c -o blah.o # Runs second
	
blah.c:
	echo "int main() { return 0; }" > blah.c # Runs first
all: one two three

one:
	touch one
two:
	touch two
three:
	touch three

clean:
	rm -f one two three

在命令前加 @ 可以让 make 不打印这一行命令, 或者用 make -s 为每行都加 @

在命令前加 - 可以忽略并打印这个命令的错误

多个 target

all: f1.o f2.o

f1.o f2.o:
	echo $@
# Equivalent to:
# f1.o:
#	 echo f1.o
# f2.o:
#	 echo f2.o

$@ 是一个自动变量

变量

定义

行首的空格会被删除, 行末的空则不会

未定义的变量会是一个空字符串

= 会延迟/递归展开, 在使用时才会计算

=: 立即/简单展开, 在定义时就会计算

# Recursive variable. This will print "later" below
one = one ${later_variable}
# Simply expanded variable. This will not print "later" below
two := two ${later_variable}

later_variable = later

all: 
	echo $(one)
	echo $(two)

one = hello
# one gets defined as a simply expanded variable (:=) and thus can handle appending
one := ${one} there

all: 
	echo $(one)

?=

在没有设置时设置变量

命令列表

使用 define / endef 来创建一个命令列表

one = "export blah="I was set!"; echo $$blah"

define two
export blah="I was set!"
echo $$blah
endef

all: 
	@echo "This prints 'I was set'"
	@$(one)
	@echo "This does not print 'I was set' because each command runs in a separate shell"
	@$(two)

为特定的 target 声明变量

all: one = cool

all: 
	echo one is defined: $(one)

other:
	echo one is nothing: $(one)

为特定的模式声明变量

%.c: one = cool

blah.c: 
	echo one is defined: $(one)

other:
	echo one is nothing: $(one)

使用

使用变量用 $()${} 使用变量

如果要使用 shell 变量则需要 $$

all:
	echo $$HOME

自动变量

  • $@ - target 的名称
  • $? - 所有比 target 新的 prerequisites
  • $^ - 所有 prerequisites
  • $< - 第一个 prerequisite
hey: one two
	# Outputs "hey", since this is the target name
	echo $@

	# Outputs all prerequisites newer than the target
	echo $?

	# Outputs all prerequisites
	echo $^

	# Outputs the first prerequisite
	echo $<

	touch hey

one:
	touch one

two:
	touch two

clean:
	rm -f hey one two

通配符

*%, * 用于 shell 字符匹配, % 用于模式匹配

* 不要直接用于变量声明, 因为当其没有匹配到任何文件时不会展开

推荐的做法是使用 wildcard 函数: $(wildcard *.o), 包装了之后如果没有匹配到则就是空字符串

thing_wrong := *.o # Don't do this! '*' will not get expanded
thing_right := $(wildcard *.o)

all: one two three four

# Fails, because $(thing_wrong) is the string "*.o"
one: $(thing_wrong)

# Stays as *.o if there are no files that match this pattern :(
two: *.o 

# Works as you would expect! In this case, it does nothing.
three: $(thing_right)

# Same as rule three
four: $(wildcard *.o)

模式规则

隐式规则

隐式规则是一种自动规则

  • CC: Program for compiling C programs; default cc
  • CXX: Program for compiling C++ programs; default g++
  • CFLAGS: Extra flags to give to the C compiler
  • CXXFLAGS: Extra flags to give to the C++ compiler
  • CPPFLAGS: Extra flags to give to the C preprocessor
  • LDFLAGS: Extra flags to give to compilers when they are supposed to invoke the linker
CC = gcc # Flag for implicit rules
CFLAGS = -g # Flag for implicit rules. Turn on debug info

# Implicit rule #1: blah is built via the C linker implicit rule
# Implicit rule #2: blah.o is built via the C compilation implicit rule, because blah.c exists
blah: blah.o

blah.c:
	echo "int main() { return 0; }" > blah.c

clean:
	rm -f blah*

上面这一段, 没有找到 blah.o 就会自动调用 gcc -g 来编译一个 blah.o, 而编译这个需要 blah.c, 就会先运行 blah.c

模式规则

使用 % 来匹配一种模式

语法:

target-pattern:
   commands

比如下面的 Makefile

objects = foo.o bar.o

%.c:
	touch $@

这里的 %.c 相当于会变成:

objects = foo.o bar.o

foo.c:
	touch $@
bar.c:
	touch $@

静态模式规则

语法:

targets...: target-pattern: prereq-patterns ...
   commands

对于所有 target-pattern 匹配到的 targets, 应用 prereq-pattern

objects = foo.o bar.o all.o
all: $(objects)
	$(CC) $^ -o all

$(objects): %.o: %.c
	$(CC) -c $^ -o $@

会变成:

objects = foo.o bar.o
	
foo.o: foo.o
	$(CC) -c $^ -o $@
bar.o: bar.o
	$(CC) -c $^ -o $@

条件控制

使用 if/else 来控制条件

  • ifeq - 变量是否相同
  • ifneq - 变量是否不同
  • ifdef - 变量是否已被定义
foo = ok

all:
ifeq ($(foo), ok)
	echo "foo equals ok"
else
	echo "nope"
endif

函数

函数调用的形式: $(fn, arguments)

subst

$(subst search, replace)

用于字符串替换

not 替换成 totally

bar := ${subst not,"totally", "I am not superman"}
all: 
	@echo $(bar)

patsubst

$(patsubst pattern,replacement,text)

模式匹配字符串替换

foo := a.o b.o l.a c.o
one := $(patsubst %.o,%.c,$(foo))
# This is a shorthand for the above
two := $(foo:%.o=%.c)
# This is the suffix-only shorthand, and is also equivalent to the above.
three := $(foo:.o=.c)

all:
	echo $(one)
	echo $(two)
	echo $(three)

foreach

$(foreach var,list,text)

foo := who are you
# For each "word" in foo, output that same word with an exclamation after
bar := $(foreach wrd,$(foo),$(wrd)!)

all:
	# Output is "who! are! you!"
	@echo $(bar)

if

$(if this-is-not-empty,then,else)

检测第一个参数是否不为空, 是执行第二, 否执行第三个参数

foo := $(if this-is-not-empty,then!,else!)
empty :=
bar := $(if $(empty),then!,else!)

all:
	@echo $(foo)
	@echo $(bar)

call

$(call variable,param,param)

调用定义的函数

首先要定义一个函数, 定义的方式是:

$(0) - 函数名

$(1) - 第一个参数

$(2) - 第二个参数

....

下面这个定义的这个函数就是纯输出的作用

fn = Variable Name: $(0) First: $(1) Second: $(2) Empty Variable: $(3)

sweet_new_fn = Variable Name: $(0) First: $(1) Second: $(2) Empty Variable: $(3)

all:
	# Outputs "Variable Name: sweet_new_fn First: go Second: tigers Empty Variable:"
	@echo $(call sweet_new_fn, go, tigers)

定义函数相当于定义一个变量

define func
echo "Function name: $(0), First: $(1)"
endef

all:
	${call func, hello}

shell

all: 
	@echo $(shell ls -la)

要注意, 换行会被替换成空格

filter

$(filter pattern, text), text 是一组空格分割的字符串

obj_files = foo.result bar.o lose.o
filtered_files = $(filter %.o,$(obj_files))

all:
	@echo $(filtered_files)

MISC

指定 shell

SHELL=/bin/bash

cool:
	echo "Hello from bash"

递归 make

new_contents = "hello:\n\ttouch inside_file"
all:
	mkdir -p subdir
	printf $(new_contents) | sed -e 's/^ //' > subdir/makefile
	cd subdir && $(MAKE)

clean:
	rm -rf subdir

使用 $(MAKE) 而不是直接使用 make, 前者会自动传递标志并且不会受到自身的影响

导出变量

使用 .EXPORT_ALL_VARIABLES

.EXPORT_ALL_VARIABLES:
new_contents = "hello:\n\techo \$$(cooly)"

cooly = "The subdirectory can see me!"
# This would nullify the line above: unexport cooly

all:
	mkdir -p subdir
	printf $(new_contents) | sed -e 's/^ //' > subdir/makefile
	@echo "---MAKEFILE CONTENTS---"
	@cd subdir && cat makefile
	@echo "---END MAKEFILE CONTENTS---"
	cd subdir && $(MAKE)

clean:
	rm -rf subdir

包含文件

和 C 一样

include filenames

这在 gcc -MMD 生成依赖文件时有用

包含与每个 .c 源文件对应的 .d 依赖文件

-include $(sources:.c=.d)

这里加 -, .d 不存在也不会报错

指定搜索路径

vpath <pattern> <directories, space/colon separated>

vpath %.h ../headers ../other-directory

# Note: vpath allows blah.h to be found even though blah.h is never in the current directory
some_binary: ../headers blah.h
	touch some_binary

../headers:
	mkdir ../headers

# We call the target blah.h instead of ../headers/blah.h, because that's the prereq that some_binary is looking for
# Typically, blah.h would already exist and you wouldn't need this.
blah.h:
	touch ../headers/blah.h

clean:
	rm -rf ../headers
	rm -f some_binary

.phony

可以用 .PHONY 来声明某个名字不是一个实际文件

some_file:
	touch some_file
	touch clean

.PHONY: clean
clean:
	rm -f some_file
	rm -f clean

这里如果不加 .PHONY clean, 在运行 make clean 时就会认为 clean 是一个目标文件, 而我们已经通过 touch 创建, 由于 clean target 无 prerequisites 无需更新, 所以不会运行 clean target 中的内容

.delete_on_error

如果 target中的一个命令返回非 0 的退出状态, 就会删除 target 生成的文件

.DELETE_ON_ERROR:
all: one two

one:
	touch one
	false

two:
	touch two
	false