我有一个有点复杂的源目录,并编写了一个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
文件只是创建一个指向每个类的指针,然后删除它。
这里有很多问题。第一:
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将不会重建您的目标文件(因为它不知道该先决条件关系)。