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

问题描述 投票:0回答:1

我有一个有点复杂的源目录,并编写了一个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 T*{
    T*();  //defined in test_*.cpp to print "T* created"
    ~T*(); //defined in test_*.cpp to print "T* destroyed"
};

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

c++ unix makefile gnu-make gnu
1个回答
0
投票

这里有很多问题。第一:

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将不会重建您的目标文件(因为它不知道该先决条件关系)。

© www.soinside.com 2019 - 2024. All rights reserved.