I completely rely on the OTP principles. That’s why I use applications, gen_server and stuff overall and it works for me very well. There is only one issue with CLI applications:
If all code is started with applications and application:start/2 is supposed to start a supervisor than it doesn’t work in this case. One application is meant to be the main controller of the CLI application that only calls server functions.
Following solutions don’t work:
run the controller in start/2 and stop the whole program/release(?) with init:stop(). This will raise bad return value ok from almmain_app:start(normal, [])
manually patch the boot script to eval code to start the controller in a separate module
use rebar3 to provide an additional entry point. At least I couldn’t find any configuration option
I am curious if OTP is only used for pure server applications.
Erlang is fine-ish for CLI tools, I’ve written a number of them. You can use escripts or just shell wrappers that run the code with erl -noshell -pa ... -run .... A few years ago I had to work around escript crash dumps going to the wrong output (stdout instead of stderr) but that’s fixed now.
As to OTP principles, I use gen_servers where apppropriate but not supervisors, and therefore not full-on stateful applications (they’re all library applications). A compiler say has little use for supervising the process holding lexer state.
The problem was wrong handling of OTP processes. You are discouraged to start a gen_server in a Supervisor with :start_link/2 and expect it will be closed properly.