【问题标题】:Makefile (Auto-Dependency Generation)Makefile(自动依赖生成)
【发布时间】:2023-04-11 12:12:02
【问题描述】:

只是为了快速的术语:

#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

所以在整个谷歌上,似乎有两种解决方案。这两个我都没有完全掌握。
From GNU Make Manual

Some Site that says the GNU Make Manual one is out-of-date

所以我的最终问题是:是否可以按照我想要的方式进行操作,
如果没有,有人可以分解其中一个站点的代码并向我详细解释它们是如何工作的。如果必须的话,我会以其中一种方式实现它,但我很厌倦在理解之前将一大块代码粘贴到我的 makefile 中

【问题讨论】:

标签: makefile dependencies gnu


【解决方案1】:

较新版本的 GCC 有一个 -MP 选项,可与 -MD 一起使用。我只是将 -MP 和 -MD 添加到我的项目的 CPPFLAGS 变量中(我没有编写用于编译 C++ 的自定义配方)并添加了“-include $(SRC:.cpp=.d)”行。

使用 -MD 和 -MP 会生成一个依赖文件,其中包括依赖项(不必使用一些奇怪的 sed)和虚拟目标(这样删除头文件就不会导致错误)。

【讨论】:

  • 非常有用的信息。根据manual,它似乎从 gcc-3.0 开始就可用。令人惊讶的是,它并不为人所知,因为它似乎是我见过的最好和最简单的解决方案。
  • 使用 -MD 和 -MP 似乎是最新(也是最好的)解决方案。基本相当于make.mad-scientist.net/papers/…中的高级方法,但方便地避免了sed的复杂性。我猜在作者写高级方法的时候,-MP 选项还不可用或公开,所以需要几行 sed 脚本,它本质上实现了 -MP 选项。
  • Here is another good page about make auto-dependencies with an example using -MD and -MP microhowto.info/howto/… 它还与 GNU Make Manual 中的方法进行比较,正如所指出的那样,它是一个输出-过时的解决方案。
  • 如果我对头文件进行一些更改,它将重新编译----这正是我所需要的。我有一个问题:我们可以删除所有.d 文件吗?这似乎是不可能的,因为如果我删除*.d,如果我对头文件进行一些更改,它将不会重新编译。
  • 不,您不能删除 *.d 文件。如果你删除它们,你必须删除所有 *.o 文件才能重新创建它们(make clean?)
【解决方案2】:

要在已经知道依赖项应该是什么的情况下操作文件名,可以使用模式规则:

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 自动找出依赖项列表,最好的方法(据我所知)是Advanced Auto-Dependency Generation。它看起来像这样:

%.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,该命令将依赖项列表变成没有配方的规则。最后一行是对include 任何存在的此类规则的指令。这里的逻辑微妙而巧妙:第一次构建 foo.o 时实际上并不需要依赖项,因为 Make 已经知道必须构建 foo.o,因为它不存在。下次运行 Make 时,它​​将使用上次创建的依赖项列表。如果您更改其中一个文件,使得实际上有一个不在列表中的新依赖项,Make 仍将重建 foo.o,因为 您更改了一个依赖项的文件。试试看,真的很管用!

【讨论】:

  • 高级自动依赖生成页面的精彩总结!我发现了这一点,但在整理其所有完整技术时遇到了麻烦。似乎这是一个很好的简洁的执行摘要为什么它的工作原理。
  • 当你删除一个依赖并删除关联文件时会怎样?
  • @Collin:我发布的内容并未涵盖这一点,但%.h:; 规则将解决该问题。 请注意,我发布的内容已过时,因为 g++ 现在有 -MMD 选项,可以避免使用 sed 命令。
  • file.o 可以是一个包含所有对象(及其路径)列表的变量吗?
  • @MarcusJ:您可以拥有一个具有该名称的变量,其中包含该列表。我怀疑我没有回答你的问题。
【解决方案3】:

