What precompiled does not do yet, or does not protect against. Each
item says what happens and what to do about it. Apart from the first item
under Matching, none of them can make Mix use a wrong build: the worst
case is a dependency that compiles anyway.
Matching
Your build environment is not recorded. Builds are made in a clean
environment, and no build records the environment variables, PATH or
tools of the machine it would replace. A dependency whose compile-time
output depends on them (a Makefile, c_src, an env read in mix.exs or
inside a compile-time function) can be served a build made without your
values. Variables set in your shell and a global
Mix.install(deps, system_env: ...) count the same. Rule 8 refuses only
the case it can see: an env read at module level in lib/.
Fix: list such a dependency in :except.
Trust
One key signs everything. The portal holds one online key for both
the root and the batches. Whoever controls the portal host could ship code
to every project and device that uses the library, until a library
release ships a new root.
Status: proposals to split the roles are in the compatibility
repository's docs/PRECOMPILED_SIGNING_PROPOSALS.md. See
Who you are trusting today.
A fresh machine can't spot an older batch. With no trust state yet
(a fresh install, or a new :home), there is no stored sequence to
compare against. An older batch, issued within the last 24 hours, still
passes the freshness checks.
Impact: small: the batch must still be signed and less than a day old.
A mirror can hold back a root rotation. A root is only adopted when
the source serves it, so a mirror that withholds newer roots keeps a
fresh :home on the root this library ships.
Impact: that machine misses newer builds signed under the new root;
nothing wrong installs.
_build is trusted. The provenance file (.mix/precompiled.json in a
dependency's build dir) is how an installed build is told apart from a
compiled one. Anyone who can write _build can forge it, but they could
already change any compiled code there.
Mix.install
These options of Mix.install/2 are not applied to the precompiled step:
| Option | What happens | Result |
|---|---|---|
:lockfile | Passed to Mix, not used for the check | Mix may re-resolve and recompile what was just installed |
:config, :config_path | Not applied before the check | A dependency whose compile-time config they change misses, or is placed and then recompiled by Mix |
MIX_INSTALL_RESTORE_PROJECT_DIR | Ignored | — |
In every case the dependency ends up compiled, never wrong.
Nerves
Released nerves serves only pure_elixir builds. Nerves 1.x has no
Nerves.build_plan/0, so the system isn't known. Use the fork for system
builds; see Nerves.
System builds may have stripped beams. Protocols they define are not consolidated, and Dialyzer has no types for them. A run says how many.
The fork's hook does its own setup under precompiled.* tasks. The
library does nothing under help, clean and precompiled.*, but the
fork's nerves.init still runs its setup first. Fix: the fork's skip
list should name precompiled.* too (a change on the fork's side).
On a device
See also Not supported in the device guide.
Offline bundles last 24 hours for now. A bundle that is good for
longer needs a signed offline.json, and the root this library ships has
no offline role yet.
Until then: build the bundle the same day you install it.
An unsynced clock weakens a bundle's expiry check. A device without
NTP judges a bundle's batch by its own clock, which only has to be later
than this library's build time. A batch that has really expired can still
pass. The batch must still be signed and not older than one the device
already saw. This goes away once bundles carry a signed offline.json,
which needs no clock.
Open handles are not "in use". Precompiled.uninstall/2 sees code that
is running, not resources that are open, like a GPIO handle.
Fix: close them before you uninstall.
Upgrading from an older version
Older installs are not rechecked. Builds installed by a version of this
library from before provenance files carry none, so they are not
rechecked.
Fix: run mix deps.clean --build, or start from a clean _build. For
Precompiled.Host.install/2, use a forced install.