Make is a build automation software. GNU Make is GNU flavor of the Make.
It is simple, stable, and widely used de-facto standard of building and installing software on Linux and UNIX systems. We will use it for build and installation of software written in languages that do not provide builtin build and installation mechanism but it can be used with those language/platforms as well.
The GNU Make website https://www.gnu.org/software/make/ contains an extensive documentation you normally refer to. This goal of this document is to give you only a limited quick getting started introduction and exposure to some of the best practice for writing makefiles.
A Tutorial on Portable Makefiles by Chris Wellons is also very nice. That article is more theory and principles oriented while this guide is more of a lets get started quick so we can get on with other things kind of thing.
Before we get to talk about the make itself we need to assume some source and installation structure. For demonstration purposes we will assume a simple bash software as outlined in Shell Scripting Survival Guide.
foo/ └── src/ ├── foo.bash ├── foo_prelude.bash └── foo-subroutine.bash
/bin/ ├── foo ├── foo_prelude └── foo-subroutine
Make is used by entering the project root and running:
$ make <target>
target typically being one of
for more about standard targets.
make then reads Makefile from the current directory and executes recipe defining how to build the
Makefile should be named
GNUmakefile to indicate it is written for the GNU flavor of make.
Yielding new source structure:
foo ├── GNUmakefile └── src ├── foo.bash ├── foo_prelude.bash └── foo-subroutine.bash
At its simplest, we do not need a build step and go straight to installing the software.
src := $(wildcard src/*.bash) i_bin = $(patsubst src/%.bash,/usr/bin/%) .PHONY: install install: $(i_bin) /usr/bin/%: src/%.bash install -m755 $< $@
make install will now install your bash scripts into executables at
How it works:
We use patsubst function to translate the source file path into the installation file paths.
We define the
/usr/bin/%targets as a pattern rules https://www.gnu.org/software/make/manual/html_node/Pattern-Rules.html to depend on
src/%.bashand to be built by using install and using automatic variables
$@to refer to the target and source file paths.
Meaning any target of the form
src/<whatever>.bashand the command to build the target is
install -m755 src/<whatever>.bash /usr/bin/<whatever>.
Targets are file paths
The recipe starts with a tab character. See https://www.gnu.org/software/make/manual/html_node/Rule-Syntax.html#Rule-Syntax
We define the target
installto be depend on
$(i_bin)targets. Meaning in order to build
$(i_bin)targets need to be built first.
We define the
installtarget as phony target to instruct make it is not actually supposed to build the file
This is basically all there is to makefiles in principle. Adding a build step is as simple as
adding build targets like
build/% depending on the source files
src/% and changing the
install target to depend on the build targets instead.
make will execute default target which is normally the first target defined.
.DEFAULT_GOAL := build to define the default target.
Install targets should be composed of
prefix ?= /usr/local bin_dir = $(DESTDIR)$(prefix)/bin i_bin = $(bin_dir)/foo
DESTDIR is typically empty but can be used to perform a staged install. Typically by system
integrators. It can also be used to build and install the software into a temporary fake
root filesystem to support simpler and more controlled software testing.
See https://www.gnu.org/software/make/manual/html_node/DESTDIR.html for more.
prefix is path to root filesystem subtree where the program should be installed. Defaults
/usr/local as custom installed software but system integrators will override this (usually
/usr) in their packages.
See https://www.gnu.org/software/make/manual/html_node/Directory-Variables.html for more.
Generally the paths should be constructed in a way to respect the
Filesystem Hierarchy Standard
This is an example of executable install target but other targets like documentation, data, shared files, etc should analogously respect the relevant variables. See https://www.gnu.org/software/make/manual/html_node/Directory-Variables.html for more make directory variables recommendations.
You may want to install directories separately for portability reasons (eg. supporting both Linux and FreeBSD).
build_dir = build $(build_dir): mkdir -p $@ $(build_dir)/%: src/%.bash | $(build_dir) sed -e <expression> $< > $@
How this works:
There is a separate rule to make directories
Rules that depend on existence of the
build_diradd it as order-only dependency to make sure the
build_diris built before the rule but not cause a rebuild if the
build_diris newer than the rule’s targets. See https://www.gnu.org/software/make/manual/html_node/Prerequisite-Types.html#Prerequisite-Types for more.
In The Getting Started section we have seen an example of installing software.
In similar fashion it can be used to build software or documentation. For example building manual pages from ReST source files:
build/man/%.1: man/%.1.rst rst2man $< $@
It can also be used as convenient entry point to carry common tasks over the repository such as formatting and linting:
.PHONY: lint lint: pylint ... .PHONY: format format: black ...
Or any other convenience function. If you build html files, you may want to have a convenient way to open the browser at the built file path:
.PHONY: open open: xdg-open ./build/index.html
.PHONY: check check: pytest ...
Installing into users $HOME directory:
.PHONY: install-home install-home: $(MAKE) prefix=$(HOME)/.local install-home