如何让 Makefile 自动重建包含修改后的头文件的源文件? (在 C/C++ 中)

Posted

技术标签:

【中文标题】如何让 Makefile 自动重建包含修改后的头文件的源文件? (在 C/C++ 中)【英文标题】:How can I have a Makefile automatically rebuild source files that include a modified header file? (In C/C++) 【发布时间】:2010-09-22 18:55:29 【问题描述】:

我有以下生成文件,用于构建我正在处理的程序(实际上是内核)。它从头开始,我正在学习这个过程,所以它并不完美,但我认为它在这一点上已经足够强大,足以满足我编写 makefile 的经验水平。

AS  =   nasm
CC  =   gcc
LD  =   ld

TARGET      =   core
BUILD       =   build
SOURCES     =   source
INCLUDE     =   include
ASM         =   assembly

VPATH = $(SOURCES)

CFLAGS  =   -Wall -O -fstrength-reduce -fomit-frame-pointer -finline-functions \
            -nostdinc -fno-builtin -I $(INCLUDE)
ASFLAGS =   -f elf

#CFILES     =   core.c consoleio.c system.c
CFILES      =   $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
SFILES      =   assembly/start.asm

SOBJS   =   $(SFILES:.asm=.o)
COBJS   =   $(CFILES:.c=.o)
OBJS    =   $(SOBJS) $(COBJS)

build : $(TARGET).img

$(TARGET).img : $(TARGET).elf
    c:/python26/python.exe concat.py stage1 stage2 pad.bin core.elf floppy.img

$(TARGET).elf : $(OBJS)
    $(LD) -T link.ld -o $@ $^

$(SOBJS) : $(SFILES)
    $(AS) $(ASFLAGS) $< -o $@

%.o: %.c
    @echo Compiling $<...
    $(CC) $(CFLAGS) -c -o $@ $<

#Clean Script - Should clear out all .o files everywhere and all that.
clean:
    -del *.img
    -del *.o
    -del assembly\*.o
    -del core.elf

我对这个 makefile 的主要问题是,当我修改包含一个或多个 C 文件的头文件时,不会重新构建 C 文件。我可以通过让我的所有头文件成为我所有 C 文件的依赖项来很容易地解决这个问题,但这会在我更改/添加头文件时有效地导致项目的完全重建,这不是很优雅。

我想要的只是重建我更改的头文件包含的C文件,并重新链接整个项目。我可以通过使所有头文件成为目标的依赖项来进行链接,但我不知道如何使 C 文件在其包含的头文件较新时失效。

我听说 GCC 有一些命令可以实现这一点(因此 makefile 可以以某种方式确定哪些文件需要重新构建),但我终其一生都无法找到一个实际的实现示例来查看。有人可以在生成文件中发布一个可以启用此行为的解决方案吗?

编辑:我应该澄清一下,我熟悉将单个目标放入并让每个 target.o 都需要头文件的概念。这要求我每次在某处包含头文件时都编辑makefile,这有点痛苦。我正在寻找一种可以自行派生头文件依赖项的解决方案,我很确定我在其他项目中看到过。

【问题讨论】:

【参考方案1】:

您必须为每个 C 文件创建单独的目标,然后将头文件列为依赖项。您仍然可以使用您的通用目标,然后只需放置 .h 依赖项,如下所示:

%.o: %.c
        @echo Compiling $<...
        $(CC) $(CFLAGS) -c -o $@ $<

foo.c: bar.h
# And so on...

【讨论】:

以下不能工作吗? : %.c : %.h @cassepipe:如果头文件与C文件同名,并且C文件只依赖于它所附的头文件,是的。 Fossil 项目中有一个名为makeheaders 的程序(vcs 替代 git)。它从您的 .h 生成和更新 per-file headers。因此,它可以与 %.c : %.h 完美结合,因为生成的 .h 与 .c 具有相同的名称。一种 KISSer 的方式,而不是制造的自动依赖生成混乱。您可以从 [源代码][1] 轻松编译 makeheaders。我还建议您阅读 [doc][2]。 [1]:fossil-scm.org/fossil/file/src/makeheaders.c [2]:fossil-scm.org/fossil/doc/trunk/src/makeheaders.html#H0006【参考方案2】:

