Makefile 是一个用于自动化构建和管理项目的工具,通常用于编译源代码、链接目标文件以及生成最终的可执行文件或库。它基于 Make 工具,通过定义规则和依赖关系来高效地管理构建过程。

.s.o 文件是两种常见的中间文件,它们分别代表 汇编代码文件目标文件

对于庞大的项目,Makefile对.o文件进行链接。

Makefile 的基本语法

命令必须以 Tab 开头,

(1)规则(Rule)结构

1
2
target: prerequisites
recipe
  • target:生成的目标文件(如 main.o)或伪目标(如 clean)。
  • prerequisites:依赖文件(如 main.c)。
  • recipe:执行的命令(必须以 Tab 开头)。

示例

1
2
hello: hello.c
gcc hello.c -o hello

(2)变量(Variable)

1
2
3
4
5
CC = gcc
CFLAGS = -Wall -O2

hello: hello.c
$(CC) $(CFLAGS) hello.c -o hello

(3)伪目标(Phony Target)

用于定义非文件任务(如清理):

1
2
3
.PHONY: clean
clean:
rm -f *.o hello

进阶特性

(1)自动变量(Automatic Variables)

变量 说明
$@ 当前目标(如 hello)。
$< 第一个依赖文件(如 hello.c)。
$^ 所有依赖文件。
$? 比目标更新的依赖文件。

示例

1
2
3
4
5
hello: hello.o utils.o
$(CC) $^ -o $@

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

(2)模式规则(Pattern Rules)

通用规则编译所有 .c 文件,使用%符号:

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

(3)变量(Value)

变量的赋值方式

(1)递归展开 (=)

  • 延迟求值:变量在引用时才会展开(可能受后续赋值影响)。

  • 示例:

    1
    2
    VAR1 = $(VAR2)  # VAR1 的值会随 VAR2 的变化而改变
    VAR2 = value

(2)直接展开 (:=)

  • 立即求值:变量在定义时展开(后续赋值不影响已定义的值)。

  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    VAR1 := $(VAR2)  # VAR1 的值固定为定义时的 VAR2
    VAR2 := value

    a:=abc
    b:=bcd
    all:
    @echo $(A) #若不想输出命令行,前面加入@
    echo $(B)

(3)条件赋值 (?=)

  • 仅在变量未定义时赋值

  • 示例:

    1
    VAR ?= default_value  # 如果 VAR 未定义,则赋值为 default_value

(4)追加赋值 (+=)

  • 向变量追加内容

  • 示例:

    1
    2
    CFLAGS = -Wall
    CFLAGS += -O2 # CFLAGS 变为 "-Wall -O2"

(4)函数(Functions)

常用函数:

wildcard:匹配文件模式。

  • 展开当前目录下匹配 pattern 的文件名(支持 *? 等通配符),取出存在的文件
1
SRC = $(wildcard *.c) #*.c:通配符模式,表示所有以 .c 结尾的文件

patsubst:替换文件名后缀。

1
OBJ = $(patsubst %.c,%.o,$(SRC)) #从SRC列表中取出每一个值如果符合%.c*(pattern)就替换

(foreach var,list,text):循环遍历 list,将每次迭代的值赋给 var,并展开 text

1
2
3
4
5
A = a, b, c
B = $(foreach f, S(A), $(f).o) #对每个变量添加.o

DIRS = src lib
FILES = $(foreach dir,$(DIRS),$(wildcard $(dir)/*.c))

(filter pattern...,text)$(filter-out pattern...,text)

  • filter:保留 text 中匹配 pattern 的单词。
  • filter-out:排除匹配 pattern 的单词。
1
2
3
4
5
6
C = a b c d/
D = $(filter %/, $(C)) #取出含有'/'的
E = $(filter-out %/, S(C)) #去除含有'/'的

FILES = main.c utils.h lib.o
C_FILES = $(filter %.c,$(FILES)) # 输出: main.c

当文件有许多.h文件的依赖时

1
2
3
4
5
gcc -M c.o  #打印出依赖

gcc -M -MF c.d c.c #将依赖写入c.d文件

gcc -c -o c.o c.c -MD -MF c.d #编译c.o,将依赖写入c.d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
objs = a.o b.o c.o

dep_files := $(patsubst %, .%.d, $(objs))
dep_files := $(wildcard $(dep_files))

CFLAGS = -Werror #将编译时的警告变成错误

test: S(objs)
gcc -o test $^

ifneq ($(dep_files),)
include $(dep_files)
endif
%.o :%.c
gcc -c -o $@ $< -MD -MF .$@.d

clean:
rm *.o test

distclean:
rm $(dep_files)

其他

在使用make [目标]编译时,会执行对应的目标下的命令,若无[目标]则默认运行第一个目标下的命令。

1
2
3
4
5
6
7
8
hello: hello.o utils.o
$(CC) $^ -o $@

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

clean:
rm -f *.o my_program

如果项目目录中意外存在一个名为 clean 的文件(或文件夹),make 会认为 clean 是一个需要构建的 文件目标,而非伪目标。可以将clean定义为假想目标。

1
.PHONY: clean