I have a project for school and I want to write a Makefile, I have seen some examples of using Makefile with multiple source directories and multiple executables but still could not implement it properly to my Makefile.
PS: I'm using doctest for the unit testing (and I can't change it).
Here is the project structure (and I can't change it):
.
├── bin
├── build
├── extern
│ └── doctest.h
├── include
│ ├── file1.hpp
│ └── file2.hpp
├── src
│ ├── file1.cpp
│ └── file2.cpp
├── tests
│ ├── file1-test.cpp
│ └── file2-test.cpp
└── Makefile
I have the following directories:
bin
: for all the executables.build
: for all the objects (.o
).extern
: for the doctest header (this is where I would have stored any other library)include
: for all the headers (.hpp
).src
: for all the classes (.cpp
).tests
: for all the unit tests (also.cpp
)
You can see file1.cpp
as a class, file1.hpp
as the class header and file1-test.cpp
as the unit tests for the class.
Here is my 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)
So my questions are:
Is my Makefile following good practices ?
Is it optimized ? If no, how can I make it even better ?
For every new executable I've to add a
OBJS_X
variable and a target$(BIN_DIR)fileX-test: $(OBJS_X)
, can i get rid of it ? If yes can someone write me some generic rule, so I don't have to specify a variable and a target every time I want a new executable.If I want to compile only one executable I have to use
make bin/fileX-test
. Is it possible to run onlymake fileX-test
instead ofmake bin/fileX-test
(but still building it in thebin
directory) ? I tried to implement a rule like this:fileX-test: $(BIN_DIR)fileX-test
but it's not working as I want, at the very end of the compilation it starts executing builtin rules and I don't know why. Can someone explain ?
Final answer:
This is what I considere a good answer, if it can help someone later:
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)*