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

时间:2018-01-02 22:37:42

标签: c++ unix makefile gnu-make gnu

我有一个有点复杂的源目录,并编写了一个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文件只是创建一个指向每个类的指针,然后删除它。

1 个答案:

答案 0 :(得分: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,所以&#39; s&#39 39;总是编译。

当您创建模式规则时,(至少)第一个源文件应该(几乎总是)应该是一个模式,以便它与目标一起更改以构建每个目标文件(在本例中)。

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

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

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

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

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