make best practices


I’ve made a detailed blog post about how all of this works.

This should go on top of every Makefile.

MAKEFLAGS += --no-builtin-rules --no-builtin-variables --warn-undefined-variables
unexport MAKEFLAGS
.DEFAULT_GOAL := all
.DELETE_ON_ERROR:
.SUFFIXES:
SHELL := bash
.SHELLFLAGS := -eu -o pipefail -c

escape = $(subst ','\'',$(1))

define noexpand
ifeq ($$(origin $(1)),environment)
    $(1) := $$(value $(1))
endif
ifeq ($$(origin $(1)),environment override)
    $(1) := $$(value $(1))
endif
ifeq ($$(origin $(1)),command line)
    override $(1) := $$(value $(1))
endif
endef

Quote command arguments and use the escape function on variables and shell output.

var := Includes ' quote
test:
	printf '%s\n' '$(call escape,$(var))'
var := Includes space
test:
	printf '%s\n' $(var)
var := Includes ' quote
test:
	printf '%s\n' '$(var)'
cwd := $(shell pwd)
test:
	printf 'In directory %s\n' '$(call escape,$(cwd))'
cwd := $(shell pwd)
test:
	printf 'In directory %s\n' $(cwd)
cwd := $(shell pwd)
test:
	printf 'In directory %s\n' '$(cwd)'

Use the noexpand function on environment variables or variables that can be overridden on the command line.

has_default ?= Default value
$(eval $(call noexpand,has_default))

test:
	echo '$(call escape,$(has_default))'
has_default ?= Default value

test:
	echo '$(call escape,$(has_default))'
has_default ?= Default value
export has_default

test:
	echo "$$has_default"
$(eval $(call noexpand,ENV_VAR))

test:
	echo '$(call escape,$(ENV_VAR))'
test:
	echo '$(call escape,$(ENV_VAR))'