MobDev.NativeBuild (mob_dev v0.4.0)

Copy Markdown View Source

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 iOS build script if ios/build.sh exists (simulator), or xcodebuild if targeting a physical iOS device via device: opt.

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.

Generates the fallback entitlements plist that build_device.sh writes when no ios/*.entitlements file is found in the project.

Returns the bash script that mix mob.deploy --native --device writes to ios/build_device.sh and runs.

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.

Builds the env list passed to build_device.sh when Pythonx is in the project. Returns [] when the project doesn't depend on Pythonx — the generated script's if [ -d "_build/dev/lib/pythonx" ] gate makes the unset env var harmless in that case.

Returns true when the user's project has a built :pythonx dependency. Public for testing — callers see it via build_device_env.

Functions

android_toolchain_available?(project_dir \\ File.cwd!())

@spec android_toolchain_available?(String.t()) :: boolean()

Returns true when the Android build toolchain looks usable from the given project directory. Three signals must all be present:

  1. adb is on PATH (build needs it to install the APK after Gradle)
  2. <project_dir>/android/local.properties exists and sets sdk.dir
  3. The directory sdk.dir points at exists on disk

Returns false otherwise so the deploy can skip Android cleanly instead of failing late inside Gradle. Pure of side effects.

build_all(opts \\ [])

@spec build_all(keyword()) :: [:ok | {:error, term()}]

Builds native binaries for all platforms present in the project. Runs Android Gradle build if android/ dir exists. Runs iOS build script if ios/build.sh exists (simulator), or xcodebuild if targeting a physical iOS device via device: opt.

detect_physical_ios()

@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.

fallback_entitlements_plist(team_id, bundle_id, aps_env \\ nil)

@spec fallback_entitlements_plist(String.t(), String.t(), String.t() | nil) ::
  String.t()

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.

generate_build_device_sh(cfg, otp_root)

@spec generate_build_device_sh(
  keyword(),
  String.t()
) :: String.t()

Returns the bash script that mix mob.deploy --native --device writes to ios/build_device.sh and runs.

Public to enable shape-tests (per AGENTS.md convention) — the Pythonx, exqlite, and signing blocks all matter and shouldn't regress silently. Don't call this from production code; the build flow always pairs it with build_device_env/3.

ios_toolchain_available?()

@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.

narrow_platforms_for_device(platforms, device_id)

@spec narrow_platforms_for_device([atom()], String.t() | nil) :: [atom()]

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.

narrow_platforms_for_device(platforms, device_id, lister)

@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.

otp_dir_for_abi(arg1, arm64, arm32)

@spec otp_dir_for_abi(String.t(), String.t(), String.t()) :: String.t()

Returns the OTP directory for the given Android ABI string.

python_apple_support_env(bool, bundle)

@spec python_apple_support_env(boolean(), String.t() | nil) :: [
  {String.t(), String.t()}
]

Builds the env list passed to build_device.sh when Pythonx is in the project. Returns [] when the project doesn't depend on Pythonx — the generated script's if [ -d "_build/dev/lib/pythonx" ] gate makes the unset env var harmless in that case.

Public for testing.

pythonx_in_project?(project_dir \\ File.cwd!())

@spec pythonx_in_project?(String.t()) :: boolean()

Returns true when the user's project has a built :pythonx dependency. Public for testing — callers see it via build_device_env.

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.