# `MobDev.AppConfig`
[🔗](https://github.com/genericjam/mob_dev/blob/master/lib/mob_dev/app_config.ex#L1)

Ships the project's application config to the device as the generated
Erlang module `mob_app_config`.

A Mob app boots from `<app>:start()` rather than an OTP release, so nothing
loads `config/*.exs` on the device and `Application.get_env/3` returns nil
for everything except `compile_env` values. mob_dev therefore evaluates the
config on the host and compiles it into a module:

    mob_app_config:config() :: [{App :: atom(), [{Key :: atom(), Value :: term()}]}]

`Mob.App.start/0` (mob 0.9.6 and later) loads it first thing and applies it
with `Application.put_all_env/2`.

## What goes in

  * `config/config.exs` (the project's `:config_path`), read with
    `Config.Reader` for the build's `Mix.env()` and `Mix.target()`, so its
    `import_config` and `config_env()`/`config_target()` branches resolve as
    they do on the host.
  * `config/runtime.exs` next to it, when present, merged on top.
    **It is evaluated on the host, at build time**, not on the device at
    boot: `System.get_env/1` there reads the developer's environment.
  * minus `:mob_dev`, which is host tooling configuration.

The config is embedded with `:erlang.term_to_binary/2` and decoded by
`config/0`, so values `Macro.escape/1` can't turn into literals (tuples of
references to modules, large nested maps, ...) survive. Values that can't
mean anything on another VM (local funs such as `fn` literals in a config
file, pids, ports, references, which includes compiled regexes) are dropped
per key with a warning; external funs (`&Mod.fun/1`) are kept.

## Where it goes

`write!/0` puts `mob_app_config.beam` into the app's own compile path, next
to `<app>.beam`. Every path that ships the app's BEAMs copies that
directory, so the module rides along with no per-platform step: the
filesystem and dist pushes of `mix mob.deploy` (through
`MobDev.HotPush.runtime_beam_dirs/0`), the iOS simulator and device builds,
and the Android and iOS release builds. The file is only rewritten when its
bytes change, so `mix mob.watch` doesn't push it on every save.

A config change reaches a running app on its next start: a hot push loads the
new module but does not re-apply it.

# `skipped`

```elixir
@type skipped() :: {atom(), atom(), String.t()}
```

`{app, key}` of a value dropped as non-portable, with the reason.

# `compile`

```elixir
@spec compile(
  [{atom(), keyword()}],
  keyword()
) :: binary()
```

Compiles `config` into the `mob_app_config` module's BEAM bytes.

Deterministic: the same config always produces the same bytes. `env` and
`target` are recorded as the `mob_app_config` module attribute so a device
can tell which build produced its config.

# `module`

```elixir
@spec module() :: atom()
```

The generated module's name.

# `read`

```elixir
@spec read(keyword()) :: {[{atom(), keyword()}], [skipped()]}
```

Evaluates the project config for the device.

Options: `:config_path` (default: the project's `:config_path`), `:env`
(default `Mix.env()`), `:target` (default `Mix.target()`).

Returns `{config, skipped}`; `skipped` lists the keys dropped because their
value is non-portable.

# `write!`

```elixir
@spec write!(
  Path.t(),
  keyword()
) :: Path.t()
```

Generates `mob_app_config.beam` into `ebin` (default: the project's compile
path) and returns its path. Warns about each dropped key, once per run
(`mix mob.watch` regenerates on every save).

The file is left untouched when its content would not change.

---

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