Task Automation: Just Make it Easy
Cyril Matthey-Doret March 14, 2025 [programming] #automation #toolsWhen working in a git repository, we often use recipes to automate frequent tasks, such as building, linting, formatting, testing or benchmarking.
This provides a written trace of such "recipes" and a way to discover, remember and run the commands easily in the right order.
While this can be achieved with custom shell scripts, it would be error prone and require much boilerplate code.
Instead, there are "command runner" tools that are made for this purpose.
Make is a Build System
Most projects use make
as a "command runner", because it is well known and available on most systems.
However, make
was intended as a build system, not a generic command runner: its purpose is to organize the compilation of source files, and this is reflected by its features:
- recipes are designed around file dependencieiave file Recipes in a "Makefile" are expected to have file inputs and outputs.
- running arbitrary commands require explicitely marking rules with
.PHONY
. - no built-in way to pass arguments to recipes.
Additionally, some default behaviours get in the way of using make
as a task runner, for example if a .PHONY
recipe has the same name as a file or directory (e.g. test/
), it will refuse to run it.
Just Use a Task Runner
just
is only one of many tools that were designed specifically as task runners. Other examples include task or doit.
In brief, it has saner default than make
for use as a task runner and provides useful options which remove the need for glue code:
- print recipe comments as help message
- group recipes into sub commands
- automatically load .env files, or make it a requirement
- pass variadic or named arguments to recipes
The main con of just
, is that it is likely not available on the system, however it is available on most package registries and can be used as a standalone binary.
Example
Here is a small example justfile
showcasing some useful just features:
#!/usr/bin/env just
set shell := # <- enforce specific shell in recipes
set positional-arguments # <- allow passing positional args
set dotenv-required # <- source .env, exit if missing
alias bench := # default behaviour: show list of recipes
: # <- underscore prefix hides recipe from the list# Install dependencies
:
# Run tests
: # <- install, then test# Measure performances
: # <- args with default values
If the above is saved in ./justfile
, we can use just
as follows:
==============================
<Dir <Dir <Module <Function <Function
===========================
Conclusion
Only use make
if you actually need a build system, otherwise use a proper task runner tool. I personally think the convenience is well worth having one extra dependency, and this is a non-issue if the project comes with its environment (e.g. ~conda~ micromamba, docker, nix, pixi). In any case, reimplementing it with shell script glue is risky and time consuming.
Resources
https://theorangeone.net/posts/just-stop-using-makefile/