lib/mix/task/monorepo.export.ex

defmodule Mix.Tasks.Monorepo.Export do
  @moduledoc """
  Takes all the dependenceis of the project and exports as a .ez files.

      $ mix monorepo.export

  The export is always done for a combination of lockfile, arch, erlang/elixir
  vsns such that for every update in dependencies, versions or similar the
  archives must be rexported

  Only dependencies from hex are exported.
  """

  @shortdoc "Export all dependencies as archives."

  use Mix.Task

  @impl true
  def run(_args) do
    env = Mix.env()

    Mix.Task.run(:compile, [])

    if dist = Mix.Project.config()[:monodist] do
      dist = Path.expand(dist)
      dir = Path.join([dist, "#{Mix.env()}", target()])

      existing = Path.wildcard(Path.join([dist, "#{Mix.env()}", "**"]))

      deps = Mix.Dep.load_on_environment(only: env)
      lock = Mix.Dep.Lock.read()

      # Output the archives
      new =
        for %{app: app, status: status} <- deps do
          # Assume hex SCM manager
          {:ok, vsn} = status

          case lock[app] do
            nil ->
              # don't export archive not managed by hex
              nil

            lock ->
              {^app, ^vsn, chksum} = lock_meta(app, lock)
              output = Path.join([dir, "#{app}-#{vsn}-#{chksum}.ez"])
              input = Path.join([Mix.Project.build_path(), "lib", "#{app}"])

              # Only export if not already present
              if not File.exists?(output) do
                Mix.Task.rerun("archive.build", ["-i", input, "-o", output])
              end

              output
          end
        end

      stale = existing -- [dir | new]

      # Remove old distribution files
      for old <- stale do
        IO.write(:stderr, "remove stale archive #{old}")
        File.rm_rf!(old)
      end
    end

    :ok
  end

  @doc """
  Get the source checksum of from a lock
  """
  def lock_meta(app, {:hex, app, vsn, _checksum, _mngr, _deps, _repo, chksum}),
    do: {app, vsn, String.slice(chksum, 0, 7)}

  def lock_meta(app, {:git, _remote, ref, _opts}), do: {app, ref, ref}

  # {:git, "https://github.com/lafka/elixir-keycloak.git", "650500fbd72b55847be3d81deb4b66ceafa99770", [ref: "650500fbd72b55847be3d81deb4b66ceafa99770"]}

  def lock_meta(app, nil), do: {app, nil, "0000000"}

  @doc """
  Get runtime target
  """
  def target() do
    arch = :erlang.system_info(:system_architecture)
    elixir_vsn = System.version()
    erlang_vsn = System.otp_release()

    Enum.join([arch, erlang_vsn, elixir_vsn], "-")
  end
end