Current section

Files

Jump to
nerves lib nerves package.ex
Raw

lib/nerves/package.ex

# SPDX-FileCopyrightText: 2016 Justin Schneck
# SPDX-FileCopyrightText: 2017 Frank Hunleth
# SPDX-FileCopyrightText: 2017 Greg Mefford
# SPDX-FileCopyrightText: 2022 Jon Carstens
#
# SPDX-License-Identifier: Apache-2.0
#
defmodule Nerves.Package do
@moduledoc """
Defines a Nerves package struct and helper functions.
A Nerves package is a Mix application that defines the configuration for a
Nerves system or Nerves toolchain. For more details, see the Nerves
[system documentation](https://nerves.hexdocs.pm/systems.html#package-configuration)
"""
alias Nerves.Artifact
defstruct app: nil,
path: nil,
dep: nil,
env: [],
type: nil,
version: nil,
platform: nil,
build_runner: nil,
compilers: [],
dep_opts: [],
config: []
@type t :: %__MODULE__{
app: atom,
path: binary,
env: %{String.t() => String.t()},
type:
:system
| :package
| :toolchain
| :system_platform
| :toolchain_platform,
dep:
:project
| :path
| :hex
| :git,
platform: atom,
build_runner: {module(), Keyword.t()},
compilers: [atom],
dep_opts: Keyword.t(),
version: Version.t(),
config: Keyword.t()
}
@required [:type, :version, :platform]
@doc """
Loads the package config and parses it into a `%Package{}`
"""
@spec load_config({app :: atom, path :: String.t()}) :: Nerves.Package.t()
def load_config({app, path}) do
config = config(app, path)
version = config[:version]
type = config[:nerves_package][:type]
compilers = config[:compilers] || Mix.compilers()
env = Map.new(config[:nerves_package][:env] || %{})
if !type do
Mix.shell().error(
"The Nerves package #{app} does not define a type.\n\n" <>
"Verify that the key exists.\n"
)
exit({:shutdown, 1})
end
platform = config[:nerves_package][:platform]
build_runner = Artifact.build_runner(config)
config = Enum.reject(config[:nerves_package], fn {k, _v} -> k in @required end)
%__MODULE__{
app: app,
type: type,
env: env,
platform: platform,
build_runner: build_runner,
compilers: compilers,
dep_opts: nerves_options_on_dep(app),
dep: dep_type(app),
path: path,
version: version,
config: config
}
end
# Return the nerves options that the user specified on the dependency
# E.g.,
# {:nerves_system_rpi4, "~> 1.28", runtime: false, targets: :rpi4, nerves: [compile: true]}
# ^^^^^^^^^^^^^^^
defp nerves_options_on_dep(app) do
load_env_deps()
|> Enum.find(%{}, &(&1.app == app))
|> Map.get(:opts, [])
|> Keyword.get(:nerves, [])
end
if Version.match?(System.version(), ">= 1.16.0") do
defp load_env_deps() do
Mix.Dep.Converger.converge(env: Mix.env())
end
else
defp load_env_deps() do
# deprecated in Elixir >= 1.16.0
Mix.Dep.load_on_environment(env: Mix.env())
end
end
@doc """
Starts an interactive shell with the working directory set
to the package path
"""
@spec shell(Nerves.Package.t() | nil) :: :ok
def shell(nil) do
Mix.raise("Package is not loaded in your Nerves Environment.")
end
def shell(%{platform: nil, app: app}) do
Mix.raise("Cannot start shell for #{app}")
end
def shell(pkg) do
pkg.build_runner.shell(pkg)
end
@doc """
Get Mix.Project config for an application
"""
@spec config(Application.app(), Path.t()) :: Keyword.t()
def config(app, path) do
if app == Mix.Project.config()[:app] do
Mix.Project.config()
else
Mix.Project.in_project(app, path, fn _mod ->
Mix.Project.config()
end)
end
end
defp dep_type(pkg) do
deps_paths = Mix.Project.deps_paths()
case Map.get(deps_paths, pkg) do
nil ->
:project
path ->
deps_path =
File.cwd!()
|> Path.join(Mix.Project.config()[:deps_path])
|> Path.expand()
if String.starts_with?(path, deps_path) do
:hex
else
:path
end
end
end
end