lib/hex_solver.ex

defmodule HexSolver do
  @moduledoc """
  A version solver.
  """

  @type dependency() :: %{
          repo: repo(),
          name: package(),
          constraint: constraint(),
          optional: optional(),
          label: label()
        }
  @type locked() :: %{
          repo: repo(),
          name: package(),
          version: Version.t(),
          label: label()
        }
  @type repo() :: String.t() | nil
  @type package() :: String.t()
  @type label() :: String.t()
  @type optional() :: boolean()
  @type result() :: %{package() => {Version.t(), repo()}}
  @opaque constraint() :: HexSolver.Requirement.t()

  alias HexSolver.{Failure, Requirement, Solver}

  @doc """
  Runs the version solver.

  Takes a `HexSolver.Registry` implementation, a list of root dependencies, a list of locked
  package versions, and a list of packages that are overridden by the root dependencies.

  Locked dependencies are treated as optional dependencies with a single version as
  their constraint.

  The overrides are a set of labels. If a dependency with a matching label is declared the
  solver will ignore that dependency unless it's a root dependency.

  Returns a map of packages and their selected versions or a human readable explanation of
  why a solution could not be found.

  ## Options

    * `:ansi` - If `true` adds ANSI formatting to the failure message (Default: `false`)

  """
  @spec run(module(), [dependency()], [locked()], [label()], ansi: boolean()) ::
          {:ok, result()} | {:error, String.t()}
  def run(registry, dependencies, locked, overrides, opts \\ []) do
    case Solver.run(registry, dependencies, locked, overrides) do
      {:ok, solution} -> {:ok, Map.drop(solution, ["$root", "$lock"])}
      {:error, incompatibility} -> {:error, Failure.write(incompatibility, opts)}
    end
  end

  @doc """
  Parses or converts a SemVer version or Elixir version requirement to an internal solver constraint
  that can be returned by the `HexSolver.Registry` or passed to `HexSolver.run/4`.
  """
  @spec parse_constraint(String.t() | Version.t() | Version.Requirement.t()) ::
          {:ok, constraint()} | :error
  def parse_constraint(string) do
    Requirement.to_constraint(string)
  end

  @doc """
  Parses or converts a SemVer version or Elixir version requirement to an internal solver constraint
  that can be returned by the `HexSolver.Registry` or passed to `HexSolver.run/4`.
  """
  @spec parse_constraint!(String.t() | Version.t() | Version.Requirement.t()) :: constraint()
  def parse_constraint!(string) do
    Requirement.to_constraint!(string)
  end
end