Cross Platform Justfile

27 Oct 2023 | Two-minute read


just is a cross-platform task runner. I use just extensively to define the build tasks for IronPLC.

I use just so that my local builds and the continuous integration builds are as close as possible. just makes that work for multiple platforms (IronPLC supports Windows, macOS and Linux) and multiple languages (IronPLC uses Rust, TypeScript and Python). It is trivial to install and run on most platforms.

Problem

The biggest problem I had was getting just to work on Windows. just runs each command in a shell and sh is the default for Windows. Getting sh to work means running through Git Bash (or similar) which defeats being trivial to install and run.

I wanted to use just as a task runner but making it work having the exact same commands via sh was an enormous headache.

Insight

The key insight came as I was trying to make integration tests work for Visual Studio Code. For macOS, these run directly:

npm run test

For Linux, these run inside an Xvfb (X virtual framebuffer) context:

xvfb-run -a npm run test

I thought I needed a way to run the same command on each platform. Instead, I needed a way to make if feel as though it ran the same command on each platform. Different paths for different platforms was not duplicating code - that was actually what I needed. The pieces fell into place when I stopped trying to force sh onto Windows.

Solution

My solution is to use sh on macOS and Linux and use PowerShell on Windows. For most cases, those are exactly the same thing. In cases where they are different, I divide and conquer using just functions to divide.

First, I set the default shell for Windows to PowerShell:

set windows-shell := ["powershell.exe", "-c"]

Then, when the commands vary, create hidden commands for each platform, for example:

test:
  just _test-{{os()}}

_test-windows:
  npm run test

_test-macos:
  npm run test

_test-linux:
  xvfb-run -a npm run test

I use this strategy when I want to run the same thing on different platforms and when I want different platforms to do different things that are specific to the platform. For example, the package task on Windows creates a Windows installer and the package task on macOS creates a Homebrew tap.

There are lots more examples in the IronPLC repository. Especially look that there is more than one justfile in the repository.