Skip to content

Commit

Permalink
Allow watchers to be anonymous functions
Browse files Browse the repository at this point in the history
  • Loading branch information
josevalim committed Jul 17, 2021
1 parent ee5fa77 commit e13a022
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 26 deletions.
9 changes: 1 addition & 8 deletions lib/phoenix/endpoint/supervisor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""
Expand Down
58 changes: 42 additions & 16 deletions lib/phoenix/endpoint/watcher.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
12 changes: 10 additions & 2 deletions test/phoenix/endpoint/watcher_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit e13a022

Please sign in to comment.