当删除部分依赖列表时,GNU make 对每个目标文件使用相同的源文件

GNU make uses same source file for each object file when part of dependency list is removed

我的源码目录有点复杂,写了个makefile来编译:

├── include
│   ├── subinc
│   │   ├── test_y.h
│   │   └── test_z.h
│   ├── test_w.h
│   └── test_x.h
├── makefile
├── src
│   ├── test_w.cpp
│   └── test_x.cpp
├── src2
│   ├── test_y.cpp
│   └── test_z.cpp
└── test.cpp

如下所示的 makefile 正在运行。但是,我有点困惑为什么。它似乎没有使用 $(DEPS),因为当我在规则中回显它时,它给出了像 ./include/./include/subinc/test_y.h 这样的路径。这很明显,因为 patsubst 行,但是将其更改为 patsubst %,%,$(INCLUDES) 也会破坏它......(也许这就是整个问题的根源!)

但是,当我从规则 %.o 的依赖列表中删除该常量时,发生了一些奇怪的事情,所以规则只是 %.o: $(SOURCES)。在 运行 make 时,它​​使用 $(SOURCES) 中的第一项作为每次调用 g++ 创建目标文件的目标:

$ make
g++ -c -o test.o test.cpp -I./include -I./include/subinc
g++ -c -o src/test_x.o test.cpp -I./include -I./include/subinc
g++ -c -o src/test_w.o test.cpp -I./include -I./include/subinc
g++ -c -o src2/test_z.o test.cpp -I./include -I./include/subinc
g++ -c -o src2/test_y.o test.cpp -I./include -I./include/subinc

我认为这是有道理的,因为使用了 $<

但是为什么当我从列表中取出第二个常量(头文件——有些甚至格式错误)时,它只打印依赖列表中的第一个?

我的想法是 make 以某种方式智能地将列表中的 .cpp 文件与列表中相应的 .h 文件匹配,然后每次都将它们从列表中删除它运行规则....

谢谢


Makefile(工作版本,可能充满不良做法...)

INCDIR=./include
SRCDIR=./src

CXX=g++
CXXFLAGS=-I$(INCDIR) -I$(INCDIR)/subinc

INCLUDES=$(shell find . -name "*.h" -o -name "*.hpp")
DEPS = $(patsubst %,$(INCDIR)/%,$(INCLUDES))

EXE=testexe
SOURCES=$(wildcard *.cpp) $(wildcard **/*.cpp)
OBJ=$(SOURCES:.cpp=.o)

####RULES
%.o: $(SOURCES) $(DEPS)
    $(CXX) -c -o $@ $< $(CXXFLAGS)

$(EXE): $(OBJ)
    g++ -o $@ $^ $(CXXFLAGS)
    rm $(OBJ)

编辑

如果你想要一个 MCVE,每个 test_*.h 定义一个空的 class like

class T*{
    T*();  //defined in test_*.cpp to print "T* created"
    ~T*(); //defined in test_*.cpp to print "T* destroyed"
};

test.cpp 文件只是创建一个指向每个 class 的指针,然后将其删除。

这里有很多问题。第一:

SOURCES=$(wildcard *.cpp) $(wildcard **/*.cpp)

GNU make 使用简单的 globbing,它不理解 **。这与 * 的行为相同。由于您只有一级子目录,这对您有用,但如果您添加另一级(子子目录),这将不匹配。

第二,这是错误的:

DEPS = $(patsubst %,$(INCDIR)/%,$(INCLUDES))

正如您所指出的,这会将依赖路径从(正确的)./include/xy/z.h 更改为(不正确的)./include/./include/xy/z.h。我不确定您为什么要在这里尝试更改任何内容:为什么不直接使用 INCLUDES 变量内容?使用 $(patsubst %,%,$(INCLUDES)) 是空操作;没有效果。

第三,您应该对这些类型的赋值使用简单扩展 (:=),这样它们就不会在每次使用变量时都重新运行。

接下来,这是错误的:

%.o: $(SOURCES) $(DEPS)
        $(CXX) -c -o $@ $< $(CXXFLAGS)

SOURCES 解决后,这将扩展为如下内容:

%.o: test.cpp src/test_w.cpp src/test_x.cpp src2/test_y.cpp src2/test_z.cpp ./include/include/subinc/test_y.h $(DEPS)
        $(CXX) -c -o $@ $< $(CXXFLAGS)

这意味着每个目标文件依赖于所有源文件(和所有$(DEPS))。因此,如果任何源文件或头文件发生更改,所有目标文件都将被重建。显然这不是你想要的。

另外,它总是编译同一个文件的原因是食谱使用了$<,代表第一个前提,而这里的第一个前提是test.cpp,所以总是编译。

当您创建模式规则时,(至少)第一个源文件也应该(几乎总是)是一个模式,以便它随着构建每个目标文件的目标而变化(在本例中)。

因此,您希望您的模式规则如下所示:

%.o: %.cpp $(INCLUDES)
        $(CXX) -c -o $@ $< $(CXXFLAGS)

当然这确实意味着每个目标文件都依赖于所有头文件,因此如果您更改任何头文件,您的所有目标文件都会被重建。也许没关系;如果不是,你需要做 something more advanced.

最后,您问为什么如果您在 $(DEPS) 变量中创建虚假路径,您的 makefile 似乎可以正常工作。原因如下:因为这些路径不存在,make 决定您的模式规则不适用(因为无法创建所有先决条件),因此它完全忽略您的模式规则

一旦发生这种情况,make 关于如何构建目标文件的默认模式规则就会接管,并且该默认规则会正确地为您构建内容。但是,您可能会注意到,如果您修改任何头文件,make 将不会重建您的目标文件(因为它不知道先决条件关系)。