PageRenderTime 93ms CodeModel.GetById 40ms app.highlight 16ms RepoModel.GetById 34ms app.codeStats 0ms

/src/reloader.erl

http://github.com/basho/mochiweb
Erlang | 179 lines | 96 code | 26 blank | 57 comment | 3 complexity | 2ac99b87a181d9de5d70062d9a5d4aeb MD5 | raw file
  1%% @author Matthew Dempsky <matthew@mochimedia.com>
  2%% @copyright 2007 Mochi Media, Inc.
  3%%
  4%% Permission is hereby granted, free of charge, to any person obtaining a
  5%% copy of this software and associated documentation files (the "Software"),
  6%% to deal in the Software without restriction, including without limitation
  7%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
  8%% and/or sell copies of the Software, and to permit persons to whom the
  9%% Software is furnished to do so, subject to the following conditions:
 10%%
 11%% The above copyright notice and this permission notice shall be included in
 12%% all copies or substantial portions of the Software.
 13%%
 14%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 15%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 16%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 17%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 18%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 19%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 20%% DEALINGS IN THE SOFTWARE.
 21
 22%% @doc Erlang module for automatically reloading modified modules
 23%% during development.
 24
 25-module(reloader).
 26-author("Matthew Dempsky <matthew@mochimedia.com>").
 27
 28-include_lib("kernel/include/file.hrl").
 29
 30-behaviour(gen_server).
 31-export([start/0, start_link/0]).
 32-export([stop/0]).
 33-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
 34-export([all_changed/0]).
 35-export([is_changed/1]).
 36-export([reload_modules/1]).
 37-record(state, {last, tref}).
 38
 39%% External API
 40
 41%% @spec start() -> ServerRet
 42%% @doc Start the reloader.
 43start() ->
 44    gen_server:start({local, ?MODULE}, ?MODULE, [], []).
 45
 46%% @spec start_link() -> ServerRet
 47%% @doc Start the reloader.
 48start_link() ->
 49    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
 50
 51%% @spec stop() -> ok
 52%% @doc Stop the reloader.
 53stop() ->
 54    gen_server:call(?MODULE, stop).
 55
 56%% gen_server callbacks
 57
 58%% @spec init([]) -> {ok, State}
 59%% @doc gen_server init, opens the server in an initial state.
 60init([]) ->
 61    {ok, TRef} = timer:send_interval(timer:seconds(1), doit),
 62    {ok, #state{last = stamp(), tref = TRef}}.
 63
 64%% @spec handle_call(Args, From, State) -> tuple()
 65%% @doc gen_server callback.
 66handle_call(stop, _From, State) ->
 67    {stop, shutdown, stopped, State};
 68handle_call(_Req, _From, State) ->
 69    {reply, {error, badrequest}, State}.
 70
 71%% @spec handle_cast(Cast, State) -> tuple()
 72%% @doc gen_server callback.
 73handle_cast(_Req, State) ->
 74    {noreply, State}.
 75
 76%% @spec handle_info(Info, State) -> tuple()
 77%% @doc gen_server callback.
 78handle_info(doit, State) ->
 79    Now = stamp(),
 80    _ = doit(State#state.last, Now),
 81    {noreply, State#state{last = Now}};
 82handle_info(_Info, State) ->
 83    {noreply, State}.
 84
 85%% @spec terminate(Reason, State) -> ok
 86%% @doc gen_server termination callback.
 87terminate(_Reason, State) ->
 88    {ok, cancel} = timer:cancel(State#state.tref),
 89    ok.
 90
 91
 92%% @spec code_change(_OldVsn, State, _Extra) -> State
 93%% @doc gen_server code_change callback (trivial).
 94code_change(_Vsn, State, _Extra) ->
 95    {ok, State}.
 96
 97%% @spec reload_modules([atom()]) -> [{module, atom()} | {error, term()}]
 98%% @doc code:purge/1 and code:load_file/1 the given list of modules in order,
 99%%      return the results of code:load_file/1.
100reload_modules(Modules) ->
101    [begin code:purge(M), code:load_file(M) end || M <- Modules].
102
103%% @spec all_changed() -> [atom()]
104%% @doc Return a list of beam modules that have changed.
105all_changed() ->
106    [M || {M, Fn} <- code:all_loaded(), is_list(Fn), is_changed(M)].
107
108%% @spec is_changed(atom()) -> boolean()
109%% @doc true if the loaded module is a beam with a vsn attribute
110%%      and does not match the on-disk beam file, returns false otherwise.
111is_changed(M) ->
112    try
113        module_vsn(M:module_info()) =/= module_vsn(code:get_object_code(M))
114    catch _:_ ->
115            false
116    end.
117
118%% Internal API
119
120module_vsn({M, Beam, _Fn}) ->
121    {ok, {M, Vsn}} = beam_lib:version(Beam),
122    Vsn;
123module_vsn(L) when is_list(L) ->
124    {_, Attrs} = lists:keyfind(attributes, 1, L),
125    {_, Vsn} = lists:keyfind(vsn, 1, Attrs),
126    Vsn.
127
128doit(From, To) ->
129    [case file:read_file_info(Filename) of
130         {ok, #file_info{mtime = Mtime}} when Mtime >= From, Mtime < To ->
131             reload(Module);
132         {ok, _} ->
133             unmodified;
134         {error, enoent} ->
135             %% The Erlang compiler deletes existing .beam files if
136             %% recompiling fails.  Maybe it's worth spitting out a
137             %% warning here, but I'd want to limit it to just once.
138             gone;
139         {error, Reason} ->
140             io:format("Error reading ~s's file info: ~p~n",
141                       [Filename, Reason]),
142             error
143     end || {Module, Filename} <- code:all_loaded(), is_list(Filename)].
144
145reload(Module) ->
146    io:format("Reloading ~p ...", [Module]),
147    code:purge(Module),
148    case code:load_file(Module) of
149        {module, Module} ->
150            io:format(" ok.~n"),
151            case erlang:function_exported(Module, test, 0) of
152                true ->
153                    io:format(" - Calling ~p:test() ...", [Module]),
154                    case catch Module:test() of
155                        ok ->
156                            io:format(" ok.~n"),
157                            reload;
158                        Reason ->
159                            io:format(" fail: ~p.~n", [Reason]),
160                            reload_but_test_failed
161                    end;
162                false ->
163                    reload
164            end;
165        {error, Reason} ->
166            io:format(" fail: ~p.~n", [Reason]),
167            error
168    end.
169
170
171stamp() ->
172    erlang:localtime().
173
174%%
175%% Tests
176%%
177-ifdef(TEST).
178-include_lib("eunit/include/eunit.hrl").
179-endif.