├── .github └── workflows │ └── publish_to_hexpm.yml ├── .gitignore ├── LICENSE ├── README.md ├── rebar.config ├── rebar.lock └── src ├── rebar3_auto.app.src └── rebar3_auto.erl /.github/workflows/publish_to_hexpm.yml: -------------------------------------------------------------------------------- 1 | name: Publish to Hex.pm 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v4 13 | 14 | - name: Prepare Hex.pm Configuration 15 | run: | 16 | mkdir -p ~/.config/rebar3 17 | echo "{plugins, [rebar3_hex]}." > ~/.config/rebar3/rebar.config 18 | echo '${{ secrets.HEX_CONFIG }}' > ~/.config/rebar3/hex.config 19 | 20 | - name: Set up Erlang 21 | uses: erlef/setup-beam@v1 22 | with: 23 | otp-version: '25.3' 24 | elixir-version: '1.15.4' 25 | rebar3-version: '3.22.1' 26 | 27 | - name: Publish to Hex.pm 28 | run: | 29 | mix local.rebar --force 30 | mix local.hex --force 31 | rebar3 update 32 | echo "" | rebar3 hex publish --yes 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | doc 2 | .rebar3 3 | _* 4 | .eunit 5 | *.o 6 | *.beam 7 | *.plt 8 | *.swp 9 | *.swo 10 | .erlang.cookie 11 | ebin 12 | log 13 | erl_crash.dump 14 | .rebar 15 | _rel 16 | _deps 17 | _plugins 18 | _tdeps 19 | logs 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Tristan Sloughter . 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | * The names of its contributors may not be used to endorse or promote 16 | products derived from this software without specific prior written 17 | permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | rebar_auto_plugin 2 | ===== 3 | 4 | A rebar3 plugin for auto running compile on source file change reloading modules in the shell. 5 | 6 | Prerequisite 7 | ----- 8 | On Linux you need to install inotify-tools. 9 | 10 | ``` 11 | -m: 1: inotifywait: not found 12 | ``` 13 | 14 | Use 15 | --- 16 | 17 | Add the plugin only to your user local rebar config in `~/.config/rebar3/rebar.config`: 18 | 19 | ```erlang 20 | {plugins, [rebar3_auto]}. 21 | ``` 22 | 23 | If you add it to your project rebar.config, it will get unloaded each time compilation occurs, thus breaking it. 24 | 25 | Then run 26 | ``` 27 | $ rebar3 compile 28 | ``` 29 | 30 | Then just call your plugin directly in an existing application: 31 | 32 | 33 | ``` 34 | (relx) $ rebar3 auto 35 | ===> Compiling rebar3_auto 36 | Setting up watches. Beware: since -r was given, this may take a while! 37 | Watches established. 38 | Erlang/OTP 18 [erts-7.1] [source] [64-bit] [smp:4:4] [async-threads:0] [hipe] [kernel-poll:false] 39 | 40 | Eshell V7.1 (abort with ^G) 41 | 1> ===> This feature is experimental and may be modified or removed at any time. 42 | Compiling rebar3_auto 43 | Verifying dependencies... 44 | Compiling relx 45 | Compiling rebar3_auto 46 | Verifying dependencies... 47 | Compiling relx 48 | 49 | 1> 50 | ``` 51 | 52 | Custom extensions, thanks abxy. 53 | Regex matches are supported, "$" is suffixed automatically, thanks xuchaoqian. 54 | ``` 55 | re:run(<<"file_name.erl_ab">>, <>) 56 | ``` 57 | 58 | 59 | To extend the list add an option to your rebar.config like so: 60 | ``` 61 | {auto, [ 62 | {extra_extensions, [".alp", ".hterl", ".erl(.*?)"]} 63 | ]}. 64 | ``` 65 | 66 | Extra directories. 67 | To extend the list of directories to be watched add an option to your rebar.config like so: 68 | ``` 69 | {auto, [ 70 | {extra_dirs, ["priv/dtl"]} 71 | ]}. 72 | ``` -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info]}. 2 | {deps, [fs]}. 3 | -------------------------------------------------------------------------------- /rebar.lock: -------------------------------------------------------------------------------- 1 | {"1.2.0", 2 | [{<<"fs">>,{pkg,<<"fs">>,<<"8.6.1">>},0}]}. 3 | [ 4 | {pkg_hash,[ 5 | {<<"fs">>, <<"7C9C0D0211E8C520E4E9EDA63B960605C2711839F47285E6166C332D973BE8EA">>}]}, 6 | {pkg_hash_ext,[ 7 | {<<"fs">>, <<"61EA2BDAEDAE4E2024D0D25C63E44DCCF65622D4402DB4A2DF12868D1546503F">>}]} 8 | ]. 9 | -------------------------------------------------------------------------------- /src/rebar3_auto.app.src: -------------------------------------------------------------------------------- 1 | {application,rebar3_auto, 2 | [{description,"Rebar3 plugin for auto compiling on changes"}, 3 | {vsn,"0.6.0"}, 4 | {registered,[]}, 5 | {applications,[kernel,stdlib,fs]}, 6 | {env,[]}, 7 | {modules,[]}, 8 | {maintainers,["Tristan Sloughter"]}, 9 | {licenses,["MIT"]}, 10 | {links,[{"Github", 11 | "https://github.com/tsloughter/rebar3_auto"}]}]}. 12 | -------------------------------------------------------------------------------- /src/rebar3_auto.erl: -------------------------------------------------------------------------------- 1 | %% @doc 2 | %% Add the plugin to your rebar config, since it is a developer tool and not 3 | %% necessary for building any project you work on I put it in 4 | %% `~/config/.rebar3/rebar.config`: 5 | %% 6 | %% ``` 7 | %% {plugins, [rebar3_auto]}.''' 8 | %% 9 | %% Then just call your plugin directly in an existing application: 10 | %% 11 | %% ``` 12 | %% $ rebar3 auto 13 | %% ===> Fetching rebar_auto_plugin 14 | %% ===> Compiling rebar_auto_plugin''' 15 | %% 16 | -module(rebar3_auto). 17 | -behaviour(provider). 18 | 19 | -export([init/1 20 | ,do/1 21 | ,format_error/1]). 22 | 23 | -export([auto/1, flush/0]). 24 | 25 | -define(PROVIDER, auto). 26 | -define(DEPS, [compile]). 27 | 28 | %% =================================================================== 29 | %% Public API 30 | %% =================================================================== 31 | -spec init(rebar_state:t()) -> {ok, rebar_state:t()}. 32 | init(State) -> 33 | Provider = providers:create([ 34 | {name, ?PROVIDER}, % The 'user friendly' name of the task 35 | {module, ?MODULE}, % The module implementation of the task 36 | {bare, true}, % The task can be run by the user, always true 37 | {deps, ?DEPS}, % The list of dependencies 38 | {example, "rebar3 auto"}, % How to use the plugin 39 | {opts, [{config, undefined, "config", string, 40 | "Path to the config file to use. Defaults to " 41 | "{shell, [{config, File}]} and then the relx " 42 | "sys.config file if not specified."}, 43 | {name, undefined, "name", atom, 44 | "Gives a long name to the node."}, 45 | {sname, undefined, "sname", atom, 46 | "Gives a short name to the node."}, 47 | {setcookie, undefined, "setcookie", atom, 48 | "Sets the cookie if the node is distributed."}, 49 | {script_file, undefined, "script", string, 50 | "Path to an escript file to run before " 51 | "starting the project apps. Defaults to " 52 | "rebar.config {shell, [{script_file, File}]} " 53 | "if not specified."}, 54 | {apps, undefined, "apps", string, 55 | "A list of apps to boot before starting the " 56 | "shell. (E.g. --apps app1,app2,app3) Defaults " 57 | "to rebar.config {shell, [{apps, Apps}]} or " 58 | "relx apps if not specified."}]}, 59 | {short_desc, "Automatically run compile task on change of source file and reload modules."}, 60 | {desc, ""} 61 | ]), 62 | {ok, rebar_state:add_provider(State, Provider)}. 63 | 64 | -spec format_error(any()) -> iolist(). 65 | format_error(Reason) -> 66 | io_lib:format("~p", [Reason]). 67 | 68 | 69 | -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. 70 | do(State) -> 71 | Opts = rebar_state:get(State, auto, []), 72 | spawn(fun() -> 73 | listen_on_project_apps(State, Opts), 74 | Extensions = get_extensions(State, Opts), 75 | ?MODULE:auto(Extensions) 76 | end), 77 | State1 = remove_from_plugin_paths(State), 78 | rebar_prv_shell:do(State1). 79 | 80 | -define(VALID_EXTENSIONS_DEFAULT,[<<".erl">>, <<".hrl">>, <<".src">>, <<".lfe">>, <<".config">>, <<".lock">>, 81 | <<".c">>, <<".cpp">>, <<".h">>, <<".hpp">>, <<".cc">>]). 82 | 83 | get_extensions(State, Opts) -> 84 | ExtraExtensions1 = rebar_state:get(State, extra_extensions, []), %%left for backward compatibility 85 | ExtraExtensions2 = proplists:get_value(extra_extensions, Opts, []), 86 | ExtraExtensions = ExtraExtensions1 ++ ExtraExtensions2, 87 | [unicode:characters_to_binary(Ext) || Ext <- ExtraExtensions] ++ ?VALID_EXTENSIONS_DEFAULT. 88 | 89 | auto(Extensions) -> 90 | case whereis(rebar_agent) of 91 | undefined -> 92 | timer:sleep(100); 93 | 94 | _ -> 95 | receive 96 | {_Pid, _Type, {ChangedFile, _Events}} -> 97 | Ext = filename:extension(unicode:characters_to_binary(ChangedFile)), 98 | IsValid = lists:any( 99 | fun(ValidExt) -> 100 | RE = <>, 101 | Result = re:run(Ext, RE), 102 | case Result of 103 | {match, _Captured} -> true; 104 | match -> true; 105 | nomatch -> false; 106 | {error, _ErrType} -> false 107 | end 108 | end, 109 | Extensions 110 | ), 111 | case IsValid of 112 | false -> pass; 113 | true -> 114 | % sleep here so messages can bottle up 115 | % or we can flush after compile? 116 | timer:sleep(200), 117 | flush(), 118 | r3:do(compile) 119 | end; 120 | _ -> pass 121 | end 122 | 123 | end, 124 | ?MODULE:auto(Extensions). 125 | 126 | flush() -> 127 | receive 128 | _ -> 129 | flush() 130 | after 131 | 0 -> ok 132 | end. 133 | 134 | listen_on_project_apps(State, Opts) -> 135 | CheckoutDeps = [AppInfo || 136 | AppInfo <-rebar_state:all_deps(State), 137 | rebar_app_info:is_checkout(AppInfo) == true 138 | ], 139 | ExtraDirs = [unicode:characters_to_binary(D) || D <- proplists:get_value(extra_dirs, Opts, [])], 140 | ProjectApps = rebar_state:project_apps(State), 141 | lists:foreach( 142 | fun(AppInfo) -> 143 | AppDir = rebar_app_info:dir(AppInfo), 144 | Dirs = ExtraDirs ++ [<<"src">>, <<"c_src">>], 145 | lists:foreach( 146 | fun(Dir) -> 147 | Path = filename:join(AppDir, Dir), 148 | case filelib:is_dir(Path) of 149 | true -> 150 | Handle = binary_to_atom(<<"fs_watcher_", Path/binary>>), 151 | fs:start_link(Handle, Path), 152 | fs:subscribe(Handle); 153 | false -> ignore 154 | end 155 | end, 156 | Dirs 157 | ) 158 | end, 159 | ProjectApps ++ CheckoutDeps 160 | ). 161 | 162 | remove_from_plugin_paths(State) -> 163 | PluginPaths = rebar_state:code_paths(State, all_plugin_deps), 164 | PluginsMinusAuto = lists:filter( 165 | fun(Path) -> 166 | Name = filename:basename(Path, "/ebin"), 167 | not (list_to_atom(Name) =:= rebar_auto_plugin 168 | orelse list_to_atom(Name) =:= fs) 169 | end, 170 | PluginPaths 171 | ), 172 | rebar_state:code_paths(State, all_plugin_deps, PluginsMinusAuto). 173 | --------------------------------------------------------------------------------