Fails an iOS build early when a plugin's cpp_archive references a symbol
nothing in the app defines.
The iOS links (mix mob.deploy's ios/build.zig / build_device.zig and
mix mob.release --ios) all pass -dead_strip, and ld64 reports an
undefined symbol only from code that survives dead-stripping. A reference
in code the app never reaches therefore links everywhere, until a link
without -dead_strip, or a change that makes that code reachable, fails
at the worst moment (MOB-470: mobwhisper 0.1.1's ggml `dl*helpers passed every dev build and failedmix mob.release --ios`).
check/5 links the plugin's archive into a throwaway dylib without
-dead_strip: every member of its own lib<module>.a (-force_load),
plus the members of its prebuilt archives (MOB-427) that those pull in,
as the app's link would. The symbols left unresolved must each be
defined by something the app's link supplies, and the allow-list is
derived from those inputs, not written down:
- the BEAM: every symbol the target's ERTS static libraries define
(
<otp>/erts-*/lib/**/*.a; theenif_*API and the rest of ERTS), - mob core: the non-static C functions, globals and ObjC classes mob's
iOS sources (
<mob_dir>/ios/*.{m,mm,c}) define, e.g.mob_register_permission_handler, - the iOS SDK: every symbol a library or framework of the target's SDK
exports (its
.tbdstubs), since the app may link any of them, explicitly or through clang module autolinking.
Anything else is a dangling reference and fails the build, naming the
plugin, the symbols and the archive members that reference them. See
decisions/2026-10-13-ios-release-dead-strips-and-plugin-link-check.md.
Summary
Functions
Check one built plugin archive for target_id (:ios_sim or :ios_device).
The Mach-O names of what a C / ObjC source defines with external linkage:
top-level non-static functions and globals, and @implementation
classes. A text scan, for mob core's sources (which import a Swift header
the build generates, so they can't be compiled on their own here). Pure.
The build error for dangling (symbols nothing in the app defines), with
the archive members referencing each from referrers. Pure.
The symbols nm -m -u lists as left for dyld to look up (the ones the
probe link could not resolve). Pure.
Which of names some .tbd under sdk_path exports, matched as whole
words. Every library and framework the SDK ships has one.
How a Mach-O symbol is spelled in an SDK .tbd stub: ObjC class, metaclass,
exception-type and ivar symbols by their bare class (or Class.ivar) name,
everything else as is. Pure.
Functions
@spec check(map(), :ios_sim | :ios_device, Path.t(), [Path.t()], keyword()) :: :ok | {:error, String.t()}
Check one built plugin archive for target_id (:ios_sim or :ios_device).
spec is the MobDev.Plugin.Merge.static_archives/2 entry (:plugin,
:module); archive its lib<module>.a; prebuilt the archives its
prebuilt bundle contributes for the target. Options: :otp_dir (the
target's OTP runtime, required) and :mob_dir (mob's checkout; mob
core's symbols are allowed only when given).
Returns :ok, or {:error, message}.
The Mach-O names of what a C / ObjC source defines with external linkage:
top-level non-static functions and globals, and @implementation
classes. A text scan, for mob core's sources (which import a Swift header
the build generates, so they can't be compiled on their own here). Pure.
@spec error_message(map(), atom(), [String.t()], %{ required(String.t()) => [String.t()] }) :: String.t()
The build error for dangling (symbols nothing in the app defines), with
the archive members referencing each from referrers. Pure.
The symbols nm -m -u lists as left for dyld to look up (the ones the
probe link could not resolve). Pure.
Which of names some .tbd under sdk_path exports, matched as whole
words. Every library and framework the SDK ships has one.
How a Mach-O symbol is spelled in an SDK .tbd stub: ObjC class, metaclass,
exception-type and ivar symbols by their bare class (or Class.ivar) name,
everything else as is. Pure.