I don’t like make.
The syntax is clunky, the semantics are unintuitive, it’s hard to get parallel builds to work well, and there are a million ways to shoot yourself in the foot.
Nevertheless, I stick with it, because it’s ubiquitous and it’s the simplest solution for what I do.
A simple Makefile might look like this:
all: main OBJS = \ Foo.o \ Bar.o \ Baz.o main: $(OBJS) $(CXX) $(LDFLAGS) $^ -o $@ |
This says main
depends on Foo.o
, Bar.o
, and Baz.o
; each of these is built using one of the builtin rules.
I often want some extra build flags for debugging or optimization, so I add:
CFLAGS += -ggdb CXXFLAGS += -fno-inline |
If I’m using GCC and I want automatic dependency generation, I add this:
CFLAGS += -MMD -MP DEP_FILES = $(patsubst %.o,%.d,$(OBJS)) -include $(DEP_FILES) |
Now whenever I build Foo.o from Foo.cpp
(or Foo.c
), g++ will generate Foo.d at the same time. The next time I type make
, it will read Foo.d to determine what Foo.o depends on.
And just in case something gets borked, I usually want a clean
rule:
GENERATED_FILES += $(OBJS) .PHONY: clean clean: $(RM) $(GENERATED_FILES) |
Now none of this is intuitive (I learned it all from trial-and-error), and there’s a whole lot more that build systems need to do (building shared libraries, finding where libraries are located on a system, etc.), but this really does solve 90% of the problems I hit in small-to-medium projects. I’ve even used a setup like this in a medium-large project (> 200K lines of code) with success.
Please, tell me why I should switch? I despise make just as much as the next guy, but it’s simple, and it works.