MobDev.Release.OpenSSL (mob_dev v0.5.4)

Copy Markdown View Source

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-asm removed; the arm32 script needed it but the comment explaining why lived only in arm32. New targets would copy from arm64 and get bitten. Here the Target spec is data — the disable-asm decision lives next to the target ID, visible to anyone reading.

  • NDK version drift. The shell mirrored MobDev.NdkVersion.@recommended as a separate constant in openssl/_lib.sh. This module calls MobDev.NdkVersion.effective/0 directly — no mirror, no drift.

  • Silent precondition failures. The shell version checked $ANDROID_NDK_ROOT and $OPENSSL_SRC and bailed with exit 1 and a single-line echo. This module returns a tagged :precondition_failed with 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-X algorithm 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_sw sequence
  • 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 :env list for Shell.cmd (Android targets set ANDROID_NDK_ROOT + prepend the NDK toolchain to PATH; iOS targets set CC/CXX/AR/RANLIB to xcrun-prefixed invocations)
  • extra_configure_args — Android adds -D__ANDROID_API__=24; arm32 adds no-asm (its hand-written ARM assembly emits non-PIC relocations against OPENSSL_armcap_P that 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

build(target_id, opts \\ [])

@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/openssl or $OPENSSL_SRC env)
  • :prefix — install dir (default: target's default_prefix)
  • :ndk_root — Android NDK root override (Android targets only)

build_all(opts \\ [])

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

configure_args(target, prefix)

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

disabled_algorithms()

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

size_flags()

@spec size_flags() :: [String.t()]

Size + compile flags shared across all targets.

target_spec(atom)

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

targets()

@spec targets() :: [atom()]

All known targets in canonical order.