C++ Makefile,是否可以进一步分解它?

C++ Makefile, is it possible to factorize it even more?

我有一个学校项目,我想编写一个 Makefile,我看到了一些将 Makefile 与多个源目录和多个可执行文件一起使用的示例,但仍然无法在我的 Makefile 中正确实现它。

PS: 我正在使用 doctest 进行单元测试(我无法更改它)。

这是项目结构(我无法更改它):

.
├── bin
├── build
├── extern
│   └── doctest.h
├── include
│   ├── file1.hpp
│   └── file2.hpp
├── src
│   ├── file1.cpp
│   └── file2.cpp
├── tests
│   ├── file1-test.cpp
│   └── file2-test.cpp
└──  Makefile

我有以下目录:

您可以将 file1.cpp 视为 class,将 file1.hpp 视为 class header,并将 file1-test.cpp 视为单元测试class.

这是我的 Makefile:

BIN_DIR := bin/
BUILD_DIR := build/
EXTERN_DIR := extern/
INCLUDE_DIR := include/
SOURCE_DIR := src/
TESTS_DIR := tests/
DEP_DIR := .dep/

DEPENDS := $(patsubst %.o, $(BUILD_DIR)$(DEP_DIR)%.d, $(notdir $(wildcard $(BUILD_DIR)*.o)))

EXE := $(addprefix $(BIN_DIR), file1-test file2-test)

OBJS_1 := $(addprefix $(BUILD_DIR), file1.o)
OBJS_2 := $(addprefix $(BUILD_DIR), file1.o file2.o)

CXX := clang++
CXXFLAGS := -Wall -std=c++11 -g -O3 -I$(INCLUDE_DIR) -I$(EXTERN_DIR)

vpath %.cpp $(SOURCE_DIR) $(TESTS_DIR)

.PHONY: all clean

all: $(EXE)

$(BUILD_DIR) $(BIN_DIR) $(BUILD_DIR)$(DEP_DIR):
    @mkdir -p $@

$(BUILD_DIR)%.o: %.cpp | $(BUILD_DIR) $(BUILD_DIR)$(DEP_DIR)
    @$(CXX) $(CXXFLAGS) -MMD -MP -MF $(BUILD_DIR)$(DEP_DIR)$(notdir $(basename $@).d) -c $< -o $@

$(BIN_DIR)%: $(BUILD_DIR)%.o | $(BIN_DIR)
    @$(CXX) -o $@ $^

$(BIN_DIR)file1-test: $(OBJS_1)
$(BIN_DIR)file2-test: $(OBJS_2)

.PRECIOUS: $(BUILD_DIR)%.o

-include $(DEPENDS)

clean:
    -rm -rf $(BIN_DIR) $(BUILD_DIR)

所以我的问题是:

最终答案:

这是我认为的一个很好的答案,如果它对以后的人有帮助的话:

BIN_DIR := bin/
BUILD_DIR := build/
EXTERN_DIR := extern/
INCLUDE_DIR := include/
SOURCE_DIR := src/
TESTS_DIR := tests/
DEP_DIR := $(BUILD_DIR).dep/

CXX := g++
CXXFLAGS := -Wall -std=c++11 -g -O3 -I$(INCLUDE_DIR) -I$(EXTERN_DIR)
DEPFLAGS := -MMD -MP -MF $(DEP_DIR)

vpath %.cpp $(SOURCE_DIR) $(TESTS_DIR)

file1-test_OBJECTS := $(addprefix $(BUILD_DIR), file1.o)
file2-test_OBJECTS := $(addprefix $(BUILD_DIR), file1.o file2.o)

EXE := $(patsubst %_OBJECTS, %, $(filter %_OBJECTS, $(.VARIABLES)))

.PHONY: all keep help check clean $(EXE)

all: $(EXE:%=$(BIN_DIR)%)

$(foreach E, $(EXE), $(eval $(BIN_DIR)$E: $($E_OBJECTS)))
$(foreach E, $(EXE), $(eval $E: $(BIN_DIR)$E ;))

$(BUILD_DIR) $(BIN_DIR) $(DEP_DIR):
    @mkdir -p $@

