Development
Compiler: Sky's compiler is written in Rust (cargo workspace at
rust/, crateskybuilds theskybinary). The retired Haskell compiler lives underlegacy-haskell-compiler/for historical reference. Type-directed lowering, Go generics on parametric record aliases, Layer-3 stdlib, and whole-program DCE all carry over; runtime verification runs across ~50 examples. Seecompiler/versions.mdfor the changelog.
Building Sky from source — for contributors, language-tooling work, or anyone who wants to run the compiler before a release lands.
Prerequisites
- Rust toolchain — installed via rustup;
the exact version is pinned by
rust/rust-toolchain.toml, sorustupauto-selects it when you build inside the workspace. - Go 1.21+ — required both to build
sky-ffi-inspectand at runtime (Sky compiles to Go and invokesgo build).
Verify:
rustc --version # matches rust/rust-toolchain.toml
cargo --version
go version # 1.21+
Local build (one shot)
./scripts/build.sh --clean
This runs cargo build --release -p sky and produces:
sky-out/sky— the Sky compiler (Rust). The only artefact end users need.bin/sky-ffi-inspect— local dev copy of the Go helper. Optional; see "Embedded inspector" below.
Flags:
| Flag | Effect |
|---|---|
--clean | rm -rf rust/target/ sky-out/ bin/ first |
--self-tests | Run sky build across every fixture in test-files/ |
--sweep | Clean-build every project under examples/ |
Quick rebuild (while hacking)
The full scripts/build.sh clean-copies the binary and runs the
hygiene checks — overkill for iterative work. For a fast rebuild of
just the compiler, build the sky crate directly:
( cd rust && cargo build --release -p sky )
cp rust/target/release/sky sky-out/sky
# macOS: re-sign the copy so the kernel's code-signing cache
# doesn't flag the new binary
codesign -s - sky-out/sky
sky-out/sky --version
Debug builds (cargo build -p sky, no --release) compile
faster and land in rust/target/debug/sky — handy for cargo test
iteration.
Running tests
Four matrices, all must pass before a push:
# 1. Cargo workspace suite — lexer, parser, name resolution, type
# inference, lowering, codegen, LSP protocol, per-crate unit +
# integration tests. Run from the workspace root.
(cd rust && cargo test --workspace)
# 2. xtask gate suite — end-to-end differential + regression gates.
# Gates: roundtrip, resolve, infer, reject, fuzz, coerce-floor,
# repro, build-run (48 build-verified examples), golden.
(cd rust && cargo run -p xtask -- build-run) # one gate; repeat per gate
# … or run each of: roundtrip resolve infer reject fuzz \
# coerce-floor repro build-run golden
# 3. Runtime Go tests — rt helpers, ADT shape, coercion, typed FFI,
# security (CSRF, rate limit, auth secrets), session round-trip.
(cd runtime-go && go test ./rt/)
# 4. Self-tests — every fixture in test-files/ must build clean.
pass=0; fail=0
for f in test-files/*.sky; do
rm -rf .skycache
./sky-out/sky build "$f" >/dev/null 2>&1 \
&& pass=$((pass+1)) \
|| fail=$((fail+1))
done
echo "self-tests: $pass passed, $fail failed"
Nix
A flake.nix at the repo root provides a Rust dev shell (rustc,
cargo, rustfmt, rust-analyzer) plus Go and pkg-config. A separate
legacy shell pins GHC 9.4.8 + the system libraries the retired
Haskell compiler links against (gmp, libffi, ncurses, zlib).
Reproducible shell
nix develop # primary — Rust + Go toolchain
# Inside the shell you now have cargo, rustc, go, pkg-config on PATH.
./scripts/build.sh --clean
nix develop .#legacy # only for building legacy-haskell-compiler/
Each shell's shellHook sets SKY_RUNTIME_DIR to the repo's
runtime-go/ so in-tree builds resolve the runtime without the
embedded fallback.
Build the compiler via Nix
nix build .#sky
./result/bin/sky --version
This runs the cargo build -p sky pipeline (via
rustPlatform.buildRustPackage) inside the Nix sandbox and puts the
result in ./result/bin/sky. The embedded-runtime and
embedded-inspector splices still bundle the Go source trees into the
binary, so the result is a fully self-contained executable.
Ad-hoc run
nix run .#sky -- build src/Main.sky
Artefact layout
A ./scripts/build.sh run leaves:
sky-out/
sky -- the compiler (ship this)
bin/
sky-ffi-inspect -- local dev copy (optional)
rust/target/ -- cargo's intermediate output
-- (release/sky, debug/sky, deps)
End-user install via install.sh or a released tarball only lays
down sky-out/sky. There is no separate sky-ffi-inspect binary
to install — it's embedded.
Embedded inspector
sky add needs a Go-side helper (sky-ffi-inspect, a Go tool at
tools/sky-ffi-inspect/) to introspect package APIs. Rather than
shipping a second executable, the Rust compiler embeds the helper's
Go source at build time (alongside the runtime and stdlib embeds)
and materialises + go builds it on first use, caching to
$XDG_CACHE_HOME/sky/tools/sky-ffi-inspect-<contentHash>/.
Resolution inside the compiler is one step, not three.
ffi::ensure_inspector (rust/crates/ffi/src/inspect.rs:329-351) is the only
resolver — three call sites, all in project/src/ffi_ops.rs. It goes straight
to <repo_root>/tools/sky-ffi-inspect, content-hashes the sources, and
go builds into $XDG_CACHE_HOME/sky/tools/sky-ffi-inspect-<hash>/, returning
the cached binary if it is already there.
Content-hash keying means sky upgrade auto-invalidates stale
cached helpers — no manual cleanup required.
Two probes documented here never existed in the Rust compiler. This passage used to list a three-step order:
$SKY_FFI_INSPECTORoverride, thenbin/sky-ffi-inspectwalking up from the cwd ("contributor workflow hits this"), then the embedded fallback. Only the third is real —grep -rn 'SKY_FFI_INSPECTOR\|bin/sky-ffi-inspect' rust/crates --include='*.rs'returns nothing.The practical consequence is the one that wasted time:
scripts/build.sh:88still writesbin/sky-ffi-inspect, and nothing consumes it. The old instruction to "rebuild thebin/copy so your dev workflow picks the change up" had no effect. If you edittools/sky-ffi-inspect/, the content hash changes and the next FFI operation rebuilds the cached helper on its own — that is the whole workflow.
Releases
scripts/build.sh produces the binary every release pipeline ships.
Before tagging:
./scripts/build.sh --clean( cd rust && cargo test --workspace )+ the xtask gate suite (cargo run -p xtask -- <gate>for each gate)./sky-out/sky verify— runs every example end-to-end (forbidden-pattern gate, build, run, HTTP probe).- Tag + push.
See compiler/runtime-verification.md
for the full gate matrix.
Troubleshooting
sky-ffi-inspect: go build failed on first sky add — go is
not on PATH inside the environment where sky runs, or the Go
module cache is missing network access. Verify go version and
go env GOCACHE.
Rust toolchain mismatch — cargo build uses the version pinned
in rust/rust-toolchain.toml; rustup fetches it automatically the
first time you build inside the workspace. If rustc --version
disagrees, run rustup show (or enter nix develop) to confirm the
active toolchain.
macOS: killed: 9 after copying sky-out/sky — the kernel
caches code-signing. Run codesign -s - sky-out/sky after any
cp of the freshly built rust/target/release/sky.
Slow first build / missing crate deps — the first cargo build
downloads and compiles the dependency graph; subsequent builds are
incremental. If a fetch fails, retry with network access or check
cargo proxy settings.