Search code examples
erlangescript

Erlang escript launching application with start parameters


Currently my Erlang application is started within an escript (TCP server) and all works fine since it uses the default port I provided. Now I want to pass the port via the escript to the application but I have no idea how. (The app runs a supervisor)

script.escript

!/usr/bin/env escript
%% -*- erlang -*-

-export([main/1]).

main([UDPort, TCPort]) ->
   U = list_to_integer(UDPort),
   T = list_to_integer(TCPort),

    app:start(), %% Want to pass T into the startup.
  receive
    _ -> ok
  end;

...

app.erl

-module(app).
-behaviour(application).

-export([start/0, start/2, stop/0, stop/1]).

-define(PORT, 4300).

start () -> application:start(?MODULE). %% This is called by the escript.
stop () -> application:stop(?MODULE).

start (_StartType, _StartArgs) -> supervisor:start(?PORT).
stop (_State) -> ok.

I'm honestly not sure if this is possible with using application but I thought it best to just ask.


Solution

  • The common way is to start things from whatever shell just calling

    erl -run foo
    

    But you can also do

    erl -appname key value
    

    to set an environment value and then

    application:get_env(appname, key)
    

    to get the value you are looking for.

    That said...

    I like to have service applications be things that don't have to shut down to be (re)configured. I usually include some message protocol like {config, Aspect, Setting} or similar that can alter the basic state of a service on the fly. Because I often do this I usually just wind up having whatever script starts up the application also send a configuration message to it.

    So with this in mind, consider this rough conceptual example:

    !/usr/bin/env escript
    %% -*- erlang -*-
    
    -export([main/1]).
    
    main([UDPort, TCPort]) ->
        U = list_to_integer(UDPort),
        T = list_to_integer(TCPort),
        ok = case whereis(app) of
            undefined ->  app:start();
            _Pid      ->  ok
        end,
        ok = set_ports(U, T).
    
    %% Just an illustration.
    %% Making this a synchronous gen_server/gen_fsm call is way better.
    set_ports(U, T) ->
        app ! {config, listen, {tcp, T}},
        app ! {config, listen, {udp, U}},
        ok.
    

    Now not only is the startup script a startup script, it is also a config script. The point isn't to have a startup script, it is to have a service running on the ports you designated. This isn't a conceptual fit for all tools, of course, but it should give you some ideas. There is also the practice of putting a config file somewhere the application knows to look and just reading terms from it, among other techniques (like including ports in the application specification, etc.).

    Edit

    I just realized you are doing this in an escript which will spawn a new node every time you call it. To make the technique above work properly you would need to make the escript name a node for the service to run on, and locate it if it already exists.