Makefile 解析

最简约的

Makefile 文件如下

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 文件如下

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 文件如下

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 文件如下

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 文件内容如下:

 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在哪些目录查找源文件。

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 即可实现。

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 是因为需求文件的路径是完整的, 也就是顺利匹配的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,816评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,729评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,300评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,780评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,890评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,084评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,151评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,912评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,355评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,666评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,809评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,504评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,150评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,121评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,628评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,724评论 2 351

推荐阅读更多精彩内容