当删除部分依赖列表时,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 将不会重建您的目标文件(因为它不知道先决条件关系)。
我的源码目录有点复杂,写了个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 将不会重建您的目标文件(因为它不知道先决条件关系)。