【问题标题】:Makefile, header dependenciesMakefile,头文件依赖
【发布时间】:2011-01-24 13:48:34
【问题描述】:

假设我有一个包含规则的生成文件

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

我希望在头文件更改时重建 *.o。只要/include 中的任何头文件发生更改,就必须重新构建目录中的所有对象,而不是制定依赖项列表。

我想不出改变规则以适应这种情况的好方法,我愿意接受建议。如果标题列表不必硬编码,则加分

【问题讨论】:

  • 在下面写下我的答案后,我查看了相关列表,发现:stackoverflow.com/questions/297514/… 似乎是重复的。 Chris Dodd 的答案与我的相同,尽管它使用不同的命名约定。

标签: dependencies makefile header-files


【解决方案1】:

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

depend: .depend

.depend: $(SRCS)
        rm -f "$@"
        $(CC) $(CFLAGS) -MM $^ -MF "$@"

include .depend

depend: .depend

.depend: $(SRCS)
        rm -f "$@"
        $(CC) $(CFLAGS) -MM $^ > "$@"

include .depend

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

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

【讨论】:

  • 我喜欢这个技巧,但我怎样才能让depend 仅在源文件发生更改时运行?它似乎每次都运行...
  • @chase:嗯,我错误地依赖于目标文件,而它显然应该在源上,并且两个目标的依赖顺序也错误。这就是我从记忆中打字所得到的。立即尝试。
  • 是否可以在每个文件之前添加一些前缀以表明它在另一个目录中,例如 build/file.o
  • 我将 SRCS 更改为 OBJECTS,其中 OBJECTS 是我的 *.o 文件的列表。这似乎阻止了依赖每次运行,并且只捕获了对头文件的更改。这似乎与之前的 cmets 背道而驰。我错过了什么吗?
  • @dmckee 在处理多个目标及其依赖项时,以下问题是否存在相同的问题? stackoverflow.com/questions/30043480/…
【解决方案2】:

大多数答案都出人意料地复杂或错误。然而,简单而强大的示例已在其他地方发布 [codereview]。诚然,gnu 预处理器提供的选项有点令人困惑。但是,使用 -MM 从构建目标中删除所有目录已记录在案,而不是错误 [gpp]:

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

(更新的)-MMD 选项可能是您想要的。为了完整起见,一个支持多个 src 目录并使用一些 cmets 构建目录的 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 multiple dependency lines for a single target?

【讨论】:

  • OBJ 变量值存在拼写错误:CPP 应为 CPPS
  • 谢谢@trucza,现在应该修复了。
  • 开箱即用,即使 hpp 和 cpp 都在同一个目录上,它也无法为我找到标题。
  • 如果你的源文件(a.cppb.cpp)在./src/,那替换不是$(OBJ)=./build/src/a.o ./build/src/b.o吗?
  • 很好的答案。作为一个非常非常小的(并且稍微偏离主题!)改进,我建议使用此处列出的GNU make 的标准变量名称:gnu.org/software/make/manual/html_node/Implicit-Variables.html。所以,CXXFLAGS 而不是 CXX_FLAGS,你不是说 LDFLAGS,而不是 CXX_FLAGS 用于链接吗?
【解决方案3】:

正如我发布的heregcc 可以同时创建依赖和编译:

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

-include $(DEPS)

%.o: %.c
    $(CC) $(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。

【讨论】:

  • 当我尝试这样做时,它会导致我的所有 .o 文件都被创建为空文件。我确实在构建子文件夹中有我的对象(所以 $OBJECTS 包含 build/main.o build/smbus.o build/etc...),这肯定会创建 .d 文件,如您所描述的明显错误,但它肯定根本不构建 .o 文件,而如果我删除 -MM 和 -MF 就会这样做。
  • 使用 -MT 将解决答案最后几行中的注释,该注释会更新每个依赖项列表的目标。
  • @bobpaul 因为man gcc-MM 意味着-E,它“在预处理后停止”。你需要-MMD 代替:stackoverflow.com/a/30142139/895245
【解决方案4】:

怎么样:

includes = $(wildcard include/*.h)

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

您也可以直接使用通配符,但我往往会发现不止一个地方需要它们。

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

【讨论】:

  • 这行得通,但是,这样做的问题是每个目标文件都会被重新编译,每次进行小的更改时,即,如果您有 100 个源/头文件,并且您进行了小的更改只有一个,所有 100 个都会重新编译。
  • 这是一个非常糟糕的解决方案。当然它适用于一个小项目,但是对于任何生产规模的团队和构建,这将导致可怕的编译时间,并且相当于每次运行make clean all
  • 在我的测试中,这根本不起作用。 gcc 行根本不执行,而是执行内置规则(%o: %.c 规则)。
【解决方案5】:

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

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

-include $(DEPS)

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

【讨论】:

    【解决方案6】:

    这可以很好地完成工作,甚至可以处理指定的子目录:

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

    用 gcc 4.8.3 测试过

    【讨论】:

      【解决方案7】:

      这里有两条线:

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

      这适用于默认的 make 配方,只要您在 OBJS 中拥有所有目标文件的列表。

      【讨论】:

        【解决方案8】:

        Sophie 的answer 的略微修改版本,它允许将 *.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 的 version 中)。在这种组合中,它似乎将 *.o 文件的完整路径写入 *.d 文件。如果没有这种组合,-MMD 也只会将没有任何目录组件的纯文件名写入 *.d 文件。也许有人知道为什么 -MMD 与 -c 结合使用时会写入完整路径。我在 g++ 手册页中没有找到任何提示。

        【讨论】:

          【解决方案9】:

          我更喜欢这个解决方案,而不是 Michael Williamson 接受的答案,它捕获对源 + 内联文件的更改,然后是源 + 标题,最后只捕获源。这里的优点是,如果只进行了一些更改,则不会重新编译整个库。对于有几个文件的项目来说,这不是一个重要的考虑因素,如果你有 10 个或 100 个源,你会注意到差异。

          COMMAND= gcc -Wall -Iinclude ...
          
          %.o: %.cpp %.inl
              $(COMMAND)
          
          %.o: %.cpp %.hpp
              $(COMMAND)
          
          %.o: %.cpp
              $(COMMAND)
          

          【讨论】:

          • 只有当你的头文件中没有任何需要重新编译除相应实现文件之外的任何 cpp 文件的任何内容时,此方法才有效。
          【解决方案10】:

          以下对我有用:

          DEPS := $(OBJS:.o=.d)
          
          -include $(DEPS)
          
          %.o: %.cpp
              $(CXX) $(CFLAGS) -MMD -c -o $@ $<
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2017-10-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-09-14
            • 1970-01-01
            相关资源
            最近更新 更多