I write makefiles just infrequently enough to remember the syntax and structure.
They are surely on their way out, but as long as there are c/c++
, tex
and sphinx-docs
,
there will be make.
The problem: you write a collection of scripts, and there is some procedure that takes these and puts together some intended output. Examples: compiling in a compiling language (eg c) or producing a PDF from a tex file. Make allows us to automate this sensibly.
Below walks through a super simple example: using pandoc to make a static website from a collection of markdown files. I used this for a while, before I met hugo.
Full code at the bottom.
Make
Make is a pretty old program that automates a build, while using annoying, opaque syntax (relatively). But it has endured for a reason - it is still useful and does precisely what it is intended to do well enough. A full manual can be found in the link.
Make was most concerned with compiling for c programming. As such, it has inbuilt implicit rules for c compiling, - and examples in c can be less transparent AFAICS.
Make rules
Make reads a Makefile
.
Rules make up the staple part of a makefile’s logic.
A rule takes the following form:
<target>: <prequisite 1> <prequisite 2> ...
<command 1> <command 2>
Here <target>
is the name of the output file.
Succeeding it is a list of names of files on which the target file depends.
If the target file exists and it has been modified more recently that all the prerequisite files,
then make will skip on to the next rule.
That is, it assumes there are no new changes.
If a prerequisite has been modified, then make will run through the commands.
Note: that commands must be
Make variables
You can define and reference variables in make.
For example, here we define variables SOURCEDIR
and BUILDDIR
and later reference the latter:
SOURCEDIR := source # Define
BUILDDIR := build
SOURCEDOCS := $(wildcard $(SOURCEDIR)/*.md) # Use
...
Variable references work by strict textual substitution
There are variations in syntax which are also acceptable.
For example ${SOURCEDIR}
would also be substituted with source
.
See docs for explanation on the subtle
differences between definition syntax =
, :=
,::=
.
Special variables
This is where things start getting a bit wtf-ish for me.
Make has some special variables.
Variable | Meaning |
---|---|
$@ |
target |
$< |
first prerequisite |
$^ |
all prerequisites |
$? |
all prerequisites newer than the target |
$(@D) |
directory of target |
$(@F) |
same as $(notdir $@) |
See also $%
archived target, $+
prerequisites with duplicates,
$|
all order-only prerequisites, $*
stem of implicit rule match.
In addition we can extend the use of D
and F
in analogous,
eg $(<D)
,$(<F)
etc.
In our example, we have the following command.
$(PANDOC) $(PANDOC_OPTIONS) $(PANDOC_HTML_OPTIONS) -o $@ $<
The -o
flag informs pandoc that $@
is the output file name,
while pandoc implicitly understands $<
is the input.
Functions for Transforming Text
The basic function in make looks like
$(function args)
Here are some functions and their usage. See docs for explanation.
function | Example:In | Example:Out |
---|---|---|
dir |
$(dir src/foo.c hacks) |
src/ ./ |
notdir |
$(notdir src/foo.c hacks) |
foo.c hacks |
suffix |
$(suffix src/foo.c hacks) |
.c |
basename |
$(basename src/foo.c hacks) |
src/foo hacks |
addsuffix |
$(addsuffix .c,foo bar) |
foo.c bar.c |
addprefix |
$(addprefix src/,foo bar) |
src/foo src/bar |
join |
$(join a b,.c .o) |
a.c b.o |
word |
$(word 2, foo bar baz) |
bar |
words |
$(words there are 4 words) |
4 |
firstword |
$(firstword foo bar) |
foo |
See also wildcard
, realpath
, abspath
.
We use the wildcard.
SOURCEDOCS := $(wildcard $(SOURCEDIR)/*.md)
This will result in a whitespace separated list of all files in source
(to where the variable SOURCEDIR
is assigned)
ending in .md
.
Control
Make includes if.. then
, and
, or
, foreach
, etc.
which we don’t need right now.
Pattern Rules
A pattern rule is a rule in which the target contains precisely one %
character.
In our example we have the following pattern rule.
$(BUILDDIR)/%.html : $(SOURCEDIR)/%.md
$(PANDOC) $(PANDOC_OPTIONS) $(PANDOC_HTML_OPTIONS) -o $@ $<
This creates a rule for any html file in the build directory, and stipulates that its prerequisite is the corresponding markdown file located in the source directory.
.PHONY
and clean
All (almost all) make files have these two rules that look a little like this:
.PHONY: all clean
clean:
- $(RM) $(BUILDHTML)
clean
is used to remove all built files,
but it doesn’t have any dependencies.
To solve this we use a special built in target name .PHONY
.
An aside on pandoc and markdown with yaml
Pandoc is a super useful document conversion format. It allows you to write in something easy to write in, like markdown, and convert to something easy to read in, like html or pdf. It hosts a great many options and extensions. See the site for docs.
Using a yaml header inside the markdown, allows us to include some metadata tags to the webpages, like a title.
In our example we instruct pandoc to include a css file located in the static
folder.
PANDOC=/usr/bin/pandoc
PANDOC_OPTIONS=--smart --standalone
PANDOC_HTML_OPTIONS=--from markdown+yaml_metadata_block --css ../static/minidark.css --to html5
... $(BUILDDIR)/%.html : $(SOURCEDIR)/%.md
$(PANDOC) $(PANDOC_OPTIONS) $(PANDOC_HTML_OPTIONS) -o $@ $<
Our example make file
Altogether we have the following makefile:
SOURCEDIR := source
BUILDDIR := build
SOURCEDOCS := $(wildcard $(SOURCEDIR)/*.md)
BUILDHTML = $(addprefix $(BUILDDIR)/, $(notdir $(SOURCEDOCS:.md=.html)))
RM=/bin/rm
PANDOC=/usr/bin/pandoc
PANDOC_OPTIONS=--smart --standalone
PANDOC_HTML_OPTIONS=--from markdown+yaml_metadata_block --css ../static/minidark.css --to html5
# Pattern-matching Rules
$(BUILDDIR)/%.html : $(SOURCEDIR)/%.md
$(PANDOC) $(PANDOC_OPTIONS) $(PANDOC_HTML_OPTIONS) -o $@ $<
.PHONY: all clean
clean:
- $(RM) $(BUILDHTML)
html: $(BUILDHTML)