# `MobDev.Release.OpenSSL`
[🔗](https://github.com/genericjam/mob_dev/blob/master/lib/mob_dev/release/openssl.ex#L1)

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

# `build`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

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

Size + compile flags shared across all targets.

# `target_spec`

```elixir
@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`

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

All known targets in canonical order.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