$(BUILD_DIR)%.o: %.cpp | $(BUILD_DIR) $(DEP_DIR) $(BIN_DIR)
    @$(CXX) $(CXXFLAGS) $(DEPFLAGS)$(@F:.o=.d) -c $< -o $@

$(BIN_DIR)%: $(BUILD_DIR)%.o
    @$(CXX) -o $@ $^

-include $(wildcard $(DEP_DIR)*.d)

keep: $(EXE:%=$(BUILD_DIR)%.o)

clean:
    -@rm -rf $(BIN_DIR)* $(BUILD_DIR)* $(DEP_DIR)*

我正在将我的评论变成一个答案,以允许其他人不赞成这种观点:我认为 CMake 更适合您。查看 this SO 以了解 Make 和 CMake 之间的一些差异以及 CMake 的参数。

与您的问题相关的优点:

  • 它会让你更容易遵循好的做法
  • 扩展性更好
  • 您不必为添加到代码中的新可执行文件编写如此多的样板文件
  • 构建单个可执行文件是可能的,参见 作为提示。

大多数情况下,您的 makefile 非常好。您可以进行一些简化,但它们只是语法而不是真正的性能等:

DEP_DIR := .dep/

您永远不会单独使用它,因此如果您将其定义更改为:

DEP_DIR := $(BUILD_DIR).dep/

您可以简化对它的引用。

DEPENDS := $(patsubst %.o, $(BUILD_DIR)$(DEP_DIR)%.d, $(notdir $(wildcard $(BUILD_DIR)*.o)))

-include $(DEPENDS)

这看起来很复杂。为什么不去掉 DEPENDS 而只写:

include $(wildcard $(DEP_DIR)*.d)

这个:

@$(CXX) $(CXXFLAGS) -MMD -MP -MF $(BUILD_DIR)$(DEP_DIR)$(notdir $(basename $@).d) -c $< -o $@

也很复杂。你可以把它写成(如果你只是DEP_DIR):

@$(CXX) $(CXXFLAGS) -MMD -MP -MF $(DEP_DIR)$(@F:.o=.d) -c $< -o $@

对于:

.PRECIOUS: $(BUILD_DIR)%.o

我绝对不会用这个。 .PRECIOUS 应该很少使用,如果有的话。如果你试图避免目标文件被认为是中间文件,最好直接将它们列为先决条件,例如:

keep : $(EXE:$(BIN_DIR)%=$(BUILD_DIR)%.o)

但是除非您有特殊需要查看这些目标文件,否则让 make 删除它们也没什么坏处。

关于你关于快捷方式的问题:你看到你所做的行为的原因是你的目标定义:

fileX-test: $(BIN_DIR)fileX-test

没有附加的配方,因此 make 将尝试使用隐式规则查找配方。它会找到 % : %.c 的内置配方,并且因为您设置了 vpath 它可以找到匹配的 %.c 文件,所以它会使用它。为避免这种情况,您可以只提供一个空的食谱;将上面的替换为:

fileX-test: $(BIN_DIR)fileX-test ;

(注意添加分号)。

你的主要问题是如何简化这个:

EXE := $(addprefix $(BIN_DIR), file1-test file2-test)

OBJS_1 := $(addprefix $(BUILD_DIR), file1.o)
OBJS_2 := $(addprefix $(BUILD_DIR), file1.o file2.o)

all: $(EXE)

$(BIN_DIR)file1-test: $(OBJS_1)
$(BIN_DIR)file2-test: $(OBJS_2)

您可以自动执行此操作,但这样做需要更深入地了解 GNU make。您可能会发现这组博文很有趣:http://make.mad-scientist.net/category/metaprogramming/(从底部/最旧的开始,逐步向上)。

将上面的替换为:

# Write one of these for each program you need:

file1-test_OBJECTS = file1.o
file2-test_OBJECTS = file1.o file2.o

# Now everything below here is boilerplate

EXE = $(patsubst %_OBJECTS,%,$(filter %_OBJECTS,$(.VARIABLES)))

all: $(EXE:%=$(BIN_DIR)%)

$(foreach E,$(EXE),$(eval $(BIN_DIR)$E: $($E_OBJECTS)))
$(foreach E,$(EXE),$(eval $E: $(BIN_DIR)$E ;))
.PHONY: $(EXE)