The Compiler Runs Brew For You
The Last Mile
In Import = Install, we showed how Koru eliminates package.json. Your imports ARE your dependencies.
But there was still a gap. When you import $koru/curl, the compiler knows to npm install @korulang/curl. But what about libcurl itself? The C library that needs to be installed via brew install curl or apt install libcurl4-openssl-dev?
That was still manual. Until now.
Source Code as Bill of Materials
~import "$std/deps"
~std.deps:requires.system {
"name": "curl",
"check": "curl-config --version",
"brew": "curl",
"apt": "libcurl4-openssl-dev",
"dnf": "libcurl-devel",
"pacman": "curl"
} That’s it. Your library declares what system dependencies it needs, how to check if they’re installed, and which package to install on each platform.
The Developer Experience
Clone a project. Run one command:
$ koruc main.kz deps
System dependencies for this project:
curl ✓ (libcurl 8.7.1)
brew: curl | apt: libcurl4-openssl-dev
zlib ✓ (1.2.12)
brew: zlib | apt: zlib1g-dev
sqlite3 ✗ (not found)
brew: sqlite3 | apt: libsqlite3-dev
To install missing dependencies:
koruc main.kz deps install (detected: brew) The compiler:
- Walks your AST to find all
requires.systemdeclarations - Runs each
checkcommand to see what’s installed - Shows you the status with version info
- Auto-detects your package manager
Then install everything with:
$ koruc main.kz deps install
Installing 1 missing dependencies with brew...
Installing sqlite3...
✓ installed Transitive Dependencies
The beauty is that this works transitively. When you import $koru/curl:
~import "$koru/curl"
~koru.curl:get(url: "https://example.com")
| ok r |> koru.curl:close(resp: r)
| |> std.io:print.ln(r.body)
| err e |> std.io:print.ln(e.msg) The curl library declares its system dependency on libcurl. Your code doesn’t need to know about it. Just run koruc main.kz deps and the compiler finds everything your project needs, including dependencies of dependencies.
The Four Layers
Here’s where it gets wild. Koru handles dependencies at four levels:
┌─────────────────────────────────────────────────┐
│ $std/deps System libraries │
│ brew, apt, dnf, pacman, apk │
├─────────────────────────────────────────────────┤
│ $std/package Koru packages │
│ npm, cargo, pip, go │
├─────────────────────────────────────────────────┤
│ $std/build Build requirements (YOUR app) │
│ linkSystemLibrary, addModule │
├─────────────────────────────────────────────────┤
│ $std/compiler Build requirements (COMPILER) │
│ Metacircular backend deps! │
└─────────────────────────────────────────────────┘ Wait, what’s that fourth layer?
The Metacircular Layer
Koru’s compiler is metacircular - written in Koru itself. The compiler backend is generated Zig code, and it can have its own build dependencies:
~import "$std/compiler"
~std.compiler:requires {
exe.linkSystemLibrary("sqlite3");
exe.addModule("custom_analysis", b.path("src/custom.zig"));
} This isn’t for YOUR application. This is for THE COMPILER ITSELF. When you write compiler passes, custom analysis, or extend the metacircular compiler, you can inject dependencies into the compiler’s build.zig.
The compiler that compiles your code has dependencies declared in the same language it compiles. It’s turtles all the way down.
The Full Picture
// Layer 1: System deps - install libcurl on the OS
~std.deps:requires.system {
"name": "curl",
"check": "curl-config --version",
"brew": "curl"
}
// Layer 2: Koru packages - npm install @korulang/curl
~std.package:requires.npm {
"@korulang/curl": "^1.0.0"
}
// Layer 3: Build deps - link libcurl into YOUR binary
~std.build:requires {
exe.linkSystemLibrary("curl");
}
// Layer 4: Compiler deps - link something into THE COMPILER
~std.compiler:requires {
exe.linkSystemLibrary("tree-sitter");
} Four layers. One language. One AST walk. The compiler knows everything about everything that needs to exist for your code to run.
All declared in source code. All collected by the compiler. All installed with single commands.
No More “Install X First”
Every project README has that section:
Prerequisites:
- Install libfoo:
brew install fooorapt install libfoo-dev- Install libbar:
brew install barorapt install libbar-dev- Make sure you have…
Gone.
git clone https://github.com/you/project
cd project
koruc main.kz deps install
koruc main.kz Your source code contains everything the compiler needs to know. The README becomes: “Clone and run.”
The Philosophy
Your source code is the single source of truth.
Not a README that drifts out of date. Not a setup script that breaks. Not tribal knowledge about what needs to be installed.
The code declares what it needs. The compiler provides it.
~std.deps:requires.system {
"name": "curl",
"check": "curl-config --version",
"brew": "curl",
"apt": "libcurl4-openssl-dev"
} That’s the entire system dependency declaration. The compiler handles the rest.