【问题标题】:GNU make uses same source file for each object file when part of dependency list is removed删除部分依赖项列表时,GNU make 对每个目标文件使用相同的源文件
【发布时间】:2018-01-02 22:37:42
【问题描述】:

我有一个有点复杂的源目录,并编写了一个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】:

    这里有很多问题。第一:

    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))。因此,如果任何源文件或头文件发生更改,所有目标文件都将被重建。显然这不是你想要的。

    另外,它总是编译同一个文件的原因是配方使用$&lt;,它代表第一个前提条件,而这里的第一个前提条件是test.cpp,所以总是编译的。

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

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

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

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

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

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

    【讨论】:

      猜你喜欢
      • 2020-08-22
      • 2013-03-25
      • 1970-01-01
      • 2011-03-20
      • 1970-01-01
      • 2021-10-25
      • 2018-05-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多