Repo & source layout
Each top-level directory under src/ is a layer with one job, and
dependencies point downward only.
src/
main.zig / root.zig CLI + library root — the only place that wires layers together
│
├── starlark/ the Starlark front-end (lexer → parser → eval → rules)
├── crates/ the crates.io importer (lock, fetch, metadata, gen)
├── exec/ the sandboxed executor + cache orchestration
├── dice/ the incremental engine
├── graph/ target identity (labels) and the build graph
├── core/ the content-addressed substrate: digest, codec, cas,
│ merkle, action_cache
├── cache/ the remote-cache tier on top of core: remote.zig
│ (RemoteStore tiering + backends), objframe.zig (envelope)
├── rules/ the built-in @builtin prelude (rust.bzl, zig.bzl)
├── aws/ STANDALONE AWS SDK — sigv4, credentials, s3 (own module)
├── gcs/ STANDALONE GCS SDK — storage (own module)
└── log.zig STANDALONE leveled logger (own module, std-only)
Why some pieces are standalone modules
aws, gcs, and log are separate build modules, not just directories.
That gives a compiler-enforced boundary: they cannot reach into zut internals,
so they stay reusable on their own.
-
aws/gcsare the seeds of general-purpose Zig SDKs (aws.s3.Client,gcs.storage.Client) that have nothing to do with build systems. zut’s remote cache is a thin adapter over them; framing/compression (objframe) stays on zut’s side so the SDKs only move opaque bytes. Each can later be extracted to its own package. -
logdepends on nothing butstd, so every layer imports it as@import("log")— no../log.zigreaching across directories — and there is exactly one module instance, hence one process-global log threshold.
The dependency arrows
The front-end and importer sit on top; core/ is the content-addressed
substrate everything stands on; cache/ builds the remote tier on core/ using
the SDKs as backends without leaking them. Nothing imports main.
The full rationale and the migration history are in
docs/design/layout.md.