Makefile教程

项目结构

1
2
3
4
5
6
7
<project>
├── Makefile
├── build
└── src
├── hello.c
├── hello.h
└── main.c

Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
SRC_DIR = ./src
BUILD_DIR = ./build
TARGET = $(BUILD_DIR)/world.out

CC = cc
CFLAGS = -Wall

# ./src/*.c
SRCS = $(shell find $(SRC_DIR) -name '*.c')
# ./src/*.c => ./build/*.o
OBJS = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(SRCS))
# ./src/*.c => ./build/*.d
DEPS = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.d,$(SRCS))

# 默认目标:
all: $(TARGET)

# build/xyz.d 的规则由 src/xyz.c 生成:
$(BUILD_DIR)/%.d: $(SRC_DIR)/%.c
@mkdir -p $(dir $@); \
rm -f $@; \
$(CC) -MM $< >$@.tmp; \
sed 's,\($*\)\.o[ :]*,$(BUILD_DIR)/\1.o $@ : ,g' < $@.tmp > $@; \
rm -f $@.tmp

# build/xyz.o 的规则由 src/xyz.c 生成:
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
@mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c -o $@ $<

# 链接:
$(TARGET): $(OBJS)
@echo "buiding $@..."
@mkdir -p $(dir $@)
$(CC) -o $(TARGET) $(OBJS)

# 清理 build 目录:
clean:
@echo "clean..."
rm -rf $(BUILD_DIR)

# 引入所有 .d 文件:
-include $(DEPS)

变量

1
2
BUILD_DIR = ./build  # 变量的定义
TARGET = $(BUILD_DIR)/world.out # 变量的引用
  • 内置变量:CC,代表C编译器的名字,默认值cc
  • 自动变量:@表示目标文件 ;^表示所有依赖文件 ;<表示第一个依赖项
  • 变量替换规则:objs = $(SRCS:.c=.o),类似函数patsubst
  • 常用函数
    • wildcard
    • patsubst
      1
      2
      SRCS = $(wildcard *.c)
      OBJS = $(patsubst %.c,%.o,$(SRCS))
  • 变量定义时使用shell:SRCS = $(shell find $(SRC_DIR) -name '*.c')

规则

1
2
3
4
5
6
7
8
9
10
$(TARGET): $(OBJS)  # 目标文件: 依赖文件
@echo "buiding $@..." # 生成目标文件的命令,以tab开头
@mkdir -p $(dir $@) # 每条命令会创建独立的shell环境,使用cd不会影响当前目录
$(CC) -o $(TARGET) $(OBJS)

# 复合规则
hello.o hello.d : hello.c
# 等同于
hello.o : hello.c
hello.d : hello.c

多条命令独立shell的解决方法:

  • 多条命令以;分隔写到一行,可以用\把一行拆成多行,便于浏览
  • 使用&&连接,当某天语句失败时,后续命令不会执行
    1
    2
    3
    $(BUILD_DIR)/%.d: $(SRC_DIR)/%.c
    @mkdir -p $(dir $@); \
    rm -f $@;
  • 不打印命令本身:@
    1
    @echo "buiding $@..."
  • 忽略错误:-
    1
    -include $(DEPS)
  • 隐式规则:遇到一个xyz.o时,没找到对应的规则,自动运用隐式规则。自动找到同名的c文件进行编译
    1
    2
    *.o: *.c
    $(CC) $(CFLAGS) -c -o $@ $<
  • 模式规则:按需生成,当匹配成功时,自动生成规则
    1
    2
    3
    %.o: %.c
    @echo 'compiling $<...'
    cc -c -o $@ $<
  • 类似clean语句,没有创建clean文件的语句,就没有clean文件,每次使用make clean都会运行

自动生成依赖

1
2
3
4
5
6
7
$(BUILD_DIR)/%.d: $(SRC_DIR)/%.c
@mkdir -p $(dir $@); \ # 生成目标文件路径中必要的目录
rm -f $@; \
$(CC) -MM $< >$@.tmp; \
# 替换依赖信息中的输出文件路径
sed 's,\($*\)\.o[ :]*,$(BUILD_DIR)/\1.o $@ : ,g' < $@.tmp > $@; \
rm -f $@.tmp
  • 生成源文件的依赖信息

    1
    2
    $ cc -MM main.c
    main.o: main.c hello.h
  • sed 's,\($*\)\.o[ :]*,$(BUILD_DIR)/\1.o $@ : ,g' < $@.tmp > $@;
    假设我们有以下内容在 $@.tmp 文件中:main.o: main.c utils.c
    执行这条 sed 命令后,输出将变为:main.o build/main.d : main.c utils.c

  • 匹配模式:

    • sed 查找匹配 \($*\)\.o[ :]* 的部分。在这个例子中,$*main,所以匹配模式是 main.o[ :]*
    • [ :]* 匹配零个或多个空格或冒号,因此 main.o: 被匹配。
  • 替换文本:

    • sed 将匹配的部分替换为 \1.o $@ :
    • \1 是第一个捕获组,即 main
    • $@ 是完整的目标文件路径,例如 build/main.d
    • 因此,替换文本是 main.o build/main.d :
  • 输出结果:

    • sed 将处理后的文本写入文件 $@,即 build/main.d
Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2023-2025 John Doe
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信