GNU Make Coding Guide¶
Contents
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 |
|
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:
Line 1 uses wildcard function and variable assignment to define
src
- the list of source file paths relative to current directory.Line 2 uses patsubst function to translate the source file path into the installation file paths.
Line 7 defines
/usr/bin/%
target as a pattern rule https://www.gnu.org/software/make/manual/html_node/Pattern-Rules.html to depend onsrc/%.bash
and to be built by using install and using automatic variables$<
and$@
to refer to the target and source file paths.Meaning any target of the form
/usr/bin/<whatever>
depends onsrc/<whatever>.bash
and the command to build the target isinstall -m755 src/<whatever>.bash /usr/bin/<whatever>
.Note
Targets are file paths
Note
The recipe starts with a tab character. See https://www.gnu.org/software/make/manual/html_node/Rule-Syntax.html#Rule-Syntax
Line 5 defines the target
install
to depend on$(i_bin)
targets. Meaning in order to buildinstall
target, the$(i_bin)
targets need to be built first.Line 4 defines the
install
target as phony target to instruct make it is not actually supposed to build the fileinstall
.
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:
There is a separate rule to make directories
$(build_dir)
.Rules that depend on existence of the
build_dir
add it as order-only dependency to make sure thebuild_dir
is built before the rule but not cause a rebuild if thebuild_dir
is newer than the rule’s targets. See https://www.gnu.org/software/make/manual/html_node/Prerequisite-Types.html#Prerequisite-Types for more.
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