Skip to content

iOS release: compile + link activated-plugin NIFs#36

Merged
GenericJam merged 2 commits into
masterfrom
ios-release-plugin-nifs
Jul 8, 2026
Merged

iOS release: compile + link activated-plugin NIFs#36
GenericJam merged 2 commits into
masterfrom
ios-release-plugin-nifs

Conversation

@GenericJam

Copy link
Copy Markdown
Owner

Problem

mix mob.release --ios produced a binary that failed to link for any app with NIF plugins:

Undefined symbols for architecture arm64:
  "_mob_camera_nif_nif_init", referenced from:
      _erts_static_nif_tab in driver_tab_ios.o
  ... (one per activated plugin)

iOS statically links every NIF into the single app binary (no dlopen under the App Store sandbox), so driver_tab_ios.c references each activated plugin's <module>_nif_init. The dev path (build.zig -Dplugin_c_nifs, fed by MobDev.Plugin.Merge.nif_sources/2) compiles those in — but the release path (release.exrelease_device.sh) is a separate hand-rolled script that predates plugin support and compiled a fixed object list, never touching plugins. Android was unaffected (per-ABI .sos, not one static binary).

Surfaced shipping Sloppy Joe (10 capability plugins) to the App Store.

Fix

Bring the release path to parity. release_env/2 emits two env vars from MobDev.Plugin.activated() via the pure, unit-tested Release.plugin_ios_build_env/1:

  • MOB_PLUGIN_IOS_NIF_SOURCES — each activated plugin's iOS C/ObjC NIF source (Merge.nif_sources(activated, :ios))
  • MOB_PLUGIN_IOS_FRAMEWORKS — the union of declared frameworks (Merge.ios_frameworks/1)

release_device.sh loops over the sources, compiling each with -DSTATIC_ERLANG_NIF_LIBNAME=<basename> (→ <basename>_nif_init, matching the table) and -fmodules (Clang autolinks every @imported framework — plugins often import beyond their manifest set, e.g. Accelerate). Objects join the swiftc link line; declared frameworks are passed explicitly.

Verified

Sloppy Joe's 10 ObjC NIF plugins compile + link; the resulting .ipa code-signs and validates against its App Store distribution profile (valid on disk, satisfies its Designated Requirement).

Tests

  • plugin_ios_build_env/1 — pure matrix (none / one / many / platform-filtered) in release_test.exs
  • release_script_test.exs — script-shape assertions (compile loop, libname derivation, $PLUGIN_OBJS on the link, framework flags) so a regression is caught at mix test, not a TestFlight round trip

Also (2nd commit) — a pre-existing test-infra bug

MobDev.Release.HelpersTest's git fixture didn't clear the ambient git env. Inside a git hook (.githooks/pre-push), git exports GIT_DIR/GIT_WORK_TREE/GIT_INDEX_FILE; the fixture inherited them and operated on the outer repo → git commit exited non-zero → setup crashed. Passed standalone, failed only on push. Now cleared on all three git commands.

Scope / follow-up

Covers nif_sources (lang: :c | :objc) + ios_frameworks — what every current plugin uses. The dev path also handles plugin swift_files and :cpp_archive static archives (e.g. mob_nx_eigen); the release path does not yet (no shipped plugin needs them on iOS today). Documented in decisions/2026-07-07-ios-release-links-plugin-nifs.md.

🤖 Generated with Claude Code

GenericJam and others added 2 commits July 7, 2026 23:41
The release path (release.ex/release_device.sh) hand-compiled a fixed
object list and never touched plugins, so any app with NIF plugins failed
to link: driver_tab_ios references each activated plugin's <module>_nif_init
but nothing defined them (iOS statically links all NIFs — no dlopen). The
dev path already compiles them via build.zig -Dplugin_c_nifs; this brings
the release path to parity.

release_env/2 now emits MOB_PLUGIN_IOS_NIF_SOURCES + MOB_PLUGIN_IOS_FRAMEWORKS
from MobDev.Plugin.activated() (via the pure, tested plugin_ios_build_env/1).
release_device.sh compiles each source with -DSTATIC_ERLANG_NIF_LIBNAME=<basename>
(-> <basename>_nif_init) and -fmodules (framework autolink), links the objects,
and passes the declared frameworks explicitly.

Verified: Sloppy Joe's 10 ObjC NIF plugins compile + link; the IPA code-signs
and validates against its App Store profile.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
MobDev.Release.HelpersTest's git fixture ran git init/add/commit without
clearing the ambient git environment. When the suite runs inside a git hook
(.githooks/pre-push), git exports GIT_DIR/GIT_WORK_TREE/GIT_INDEX_FILE into
the env; the fixture inherited them and operated on the outer repo instead of
its tmpdir (git add matched nothing -> git commit exited non-zero -> setup
crashed, failing both tests). Passed standalone, failed only on push. Clear
the leaking vars (nil removes them for the child) on all three git commands.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@GenericJam GenericJam merged commit 89dd5d1 into master Jul 8, 2026
3 checks passed
@GenericJam GenericJam deleted the ios-release-plugin-nifs branch July 8, 2026 05:44
GenericJam added a commit that referenced this pull request Jul 8, 2026
The iOS release path now compiles + links activated-plugin NIFs (#36), so
multi-plugin apps can build a store-ready IPA. Also fixes a git-fixture flake
that failed only inside git hooks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant