GNU Make Coding Guide

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.

Tip

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.

Getting Started

Source Structure

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.

Source structure:

foo/
└── src/
    ├── foo.bash
    ├── foo_prelude.bash
    └── foo-subroutine.bash

Installation structure:

/bin/
├── foo
├── foo_prelude
└── foo-subroutine

Using Make

Make is used by entering the project root and running:

$ make <target>

target typically being one of all, build, install, check. See https://www.gnu.org/software/make/manual/html_node/Standard-Targets.html for more about standard targets.

make then reads Makefile from the current directory and executes recipe defining how to build the <target>.

Makefile Name

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

Install Recipe

At its simplest, we do not need a build step and go straight to installing the software.

1
2
3
4
5
6
7
8
9
src := $(wildcard src/*.bash)
i_bin = $(patsubst src/%.bash,/usr/bin/%)

.PHONY: install
install: $(i_bin)

/usr/bin/%: src/%.bash

     install -m755 $< $@

Running make install will now install your bash scripts into executables at /usr/bin/foo, /usr/bin/foo_prelude, and foo-subroutine.

How it works:

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.

Best Practices

Default Target

Running make will execute default target which is normally the first target defined.

Use .DEFAULT_GOAL := build to define the default target.

See https://www.gnu.org/software/make/manual/html_node/Special-Variables.html#Special-Variables

Install Targets

Install targets should be composed of DESTDIR and prefix variables:

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 to /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 <https://refspecs.linuxfoundation.org/FHS_3.0/fhs/index.html>.

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.

Directories

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:

Example Uses

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

Running tests:

.PHONY: check
check:

     pytest ...

Installing into users $HOME directory:

 .PHONY: install-home
install-home:

     $(MAKE) prefix=$(HOME)/.local install-home