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 相关问题还有啥疑问吗?

相关推荐
森G1 天前
七、04ledc-sdk--------makefile有变化
linux·c语言·arm开发·c++·ubuntu
VekiSon1 天前
Linux内核驱动——杂项设备驱动与内核模块编译
linux·c语言·arm开发·嵌入式硬件
AI+程序员在路上1 天前
Nand Flash与EMMC区别及ARM开发板中的应用对比
arm开发
17(无规则自律)1 天前
深入浅出 Linux 内核模块,写一个内核版的 Hello World
linux·arm开发·嵌入式硬件
梁洪飞2 天前
内核的schedule和SMP多核处理器启动协议
linux·arm开发·嵌入式硬件·arm
代码游侠2 天前
学习笔记——Linux字符设备驱动
linux·运维·arm开发·嵌入式硬件·学习·架构
syseptember3 天前
Linux网络基础
linux·网络·arm开发
代码游侠3 天前
学习笔记——Linux字符设备驱动开发
linux·arm开发·驱动开发·单片机·嵌入式硬件·学习·算法
程序猿阿伟3 天前
《Apple Silicon与Windows on ARM:引擎原生构建与模拟层底层运作深度解析》
arm开发·windows
wkm9563 天前
在arm64 ubuntu系统安装Qt后编译时找不到Qt3DExtras头文件
开发语言·arm开发·qt