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


91

我使用以下makefile来构建正在使用的程序(实际上是内核)。它是从头开始的,我正在学习该过程,所以它并不完美,但是对于我自己编写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可以以某种方式找出需要重建的文件),但是我一生都找不到真正的实现示例来查看。有人可以发布解决方案以在Makefile中启用此行为吗?

编辑:我应该澄清一下,我熟悉将各个目标放入并使每个target.o需要头文件的概念。这就要求我每次在某个地方包含头文件时都要编辑makefile,这有点麻烦。我正在寻找一种可以自行导出头文件依赖项的解决方案,我可以肯定在其他项目中也能看到这种依赖。

Answers:


30

如本网站其他地方所指出的,请参阅此页面: 自动依赖项生成

简而言之,gcc可以为您自动创建.d依赖文件,这些文件是微型makefile片段,其中包含您编译的.c文件的依赖关系。每次更改.c文件并进行编译时,.d文件都会更新。

除了在gcc中添加-M标志之外,您还需要在makefile中包含.d文件(如Chris上面所写)。页面中存在一些更复杂的问题,这些问题可以使用sed解决,但您可以忽略它们,并在每次抱怨无法构建不再存在的头文件时进行“清理”以清除.d文件。


2
我可能会误会,但我认为GCC实际上已经添加了一项功能来尝试解决该sed问题。专门查看gcc.gnu.org/onlinedocs/gcc-4.3.1/gcc/Preprocessor-Options.html -MP。
Eugene Marcotte

是的,-MP自GCC 3起就存在,存在于clang和icc中,并且不再需要sed。bruno.defraine.net/techtips/makefile-auto-dependencies-with-gcc/…–
hmijail哀悼辞职者

答案中链接的最新版本包含使用GCC标志的示例。
MadScientist

20

您可以像其他人所说的那样添加一个“ 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


18
这对我没有用。例如生成了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 $@ $<
Darren Cook

17

这等效于Chris Dodd的回答,但是使用了不同的命名约定(并且恰巧不需要sed魔术。从后来的副本中复制过来)。


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

depend: .depend

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

include .depend

也有该工具makedepend,但我从未像现在这样喜欢它gcc -MM


4
您为什么不拼出SOURCES?不需要特别多的字符,并且不像看起来像首字母缩略词的“ SRCS”那样混乱。
HelloGoodbye 2015年

@HelloGoodbye有点风格。我一直在使用,并且经常看到SRCSOBJS。我大部分时间都会同意,但是每个人都应该知道那是什么。
sherrellbc

@sherrellbc人们“应该”知道的和人们实际知道的通常是两件事:P
HelloGoodbye

7

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

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

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

4

基本上,您需要动态创建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中。


2
我有点理解这一点,除了(除了-MM和-MG标志是新的之外),我不了解隐秘文本的正则表达式查找行是做什么的。那不会让我的队友高兴……^ _ ^不过我会尝试一下,看看是否有任何结果。
Nicholas Flynt

sed是“流编辑器”的缩写,它可以修改文本流而无需使用文件。它是标准的Unix工具,并且更小,更快,因此比awk或perl更为常用。
Zan Lynx

嗯,有个问题:我是在Windows下执行的。
尼古拉斯·弗林特

3

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


0

没有答案对我有用。例如,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文件。对于每个源文件,依此类推。


0

更简单的解决方案:只需使用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 $@ $<

-1

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.