Any suggestions on how to improve the Makefile
, e.g. how to best replace the multiple uses of example1/example2
, ex1_src/ex2_src
, ex1_obj/ex2_obj
with static pattern rules?
build := ./build
targets := $(build)/example1 $(build)/example2
src := ./src
srcfiles := $(shell find $(src) \( -name "*.cpp" -or -name "*.c" \) ! -path '*/examples/*')
objects := $(srcfiles:%=$(build)/%.o)
ex1_src := $(shell find $(src) -name example1.cpp)
ex1_obj := $(ex1_src:%=$(build)/%.o)
ex2_src := $(shell find $(src) -name example2.cpp)
ex2_obj := $(ex2_src:%=$(build)/%.o)
all_obj := $(objects) $(ex1_obj) $(ex2_obj)
depends := $(all_obj:%.o=%.d)
incdirs := $(shell find $(src) -type d ! -path '*/examples')
includes := $(addprefix -I,$(incdirs))
CXX := g++
CXXFLAGS := -std=c++17 $(includes) -Wall -MMD -MP
LDFLAGS := -lpthread
all: $(targets)
$(build)/example1: $(objects) $(ex1_obj)
$(build)/example2: $(objects) $(ex2_obj)
$(targets):
$(CXX) $(LDFLAGS) $^ -o $@
$(build)/%.cpp.o: %.cpp
@mkdir -p $(dir $@)
$(CXX) $(CXXFLAGS) -c $< -o $@
.PHONY: clean
clean:
rm -rf $(build)
-include $(depends)
-
1\$\begingroup\$ Welcome to Code Review! Incorporating advice from an answer into the question violates the question-and-answer nature of this site. You could post improved code as a new question, as an answer, or as a link to an external site - as described in I improved my code based on the reviews. What next?. I have rolled back the edit, so the answers make sense again. \$\endgroup\$Toby Speight– Toby Speight2021年02月07日 09:40:58 +00:00Commented Feb 7, 2021 at 9:40
1 Answer 1
It's usually best to have the Makefile in the same directory as the build products, or at least have that as the working directory. Then there's no need to re-write the built-in rules for compiling and linking. Use VPATH
to ensure the source files can be found.
LDFLAGS := -lpthread
That should go into LIBS
(which gets expanded later in the command line), so that it gets used as needed. LDFLAGS
is for flags such as -L
which need expanding further to the left.
I'm not convinced that example1
and example2
are great names for your programs - surely you can think of something more descriptive and memorable for your users?
ex1_src := $(shell find $(src) -name example1.cpp)
Do you really need to invoke a find
there? I would expect there to be few enough matches that you could simply list them, and update the list when you add a new one. I'd do the same for srcfiles
too - or create a library, so that the resultant programs only include the objects they need.
I think generally there's too much use of shells here, despite them all being in :=
assignments. You want makefile parsing to be fast, so you can see all your unit test results as quickly as possible.
So I'd write
example1: example1.o object_a.o object_b.o object_c.o
example1: LINK.o = $(LINK.cc)
The target-specific LINK.o
is necessary so that the C++ linker is used. It's cleaner and more portable than adding the C++ runtime library to LIBS
.
You're missing some very important CXXFLAGS
:
CXXFLAGS += -Wall -Wextra
I'd likely add a few more:
CXXFLAGS += -Wpedantic -Warray-bounds -Weffc++
CXXFLAGS += -Werror
Good use of .PHONY
- that's often overlooked.
You should also have .DELETE_ON_ERROR:
so that interrupted builds don't cause problems.
-
1\$\begingroup\$ Thanks! +1 Only one concern, I dont like to have all objects and deps in the top level build folder. Any good advice for that? \$\endgroup\$linkjumper– linkjumper2021年02月06日 20:22:47 +00:00Commented Feb 6, 2021 at 20:22
-
1\$\begingroup\$ I don't see why not - it's the way Make is designed to work. It's nice to have all the products in one place - then you can clean just by deleting the build directory. If you want to work against Make, then that's your choice of course. But I find it easier to let the built-in rules carry the load, and have a makefile that follows convention so that anyone else can follow it. \$\endgroup\$Toby Speight– Toby Speight2021年02月06日 21:23:39 +00:00Commented Feb 6, 2021 at 21:23