Builds native binaries (APK for Android, .app bundle for iOS simulator) for the current Mob project.
Reads paths from mob.exs in the project root. If mob.exs is missing
or paths haven't been configured, prints instructions and exits.
OTP runtimes for Android and iOS are downloaded automatically from GitHub
and cached at ~/.mob/cache/ by MobDev.OtpDownloader.
mob.exs keys
:mob_dir— mob library repo (native C/ObjC/Swift source):elixir_lib— Elixir stdlib lib dir
Summary
Functions
Returns true when the Android build toolchain looks usable from the given project directory. Three signals must all be present
Builds native binaries for all platforms present in the project.
Runs Android Gradle build if android/ dir exists.
Runs the Mix-driven iOS pipeline (delegating native compile + link
to ios/build.zig for sim, ios/build_device.zig for device) when
ios/build.zig exists. Selection between sim and device is driven
by the device: opt.
iOS-flavoured counterpart to copy_project_python_wheels/1. Same
priv/python_wheels/ convention, same site-packages destination,
but skips any wheel directory containing a .so file at any depth.
Copy the TFLite frameworks (Core + CoreML + Metal) into the iOS app's
Frameworks/ dir so the .app bundle ships them. Called during iOS
app assembly when TFLite is enabled.
Copy the TFLite runtime library (libtensorflowlite_jni.so) into the
Android app's jniLibs/<abi>/ so the APK packager includes it. Called
during the Android assemble step when TFLite is enabled.
Returns the UDID of the sole connected physical iOS device, or nil. When exactly one physical device is connected, it can be used automatically. With zero or 2+ physical devices, returns nil.
True when the current project has :emlx in its dependency tree.
Mirrors pythonx_in_project?/1 — the trigger for downloading the MLX
bundle and adding -Dmlx_static=true to the iOS Zig build.
Generates the fallback entitlements plist that build_device.sh writes when
no ios/*.entitlements file is found in the project.
Decides what to do for the exqlite install step.
Returns true when an iOS build is feasible: macOS host with xcrun
installed. Linux/Windows always returns false. Pure of side effects.
When --device <id> is given, narrow platforms to just the platform
the device lives on. Drops Android when the id resolves to an iOS
device (sim or physical), drops iOS otherwise.
Variant that takes an iOS-discovery function so tests (and other
callers that already have the device list in hand) can avoid the
network-bound IOS.list_devices/0 LAN scan.
Returns the OTP directory for the given Android ABI string.
Returns the PYTHON_APPLE_SUPPORT env entry list when Pythonx is in the
project, otherwise []. Kept public — mob.release and other release
paths still call into this when constructing distribution-mode envs.
Returns true when the user's project has a built :pythonx dependency.
Given the JSON-decoded xcrun simctl list devices booted -j result
and an optional device_id (full UDID or any case-insensitive
prefix of one), return the matching booted simulator's full UDID
or nil.
True if wheel_dir contains at least one .so file at any depth.
Used by copy_ios_safe_project_python_wheels/2 to detect
Android-only wheels.
Functions
Returns true when the Android build toolchain looks usable from the given project directory. Three signals must all be present:
adbis on PATH (build needs it to install the APK after Gradle)<project_dir>/android/local.propertiesexists and setssdk.dir- The directory
sdk.dirpoints at exists on disk
Returns false otherwise so the deploy can skip Android cleanly instead of failing late inside Gradle. Pure of side effects.
Builds native binaries for all platforms present in the project.
Runs Android Gradle build if android/ dir exists.
Runs the Mix-driven iOS pipeline (delegating native compile + link
to ios/build.zig for sim, ios/build_device.zig for device) when
ios/build.zig exists. Selection between sim and device is driven
by the device: opt.
@spec classify_project_nif(MobDev.StaticNifs.nif_entry()) :: {:c, Path.t()} | {:rust, Path.t()} | {:zig, atom()} | :elixir_only
iOS-flavoured counterpart to copy_project_python_wheels/1. Same
priv/python_wheels/ convention, same site-packages destination,
but skips any wheel directory containing a .so file at any depth.
Today's wheel set ships Android-built binaries (Chaquopy-compatible)
under names like _cffi_backend.so and _rust.so — no "android"
in the filename — so a name-based heuristic misses them. Until the
wheels directory holds platform-tagged subdirs (or an iOS-specific
source), treating "has any .so" as "Android-only, skip on iOS"
matches the current reality: pure-Python wheels (rns, lxmf,
pyserial, pycparser) are the only iOS-safe ones. RNS falls back to
its internal crypto provider when cryptography isn't importable,
so this is enough to bring the Reticulum stack up on iOS device
builds.
Public so the iOS-specific filter can be tested independently of the rest of the bundle pipeline.
Copy the TFLite frameworks (Core + CoreML + Metal) into the iOS app's
Frameworks/ dir so the .app bundle ships them. Called during iOS
app assembly when TFLite is enabled.
Same pattern as Python.framework embedding. Codesigning happens at
the app-bundle level — the frameworks just need to be present in the
bundle when the codesign step runs.
slice is either "ios-arm64" (device) or
"ios-arm64_x86_64-simulator" (sim).
No-op when tflite_build is nil.
Copy the TFLite runtime library (libtensorflowlite_jni.so) into the
Android app's jniLibs/<abi>/ so the APK packager includes it. Called
during the Android assemble step when TFLite is enabled.
No-op when tflite_build is nil (TFLite not enabled in this project).
@spec detect_physical_ios() :: String.t() | nil
Returns the UDID of the sole connected physical iOS device, or nil. When exactly one physical device is connected, it can be used automatically. With zero or 2+ physical devices, returns nil.
True when the current project has :emlx in its dependency tree.
Mirrors pythonx_in_project?/1 — the trigger for downloading the MLX
bundle and adding -Dmlx_static=true to the iOS Zig build.
Generates the fallback entitlements plist that build_device.sh writes when
no ios/*.entitlements file is found in the project.
aps_env should be "development", "production", or nil. When non-nil
the aps-environment key is included, allowing APNs push token registration
to succeed. When nil the key is omitted (the historic default, suitable for
apps that do not use push notifications).
This function is public so it can be unit-tested independently of the shell script that actually writes the file on device builds.
@spec generate_erl_errno_compat_stub(Path.t()) :: :ok
@spec install_exqlite_decision(String.t() | nil, String.t()) :: :noop | :stale | {:install, String.t()}
Decides what to do for the exqlite install step.
:noop— no exqlite lock entry; project doesn't use it.:stale— lock entry exists but the dep isn't compiled in_build/dev/lib/exqlite/. Common cause:ecto_sqlite3was once a dep, was removed, and the transitiveexqlitelock entry stayed behind (mix.lock isn't auto-pruned). Returning:stalemakes the caller skip cleanly instead of crashing on a missing-sourceFile.cp!.{:install, vsn}— version is locked and the.appfile is present; safe to install.
Public so the stale-lock guard can be regression-tested without setting up an end-to-end build.
@spec ios_toolchain_available?() :: boolean()
Returns true when an iOS build is feasible: macOS host with xcrun
installed. Linux/Windows always returns false. Pure of side effects.
When --device <id> is given, narrow platforms to just the platform
the device lives on. Drops Android when the id resolves to an iOS
device (sim or physical), drops iOS otherwise.
Public so mix mob.deploy can apply the same narrowing before calling
MobDev.Deployer.deploy_all/1 — otherwise the deployer's per-platform
filter_by_device_id complains "No device matched" against the
irrelevant platform even though the build itself was correctly
targeted.
Returns platforms unchanged when device_id is nil.
@spec narrow_platforms_for_device([atom()], String.t() | nil, (-> [MobDev.Device.t()])) :: [atom()]
Variant that takes an iOS-discovery function so tests (and other
callers that already have the device list in hand) can avoid the
network-bound IOS.list_devices/0 LAN scan.
The lister is called at most once per invocation; both ios_device?
and the physical-UDID format fallback consume the same result.
Returns the OTP directory for the given Android ABI string.
Returns the PYTHON_APPLE_SUPPORT env entry list when Pythonx is in the
project, otherwise []. Kept public — mob.release and other release
paths still call into this when constructing distribution-mode envs.
Returns true when the user's project has a built :pythonx dependency.
Detection is via _build/dev/lib/pythonx/ rather than scanning mix.exs
so users get the same behavior whether they mix mob.enable python and
rely on the dep being added, or vendor pythonx some other way.
Given the JSON-decoded xcrun simctl list devices booted -j result
and an optional device_id (full UDID or any case-insensitive
prefix of one), return the matching booted simulator's full UDID
or nil.
When device_id is nil → first booted sim wins.
When device_id is a string → case-insensitive prefix match
against booted UDIDs. A full UDID matches itself; an 8-char
prefix matches the corresponding device. Public for testing —
JSON shape is the contract.
True if wheel_dir contains at least one .so file at any depth.
Used by copy_ios_safe_project_python_wheels/2 to detect
Android-only wheels.