Cross-platform `sh` via `deno` or `busybox`
I wanted to quickly share an option for writing cross-platform justfiles using (limited) sh-style syntax: leverage deno's task subcommand.
Context
I'm an OSS maintainer, projects support Windows, I want to use justfiles, I must make it extremely easy for potential contributors to install all necessary dependencies that are outside of the language ecosystem's core tools. For example, Golang devs obviously have golang installed, but they shouldn't be expected to jump through hoops to install shells and command runners.
Setup
Tell just to use deno's sh interpreter subcommand, deno task.
set shell := ["deno", "task", "--quiet", "--eval"]
# Sample recipe
prune-docs:
cat *.md | markdown-formatter-tool dist/docs.md
PowerShell can't do this, because PowerShell's cat is subtly different -- breaks newlines, breaks binary files -- and PowerShell doesn't do * globbing.
Deno handles it correctly.
Installation
For contributors on Windows:
winget install -e --id Casey.Just
winget install -e --id DenoLand.Deno
Why deno?
"Isn't deno a JavaScript/TypeScript thing? I hate JavaScript/TypeScript! My project is C#/golang/rust/etc!"
In this case, we are using deno's built-in ability to run one-line commands written in sh-style syntax, exactly what just needs for vanilla recipes!
We are not running any JavaScript.
https://docs.deno.com/runtime/reference/cli/task/#syntax
Deno installs as a single, ~100MB binary file.
Linking to tickets related to the challenges of configuring a justfile shell correctly on Windows:
- #1907
- https://github.com/casey/just/issues/2532#issuecomment-2740633128
- #2604
- #2610
- #2641
- #2656
- https://github.com/casey/just/issues/1592#issuecomment-1533842558
- #534
- #1549
Immediately after posting this, I remembered to search winget for busybox. I feel a bit silly because, sure enough, there it is:
winget install -e --id frippery.busybox-w32
The great thing about busybox is that it bakes-in the standard suite of unix tools which are often shipped as external binaries -- cat, echo, rm, awk, etc. And it's a single, 1/2MB binary.
Setup like so:
set windows-shell := ["busybox", "sh", "-eu", "-c"]
set script-interpreter := if os() == "windows" { ["busybox", "sh", "-eu"] } else { ["sh", "-eu"] } # This conditional syntax is not supported; how to do it??
Good find! I think that's something that could probably be added to the readme, assuming that there are no gotchas.
There is bash bundled with git which is (IMO) more self-contained than installing an external binary as busybox, future readers can consider this as alternative approach.
It's living inside C:\Program Files\Git\bin\bash.exe / $env:ProgramFiles\Git\bin\bash.exe and we can make some special attributes like this.
set unstable # this is pretty much stable though.
[script("C://Program Files//Git//bin//bash.exe","-eu")]
show-big-object:
git ls-tree -rl HEAD | sort -k4 -n | tail | awk '{print $4, $5}' | numfmt --to=iec-i
Random thoughts
Another alternative that is coreutils from uutils team, it's integrated within nushell and nu is pretty usable at this moment so I think it's feasible to integrate uu crate for basic usage of a primitive utils set so just can work without putting any [script] or shell interpreter.