Replaces scripts/release/openssl/{android_arm64,android_arm32,ios_sim, ios_device}.sh. Cross-compiles OpenSSL 3.x for the four target
ABIs that Mob's bundled OTP tarballs link against, producing static
libcrypto.a + libssl.a + headers under a per-target --prefix.
API
iex> MobDev.Release.OpenSSL.build(:android_arm64)
{:ok, %{prefix: "/tmp/openssl-android-arm64", libcrypto: ".../libcrypto.a"}}
iex> MobDev.Release.OpenSSL.build_all()
[{:android_arm64, {:ok, _}}, {:android_arm32, {:ok, _}}, ...]Why this exists
The shell version compiled fine but had four classes of failure mode that this module addresses:
Drift between targets. The arm64 script had
no-asmremoved; the arm32 script needed it but the comment explaining why lived only in arm32. New targets would copy from arm64 and get bitten. Here theTargetspec is data — the disable-asmdecision lives next to the target ID, visible to anyone reading.NDK version drift. The shell mirrored
MobDev.NdkVersion.@recommendedas a separate constant inopenssl/_lib.sh. This module callsMobDev.NdkVersion.effective/0directly — no mirror, no drift.Silent precondition failures. The shell version checked
$ANDROID_NDK_ROOTand$OPENSSL_SRCand bailed withexit 1and a single-lineecho. This module returns a tagged:precondition_failedwith an actionable hint, so the Mix task layer can format it with the same shape as every other release error.No tests. Self-explanatory.
Target spec — what's shared, what differs
All four targets share:
- The
no-Xalgorithm disable list (legacy/niche crypto we don't ship) - Size flags:
-Os -ffunction-sections -fdata-sections -fPIC - The
make distclean/Configure/make -j8/make install_swsequence - Output layout:
$PREFIX/lib/libcrypto.a,$PREFIX/include/openssl/*
Per-target differences are encoded in Target structs (see
target_spec/1):
configure_target—"android-arm64","android-arm","iossimulator-xcrun","ios64-xcrun"default_prefix—/tmp/openssl-<target>env_fn— function that returns the:envlist forShell.cmd(Android targets setANDROID_NDK_ROOT+ prepend the NDK toolchain toPATH; iOS targets setCC/CXX/AR/RANLIBto xcrun-prefixed invocations)extra_configure_args— Android adds-D__ANDROID_API__=24; arm32 addsno-asm(its hand-written ARM assembly emits non-PIC relocations againstOPENSSL_armcap_Pthat ld.lld rejects).
Verifying outputs
build/2 doesn't ship — it returns a map naming the produced files.
Callers (or the integration test) can assert on the existence of
those files. We don't run file or xcrun nm here; the shell did
that as a final "did it produce something for the right arch" check.
In Elixir, that's a separate verify/2 step (TODO — likely lands in
iter 4 alongside tarball verification).
Summary
Functions
Build OpenSSL for one target. Returns {:ok, info} where info names
the produced artifacts, or a tagged error.
Build all four targets in sequence. Returns a list of
{target_id, result} pairs in canonical target order. Does NOT
short-circuit on first failure — callers can decide what to do with
partial results.
Assemble the full ./Configure argv (excluding the program name) for
a given target + prefix. Public so tests can assert on the exact
list without running a build.
The full no-X list — legacy ciphers + protocols we don't ship.
Public so tests can pin the surface and any addition surfaces in
a code review rather than buried in a shell script.
Size + compile flags shared across all targets.
Return the Target spec for an id. Public so tests can inspect specs
without having to build them. Raises on unknown id (programmer error).
All known targets in canonical order.
Functions
@spec build( atom(), keyword() ) :: {:ok, map()} | MobDev.Release.Errors.t()
Build OpenSSL for one target. Returns {:ok, info} where info names
the produced artifacts, or a tagged error.
Options:
:openssl_src— OpenSSL source checkout (default:~/code/opensslor$OPENSSL_SRCenv):prefix— install dir (default: target'sdefault_prefix):ndk_root— Android NDK root override (Android targets only)
@spec build_all(keyword()) :: [{atom(), {:ok, map()} | MobDev.Release.Errors.t()}]
Build all four targets in sequence. Returns a list of
{target_id, result} pairs in canonical target order. Does NOT
short-circuit on first failure — callers can decide what to do with
partial results.
@spec configure_args(MobDev.Release.OpenSSL.Target.t(), Path.t()) :: [String.t()]
Assemble the full ./Configure argv (excluding the program name) for
a given target + prefix. Public so tests can assert on the exact
list without running a build.
Returns the args in canonical order: configure_target, size flags, per-target extras, prefix flags, disable-algorithm flags. The order is observable (OpenSSL's Configure is sensitive to placement of some flags) — tests pin it.
@spec disabled_algorithms() :: [String.t()]
The full no-X list — legacy ciphers + protocols we don't ship.
Public so tests can pin the surface and any addition surfaces in
a code review rather than buried in a shell script.
Every entry has a justification kept inline in disabled_algorithms_doc/0.
@spec size_flags() :: [String.t()]
Size + compile flags shared across all targets.
@spec target_spec(atom()) :: MobDev.Release.OpenSSL.Target.t()
Return the Target spec for an id. Public so tests can inspect specs
without having to build them. Raises on unknown id (programmer error).
@spec targets() :: [atom()]
All known targets in canonical order.