Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
bjnortier committed Sep 24, 2010
0 parents commit 51c6364
Show file tree
Hide file tree
Showing 7 changed files with 227 additions and 0 deletions.
1 change: 1 addition & 0 deletions README
@@ -0,0 +1 @@
application:start(reloader)
8 changes: 8 additions & 0 deletions ebin/reloader.app
@@ -0,0 +1,8 @@
{application,reloader,
[{description,[]},
{vsn,"0.1"},
{registered,[]},
{applications,[kernel,stdlib]},
{mod,{reloader_app,[]}},
{env,[]},
{modules,[reloader,reloader_app,reloader_sup]}]}.
Binary file added rebar
Binary file not shown.
12 changes: 12 additions & 0 deletions src/reloader.app.src
@@ -0,0 +1,12 @@
{application, reloader,
[
{description, ""},
{vsn, "0.1"},
{registered, []},
{applications, [
kernel,
stdlib
]},
{mod, { reloader_app, []}},
{env, []}
]}.
161 changes: 161 additions & 0 deletions src/reloader.erl
@@ -0,0 +1,161 @@
%% @copyright 2007 Mochi Media, Inc.
%% @author Matthew Dempsky <matthew@mochimedia.com>
%%
%% @doc Erlang module for automatically reloading modified modules
%% during development.

-module(reloader).
-author("Matthew Dempsky <matthew@mochimedia.com>").

-include_lib("kernel/include/file.hrl").

-behaviour(gen_server).
-export([start/0, start_link/0]).
-export([stop/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-export([all_changed/0]).
-export([is_changed/1]).
-export([reload_modules/1]).
-record(state, {last, tref}).

%% External API

%% @spec start() -> ServerRet
%% @doc Start the reloader.
start() ->
gen_server:start({local, ?MODULE}, ?MODULE, [], []).

%% @spec start_link() -> ServerRet
%% @doc Start the reloader.
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

%% @spec stop() -> ok
%% @doc Stop the reloader.
stop() ->
gen_server:call(?MODULE, stop).

%% gen_server callbacks

%% @spec init([]) -> {ok, State}
%% @doc gen_server init, opens the server in an initial state.
init([]) ->
{ok, TRef} = timer:send_interval(timer:seconds(1), doit),
{ok, #state{last = stamp(), tref = TRef}}.

%% @spec handle_call(Args, From, State) -> tuple()
%% @doc gen_server callback.
handle_call(stop, _From, State) ->
{stop, shutdown, stopped, State};
handle_call(_Req, _From, State) ->
{reply, {error, badrequest}, State}.

%% @spec handle_cast(Cast, State) -> tuple()
%% @doc gen_server callback.
handle_cast(_Req, State) ->
{noreply, State}.

%% @spec handle_info(Info, State) -> tuple()
%% @doc gen_server callback.
handle_info(doit, State) ->
Now = stamp(),
doit(State#state.last, Now),
{noreply, State#state{last = Now}};
handle_info(_Info, State) ->
{noreply, State}.

%% @spec terminate(Reason, State) -> ok
%% @doc gen_server termination callback.
terminate(_Reason, State) ->
{ok, cancel} = timer:cancel(State#state.tref),
ok.


%% @spec code_change(_OldVsn, State, _Extra) -> State
%% @doc gen_server code_change callback (trivial).
code_change(_Vsn, State, _Extra) ->
{ok, State}.

%% @spec reload_modules([atom()]) -> [{module, atom()} | {error, term()}]
%% @doc code:purge/1 and code:load_file/1 the given list of modules in order,
%% return the results of code:load_file/1.
reload_modules(Modules) ->
[begin code:purge(M), code:load_file(M) end || M <- Modules].

%% @spec all_changed() -> [atom()]
%% @doc Return a list of beam modules that have changed.
all_changed() ->
[M || {M, Fn} <- code:all_loaded(), is_list(Fn), is_changed(M)].

%% @spec is_changed(atom()) -> boolean()
%% @doc true if the loaded module is a beam with a vsn attribute
%% and does not match the on-disk beam file, returns false otherwise.
is_changed(M) ->
try
module_vsn(M:module_info()) =/= module_vsn(code:get_object_code(M))
catch _:_ ->
false
end.

%% Internal API

module_vsn({M, Beam, _Fn}) ->
{ok, {M, Vsn}} = beam_lib:version(Beam),
Vsn;
module_vsn(L) when is_list(L) ->
{_, Attrs} = lists:keyfind(attributes, 1, L),
{_, Vsn} = lists:keyfind(vsn, 1, Attrs),
Vsn.

doit(From, To) ->
[case file:read_file_info(Filename) of
{ok, #file_info{mtime = Mtime}} when Mtime >= From, Mtime < To ->
reload(Module);
{ok, _} ->
unmodified;
{error, enoent} ->
%% The Erlang compiler deletes existing .beam files if
%% recompiling fails. Maybe it's worth spitting out a
%% warning here, but I'd want to limit it to just once.
gone;
{error, Reason} ->
io:format("Error reading ~s's file info: ~p~n",
[Filename, Reason]),
error
end || {Module, Filename} <- code:all_loaded(), is_list(Filename)].

reload(Module) ->
io:format("Reloading ~p ...", [Module]),
code:purge(Module),
case code:load_file(Module) of
{module, Module} ->
io:format(" ok.~n"),
case erlang:function_exported(Module, test, 0) of
true ->
io:format(" - Calling ~p:test() ...", [Module]),
case catch Module:test() of
ok ->
io:format(" ok.~n"),
reload;
Reason ->
io:format(" fail: ~p.~n", [Reason]),
reload_but_test_failed
end;
false ->
reload
end;
{error, Reason} ->
io:format(" fail: ~p.~n", [Reason]),
error
end.


stamp() ->
erlang:localtime().

%%
%% Tests
%%
-include_lib("eunit/include/eunit.hrl").
-ifdef(TEST).
-endif.
16 changes: 16 additions & 0 deletions src/reloader_app.erl
@@ -0,0 +1,16 @@
-module(reloader_app).

-behaviour(application).

%% Application callbacks
-export([start/2, stop/1]).

%% ===================================================================
%% Application callbacks
%% ===================================================================

start(_StartType, _StartArgs) ->
reloader_sup:start_link().

stop(_State) ->
ok.
29 changes: 29 additions & 0 deletions src/reloader_sup.erl
@@ -0,0 +1,29 @@

-module(reloader_sup).

-behaviour(supervisor).

%% API
-export([start_link/0]).

%% Supervisor callbacks
-export([init/1]).

%% Helper macro for declaring children of supervisor
-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).

%% ===================================================================
%% API functions
%% ===================================================================

start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).

%% ===================================================================
%% Supervisor callbacks
%% ===================================================================

init([]) ->
Reloader = ?CHILD(reloader, worker),
{ok, { {one_for_one, 5, 10}, [Reloader]} }.

0 comments on commit 51c6364

Please sign in to comment.