基本结构
target: prerequisites
command
变量赋值
foo = "bar"
bar = $(foo) foo # 动态(更新)赋值
foo := "boo" # 一次性赋值,现在 $(bar) 是 "boo foo"
foo ?= /usr/local # 安全赋值,$(foo) 和 $(bar) 依然保持不变
bar += world # 追加,"boo foo world"
foo != echo fooo # 执行 shell 命令并赋值给 foo
# $(bar) 现在是 "fooo foo world"
命令前缀
前缀 | 说明 |
---|---|
- |
忽略错误 |
@ |
不打印命令 |
+ |
即使 Make 处于“不执行”模式,也运行该命令 |
build:
@echo "compiling"
-gcc $< $@
-include .depend
查找文件
js_files := $(wildcard test/*.js)
all_files := $(shell find images -name "*")
替换
file = $(SOURCE:.cpp=.o) # foo.cpp => foo.o
outputs = $(files:src/%.coffee=lib/%.js)
outputs = $(patsubst %.c, %.o, $(wildcard *.c))
assets = $(patsubst images/%, assets/%, $(wildcard images/*))
包含文件
-include foo.make
单美元符号和双美元符号
美元符号 | 描述 |
---|---|
$ |
使用 $ 引用 Make 变量 |
$$ |
使用 $$ 引用 shell 变量 |
Makefile
LIST = one two three
.PHONY: all single_dollar double_dollar
all: single_dollar double_dollar
double_dollar:
@echo "=== 双美元符号例子 ==="
@for i in $(LIST); do \
echo $$i; \
done
single_dollar:
@echo "=== 单美元符号例子 ==="
@for i in $(LIST); do \
echo $i; \
done
输出
$ make
=== 单美元符号例子 ===
=== 双美元符号例子 ===
one
two
three
自动变量
自动变量 | 描述 |
---|---|
$@ | 目标文件的文件名 |
$< | 第一个依赖文件的名字 |
$^ | 所有依赖文件的名字 |
$+ | 所有依赖文件的名字(包括重复的,按顺序) |
Makefile
.PHONY: all
all: hello world
hello world: foo foo foo bar bar
@echo "== 目标: $@ =="
@echo $<
@echo $^
@echo $+
foo:
@echo "Hello foo"
bar:
@echo "Hello Bar"
输出
Hello foo
Hello Bar
== 目标: hello ==
foo
foo bar
foo foo foo bar bar
== 目标: world ==
foo
foo bar
foo foo foo bar bar
使用 $(warning text)
检查 Make 规则(用于调试)
$(warning 顶层警告)
FOO := $(warning FOO 变量)foo
BAR = $(warning BAR 变量)bar
$(warning 目标)target: $(warning 依赖文件列表)Makefile $(BAR)
$(warning 目标脚本)
@ls
$(BAR):
输出
Makefile:1: 顶层警告
Makefile:3: FOO 变量
Makefile:6: 目标
Makefile:6: 依赖文件列表
Makefile:6: BAR 变量
Makefile:9: BAR 变量
Makefile:7: 目标脚本
Makefile
分别编译可执行文件
目录结构
.
|-- Makefile
|-- bar.c
|-- bar.h
|-- foo.c
`-- foo.h
Makefile
# CFLAGS: 传递给 C 编译器的额外标志
CFLAGS += -Werror -Wall -O2 -g
SRC = $(wildcard *.c)
OBJ = $(SRC:.c=.o)
EXE = $(subst .c,,$(SRC))
.PHONY: all clean
all: $(OBJ) $(EXE)
clean:
rm -rf *.o *.so *.a *.la $(EXE)
输出
$ make
cc -Werror -Wall -O2 -g -c -o foo.o foo.c
cc -Werror -Wall -O2 -g -c -o bar.o bar.c
cc foo.o -o foo
cc bar.o -o bar
使用 $(eval)
预定义变量
不使用 $(eval)
SRC = $(wildcard *.c)
EXE = $(subst .c,,$(SRC))
define PROGRAM_template
$1_SHARED = lib$(strip $1).so
endef
.PHONY: all
$(foreach exe, $(EXE), $(call PROGRAM_template, $(exe)))
all:
@echo $(foo_SHARED)
@echo $(bar_SHARED)
输出
$ make
Makefile:11: *** missing separator. Stop.
使用 $(eval)
CFLAGS += -Wall -g -O2 -I./include
SRC = $(wildcard *.c)
EXE = $(subst .c,,$(SRC))
define PROGRAM_template
$1_SHARED = lib$(strip $1).so
endef
.PHONY: all
$(foreach exe, $(EXE), $(eval $(call PROGRAM_template, $(exe))))
all:
@echo $(foo_SHARED)
@echo $(bar_SHARED)
输出
$ make
libfoo.so
libbar.so
编译子目录并链接
目录结构
.
|-- Makefile
|-- include
| `-- foo.h
`-- src
|-- foo.c
`-- main.c
Makefile
CFLAGS += -Wall -g -O2 -I./include
SRC = $(wildcard src/*.c)
OBJ = $(SRC:.c=.o)
EXE = main
.PHONY: all clean
all: $(OBJ) $(EXE)
$(EXE): $(OBJ)
$(CC) $(LDFLAGS) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -rf *.o *.so *.a *.la $(EXE) src/*.o src/*.so src/*a
输出
$ make
cc -Wall -g -O2 -I./include -c src/foo.c -o src/foo.o
cc -Wall -g -O2 -I./include -c src/main.c -o src/main.o
cc -o main src/foo.o src/main.o
编译共享库
目录结构
.
|-- Makefile
|-- include
| `-- common.h
`-- src
|-- bar.c
`-- foo.c
Makefile
SONAME = libfoobar.so.1
SHARED = src/libfoobar.so.1.0.0
SRC = $(wildcard src/*.c)
OBJ = $(SRC:.c=.o)
CFLAGS += -Wall -Werror -fPIC -O2 -g -I./include
LDFLAGS += -shared -Wl,-soname,$(SONAME)
.PHONY: all clean
all: $(SHARED) $(OBJ)
$(SHARED): $(OBJ)
$(CC) $(LDFLAGS) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c $^ -o $@
clean:
rm -rf src/*.o src/*.so.* src/*.a src/*.la
输出
$ make
cc -Wall -Werror -fPIC -O2 -g -I./include -c src/foo.c -o src/foo.o
cc -Wall -Werror -fPIC -O2 -g -I./include -c src/bar.c -o src/bar.o
cc -shared -Wl,-soname,libfoobar.so.1 -o src/libfoobar.so.1.0.0 src/foo.o src/bar.o
编译共享库和静态库
目录结构
.
|-- Makefile
|-- include
| |-- bar.h
| `-- foo.h
`-- src
|-- Makefile
|-- bar.c
`-- foo.c
Makefile
SUBDIR = src
.PHONY: all clean $(SUBDIR)
all: $(SUBDIR)
clean: $(SUBDIR)
$(SUBDIR):
make -C $@ $(MAKECMDGOALS)
src/Makefile
SRC = $(wildcard *.c)
OBJ = $(SRC:.c=.o)
LIB = libfoobar
STATIC = $(LIB).a
SHARED = $(LIB).so.1.0.0
SONAME = $(LIB).so.1
SOFILE = $(LIB).so
CFLAGS += -Wall -Werror -g -O2 -fPIC -I../include
LDFLAGS += -shared -Wl,-soname,$(SONAME)
.PHONY: all clean
all: $(STATIC) $(SHARED) $(SONAME) $(SOFILE)
$(SOFILE): $(SHARED)
ln -sf $(SHARED) $(SOFILE)
$(SONAME): $(SHARED)
ln -sf $(SHARED) $(SONAME)
$(SHARED): $(STATIC)
$(CC) $(LDFLAGS) -o $@ $<
$(STATIC): $(OBJ)
$(AR) $(ARFLAGS) $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
clean:
rm -rf *.o *.a *.so *.so.*
输出
$ make
make -C src
make[1]: 进入目录 '/root/test/src'
cc -Wall -Werror -g -O2 -fPIC -I../include -c -o foo.o foo.c
cc -Wall -Werror -g -O2 -fPIC -I../include -c -o bar.o bar.c
ar rv libfoobar.a foo.o bar.o
ar: 创建 libfoobar.a
a - foo.o
a - bar.o
cc -shared -Wl,-soname,libfoobar.so.1 -o libfoobar.so.1.0.0 libfoobar.a
ln -sf libfoobar.so.1.0.0 libfoobar.so.1
ln -sf libfoobar.so.1.0.0 libfoobar.so
make[1]: 离开目录 '/root/test/src'
递归构建
目录结构
.
|-- Makefile
|-- include
| `-- common.h
|-- src
| |-- Makefile
| |-- bar.c
| `-- foo.c
`-- test
|-- Makefile
`-- test.c
Makefile
SUBDIR = src test
.PHONY: all clean $(SUBDIR)
all: $(SUBDIR)
clean: $(SUBDIR)
$(SUBDIR):
$(MAKE) -C $@ $(MAKECMDGOALS)
src/Makefile
SONAME = libfoobar.so.1
SHARED = libfoobar.so.1.0.0
SOFILE = libfoobar.so
CFLAGS += -Wall -g -O2 -Werror -fPIC -I../include
LDFLAGS += -shared -Wl,-soname,$(SONAME)
SRC = $(wildcard *.c)
OBJ = $(SRC:.c=.o)
.PHONY: all clean
all: $(SHARED) $(OBJ)
$(SHARED): $(OBJ)
$(CC) $(LDFLAGS) -o $@ $^
ln -sf $(SHARED) $(SONAME)
ln -sf $(SHARED) $(SOFILE)
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -rf *.o *.so.* *.a *.so
test/Makefile
CFLAGS += -Wall -Werror -g -I../include
LDFLAGS += -Wall -L../src -lfoobar
SRC = $(wildcard *.c)
OBJ = $(SRC:.c=.o)
EXE = test_main
.PHONY: all clean
all: $(OBJ) $(EXE)
$(EXE): $(OBJ)
$(CC) -o $@ $^ $(LDFLAGS)
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -rf *.so *.o *.a $(EXE)
输出
$ make
make -C src
make[
1]: 进入目录 '/root/proj/src'
cc -Wall -g -O2 -Werror -fPIC -I../include -c foo.c -o foo.o
cc -Wall -g -O2 -Werror -fPIC -I../include -c bar.c -o bar.o
cc -shared -Wl,-soname,libfoobar.so.1 -o libfoobar.so.1.0.0 foo.o bar.o
ln -sf libfoobar.so.1.0.0 libfoobar.so.1
ln -sf libfoobar.so.1.0.0 libfoobar.so
make[1]: 离开目录 '/root/proj/src'
make -C test
make[1]: 进入目录 '/root/proj/test'
cc -Wall -Werror -g -I../include -c test.c -o test.o
cc -o test_main test.o -Wall -L../src -lfoobar
make[1]: 离开目录 '/root/proj/test'
$ tree .
.
|-- Makefile
|-- include
| `-- common.h
|-- src
| |-- Makefile
| |-- bar.c
| |-- bar.o
| |-- foo.c
| |-- foo.o
| |-- libfoobar.so -> libfoobar.so.1.0.0
| |-- libfoobar.so.1 -> libfoobar.so.1.0.0
| `-- libfoobar.so.1.0.0
`-- test
|-- Makefile
|-- test.c
|-- test.o
`-- test_main
3 个目录, 14 个文件
替换当前 Shell
OLD_SHELL := $(SHELL)
SHELL = /usr/bin/python
.PHONY: all
all:
@import os; print os.uname()[0]
输出
$ make
Linux
一行条件语句
语法:$(if 条件, 然后部分, 否则部分)
Makefile
VAR =
IS_EMPTY = $(if $(VAR), $(info not empty), $(info empty))
.PHONY: all
all:
@echo $(IS_EMPTY)
输出
$ make
empty
$ make VAR=true
not empty
使用 define 控制 CFLAGS
Makefile
CFLAGS += -Wall -Werror -g -O2
SRC = $(wildcard *.c)
OBJ = $(SRC:.c=.o)
EXE = $(subst .c,,$(SRC))
ifdef DEBUG
CFLAGS += -DDEBUG
endif
.PHONY: all clean
all: $(OBJ) $(EXE)
clean:
rm -rf $(OBJ) $(EXE)
输出
$ make
cc -Wall -Werror -g -O2 -c -o foo.o foo.c
cc foo.o -o foo
$ make DEBUG=1
cc -Wall -Werror -g -O2 -DDEBUG -c -o foo.o foo.c
cc foo.o -o foo
内置函数
函数 | 描述 |
---|---|
$(subst from,to,text) |
在 text 中用 to 替换 from 。 |
$(patsubst pattern,replacement,text) |
在 text 中用 replacement 替换匹配 pattern 的单词。 |
$(strip string) |
移除 string 中多余的空白字符。 |
$(findstring find,text) |
在 text 中找到 find 。 |
$(filter pattern…,text) |
选择 text 中匹配 pattern 的单词。 |
$(filter-out pattern…,text) |
选择 text 中不匹配 pattern 的单词。 |
$(sort list) |
按字典序对 list 中的单词进行排序,并去重。 |
$(word n,text) |
提取 text 中第 n 个单词(一基索引)。 |
$(words text) |
计算 text 中的单词数量。 |
$(wordlist s,e,text) |
返回 text 中从 s 到 e 的单词列表。 |
$(firstword names…) |
提取 names 中的第一个单词。 |
$(lastword names…) |
提取 names 中的最后一个单词。 |
$(dir names…) |
提取每个文件名的目录部分。 |
$(notdir names…) |
提取每个文件名的非目录部分。 |
$(suffix names…) |
提取每个文件名的后缀(最后一个 . 及其后的字符)。 |
$(basename names…) |
提取每个文件名的基名(不含后缀)。 |
$(addsuffix suffix,names…) |
将 suffix 添加到 names 中每个单词的末尾。 |
$(addprefix prefix,names…) |
将 prefix 添加到 names 中每个单词的前面。 |
$(join list1,list2) |
将两个平行的单词列表连接起来。 |
$(wildcard pattern…) |
查找与 shell 文件名模式(不是 `%’ 模式)匹配的文件名。 |
$(realpath names…) |
对 names 中的每个文件名进行扩展,生成不包含任何 . 、.. 及符号链接的绝对路径。 |
$(abspath names…) |
对 names 中的每个文件名进行扩展,生成不包含 . 或 .. 组件的绝对路径,但保留符号链接。 |
$(error text…) |
在评估此函数时,make 生成一个带有 text 信息的致命错误。 |
$(warning text…) |
在评估此函数时,make 生成一个带有 text 信息的警告。 |
$(shell command) |
执行一个 shell 命令并返回其输出。 |
$(origin variable) |
返回一个字符串,描述 make 变量 variable 是如何定义的。 |
$(flavor variable) |
返回一个字符串,描述 make 变量 variable 的类型。 |
$(foreach var,words,text) |
将 var 依次绑定到 words 中的每个单词,并评估 text ,将结果连接起来。 |
$(if cond,then-part[,else-part]) |
评估 cond ;如果非空,则替换为 then-part 的扩展,否则替换为 else-part 的扩展。 |
$(or cond1[,cond2[,cond3…]]) |
依次评估 condN ;替换为第一个非空扩展。如果所有扩展都是空的,则替换为空字符串。 |
$(and cond1[,cond2[,cond3…]]) |
依次评估 condN ;如果任何一个结果为空字符串,则替换为空字符串。否则,替换为最后一个 condN 的扩展。 |
$(call var,param,…) |
评估变量 var ,将对 $(1) 、$(2) 的引用替换为第一个、第二个等参数值。 |
$(eval text) |
评估 text ,然后将结果作为 makefile 命令读取;扩展为空字符串。 |
$(file op filename,text) |
扩展参数,然后以模式 op 打开文件 filename 并将 text 写入该文件。 |
$(value var) |
评估为变量 var 的内容,不进行任何扩展。 |
常用make命令参数
参数 | 说明 | 示例 |
---|---|---|
-f |
指定要使用的 Makefile 文件 | make -f custom_makefile |
-C |
在指定的目录中执行 make 命令 | make -C /path/to/directory |
-j |
指定同时执行的任务数量(并行编译) | make -j4 |
-k |
即使遇到错误也继续编译剩余的目标 | make -k |
-s |
静默模式,不打印命令执行信息 | make -s |
-i |
忽略所有命令的错误 | make -i |
-n |
打印将要执行的命令,但不实际执行 | make -n |
-p |
打印所有的 Makefile 信息,包括变量和规则 | make -p |
-q |
仅检查文件是否是最新的,如果是最新的则返回 0 否则返回 1 | make -q |
-r |
禁用内置规则 | make -r |
-t |
仅更新目标文件的时间戳而不执行命令 | make -t |
-v |
显示 make 的版本信息 | make -v |
--no-print-directory |
禁止 make 在进入子目录时打印目录信息 | make --no-print-directory |
--debug |
打印调试信息,可选参数有 a (all)、b (basic)、v (verbose)、i (implicit)等 |
make --debug=b |
VAR=value |
通过命令行传递变量值到 Makefile 中 | make CC=gcc |
特殊变量
变量 | 描述 |
---|---|
MAKEFILES |
在每次调用 make 时读取的 Makefile。 |
VPATH |
未在当前目录中找到的文件的目录搜索路径。 |
SHELL |
系统默认命令解释器的名字,通常是 /bin/sh 。 |
MAKESHELL |
make 使用的命令解释器的名字,优先于 SHELL (仅 MS-DOS)。 |
MAKE |
调用 make 的名字(在配方中使用此变量具有特殊含义)。 |
MAKE_VERSION |
内置变量 MAKE_VERSION 扩展为 GNU make 程序的版本号。 |
MAKE_HOST |
内置变量 MAKE_HOST 扩展为一个字符串,表示构建 GNU make 的主机。 |
MAKELEVEL |
递归级别的数量(子 make)。 |
MAKEFLAGS |
传递给 make 的标志。可以在环境或 makefile 中设置它以设置标志。在配方行中直接使用 MAKEFLAGS 是不合适的:其内容可能未正确引用以用于 shell。始终允许递归 make 从其父进程通过环境获取这些值。 |
GNUMAKEFLAGS |
make 解析的其他标志。可以在环境或 makefile 中设置它以设置 make 命令行标志。GNU make 永远不会自己设置这个变量。仅当你希望在 POSIX 兼容的 makefile 中设置 GNU make 特定的标志时才需要这个变量。GNU make 将看到这个变量,而其他 make 实现将忽略它。如果你只使用 GNU make,则不需要它;只需直接使用 MAKEFLAGS 。参见“向子 make 传递选项”。 |
MAKECMDGOALS |
在命令行中给定的目标。设置此变量对 make 的操作没有影响。 |
CURDIR |
设置为当前工作目录的绝对路径名(在处理所有 -C 选项之后,如果有的话)。设置此变量对 make 的操作没有影响。 |
SUFFIXES |
在 make 读取任何 makefile 之前的默认后缀列表。 |
.LIBPATTERNS |
定义 make 搜索库的命名及其顺序。 |