Skip to main content

MEP-46 research note 10, Build system: rebar3, mix, escript, releases

Author: research pass for MEP-46. Date: 2026-05-23 (GMT+7).

This note specifies how mochi build --target=beam-... integrates with the OTP build ecosystem: when to drive erlc directly, when to emit a rebar3 project, when a mix.exs, how to package as escript vs release, and how to handle deps.


1. The OTP build ecosystem

Three main build tools coexist:

  • erlc: the OTP compiler driver. Single-file, single-pass. The lowest level.
  • rebar3 (Tristan Sloughter et al., since 2014, v3.24 in 2024): the de facto Erlang build tool. Hex.pm integration, plugins, profile system, release support via relx (now built-in). Most production Erlang uses rebar3.
  • mix (Elixir, since 2014): the Elixir build tool. Hex.pm integration. Works for Erlang projects via the mix new --module ... and def deps settings. Heavier than rebar3; usually only used for Elixir projects.

The MEP-46 build driver targets rebar3 as the OTP integration; Mochi-built artifacts include a rebar.config so Erlang devs can use the result naturally.

2. The mochi build --target=beam-... matrix

TargetOutput
beam-erlcA directory of .beam files + .app resource. The "library" output.
beam-escriptA single executable file (the escript).
beam-releaseA release tarball (.tar.gz) or directory.
beam-rebar3-projectA directory layout rebar3 can build from.
beam-mix-projectA directory layout mix can build from.
beam-atomvmA directory of .beam files + a packaging script for AtomVM.

The default for mochi build on a Mochi project's main.mochi is beam-escript. For a Mochi project with a mochi.toml declaring mode = "service", the default is beam-release.

3. Direct erlc path (no project)

For a single-file Mochi program:

$ mochi build --target=beam-erlc main.mochi

The build driver:

  1. Compiles main.mochi to Core Erlang via cerl (see [[05-codegen-design]]).
  2. Writes intermediate .core files to _build/beam/core/.
  3. Calls compile:forms({c_module, ...}, [from_core, debug_info, return_errors, return_warnings]) via an embedded erl (the build driver spawns a one-shot erl -noshell -eval ... -s init stop).
  4. Writes .beam files to _build/beam/ebin/.
  5. Writes mochi_user_main.app (the application resource file).

