Hive
Add granular Elixir target rules
GitHub issue · Closed
Summary
- New
fabrik-elixircrate lowers[[elixir.library]]and[[elixir.binary]]targets into per-target compile actions, each producing a.ebindirectory of.beamfiles. Dep.ebins flow into downstream invocations through-paso the BEAM code path resolves transitive modules at compile time. [[elixir.binary]]additionally emits a launcher script that walks up to the workspace root via.fabrik/at run time, so the cached launcher stays byte-identical across machines with different absolute paths.- Compile actions go through a
fabrik elixir-compilewrapper that talks to a long-lived BEAM compile daemon when one is reachable and falls back to spawningelixircdirectly otherwise. The wrapper’s argv is identical in both modes, so daemon presence is invisible to the cache. - Wires the new kinds through the frontend manifest (
[[elixir.library]]/[[elixir.binary]]/[[elixir.test]]andrule = "elixir.*"), the CLI build dispatcher, an example project, and a shellspec suite that uses fakeelixirc+fabrikshims so CI doesn’t need an Elixir toolchain.
Compile daemon
The daemon is opt-in. Without it, builds still work via the direct elixirc fallback. Run it when you want to amortize BEAM startup across many actions.
fabrik elixir-daemon start # listens on .fabrik/elixir-daemon.sock
fabrik elixir-daemon status # probe round-trip health
The Elixir program that implements the daemon is checked in at crates/fabrik-elixir/elixir/fabrik_compiler.exs and embedded into the fabrik binary via include_str!; start materializes it under .fabrik/daemon/ on first use. Protocol is line-delimited JSON over a unix domain socket; the Rust client lives in fabrik_elixir::daemon and is unix-gated.
Cache behavior
Per-target actions are content-addressed on sources, dep action digests, and the resolved tool path. A live demo against the example:
edit core (leaf) -> 0 hit, 2 miss
no change -> 2 hit, 0 miss
edit cli only (leaf+1) -> 1 hit, 1 miss # core stays cached
That last case is the granularity win mix can’t match. Both daemon and fallback modes produce identical .beam bytes (same Elixir version, same sources, same dep code path), so the cache key intentionally omits daemon presence.
Explicitly deferred
- ExUnit test runner.
use ExUnit.Caseregisters modules at compile time inside the VM that subsequently runs the suite, so compile and run can’t trivially be split into separate cached actions.fabrik testreturns a clear “elixir test runner not yet wired” message for now;[[elixir.test]]compiles viaelixir_librarysemantics. - Pinning Elixir / Erlang in
mise.tomlplus real benchmarks againstmix compilefor the daemon’s cold-build win. The wiring is in place; numbers come next. - Tracer-driven per-module dep manifest to push caching down from per-target to per-module granularity.
Test plan
-
cargo test --workspace(200 tests, 27 infabrik-elixirincluding daemon protocol round-trip + materialization) -
cargo clippy --workspace --all-targets -- -D warnings -
cargo fmt --check -
shellspec(96 specs, 3 new inexamples_elixir_speccovering library + binary + cache hit, all going through the daemon-fallback path) - Live cache demo against the checked-in example shows hit/miss propagation per the table above
🤖 Generated with Claude Code
No GitHub comments yet.