很好的答案,但在我的构建中,我将 .obj 文件放在基于构建类型的子目录中(即:调试与发布)。例如,如果我正在构建调试,我将所有目标文件放在构建/调试文件夹中。尝试让上面的多行 sed 命令使用正确的目标文件夹是一项令人麻木的任务,但经过一些实验,我偶然发现了一个非常适合我的构建的解决方案。希望它也能对其他人有所帮助。

这是一个sn-p:

# 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,因此我的构建速度更快(尽管我尚未证明这一点,因为此时我的构建相对较小)。如果您看到我可以改进这一点的方法,我总是乐于接受建议。享受

【讨论】:

    【解决方案4】:

    为了记录,这就是我现在自动生成依赖项的方式:

    CPPFLAGS = -std=c++1y -MD -MP 
    
    SRC = $(wildcard *.cpp)
    all: main
    
    main: $(SRC:%.cpp=%.o)
        g++ $(CPPFLAGS) -o $@ $^
    
    -include $(SRC:%.cpp=%.d)
    

    编译器标志 -MD 和 -MP 有助于解决问题。

    【讨论】:

      【解决方案5】:

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

      然后你可以运行make -p来了解make的内置规则

      通常的做法是将 makefile 依赖项生成到 *.md 文件中:

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

      稍后在您的Makefile including them 中使用类似

      -include $(wildcard *.md)
      

      但您也可以考虑使用其他构建器,例如 omake 和许多其他构建器

      【讨论】:

        【解决方案6】:

        哇!我确实设法让 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  
        

        现在我想分享我在Managing Projects with GNU Make, 3rd Edition. 中找到的信息,因为它指出了关于这个问题的一些重要问题,并提供了我还没有完全掌握的代码。
        书中出现了一种方法,与Make manual page上的方法相似。
        它看起来像这样:

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

        这就是我认为正在发生的事情。
        马上,“make”想要为每个源文件包含一个“.d”文件。
        因为最初不存在 .d 文件,所以会一次又一次地运行代码块以创建所有丢失的 .d 文件。
        这意味着 make 将一次又一次地重新开始,直到每个 .d 文件都被创建并包含在 makefile 中。
        每个“.d”文件就是 Beta 所说的:一个具有一组依赖项且没有配方的目标。

        如果头文件发生更改,其中包含的那些规则将需要首先更新依赖项。这让我有点失望,这段代码怎么能被再次调用?它用于更新 .d 文件,所以如果 .h 文件发生更改,它如何被调用?除此之外,我意识到默认规则用于编译对象。对此解释的任何澄清/误解表示赞赏。


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

        他们说解决这些问题的方法是 Tromey 的方法,但代码看起来与网站上的代码大不相同。也许只是因为他们使用了一些宏,把它变成了一个函数调用,并且写的有点不同。我仍在研究它,但想分享我迄今为止所做的一些发现。希望这会引发一点更多的讨论,让我更接近这一切的底线。

        【讨论】:

          【解决方案7】:

          我更喜欢将 $(shell ...) 函数与 find 一起使用。这是我的一个 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))
          

          在这种方法中,我首先获取所有内部目录结构,具有任意深度。然后我得到结构内的所有文件。这时候我可以使用filter、filter-out、addsuffix等,每次都能准确得到我需要的东西。

          此示例涵盖 *.c 文件,但您也可以将其更改为 *.cpp。

          【讨论】:

            【解决方案8】:

            基于上一篇文章中 cmets 中引用的 Auto-Dependency Generation 文章的内容,我创建了一个 annotated makefile project,其中包括一个带有 cmets 注释的 generic Makefile,并为一个具有 3 个 .c 文件和2个.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))
            

            【讨论】:

              【解决方案9】:

              here 提供了一个简单而优雅的解决方案,包括对其工作原理的详细说明。

              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))
              

              【讨论】:

                猜你喜欢
                • 2012-06-20
                • 1970-01-01
                • 1970-01-01
                • 2012-08-05
                • 2012-07-06
                • 1970-01-01
                • 1970-01-01
                • 2011-01-29
                • 1970-01-01
                相关资源
                最近更新 更多