本溪市网站建设_网站建设公司_支付系统_seo优化
2025/12/21 14:24:59 网站建设 项目流程

韦东山Linux - 通用Makefile解析

# Makefile文件 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)
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)

这是一个经典的Kbuild 风格(Linux 内核构建系统)的Makefile架构。这个系统将项目的配置信息(Makefile) 与通用构建规则(Makefile.build) 彻底分离,并实现了递归编译自动依赖追踪

下面将对MakefileMakefile.build进行详细的逐行解析。


📄 一、顶层Makefile(配置与入口)

这个文件位于项目的顶层,负责定义编译工具、全局选项、目标列表,并作为启动递归构建的入口 1。

  1. 工具链变量定义
CROSS_COMPILE = AS = $(CROSS_COMPILE)as LD = $(CROSS_COMPILE)ld # ... (其他工具变量如 CC, CPP, AR, NM, STRIP, OBJCOPY, OBJDUMP)
  • 定义工具链前缀CROSS_COMPILE。如果为空,则使用本地工具(如gcc,as)。
  • 基于CROSS_COMPILE定义所有工具的完整名称(如CCgcc)。
  • 变量导出(Export)
export AS LD CC CPP AR NM export STRIP OBJCOPY OBJDUMP
  • export:将这些变量(如CC)导出到Shell 环境。这样,在Makefile规则中执行的任何命令(如gcc)都能访问这些变量的值,确保所有子目录和递归调用都使用相同的工具链。
  • 编译与链接选项
CFLAGS := -Wall -O2 -g CFLAGS += -I $(shell pwd)/include LDFLAGS := export CFLAGS LDFLAGS
  • CFLAGS:定义全局 C 编译选项。
    • := -Wall -O2 -g:使用即时变量定义-Wall(所有警告)、-O2(优化级别)、-g(生成调试信息)。
    • += -I $(shell pwd)/include:添加一个搜索头文件的路径,即项目根目录下的include文件夹。
  • LDFLAGS:定义链接选项(当前为空)。
  • export:将全局选项导出,确保在所有递归子目录中,编译和链接都使用这些统一的选项。
  • 路径与目标定义
TOPDIR := $(shell pwd) export TOPDIR TARGET := test obj-y += main.o obj-y += sub.o obj-y += a/
  • TOPDIR:定义项目的顶层目录的绝对路径,并导出。这对于子目录调用Makefile.build时查找文件路径至关重要。
  • TARGET:定义最终可执行文件名为test
  • obj-y:定义了本层目录的构建配置:
    • main.o,sub.o:需要编译的文件。
    • 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)
  • all:默认目标,依赖于start_recursive_build$(TARGET)
  • start_recursive_build递归构建的入口
    • make -C ./ -f $(TOPDIR)/Makefile.build:启动一个新的make进程。它告诉make切换到当前目录 (./),并使用位于$(TOPDIR)Makefile.build作为规则文件。这将触发Makefile.build开始执行本层和子目录的编译。
  • $(TARGET)(test):最终的链接目标。
    • 依赖于start_recursive_build(确保所有.o文件都已编译,并被打包到built-in.o中)。
    • 命令:将built-in.o(由Makefile.build聚合而成) 链接成最终的test可执行文件。
  • 清理目标
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)
  • clean:删除所有.o文件和最终目标test
  • distclean:更彻底的清理,还删除了自动生成的依赖文件 (.d文件)。

📄 二、通用规则文件Makefile.build

这个文件包含了所有通用的编译逻辑、递归规则和文件聚合机制,它不包含任何特定于项目的配置

  1. 变量初始化与包含
PHONY := __build __build: obj-y := subdir-y := EXTRA_CFLAGS := include Makefile
  • PHONY__build__buildMakefile.build中的核心目标,它代表“构建当前目录的所有内容”,被声明为伪目标 (PHONY)。
  • 变量清空obj-y,subdir-y,EXTRA_CFLAGS被清空,以确保它们仅包含当前目录 (Makefile) 中定义的值。
  • include Makefile关键步骤。此时make停止,转而读取当前目录下的Makefile(即顶层Makefile或子目录下的Makefile)。这个include会导入obj-y等配置变量。
    • 例如,在顶层运行时,obj-y变为main.o sub.o a/
  • 目录和文件分离
__subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y))) subdir-y += $(__subdir-y) subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o) cur_objs := $(filter-out %/, $(obj-y))
  • __subdir-y:将obj-y中所有以/结尾的项(如a/)筛选出来 (filter %/, ...),并去除斜杠 (patsubst %/,%, ...),得到子目录名列表 (a)。
  • subdir-y:存储需要递归的子目录列表 (a)。
  • subdir_objs:生成子目录构建完成后的聚合目标文件列表(a/built-in.o)。
  • cur_objs:将obj-y/结尾的项筛选出来,得到本层需要编译的.o文件列表(main.o sub.o)。
  • 自动依赖追踪 (Auto-Dependency)
dep_files := $(foreach f,$(cur_objs),.$(f).d) dep_files := $(wildcard $(dep_files)) ifneq ($(dep_files),) include $(dep_files) endif
  • 生成依赖文件名的理论列表 (.main.o.d,.sub.o.d)。

  • 使用wildcard查找实际存在的依赖文件。

  • 如果找到任何.d文件 (ifneq), 则将其include进来,实现了自动依赖追踪:一旦头文件有改动,make就能通过.d文件中的规则知道哪些.o文件需要重编译。

    递归与聚合规则

__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)
  • __build:核心目标,它依赖于所有子目录的完成 ($(subdir-y)) 和本层聚合文件 (built-in.o)。
  • $(subdir-y)(a):递归规则。对于每个子目录a
    • make -C $@ -f $(TOPDIR)/Makefile.build递归调用make。切换到子目录 (-C $@,即a/),并要求子目录再次使用Makefile.build进行构建。
  • built-in.o聚合规则
    • 依赖于所有子目录的聚合文件 ($(subdir-y)) 和本层.o文件 ($(cur_objs))。
    • 命令:$(LD) -r -o $@ ...:使用链接器 (LD) 的-r选项(可重定位输出),将本层所有编译出的.o文件 ($(cur_objs)) 以及所有子目录的聚合文件 ($(subdir_objs))打包成一个大的聚合文件built-in.o
  • 编译模式规则

Makefile

dep_file = .$@.d %.o : %.c $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<
  • dep_file:定义了当前目标.o对应的.d文件名 (.$@.d)。
  • %.o : %.c:通用的编译规则。
    • 调用gcc($(CC))。
    • 使用全局 ($(CFLAGS)), 额外的 ($(EXTRA_CFLAGS)), 局部 ($(CFLAGS_$@)) 选项。
    • -Wp,-MD,$(dep_file):这是 GCC 产生依赖的另一种写法。-MD标志生成依赖,并将其写入$(dep_file)中。
    • -c -o $@ $<:正常的编译命令。

总结工作流

  1. 顶层Makefile启动make -C ./ -f Makefile.build

  2. Makefile.build读取Makefile中的配置 (obj-y = main.o sub.o a/)

  3. Makefile.build编译main.o,sub.o(通过%.o : %.c规则)

  4. Makefile.build发现a/,执行递归调用make -C a/ -f Makefile.build

  5. 子目录重复步骤 2-4,并最终生成a/built-in.o.

  6. 回到顶层,Makefile.buildmain.o,sub.o, 和a/built-in.o打包成顶层的built-in.o

  7. 顶层Makefile将顶层的built-in.o链接成最终的可执行文件test

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询