通过解析 Makefile 检索使用的头文件和源文件名

Retrieve used header and source file names by parsing Makefile

我想从还包含未使用目标的 Makefile 中提取所有 *.cc*.h 文件名。示例 Makefile 在这里:https://gist.github.com/berceanu/7554a9c4371b807e425259c7e99b5de9

我已经尝试 运行 make -Bnd 并查看了修剪后的文件,但我不知道这是否遗漏了任何内容。

make -Bnd | grep "Pruning file" | sort | uniq

预期结果:make run 在上述 Makefile 中使用的所有 *.h*.cc 文件的列表。

而不是-Bnd,我建议使用--dry-run --print-data-base来转储目标的完整数据库、它们的依赖关系、规则、变量等。

您尝试从 Makefile 中提取此信息的方法可能是错误的方法。 make 不知道实际上 使用了哪些头文件。 make 只知道您在依赖项中明确告诉 make 哪些头文件,这不是很可靠。 Makefile 中的信息可能在两个方面是错误的。它可能包含未使用的目标(正如您所注意到的)或未使用的头文件。它可能会遗漏 Makefile 中实际包含但未提及的头文件。更糟糕的是,如果头文件的实际包含取决于宏,比如 #ifdef XYZ_FEATURE #include "additionalHeaderFile.h" #endif.

至少有三种方法可以生成所需的列表,即编译期间实际使用的 .cc.h 文件的列表:

  • (盲目相信Makefilemake -n --print-data-base
  • (跟build的真实文件一样,但也有点难解析)strace -f make
  • (依赖于 GCC、clang、armcc 和可能的其他编译器中存在的功能 Makefile 生成,非常可靠)将 CPPFLAGS:=-MMD 添加到 Makefile、运行 make clean,然后 make,然后使用 cat *.d 获取用于构建 run 的所有 .cc.h 文件的列表。您甚至可以在不更改 Makefile 的情况下执行此操作:make clean; make CPPFLAGS:=-MMD && cat *.d | sed -e 's/\//g' -e 's/:/ /g' -e 's/ \+/\n/g' | sort -u.

此外,您在要点中分享的 Makefile 存在大量问题。

  • 按照惯例,默认目标应命名为 all 1 2 3。这不需要是二进制文件的名称,它只是一个 .PHONY 目标。
  • 包含目标文件列表的变量应命名为 OBJSOBJECTS,而不是 OBJ。名称 OBJ 具有误导性,因为它是单数。
  • 而不是 rm 使用 $(RM) 这意味着 -f 因此在文件不存在的情况下不会有问题。 (作为副作用,Makefile 将变得更便携,因为并非所有平台都使用 rm 删除文件。)
  • clean 不是文件,因此应该是 .PHONY 目标。
  • clean 应该使用 :: 而不是 : 作为它的配方,以便将来当 Makefile 更大并分成多个文件时,每个文件都可以有自己的 clean 目标没有问题。
  • 非特定规则的变量应该在定义时扩展,而不是在引用时扩展,因此使用 := 而不是 = 定义。
  • 而不是 C++ 使用已经定义的 CXX
  • 不要将选项放入 C++/CXX,而是使用 LDFLAGS,因为你 linking.
  • 您应该有一个与二进制文件具有相同基本名称的源文件。然后你可以使用内置规则 linking.
  • Makefile 中的显式依赖项是维护的难题。每次添加、删除或更改项目头文件的 #include 语句时,都必须更新 Makefile,这很容易忘记,而且很痛苦,尤其是当 #include 语句在头文件中。即使尽职调查,这也是一种无形的合并冲突。您应该在 Makefile 的开头使用 CPPFLAGS+=-MMD 并在 Makefile 的末尾附加 -include $(wildcard *.d),而不是在 Makefile 中具有显式依赖关系,并且将 *.d 添加到 clean 中要删除的文件列表中。然后,您可以从 Makefile 中删除 all 依赖规则(linkage 除外)。
  • 将二进制文件命名为 run 不是一个好主意。看到你的 Makefile 有一个 run 目标的用户会期望这个 运行 实际程序,而不是 link 它。
  • 最好将提到的每个对象单独占一行。当多个开发人员同时更改对象列表时,这会显着减少项目中的合并冲突。

您的实际 Makefile 应该如下所示,二进制文件从 run 重命名为 program:

LDFLAGS:=-Wno-deprecated -lm
CPPFLAGS+=-MMD
BINARY:=program

OBJECTS:= \
    $(BINARY).o \
    binomh.o \
    # More objects here

.PHONY: all
all: $(BINARY)

$(BINARY): $(OBJECTS)

.PHONY: clean
clean::
    $(RM) \
        $(BINARY) \
        *.[adios] \

-include $(wildcard *.d)

Makefile 会像您的 Makefile 那样做 "same" 的事情,但它几乎 无需维护 。无需更新依赖项,因为它们是自动从 C 预处理器生成的依赖项文件中获取的。如果您将 -save-temps 添加到任何 CFLAGSCXXFLAGSCPPFLAGS.

*.[adios] 也会删除创建的文件

已知这种类型的 Makefile 适用于 GCC、clang、AOCC(AMD 优化 C 编译器)和 armcc。它可能也适用于许多其他编译器和预处理器,尤其是当它们基于或试图与 GCC 或 clang/LLVM.

兼容时

顺便说一句,如果你有兴趣知道这对你有用,除了经验之外,我有很高的信心:我已经拿走了你的 Makefile 并在其中添加了以下几行以重现你的源代码代码结构。头文件只是空文件。 C++ 源文件将是从 Makefile.

中的依赖项中获取的 #include 语句的列表
%.cc:
    grep '^$*\.o.*:' $(MAKEFILE_LIST) | sed -e 's/.*://' -e 's/.*$*\.cc//' -e 's/ \([^ ]\+\)/#include ""\n/g' >$@

%.h:
    touch $@