Conditionals, includes and configuration

Make · Advanced · 12 min

Real builds need debug and release modes, platform differences and shared fragments without copy-paste.

Conditionals are evaluated when make reads the Makefile, not when recipes run:

ifeq ($(DEBUG),1)
  CFLAGS += -O0 -g
else
  CFLAGS += -O2
endif

Also available: ifneq, ifdef VAR (true if the variable has a non-empty value), ifndef. Detect the platform with UNAME_S := $(shell uname -s) and ifeq ($(UNAME_S),Darwin); on Windows $(OS) is Windows_NT.

Don't indent conditional directives with a tab: lines beginning with a tab are treated as recipe lines.

include reads other makefiles as if pasted in: include config.mk for shared settings, -include local.mk for optional per-developer overrides (not committed). make tries to remake included files: if there is a rule for config.mk, make builds it, then restarts reading. That powers generated configuration but can surprise you.

For build modes that should not mix, put outputs in separate directories (build/debug, build/release), so switching DEBUG=1 doesn't leave you linking objects compiled with different flags. make doesn't track flag changes on its own; a common trick is a build/flags.txt file rewritten only when the flags change and listed as a prerequisite.

$(MAKECMDGOALS) holds the targets given on the command line, handy to skip expensive includes when only clean was requested.

Example

UNAME_S := $(shell uname -s)
MODE    := $(if $(filter 1,$(DEBUG)),debug,release)
BUILD   := build/$(MODE)

ifeq ($(MODE),debug)
  CFLAGS += -O0 -g -fsanitize=address
else
  CFLAGS += -O2 -DNDEBUG
endif

ifeq ($(UNAME_S),Darwin)
  LDLIBS += -framework CoreFoundation
endif

-include local.mk          # optional, not in git

info:
	@echo "$(MODE) -> $(BUILD), CFLAGS=$(CFLAGS)"

# $ make info            -> release -> build/release, CFLAGS=-O2 -DNDEBUG
# $ make info DEBUG=1    -> debug -> build/debug, CFLAGS=-O0 -g -fsanitize=address

Remember

  • ifeq/ifdef are evaluated while reading the Makefile
  • include shares fragments; -include makes them optional
  • Separate output directories per build mode

Watch out for

  • Indenting ifeq with a tab inside a rule area, so make treats it as a shell command.

Check yourself

  1. When is ifeq ($(DEBUG),1) evaluated?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.gnu.org/software/make/manual/make.html#Conditionals

Conditionals, includes and configuration · Make · Topolearn