Skip to main content

09. rebar3 and mochi.lock

Two-level lockfile model

The Erlang bridge uses a two-level lockfile model, similar to how MEP-76 relates to Gemfile.lock and MEP-73 relates to Cargo.lock:

  1. mochi.lock [[erlang-package]] table: the Mochi-level lock, recording the resolved package version, all three content hashes, the BEAM ingest hash, the shim hash, and the declared capabilities. This is the authoritative reproducibility anchor for the bridge.

  2. rebar3.lock (synthesised by the bridge during mochi pkg lock): the rebar3-level lock, recording the same package versions in rebar3's native format. This file is generated from mochi.lock and is used only by the rebar3 compile step during the build. It is committed to the repository alongside mochi.lock.

The Mochi lock is the primary; the rebar3 lock is derived. If the two drift (e.g., a hand-edit to rebar3.lock), mochi pkg lock --check fails: it recomputes the rebar3 lock from mochi.lock and diffs it.

mochi.lock erlang-package schema

[[erlang-package]]
name = "cowboy"
version = "2.12.0"
source = { kind = "hex", registry = "https://hex.pm" }
outer-sha256 = "a1b2c3d4e5f6..." # SHA-256 of the outer .tar (before extraction)
inner-sha256 = "f6e5d4c3b2a1..." # SHA-256 of contents.tar.gz
inner-sha512 = "9f8e7d6c5b4a..." # SHA-512 of contents.tar.gz (cross-verified with Hex.pm API)
beam-ingest-sha256 = "111aaa..." # SHA-256 of all Dbgi chunk bytes, sorted by module name
shim-sha256 = "222bbb..." # SHA-256 of erlang_shims/cowboy/shim.erl
capabilities-declared = ["net"]
dependencies = ["cowlib@~> 2.13", "ranch@~> 2.1"]
otp-app = "cowboy"
modules = ["cowboy", "cowboy_req", "cowboy_router", "cowboy_handler", "cowboy_websocket"]

rebar3.lock format

rebar3's lock file (rebar3.lock) is a list of Erlang terms:

[
{<<"cowboy">>, {hex, <<"cowboy">>, <<"2.12.0">>,
<<"a1b2c3d4e5f6...">>, %% inner-sha256 in hex
[<<"cowlib">>, <<"ranch">>],
<<"default">>}},
{<<"cowlib">>, {hex, <<"cowlib">>, <<"2.13.0">>,
<<"abcdef...">>,
[],
<<"default">>}},
{<<"ranch">>, {hex, <<"ranch">>, <<"2.1.0">>,
<<"fedcba...">>,
[],
<<"default">>}}
].

The bridge generates this file from [[erlang-package]] entries. The hex hash in rebar3.lock is the inner-sha256 value from mochi.lock, which matches the hash format rebar3 uses (it checksums the inner contents.tar.gz).

rebar.config synthesis

The rebar.config generated by the bridge for the build workspace:

{erl_opts, [debug_info]}.

{deps, [
{cowboy, "2.12.0"},
{hackney, "1.20.0"},
{jose, "1.11.0"}
]}.

{minimum_otp_vsn, "25"}.

{profiles, [
{mochi_bridge, [
{deps, []},
{erl_opts, [debug_info, {i, "include"}]}
]}
]}.

Each {name, "version"} dep uses the exact version from mochi.lock. The minimum_otp_vsn field comes from [erlang].otp-version in mochi.toml. The mochi_bridge profile is used when compiling the shim modules to avoid polluting the user's default profile.

--check mode

mochi pkg lock --check performs the following verifications for each [[erlang-package]] entry:

  1. Recompute outer-sha256 from the cached .tar file in ~/.cache/mochi/erlang-deps/. If the file is not in the cache, fail with cache_miss.
  2. Extract contents.tar.gz from the cached .tar and recompute inner-sha256 and inner-sha512.
  3. Re-run the BEAM ingest on the cached .beam files and recompute beam-ingest-sha256.
  4. Re-run the shim synthesiser (without writing files) and recompute shim-sha256.
  5. Regenerate the rebar3.lock from [[erlang-package]] and diff it against the checked-in rebar3.lock.

Any mismatch in steps 1-5 causes a non-zero exit with a diagnostic message. The check is designed to be run in CI before the build step, ensuring the build is fully reproducible.

Content-addressed cache

The bridge caches downloaded packages in ~/.cache/mochi/erlang-deps/. The cache key is the outer-sha256 hex string. This means:

  • Different packages with the same content hash share a cache entry.
  • Packages are never re-downloaded if the hash matches.
  • The cache is shared across all Mochi projects on the machine.
  • A corrupted cache entry (hash mismatch) is detected at mochi pkg lock --check time.

The cache layout:

~/.cache/mochi/erlang-deps/
<outer-sha256-hex>/
package.tar (outer tarball, verbatim as downloaded)
contents.tar.gz (inner tarball, extracted)
ebin/ (compiled .beam files, extracted from contents)
src/ (Erlang source files, extracted)

The ebin/ directory is populated lazily: only when the BEAM ingest step reads .beam files for type extraction.

rebar3 version check

At mochi pkg lock time and at build time, the bridge runs rebar3 --version and parses the output (e.g., rebar 3.23.0 on Erlang/OTP 27 Erts 15.0). It checks the rebar3 version against the [erlang].rebar3-version constraint from mochi.toml. If the constraint is not met, the bridge fails with a rebar3_version_mismatch error. This prevents silent build failures from rebar3 API changes between versions.

Cross-references