如何编写必须生成两个可执行文件的 makefile

How to write a makefile which has to generate two executables

我正在学习如何使用 make。最近我写了一个makefile来编译我的一个项目,它的结构是:src(其中包含file.cppmain.cpp) 和 include(其中包含 file.h)文件夹和 makefile,里面写法如下:

TARGET_EXEC := main
CC := g++

BUILD_DIR := .
SRC_DIR := src
OBJ_DIR := obj

SRC := $(shell find $(SRC_DIR) -name '*.cpp')
OBJ := $(SRC:%=$(OBJ_DIR)/%.o)
DEPS := $(OBJ:.o=.d)

INC_DIR := $(shell find $(SRC_DIR) -type d)
INC_FLAGS := $(addprefix -I,$(INC_DIR))

CPPFLAGS := $(INC_FLAGS) -MMD -MP

$(BUILD_DIR)/$(TARGET_EXEC): $(OBJ)
    $(CC) $(OBJ) -o $@ $(LDFLAGS)

$(OBJ_DIR)/%.cpp.o: %.cpp
    @ mkdir -p $(dir $@)
    $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@

.PHONY: clean
clean:
    rm -r $(OBJ_DIR) main
    
-include $(DEPS)

现在我想添加一个新文件夹 (test),我将在其中托管一个 tests.cpp 代码测试代码。在这种情况下,我想获得两个可执行文件:maintests(不仅是 main ),一个用于主要,一个用于测试。我什么都试过了,但我不明白如何修改以前的 makefile 以实现新的 test 文件夹信息。

编辑 1

我尝试添加 Andreas 的建议以及 all 命令来创建两个可执行文件:

TARGET_EXEC := main
TEST_EXEC := tests
CC := g++

BUILD_DIR := .
SRC_DIR := src
OBJ_DIR := obj
TEST_DIR := test

SRC := $(shell find $(SRC_DIR) -name '*.cpp')
OBJ := $(SRC:%=$(OBJ_DIR)/%.o)
DEPS := $(OBJ:.o=.d)

INC_DIR := $(shell find $(SRC_DIR) -type d)
INC_FLAGS := $(addprefix -I,$(INC_DIR))

CPPFLAGS := $(INC_FLAGS) -MMD -MP

all: $(BUILD_DIR)/$(TARGET_EXEC) $(BUILD_DIR)/$(TEST_EXEC)

$(BUILD_DIR)/$(TARGET_EXEC): $(SRC_DIR)/$(TARGET_EXEC).o $(OBJ)
    $(CC) $(OBJ) -o $@ $(LDFLAGS)

$(BUILD_DIR)/$(TEST_EXEC): $(TEST_DIR)/$(TEST_EXEC).o $(OBJ)
    $(CC) $(OBJ) -o $@ $(LDFLAGS)

$(OBJ_DIR)/%.cpp.o: %.cpp
    @ mkdir -p $(dir $@)
    $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@

.PHONY: clean all
clean:
    rm -r $(OBJ_DIR) main
    
-include $(DEPS)

现在创建了两个可执行文件 maintests,但是它们做同样的事情(与之前的 main 相同),所以它是错的。编译时我的输出是:

g++ -Isrc -MMD -MP  -c src/osmanip.cpp -o obj/src/osmanip.cpp.o
g++ -Isrc -MMD -MP  -c src/main.cpp -o obj/src/main.cpp.o
g++ obj/src/osmanip.cpp.o obj/src/main.cpp.o -o main 
g++ obj/src/osmanip.cpp.o obj/src/main.cpp.o -o tests 

我认为错误可能在:

$(OBJ_DIR)/%.cpp.o: %.cpp
    @ mkdir -p $(dir $@)
    $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@

你怎么看?

编辑 2

好的,我想我找到了解决方案:

TARGET_EXEC := main
TEST_EXEC := tests
CC := g++

BUILD_DIR := .
SRC_DIR := src
OBJ_DIR := obj
TEST_DIR := test

SRC := $(shell find $(SRC_DIR) -name '*.cpp')
TEST := $(shell find $(TEST_DIR) -name '*.cpp')
OBJ := $(SRC:%=$(OBJ_DIR)/%.o)
TEST_OBJ := $(TEST:%=$(OBJ_DIR)/%.o)
DEPS := $(OBJ:.o=.d)

INC_DIR := $(shell find $(SRC_DIR) -type d)
INC_FLAGS := $(addprefix -I,$(INC_DIR))

CPPFLAGS := $(INC_FLAGS) -MMD -MP

.PHONY: clean all

all: $(BUILD_DIR)/$(TARGET_EXEC) $(BUILD_DIR)/$(TEST_EXEC)

$(BUILD_DIR)/$(TARGET_EXEC): $(OBJ)
    $(CC) $(OBJ) -o $@ $(LDFLAGS)

$(BUILD_DIR)/$(TEST_EXEC): $(TEST_OBJ)
    $(CC) $(TEST_OBJ) -o $@ $(LDFLAGS)

$(OBJ_DIR)/%.cpp.o: %.cpp
    @ mkdir -p $(dir $@)
    $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@

clean:
    rm -r $(OBJ_DIR) main tests
    
-include $(DEPS)

使用这个 makefile 编译很好(我认为),因为 obj/srcobj/test文件夹被创建。 main 可执行文件已正确编译并运行。 tests 可执行文件也被编译(我认为是正确的),但我得到另一个错误,与 makefile 无关:

g++ -Isrc -MMD -MP  -c src/osmanip.cpp -o obj/src/osmanip.cpp.o
g++ -Isrc -MMD -MP  -c src/main.cpp -o obj/src/main.cpp.o
g++ obj/src/osmanip.cpp.o obj/src/main.cpp.o -o main 
g++ -Isrc -MMD -MP  -c test/tests.cpp -o obj/test/tests.cpp.o
g++ obj/test/tests.cpp.o -o tests 
/usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/Scrt1.o: in function `_start':
(.text+0x24): undefined reference to `main'
collect2: error: ld returned 1 exit status
make: *** [makefile:29: tests] Error 1

我认为这是因为我的 test/tests.cpp 文件是

#define DOCTEST_CONFIG_IMPLEMENT
#include "doctest/doctest.h"
#include <string>

我暂时没有给它任何命令。我在没有 main 的情况下实现了 doctest,实际上它告诉我缺少 main 函数(因为它在 src/main.cpp 文件中实现)。我试着查看 答案,但我没有解决这个问题。

你知道如何正确编译doctest test.cpp文件吗?谢谢。

编辑 3

感谢 Jarod42 的回答,我也解决了与 doctest 相关的最后一个问题。现在一切似乎都很完美。

非常感谢你们!这是我在 Stack Overflow 上的第一个 post,我真的很喜欢这个网站。

尝试过卑微的开始吗?这是我开始和构建的地方,关键是将 main.o 与 OBJ 分开,以便测试可以从 tests.o:

获得其主要内容
# Adding include directories (weird way of doing it but ok)
CPPFLAGS := $(addprefix -I,$(shell find src -type d))

# object files
OBJ := src/file1.o src/file2.o

# main
main: src/main.o $(OBJ)

# the test program
tests: test/tests.o $(OBJ)

(注意这依赖于内置的配方,开箱即用。)

从那里您可以逐步重新介绍其他概念:

  • 树外构建。
    • 被高估了(至少对于全手动的 GNU Make 而言)。
  • 全局源文件。
    • 如果你知道你有什么文件,最好不要 glob。
  • 依赖文件生成和包含。
  • 将文件夹等保存在变量中。