commit 620f76b84c78cee762f8f3d100051ecf62398cc6
parent 0e96291b65c4c9de2ef9a858cf45d5fe85349977
Author: Jack Mordaunt <jackmordaunt.dev@gmail.com>
Date: Wed, 23 Sep 2026 14:59:00 -0300
sonar: replace makefile with justfile
The recipes are the same: ASan debug build, optimised release, per-package
tests, check, clean, plus the standard install. What goes is make's
timestamp-based skipping of an up-to-date binary; an Odin build of this size
is quick enough that a rebuild on every call costs less than the second
build system it took to avoid it.
Diffstat:
| D | Makefile | | | 54 | ------------------------------------------------------ |
| A | justfile | | | 72 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
2 files changed, 72 insertions(+), 54 deletions(-)
diff --git a/Makefile b/Makefile
@@ -1,54 +0,0 @@
-# sonar build
-#
-# make debug build with AddressSanitizer -> build/debug/sonar.exe
-# make release optimised build -> build/release/sonar.exe
-# make test run the package tests under ASan -> build/test/
-# make check type-check both modes without producing a binary
-# make clean remove build/
-#
-# Debug and test builds carry -sanitize:address. The debug allocator poisons its
-# guard bytes and freed blocks, so instrumented reads past the end or after free trap
-# at the faulting instruction. Pass SAN= to build without it (e.g. make debug SAN=).
-#
-# GNU Make runs recipes through sh even on Windows (scoop's make finds sh.exe), so
-# the recipes use POSIX commands. Odin does not create output directories, hence the
-# order-only directory prerequisites.
-
-ODIN ?= odin
-BUILD := build
-FLAGS := -vet -strict-style
-SAN ?= -sanitize:address
-EXE := $(if $(filter Windows_NT,$(OS)),.exe,)
-
-SRC := $(wildcard *.odin) $(wildcard ntfs/*.odin) $(wildcard debug/*.odin) $(wildcard flow/*.odin) $(wildcard scan/*.odin) $(wildcard walk/*.odin)
-
-.PHONY: all debug release test check clean
-
-all: debug
-
-debug: $(BUILD)/debug/sonar$(EXE)
-
-release: $(BUILD)/release/sonar$(EXE)
-
-$(BUILD)/debug/sonar$(EXE): $(SRC) | $(BUILD)/debug
- $(ODIN) build . -debug $(SAN) $(FLAGS) -out:$@
-
-$(BUILD)/release/sonar$(EXE): $(SRC) | $(BUILD)/release
- $(ODIN) build . -o:speed $(FLAGS) -out:$@
-
-test: | $(BUILD)/test
- $(ODIN) test ntfs -debug $(SAN) $(FLAGS) -out:$(BUILD)/test/ntfs$(EXE)
- $(ODIN) test flow -debug $(SAN) $(FLAGS) -out:$(BUILD)/test/flow$(EXE)
- $(ODIN) test scan -debug $(SAN) $(FLAGS) -out:$(BUILD)/test/scan$(EXE)
- $(ODIN) test walk -debug $(SAN) $(FLAGS) -out:$(BUILD)/test/walk$(EXE)
- $(ODIN) test . -debug $(SAN) $(FLAGS) -out:$(BUILD)/test/main$(EXE)
-
-check:
- $(ODIN) check . $(FLAGS)
- $(ODIN) check . -debug $(FLAGS)
-
-$(BUILD)/debug $(BUILD)/release $(BUILD)/test:
- mkdir -p $@
-
-clean:
- rm -rf $(BUILD)
diff --git a/justfile b/justfile
@@ -0,0 +1,72 @@
+# Standard recipes: build (debug), release, clean, test, install.
+#
+# just build debug build with AddressSanitizer -> build/debug/sonar
+# just release optimised build -> build/release/sonar
+# just test run the package tests under ASan -> build/test/
+# just check type-check both modes without producing a binary
+# just clean remove build/
+#
+# Debug and test builds carry -sanitize:address. The debug allocator poisons its
+# guard bytes and freed blocks, so instrumented reads past the end or after free
+# trap at the faulting instruction. Pass SAN= to build without it:
+# `SAN= just build`. Odin does not create output directories, hence the mkdir.
+
+odin := env("ODIN", "odin")
+flags := "-vet -strict-style"
+san := env("SAN", "-sanitize:address")
+exe := if os() == "windows" { ".exe" } else { "" }
+bin := "sonar"
+
+# `just` alone lists the recipes.
+default:
+ @just --list --unsorted
+
+# Debug build with AddressSanitizer -> build/debug/sonar
+build:
+ mkdir -p build/debug
+ {{odin}} build . -debug {{san}} {{flags}} -out:build/debug/{{bin}}{{exe}}
+
+# Optimised build -> build/release/sonar
+release:
+ mkdir -p build/release
+ {{odin}} build . -o:speed {{flags}} -out:build/release/{{bin}}{{exe}}
+
+# Run every package's tests under ASan -> build/test/
+test:
+ mkdir -p build/test
+ {{odin}} test ntfs -debug {{san}} {{flags}} -out:build/test/ntfs{{exe}}
+ {{odin}} test flow -debug {{san}} {{flags}} -out:build/test/flow{{exe}}
+ {{odin}} test scan -debug {{san}} {{flags}} -out:build/test/scan{{exe}}
+ {{odin}} test walk -debug {{san}} {{flags}} -out:build/test/walk{{exe}}
+ {{odin}} test . -debug {{san}} {{flags}} -out:build/test/main{{exe}}
+
+# Type-check both modes without producing a binary.
+check:
+ {{odin}} check . {{flags}}
+ {{odin}} check . -debug {{flags}}
+
+# Remove build/.
+clean:
+ rm -rf build
+
+# Copy the release binary to a directory on PATH. An installed copy is replaced in
+# place; otherwise ~/.local/bin, ~/bin, GOBIN, GOPATH/bin, /usr/local/bin, else the
+# first writable PATH entry.
+install: release
+ #!/usr/bin/env bash
+ set -euo pipefail
+ dest=""
+ if existing=$(command -v {{bin}} 2>/dev/null) && [[ -w ${existing%/*} ]]; then
+ dest=${existing%/*}
+ fi
+ gopath=$(go env GOPATH 2>/dev/null || true); gobin=$(go env GOBIN 2>/dev/null || true)
+ for d in "$HOME/.local/bin" "$HOME/bin" "$gobin" "${gopath:+$gopath/bin}" /usr/local/bin; do
+ [[ -z $dest && -n $d && -d $d && -w $d && ":$PATH:" == *":$d:"* ]] && dest=$d
+ done
+ if [[ -z $dest ]]; then
+ IFS=: read -ra dirs <<<"$PATH"
+ for d in "${dirs[@]}"; do [[ -d $d && -w $d ]] && { dest=$d; break; }; done
+ fi
+ [[ -n $dest ]] || { echo "install: no writable directory on PATH" >&2; exit 1; }
+ install -m 755 build/release/{{bin}}{{exe}} "$dest/{{bin}}"
+ echo "installed $dest/{{bin}}"