All notable changes to mob_dev are documented here.
Format: Keep a Changelog. Versioning: SemVer.
Full module documentation: hexdocs.pm/mob_dev.
[0.5.17]
Fixed
- iOS simulator deploy now boots from a clean
mix mob.deploy --native(was device-only). Three gaps made the sim deploy incomplete vs the device path, so the sim crashed on boot even though the device worked:- The Elixir-distribution apps
elixir/loggerwere staged only underlib/<app>/ebin, which the sim'smob_beam.mdoesn't add to the code path — boot failed atensure_all_started(:elixir)with "elixir.app not found". They're now flattened into the flat BEAMS_DIR alongsideeex(which already needed this), where the path resolves. priv/was only partially staged (repo/migrations), soApplication.app_dir(:<app>, "priv/cacerts.pem")was:enoentandMob.Certs.load_cacerts!crashed the boot. The wholepriv/is now rsynced into the flat dir (cacerts,mix/hexebins, vendored static, …), matching the device release.Paths.sim_runtime_dir/0fell back to/tmp/otp-ios-simfor zig-based projects (noios/build.sh), but the runtime is synced to~/.mob/runtime/ios-sim— so the launcher and staging disagreed. It now recognizesios/build.zigand returns the default runtime dir. Verified: a cleanmob.deploy --nativeto an iPhone 11 Pro Max sim boots Io, Phoenix endpoint up, embedded Livebook home renders — no manual runtime fixups.
- The Elixir-distribution apps
[0.5.16]
Fixed
- Gate the plugin flags on the iOS device build path too. 0.5.15 gated the plugin-flag emission for Android and the iOS simulator build, but
zig_build_binary_ios_devicestill emitted-Dplugin_swift_files/-Dplugin_frameworks(and generated the bootstrap, makingplugin_swift_filesalways non-empty) unconditionally — somix mob.deploy --nativeto a physical iPhone broke on an app scaffolded before the plugin system (invalid option: -Dplugin_swift_files). The device path now mirrors the sim path: bootstrap + flags only when plugins are activated. Verifiedmix mob.deploy --nativeto a physical iPhone — full OTP, Phoenix endpoint up, LiveView connected, embedded Livebook home rendered.
[0.5.15]
Fixed
mix mob.release --android --no-slimnow actually ships the full OTP tree. The Android release stripped OTP libs unconditionally (OtpAssetBundle.build/2was called with no opts), so--no-slimwas silently ignored on Android.slimis now threadedbuild_aab → OtpAssetBundle.build(slim:); withslim: falsethe OTP tree ships untouched. Required for apps that run arbitrary user code at runtime (e.g. an embedded Livebook host doingMix.install) — stripping any OTP lib (inets,ssl,xmerl,runtime_tools, …) is a latent crash when a user's deps need it. Default staysslim: true.- iOS
--no-slimrelease passes App Store validation. The always-on Apple-policy strip clearederts-*/binandpriv/binbut missed standalone executables inside OTP libs (e.g.erl_interface/bin/erl_call), which App Store validation rejects (90171). Nowlib/*/bin/*executables are stripped too (always on), keeping every lib's.beam/.app— so a full-OTP--no-slimbundle is still Apple-compliant. - Native builds no longer break on pre-plugin app scaffolding.
native_build.exemitted-Dplugin_c_nifs/-Dplugin_zig_nifs/-Dplugin_jni_sources(Android) and-Dplugin_swift_files/-Dplugin_frameworks(iOS) unconditionally, but an app scaffolded before the plugin system has no such options in itsbuild.zigand Zig rejects the unknown-Dflag. These flags (and the iOS plugin bootstrap) are now emitted only when plugins are activated; a plugin-awarebuild.zigdefaults them to""so behaviour is unchanged there.
[0.5.14]
Fixed
- iOS release:
erl_errno_id_unknownshim written with a literal\n. The weak-stub line inrelease_device.shusedprintf '%s\\n'inside the~S(raw) heredoc, so bash received both backslashes andprintfwrote a literal backslash-nintoerl_errno_id_compat.c— clang then rejected the trailing}\nandmix mob.release --iosfailed. Nowprintf '%s\n'(one backslash) emits a real newline. Regression guard added torelease_script_test. - iOS release: clear preflight when
priv/generated/driver_tab_ios.cis missing. The release links a per-app static-NIF driver table, but the dev build uses the built-in Zig table andmix mob.regen_driver_tabdefaults to Zig, so a project that never ran it with--format cdied deep inrelease_device.shwith a crypticcc: no such file.build_ipanow fails early with the exact command to run (mix mob.regen_driver_tab --format c).
[0.5.13]
Fixed
- iOS deploy now ships the whole
priv/, not justpriv/repo/migrations+priv/static.MobDev.Release's iOS bundler copied only migrations andpriv/static, so apps that bundle extra runtime assets underpriv/—:mix/:hexebins for on-deviceMix.install, or a vendored library's ownpriv/(e.g. Livebook'spriv/static+priv/livebook) — silently never reached the device. Now rsyncs all ofpriv/, matching the Android deployer. Unblocks on-deviceMix.installand embedded Livebook on iOS. Verified on a physical iPhone:priv/mix/ebin(103 beams) andpriv/livebook/staticpresent on device, embedded Livebook serves, andMix.install([{:short_uuid, "~> 0.1"}])returns:ok.
[0.5.12]
Changed
- OTP runtime bumped to
7d46fdd4(Elixir 1.20.0-rc.5).@otp_hashnow points at theotp-7d46fdd4release: all four platform tarballs (ios-sim, ios-device, android, android-arm32) bundle Elixir 1.20.0-rc.5 matched to the OTP-29 erts. Completes the 1.19.5 to 1.20 runtime migration the bundled-versions manifest was already staged for. Verified end-to-end on a physical iPhone and a physical Android (Moto G):System.version1.20.0-rc.5, OTP 29,Mix.install([{:short_uuid, "~> 0.1"}])returns:okwith the dep compiled on-device.
Fixed
- iOS
{spawn, <linked-in driver>}now works (ertserts_open_driver). The iOS-build#ifdef __IOS__guard returned BADARG for anyopen_portwhose spawntype included the EXECUTABLE bit (i.e. plain{spawn, Name}), firing before the linked-in-driver name lookup. This brokeram_file({spawn, "ram_file_drv"}), and therefore `file:open(, [:ram]),erl_tarin-memory extract,hex_tarball.unpack, andMix.installon iOS. The guard now fires only when no linked-in driver matched the name. Bundled in the7d46fdd4` OTP tarballs.
[0.5.11]
Added
mob.exs :project_swift_sourcesconfig key — optional list of extra Swift sources to compile into the iOS app module alongside Mob's bridge sources. Threaded into bothzig_build_binary_ios_simandzig_build_binary_ios_deviceas-Dproject_swift_sources=<absolute,paths>. Comma-containing entries are rejected at the boundary; nil/[] is a no-op. Pairs with mob_new'sproject_swift_sourcesbuild hook (mob_new#5). Originally proposed by @dl-alexandre.
[0.5.10]
Added
mix mob.deploy --dist-port Nand--node-suffix Sflags — manual override path for the BEAM-distribution surface. When set, all targeted devices use the same value (use with--device <id>to be explicit). Nil falls back to per-device auto-allocation (Tunnel.dist_port(idx)+Discovery.Android.device_node_suffix/ SIMULATOR_UDID-derived suffix). Resolves theregister/listen error: no_reg_reply_from_epmdsymptom seen when running multiple sims/emulators of the same app concurrently for cross-platform visual comparison.MobDev.Devicestruct gains a:node_suffixfield for plumbing the override per-device alongside:dist_port. Nil keeps auto-derive.MobDev.Discovery.IOS.launch_app/3accepts:node_suffixopt and forwards asSIMCTL_CHILD_MOB_NODE_SUFFIXto the launched sim. Companion tomob 0.6.10'sMOB_NODE_SUFFIXsupport inmob_beam.m.MobDev.Discovery.IOS.build_simctl_env/2— pure helper extracted fromlaunch_app/3so override behaviour is unit-testable without spawningsimctl. 7 new tests cover dist_port + node_suffix override paths.
Changed
MobDev.Connector.restart_app/1pattern-matches:node_suffixfromDevicein both Android + iOS-sim variants, threading the value to the launchers.MobDev.Deployer.deploy_all/1accepts top-level:dist_port+:node_suffixopts; threaded throughdeploy_androidanddeploy_ios_simulator.
[0.5.9]
Changed
mix mob.enable tflitenow injects{:nx_tflite_mob, "~> 0.0.3"}(Hex) instead of the GitHub-branch form.nx_tflite_mobv0.0.3 went live on Hex with 16 integration tests + a reproducible Mac host build path (see its CHANGELOG). Downstream Mob apps now get version-pinned deps + cleanmix deps.treeoutput, instead of a transientgithub:checkout.
Notes
- The Mac host-build path in
nx_tflite_mobis for that package's own test suite, not for downstream consumers — production phone builds viamix mob.deploy --nativecontinue to use the prebuilt Android AAR + iOS xcframework that mob_dev'sMobDev.TfliteDownloaderfetches.
[0.5.8]
Added
- End-to-end
mix mob.enable tflite— what 0.5.7 promised as "lands in 0.5.8".MobDev.NativeBuildnow auto-detects the:nx_tflite_mobdep and threads the full TFLite path through Android + iOS sim + iOS device build pipelines:maybe_build_tflite/1→MobDev.TfliteDownloader.ensure/1+MobDev.TfliteNif.build/2for each target archtflite_zig_args_android/1emits-Dtflite_static=true -Dtflite_lib=…for the per-ABI Android linktflite_zig_args_ios/1emits-Dtflite_static=true -Dtflite_dir=… -Dtflite_framework_dir=…for the iOS linkcopy_tflite_runtime_lib_android/2dropslibtensorflowlite_jni.sointoandroid/app/src/main/jniLibs/<abi>/during the assemble step
copy_tflite_frameworks_ios/3(kept as future-compat hook) — see the iOS-deploy-fix gotcha below- 13 new tests covering the public NativeBuild plumbing
(
native_build_tflite_test.exs), bringing the TFLite suite to 76 passing total
Fixed
- iOS deploy: TFLite framework binaries are MH_OBJECT, not
MH_DYLIB. TFLite's iOS xcframework slices ship their binaries as
filetype=1 relocatable objects, which the linker statically pulls
into the app's main Mach-O at build time. Trying to embed them as
runtime
.frameworkbundles tripped iOS install twice during this cut: first on missing per-framework Info.plist (which CocoaPods generates), then on "code signature version no longer supported" (iOS 26+ rejects v1 signatures, and codesign only makes v3 sigs for MH_EXECUTE/MH_DYLIB). The fix is to do nothing — the framework search-path arg already covers everything at build time. - Resolve
:nx_tflite_mobviaMix.Project.deps_paths()rather thanPath.join(deps_path, "nx_tflite_mob"). The latter assumes the dep landed indeps/(hex / git deps do), butpath:deps consume in-place from the user's source tree.
Verified on real hardware
- Moto G Power 5G (BXM-8-256, Android 15): 75-117 ms YOLOv8n via
NNAPI /
mtk-gpu_shim - iPhone SE 3rd gen (A15, iOS 26.4): 24 ms YOLOv8n via Core ML → ANE (FP16 model; 214/385 nodes delegated)
[0.5.7]
Added
mix mob.enable tflite— wires TensorFlow Lite into a Mob project on iOS AND Android. Adds{:nx_tflite_mob, ...}to deps and generates<App>.TfliteInit(returns per-platform default delegate opts — NNAPI/mtk-gpu_shimon Android, Core ML delegate on iOS). The static-NIF table entry%{module: :tflite_nif, guard: "MOB_STATIC_TFLITE_NIF"}is registered inMobDev.StaticNifs.default_nifs/0, so the zig build picks it up automatically oncetflite_static=trueis set.MobDev.TfliteDownloader— fetchestensorflow-lite-2.16.1.aar(Maven Central, Android) andTensorFlowLiteC-2.17.0.tar.gz(dl.google.com, iOS) into~/.mob/cache/. HonoursMOB_CACHE_DIRfor test redirection andMOB_TFLITE_LOCAL_TARBALL_DIRfor offline iteration.MobDev.TfliteNif— cross-compilestflite_nif.c(from the:nx_tflite_mobdep) per-arch and archives aslibtflite_nif.afor static linking. MirrorsMobDev.NxEigenNifshape. Validates the produced symbol (tflite_nif_nif_init) before declaring success.
Bundle size impact: ~3-4 MB extracted (Android libtensorflowlite_jni.so),
~20-30 MB on iOS (TensorFlowLiteC + CoreML + Metal frameworks). Apps
that don't enable TFLite pay zero size cost — the guard keeps the
static-NIF table entry inactive.
End-to-end deploy (mob.deploy --native auto-build + runtime-lib
embedding) lands in 0.5.8; this release ships the building blocks.
[0.5.6]
Added
CLAUDE.md"Release flow" section pointing at the canonical process inmob/RELEASE.md(URL form so it resolves without a local mob checkout). mob_dev specifics: the pre-push hook additionally runsmix mob.security_scanhere (this is the only repo that ships the scanner), and the OTP tarball workflow stays separate frommix.exsversion bumps..githooks/pre-push— same script shipped in mob (cheap preflight always, release preflight whenmix.exschanged). Themob.security_scanstep is gated viamix helpavailability so the same hook script works in all three repos.
[0.5.5]
Fixed
- Android 15 segfault on launch (Pixel 7+, after the OS rolled out via OTA). Bumps
@otp_hashfrom550d7b78→d9045670to pick up OTP tarballs cross-compiled with-Wl,-z,max-page-size=16384. Without the flag, every.soin the bundled OTP runtime (crypto.so,asn1rt_nif.so,dyntrace.so, etc.) shipped with 4KB-aligned ELFPT_LOADsegments. Android 15 enforces 16KB alignment on devices with 16KB-page kernels and refuses to load misaligned libs, crashing the app at startup. New tarballs are 16KB-aligned (Align=0x4000).
[0.5.4]
Fixed
- HexDocs source links pointed at the non-existent
mainbranch — corrected tomasterso each</>glyph in generated docs opens the actual source file.
Added
.github/workflows/test.yml— runsmix test,mix format --check-formatted,mix credo --strict, andmix mob.security_scanon push to master and on every PR..github/workflows/release.yml— on tag push, creates a GitHub Release whose body is the matching## [X.Y.Z]section from this changelog.
[0.5.3]
Changed
guides/nifs.md— rewrote the "Nx backends on mobile" section to match the current state (mix mob.enable nxeigennow real,mix mob.enable mlxincludes the Metal GPU path on iOS device, EXLA "why not" preserved).guides/nifs.md— restructured the multi-Rust-NIF section to lead with filmor's preferred shape (one Rustler crate per app, multiple#[rustler::nif]functions inside it). Multi-crate static linking remains supported and documented as an escape hatch, with the specific tradeoffs called out.
[0.5.2]
Added
mix mob.enable nxeigen— wires NxEigen (Eigen C++ CPU backend) into a Mob app. Builds as a C++:static_nifsentry, cross-compiled per arch (arm64-ios,arm64-iossim,arm64-android,armv7a-android). FFT support uses Eigen's bundled kissfft.- EMLX Metal GPU enabled on iOS device.
lib/mob_dev/mlx_downloader.exnow fetches the Metal-enabledlibmlx.a+mlx.metallibbundle;lib/mob_dev/native_build.ex#maybe_bundle_mlx_metallib/2copies the precompiled kernel library into the .app at build time, soEMLX.Backendwithdevice: :gpuworks on device without runtime kernel compilation. scripts/release/mlx/ios_device_metal.sh+ supporting build scripts for producing the Metal-enabled tarball;scripts/release/mlx/patches/0001-ios-metal-build.patchpatches MLX 0.25.1's CMakeLists to switch SDK frommacosxtoiphoneosbased onCMAKE_SYSTEM_NAME.
[0.5.1] and earlier
Earlier releases predate this changelog; consult the tag list and the per-tag commit messages for history.