MobDev.OtpAssetBundle (mob_dev v0.7.21)

Copy Markdown View Source

Builds the assets/otp.zip that release-mode Android Mob apps extract on first launch.

Mirrors the elixir-desktop example-app pattern (their Bridge.kt:unpackZip()) but with Mob-specific stripping inherited from the iOS release pass — drops unused OTP libs and standalone executables to shave bundle size and avoid shipping anything Mob never actually executes.

Called from mix mob.release --android (Workstream 3). Kept as a separate module so the strip rules + zip layout are testable without spinning up the full release pipeline.

Layout produced

otp.zip
├── erts-VSN/...           (BEAM emulator support — minus bin/)
├── lib/elixir-VSN/...
├── lib/logger-VSN/...
├── lib/eex-VSN/...
├── lib/<other-otp-libs-the-app-uses>/...
└── releases/...

The MobBridge.extractOtpIfNeeded() Kotlin function unzips this into <filesDir>/otp/ on first launch, keyed by PackageInfo.lastUpdateTime so app updates trigger a re-extract.

What gets stripped

Three categories, mirroring the iOS strip pass in MobDev.Release:

  1. Whole OTP libs the framework doesn't use — megaco, runtime_tools, wx, observer, debugger, etc. These are dead weight in a Mob app. A lib in that list is kept when the app needs it: anything in the :required_apps closure (MobDev.OtpRequiredApps). Stripping a lib a shipped app lists in its .app makes starting that app fail on the device, e.g. {:inets, {~c"no such file or directory", ~c"inets.app"}}.
  2. Standalone executables — priv/bin/* and erts-*/bin/*. The few that Mob actually executes (erl_child_setup, inet_gethost, epmd) are packaged separately in jniLibs/<abi>/ as lib<name>.so so they get the apk_data_file SELinux label that allows execve from untrusted_app. The rest aren't reachable.
  3. Static archives (.a) — already linked into the app's native lib at build time. Shipping them inside the runtime tree is pure waste.

.so files inside OTP libs (e.g. priv/lib/asn1.so) are KEPT in the zip — they're loaded by the BEAM at runtime via dlopen(), which works fine from app data dir on Android (only execve() is blocked).

Summary

Functions

Builds the OTP asset zip from source_otp_tree into target_zip_path.

Returns the list of OTP lib name prefixes that are stripped by default. Public so tests can assert the policy without re-importing the module-private list.

Functions

build(source_otp_tree, target_zip_path, opts \\ [])

@spec build(Path.t(), Path.t(), keyword()) :: {:ok, map()} | {:error, term()}

Builds the OTP asset zip from source_otp_tree into target_zip_path.

Returns {:ok, %{zipped_files: count, original_size_kb: integer, zip_size_kb: integer, kept_required: [String.t()]}} on success, {:error, reason} on failure. kept_required lists the libs of the strip set kept because the app needs them.

Options

  • :required_apps — names of the apps the release starts (normally MobDev.HotPush.runtime_lib_names/0). They and everything their .app files in the OTP tree need, transitively, are never stripped.
  • :strip_extra_prefixes — additional OTP lib prefixes to drop on top of the default list. Atoms or strings. Explicit, so it wins over :required_apps.
  • :keep_prefixes — prefixes to KEEP even if in the default strip list. Lets a specific app opt back into a stripped lib.

default_stripped_prefixes()

@spec default_stripped_prefixes() :: [String.t()]

Returns the list of OTP lib name prefixes that are stripped by default. Public so tests can assert the policy without re-importing the module-private list.