除了@mipadi 所说的之外,您还可以探索使用“-M”选项来生成依赖项记录。您甚至可以将它们生成到一个单独的文件(可能是“depend.mk”)中,然后将其包含在 makefile 中。或者你可以找到一个'make depend'规则,它使用正确的依赖关系编辑makefile(谷歌术语:“不要删除这一行”并依赖)。

【讨论】:

【参考方案3】:

基本上,您需要动态创建makefile 规则以在头文件更改时重建目标文件。如果你使用 gcc 和 gnumake,这相当容易;只需输入以下内容:

$(OBJDIR)/%.d: %.c
        $(CC) -MM -MG $(CPPFLAGS) $< | sed -e 's,^\([^:]*\)\.o[ ]*:,$(@D)/\1.o $(@D)/\1.d:,' >$@

ifneq ($(MAKECMDGOALS),clean)
include $(SRCS:%.c=$(OBJDIR)/%.d)
endif

在你的 makefile 中。

【讨论】:

我有点理解这一点,除了(除了 -MM 和 -MG 标志是新的)我不明白正则表达式查找的神秘文本行的用途。这不会让我的队友开心... ^_^ 不过我会尝试一下,看看我是否有任何结果。 sed 是“流编辑器”的缩写,它可以在不需要使用文件的情况下修改文本流。它是一个标准的 Unix 工具,体积更小,速度更快,因此比 awk 或 perl 更常用。 啊,问题来了:我是在 Windows 下做的。【参考方案4】:

正如本网站其他地方已经指出的那样,请参阅此页面: Auto-Dependency Generation

简而言之,gcc 可以自动为你创建 .d 依赖文件,它们是包含你编译的 .c 文件的依赖的迷你 makefile 片段。 每次更改 .c 文件并编译时,都会更新 .d 文件。

除了将 -M 标志添加到 gcc 之外,您还需要在 makefile 中包含 .d 文件(就像 Chris 上面写的那样)。 页面中有一些更复杂的问题可以使用 sed 解决,但是当 make 抱怨无法构建不再存在的头文件时,您可以忽略它们并执行“make clean”以清除 .d 文件.

【讨论】:

我可能弄错了,但我认为 GCC 实际上已经添加了一个功能来尝试解决这个 sed 问题。查看gcc.gnu.org/onlinedocs/gcc-4.3.1/gcc/Preprocessor-Options.html 特别是-MP。 是的,-MP 从 GCC 3 开始就存在,存在于 clang 和 icc 中,并且不需要 sed。 bruno.defraine.net/techtips/makefile-auto-dependencies-with-gcc/… 答案中链接的最新版本包含使用 GCC 标志的示例。【参考方案5】:

这等效于Chris Dodd's answer,但使用不同的命名约定(巧合的是不需要sed 魔法。复制自a later duplicate。


如果您使用的是 GNU 编译器,编译器可以为您组装一个依赖项列表。 Makefile 片段:

depend: .depend

.depend: $(SOURCES)
        rm -f ./.depend
        $(CC) $(CFLAGS) -MM $^>>./.depend;

include .depend

还有工具makedepend,但我从来没有像gcc -MM那样喜欢它

【讨论】:

你为什么不拼出来源?不需要特别多的字符,并且不像“SRCS”那样混淆,可能看起来像首字母缩略词。 @HelloGoodbye 有点风格。我一直使用并经常看到SRCSOBJS。大多数时候我会同意,但每个人都应该知道这些是什么。 @sherrellbc 人们“应该”知道的和人们实际知道的通常是两件不同的事情:P【参考方案6】:

我相信mkdep 命令是您想要的。它实际上扫描 .c 文件中的 #include 行并为它们创建依赖关系树。我相信 Automake/Autoconf 项目默认使用这个。

【讨论】:

【参考方案7】:

您可以像其他人所说的那样添加一个“makedepend”命令,但为什么不让 gcc 同时创建依赖项和编译:

DEPS := $(COBJS:.o=.d)

