zine/issues/2/make.md

4.7 KiB

makefile magic

author: ben

i've used makefiles plenty of times for a variety of projects, but my understanding has not really improved beyond the most basic level.

working with pandoc for the ~club wiki and the zine inspired me to replace a janky build script with some makefile goodness.

feeling a loss for where to start, i reached out to tomasino for some guidance. he responded that i should have a look at a makefile he built for a javascript project template which he linked on his github.

let's go through some of my biggest takeaways from inspecting tomasino's example and creating several of my own makefiles from it.

anatomy of a makefile

this was something that i thought i understood, but realized i didn't fully grok.

i think that i understand it now, or at least know now what to search for when i get stuck the next time.

let's start with the basics: a makefile should be named Makefile (note the capital M). the basic syntax consists of a target followed by a colon (:) and an optional list of requirements, then a list of commands to be run (indented by literal tabs).

myproc: source.c
    cc -o myproc source.c

this example defines how to build myproc, which depends on source.c.

variables

makefiles use variables in two main formats.

  • recursively-expanded: evaluated every time they're referenced, even in other variables
    • defined with a single =
  • simply-expanded: evaluated once at the time of definition
    • defined with :=

simply-expanded variables are generally more predictable and will likely be what you should use in most cases.

x := foo
y := $(x) bar
x := later

will become

y := foo bar
x := later

variable names are case-sensitive. you'll likely see all-caps variable names more often than not.

guided example

this was the major revelation for me coming from my brittle buildscript that deleted and recreated everything on each run.

make wants to know what files you want built, as well as how to build them.

let's take the ~club wiki as an example: we want to build one html for each markdown file in the source directory. how do we tell make which files to build and how to build each one?

using a standard static target name, you'd need to add a target for each markdown file.

ssh.html: source/ssh.md
    pandoc -so ssh.html source/ssh.md

git.html: source/git.md
    pandoc -so git.html source/git.md

this already feels like a lot of duplication. let's stay DRY. here's an example from tomasino's makefile that i adapted for the wiki: github source

$(DST_DIR)/js/%.js: $(SRC_DIR)/ts/%.ts
    $(mkdir)
    $(tsc) $< --outFile $@

in this example, we're telling make that files under DST_DIR are built from SRC_DIR with a command defined in the tsc variable.

let's adapt this to our wiki example:

%.html: source/%.md
    pandoc -so $@ $<

here we're telling make that it can build html files from the corresponding md file in source by running pandoc. but how do we tell make which html files we want to build? let's go back to tomasino's example

SRC_TS_FILES != find $(SRC_DIR)/ts -name '*.ts'
DST_JS_FILES := $(SRC_TS_FILES:$(SRC_DIR)/ts/%.ts=$(DST_DIR)/js/%.js)

in this case, we're using the special assignment form with != that uses the output of a shell executable. we now have a list all the .ts files that need to be compiled in SRC_TS_FILES. next, we assign DST_JS_FILES with the special replacement syntax, which changes ts/%.ts into js/%.js. so, these variables now contain a list of source and desired files, along with a definition of how to build those files.

here's how i adapted this step for the wiki:

SRC_MD_FILES != find source -name '*.md'
DST_HTML_FILES := $(SRC_MD_FILES:source/%.md=%.html)

this means that make now has a list of html files that are required based on the list of all markdown files in the source directory.

now all that's left is to tell make that we want to build all of the DST_HTML_FILES. let's add that list to the all target:

all: $(DST_HTML_FILES)

now we can generate all html files by simply calling make

here's the completed Makefile.