找回密码
立即注册
搜索
发回帖 发新帖

5253

积分

0

好友

677

主题
发表于 昨天 23:20 | 查看: 7| 回复: 0

这是一个参考 Linux 内核 Makefile 思路整理的通用 Makefile 模板,只要简单配置就能直接用到项目里。下面先看它解决了哪些问题。

何为通用 Makefile?

一个通用的 Makefile 应具备这些能力:

  • 支持多个目录、多层目录、多个文件;
  • 支持给所有文件设置编译选项;
  • 支持给某个目录设置编译选项;
  • 支持给某个文件单独设置编译选项;
  • 简单、好用。

通用 Makefile 的设计思想

首先在 Makefile 文件中确定要编译的文件、目录。比如要编译当前目录下的 main.c 以及 a 目录下的某些文件,只需要在对应 Makefile 中添加:

obj-y += main.o
obj-y += a/

然后由 Makefile.build 设置编译规则。Makefile 文件总是被 Makefile.build 包含,所以主要解决三个问题:

  1. 怎么编译子目录?进入子目录编译:
$(subdir-y):
    make -C $@ -f $(TOPDIR)/Makefile.build
  1. 怎么编译当前目录中的文件?
%.o : %.c
    $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<
  1. 当前目录下的 .o 和子目录下的 built-in.o 要打包起来:
built-in.o : $(cur_objs) $(subdir_objs)
    $(LD) -r -o $@ $^

最后在顶层 Makefile 中把顶层目录的 built-in.o 链接成 APP:

$(TARGET) : built-in.o
    $(CC) $(LDFLAGS) -o $(TARGET) built-in.o

这里的 $@、$<、$^ 是 make 自动变量,注意不要和 shell 变量混淆。

通用 Makefile 设计说明

本模板中的 Makefile 文件分为三类:

  1. 顶层目录的 Makefile
  2. 顶层目录的 Makefile.build
  3. 各级子目录的 Makefile

各级子目录的 Makefile

它最简单,形式如下:

EXTRA_CFLAGS  :=
CFLAGS_file.o :=

obj-y += file.o
obj-y += subdir/
  • obj-y += file.o:把当前目录下的 file.c 编进程序;
  • obj-y += subdir/:进入 subdir 子目录寻找文件编进程序,具体文件由 subdir 目录下的 Makefile 决定;
  • EXTRA_CFLAGS:给当前目录下所有文件(不含子目录)设置额外编译选项,可以不设置;
  • CFLAGS_xxx.o:给当前目录下的 xxx.c 设置自己的编译选项,同样可以不设置。

注意:

  1. subdir/ 中的斜杠 / 不可省略;
  2. 顶层 Makefile 中的 CFLAGS 在编译任意一个 .c 文件时都会使用;
  3. CFLAGS、EXTRA_CFLAGS、CFLAGS_xxx.o 三者共同组成 xxx.c 的编译选项。

顶层目录的 Makefile

除了通过 obj-y 指定根目录下要编进程序的文件、子目录外,顶层 Makefile 主要定义工具链前缀 CROSS_COMPILE、编译参数 CFLAGS、链接参数 LDFLAGS,这些参数就是文件中用 export 导出的各变量。

CROSS_COMPILE =
AS      = $(CROSS_COMPILE)as
LD      = $(CROSS_COMPILE)ld
CC      = $(CROSS_COMPILE)gcc
CPP     = $(CC) -E
AR      = $(CROSS_COMPILE)ar
NM      = $(CROSS_COMPILE)nm

STRIP   = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump

export AS LD CC CPP AR NM
export STRIP OBJCOPY OBJDUMP

CFLAGS := -Wall -O2 -g
CFLAGS += -I $(shell pwd)/include

LDFLAGS :=

export CFLAGS LDFLAGS

TOPDIR := $(shell pwd)
export TOPDIR

TARGET := test

obj-y += main.o
obj-y += sub.o
obj-y += a/

all : start_recursive_build $(TARGET)
    @echo $(TARGET) has been built!

start_recursive_build:
    make -C ./ -f $(TOPDIR)/Makefile.build

$(TARGET) : start_recursive_build
    $(CC) -o $(TARGET) built-in.o $(LDFLAGS)

clean:
    rm -f $(shell find -name "*.o")
    rm -f $(TARGET)

distclean:
    rm -f $(shell find -name "*.o")
    rm -f $(shell find -name "*.d")
    rm -f $(TARGET)

顶层目录的 Makefile.build

这是最复杂的部分,它的功能就是把某个目录及它的所有子目录中、需要编进程序去的文件都编译出来,打包为built-in.o。

PHONY := __build

__build:

obj-y :=
subdir-y :=
EXTRA_CFLAGS :=

include Makefile

# obj-y := a.o b.o c/ d/
# $(filter %/, $(obj-y))   : c/ d/
# __subdir-y  : c d
# subdir-y    : c d
__subdir-y  := $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y    += $(__subdir-y)

# c/built-in.o d/built-in.o
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)

# a.o b.o
cur_objs := $(filter-out %/, $(obj-y))
dep_files := $(foreach f,$(cur_objs),.$(f).d)
dep_files := $(wildcard $(dep_files))

ifneq ($(dep_files),)
include $(dep_files)
endif

PHONY += $(subdir-y)

__build : $(subdir-y) built-in.o

$(subdir-y):
    make -C $@ -f $(TOPDIR)/Makefile.build

built-in.o : $(subdir-y) $(cur_objs)
    $(LD) -r -o $@ $(cur_objs) $(subdir_objs)

dep_file = .$@.d

%.o : %.c
    $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<

.PHONY : $(PHONY)

怎么使用这套 Makefile

4.1 放入顶层文件

把顶层 Makefile 和 Makefile.build 文件放入项目工程的顶层目录,然后在各自子目录下分别创建一个空白的 Makefile 文件。

4.2 确定编译哪些源文件

修改顶层目录和各自子目录 Makefile 的 obj-y:

obj-y += xxx.o
obj-y += yyy/

obj-y += xxx.o 表示要编译当前目录下的 xxx.c;obj-y += yyy/ 表示要编译当前目录下的 yyy 子目录。

4.3 确定编译选项、链接选项

  1. 修改顶层目录 Makefile 的 CFLAGS 和 LDFLAGS:CFLAGS 是编译所有 .c 文件时都要用的编译选项;LDFLAGS 是链接最后应用程序时的链接选项。
  2. 修改各自子目录下的 Makefile:EXTRA_CFLAGS 给当前目录下所有文件(不含子目录)设置额外编译选项,可以不设置;CFLAGS_xxx.o 给当前目录下的 xxx.c 设置它自己的编译选项,可以不设置。

4.4 使用哪个编译器

修改顶层目录 Makefile 的 CROSS_COMPILE,用来指定工具链的前缀,比如 arm-linux-。

4.5 确定应用程序的名字

修改顶层目录 Makefile 的 TARGET,用来指定编译出来的程序名字。

4.6 编译或清除

执行 make 编译,执行 make clean 清除,执行 make distclean 彻底清除。

这套模板最核心的思路是:通过 obj-y 声明目标,再由 Makefile.build 递归处理子目录,最后把各层 built-in.o 链接成最终程序。把它放到嵌入式项目里,能省去不少重复的 Makefile 工作。




上一篇:Makefile语法规则与常用函数速查手册
下一篇:牛顿法 Hessian 矩阵与二阶泰勒展开:PyTorch 实现与收敛分析
您需要登录后才可以回帖 登录 | 立即注册

关闭

站长推荐上一条 /1 下一条

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-10-28 03:27 , Processed in 0.082392 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表