My bad opinions

2016/01/05

rebar3 shell

It took me a long time to write this article. One of the things about rebar3 shell is that it does what you expect it to do with a project, whether small or large: it loads up the code paths, boots up applications (if specified to), allows a distributed node to run, lets you define custom scripts to prime its state, and lets you run rebar3 tasks within it, taking care of updating the state to work properly.

That's a lot of nifty features, but they are, in my opinion, the bare minimum that a shell tool for Erlang should support. As such, trying to showcase them sounds like bragging about doing the bare minimum you are supposed to do; it may be better to say nothing and move on.

Still, the state of usability for some Erlang stuff has historically been pretty bad, and a lot of the work we (contributors) have been putting in Rebar3 has been driven by trying to solve that kind of experience. I have to say that mostly, the community has plenty of very good tools, but they're all independent and making good use of them is left as an exercise to the reader. That's where rebar3 comes in.

The first thing to notice when using the rebar3 shell command is that it will automatically compile the project (if not done yet) and add it to your code path:

 rebar3 shell
===> Verifying dependencies...
===> Compiling vegur
Erlang/OTP 18 [erts-7.1] [source] [64-bit] [smp:8:4] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V7.1  (abort with ^G)
1> vegur:module_info().
[{module,vegur},
 {exports,[{start_http,3},
           {start_proxy,3},
...

This works for any and all projects. This, at the very least, gets rid of a lot of annoyances with default setups requiring you to wire everything by hand. It's now taken care of.

A good shell environment for a programming language is all about interactivity; it is therefore normal to expect to be able to quickly recompile code or run tests, and reload the code without any interruption or needing to lose state. The rebar3 shell has an agent, hidden behind the r3 module, ready to do all the state management required to recompile code or run tasks. So for example, in any existing project, I can either ask for recompile jobs, or for any other task to be run, really:

2> r3:do(compile).
===> This feature is experimental and may be modified or removed at any time.
Verifying dependencies...
Compiling vegur
ok
3> r3:do(ct).
Verifying dependencies...
Fetching websocket_client ({git,"git@github.com:jeremyong/websocket_client.git",
                                {tag,"v0.7"}})
Linking _build/default/lib/cowboyku to _build/test/lib/cowboyku
Linking _build/default/lib/cowlib to _build/test/lib/cowlib
Linking _build/default/lib/erequest_id to _build/test/lib/erequest_id
...
Running Common Test suites...
%%% vegur_bytepipe_SUITE ==> pipe_proc: OK
%%% vegur_bytepipe_SUITE ==> pipe_proc_callback: OK
%%% vegur_bytepipe_SUITE ==> pipe_proc_timeout: OK
...
All 140 tests passed.
ok
3> r3:do(dialyzer).
Verifying dependencies...
...
Analyzing 19 files with "/home/ferd/code/self/vegur/_build/default/rebar3_18.1.5_plt"...
ok

... and so on. Each of these tasks will use the latest version of your rebar.config file to run its tasks, and will take care of switching paths, reloading modules, and so on.

The rebar3 agent has also been given a process name to make it callable from the outside world. You can start a shell with a name (rebar3 shell --name my_shell or rebar3 shell --sname my_shell) and then use it to send instructions remotely, either as messages or direct RPC calls:

 rebar3 shell --sname=my_shell
 erl -sname remote -eval 'rpc:call(my_shell@localhost, r3, do, [ct]), halt(0).' -noshell

The objective being that people's tooling could now be used to add hooks from your IDE or editor into the Erlang shell.

Plugins written to work with the rebar3 agent are also an option. For example, rebar3_auto can be used to automatically recompile modified files by watching the disk, removing the need from editor or IDE integration altogether.

This gives place to some pretty sweet improvements, for example when running debug cycles, where tests, code analysis, and interactive debugging can all take place in the same environment.

To make the development cycle even more seamless as your projects grow, rebar3 shell will automatically detect release configurations within the build tool, and boot your system the way the release would, along with the application configuration[1]. This means that as long as your codebase is configured to ship an executable release, you can access the code reloading features of the shell within it with the same tooling you would otherwise use.

And if you're not using a release? Applications can be specified in your rebar.config file as either {shell, [{apps, [myapp]}]} or as a command line argument (--apps app1,app2):

 rebar3 shell --apps vegur
===> Verifying dependencies...
===> Compiling vegur
...
===> Booted midjan
===> Booted quickrand
===> Booted uuid
===> Booted erequest_id
===> Booted vegur

In the eventuality your development environment doesn't match the production one, arbitrary code can be run to prime the shell and configure it through escript files. Those can be specifically useful to set up specific dynamic environment variables or boot mocked up external dependencies. To expand a bit more, the example that follows shows how to keep a shell from booting unless all required environment variables are set:

#!/usr/bin/env escript

main(_) ->
    OSVars = ["USER", "PASS"],
    [check_is_set(Var) || Var <- OSVars].

check_is_set(Var) ->
    case os:getenv(Var) of
        false ->
            rebar_api:error("Missing var ~s", [Var]),
            halt(1);
        _ ->
            ok
    end.

The file can be configured to always run by adding {shell, [{script_file, "path/to/file"}]} to your rebar.config file, or can be called directly by the command line:

 rebar3 shell --script_file test/check_env.escript
===> Verifying dependencies...
===> Compiling vegur
Erlang/OTP 18 [erts-7.1] [source] [64-bit] [smp:8:4] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V7.1  (abort with ^G)
1> ===> Missing var PASS

Hopefully, with all these things, in place, the life of Erlang developers can be made easier than what they get with the tools coming out of the box.

[1]: There's a few caveats here; as rebar3 shell is a development tool, options such as VM configuration (number of schedulers, for example) are already in place at run-time and cannot be modified. It cannot replicate all of a release's configuration, and as such, you should keep using releases for production deployments.