MobDev.AppConfig (mob_dev v0.7.8)

Copy Markdown View Source

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.

Summary

Types

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

Functions

Compiles config into the mob_app_config module's BEAM bytes.

The generated module's name.

Evaluates the project config for the device.

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

Types

skipped()

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

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

Functions

compile(config, meta \\ [])

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

@spec module() :: atom()

The generated module's name.

read(opts \\ [])

@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!(ebin \\ Mix.Project.compile_path(), opts \\ [])

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