Notifications after phoenix auto compiles

I added a compiler (pasted below) to the end of the list of compilers in mix.exs and it works for regular compilation, like running recompile in iex or when the app starts but it doesn’t run when phoenix does a live reload. Any idea how I can make that work?

defmodule Mix.Tasks.Compile.Notifications do
  use Mix.Task

  def run(_args) do
    Task.start(fn ->
      System.cmd("afplay", ["/Applications/Slack.app/Contents/Resources/confirm_delivery.mp3"])
    end)

    System.cmd("noti", ["-t", "Compile complete", "-m", "Fuck yeah"])

    IO.puts "Compile complete."

    :ok
  end
end
1 Like

To anyone googling for something similar, I tracked this down with the help of @bryanjos. You have to add the compiler to the list of reloadable_compilers in config/dev.exs. This gets called multiple times when you’re refreshing a page so I updated the compiler to only send notifications every 10 seconds using a GenServer for the hell of it. Hope this helps someone.

defmodule Mix.Tasks.Compile.Notifications do
  @moduledoc """
  Send notifications when compiling is done

  Uses a GenServer to limit the number of notifications it sends to one every
  @notification_timeout_ms milliseconds. This is because phoenix checks if any
  files need to be compiled on every request.

  Setup:
  * Put this in lib/mix/tasks/compile
  * add :notifications to the end of the list of compilers in mix.exs
  * add :notifications to the end of the list of reloadable_compilers in config/dev.exs
  * install noti if you want system notifications
  """

  use Mix.Task
  use GenServer

  @notification_timeout_ms 10_000

  @doc """
  This is called when the compiler runs
  """
  def run(_args) do
    case GenServer.start(__MODULE__, [], name: __MODULE__) do
      {:ok, pid} ->
        send(pid, :queue_notification)

      {:error, {:already_started, pid}} ->
        send(pid, :queue_notification)

      error ->
        IO.inspect error
        IO.puts("Error starting compiler notifications")
    end
    IO.puts "Compile complete."
    :ok
  end

  def init(args) do
    Process.send_after(self(), :send_notifications, @notification_timeout_ms)

    {:ok, args}
  end

  # Sets the state to :ok meaning it should send notifications
  def handle_info(:queue_notification, _state) do
    {:noreply, :ok}
  end

  # when the state is nil, notifications should not be sent
  def handle_info(:send_notifications, nil) do
    Process.send_after(self(), :send_notifications, @notification_timeout_ms)

    {:noreply, nil}
  end

  # send notifications then set the state to nil
  def handle_info(:send_notifications, :ok) do
    # this is in a task because afplay blocks for about 1.1 seconds
    Task.start(fn ->
      System.cmd("afplay", ["/Applications/Slack.app/Contents/Resources/confirm_delivery.mp3"])
    end)

    System.cmd("noti", ["-t", "Compile complete", "-m", "Fuck yeah"])


    Process.send_after(self(), :send_notifications, @notification_timeout_ms)

    {:noreply, nil}
  end
end
1 Like