本文主要介绍Makefile的常用功能
最简约的
Makefile 文件如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| SRCS = main.c src/add.c
OBJS = $(SRCS:%.c=%.o)
CFLAGS = -Iinc
all: $(OBJS) CC $(CFLAGS) $(OBJS) -o a.exe
%.o: %.c CC $(CFLAGS) -c $< -o $@
clean: rm $(OBJS)
|
只要列出详细的源文件路径, OBJ 直接从 SRC 替换 .c 得来,
然后直接在以OBJ作为依赖, 假设已经得到所有 OBJ文件, 即可直接使用 CC
进行链接即可, 而 OBJ 列表中的内容则由 %.o: %.c 一个一个匹配生成.
最后清除所有中间文件也十分的简单, 直接 rm $(OBJS) 即可.
改进版,
把输出中间文件都放到指定目录
Makefile 文件如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| SRCS = main.c src/add.c
OBJS = $(addprefix build/, $(SRCS:%.c=%.o))
CFLAGS = -Iinc
all: $(OBJS) CC $(CFLAGS) $(OBJS) -o build/a.exe
build/%.o: %.c @mkdir -p $(dir $@) CC $(CFLAGS) -c $< -o $@
clean: rm -fR build
|
使用addprefix命令将src列表转obj时在每一项添加build/, 当然,
在推导时不要忘了前缀 build/%.c:%.o
更进一步, 自动扫描源文件
Makefile 文件如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| SRCS += $(wildcard src/*.c *.c)
OBJS = $(addprefix build/, $(SRCS:%.c=%.o))
CFLAGS = -Iinc
all: $(OBJS) CC $(CFLAGS) $(OBJS) -o build/a.exe
build/%.o: %.c @mkdir -p $(dir $@) CC $(CFLAGS) -c $< -o $@
clean: rm -fR build
|
使用 wildcard 命令可以帮我们自动收集指定目录下的源文件,
就不需要一个一个输入了
多文件构建
这里以 LVGL 模拟器仿真工程为例, 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
| LVGL_DIR = . LVGL_DIR_NAME = lvgl
include lv_drivers/lv_drivers.mk include lvgl/lvgl.mk include lv_examples/lv_demo.mk
CSRCS += $(wildcard main/*.c *.c)
INCS += -I. -Imain -Ilvgl
OBJS = $(addprefix build/, $(CSRCS:%.c=%.o))
CFLAGS += -DLV_CONF_INCLUDE_SIMPLE -DLV_LVGL_H_INCLUDE_SIMPLE -DLV_DEMO_CONF_INCLUDE_SIMPLE $(INCS)
all: $(OBJS) CC $(CFLAGS) $(OBJS) -o a.exe -lSDL2 -lm -mwindows
build/%.o: %.c @mkdir -p $(dir $@) CC $(CFLAGS) -c $< -o $@
clean: rm -fR build
|
>
需要用到3个模块, lv_drivers、lvgl、lv_examples
而这些模块的编译信息都在模块的根目录下.mk 文件里, 比如 lv_drivers.mk
文件内容如下: >
1 2 3 4 5 6 7
| LV_DRIVERS_DIR_NAME ?= lv_drivers
CSRCS += $(wildcard $(LVGL_DIR)/$(LV_DRIVERS_DIR_NAME)/*.c) CSRCS += $(wildcard $(LVGL_DIR)/$(LV_DRIVERS_DIR_NAME)/wayland/*.c) CSRCS += $(wildcard $(LVGL_DIR)/$(LV_DRIVERS_DIR_NAME)/indev/*.c) CSRCS += $(wildcard $(LVGL_DIR)/$(LV_DRIVERS_DIR_NAME)/gtkdrv/*.c) CSRCS += $(wildcard $(LVGL_DIR)/$(LV_DRIVERS_DIR_NAME)/display/*.c)
|
> 它帮我们导入了该模块必要的 C
文件, 添加到全局变量 CSRCS 中, 同理 lvgl, lv_drivers 也一样, 我们只要
include 该make文件就可以了, 源文件有了, 头文件路径怎么解决呢?LVGL
的方案是在模块根目录有一个头文件, 该头文件 里面 #include
了必要的头各种必要的头文件, 因此要用到该模块时的任何函数接口, 只要
#include 模块的根头文件就可以了.
这样我们写程序只要关注自己的头文件路径就可以了。
Cubemx 生成的 STM32 项目
先列出所有源文件,再根据源文件生成目标文件列表,
对目标文件自动推导即可完成。但是需要注意的是,
这里的目标文件缺失了源文件的路径信息, 因此
$(BUILD_DIR)/%.o : %.c
这一句中, make 推导时
%
.o 已经是非完整路径的文件名, 这导致相应的需求文档中
%.c
也没有路径信息,
那么make如何确定是哪个源文件生成的呢?没错,
vpath %.c $(sort $(dir $(SOURCES)))
起到了关键作用,
他将告诉make在哪些目录查找源文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| BUILD_DIR = build
# 1. 手工生成源文件列表 SOURCES += src/add.c SOURCES += src/main.c
# 2. 源文件列表 -> 目标文件列表, 并且目标文件重定向到 build OBJECTS += $(addprefix $(BUILD_DIR)/, $(notdir $(SOURCES:.c=.o))) # 3. 设置 vpath, 对所有源文件的 '所在目录' 进行注册 vpath %.c $(sort $(dir $(SOURCES)))
# 4. $(OBJECTS) 作为需求, make 将自动推导如何生成 all: $(OBJECTS) cc $(OBJECTS)
$(BUILD_DIR)/%.o : %.c | $(BUILD_DIR) cc -c $< -o $@
$(BUILD_DIR) : @mkdir $@
clean : rm -rf build
|
> make 自动推导原理,
先看目标文件是否匹配, 然后看需求文件是否匹配,
如果目标文件匹配OK,将目标文件的 % 带到需求文件中去,如果有文件能够匹配,
则可以顺利生成。
LVGL 官方 Example
其原理是利用 shell 命令 find 查找来获取所有源文件列表,
然后利用源文件生成目标文件列表,目标文件都放到build目录下,而且对源文件的目录信息进行保留。因为源文件和目标文件的名称只相差一个
build目录, 因此直接使用 $(BUILD_DIR)/%.o : %.c
让 make
自行推导生成。关于保留目录信息,只需对目标文件所在文件夹进行 -p 的 mkdir
即可实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| SRC_DIR = src BUILD_DIR = build
# 1. 递归查找 'SRC_DIR' 下所有源文件 SOURCES := $(shell find $(SRC_DIR) -type f -name '*.c' -not -path '*/\.*') # 2. 源文件列表 -> 目标文件列表, 并且目标文件重定向到 build, SOURCES 是包含路径的 OBJECTS := $(addprefix $(BUILD_DIR)/, $(SOURCES:.c=.o))
# 3. $(OBJECTS) 作为需求, make 将自动推导如何生成 all: $(OBJECTS) @echo $(OBJECTS)
$(BUILD_DIR)/%.o : %.c @mkdir -p $(dir $@) cc -c $< -o $@
clean : rm -rf build
|
> 值得注意的是, 上面的 SRC_DIR
变量是可以多目录指定的, 并且还可以精确控制参加编译的目录。 >
可以看到,这里不需要指定 vpath 是因为需求文件的路径是完整的,
也就是顺利匹配的。
多 Makefile 文件
include
有很多项目的构建框架都是使用 include 的方式来实现 SDK 与应用分离,
其原理是在应用代码处的 Makefile 通过 include SDK_PATH 下的 Makefile
框架来提供统一的构建功能,从而实现在应用目录下进行的代码编译。
1) TinyUSB
TinyUSB 的每一个 example 就相当于一个完整的工程项目,每个 example 的
Makefile 基本内容如下:
1 2 3 4 5 6 7 8 9 10 11
| include ../../make.mk
INC += \ src \ $(TOP)/hw \
EXAMPLE_SOURCE += $(wildcard src/*.c) SRC_C += $(addprefix $(CURRENT_PATH)/, $(EXAMPLE_SOURCE))
include ../../rules.mk
|
在开头处添加一个 include 来导入一些环境和变量,
然后对给出当前工程的相关编译信息, 这里主要是向变量 INC
中添加头文件路径,向 SRC_C 中添加源文件信息, 这其中包含了 main
函数。
最后在结尾处添加一个 include 来完成真正的编译。
模板
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
| TARGET = awa_pc BUILD_DIR = build
SRCS += $(wildcard src/*.c port/*.c test/*.c) SRCS += cJSON/cJSON.c
OBJS = $(addprefix $(BUILD_DIR)/, $(notdir $(SRCS:.c=.o))) vpath %.c $(sort $(dir $(SRCS)))
CFLAGS += -Isrc -IcJSON -Iport -Itest CFLAGS += -g -O0 -Werror -Wall
LDFLAGS += -lpthread -static
$(BUILD_DIR)/$(TARGET): $(OBJS) @gcc $(OBJS) $(LDFLAGS) -o $@ @echo Output $@
$(BUILD_DIR)/%.o: %.c | $(BUILD_DIR) @gcc $(CFLAGS) -c $< -o $@ @echo CC $<
run: cd build && start awa_pc.exe
$(BUILD_DIR) : @mkdir $@
clean: rm -fR build
|
源文件来自于