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