Makefile,标头依赖性


97

假设我有一个带有规则的makefile

%.o: %.c
 gcc -Wall -Iinclude ...

每当头文件更改时,我都希望重建* .o。无论何时/include更改任何头文件,都不必重建依赖关系列表,而必须重建目录中的所有对象。

我想不出一种改变规则以适应这一需求的好方法,我愿意提出建议。如果不需要对标题列表进行硬编码,则可获得加分


在下面写下我的答案后,我查看了相关列表,然后发现:stackoverflow.com/questions/297514/…这似乎是重复的。克里斯·多德(Chris Dodd)的答案与我的答案相同,尽管它使用了不同的命名约定。
dmckee ---前主持人小猫,2010年

Answers:


116

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

depend: .depend

.depend: $(SRCS)
        rm -f ./.depend
        $(CC) $(CFLAGS) -MM $^ -MF  ./.depend;

include .depend

要么

depend: .depend

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

include .depend

其中SRCS的变量指向您的整个源文件列表。

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


2
我喜欢这个技巧,但是如何depend仅在源文件更改后才能运行?它似乎每次都在运行,无论...
追赶

2
@chase:好吧,我错误地对目标文件进行了依赖,因为它显然应该在源文件上,并且对两个目标的依赖顺序也是错误的。这就是我从内存键入内容。现在就试试。
dmckee ---前主持人小猫,

4
是否可以在每个文件之前添加一些前缀来表明它在另一个目录中,例如build/file.o
RiaD 2012年

我将SRCS更改为OBJECTS,其中OBJECTS是我的* .o文件的列表。这似乎阻止了Depend每次运行,并且仅捕获了对头文件的更改。这似乎与之前的评论相反。我遗漏了一些东西吗?
BigBrownBear00 2013年

2
为什么需要分号?如果我尝试不使用它,或者不使用-MF ./.depend作为最后一个参数,它将仅将最后一个文件的依赖项保存在$(SRCS)中。
humodz 2014年

71

大多数答案出奇地复杂或错误。但是,简单而强大的示例已发布在其他地方[ codereview ]。诚然,gnu预处理器提供的选项有些混乱。但是,记录了从构建目标中删除所有目录的过程,-MM而不是错误[ gpp ]:

默认情况下,CPP使用主输入文件的名称,删除任何 目录组件和任何文件后缀(例如“ .c”),并附加平台的通常对象后缀。

(稍微更新)-MMD选项可能是您想要的。为了完整起见,一个支持多个src目录并带有一些注释的目录的makefile示例。对于没有构建目录的简单版本,请参见[ codereview ]。

CXX = clang++
CXX_FLAGS = -Wfatal-errors -Wall -Wextra -Wpedantic -Wconversion -Wshadow

# Final binary
BIN = mybin
# Put all auto generated stuff to this build dir.
BUILD_DIR = ./build

