lib/mix/tasks/compile.phoenix.ex

defmodule Mix.Tasks.Compile.Phoenix do
  use Mix.Task
  @recursive true

  @moduledoc """
  Compiles Phoenix source files that support code reloading.

  If you are using Elixir v1.11+ or later, there is no longer
  a need to use this module as this functionality is now provided
  by Elixir. Just remember to update `__phoenix_recompile__?` to
  `__mix_recompile__?` in any module that may define it.
  """

  # TODO: Deprecate this module once we require Elixir v1.11+
  @mix_recompile Version.match?(System.version(), ">= 1.11.0")

  @doc false
  def run(_args) do
    {:ok, _} = Application.ensure_all_started(:phoenix)

    case touch() do
      [] -> {:noop, []}
      _ -> {:ok, []}
    end
  end

  @doc false
  def touch do
    Mix.Phoenix.modules()
    |> modules_for_recompilation
    |> modules_to_file_paths
    |> Stream.map(&touch_if_exists(&1))
    |> Stream.filter(&(&1 == :ok))
    |> Enum.to_list()
  end

  defp touch_if_exists(path) do
    :file.change_time(path, :calendar.local_time())
  end

  defp modules_for_recompilation(modules) do
    Stream.filter(modules, fn mod ->
      Code.ensure_loaded?(mod) and (phoenix_recompile?(mod) or mix_recompile?(mod))
    end)
  end

  defp phoenix_recompile?(mod) do
    function_exported?(mod, :__phoenix_recompile__?, 0) and mod.__phoenix_recompile__?()
  end

  if @mix_recompile do
    # Recompile is provided by Mix, we don't need to do anything
    defp mix_recompile?(_mod), do: false
  else
    defp mix_recompile?(mod) do
      function_exported?(mod, :__mix_recompile__?, 0) and mod.__mix_recompile__?()
    end
  end

  defp modules_to_file_paths(modules) do
    Stream.map(modules, fn mod -> mod.__info__(:compile)[:source] end)
  end
end