MobDev.SupportMatrix (mob_dev v0.5.7)

Copy Markdown View Source

Per-feature device requirements + the validation that runs before mix mob.deploy builds anything.

The instinct is to ship the latest-arch path because that's where the upstream toolchains are easiest to integrate. The cost of that instinct is silent failure: a user with an older / cheaper / 32-bit device buys hardware, runs mix mob.deploy, sees a vague gradle error, and walks away assuming Mob is broken. They never find out the device was below our floor.

This module makes the floors explicit, declarable, and enforced:

  • feature_requirements/1 — the data. What ABIs / SDK levels does a feature need? Where does the constraint come from upstream (Chaquopy, BeeWare, Apple)?
  • enabled_features/1 — what features does this project use? Inferred from the project's mix.exs / generated files, not from a flag the user has to remember to set.
  • check_device/2 — given a device's discovered properties + a list of enabled features, return :ok or {:error, [%{feature, reason, device}]}.

mix mob.deploy calls these before invoking MobDev.NativeBuild so the message a user sees on an unsupported device is:

 Moto e (armeabi-v7a, Android 10) cannot run this project:
    - pythonx requires Android arm64-v8a or x86_64. Chaquopy
      (the upstream Python distribution we bundle) dropped
      32-bit Android support several releases ago.

To target this device, either disable Pythonx or use an arm64
device. See guides/support_matrix.md for the full floor.

…rather than a build that silently produces an APK the device can't load.

Adding a new feature

When you add a feature that has device requirements distinct from the base Mob floor, add a clause to feature_requirements/1 and a detection clause to enabled_features/1. Don't bury the constraint in build-time code — that's how silent failures happen.

Summary

Types

Full requirement spec for a feature. No feature currently models :unsupported (a feature outright incompatible with a platform) — if one needs to, add | :unsupported to the platform value type here AND re-add the matching case clause in check_against/2.

An incompatibility found by check_device/2.

Requirement spec for a single feature on a single platform.

Functions

Returns the base device requirements every Mob app inherits.

Checks that device can run the given list of enabled features (plus the base Mob floor).

Returns the list of features enabled in project_dir that have non-base device requirements.

Returns the device requirements for a specific feature.

Renders an {:error, [incompatibility]} result as the human-readable block printed by mix mob.deploy before exiting.

Types

feature_req()

@type feature_req() :: %{android: platform_req(), ios: platform_req()}

Full requirement spec for a feature. No feature currently models :unsupported (a feature outright incompatible with a platform) — if one needs to, add | :unsupported to the platform value type here AND re-add the matching case clause in check_against/2.

incompatibility()

@type incompatibility() :: %{
  device: MobDev.Device.t(),
  feature: atom(),
  reason: String.t()
}

An incompatibility found by check_device/2.

platform_req()

@type platform_req() :: %{
  :abis => [String.t()],
  :min_sdk => non_neg_integer(),
  optional(:reason) => String.t()
}

Requirement spec for a single feature on a single platform.

Functions

base_requirements()

@spec base_requirements() :: feature_req()

Returns the base device requirements every Mob app inherits.

check_device(device, features)

@spec check_device(MobDev.Device.t(), [atom()]) :: :ok | {:error, [incompatibility()]}

Checks that device can run the given list of enabled features (plus the base Mob floor).

Returns :ok if every requirement is satisfied, or {:error, [incompatibility]} listing every reason the device is unsupported. We collect every reason rather than short-circuiting so the user sees the full picture, not a one-at-a-time game of whack-a-mole.

enabled_features(project_dir)

@spec enabled_features(Path.t()) :: [atom()]

Returns the list of features enabled in project_dir that have non-base device requirements.

Inferred from project artifacts (deps in mix.exs, generated source files), not from a flag — so a user can't accidentally bypass the validation by forgetting an option.

feature_requirements(arg1)

@spec feature_requirements(atom()) :: feature_req() | nil

Returns the device requirements for a specific feature.

Returns nil for unknown features (caller should treat as base-only).

format_error(issues)

@spec format_error([incompatibility()]) :: String.t()

Renders an {:error, [incompatibility]} result as the human-readable block printed by mix mob.deploy before exiting.

Groups by device so multi-feature failures on one device collapse into a single block, then re-displays the device summary line.