Single source of truth for the Android NDK version Mob's bundled OTP runtime was cross-compiled against.
The on-device libbeam.a (in the otp-android-* tarballs) embeds C++
stdlib symbols using libc++'s versioned inline namespace. NDK 27.2
uses std::__ne180000::; NDK 25 uses std::__ne140000::. These
don't link cross-version: an app's libpigeon.so built with the
wrong NDK fails with undefined symbol: __cxa_allocate_exception
(or similar libc++ ABI symbols).
This module is consulted by:
mix mob.doctor— checks the recommended NDK is installed and that the project's gradle pin (or override) doesn't drift.mix mob.install— same check during onboarding.mix mob.new's gradle template (viaMobNew.NdkVersion) — sets thendkVersionliteral so AGP picks deterministically.scripts/release/openssl/*.sh— sourcesNDK_VERSIONfrom_lib.shso the host-side OpenSSL cross-compile uses the same NDK as the bundled tarballs.
Recommended vs effective
recommended/0 is the version Mob's tarballs were built against. It
changes only when we cross-compile new tarballs.
effective/0 returns the recommended version unless the user has
overridden it. Two override mechanisms:
Environment variable (
MOB_ANDROID_NDK_VERSION=...) — machine-local. Use when one developer needs a specific NDK on their box and the team's project config should stay clean.Per-project config in
mob.exs:config :mob_dev, android_ndk_version: "25.1.8937393"Travels with the project. Use when the whole team needs to build against a non-recommended NDK (legacy library dependency, hardware-specific toolchain, etc).
Precedence: env var > mob.exs > recommended.
Override caveat
When an override is active the user opts out of the libc++ ABI
guarantee against the bundled tarballs. They're navigating that
alone — mob.doctor warns but does not fail. Cryptic link errors
against libbeam.a are then their problem to debug. See
~/code/mob/common_fixes.md "NDK 27 / clang 18 split libc++"
for the symptom and the diagnostic.
Summary
Functions
The NDK version the user's build should target.
Build the suggested install command for the recommended NDK. Used by
mob.doctor and mix mob.install to give the user a one-liner.
True if the given NDK version is installed under the local Android SDK.
Returns all NDK versions present under the local SDK, newest-first by string sort.
Returns {:env, version}, {:mob_exs, version}, or :none
describing which override mechanism is active (if any).
Reads the project's android/app/build.gradle (or .kts) for the
ndkVersion literal. Returns the string or nil if not pinned.
The NDK version the bundled OTP tarballs were cross-compiled with.
Returns the absolute path to the recommended NDK install if present,
or nil.
Functions
@spec effective() :: String.t()
The NDK version the user's build should target.
Returns recommended/0 unless overridden via MOB_ANDROID_NDK_VERSION
env var or :android_ndk_version in mob.exs's :mob_dev config.
@spec install_command() :: String.t()
Build the suggested install command for the recommended NDK. Used by
mob.doctor and mix mob.install to give the user a one-liner.
True if the given NDK version is installed under the local Android SDK.
Looks for <sdk>/ndk/<version>/source.properties since the directory
alone can be a half-extracted partial install.
@spec installed_versions() :: [String.t()]
Returns all NDK versions present under the local SDK, newest-first by string sort.
@spec override() :: {:env | :mob_exs, String.t()} | :none
Returns {:env, version}, {:mob_exs, version}, or :none
describing which override mechanism is active (if any).
Used by mob.doctor to explain why the effective version differs
from the recommendation.
Reads the project's android/app/build.gradle (or .kts) for the
ndkVersion literal. Returns the string or nil if not pinned.
Accepts an optional project root; defaults to the current working directory.
@spec recommended() :: String.t()
The NDK version the bundled OTP tarballs were cross-compiled with.
@spec recommended_install_path() :: String.t() | nil
Returns the absolute path to the recommended NDK install if present,
or nil.