Makefile tutorial

总结一下makefile tutorial里的教程,总不能一直拿bash做构建系统

Makefile rules

rules是makefile最基本的内容,当运行make时,其实就是在执行对应rule下面的命令

1
2
3
4
5
target: prerequirement
command
command
...
command

一个rule由以上结构组成,target是rule的目标文件名称,prerequirement是rule的依赖文件,只有这些文件都存在才会执行对应的rule,command是make命令或者shell命令,前面有一个tab缩进

rule之间可以互相依赖

1
2
3
4
5
6
test: test.o
gcc -o test test.o

test.o: test.c
gcc -c test.c test.o

比如上面这个,test的依赖是test.o,运行make时会先运行test.o,再执行test

变量

makefile变量只能是纯文本,引号之类的也不会转义,有三种给变量赋值的方式

1
2
3
a:= 1
b?= 2
c= 3

:=是立刻赋值,=是当使用到了才赋值(延迟赋值),?=是未初始化则赋值
使用+=实现变量字符串的拼接

变量通过$()来使用

1
2
3
a:= test1 test2 test3
test:
echo $(a)

target

rule可以设置多个prerequirement

1
2
3
4
5
6
7
8
all: one two three

one:

two:

three:

这个经典的例子就是make all,等价于make one two three

rule同时也可以设置多个target,等价于执行好多遍

1
2
3
4
5
6
7
f1.o f2.o:
echo $@
# $@的意思是target名,这个等价于
# f1.o:
# echo f1.o
# f2.o:
# echo f2.o

预设变量

下面是几个常用的预设变量

  • $@ target
  • $? 所有时间戳比target新的prerequirement
  • $^ 所有的prerequirement
  • $< 第一个prerequirement

正则匹配

只有*%两种匹配符,*正则匹配只建议由wildcard函数包裹使用

  • *的功能是匹配任意字符,比如$(wildcard *.c)就是目录下所有后缀为.c的文件

  • %的功能是匹配并替换,通常用在模式固定的rule或者字符串替换中

1
2
%.o : %.c
$(CC) -c $(CFLAGS) $< -o $@

这里就是匹配目录下所有.c文件的名称作为pattern然后生成对应名字的.o文件

1
2
3
objects = foo.o bar.o all.o
$(objects): %.o: %.c
$(CC) -c $^ -o $@

这个则是从objects中提取符合%.o的模式,然后替换到%.c中,等于是为每个.o寻找对应的.c文件并编译

c/cpp

makefile会自动对目录下的与目标同名的c/cpp文件生成编译命令,不建议使用这个技巧

  • n.o会以n.c作为源被以下命令编译$(CC) -c $(CPPFLAGS) $(CFLAGS) $^ -o $@

  • 如果是n.cc或n.cpp则是如下$(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $^ -o $@

  • n会以n.o默认以以下命令链接$(CC) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@

命令

在命令前加@可以静默执行,make -s可以让所有命令静默执行
每行命令的shell环境是独立的,但是可以用;在一行连接多个命令,然后用\ 连接多个行

1
2
3
4
5
6
7
8
9
10
11
all: 
cd ..
# The cd above does not affect this line, because each command is effectively run in a new shell
echo `pwd`

# This cd command affects the next because they are on the same line
cd ..;echo `pwd`

# Same as above
cd ..; \
echo `pwd`

shell变量

shell环境变量和makefile变量是两种不同的东西,要使用shell环境变量,则要在变量名前加$$且不用括号包裹(这里类似转义)

1
2
3
4
5
6
7
make_var = I am a make variable
all:
# Same as running "sh_var='I am a shell variable'; echo $sh_var" in the shell
sh_var='I am a shell variable'; echo $$sh_var

# Same as running "echo I am a make variable" in the shell
echo $(make_var)

递归调用make

如果需要在子文件夹中递归调用make则使用$(MAKE)命令而不是shell命令,因为前者会帮助传递makefile变量

export

由export命令设置成环境变量的makefile变量可以被所有的rule和子makefile访问

1
2
3
4
5
shell_env_var=Shell env var, created inside of Make
export shell_env_var
all:
echo $(shell_env_var)
echo $$shell_env_var

这里输出的两行都是一样的

override

带override修饰的变量会忽略来自命令行的传参
override option_one = did_override
option_one永远是did_override

target特化的变量

1
2
3
4
5
6
7
all: one = cool

all:
echo one is defined: $(one)

other:
echo one is nothing: $(one)

变量可以为不同的target设置不同的值,这里make allone的值为cool,否则为空
这里的target也可以用正则

1
2
3
4
5
6
7
%.c: one = cool

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

other:
echo one is nothing: $(one)

条件表达式

1
2
3
4
5
6
7
8
foo = ok

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

函数

函数通过这两种模式的任意一种调用${fn,arguments},$(fn,arguments)
第一个参数的参数前不要带空格,否则空格会被识别成参数字符串的一部分

字符串替换

$(patsubst pattern,replacement,text)
patsubst函数从text中筛选符合pattern的空格分隔的字符串并替换成replacement,这里通常结合正则使用

1
2
foo := a.o b.o l.a c.o
one := $(patsubst %.o,%.c,$(foo))
批量修改

$(foreach var,list,text)
foreach函数把list中按空格分隔的每个字符串修改成新的,var是遍历时的变量,在text中使用,text是目标字符串

1
2
3
4
5
6
7
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)
过滤

$(filter pattern,objects)
filterobjects中按空格为分隔符筛选出符合pattern的字符串
filter-out则是筛掉符合的

vpath

vpath <pattern> <directories, space/colon separated>
vpath是为指定pattern的文件添加搜索路径,pattern可以是%正则
vpath %.h ../headers ../other-directory
比如这个就会在headers里搜索.h文件

.PHONY

.PHONY是伪目标宏,听起来很难懂,其实就是带.PHONY修饰的rule不管target状态如何都会执行

1
2
3
4
.PHONY: clean
clean:
rm -f some_file
rm -f clean
Author

SGSG

Posted on

2025-08-14

Updated on

2025-08-14

Licensed under