makefile中include *.d文件的作用

这行 Makefile 代码:

makefile 复制代码
# Include the dependency files.
-include $(shell mkdir $(OBJDIR) 2>/dev/null) $(wildcard $(OBJDIR)/*.d)

在嵌入式开发(如你的 STM32 项目)中非常常见,主要用于自动依赖管理 ,确保 Make 能够跟踪源文件和头文件的依赖关系,并动态创建目标目录(如 obj/)。以下我将详细解释这行代码的作用、原理以及在你的项目中的具体意义。


1. 代码分解

这行代码包含三个关键部分:

  • -include

    • GNU Make 的指令,用于包含其他文件(如 Makefile 或依赖文件)。
    • 前缀 - 表示即使包含的文件不存在,make 也不会报错,而是继续执行(与普通 include 的区别)。
  • $(shell mkdir $(OBJDIR) 2>/dev/null)

    • $(shell ...) 是 Make 的函数,执行 shell 命令并返回其输出。
    • mkdir $(OBJDIR) 创建目标目录(如 obj/),$(OBJDIR) 是一个变量,通常定义为输出目录(例如 OBJDIR = obj)。
    • 2>/dev/null 重定向标准错误(stderr)到 /dev/null,抑制错误输出(如"目录已存在")。
    • 输出mkdir 通常不产生标准输出,因此 $(shell mkdir $(OBJDIR) 2>/dev/null) 返回空字符串。
  • $(wildcard $(OBJDIR)/*.d)

    • $(wildcard ...) 是 Make 的函数,查找文件系统中的文件,匹配指定模式。
    • $(OBJDIR)/*.d 查找 $(OBJDIR) 目录下所有 .d 文件(如 obj/main.dobj/util.d)。
    • 输出 :返回匹配的 .d 文件列表,如果目录不存在或没有 .d 文件,返回空。

2. 整体作用

这行代码的目的是:

  1. 创建输出目录

    • $(shell mkdir $(OBJDIR) 2>/dev/null) 确保 $(OBJDIR) 目录存在,准备存放 .o 文件和 .d 文件。
    • 静默处理错误(即使目录已存在),避免不必要的错误信息。
  2. 包含依赖文件

    • $(wildcard $(OBJDIR)/*.d) 查找所有 .d 文件(依赖文件),这些文件通常由编译器(如 arm-none-eabi-gcc)生成,记录源文件(.c)和头文件(.h)的依赖关系。
    • -include 将这些 .d 文件包含到 Makefile 中,使 make 知道哪些目标(如 .o 文件)依赖哪些头文件。
  3. 支持增量编译

    • 通过包含 .d 文件,make 可以检测头文件修改(如 config.h),自动重新编译受影响的 .c 文件。
    • 例如,如果 config.h 修改,make 会重新编译依赖它的 .c 文件,生成新的 .o 文件。
  4. 健壮性

    • -include 确保即使 $(OBJDIR).d 文件不存在,make 也不会报错,适合初次构建或清理后的场景。

3. 依赖文件(.d 文件)的生成与作用

(1).d 文件的生成
  • 生成方式

    • 编译器(如 arm-none-eabi-gcc)通过 -M-MD 选项生成 .d 文件。

    • 示例编译规则:

      makefile 复制代码
      $(OBJDIR)/%.o: %.c
          arm-none-eabi-gcc $(CFLAGS) -c $< -o $@ -MD
      • -MD:生成 .o 文件的同时,生成对应的 .d 文件(如 obj/main.d)。

      • .d 文件记录 .o 文件的依赖,例如:

        makefile 复制代码
        obj/main.o: main.c config.h utils.h

        表示 main.o 依赖 main.cconfig.hutils.h

  • 内容

    • .d 文件是 Makefile 格式的片段,指定目标(.o 文件)及其依赖(.c 文件和 #include 的头文件)。
    • 由编译器的预处理器自动生成,包含所有直接和间接的 #include 文件。
(2).d 文件的作用
  • 自动依赖跟踪
    • -include $(wildcard $(OBJDIR)/*.d) 将所有 .d 文件包含到 Makefile 中,告诉 make 哪些 .o 文件依赖哪些头文件。
    • 当头文件(如 config.h)修改时,make 根据 .d 文件的依赖关系,重新编译受影响的 .c 文件。
  • 增量编译
    • 避免重新编译未受影响的文件,提高构建效率。
    • 例如,修改 config.h 只重新编译依赖它的 .c 文件,而不影响其他文件。

4. 为什么需要这行代码?

在你的 STM32 项目中,这行代码的作用尤其重要,原因如下:

  • 确保目录存在

    • STM32 项目通常将 .o.d 文件存储在 obj/ 目录(如 OBJDIR = obj)。
    • $(shell mkdir $(OBJDIR) 2>/dev/null) 确保 obj/ 目录在编译前存在,避免因目录缺失导致的错误。
  • 动态依赖管理

    • STM32 项目可能包含多个 .c 文件和复杂的头文件依赖(如 HAL 库的 stm32f4xx_hal.h)。
    • .d 文件自动跟踪这些依赖,无需手动在 Makefile 中列出所有头文件。
    • 例如,main.c 可能通过 #include "stm32f4xx_hal.h" 间接包含多个头文件,.d 文件会自动记录这些依赖。
  • 支持初次构建和清理

    • 初次构建时,obj/ 目录和 .d 文件可能不存在,-include 确保 make 不会因缺失文件报错。
    • 清理后(make clean 删除 obj/),这行代码重新创建目录并准备生成新的 .d 文件。
  • 多核编译兼容

    • 在多核编译(make -j)中,-include$(shell mkdir ...) 确保依赖文件和目录的正确性,避免竞争条件(如之前讨论的 ff_stm32.elf 不存在问题)。

5. 具体工作原理

以下是这行代码在你的 STM32 项目中的执行流程:

  1. 创建目录

    • $(shell mkdir $(OBJDIR) 2>/dev/null) 执行 mkdir obj(假设 OBJDIR = obj)。
    • 如果 obj/ 已存在,错误信息被 2>/dev/null 抑制。
    • 输出为空,-include 忽略此部分。
  2. 查找 .d 文件

    • $(wildcard $(OBJDIR)/*.d) 返回 obj/ 目录下所有 .d 文件的列表(如 obj/main.dobj/util.d)。
    • 如果目录为空或不存在,返回空列表。
  3. 包含 .d 文件

    • -include 将找到的 .d 文件作为 Makefile 片段加载。

    • 例如,obj/main.d 包含:

      makefile 复制代码
      obj/main.o: main.c config.h stm32f4xx_hal.h
    • make 将这些依赖合并到依赖图中,确保 obj/main.oconfig.hstm32f4xx_hal.h 修改时重新编译。

  4. 增量构建

    • 当运行 makemake 检查 .o 文件的依赖:
      • 如果 main.cconfig.h 的时间戳比 obj/main.o 新,触发重新编译。
      • 如果 .d 文件不存在(初次构建),-include 确保不报错,编译生成新的 .d 文件。

6. 改进建议

你的代码已经很标准,但在 STM32 项目中可以做以下优化:

  • 使用 mkdir -p

    • 如果 $(OBJDIR) 包含子目录(如 obj/src),mkdir -p 能递归创建目录:

      makefile 复制代码
      $(shell mkdir -p $(OBJDIR) 2>/dev/null)
  • 检查 $(OBJDIR) 定义

    • 确保 OBJDIR 已定义,避免空目录导致意外行为:

      makefile 复制代码
      OBJDIR = obj
      ifneq ($(OBJDIR),)
          $(shell mkdir -p $(OBJDIR) 2>/dev/null)
          -include $(wildcard $(OBJDIR)/*.d)
      endif
  • 使用 -MMD 替代 -MD

    • -MD 生成依赖文件,包括系统头文件(如 <stdint.h>)。

    • -MMD 只包含用户头文件(如 config.h),减少不必要的依赖:

      makefile 复制代码
      $(OBJDIR)/%.o: %.c
          @echo "Compiling $<"
          $(CC) $(CFLAGS) -c $< -o $@ -MMD
  • 完整 Makefile 示例

    makefile 复制代码
    CC = arm-none-eabi-gcc
    OBJCOPY = arm-none-eabi-objcopy
    OBJDUMP = arm-none-eabi-objdump
    SIZE = arm-none-eabi-size
    OBJDIR = obj
    CSRC = $(wildcard *.c)
    OBJS = $(patsubst %.c, $(OBJDIR)/%.o, $(CSRC))
    LST = $(OBJDIR)/ff_stm32.lst
    HEX = $(OBJDIR)/ff_stm32.hex
    CFLAGS = -mcpu=cortex-m4 -mthumb -Wall -g -O0 -I. -IDrivers -ICore/Inc
    LDFLAGS = -mcpu=cortex-m4 -mthumb -Wl,--gc-sections -Tlinker_script.ld -lc -lm -lnosys
    
    # 确保 OBJDIR 存在
    $(shell mkdir -p $(OBJDIR) 2>/dev/null)
    
    # 包含自动依赖
    -include $(wildcard $(OBJDIR)/*.d)
    
    all: $(OBJDIR)/ff_stm32.elf $(LST) $(HEX) size
    
    $(OBJDIR)/ff_stm32.elf: $(OBJS)
        @echo "Linking $@"
        $(CC) $(LDFLAGS) $^ -o $@
        @ls -l $@
    
    $(OBJDIR)/%.o: %.c
        @echo "Compiling $<"
        $(CC) $(CFLAGS) -c $< -o $@ -MMD
    
    $(LST): $(OBJDIR)/ff_stm32.elf
        @echo "Generating $@"
        $(OBJDUMP) -h -S -C $< > $@
    
    $(HEX): $(OBJDIR)/ff_stm32.elf
        @echo "Generating $@"
        $(OBJCOPY) -O ihex $< $@
    
    size: $(OBJDIR)/ff_stm32.elf
        @echo "Analyzing size of $<"
        $(SIZE) -A --radix=16 $<
    
    flash: $(HEX)
        @echo "Flashing $<"
        openocd -f interface/stlink.cfg -f target/stm32f4x.cfg -c "program $< verify reset exit"
    
    clean:
        @echo "Cleaning project..."
        -rm -rf $(OBJDIR)
    
    .PHONY: all size flash clean

7. 回答你的问题

  • "-include $(shell mkdir $(OBJDIR) 2>/dev/null) $(wildcard $(OBJDIR)/*.d) 起到什么作用?"
    • 创建目录
      • $(shell mkdir $(OBJDIR) 2>/dev/null) 确保 $(OBJDIR)(如 obj/)存在,用于存储 .o.d 文件,静默处理错误。
    • 包含依赖文件
      • $(wildcard $(OBJDIR)/*.d) 查找所有 .d 文件,-include 将其包含到 Makefile 中,自动跟踪 .c 文件和头文件的依赖关系。
    • 增量编译
      • 使 make 在头文件(如 config.h)修改时只重新编译受影响的 .c 文件,提高效率。
    • 健壮性
      • -include 确保初次构建或清理后(无 .d 文件)不会报错。
    • STM32 项目背景
      • 支持复杂的头文件依赖(如 HAL 库),确保编译过程高效且正确。

8. 调试建议

  1. 验证 .d 文件

    bash 复制代码
    cat obj/main.d

    检查是否正确列出依赖(如 main.cconfig.h)。

  2. 测试增量编译

    bash 复制代码
    touch config.h
    make

    确认只重新编译依赖 config.h.c 文件。

  3. 检查目录创建

    bash 复制代码
    ls -d obj/

    确保 obj/ 存在。

  4. 多核编译

    bash 复制代码
    make -j

    确认依赖文件在并行构建中正常工作。

如果你有具体问题(例如,.d 文件未生成、依赖不正确),或想优化 Makefile,可以分享更多细节,我可以进一步帮你!😄 你对依赖管理或其他 STM32 相关问题还有啥疑问吗?

相关推荐
only-lucky1 小时前
交叉编译ARM环境
arm开发
風月同天11 小时前
Keil-C51 与 Keil -ARM 项目工程兼容的方法
arm开发
吻等离子11 小时前
Linux ARM 平台 C 语言操作 Excel 文件的常用库与工具汇总(支持 xls 和 xlsx)
linux·c语言·arm开发
SIKDD1 天前
arm v3 smmu 图示
arm开发
CLO_se_2 天前
嵌软面试——ARM Cortex-M寄存器组
arm开发·面试·职场和发展
亿道电子Emdoor2 天前
【ARM】ARM架构的发展和相关架构
arm开发·架构·arm
XINVRY-FPGA4 天前
XCZU4EV-1FBVB900E Xilinx FPGA AMD Zynq UltraScale+ MPSoC EV(Embedded Vision)
arm开发·嵌入式硬件·计算机视觉·fpga开发·硬件架构·硬件工程·fpga
学不动CV了4 天前
单片机ADC采集机理层面详细分析(二)
c语言·arm开发·stm32·单片机·嵌入式硬件·开源·51单片机
学不动CV了4 天前
51核和ARM核单片机OTA实战解析(二)
c语言·arm开发·stm32·单片机·嵌入式硬件·51单片机