The Compiler Runs Brew For You

· 5 min read

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:

  1. Walks your AST to find all requires.system declarations
  2. Runs each check command to see what’s installed
  3. Shows you the status with version info
  4. 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 foo or apt install libfoo-dev
  • Install libbar: brew install bar or apt 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.