The Task Runner: Is Just the New Make?

Make is the classic build tool and task runner, originally intended for use with C but useable in any language environment. It is powerful, but its syntax is not highly readable. My search for a general-purpose task runner led me to Just, so I thought I’d see how it stacks up against Make.

Just vs. Make: What's the Best Task Runner?

Requirements

A task runner is used far more frequently than it is modified. As a result, the syntax should be simple, and easy to read and understand at any time. When it comes time to add or change a task — probably a few months since the last time — you’ll have forgotten the idiosyncrasies of the task file syntax.

This is possibly less of an issue if your task runner uses the same language as your project. For instance, grunt and gulp both use Javascript for their task file specification. But this can backfire as the task file accumulates plugins and other random bits of Javascript pasted from StackOverflow. There is something to be said for simplicity.

make uses a lot of terse, symbol-based identifiers like $@ and @<. This is fun when you’re writing the makefile but less so when you’re trying to read it. just has some built-in functions that are not super discoverable when writing a justfile but much more readable later on.

A task runner should work well both in a CI pipeline and on a developer’s machine. For CI, this means it should be easy to install. just is available in popular package managers. For a developer, it should have syntax highlighting and shell completion. just has plugins for and has these available for common editors and shells.

Parameterization

Targets in a makefile are not easily parameterized. Say you have a deploy task that can target either the “staging” or “prod” environment. You could set an environment variable when running make, like DEPLOY=staging make deploy. Or you could duplicate static targets, like make deploy-staging, make deploy-prod, etc.

This is not terrible, but just cleans it up a bit by supporting parameters for targets. So you can have a single deploy target that takes an environment name as a parameter. Running it is simply just deploy staging. A bonus is that it’s self-documenting. When you run just --list, the deploy task will be listed once along with its parameter names.

Task Dependencies

Ideally a task runner will help you focus on outputs rather than on dependencies. If you want to build the application, you should be able to just run build. You shouldn’t have to run a separate task first to produce a shared library, a separate task for generated code, etc.

Both make and just use the same syntax for specifying dependencies. The difference is that in a makefile, a target could be either a static target or a filename. If the name matches a file that exists, make will skip the target if its dependencies are up-to-date. To force it to be a static target, you have to add it as a dependency of the kludgey .PHONY target. just avoids this by making all targets static targets (just does not try to determine if targets are up-to-date).

Incremental Builds

This behavior of only running a target if it is out-of-date (a.k.a. incremental build) is actually one of the most compelling features of make. But it also blurs the line between “task runner” and “build tool.” make could be considered both a build tool and a task runner, whereas just is solely a task runner. This is not such a big deal, since more specific build tools probably have a better idea of what is considered “out of date”.

If this functionality is needed in just, it’s not too hard to incorporate. For simple cases, a shell built-in can do the timestamp comparison, like test in bash: test $INFILE -nt $OUTFILE && codegen <$INFILE >$OUTFILE. More complex cases could be handled using find with its -newer flag. But to keep things readable, a utility like checkexec may be better.

Just vs. Make

It’s probably too early to say that just will replace make, but in my current usage I haven’t found anything that I desperately miss from make! Next time you’re thinking of writing a makefile, give just a try.