Generic makefile for C++ projects Looking at back at the C++ projects I've developed, the Makefiles are a mess.  To tell you the truth, I copied one a long time ago and basically have been cutting and pasting it haphazardly ever since.  I've decided to change that so I've been reading more about how Make actually works and some best practices for using it going forth.  As most of the programs I write are not that complicated I've come up with a generic Makefile I can cut and paste ^W^W^W deploy going forth.  I'd like some feedback on it.

Any input (except "you should be using cmake or $(OTHER_TRENDY_NEW_TOOL) instead") is welcome but some specific questions that come to mind are:


Have I forgotten to add something?
Conversely, have I added too much?
Is it worth making this a POSIX makefile.  Currently I've only tried
it with GNU make on Linux but portability is good.  What about BSD or
Windows make? Or should I just rely on gmake being available pretty
much everywhere?
Do I have all the recommended compiler/linker flags for C++?  I would
like to be able to choose between g++ and clang atleast so options
should be portable between both though if there is something good
that only one supports I can special case it.  (Can I?)
I'm assuming Visual Studio/Windows in general is a whole separate can
of worms so I've ignored it for now but if you know how this Makefile
could be adapted for Windows development I'd be happy to know.


Thanks in advance.

    PROGRAM=someprogram
    SRCDIR:=src
    INCDIR:=include
    DEPDIR:=deps
    BUILDDIR:=build
    DESTDIR?=
    PREFIX?=/usr/local
    BINDIR?=bin

    SRC:=$(wildcard $(SRCDIR)/*.cc)
    OBJECTS:=$(patsubst $(SRCDIR)/%.cc,$(BUILDDIR)/%.o,$(SRC))
    DEPFILES:=$(patsubst $(SRCDIR)/%.cc,$(DEPDIR)/%.d,$(SRC))

    CXX?=/usr/bin/g++
    STRIP?=/usr/bin/strip
    INSTALL?=/usr/bin/install
    VALGRIND?=/usr/bin/valgrind

    CPPFLAGS:=-I$(INCDIR)
    CXXFLAGS:=-std=c++17 -Wall -Wextra -Wpedantic -flto ${CXXFLAGS}
    LDFLAGS:=-ffunction-sections -fdata-sections -Wl,-gc-sections $(LDFLAGS)
    DEPFLAGS=-MT $@ -MMD -MP -MF $(DEPDIR)/$*.Td

    COMPILE.cc=$(CXX) $(DEPFLAGS) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
    LINK.cc=$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)
    POSTCOMPILE=mv -f $(DEPDIR)/$*.Td $(DEPDIR)/$*.d &amp;&amp; touch $@

    all: debug

    $(BUILDDIR)/%.o: $(SRCDIR)/%.cc $(DEPDIR)/%.d | $(DEPDIR) $(BUILDDIR)
        $(COMPILE.cc) $(OUTPUT_OPTION) $&lt;
        $(POSTCOMPILE)

    $(BUILDDIR)/%.o: $(SRCDIR)/%.s | $(BUILDDIR)
        $(COMPILE.s) $(OUTPUT_OPTION) $&lt;

    $(BUILDDIR): ; @mkdir -p $@

    $(DEPDIR): ; @mkdir -p $@

    $(DEPFILES):

    $(PROGRAM): $(OBJECTS)
        $(LINK.cc) $(OUTPUT_OPTION) $^

    debug: CPPFLAGS += -DDEBUG
    debug: CXXFLAGS += -g3
    debug: $(PROGRAM)

    release: CXXFLAGS += -O2
    release: distclean $(PROGRAM)
        $(STRIP) --strip-all  -R .comment -R .note $(PROGRAM)

    memcheck: debug
        $(VALGRIND) --suppressions=valgrind.suppressions --quiet --verbose --leak-check=full --show-leak-kinds=all --track-origins=yes --log-file=valgrind.log ./$(PROGRAM)

    install: release
        $(INSTALL) -m755 -D -d $(DESTDIR)$(PREFIX)/$(BINDIR)
        $(INSTALL) -m755 $&lt; $(DESTDIR)$(PREFIX)/$(BINDIR/$(PROGRAM)

    clean:
        -rm -rf $(BUILDDIR)
        -rm -rf $(DEPDIR)
        -rm valgrind.log

    distclean: clean
        -rm $(PROGRAM)

    .PHONY: all debug release memcheck install clean distclean

    .DELETE_ON_ERROR:

    -include $(wildcard $(DEPFILES))

```
It's reasonable to require GNU Make - it's available on all platforms that have their own Make (as far as I know), and trying to cope with the vagaries of all vendors' Make implementations is an exercise in futility.  So I think you've taken the right approach here.

You're working against Make by putting build products into subdirectories.  It's easier to build them into the working directory, and the sources can be found using VPATH, then there's no need to copy all the built-in rules (not so bad when all your source files are C++, but when you need to add a few assembler and C files, then the maintenance starts to grow).
Creating separate debug and release binaries in the same build tree is problematic - it can mean a total rebuild when switching from one kind to another.  It's more usual to have separate build directories for the two, so you can incrementally build either at any time (of course, they can share the same source files, using VPATH, and can share most of the Makefile the same way).
That would look something like this:
debug/Makefile
CXXFLAGS += -g3 -DDEBUG
VPATH = ../src:../include
include ../Makefile

release/Makefile
CXXFLAGS += -O2
VPATH = ../src:../include
include ../Makefile

Then, building the release version doesn't affect the objects used to build the debug version, and vice versa.

This target is problematic for a parallel build:

release: distclean $(PROGRAM)


We'll lose some of the files as the distclean isn't sequenced with respect to $(PROGRAM).
I don't like the explicit strip invocation in the release target: since install depends on release, this makes it impossible to build debug-symbol Debian packages the usual way.  Just let the packager do the stripping.

I see no value in redefining COMPILE.cc and LINK.cc: CXXFLAGS and LDFLAGS are provided specifically for you to add your own flags to these lines - just use them.
Speaking of which, we normally use += to add to the flags:
CPPFLAGS += -I$(INCDIR)
CXXFLAGS += -std=c++17 -Wall -Wextra -Wpedantic
CXXFLAGS += -flto

LDFLAGS += -ffunction-sections -fdata-sections
LDFLAGS += -Wl,-gc-sections

DEPFLAGS = -MT $@ -MMD -MP -MF $(DEPDIR)/$*.Td
CPPFLAGS += $(DEPFLAGS)


$(RM) is provided as a more portable alternative to rm -f; we could use that in a few places.
.DELETE_ON_ERROR is often missed - kudos for remembering that.
DESTDIR?= is a no-op - undefined Make variables already expand to nothing.  Just omit this line.  It's good that you're correctly allowing this to be set by packaging systems etc.