多个子目录的 Makefile 模式规则

Makefile pattern rules on multiple subdirectories

所以我有一个 GNU Makefile 可以将我的游戏目录及其子目录中的所有 .cpp 文件编译成 build/obj 文件夹中的 .o 文件(那个文件夹中没有子目录)。我的问题是只编译了列表中的第一个文件,我知道问题是我需要一个构建模式,但我想不出在每个子目录中实现它的方法。我尝试了几件事,使用 foreach 遍历所有目录,从目录中删除文件名并放置通配符,但都无济于事。有帮助吗?

SRC_DIR=src
BUILD_DIR=build
OBJ_DIR=$(BUILD_DIR)/obj

GAME_DIR=$(SRC_DIR)/Game
GAME_DIRS=$(GAME_DIR)/Classes $(GAME_DIR)/ConVars $(GAME_DIR)/EventListener $(GAME_DIR)/Functions $(GAME_DIR)/Interfaces $(GAME_DIR)/Modules $(GAME_DIR)/NetVars $(GAME_DIR)/Offsets $(GAME_DIR)

SRC_FILES=$(foreach dir,$(GAME_DIRS),$(wildcard $(dir)/*.cpp))
OBJ_FILES=$(addprefix $(OBJ_DIR)/,$(notdir $(patsubst %.cpp,%.o,$(SRC_FILES))))

all: $(OUT)

$(OUT): $(OBJ_FILES)
    $(CC) -shared $^ -o $@ -std=c++17 -lgdi32

$(OBJ_FILES): $(OBJ_DIR)/%.o: $(SRC_FILES)
    $(CC) -c $< $(CFLAGS) -o $@

这是错误的:

$(OBJ_FILES): $(OBJ_DIR)/%.o: $(SRC_FILES)

它说,每个目标文件都依赖于 ALL 个源文件。 SRC_FILES 变量每次都会扩展到相同的文件列表,所以对于每个目标文件,先决条件列表都是相同的,所以每次编译它时,你都使用 $< 扩展到第一个先决条件,所以同一个文件被一遍又一遍地编译。

在模式规则中,与模式匹配的名称部分在目标和先决条件之间必须相同,而与模式不匹配的名称部分必须是静态的。

因此,无法像这样使用单一模式规则构建所有源文件。您有多种选择:

您可以创建多个模式规则,每个源目录一个(在这种情况下更容易避免静态模式规则):

$(OBJ_DIR)/%.c : Classes/%.cpp
         ...
$(OBJ_DIR)/%.c : ConVars/%.cpp
         ...

或者,您可以将目标文件放入 OBJ_DIR 的子目录中,但具有相同的前缀。因此,不是 Classes/class.cpp 构建到 obj/class.o,而是构建到 obj/Classes/class.o。为此,您将使用:

OBJ_FILES=$(addprefix $(OBJ_DIR)/,$(patsubst %.cpp,%.o,$(SRC_FILES)))

$(OBJ_FILES): $(OBJ_DIR)/%.o : %.cpp
        @mkdir -p $(@D)
        $(CC) -c $< $(CFLAGS) -o $@

(注意,使用 CCCFLAGS 构建 C++ 代码并不正确;这些变量传统上用于构建 C 代码。CXXCXXFLAGS 是构建 C++ 代码的最佳变量)

另一种选择是使用 VPATH,如下所示:

 VPATH = $(GAME_DIRS)

 $(OBJ_FILES): $(OBJ_DIR)/%.o : %.cpp
          $(CC) -c $< $(CFLAGS) -o $@