如何为具有多个目录的 C++ 项目创建 Makefile?

How to create a Makefile for a C++ project with multiple directories?

我想为具有以下布局的项目创建 Makefile:

Source files (.cpp, potentially .c) in /src, with potential subdirectories
Header files (.h, .hpp...) in /inc, with potential subdirectories
Object files (.o) in /obj, with potential subdirectories
External libraries in /lib
Compiled program in /bin

到目前为止,我已经成功地编写了这个 Makefile,但存在一些问题:

SRC_DIR     := src
BIN_DIR     := bin
LIB_DIR        := lib
INC_DIR        := inc
OBJ_DIR        := obj

SRCEXTS     := .c .C .cc .cpp .CPP .c++ .cxx .cp
HDREXTS     := .h .H .hh .hpp .HPP .h++ .hxx .hp

TARGETS        := $(BIN_DIR)/program
SOURCES        := $(wildcard $(addprefix $(SRC_DIR)/*,$(SRCEXTS)))
HEADERS        := $(wildcard $(addprefix $(LIB_DIR)/*,$(HDREXTS)))
OBJECTS        := $(addsuffix .o, $(basename $(SOURCES)))

CXX         = g++
CXXFLAGS     = -std=c++17 -c -g -Wall

.PHONY: all clean

all: $(TARGETS)

$(TARGETS): $(OBJECTS)
    $(CXX) $^ -o $@

$(OBJ_DIR)%$(OBJECTS): $(SRC_DIR)%$(SOURCES)
    $(CXX) $(CXXFLAGS) $< -o $@

clean:
    rm -f $(OBJECTS) $(TARGETS)

我已尝试使其尽可能“通用”,以便将来的项目可以使用此布局和 makefile 作为模板来启动。目前,它在源代码所在的 src 目录中创建 .o 文件。尝试使用

编译程序时也会失败
g++ src/main.o -o bin/program
/usr/bin/ld: src/main.o: _ZSt4cout: invalid version 3 (max 0)
/usr/bin/ld: src/main.o: error adding symbols: bad value
collect2: error: ld returned 1 exit status
make: *** [Makefile:23: bin/program] Error 1

C++ 开发的新手。有一段时间一直在徒劳地追逐,试图清楚地了解这一切是如何运作的。我的代码基本上是我偶然发现的几个代码片段的怪异弗兰肯斯坦怪物。希望我的意图足够明确,这是我最后的努力!提前致谢:)

正如@JohnBollinger 所指出的,您一次尝试的太多了。我将建议一些更改以使您的 makefile 开始运行。

我无法解释您在尝试构建可执行文件时遇到的错误(您没有给我们足够的信息来重现错误),但它看起来不像是 Make 问题。我建议你尝试使用命令行构建它 without Make,看看会发生什么。

我假设你的源文件名以“.cpp”结尾(比如src/sailboat/foo.cpp),你的头文件名以“.hpp”结尾,目录树在[=19下=] 已经存在并且正确。这些限制是临时的训练轮;当你有更多的技能时,你可以删除它们。

首先,找到源文件。这个:

SOURCES := $(wildcard $(addprefix $(SRC_DIR)/*,$(SRCEXTS)))
如果 src/ 有子目录,

将不起作用。要递归到子目录,我们将使用 find。 (GNUMake 有一个快捷方式,但现在我们将以缓慢而谨慎的方式进行操作)。

SOURCES := $(shell find src -name "*.cpp")

现在构造所需目标文件的名称,例如obj/sailboat/foo.o。这个:

OBJECTS := $(addsuffix .o, $(basename $(SOURCES)))

会给你src/sailboat/foo.o。我们需要一个不同的命令来更改前导目录以及后缀:

OBJECTS := $(patsubst src/%.cpp,obj/%.o,$(SOURCES))

一些源文件引用头文件,因此在我们开始构建对象之前,我们必须能够提供它们。编译器可以找到需要的头文件,但我们必须告诉它在哪里搜索。所以我们需要目录,而不是完整路径:

HEADERS := $(shell find inc -name "*.hpp")
HEADERDIRS := $(sort $(dir $(HEADERS)))

sort只是去掉重复的,没必要,但是整洁。)

现在是构建对象的规则。 这是不正确的:

$(OBJ_DIR)%$(OBJECTS): $(SRC_DIR)%$(SOURCES)
    $(CXX) $(CXXFLAGS) $< -o $@

请记住 OBJECTS 可以包含多个 space 分隔的单词。因此,如果它包含 foo bar,目标将是 obj/%foo bar,这显然不是您想要的。同样,先决条件列表是错误的,配方也是如此。扔掉它并重新开始。

$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp
    $(CXX) $< -c -o $@

然后记住头文件,并添加标志以告诉编译器在哪里寻找它们:

INCLUDEFLAGS := $(addprefix -I,$(HEADERDIRS))

$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp
    $(CXX) $< -c $(INCLUDEFLAGS) -o $@

这应该足以让您的 makefile 正常工作;进一步的改进可以等待。