-include $(DEPS)

%.o: %.c
    $(CC) -c $(CFLAGS) -MM -MF $(patsubst %.o,%.d,$@) -o $@ $<

“-MF”参数指定一个文件来存储依赖关系。

'-include' 开头的破折号告诉 Make 在 .d 文件不存在时继续(例如在第一次编译时)。

请注意,gcc 中似乎存在关于 -o 选项的错误。如果您将对象文件名设置为obj/_file__c.o,则生成的_file_.d 仍将包含_file_.o,而不是obj/_file_c.o

【讨论】:

这对我不起作用。例如。 makefile 生成并运行:g++ -c -Wall -Werror -MM -MF main.d -o main.o main.cpp 我得到了 main.d 文件,但 main.o 是 0 字节。但是 -MMD 标志似乎完全符合要求。所以我的工作makefile规则变成了:$(CC) -c $(CFLAGS) -MMD -o $@ $&lt;【参考方案8】:

没有一个答案对我有用。例如。 Martin Fido 的回答表明 gcc 可以创建依赖文件,但是当我尝试它为我生成空(零字节)对象文件时,没有任何警告或错误。这可能是一个 gcc 错误。我来了

$ gcc --version gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-16)

所以这是我的完整 Makefile 适合我;它是解决方案 + 其他任何人都没有提到的东西的组合(例如,“后缀替换规则”指定为 .cc.o:):

CC = g++
CFLAGS = -Wall -g -std=c++0x
INCLUDES = -I./includes/

# LFLAGS = -L../lib
# LIBS = -lmylib -lm

# List of all source files
SRCS = main.cc cache.cc

# Object files defined from source files
OBJS = $(SRCS:.cc=.o)

# # define the executable file 
MAIN = cache_test

#List of non-file based targets:
.PHONY: depend clean all

##  .DEFAULT_GOAL := all

# List of dependencies defined from list of object files
DEPS := $(OBJS:.o=.d)

all: $(MAIN)

-include $(DEPS)

$(MAIN): $(OBJS)
    $(CC) $(CFLAGS) $(INCLUDES) -o $(MAIN) $(OBJS) $(LFLAGS) $(LIBS)

#suffix replacement rule for building .o's from .cc's
#build dependency files first, second line actually compiles into .o
.cc.o:
    $(CC) $(CFLAGS) $(INCLUDES) -c -MM -MF $(patsubst %.o,%.d,$@) $<
    $(CC) $(CFLAGS) $(INCLUDES) -c -o $@ $<

clean:
    $(RM) *.o *~ $(MAIN) *.d

注意我使用了 .cc .. 上面的 Makefile 很容易针对 .c 文件进行调整。

还要注意这两行的重要性:

$(CC) $(CFLAGS) $(INCLUDES) -c -MM -MF $(patsubst %.o,%.d,$@) $<
$(CC) $(CFLAGS) $(INCLUDES) -c -o $@ $<

所以 gcc 被调用一次以首先构建一个依赖文件,然后实际编译一个 .cc 文件。对于每个源文件,依此类推。

【讨论】:

【参考方案9】:

更简单的解决方案:只需使用 Makefile 让 .c 到 .o 编译规则依赖于头文件以及与项目相关的任何其他依赖项。

例如,在 Makefile 某处:

DEPENDENCIES=mydefs.h yourdefs.h Makefile GameOfThrones.S07E01.mkv

::: (your other Makefile statements like rules 
:::  for constructing executables or libraries)

# Compile any .c to the corresponding .o file:
%.o: %.c $(DEPENDENCIES)
        $(CC) $(CFLAGS) -c -o $@ $<

【讨论】:

以上是关于如何让 Makefile 自动重建包含修改后的头文件的源文件? (在 C/C++ 中)的主要内容,如果未能解决你的问题,请参考以下文章

Makefile中自动生成头文件依赖

重建后的Makefile重新编译代码需要很多时间

如何使用 makefile 生成自动头文件

如何在不扩展包含的头文件的情况下预编译 C 源文件?

修改 makefile 以包含外部编译的对象

Makefile 包含来自其他目录的头文件