Makefile? Makefile!

Makefile的写法前前后后也看了很多遍,虽然大概知道怎么回事,但每次写稍微复杂一点(多文件编译,将中间文件编译到build目录中防止文件污染)的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
// main.c
#include "foo.h"
int main(void)
{
int a = 100;
int b = foo(a);
return b;
}
// foo.h
#ifndef __FOO_H
#define __FOO_H
extern int foo(int k);
#endif
// foo.c
#include <stdio.h>
#include "foo.h"
int foo(int k)
{
printf("K = %d\n", k);
return (k + 1);
}

这段代码的功能一目了然,这里就不多做解释了。

Step1: 基本功能实现版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CC = gcc
CFLAGS = -Wall -g -O1 -I.
BIN = sample
BUILD_DIR = ./build
SRCS = $(wildcard *.c)
OBJS = $(SRCS:%.c=$(BUILD_DIR)/%.o)
all:
mkdir -p $(BUILD_DIR)
$(CC) $(CFLAGS) $(SRCS) -o $(BIN)
.PHONY: clean
clean:
rm -fr build *.o *.d

这个版本会将生成的中间文件(这里主要是.o文件)保存在build文件夹下,从而避免对源码树的污染。

OBJS = $(SRCS:%.c=$(BUILD_DIR)/%.o)表示,将SRCS变量中的.c替换为$(BUILD_DIR)

SRCS = $(wildcard *.c)表示使用通配符匹配所有的.c文件。

Step2: 具有自动识别文件改动功能的版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CC = gcc
CFLAGS = -Wall -g -O1 -I.
BIN = sample
BUILD_DIR = ./build
SRCS = $(wildcard *.c)
OBJS = $(SRCS:%.c=$(BUILD_DIR)/%.o)
all: $(OBJS)
@mkdir -p $(BUILD_DIR)
$(CC) $(CFLAGS) $^ -o $(BIN)
$(BUILD_DIR)/%.o: %.c
@mkdir -p $(BUILD_DIR)
$(CC) $(CFLAGS) -c $< -o $@
.PHONY: clean
clean:
rm -fr build *.o *.d

这里有几点要解释:

  • @mkdir -p $(BUILD_DIR), 这里的@表示,不打印当前这条命令。默认情况下,make执行命时,会自动输出当前执行的指令。
  • $^变量,这个变量表示当前生成目标下(例如all这个目标),所有的依赖名。
  • $<变量,这个变量表示当前生成目标下(例如all这个目标),第一个依赖的名字。
  • $@变量,这个变量表示当前生成目标的名字。

Step3: 具有识别头文件改动功能的版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
CC = gcc
CFLAGS = -Wall -g -O1 -I.
BIN = sample
BUILD_DIR = ./build
SRCS = $(wildcard *.c)
OBJS = $(SRCS:%.c=$(BUILD_DIR)/%.o)
DEPS = $(OBJS:%.o=%.d)
all: $(OBJS)
@mkdir -p $(BUILD_DIR)
$(CC) $(CFLAGS) $^ -o $(BIN)
-include $(DEPS)
$(BUILD_DIR)/%.o: %.c
@mkdir -p $(BUILD_DIR)
$(CC) $(CFLAGS) -MMD -c $< -o $@
.PHONY: clean
clean:
rm -fr build *.o *.d

这一步我们加入了-MMD编译选项,变量DEPS = $(OBJS:%.o=%.d)-include $(DEPS)

对于-MMD选项可以自行查询gcc的man手册。简单的说就是gcc会根据预处理的结果生成.d文件,.d文件是make兼容的依赖依赖树。通过make-include可以引入该依赖,从而避免的对头文件的处理。

这里的参考来源:http://stackoverflow.com/a/30142139/3824053

Step4: 小优化

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
CC = gcc
CFLAGS = -Wall -g -O1 -I.
BIN = sample
BUILD_DIR = ./build
SRCS = $(wildcard *.c)
OBJS = $(SRCS:%.c=$(BUILD_DIR)/%.o)
DEPS = $(OBJS:%.o=%.d)
all: $(BUILD_DIR)/$(BIN)
$(BUILD_DIR)/$(BIN): $(OBJS)
@mkdir -p $(@D)
$(CC) $(CFLAGS) $^ -o $@
-include $(DEPS)
$(BUILD_DIR)/%.o: %.c
@mkdir -p $(@D)
$(CC) $(CFLAGS) -MMD -c $< -o $@
.PHONY: clean
clean:
rm -fr build *.o *.d

修改@mkdir -p $(BUILD_DIR)@mkdir -p $(@D)$(@D)make的内置变量,从手册中可以找到其描述为The directory part and the file-within-directory part of $@.

修改了all目标,这样可以避免不必要的链接操作。

为不同的构建目标使用不同的编译参数

有时候我们为了测试需要,需要在编译时添加某些参数,例如-g参数。这时我们希望能用不同的目标来生成不同的结果。那么可以这么做(这个例子和上面的例子没有关系,只是为了表明如何使用与目标绑定的变量)

1
2
3
4
5
6
7
8
release: DEBUG_FLAGS = -O2
release: build
build:
gcc $(DEBUG_FLAGS) *.c -o out
debug: DEBUG_FLAGS = -g -O1 -DDEBUG
debug: build

这时当我们执行makemake debug时就可以分别以不同的参数来编译了。

添加新的文件扩展后缀

有时候会用到某些扩展名不常见的文件类型,例如使用m4进行替换的模板文件*.in。这样的文件不能被make直接的识别,需要我们手动的添加这些扩展名。

1
2
3
4
.SUFFIXES: .h.in .h
%.h: %.h.in
$(M4) $(M4_SCRIPT) $^ > $@

¶ The end

Share Link: http://d0u9.win/posts/412858525.html