├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── package.head.exs ├── rebar ├── rebar.config └── src ├── active.app.src ├── active.erl ├── active_app.erl └── active_sup.erl /.gitignore: -------------------------------------------------------------------------------- 1 | ebin/ 2 | *.dump 3 | deps/ 4 | vmling 5 | .applist 6 | .rebar/ 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2013 Vladimir Kirillov 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ERL_FLAGS= +sbwt none +swct lazy +swt high 2 | 3 | run: 4 | ERL_LIBS=deps erl -pa ebin \ 5 | $(ERL_FLAGS) \ 6 | -eval '[ok = application:ensure_started(A, permanent) || A <- [erlfsmon,active]]' 7 | 8 | ifeq ($(shell uname), Darwin) 9 | WATCHER = fsevent_watch -F . 10 | else 11 | WATCHER = fanotify_watch -c 12 | endif 13 | 14 | sync: 15 | $(WATCHER) | env PERLIO=:raw perl -ne '/.*\t.*\t$$ENV{"PWD"}.*erl$$/ && print $$_."\0"' | tee /dev/stderr | xargs -0tn1 -I % rebar compile 16 | 17 | .PHONY: run 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## active 2 | 3 | Active is an Erlang application that triggers rebuilds according to source changes. 4 | Active is also a new [sync](https://github.com/rustyio/sync). 5 | 6 | The key features are: 7 | 8 | * `active` no longer hogs disk and cpu to check for changes. 9 | Instead, it uses [erlfsmon](https://github.com/proger/erlfsmon) to observe filesystem events. 10 | * `active` picks up new module files in your working directory 11 | 12 | ### Setting up 13 | 14 | Just add a line to `rebar.config`: 15 | 16 | ```erlang 17 | {active, ".*", {git, "git://github.com/proger/active", "HEAD"}} 18 | ``` 19 | 20 | And make sure you start it along in your release boot scripts or application startup scripts: 21 | 22 | ```sh 23 | ERL_LIBS=deps erl -pa ebin -config sys.config \ 24 | -eval '[ok = application:ensure_started(A, permanent) || A <- [erlfsmon,active]]' 25 | ``` 26 | 27 | That's it! 28 | -------------------------------------------------------------------------------- /package.head.exs: -------------------------------------------------------------------------------- 1 | Expm.Package.new(name: "active", description: "Active development for Erlang: rebuild and reload source/binary files while the VM is running", 2 | version: :head, keywords: ["Erlang", "sync", "reload", "active development"], 3 | maintainers: [[name: "Vladimir Kirillov", email: "proger@hackndev.com"]], 4 | repositories: [[github: "proger/active"]], 5 | dependencies: []) 6 | -------------------------------------------------------------------------------- /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/proger/active/15dad8d10f979259cc5b956b9c78a00e1f9ed2d7/rebar -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {deps, [ 2 | {erlfsmon, ".*", {git, "git://github.com/proger/erlfsmon", {tag, "v1.0"}}} 3 | ]}. 4 | -------------------------------------------------------------------------------- /src/active.app.src: -------------------------------------------------------------------------------- 1 | {application, active, 2 | [ 3 | {description, ""}, 4 | {vsn, git}, 5 | {registered, []}, 6 | {applications, [ 7 | kernel, 8 | stdlib, 9 | erlfsmon 10 | ]}, 11 | {mod, { active_app, []}}, 12 | {env, []} 13 | ]}. 14 | -------------------------------------------------------------------------------- /src/active.erl: -------------------------------------------------------------------------------- 1 | -module(active). 2 | -behaviour(gen_server). 3 | -define(SERVER, ?MODULE). 4 | 5 | %% ------------------------------------------------------------------ 6 | %% API Function Exports 7 | %% ------------------------------------------------------------------ 8 | 9 | -export([start_link/0]). 10 | 11 | %% ------------------------------------------------------------------ 12 | %% gen_server Function Exports 13 | %% ------------------------------------------------------------------ 14 | 15 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 16 | terminate/2, code_change/3]). 17 | 18 | -record(state, {last, root}). 19 | 20 | %% ------------------------------------------------------------------ 21 | %% API Function Definitions 22 | %% ------------------------------------------------------------------ 23 | 24 | start_link() -> 25 | gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). 26 | 27 | %% ------------------------------------------------------------------ 28 | %% gen_server Function Definitions 29 | %% ------------------------------------------------------------------ 30 | 31 | init([]) -> 32 | erlfsmon:subscribe(), 33 | erlang:process_flag(priority, low), 34 | 35 | {ok, #state{last=fresh, root=erlfsmon:path()}}. 36 | 37 | handle_call(_Request, _From, State) -> 38 | {reply, ok, State}. 39 | 40 | handle_cast(_Msg, State) -> 41 | {noreply, State}. 42 | 43 | handle_info({_Pid, {erlfsmon,file_event}, {Path, Flags}}, #state{root=Root} = State) -> 44 | Cur = path_shorten(filename:split(Root)), 45 | P = filename:split(Path), 46 | 47 | Result = case lists:prefix(Cur, P) of 48 | true -> 49 | Components = P -- Cur, 50 | %error_logger:info_msg("event: ~p ~p", [Components, Flags]), 51 | path_event(Components, Flags); 52 | false -> 53 | ok 54 | end, 55 | 56 | {noreply, State#state{last={event, Path, Flags, Result}}}; 57 | handle_info({load_ebin, Atom}, State) -> 58 | do_load_ebin(Atom), 59 | {noreply, State#state{last={do_load_ebin, Atom}}}; 60 | handle_info(Info, State) -> 61 | {noreply, State#state{last={unk, Info}}}. 62 | 63 | terminate(_Reason, _State) -> 64 | ok. 65 | 66 | code_change(_OldVsn, State, _Extra) -> 67 | {ok, State}. 68 | 69 | %% ------------------------------------------------------------------ 70 | %% Internal Function Definitions 71 | %% ------------------------------------------------------------------ 72 | 73 | path_event(C, [E|_Events]) when 74 | E =:= created; 75 | E =:= modified; 76 | E =:= renamed -> 77 | case path_filter(C) of 78 | true -> path_modified_event(C); 79 | false -> ignore 80 | end; 81 | path_event(C, [_E|Events]) -> 82 | path_event(C, Events); 83 | path_event(_, []) -> 84 | done. 85 | 86 | path_modified_event([P, Name|Px] = _Path) when P =:= "apps"; P =:= "deps" -> 87 | app_modified_event(Name, Px); 88 | 89 | path_modified_event(["ebin" = D|Px] = _Path) -> 90 | app_modified_event(toplevel_app(), [D|Px]); 91 | 92 | path_modified_event([File]) -> 93 | Tokens = string:tokens(File, "."), 94 | case Tokens of 95 | [Name, "beam"] -> do_load_ebin(list_to_atom(Name)); 96 | _ -> dont_care 97 | end; 98 | 99 | path_modified_event(_) -> 100 | %error_logger:warning_msg("active: unhandled path: ~p", [P]), 101 | dont_care. 102 | 103 | app_modified_event(_App, ["ebin", EName|_] = _Path) -> 104 | load_ebin(EName); 105 | app_modified_event(_App, _P) -> 106 | dont_care. 107 | %error_logger:warning_msg("active: app ~p; unhandled path: ~p", [App, P]). 108 | 109 | toplevel_app() -> lists:last(filename:split(filename:absname(""))). 110 | 111 | load_ebin(EName) -> 112 | Tokens = string:tokens(EName, "."), 113 | case Tokens of 114 | [Name, "beam"] -> 115 | do_load_ebin(list_to_atom(Name)); 116 | [Name, "bea#"] -> 117 | case monitor_handles_renames() of 118 | false -> 119 | erlang:send_after(500, ?SERVER, {load_ebin, list_to_atom(Name)}), 120 | delayed; 121 | true -> 122 | ignored 123 | end; 124 | %[Name, Smth] -> ok; 125 | _ -> 126 | error_logger:warning_msg("load_ebin: unknown ebin file: ~p", [EName]), 127 | ok 128 | end. 129 | 130 | do_load_ebin(Module) -> 131 | {Module, Binary, Filename} = code:get_object_code(Module), 132 | code:load_binary(Module, Filename, Binary), 133 | error_logger:info_msg("active: module loaded: ~p~n", [Module]), 134 | reloaded. 135 | 136 | monitor_handles_renames([renamed|_]) -> true; 137 | monitor_handles_renames([_|Events]) -> monitor_handles_renames(Events); 138 | monitor_handles_renames([]) -> false. 139 | 140 | monitor_handles_renames() -> 141 | case get(monitor_handles_renames) of 142 | undefined -> 143 | R = monitor_handles_renames(erlfsmon:known_events()), 144 | put(monitor_handles_renames, R), 145 | R; 146 | V -> V 147 | end. 148 | 149 | % ["a", "b", ".."] -> ["a"] 150 | path_shorten(Coms) -> 151 | path_shorten_r(lists:reverse(Coms), [], 0). 152 | 153 | path_shorten_r([".."|Rest], Acc, Count) -> 154 | path_shorten_r(Rest, Acc, Count + 1); 155 | path_shorten_r(["."|Rest], Acc, Count) -> 156 | path_shorten_r(Rest, Acc, Count); 157 | path_shorten_r([_C|Rest], Acc, Count) when Count > 0 -> 158 | path_shorten_r(Rest, Acc, Count - 1); 159 | path_shorten_r([C|Rest], Acc, 0) -> 160 | path_shorten_r(Rest, [C|Acc], 0); 161 | path_shorten_r([], Acc, _) -> 162 | Acc. 163 | 164 | % 165 | % Filters 166 | % 167 | 168 | path_filter(L) -> 169 | not lists:any(fun(E) -> not path_filter_dir(E) end, L) andalso path_filter_last(lists:last(L)). 170 | 171 | path_filter_dir(".git") -> false; 172 | path_filter_dir(".hg") -> false; 173 | path_filter_dir(".svn") -> false; 174 | path_filter_dir("CVS") -> false; 175 | path_filter_dir("log") -> false; 176 | path_filter_dir(_) -> true. 177 | 178 | path_filter_last(".rebarinfo") -> false; % new rebars 179 | path_filter_last("LICENSE") -> false; 180 | path_filter_last("4913 (deleted)") -> false; % vim magical file 181 | path_filter_last("4913") -> false; 182 | path_filter_last(_) -> true. 183 | -------------------------------------------------------------------------------- /src/active_app.erl: -------------------------------------------------------------------------------- 1 | -module(active_app). 2 | 3 | -behaviour(application). 4 | 5 | %% Application callbacks 6 | -export([start/2, stop/1]). 7 | 8 | %% =================================================================== 9 | %% Application callbacks 10 | %% =================================================================== 11 | 12 | start(_StartType, _StartArgs) -> 13 | active_sup:start_link(). 14 | 15 | stop(_State) -> 16 | ok. 17 | -------------------------------------------------------------------------------- /src/active_sup.erl: -------------------------------------------------------------------------------- 1 | -module(active_sup). 2 | -behaviour(supervisor). 3 | 4 | %% API 5 | -export([start_link/0]). 6 | 7 | %% Supervisor callbacks 8 | -export([init/1]). 9 | 10 | %% Helper macro for declaring children of supervisor 11 | -define(CHILD(I, Type, Args), {I, {I, start_link, Args}, permanent, 5000, Type, [I]}). 12 | 13 | %% =================================================================== 14 | %% API functions 15 | %% =================================================================== 16 | 17 | start_link() -> 18 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 19 | 20 | %% =================================================================== 21 | %% Supervisor callbacks 22 | %% =================================================================== 23 | 24 | init([]) -> 25 | {ok, { {one_for_one, 5, 10}, [?CHILD(active, worker, [])]} }. 26 | 27 | --------------------------------------------------------------------------------