diff --git a/lib/phoenix/endpoint/supervisor.ex b/lib/phoenix/endpoint/supervisor.ex index b25895d9e0..89379e5d3b 100644 --- a/lib/phoenix/endpoint/supervisor.ex +++ b/lib/phoenix/endpoint/supervisor.ex @@ -203,19 +203,12 @@ defmodule Phoenix.Endpoint.Supervisor do defp watcher_children(_mod, conf, server?) do if server? do - Enum.map(conf[:watchers], fn {cmd, args} -> - {Phoenix.Endpoint.Watcher, watcher_args(cmd, args)} - end) + Enum.map(conf[:watchers], &{Phoenix.Endpoint.Watcher, &1}) else [] end end - defp watcher_args(cmd, cmd_args) do - {args, opts} = Enum.split_while(cmd_args, &is_binary(&1)) - {cmd, args, opts} - end - @doc """ The endpoint configuration used at compile time. """ diff --git a/lib/phoenix/endpoint/watcher.ex b/lib/phoenix/endpoint/watcher.ex index 5e9cb850f2..a85557285f 100644 --- a/lib/phoenix/endpoint/watcher.ex +++ b/lib/phoenix/endpoint/watcher.ex @@ -10,25 +10,44 @@ defmodule Phoenix.Endpoint.Watcher do } end - def start_link({cmd, args, opts}) do - Task.start_link(__MODULE__, :watch, [to_string(cmd), args, opts]) + def start_link({cmd, args}) do + Task.start_link(__MODULE__, :watch, [to_string(cmd), args]) end - def watch(cmd, args, opts) do - merged_opts = Keyword.merge( - [into: IO.stream(:stdio, :line), stderr_to_stdout: true], opts) - :ok = validate(cmd, args, merged_opts) + def watch(_cmd, args) when is_function(args, 0) do + try do + args.() + catch + kind, reason -> + # The function returned a non-zero exit code. + # Sleep for a couple seconds before exiting to + # ensure this doesn't hit the supervisor's + # max_restarts/max_seconds limit. + Process.sleep(2000) + :erlang.raise(kind, reason, __STACKTRACE__) + end + end + + def watch(cmd, args) when is_list(args) do + {args, opts} = Enum.split_while(args, &is_binary(&1)) + opts = Keyword.merge([into: IO.stream(:stdio, :line), stderr_to_stdout: true], opts) + :ok = validate(cmd, args, opts) try do - System.cmd(cmd, args, merged_opts) + System.cmd(cmd, args, opts) catch :error, :enoent -> relative = Path.relative_to_cwd(cmd) - Logger.error "Could not start watcher #{inspect relative} from #{inspect cd(merged_opts)}, executable does not exist" + + Logger.error( + "Could not start watcher #{inspect(relative)} from #{inspect(cd(opts))}, executable does not exist" + ) + exit(:shutdown) else {_, 0} -> :ok + {_, _} -> # System.cmd returned a non-zero exit code # sleep for a couple seconds before exiting to ensure this doesn't @@ -40,23 +59,30 @@ defmodule Phoenix.Endpoint.Watcher do # We specially handle Node.js to make sure we # provide a good getting started experience. - defp validate("node", [script|_], merged_opts) do + defp validate("node", [script | _], merged_opts) do script_path = Path.expand(script, cd(merged_opts)) cond do !System.find_executable("node") -> - Logger.error "Could not start watcher because \"node\" is not available. Your Phoenix " <> - "application is still running, however assets won't be compiled. " <> - "You may fix this by installing \"node\" and then running \"npm install\" inside the \"assets\" directory." + Logger.error( + "Could not start watcher because \"node\" is not available. Your Phoenix " <> + "application is still running, however assets won't be compiled. " <> + "You may fix this by installing \"node\" and then running \"npm install\" inside the \"assets\" directory." + ) + exit(:shutdown) not File.exists?(script_path) -> - Logger.error "Could not start Node.js watcher because script #{inspect script_path} does not " <> - "exist. Your Phoenix application is still running, however assets " <> - "won't be compiled. You may fix this by running \"npm install\" inside the \"assets\" directory." + Logger.error( + "Could not start Node.js watcher because script #{inspect(script_path)} does not " <> + "exist. Your Phoenix application is still running, however assets " <> + "won't be compiled. You may fix this by running \"npm install\" inside the \"assets\" directory." + ) + exit(:shutdown) - true -> :ok + true -> + :ok end end diff --git a/test/phoenix/endpoint/watcher_test.exs b/test/phoenix/endpoint/watcher_test.exs index e1a85aefb2..258d2770e4 100644 --- a/test/phoenix/endpoint/watcher_test.exs +++ b/test/phoenix/endpoint/watcher_test.exs @@ -4,9 +4,17 @@ defmodule Phoenix.Endpoint.WatcherTest do alias Phoenix.Endpoint.Watcher import ExUnit.CaptureIO - test "starts watching and writes to stdio" do + test "starts watching and writes to stdio with args" do assert capture_io(fn -> - {:ok, pid} = Watcher.start_link({"echo", ["hello"], cd: File.cwd!()}) + {:ok, pid} = Watcher.start_link({"echo", ["hello", cd: File.cwd!()]}) + ref = Process.monitor(pid) + assert_receive {:DOWN, ^ref, :process, ^pid, :normal}, 1000 + end) == "hello\n" + end + + test "starts watching and writes to stdio with fun" do + assert capture_io(fn -> + {:ok, pid} = Watcher.start_link({"echo", fn -> IO.puts("hello") end}) ref = Process.monitor(pid) assert_receive {:DOWN, ^ref, :process, ^pid, :normal}, 1000 end) == "hello\n"