Packages
nerves
1.4.3
1.15.0
1.14.3
1.14.2
1.14.1
1.14.0
1.13.2
1.13.1
1.13.0
1.12.0
1.11.3
1.11.2
1.11.1
1.11.0
1.10.5
1.10.4
1.10.3
1.10.2
1.10.1
1.10.0
1.9.3
1.9.2
retired
1.9.1
1.9.0
1.8.0
1.7.17
1.7.16
1.7.15
1.7.14
retired
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
retired
1.7.1
retired
1.7.0
retired
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0
1.2.1
1.2.0
1.1.1
1.1.0
1.0.1
1.0.0
1.0.0-rc.2
1.0.0-rc.1
1.0.0-rc.0
0.11.0
0.10.1
0.10.0
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.3
0.8.2
0.8.1
0.8.0
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.4.0-rc.0
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.0
Craft and deploy bulletproof embedded software
Current section
Files
Jump to
Current section
Files
lib/nerves.ex
defmodule Nerves do
use Mix.Releases.Plugin
alias Mix.Releases.{Release, Shell}
# No leading '/' here since this is passed to mksquashfs and it
# doesn't like the leading slash.
@target_release_path "srv/erlang"
def before_assembly(release, _opts) do
if nerves_env_loaded?() do
vm_args = Map.get(release.profile, :vm_args) || "rel/vm.args"
plugins = order_plugins(release.profile.plugins)
profile =
release.profile
|> Map.put(:dev_mode, false)
|> Map.put(:include_src, false)
|> Map.put(:include_erts, System.get_env("ERL_LIB_DIR"))
|> Map.put(:vm_args, vm_args)
|> Map.put(:plugins, plugins)
%{release | profile: profile}
else
release
end
end
# After assembling the release, generate a file that sets the filesystem order
# priority
def after_assembly(%Release{} = release, _opts) do
if nerves_env_loaded?() do
bootfile_name = Application.get_env(:nerves, :firmware)[:bootfile] || "shoehorn.boot"
bootfile_path =
Path.join([release.profile.output_dir, "releases", release.version, bootfile_name])
case File.read(bootfile_path) do
{:ok, bootfile} ->
write_rootfs_priorities(release, bootfile)
_ ->
Shell.warn("""
Unable to load bootfile: #{inspect(bootfile_path)}
Skipping rootfs priority file generation
""")
end
end
release
end
def before_package(%Release{} = release, _opts) do
release
end
def after_package(%Release{} = release, _opts) do
release
end
def after_cleanup(_args, _opts) do
:noop
end
def version, do: unquote(Mix.Project.config()[:version])
def elixir_version, do: unquote(System.version())
def nerves_env_loaded? do
System.get_env("NERVES_ENV_BOOTSTRAP") != nil
end
defp write_rootfs_priorities(release, bootfile) do
host_release_path = release.profile.output_dir
target_release_path = @target_release_path
applications = release.applications
{:script, _, boot_script} = :erlang.binary_to_term(bootfile)
target_beam_files = target_beam_files(boot_script, host_release_path, target_release_path)
target_app_files = target_app_files(applications, target_release_path)
target_priv_dirs = target_priv_dirs(applications, target_release_path)
priorities =
(target_beam_files ++ target_app_files ++ target_priv_dirs)
|> List.flatten()
|> Enum.zip(32_000..1_000)
|> Enum.map(fn {file, priority} ->
file <> " " <> to_string(priority)
end)
|> Enum.join("\n")
build_path = Path.join([Mix.Project.build_path(), "nerves"])
File.mkdir_p(build_path)
Path.join(build_path, "rootfs.priorities")
|> File.write(priorities)
end
defp rel_paths(paths) do
paths
|> Enum.map(&to_string/1)
|> Enum.map(&Path.split/1)
|> Enum.map(fn [_root | path] ->
Path.join(path)
end)
end
defp target_beam_files(boot_script, host_release_path, target_release_path) do
{_, loaded} =
Enum.reduce(boot_script, {nil, []}, fn
{:path, paths}, {_, loaded} ->
{rel_paths(paths), loaded}
{:primLoad, files}, {paths, loaded} ->
load =
Enum.reduce(paths, [], fn path, loaded ->
load =
Enum.reduce(files, [], fn file, loaded ->
filename = to_string(file) <> ".beam"
path = Path.join([path, filename])
if File.exists?(Path.join([host_release_path, path])) do
[Path.join([target_release_path, path]) | loaded]
else
loaded
end
end)
loaded ++ load
end)
{paths, [load | loaded]}
_, acc ->
acc
end)
loaded
|> Enum.reverse()
|> List.flatten()
end
defp target_app_files(applications, target_release_path) do
Enum.reduce(applications, [], fn %{name: name, vsn: vsn, path: path}, app_files ->
app_name = to_string(name)
host_path = Path.join([path, "ebin", app_name <> ".app"])
if File.exists?(host_path) do
app_file_path =
Path.join([
target_release_path,
"lib",
app_name <> "-" <> to_string(vsn),
"ebin",
app_name <> ".app"
])
[app_file_path | app_files]
else
app_files
end
end)
end
defp target_priv_dirs(applications, target_release_path) do
Enum.reduce(applications, [], fn %{name: name, vsn: vsn, path: path}, priv_dirs ->
app_name = to_string(name)
host_priv_dir = Path.join(path, "priv")
if File.dir?(host_priv_dir) do
priv_dir =
Path.join([target_release_path, "lib", app_name <> "-" <> to_string(vsn), "priv"])
[priv_dir | priv_dirs]
else
priv_dirs
end
end)
end
# Make sure the Shoehorn plugin executes before the Nerves plugin
defp order_plugins(plugins) do
if Enum.find(plugins, &shoehorn_plugin/1) != nil do
shoehorn_idx = Enum.find_index(plugins, &shoehorn_plugin/1)
nerves_idx = Enum.find_index(plugins, &nerves_plugin/1)
if nerves_idx < shoehorn_idx do
plugins
|> List.replace_at(nerves_idx, Enum.find(plugins, &shoehorn_plugin/1))
|> List.replace_at(shoehorn_idx, Enum.find(plugins, &nerves_plugin/1))
else
plugins
end
else
plugins
end
end
defp shoehorn_plugin({Shoehorn, _}), do: true
defp shoehorn_plugin({_, _}), do: false
defp nerves_plugin({Nerves, _}), do: true
defp nerves_plugin({_, _}), do: false
end