Homebrew, reimagined in native code.
A fast, native implementation of common Homebrew commands written in Zig. Drop-in compatible — falls back to brew for anything not yet implemented.
curl -fsSL https://zieka.github.io/bru/install.sh | bashUse bru exactly like you'd use brew:
bru install ripgrep
bru search fd
bru upgrade
bru services start postgresql@14
bru bundle dump
bru rollback ripgrep39 native commands including install, upgrade, search, list, info, deps, doctor, cleanup, plus:
- services — manage background services (start/stop/restart)
- bundle — export and install from Brewfiles
- rollback — revert a formula to its previous version
- self-update — update bru itself
- post_install execution — runs Homebrew
post_installRuby blocks via the system Ruby (/usr/bin/rubyon macOS), so packages likenode,openssl,fontconfig, andca-certificatesare fully configured at install time. Disable per-invocation with--no-post-install, override the Ruby path with--use-system-ruby=<path>, or setBRU_NO_POST_INSTALL=1. Failures are logged to<cache>/post-install-logs/and never block the install.
Anything not implemented natively falls back to the real brew binary.
| Homebrew | bru | |
|---|---|---|
| Language | Ruby | Zig (single static binary) |
| Startup | ~1.5s interpreter load | Near-zero |
| Formula lookups | Parse 25MB JSON | O(1) via mmap'd binary index |
| Auto-update | Runs on every install |
Never — bru update is explicit |
| Rollback | Not supported | bru rollback <formula> with full history |
| Memory | Ruby GC pauses | Arena allocator (free is a no-op) |
| Downloads | Sequential | Parallel with worker threads |
| zerobrew | bru | |
|---|---|---|
| Drop-in compatible | No — separate /opt/zerobrew prefix, requires migration |
Yes — same prefix, same Cellar, interchangeable with brew |
| Fallback | None — switch to brew manually | Transparent — unimplemented commands delegate to brew automatically |
| Formula index | Parses JSON API on every lookup | Binary index (BRUI format), mmap'd O(1) lookups |
| Dependencies | ~30 Rust crates, SQLite, Tokio | Zero — Zig stdlib only |
| Binary size | 7.9 MB | Single static binary, significantly smaller |
| Migration | Required (zb migrate) |
Not needed — works on existing Homebrew installation |
| Casks | Not supported | .app casks (binary-and-app artifacts) work; brew falls back for .pkg/font casks |
| Correctness | Skips patching ffmpeg's libav*.pc pkg-config files — @@HOMEBREW_PREFIX@@ placeholders leak through (see Benchmark below) |
Full pkg-config patching alongside Mach-O relocation |
| nanobrew | bru | |
|---|---|---|
| Drop-in compatible | No — separate /opt/nanobrew prefix |
Yes — same prefix, same Cellar |
| Fallback | None — unsupported commands fail | Transparent — delegates to brew |
| Tar extraction | Shells out to system tar |
Native Zig tar with parallel buffer pool |
| Tab files | Separate state tracking | Writes brew-compatible INSTALL_RECEIPT.json — brew sees bru-installed packages as its own |
| Mach-O patching | Patches dylibs but leaves *.pc files unpatched |
Full relocation via install_name_tool + codesign for both binaries and pkg-config |
| Data files | Skips bundle data (e.g. tesseract tessdata/eng.traineddata doesn't extract) — tesseract --list-langs finds nothing |
Extracts everything declared in the bottle |
| Migration | Required (nb migrate) |
Not needed |
| malt | bru | |
|---|---|---|
| Drop-in compatible | No — separate /opt/malt prefix |
Yes — same prefix, same Cellar |
| Fallback | None | Transparent — delegates to brew |
| Warm-cache hot path | Extracts to content-addressed store, then APFS clonefile into the user-visible Cellar (skips re-relocation on re-install) |
Same APFS clonefile strategy, with the relocation cached per-bottle in kegs/<sha>/ |
| Mach-O patching | Patches dylibs but partial pkg-config coverage — ffmpeg's libavdevice.pc ships with @@HOMEBREW_PREFIX@@ placeholders |
Full pkg-config patching |
| Data files | Same as nanobrew — tesseract tessdata/ is missing after install |
Extracts data subdirectories declared in the bottle |
| Casks | Supported | Supported |
bru is designed as a transparent accelerator, not a parallel ecosystem. The other native alternatives chase raw install speed by skipping the unglamorous patching work — pkg-config files, language data, signature re-application — leaving subtly broken installs that pass <bin> --version but fail real consumers (compilers calling pkg-config --libs libsodium, tesseract --list-langs, etc). bru does the full patching work, keeps the brew-compatible Cellar layout, and falls back to brew for anything unimplemented. Alias brew=bru and everything works — faster for native commands, identical for the rest.
test/benchmark/ ships an end-to-end correctness × speed benchmark that compares bru against brew and three other native homebrew-alikes (zerobrew, nanobrew, malt) by actually installing real formulae and casks.
For each (tool, package) pair the bench:
- Uninstalls the package and surgically evicts every cached blob keyed by the bottle's SHA256 (brew API → cache/blobs + store, across each tool's content-addressed prefix).
- Times a cold install (no cached bottle on disk).
- Uninstalls again, times a warm install (bottle in cache but not yet linked).
- Runs a correctness probe while the install is on disk:
ffmpeg: ffprobe present, everylibav*.pcpatched,-filterslists 200+ plugins, transcode a generated test pattern to PNG.libsodium: dylib has no@@PLACEHOLDER@@,lib/pkgconfig/libsodium.pcexists and is patched.duckdb:libduckdb.dylibinstall_name patched,lib/cmake/DuckDB/*.cmakeclean, statically-linked parquet round-trip + json_extract both return expected values.tesseract:--list-langsfindseng/osd(catches missingtessdata/).act: binary launches,--helpprints the expected usage banner.rectangle(cask):Rectangle.appis in/Applications/, executable inside the bundle,codesign --verifypasses.
Run it:
bash test/benchmark/bench_contenders.sh # ensure latest brew, zb, nb, mt; build bru
bash test/benchmark/bench_chart.sh # measure + renderEach per-package result lands at test/benchmark/results-<package>.svg. Green ✓ = verified, red ! = passes --version but fails a deeper probe, grey dot = not supported by that tool.
Beta caveats:
- The deeper probes are formula-specific (hand-written per package). Adding a new package to
PACKAGES/CASKSenv vars works for timing but uses a--version-only fallback for correctness until a probe is written for it. - "Cold" install timing depends on surgical cache eviction; the bench uses brew's API to derive per-tool cache paths, so a new tool with a custom content-addressing scheme would need its
clear_cachefunction updated. - Warm-cache numbers are sensitive to disk state and CPU load — run the bench on an otherwise-idle machine for trustworthy comparisons.
Requires Zig 0.15+:
zig build -Doptimize=ReleaseFastRun tests:
zig build test