but what make
Thinking about this cool file: https://git.sr.ht/~akkartik/basic-build/tree/main/item/build
Basically we have a shell function older_than, which returns a truthy value if the first argument doesn’t exist or if it’s older than any of the remaining arguments. From there, you can basically write a crappy makefile; the example given is
older_than a.out x.c && {
$CC $CFLAGS x.c
}
(The curly braces aren’t necessary but they are subshell syntax, which I think means that cd-ing from one shell won’t affect the others)
Why do I like this so much? I think I like how “explicit” it is?
- Makefiles sometimes feel like you’re fighting the pattern-matcher rather than working with it
- Makefile rules can be executed in any order. Sometimes a blessing, sometimes a curse; if you want predictability or stronger ordering guarantees it can be difficult.
- Often times you have a specific order (first compile, then link, then …) but have to express it “backwards” to fit Make’s “everything is hung off a target” model
- It makes the “the script runs because X is older than Y” dependency explicit. You occasionally want different types of dependencies (like “the script runs if X contains different contents than last time”) which require lots of hacks to execute in Make
- You don’t have to deal with make mangling your shell script due to its own set of obscure “escaping” rules
- It is still just as freestanding. If you can assume Make is installed you can certainly assume
shis installed
In practice
I thought it’d be fun to change my website’s makefile to use a script like this. In practice shell’s abysmal array support kinda bit me in the bum
My makefile starts with basically
garden-sources := $(shell find garden -name "*.md" -type f)
garden-outs := $(patsubst garden/%.md,out/%.html,$(garden-sources))
This is easy enough in pure shell;
export garden_sources=$(find garden -name "*.md" -type f)
export garden_outs=$(echo "$garden_sources" | sed -E 's;garden/(.+)\.md;out/\1.html;')
and you can iterate over one array at a time with for garden in $garden_sources; do ... done.
for garden in $garden_sources; do
garden_out=...?
older_than "$garden_out" "$garden" && {
echo "insert pandoc command here" $garden $garden_out
}
done
But how do I index into garden_outs? Posix SH doesn’t have associative arrays, and you can’t iterate over both in lockstep (aside from monumentally goofy things like head/cut)
I could give up and use bash but let’s see how far i get with sh. What if instead we use a function to get the output name from the input, instead of materializing them all at once.
export garden_sources=$(find garden -name "*.md" -type f)
garden_src_to_dst() {
echo "$1" | sed -E 's;garden/(.+)\.md;out/\1.html;'
}
Then we only have to iterate over one array at a time.
for garden in $garden_sources; do
garden_out=$(garden_src_to_dst "$garden")
older_than "$garden_out" "$garden" && {
echo "insert pandoc command here" $garden $garden_out
}
done
but:
$ time ./b.sh > /dev/null
real 0m0.545s
user 0m0.189s
sys 0m0.430s
Come on, really. Already hit half a second of execution time due to spawning sed a million times and haven’t even done any work yet