Output: _build/beam/ebin/*.beam.

4. rebar3 project layout

For full projects, mochi build --target=beam-rebar3-project emits a standard rebar3 directory:

my_proj_beam/
├── rebar.config
├── rebar.lock
├── src/
│ ├── my_proj.app.src % Application resource (templated)
│ ├── mochi_user_<m>.erl % One per Mochi module
│ └── ...
├── priv/ % Static assets (Mochi `bundle` data)
└── README.md

rebar.config lists Mochi runtime as a Hex dep:

{deps, [
{mochi, "0.1.0"} % from Hex
]}.

{escript_main_app, my_proj}.
{escript_emu_args, "%%! -escript main mochi_escript_main\n"}.

{relx, [
{release, {my_proj, "0.1.0"}, [my_proj, mochi, sasl]},
{dev_mode, false},
{include_erts, true},
{extended_start_script, true}
]}.

{profiles, [
{prod, [{relx, [{dev_mode, false}, {include_erts, true}]}]}
]}.

The user then runs:

  • rebar3 compile to build all .beam.
  • rebar3 escriptize to build the escript.
  • rebar3 as prod release to build a release.
  • rebar3 dialyzer to type-check (Dialyzer + Mochi-emitted -spec).

This does not require Mochi to be installed on the build machine: once the rebar3 project is emitted, it's standard Erlang. Mochi developers can hand off projects to Erlang teams seamlessly.

5. .erl source emission for handoff

The codegen produces Core Erlang via cerl. When emitting to a rebar3 project, we also pretty-print to .erl alongside the .beam files. Reasons:

  • Erlang devs reading the rebar3 project can see source.
  • rebar3 dialyzer parses .erl, not .beam's debug_info chunks (it can do either; .erl is the canonical path).
  • Stack traces show source lines mapped from the Line chunk, and the .erl lets the user click through.

The pretty-printer uses OTP's erl_prettypr for the abstract format; we round-trip Core Erlang to abstract format via cerl:to_records and erl_syntax:revert. This is a known-stable path used by Concuerror and other Core Erlang tools.

6. escript packaging

mochi build --target=beam-escript produces a single-file executable:

#!/usr/bin/env escript
%%! -escript main mochi_escript_main
%%
%% Generated by mochi 0.1.0
%%
PK\x03\x04... (a zip archive containing .beam files)

Composition:

  1. A shebang line (#!/usr/bin/env escript).
  2. A %%! line with emulator args, including -escript main <Module> pointing at the entry module.
  3. A zip archive (BEAM escripts use zip as the inner format) containing:
    • All user mochi_user_*.beam modules.
    • All Mochi runtime mochi_*.beam modules (linked statically).
    • All third-party deps' .beam files.

Tooling: escript:create("output", [shebang, comment, {emu_args, "..."}, {archive, ArchiveData, []}]).

Size: 2-10 MB depending on user code and deps.

Startup: ~50ms cold (unzips into a tmp dir, loads modules), ~10ms warm (escript cache).

The escript does not include ERTS; the user must have erl on $PATH. For a self-contained escript, use the release target.

7. Release packaging

mochi build --target=beam-release uses relx (now bundled with rebar3):

$ mochi build --target=beam-release
# Internally: rebar3 as prod release

Output: _build/prod/rel/my_proj/:

my_proj/
├── bin/
│ ├── my_proj % Start script (sh)
│ ├── my_proj.cmd % Start script (Windows)
│ └── ...
├── erts-15.0/ % Bundled ERTS
├── lib/ % All OTP apps + user app
│ ├── kernel-9.0/
│ ├── stdlib-5.0/
│ ├── sasl-4.2/
│ ├── mochi-0.1.0/
│ └── my_proj-0.1.0/
├── releases/
│ └── 0.1.0/
│ ├── my_proj.rel
│ ├── sys.config
│ ├── vm.args
│ ├── start.boot
│ └── start_clean.boot
└── VERSION

Tarball: _build/prod/rel/my_proj-0.1.0.tar.gz (~30-80 MB depending on apps).

Startup: ~300ms cold, ~50ms warm. Includes SASL boot, the user's supervision tree, telemetry.

8. Dependency management

Mochi's package manager (mochi pkg) handles Mochi-source dependencies. For BEAM-target builds, additional dependency surfaces:

  • Erlang/Elixir Hex packages: usable via extern "Erlang" mod <name> declarations in Mochi source. The build driver adds them to rebar.config's deps.
  • System libs: NIFs that need C libs (we avoid these by default; see [[04-runtime]] §9).

Hex.pm is the canonical registry for Erlang/Elixir; rebar3 has Hex support built in. The MEP-46 publishes the mochi runtime library to Hex as mochi (Apache-2.0 license, matching the rest of the project).

9. mix integration

For users who prefer Elixir tooling, mochi build --target=beam-mix-project emits:

my_proj_mix/
├── mix.exs
├── lib/
│ ├── mochi_user_*.ex % Pretty-printed as Elixir...
│ └── (or .erl files; mix supports both)
├── test/
└── README.md

mix.exs:

defmodule MyProj.MixProject do
use Mix.Project
def project, do: [app: :my_proj, version: "0.1.0", deps: deps()]
def application, do: [mod: {:mochi_user_main, []}, extra_applications: [:logger]]
defp deps, do: [{:mochi, "~> 0.1"}]
end

This path is less polished than rebar3 (mix expects Elixir source primarily) but unblocks users in Elixir-heavy organisations.

10. AtomVM packaging

mochi build --target=beam-atomvm:

  1. Compile to .beam as usual.
  2. Lint the modules against the AtomVM compat profile (see [[07-erlang-target-portability]] §4). Reject any use of pg, httpc, crypto beyond AtomVM's supported subset.
  3. Bundle into an .avm (AtomVM archive) using the atomvm_packbeam tool (Python script bundled with AtomVM).
  4. Optionally flash to a connected device (mochi build ... --flash esp32 calls atomvm_packbeam --boot ...).

Output: _build/atomvm/my_proj.avm (~50-500 KB).

11. Profiles

Like rebar3 profiles, Mochi has build profiles:

  • dev (default): debug_info, no optimisation, sourcemaps.
  • test: dev + eunit/common_test enabled, dialyzer on.
  • prod: full optimisation, debug_info kept (always; see [[02-design-philosophy]]).
  • min: prod minus debug_info (for size-constrained deployments only; sacrifices stack traces).

Selected via mochi build --profile prod.

12. Watch mode

mochi build --watch runs the build incrementally, recompiling changed files. The driver:

  1. Tracks file mtimes of .mochi sources.
  2. Maintains a dependency graph (Mochi module imports).
  3. On change, recompiles affected modules to .beam and (if release-mode) hot-loads them into a running BEAM via code:load_file/1.

The hot-load path is the same as the OTP sync library and is the basis for mix.recompile and rebar3 ct --auto.

13. Tests

Mochi test "..." { ... } blocks compile to eunit test functions (see [[11-testing-gates]]). The build driver emits a mochi_test_*_SUITE.erl (Common Test format) for property tests and a mochi_test_*_eunit.erl for unit tests.

mochi test --target=beam calls rebar3 ct or rebar3 eunit under the hood.

14. Documentation

Mochi doc "..." and /// blocks compile to OTP 27 -doc attributes. rebar3 docs then produces an HTML doc site via ex_doc (the de facto OTP/Elixir doc generator).

Mochi-emitted docs include cross-references to the Mochi source line that produced each declaration, using a custom [mochi-src:<file>:<line>] link rewritten by ex_doc's post-processing hook.

15. CI integration

The Mochi build driver emits a .github/workflows/beam.yml template:

name: BEAM CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-22.04
strategy:
matrix:
otp: ['27.0', '27.latest', '28.latest']
steps:
- uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
with:
otp-version: ${{ matrix.otp }}
rebar3-version: '3.24'
- run: rebar3 compile
- run: rebar3 dialyzer
- run: rebar3 eunit
- run: rebar3 ct

This is dropped into the rebar3-project output if --ci=github is set.

16. Release upgrades

Release upgrades (relup/appup) are a Phase 5+ feature. The build driver in v0.1 produces fresh releases only; no upgrade scripts. Users upgrade by stopping the old release and starting the new one.

For v0.2+, the driver will diff exported functions between releases and generate appup files automatically.

17. Cross-compilation note

Release builds bundle ERTS, which is platform-specific. To build a Linux release on macOS, use Docker:

$ mochi build --target=beam-release --docker linux/amd64

Internally, this runs docker run --rm -v $PWD:/work erlang:27-alpine bash -c "cd /work && rebar3 as prod release". The Mochi runtime image (mochilang/mochi:beam-otp27) has rebar3 pre-installed.

18. Tooling integration: LSP

The Mochi LSP server (mochi lsp) reads Mochi source, compiles to Core Erlang in memory, and exposes diagnostics from both the Mochi type checker and OTP's erl_lint warnings. Hover and go-to-definition use the Dbgi chunk, which the in-memory compile populates.

For BEAM-specific diagnostics (e.g. "uses pg not available on AtomVM"), the LSP runs the AtomVM compat lint when the target is set to beam-atomvm.

19. Verification: round-trip with rebar3

For every new MEP-46 codegen feature, the test suite runs the full round-trip:

  1. Compile Mochi → .beam via the in-process build.
  2. Emit a rebar3 project.
  3. Run rebar3 compile from scratch on the emitted project.
  4. Verify both .beam files are bit-identical.

This catches drift between the in-process and rebar3-driven paths.

20. Summary

The build system reuses the OTP ecosystem at every level:

  • erlc for one-off compiles.
  • rebar3 for everything multi-file.
  • mix as a secondary integration for Elixir-heavy orgs.
  • escripts for CLI tools.
  • relx releases for daemons.
  • AtomVM packaging for embedded.
  • ex_doc for docs.
  • Dialyzer + eqWAlizer for type checking.
  • eunit + common_test for tests.

We add Mochi-specific glue (mochi.toml, the Mochi compiler driver) but contribute nothing new to the BEAM build space; that's the point.


Sources

  1. rebar3 docs. https://rebar3.org
  2. mix docs. https://hexdocs.pm/mix/
  3. relx docs. https://erlware.github.io/relx/
  4. escript reference. https://www.erlang.org/doc/man/escript.html
  5. ex_doc. https://hexdocs.pm/ex_doc/
  6. Tristan Sloughter, "rebar3 internals" Code BEAM 2022.
  7. Saša Jurić, "Releases without tears." Code BEAM 2024.
  8. AtomVM packaging. https://www.atomvm.net/doc/main/build-instructions.html
  9. erlef/setup-beam GitHub Action. https://github.com/erlef/setup-beam