Makefile(自动依赖关系生成)

36

仅用于快速术语:

#basic makefile rule
target: dependencies
    recipe

问题:我希望自动生成依赖关系。

例如,我希望将以下内容转换为:

#one of my targets
file.o: file.cpp 1.h 2.h 3.h 4.h 5.h 6.h 7.h 8.h another.h lots.h evenMore.h
    $(COMPILE)

转换为:

#one of my targets
file.o: $(GENERATE)
    $(COMPILE)

我不太确定是否可能。

我知道的是:

我可以使用这个编译器标记:

g++ -MM file.cpp

它将返回正确的目标和依赖项。
因此,从示例中,它将返回:

file.o: file.cpp 1.h 2.h 3.h 4.h 5.h 6.h 7.h 8.h another.h lots.h evenMore.h  

然而,“make”不允许我在规则的目标或依赖项部分明确编写shell代码:(
我知道有一个名为shell的“make”函数,但我无法将其作为依赖项插入并进行解析,因为它依赖于代表目标的宏$@..或者至少我认为问题就在这里。 我甚至尝试用这个makefile函数替换“file.cpp”依赖项,但也不起作用。
#it's suppose to turn the $@ (file.o) into file.cpp
THE_CPP := $(addsuffix $(.cpp),$(basename $@))

#one of my targets
file.o: $(THE_CPP) 1.h 2.h 3.h 4.h 5.h 6.h 7.h 8.h another.h lots.h evenMore.h
    $(COMPILE)
#this does not work

在谷歌上,似乎有两种解决方案。但我都不完全理解。
来自GNU Make手册

某个网站说GNU Make手册已经过时了

所以我的最终问题是:是否可能按照我想要的方式进行操作,
如果不行,能否有人从这些网站中分解代码并详细解释它们的工作原理。如果必须,我会实现其中一种方法,但在理解之前,我不想只是将一大块代码粘贴到我的makefile中。


可能是[Makefile,头文件依赖关系]的重复问题(https://dev59.com/NnE95IYBdhLWcg3wPrgI) - Ciro Santilli OurBigBook.com
9个回答

35
较新版的GCC有一个-MP选项,可与-MD一起使用。我只需将-MP和-MD添加到我的项目的CPPFLAGS变量中(我没有编写自定义编译C++的配方),并添加了“-include $(SRC:.cpp=.d)”行。使用-MD和-MP会提供一个依赖文件,其中包括依赖项(无需使用某些奇怪的sed)和虚拟目标(因此删除头文件不会导致错误)。

1
非常有用的信息。根据手册,似乎自gcc-3.0以来就已经可用了。令人惊讶的是,它并不是很出名,因为它似乎是我见过的最好、最简单的解决方案。 - PeterSW
1
使用-MD和-MP似乎是最新(也是最好的)解决方案。它基本上相当于http://make.mad-scientist.net/papers/advanced-auto-dependency-generation/中的高级方法,但方便地避免了sed的复杂性。我猜在作者编写高级方法时,-MP选项还没有可用或公开知道,因此需要几行sed脚本,实现了-MP选项。 - linbianxiaocao
4
这里有一篇关于如何使用-MD和-MP来自动生成Makefile依赖关系的好文章,其中包含一个示例。链接为http://www.microhowto.info/howto/automatically_generate_makefile_dependencies.html。文章还比较了GNU Make手册中的方法,指出其为过时解决方案。 - linbianxiaocao
如果我在头文件中进行更改,它将重新编译——这正是我所需要的。我只有一个问题:我们可以删除所有.d文件吗?似乎不可能,因为如果我删除*.d,则在我更改头文件时它将无法重新编译。 - Yves
不,您不能删除 *.d 文件。如果您删除它们,那么您将不得不删除所有的 *.o 文件以便重新创建它们(使用 make clean 命令?)。 - teambob
当您编译位于子目录中的文件(假设您将.o文件放置在单独的目录obj_config/中时)会发生什么?子目录在SRC列表中的文件名中,.d文件找不到/创建失败,因为路径在obj_config中不存在。 - Gauthier

20

如果你已经知道依赖关系,想要操作文件名,可以使用模式规则:

file.o: %.o : %.cpp 1.h 2.h 3.h 4.h 5.h 6.h 7.h 8.h another.h lots.h evenMore.h
    $(COMPILE)

您可以将该规则用于其他目标:

# Note these two rules without recipes:
file.o: 1.h 2.h 3.h 4.h 5.h 6.h 7.h 8.h another.h lots.h evenMore.h
anotherFile.o: 4.h 9.h yetAnother.h

file.o anotherFile.o: %.o : %.cpp
    $(COMPILE)

但是如果您想让Make自动确定依赖关系列表,最好的方法(我所知道的)是高级自动依赖项生成。它看起来像这样:

%.o : %.cc
        @g++ -MD -c -o $@ $<
        @cp $*.d $*.P; \
             sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
                 -e '/^$$/ d' -e 's/$$/ :/' < $*.d >> $*.P; \
             rm -f $*.d

-include *.P
基本上,当它编译 file.o 时,它也会构建 file.d 。然后,它会通过一个令人困惑的sed命令运行 file.d ,将依赖列表转换为没有配方的规则。最后一行是一个指令,用于包括任何存在的这样的规则。这里的逻辑是微妙而巧妙的:第一次构建 foo.o 时,您实际上不需要依赖项,因为Make已经知道必须构建 foo.o ,因为它不存在。下次运行Make时,它将使用上次创建的依赖项列表。如果更改其中一个文件以使其实际上有一个新的依赖项不在列表中,则Make仍将重新构建 foo.o ,因为您更改了作为依赖项的文件。尝试一下,它真的有效!

高级自动依赖生成页面的总结非常好!我找到了它,但是在整理所有完整的技术时遇到了麻烦。看起来这是一个很好的简明执行摘要,解释了为什么它有效。 - natevw
当您删除一个依赖项并删除相关文件时怎么办? - Talia
3
@Collin:我发布的内容没有涵盖这一点,但是%.h:;规则将解决这个问题。请注意,我发布的内容已经过时了,因为g++现在有-MMD选项,可以避免使用sed命令。 - Beta
file.o 可以是一个包含所有对象(及其路径)列表的变量吗? - MarcusJ
@MarcusJ:你可以定义一个变量使用那个名称,并包含那个列表。我猜想我可能没有回答你心中所想的问题。 - Beta
我已经解决了那个问题,只是在将所有对象添加到我的静态库时遇到了一些麻烦,感谢您对这个老问题的回复。 - MarcusJ

8
优秀的回答,但在我的构建中,我根据构建类型(即:debug vs. release)将.obj文件放在子目录中。例如,如果我正在构建debug,则将所有对象文件放在build/debug文件夹中。尝试让上面的多行sed命令使用正确的目标文件夹是一项令人头疼的任务,但经过一些实验,我偶然发现了一个适用于我的构建的解决方案。希望这也能帮助其他人。

以下是代码片段:

# List my sources
CPP_SOURCES := foo.cpp bar.cpp

# If I'm debugging, change my output location
ifeq (1,$(DEBUG))
  OBJ_DIR:=./obj/debug
  CXXFLAGS+= -g -DDEBUG -O0 -std=c++0x
else
  CXXFLAGS+= -s -O2 
  OBJ_DIR:=./obj/release
endif

# destination path macro we'll use below
df = $(OBJ_DIR)/$(*F)

# create a list of auto dependencies
AUTODEPS:= $(patsubst %.cpp,$(OBJ_DIR)/%.d,$(CPP_SOURCES))

# include by auto dependencies
-include $(AUTODEPS)

.... other rules

# and last but not least my generic compiler rule
$(OBJ_DIR)/%.o: %.cpp 
    @# Build the dependency file
    @$(CXX) -MM -MP -MT $(df).o -MT $(df).d $(CXXFLAGS) $< > $(df).d
    @# Compile the object file
    @echo " C++ : " $< " => " $@
    @$(CXX) -c $< $(CXXFLAGS) -o $@

现在进入细节部分: 我通用的构建规则中的第一次 CXX 执行非常有趣。请注意,我没有使用任何“sed”命令。新版本的 gcc 做了我所需的一切(我正在使用 gcc 4.7.2)。
-MM 构建主要依赖项规则,包括项目头文件,但不包括系统头文件。如果我像这样离开它,我的 .obj 文件将没有正确的路径。因此,我使用 -MT 选项指定到我的 .obj 目标的“真实”路径(使用我创建的“df”宏)。
我还使用第二个 -MT 选项来确保生成的依赖文件(即:.d 文件)具有正确的路径,并且包含在目标列表中,因此具有与源文件相同的依赖项。
最后但并非最不重要的是包含 -MP 选项。这告诉 gcc 为每个头文件也生成存根规则,解决了删除头文件导致 make 生成错误时出现的问题。
我怀疑由于我使用 gcc 进行所有依赖关系生成而不是通过管道传输给 sed,因此我的构建速度更快(尽管我尚未证明,因为我目前的构建相对较小)。如果您看到可以改进的地方,请随时提出建议。享受!

7

记录一下,这是我现在自动生成依赖的方式:

CPPFLAGS = -std=c++1y -MD -MP 

SRC = $(wildcard *.cpp)
all: main

main: $(SRC:%.cpp=%.o)
    g++ $(CPPFLAGS) -o $@ $^

-include $(SRC:%.cpp=%.d)

编译器标志-MD和-MP可以帮助完成这个技巧。

4

首先,你可以使用THE_CPP=$(patsubst %.o,%.cpp,$@)

然后,您可以运行make -p以了解make的内置规则。

通常的做法是将生成的Makefile依赖项保存为*.md文件:

%.o: %.c
       $(COMPILE.c) $(OUTPUT_OPTION) $< -MMD -MF $(patsubst %.c,%.md,$@)

在您的 Makefile 中稍后使用类似以下内容的方式 包含它们

-include $(wildcard *.md)

但是你也可以考虑使用其他构建工具,比如omake等等。


2

这里提供了一个简单而优雅的解决方案,包括详细的工作原理说明,可在此处获取。

DEPDIR := .deps

DEPFLAGS = -MT $@ -MMD -MP -MF $(DEPDIR)/$*.d

%.o : %.cpp
%.o : %.cpp $(DEPDIR)/%.d | $(DEPDIR)
        g++ -c $(DEPFLAGS) $(CFLAGS) $<

$(DEPDIR): ; @mkdir -p $@

DEPFILES := $(SRCS:%.c=$(DEPDIR)/%.d)
$(DEPFILES):

include $(wildcard $(DEPFILES))

2

哇!我成功地在一个小测试项目中运行了Beta帖子中的代码。
需要注意的是,如果你使用的是bash shell(像我一样),你需要在井号前面添加转义字符来避免使表达式的其余部分成为注释。(参见代码的第4行)

%.o : %.cpp  
    g++ -c -MD -o $@ $<  
    cp $*.d $*.P; \  
    sed -e 's/\#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \  
        -e '/^$$/ d' -e 's/$$/ :/' < $*.d >> $*.P; \  
    rm -f $*.d  
-include *.P  

现在我想分享一些我在《使用GNU Make管理项目,第3版》中发现的信息,因为它指出了这个问题上的一些重要问题,并提供了代码,我仍然没有完全掌握。
书中提到了一种类似于Make手册页面中的方法。
它看起来像这样:

include $(subst .c,.d,$(SOURCES))

%.d: %.c
    $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
    sed 's,\($*\).o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
    rm -f $@.$$$$

这是我认为正在发生的事情。
立即,“make”想要为每个源文件包括一个“.d”文件。
由于最初没有“.d”文件,因此需要一遍又一遍地运行代码块以创建所有缺少的“.d”文件。
这意味着make会一遍又一遍地重新开始,直到创建并包含在makefile中的每个“.d”文件都被创建。
每个“.d”文件都是Beta所说的:一个具有依赖关系集合和无食谱的目标。

如果头文件发生变化,则包含在其中的规则首先需要更新其依赖项。这使我有点困惑,代码块如何能够再次调用?它用于更新“.d”文件,因此如果.h文件更改,它如何被调用?除此之外,我意识到默认规则用于编译对象。欢迎纠正/澄清这个解释。


书中后面指出了这种方法存在的问题,我认为高级自动依赖关系生成实现也存在这些问题。
问题1:效率低下。“make”必须每次制作一个“.d”文件时重新启动。
问题2:make会为所有缺失的“.d”文件生成警告消息-这基本上只是一个麻烦,可以通过在包含语句前面添加“-”来隐藏。
问题3:如果删除源文件因为不再需要它,则下次尝试编译时“make”将崩溃,因为某些“.d”文件具有丢失的源文件作为依赖项,并且因为没有规则重新创建该源文件,所以make会拒绝继续执行。

他们说解决这些问题的方法是Tromey's方法,但是代码与网站上的代码非常不同。也许只是因为他们使用了一些宏,进行了函数调用,并稍微修改了代码。我仍在研究中,但希望分享一些我迄今为止的发现。希望这能开启更多的讨论,并让我更接近解决问题。


1

我更喜欢在find功能中使用$(shell ...)函数。这是我的一个Makefile示例:

SRCDIR = src
OBJDIR = obj
LIBDIR = lib
DOCDIR = doc

# Get Only the Internal Structure of Directories from SRCDIR
STRUCTURE := $(shell find $(SRCDIR) -type d)

#Filter-out hidden directories
STRUCTURE := $(filter-out $(shell find $(SRCDIR)/.* -type d),$(STRUCTURE))

# Get All Files From STRUCTURE
CODEFILES := $(addsuffix /*,$(STRUCTURE))
CODEFILES := $(wildcard $(CODEFILES))


## Filter Only Specific Files
SRCFILES := $(filter %.c,$(CODEFILES))
HDRFILES := $(filter %.h,$(CODEFILES))
OBJFILES := $(subst $(SRCDIR),$(OBJDIR),$(SRCFILES:%.c=%.o))
DOCFILES := $(addprefix $(DOCDIR)/,             \
            $(addsuffix .md,                    \
            $(basename $(SRCFILES))))


# Filter Out Function main for Libraries
LIBDEPS := $(filter-out $(OBJDIR)/main.o,$(OBJFILES))

在这种方法中,首先获取所有内部目录结构,无论深度如何。然后获取结构内的所有文件。此时,可以使用过滤器、过滤器排除、添加后缀等,以便每次都能获得所需内容。
此示例涵盖了*.c文件,但您也可以将其更改为*.cpp。

1

在先前的文章评论中提到的自动依赖关系生成的内容基础之上,我创建了一个带有注释的makefile项目,其中包括一个带有注释的通用Makefile, 该Makefile已经为一个包含三个.c文件和两个.h文件的简单项目实现。请查看下面完整的Makefile内容。简单的项目应该只需要自定义“TODO”部分即可。

# See http://make.mad-scientist.net/papers/advanced-auto-dependency-generation/
# for the template used to start this file

# -- TODO: customize the list below for your project ---
# List of source .c files used with the project
SRCS := main.c file1.c file2.c

# The aplication generated 
APPNAME = depend-generation-test
# -- End of customization section ---

# Replace .c extension on SRCS to get objfiles using gnu make pattern rules and substitution references.
# See https://www.gnu.org/software/make/manual/html_node/Pattern-Intro.html#Pattern-Intro for pattern rules and 
# https://www.gnu.org/software/make/manual/html_node/Substitution-Refs.html#Substitution-Refs for substitution references overview
OBJFILES := $(SRCS:%.c=%.o)

# Build the app you've specified in APPNAME for the "all" or "default" target
all : $(APPNAME)
default : $(APPNAME)

# Remove all build intermediates and output file
clean : ; @rm -rf $(APPNAME) *.o

# Build the application by running the link step with all objfile inputs
$(APPNAME) : $(OBJFILES)
    $(CC) $(LDFLAGS) $^ -o $(APPNAME)

# Add all warnings/errors to cflags default.  This is not required but is a best practice
CFLAGS += -Wall -Werror

# The below content is from  http://make.mad-scientist.net/papers/advanced-auto-dependency-generation/
# with the following changes:
#   1) Added comments
#   2) Removed TARGET_ARCH from COMPILE.c since it's no longer listed in the [default rules](https://www.gnu.org/software/make/manual/html_node/Catalogue-of-Rules.html#Catalogue-of-Rules) and [isn't documented](https://lists.gnu.org/archive/html/help-make/2010-06/msg00005.html)
# Original content below is:
# Copyright © 1997-2019 Paul D. Smith Verbatim copying and distribution is permitted in any medium, provided this notice is preserved.

# The directory (hidden) where dependency files will be stored
DEPDIR := .deps
# Flags passed to gcc to automatically build dependencies when compiling
# See https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html for detail about variable names
# $@ references the target file of the rule and will be "main.o" when compiling "main.c"
# $* references the stem of the rule, and will be "main" when target is "main.o"
DEPFLAGS = -MT $@ -MMD -MP -MF $(DEPDIR)/$*.d

# Rules for compiling a C file, including DEPFLAGS along with Implicit GCC variables.
# See https://www.gnu.org/software/make/manual/html_node/Implicit-Variables.html
# and see https://www.gnu.org/software/make/manual/html_node/Catalogue-of-Rules.html#Catalogue-of-Rules
# for the default c rule
COMPILE.c = $(CC) $(DEPFLAGS) $(CFLAGS) $(CPPFLAGS) -c

# Delete the built-in rules for building object files from .c files
%.o : %.c
# Define a rule to build object files based on .c or dependency files by making the associated dependency file
# a prerequisite of the target.  Make the DEPDIR an order only prerequisite of the target, so it will be created when needed, meaning
# the targets won't get rebuilt when the timestamp on DEPDIR changes
# See https://www.gnu.org/software/make/manual/html_node/Prerequisite-Types.html for order only prerequesites overview.
%.o : %.c $(DEPDIR)/%.d | $(DEPDIR)
    $(COMPILE.c) $(OUTPUT_OPTION) $<

# Create the DEPDIR when it doesn't exist
$(DEPDIR): ; @mkdir -p $@

# Use pattern rules to build a list of DEPFILES
DEPFILES := $(SRCS:%.c=$(DEPDIR)/%.d)
# Mention each of the dependency files as a target, so make won't fail if the file doesn't exist
$(DEPFILES):

# Include all dependency files which exist, to include the relevant targets.
# See https://www.gnu.org/software/make/manual/html_node/Wildcard-Function.html for wildcard function documentation
include $(wildcard $(DEPFILES))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接