👩‍💻 chrismanbrown.gitlab.io

how i use Make and just for builds and tasks

make is for incremental builds and just is a task runner

2024-08-04

tldr: separation of concerns (make for builds, just for tasks) allows for a terse makefile, and a consistent entrypoint from project to project. just allows for rapid iteration on scripts and oneliners, and serves as documentation and a cookbook.

Contents

  1. Make is a build system
  2. Just is a task runner
  3. Just is a lovely cookbook
  4. Just is a sketchbook
  5. Just is a friend to others

Make is a build system

Make is the worst build system, except for all the other ones.

Its dependence on literal tabs is annoying. Its globbing, matching, and substitution syntax is arcane and inscrutible. Basically, one time I wrote a makefile that works and ever since then I just copy it from project to project making slight tweaks and adjustments as needed.

Here’s the makefile for this site.

LATEST := $(shell cat LATEST)
SRCS=$(shell find src -name '*.m4' ! -name "feed.m4")
OUTS=$(SRCS:src/%.m4=%.html)
vpath %.m4 src
vpath %.html www

all: $(OUTS) list.html www/rss.xml index.html

www/index.html: src/$(LATEST).m4
    m4 -D__latest=$(LATEST) $< | pandoc -f markdown+autolink_bare_uris -t html5 > $@
www/rss.xml: src/feed.m4
    m4 -D__latest=$(LATEST) $< > $@
%.html: %.m4
    m4 -D__latest=$(LATEST) $< | pandoc -f markdown+autolink_bare_uris -t html5 > www/$@

I can’t explain to you with 100% confidence what it all does without really stopping to think about it. At least, I wouldn’t be able to recreate this from scratch without a reference. Especially the vpath stuff and the substitutions on line 3.

But make is ubiquitous. It’s everywhere. It’s available. And it’s well supported. You can get help with your makefile by asking online and people will generally be able to help you.

So I continue to use it.

My most frequent use case is for building static sites like this one. Sites where I have plain text in markdown, or in this case m4 + markdown, or in other cases in a recfile, or even in a twtxt file. And I need to do some tranformations on that text to turn it into html for the browser so you can consume it with your eyes. (Or with your ears if you’re using a screenreader. Hi, Matt!)

What I don’t use make for any more, though, is for running commands.

Just is a task runner

My makefiles these days are always distilled down to their incremental build essence.

Basically, I don’t use .PHONY commands any more. If it’s not a build, if it’s just a command, I don’t use make. For example, my makefiles always used to include a phony command to scp or rsync my build up to a remote server. Or to run a watcher to build the project when the source files change. The ubiquitous ‘clean’ and ‘install’ commands are not build steps. They only remain part of the build toolchain because historically nobody separates building from running tasks.

But I don’t do any of that any more. If it’s just a command, I just use just.

https://github.com/casey/just

just is ‘just a task runner.’ It has some real niceties and modern ergonomics and conventions. Such as whitespace indifference, more conventional variables and interpolation, scripting in arbitrary languages (just add the appropriate #! to the start of your recipe), and a whole lot more.

Here’s a portion of the justfile for this site. (Omitted are some halfbaked curiosities; see “Just is a sketchbook” below.)

# list all recipes
default:
  just --list --unsorted

# build html and rss
build:
  touch src/list.m4 src/feed.m4 && make all

# build assets
assets:
  rsync -vurp static/* www/

# watch for changes
watch:
  ls src/*.m4 | entr -r make $(cat LATEST).html

# clean build
clean:
  rm www/*.html www/rss.xml

# build all
all: assets build

Just is a lovely cookbook

Some of you are rolling your eyes right now. I can practically hear it.

“Just write write a task.sh file!” you’re crying out. And to you I say, your opinions and your ways of doing things are perfectly valid.

In fact, sometimes I do things your way! I have some projects with a bin/build.sh. But guess what. I call it from my justfile.

It calls out to make and to build scripts as needed.

My justfile is nearly always the entry point for my projects now. My default command is always just --list --unsorted so I can pop into a project, mash j, and see a documented list of all that project’s commands. It’s like a lovely cookbook full of recipes! It documents all of the weird one-off oneliners and small scripts that a project tends to accumulate over time. Not just for actually getting stuff done, but also for testing and diagnostics and reporting and statistics and stuff.

When I run just for this project, this is what I see:

just --list --unsorted
Available recipes:
    default  # list all recipes
    build    # build html and rss
    glossary # show a glossary
    assets   # build assets
    watch    # watch for changes
    clean    # clean build
    recfeed  # build recfeed
    all      # build all

Just is a sketchbook

And it serves as a sketchbook where I might be composing and testing out a finicky command pipeline or a many branching awk script or something.

If the script gets long and complicated enough, sure it might get relegated to its own .sh file. That rarely happens though. I just keep it all in the justfile.

Just is a friend to others

I use just in conjunction with other script files and, crucially, with make.

Remember, make is still probably the best build system.

My makefiles are terse these days. They contain the bare minimum. Make is just a build tool.

And my justfile will contain, for example, an html recipe that consists of just make html.

just is still my entrypoint. But it is still just a command runner. It can happily rely on make to handle incremental builds. just and make are friends.

Although, for the simplest of cases, you may not even need make:

# build html
html:
  [ source.md -nt out.html ] \
  && echo "Creating html" && markdown source.md > out.html \
  || echo "Nothing to be done"