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.
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,
make uses a lot of terse, symbol-based identifiers like
@<. 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.
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-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.
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.
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
just avoids this by making all targets static targets (
just does not try to determine if targets are up-to-date).
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.