Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 34 additions & 2 deletions lib/mob_dev/release.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ defmodule MobDev.Release do

with :ok <- check_macos(),
:ok <- check_xcrun(),
:ok <- check_driver_table(),
{:ok, cfg} <- resolve_distribution_signing(cfg),
{:ok, otp_root} <- MobDev.OtpDownloader.ensure_ios_device() do
script_path = "ios/release_device.sh"
Expand Down Expand Up @@ -67,6 +68,34 @@ defmodule MobDev.Release do
end
end

# The iOS release links a per-app static-NIF driver table compiled from
# priv/generated/driver_tab_ios.c. The dev build uses Mob's built-in Zig table,
# so a project that has only ever done dev builds never generates the C file —
# and `mix mob.regen_driver_tab` defaults to Zig, so the table must be emitted
# with `--format c`. Without this preflight the build dies deep in release_device.sh
# with a cryptic `cc: no such file or directory: 'priv/generated/driver_tab_ios.c'`.
defp check_driver_table do
path = "priv/generated/driver_tab_ios.c"

if File.exists?(path) do
:ok
else
{:error,
"""
#{path} not found.

The iOS release links a per-app static-NIF driver table. Generate it once:

mix mob.regen_driver_tab --format c

then commit priv/generated/driver_tab_{ios,android}.c so release builds are
reproducible. (The dev build uses Mob's built-in Zig table, so this file is
only needed for release; `mob.regen_driver_tab` without --format c emits Zig,
which the release path does not compile.)
"""}
end
end

# ── Signing config ───────────────────────────────────────────────────────────

@doc false
Expand Down Expand Up @@ -538,8 +567,11 @@ defmodule MobDev.Release do
# erl_errno_id_unknown but the bundled OTP doesn't define it. Weak so
# an OTP-internal definition wins if one ever appears. Written with
# printf (not a heredoc) to stay cleanly indentable inside this
# Elixir \""" string.
printf '%s\\n' '__attribute__((weak)) const char *erl_errno_id_unknown(int error) { (void)error; return "unknown"; }' > "$BUILD_DIR/erl_errno_id_compat.c"
# Elixir \""" string. NOTE the single backslash: this is a ~S (raw) heredoc,
# so '%s\\n' would reach bash verbatim and printf would emit a literal
# backslash-n into the C file (clang then rejects `}\n`). '%s\n' emits a real
# newline.
printf '%s\n' '__attribute__((weak)) const char *erl_errno_id_unknown(int error) { (void)error; return "unknown"; }' > "$BUILD_DIR/erl_errno_id_compat.c"
$CC $IFLAGS -c "$BUILD_DIR/erl_errno_id_compat.c" -o "$BUILD_DIR/erl_errno_id_compat.o"

echo "=== Linking $APP_NAME (release, no EPMD) ==="
Expand Down
7 changes: 7 additions & 0 deletions test/mob_dev/release_script_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,13 @@ defmodule MobDev.ReleaseScriptTest do
# bundled OTP doesn't define it → undefined-symbol link error.
assert sh =~ "erl_errno_id_unknown"
assert sh =~ ~s|"$BUILD_DIR/erl_errno_id_compat.o"|

# The shim must be written with a real newline: this is a ~S (raw) heredoc,
# so `printf '%s\\n'` reaches bash verbatim and emits a literal backslash-n
# into the C file (clang rejects the trailing `}\n`). `printf '%s\n'` (one
# backslash) emits a newline. Regression guard for that escaping bug.
assert sh =~ ~S|printf '%s\n' '__attribute__((weak))|
refute sh =~ ~S|printf '%s\\n'|
end

test "does NOT compile the old md5/no-op crypto+ssl shims into BEAMS_DIR", %{sh: sh} do
Expand Down
Loading