MobDev.Release.Helpers (mob_dev v0.5.0)

Copy Markdown View Source

Replaces the two scripts/release/_lib.sh files. Pure functions where possible; side-effectful ones are narrow + testable via tmpdir fixtures.

Why this lives in Elixir rather than shell

The single big reason is bugs surface in CI rather than in user inboxes. The shell version's failure path is "user runs release → obscure error → reports issue → maintainer can't reproduce locally → long discovery cycle." This module's failure path is "CI test fails → fix → ship." See MobDev.Release.Errors for the typed error tags that make distinguishing "our bug" from "user env" from "infra down" cheap at the call site.

Single source of truth conventions

The shell _lib.sh mirrored constants from Elixir modules (MobDev.NdkVersion, etc.). This module is the source — no mirroring. When something downstream needs the recommended NDK version, it calls MobDev.NdkVersion.effective/0 directly.

Summary

Types

Absolute path to an OTP source checkout (e.g. ~/code/otp).

Functions

Copy the Elixir stdlib apps (elixir, logger, eex) from the host's Elixir installation into the release stage directory. Mirrors _lib.sh's bundle_elixir_stdlib() function.

Default OTP source path (mirrors _lib.sh's ${OTP_SRC:=$HOME/code/otp}). Resolved per-call rather than at module load so test setups can swap $HOME via System.put_env/2.

Default tarball output directory (mirrors _lib.sh's ${OUT_DIR:=/tmp}).

Return the host Elixir's lib dir — the parent directory containing elixir/, logger/, eex/ as sibling app dirs. Used to bundle the Elixir stdlib into the release tarball.

Read the ERTS version (e.g. "17.0") from <otp_src>/erts/vsn.mk. The file format is one line VSN = 17.0 plus comments.

Detect the short git hash of the OTP source tree at otp_src. Used as the release-asset tag suffix (e.g. otp-android-<hash>.tar.gz).

Parse the VSN = <version> line out of an erts/vsn.mk-shaped file.

Parse a git short-hash from git rev-parse output. Trims whitespace and validates that the result is exactly @hash_length hex chars.

Collapse the per-piece resolvers into one struct-shaped return. Convenience for callers that want all of {otp_src, hash, erts_vsn, elixir_lib, out_dir} resolved in one go.

Types

otp_src()

@type otp_src() :: Path.t()

Absolute path to an OTP source checkout (e.g. ~/code/otp).

Functions

bundle_elixir_stdlib(stage_dir, elixir_lib_dir)

@spec bundle_elixir_stdlib(Path.t(), Path.t()) ::
  {:ok, [Path.t()]} | MobDev.Release.Errors.t()

Copy the Elixir stdlib apps (elixir, logger, eex) from the host's Elixir installation into the release stage directory. Mirrors _lib.sh's bundle_elixir_stdlib() function.

Bytecode is arch-independent, so the same source works for all platform tarballs. Caller is responsible for creating the stage directory.

Returns {:ok, [bundled_app_dirs]} or a tagged error.

default_otp_src()

@spec default_otp_src() :: Path.t()

Default OTP source path (mirrors _lib.sh's ${OTP_SRC:=$HOME/code/otp}). Resolved per-call rather than at module load so test setups can swap $HOME via System.put_env/2.

default_out_dir()

@spec default_out_dir() :: Path.t()

Default tarball output directory (mirrors _lib.sh's ${OUT_DIR:=/tmp}).

elixir_lib_dir()

@spec elixir_lib_dir() :: {:ok, Path.t()} | MobDev.Release.Errors.t()

Return the host Elixir's lib dir — the parent directory containing elixir/, logger/, eex/ as sibling app dirs. Used to bundle the Elixir stdlib into the release tarball.

Shell equivalent:

ELIXIR_LIB=$(elixir -e "IO.puts(:code.lib_dir(:elixir))" | xargs dirname)

Here we just call :code.lib_dir/1 directly — no subprocess hop.

erts_version(otp_src)

@spec erts_version(otp_src()) :: {:ok, String.t()} | MobDev.Release.Errors.t()

Read the ERTS version (e.g. "17.0") from <otp_src>/erts/vsn.mk. The file format is one line VSN = 17.0 plus comments.

Returns {:ok, "17.0"} on success or a tagged error.

git_hash(otp_src)

@spec git_hash(otp_src()) :: {:ok, String.t()} | MobDev.Release.Errors.t()

Detect the short git hash of the OTP source tree at otp_src. Used as the release-asset tag suffix (e.g. otp-android-<hash>.tar.gz).

Pinned to 8 characters so tarball filenames, GitHub release tags, and the @otp_hash constant in MobDev.OtpDownloader all stay in lockstep. Git's default --short length grows over time (collision avoidance) so without pinning the names would silently drift.

Returns {:ok, "8hexchars"} on success or a tagged error.

parse_erts_version(content)

@spec parse_erts_version(binary()) :: {:ok, String.t()} | MobDev.Release.Errors.t()

Parse the VSN = <version> line out of an erts/vsn.mk-shaped file.

Public for testing — version-string drift between OTP releases is exactly the kind of regression that should fail loudly with a clear message rather than silently producing tarballs named with a missing version suffix.

parse_git_hash(output)

@spec parse_git_hash(binary()) :: {:ok, String.t()} | MobDev.Release.Errors.t()

Parse a git short-hash from git rev-parse output. Trims whitespace and validates that the result is exactly @hash_length hex chars.

Public for testing — the regex/length contract is the surface we want to lock down with examples, and a pure function is the cleanest way.

resolve_release_env(opts \\ [])

@spec resolve_release_env(keyword()) :: {:ok, map()} | MobDev.Release.Errors.t()

Collapse the per-piece resolvers into one struct-shaped return. Convenience for callers that want all of {otp_src, hash, erts_vsn, elixir_lib, out_dir} resolved in one go.

Honours these env vars (in the same order _lib.sh did):

  • OTP_SRC — overrides default otp source path
  • OUT_DIR — overrides default tarball output dir
  • HASH — pre-set hash, skips git detection
  • ERTS_VSN — pre-set erts version, skips vsn.mk parsing

Returns {:ok, %{otp_src: ..., hash: ..., erts_vsn: ..., elixir_lib: ..., out_dir: ...}} or the first error encountered.