# List of all .cpp source files.
CPP = main.cpp $(wildcard dir1/*.cpp) $(wildcard dir2/*.cpp)

# All .o files go to build dir.
OBJ = $(CPP:%.cpp=$(BUILD_DIR)/%.o)
# Gcc/Clang will create these .d files containing dependencies.
DEP = $(OBJ:%.o=%.d)

# Default target named after the binary.
$(BIN) : $(BUILD_DIR)/$(BIN)

# Actual target of the binary - depends on all .o files.
$(BUILD_DIR)/$(BIN) : $(OBJ)
    # Create build directories - same structure as sources.
    mkdir -p $(@D)
    # Just link all the object files.
    $(CXX) $(CXX_FLAGS) $^ -o $@

# Include all .d files
-include $(DEP)

# Build target for every single object file.
# The potential dependency on header files is covered
# by calling `-include $(DEP)`.
$(BUILD_DIR)/%.o : %.cpp
    mkdir -p $(@D)
    # The -MMD flags additionaly creates a .d file with
    # the same name as the .o file.
    $(CXX) $(CXX_FLAGS) -MMD -c $< -o $@

.PHONY : clean
clean :
    # This should remove all generated files.
    -rm $(BUILD_DIR)/$(BIN) $(OBJ) $(DEP)

此方法之所以有效,是因为如果单个目标有多个依赖项行,则这些依赖项将简单地合并在一起,例如:

a.o: a.h
a.o: a.c
    ./cmd

等效于:

a.o: a.c a.h
    ./cmd

如以下内容所述:Makefile单个目标有多个依赖项行?


1
我喜欢这个解决方案。我不想键入makeDepend命令。有用的!
罗伯特·

1
OBJ变量值中存在拼写错误:CPP应读取CPPS
ctrucza

1
这是我的首选答案;为您+1。这是该页面上唯一有意义的内容,涵盖了(据我所知)所有需要重新编译的情况(避免不必要的编译,但足够)
Joost

1
开箱即用,即使hpp和cpp都在同一目录中,也无法为我找到标头。
villasv

1
如果您将源文件(a.cppb.cpp)放在其中./src/,那么该替换不会生效$(OBJ)=./build/src/a.o ./build/src/b.o吗?
galois

26

正如我在这里发布的 gcc可以创建依赖关系并同时进行编译:

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

-include $(DEPS)

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

“ -MF”参数指定用于存储依赖项的文件。

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

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


4
当我尝试此操作时,它会导致我的所有.o文件都被创建为空文件。我确实将我的对象放在一个build子文件夹中(因此$ OBJECTS包含build / main.o build / smbus.o build / etc ...),并且确实可以创建您所描述的带有明显错误的.d文件,但可以肯定根本不构建.o文件,而如果删除-MM和-MF则可以。
bobpaul

1
使用-MT将解决您答案的最后几行中的注释,该注释将更新每个依赖项列表的目标。
Godric Seer 2013年

3
@bobpaul是因为man gcc-MMimplies -E,它“在预处理后停止”。您需要-MMD改用:stackoverflow.com/a/30142139/895245
西罗(Ciro Santilli)郝海东冠状病六四事件法轮功2013年

23

怎么样:

includes = $(wildcard include/*.h)

%.o: %.c ${includes}
    gcc -Wall -Iinclude ...

您也可以直接使用通配符,但是我倾向于在多个地方使用通配符。

请注意,这仅适用于小型项目,因为它假定每个目标文件都依赖于每个头文件。


谢谢,我使这个问题变得比需要的复杂得多
Mike

15
这行得通,但是,问题在于,每次进行小的更改时,每个目标文件都会重新编译一次,即,如果您有100个源/头文件,而仅对其中的一个进行小的更改,则所有100个都将重新编译一次。
尼古拉斯·汉密尔顿

1
您应该真正更新您的回答,说这是一种非常低效的方法,因为每次更改任何头文件时,它都会重新生成所有文件。其他答案要好得多。
xaxxon

2
这是一个非常糟糕的解决方案。当然,它可以在一个小项目上运行,但是对于任何规模的团队和构建而言,这都将导致可怕的编译时间,并等效于make clean all每次运行。
Julien Guertault

在我的测试中,这根本不起作用。该gcc行根本不执行,而是执行内置规则(%o: %.crule)。
Penghe Geng

4

Martin的上述解决方案效果很好,但不能处理驻留在子目录中的.o文件。Godric指出-MT标志可以解决该问题,但同时会阻止.o文件的正确写入。以下内容将解决这两个问题:

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

-include $(DEPS)

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

3

这样就可以完成工作,甚至可以处理指定的子目录:

    $(CC) $(CFLAGS) -MD -o $@ $<

用gcc 4.8.3测试了它


3

这是两条线:

CPPFLAGS = -MMD
-include $(OBJS:.c=.d)

只要您在中具有所有目标文件的列表,此设置就可以使用默认的制作方法OBJS


1

与迈克尔·威廉姆森(Michael Williamson)接受的答案相比,我更喜欢这种解决方案,它可以捕获对源+内联文件,源+头文件以及最终源的更改。这样做的好处是,仅进行了少量更改就不会重新编译整个库。对于具有几个文件的项目来说,这不是一个很大的考虑因素,如果您有10个或100个源,那么您会注意到其中的区别。

COMMAND= gcc -Wall -Iinclude ...

%.o: %.cpp %.inl
    $(COMMAND)

%.o: %.cpp %.hpp
    $(COMMAND)

%.o: %.cpp
    $(COMMAND)

2
仅当头文件中没有任何需要重新编译除相应实现文件以外的任何cpp文件的文件时,此方法才有效。
matec 2014年


0

索菲(Sophie)的略微修改版本 答案的它允许将* .d文件输出到另一个文件夹(我只会粘贴生成依赖文件的有趣部分):

$(OBJDIR)/%.o: %.cpp
# Generate dependency file
    mkdir -p $(@D:$(OBJDIR)%=$(DEPDIR)%)
    $(CXX) $(CXXFLAGS) $(CPPFLAGS) -MM -MT $@ $< -MF $(@:$(OBJDIR)/%.o=$(DEPDIR)/%.d)
# Generate object file
    mkdir -p $(@D)
    $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $< -o $@

注意参数

-MT $@

用于确保生成的* .d文件中的目标(即目标文件名)包含* .o文件的完整路径,而不仅仅是文件名。

我不知道为什么将-MMD 与-c 结合使用时不需要此参数(在Sophie的版本中)。通过这种组合,似乎可以将* .o文件的完整路径写入* .d文件。如果没有这种组合,-MMD还将仅将纯文件名而不将任何目录组件写入* .d文件。也许有人知道为什么-MMD与-c结合使用时会写入完整路径。我在g ++手册页中找不到任何提示。

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.