Learning `just`: A Practical Task Runner
DEV Community

Learning just: A Practical Task Runner

Learning just : A Practical Task Runner for Developers Coming From Make I've been learning just recently. I came across it while setting up a Python project and looking for a simple way to collect all the commands I use during development. This started with something I got used to in Elixir. One of the things I really like about Elixir projects is that you can put the common development tasks behind simple commands/aliases: mix format mix test mix ci You don't have to remember every command that runs underneath. The project gives you a small set of commands and you use those. I wanted something similar for a Python project. For example, instead of remembering all the different checks I need to run, I wanted to be able to do this: just ci and let the project take care of the rest. My first thought was Make Make was the obvious choice. I've used Make a lot, and it works. It's installed almost everywhere, most developers have seen a Makefile before, and there is a huge amount of existing knowledge around it. So I started there. The setup was pretty small: format: uv run ruff format --check . lint: uv run ruff check . typecheck: uv run mypy src/ test: uv run pytest --cov=src --cov-report=term-missing ci: format lint typecheck test That gave me exactly what I wanted. I could run: make format make lint make typecheck make test make ci Nothing complicated. But while doing this, I kept seeing just mentioned in newer projects. That made me curious. Not because I thought Make was bad. I still use it a lot, and I have no problem reaching for it. I just wondered what just was doing differently. Was it basically Make with different syntax? Was it easier to use? Did it solve some of the small annoyances that come with Make? So I decided to rebuild the same thing in just and see. The just version The nice thing was that I didn't have to change any of the actual commands. I just changed the wrapper around them: format: uv run ruff format --check . lint: uv run ruff check . typecheck: uv run mypy src/ test: uv run pytest --cov=src --cov-report=term-missing ci: format lint typecheck test That's pretty much the whole file. Now I can run: just format just lint just typecheck just test Or everything with: just ci The ci recipe depends on the other four recipes, so just ci runs them in order and stops when one fails. For what I was trying to do, it felt very familiar. And that was probably the first thing I noticed. I didn't really have to learn a new way of thinking about the problem. A few things I liked about just The basic syntax is very similar to Make. You have a recipe, optional dependencies, and then the commands to run: recipe_name: dependency1 dependency2 command to run But there are a few differences that make sense when you're using it mainly as a task runner. No .PHONY With Make, you often have something like: .PHONY: format lint typecheck test ci That's because Make is also a build system and cares about files and timestamps. For these development commands, I don't really need that behaviour. just doesn't use file timestamps to decide whether a recipe needs to run, so there is no .PHONY declaration to maintain. No tab problem Then there is the classic Make issue where a command needs to be indented with a tab. I've definitely lost time to that one. With just , you don't have that same tab requirement. You use normal indentation. It might seem like a small thing, but I like small improvements in tools I use. just --list This was another nice surprise. You can run: just --list and get a list of the recipes available in the project. You can add comments above recipes too: # Format check format: uv run ruff format --check . # Run tests test: uv run pytest --cov=src --cov-report=term-missing Those comments can then show up when listing the recipes. You can also make just itself show the list: default: @just --list So a developer can clone the project and run: just to see what is available. I like that because the task runner becomes a small piece of documentation. But is just actually better than Make? I'm not sure that's the right question. Make is still a great choice in plenty of situations. If I'm working on a project that already has a Makefile, I'm not going to suggest replacing it with just just because just is newer. And if I'm writing something that needs to work in an environment where Make is already installed, that is a pretty good reason to use Make. The thing I found interesting about just is that it feels more focused on the thing I was actually trying to do. I wasn't building anything based on file timestamps. I just wanted names for common commands and a way to combine them. For that use case, just is pretty nice. But it also comes with a trade-off. Unlike Make, you probably have to install it first. So the comparison for me looks something like this: | Make | just | | |---|---|---| | Already installed | Often | Usually not | | Task runner | Yes | Yes | | Build system | Yes | No | .PHONY | Often needed | Not needed | | Tab requirement | Yes | No | | Task listing | DIY | Built in | | Cross-platform | Possible | Designed for it | Neither one wins across the board. Make has ubiquity. just has a nice developer experience. Which one makes more sense depends on the project. There are other options too Once I started looking into this, I found quite a few other tools doing roughly the same thing. Task (go-task) was one I came across. It uses a YAML Taskfile.yml and has some useful features around variables and templating. For Python, poethepoet was another interesting option. It lets you put tasks in pyproject.toml , which can be nice if you don't want another configuration file. And if you're working in Node, npm scripts already give you a similar setup through package.json . They all solve a similar problem. You have a bunch of commands that developers need to run, and you give those commands names so people don't have to remember all the details. The differences are mostly around syntax, ecosystem, and how much functionality you want. So, am I switching to just ? For everything? No. I'm still going to use Make. If I open a project and there is a Makefile, I'll use it. If I'm working on infrastructure where Make is already expected, I'll probably use it there too. But for projects I start myself, I can see myself reaching for just . The thing I liked most was how little there was to learn. My existing commands stayed exactly the same. I just put a small, readable interface around them. That brought me back to why I started looking at this in the first place. In Elixir, I'm used to having commands like: mix ci that give the project a simple interface for common development tasks. With Python, I found myself wanting the same thing. Make was one way to do it. just is another. I'm glad I spent the time learning it. Not because it replaced Make, but because now I know another tool that fits this particular problem really well. And sometimes that's enough of a reason to try something new. Top comments (0)

Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.