Home / Erlware / Tutorial

The Full Development & Release Cycle

This tutorial will go through a real world example of installing Erlware development tools, creating a release, creating an application, and publishing the release and application for others to use. The full cycle.

Installing the Development Tools

Installing Faxien

First thing to do is install the Erlware package manager; Faxien. Download the latest Faxien bootstrap installer for your platform or the Universal Bootstrapper from Google Code. Now bootstrap with one of the following commands.

$ python ./faxien-launcher-universal.py # installs to `/usr/local/erlware by default.`

or use an arch specific bootstrapper

$ ./faxien-launcher-<os-type-info>.sh # installs to `/usr/local/erlware.`
$ ./faxien-launcher-<os-type-info>.sh /home/martinjlogan/erlware # installs into the supplied path

Tip

Add /usr/local/erlware/bin to your path so that all applications you install with Faxien, including Faxien itself, are easily accessible via the command line.


Troubleshooting

You need to make sure that you have permissions to write to /usr/local to install Faxien. Linux/Unix users will probably need to use the 'sudo' command. Without the correct permissions the install will fail. If you cannot get write permissions to /usr/local then use the prefix option to install into another directory. The example below demonstrates installing faxien into /home/martinjlogan/erlware.

$ ./faxien-launcher-<os-type-info>.sh /home/martinjlogan/erlware # installs into the supplied path

Once faxien is installed, assuming the default install location, you can test the installation by typing:

$ /usr/local/erlware/bin/faxien version
0.18.1

Installing Sinan

Once Faxien is installed you can use it to easily install Sinan, our flagship build system.

$ faxien install-release sinan

This will install Sinan into your erlware directory and place a link to the Sinan executable in erlware/bin which by now should be on your path.


Troubleshooting

If your Sinan install halts with the following error:

$ faxien install-release sinan
Installing Application sgte-0.2.1 -> ok
Installing Application projgen-0.1.3 -> ok
Installing Application syntax_tools-1.5.3 -> ok
Installing Application compiler-4.4.4 -> ok
Installing Application edoc-0.7.2 -> ok
Installing Application fconf-0.1.1 -> ok
Installing Application tools-2.5.4faxien:install/1 exited with: installation_failed
 - Make sure you have the correct permissions to run Faxien
 - For error_logger information look at "/usr/local/erlware/log/faxien.err_log"
 - For sasl log information look at "/usr/local/erlware/log/faxien.sasl_log"

This most probably indicates that for your architecture and operating system a compiled version of the tools application, one that Sinan requires to function, is not present. The fix for this is easy, help us and publish it. Enter the following command to publish the tools application (that's if you have erlang installed at /usr/local/lib/erlang though of course you don't have to install Erlang from source at all with Erlware)

$ faxien publish /usr/local/lib/erlang/lib/tools-2.5.4 infinity

where infinity is the remote request timeout length. The above example is assuming, of course, that erlang is installed at /usr/local/lib/erlang. Now re-run the install script to see a successful installation.


Creating a Project

The first step in building our software is creating a project to work in. A project is at its heart an organized collection of configuration and code. To create the project we will be using for this tutorial we are going to use the sinan gen utility that is packaged with the Sinan release. In this case the project will be called "full_cycle" and the only app you will include in the project when prompted will be called "hello_world".

A Brief Look at the Anatomy of a Project

A project is at its heart an organized collection of configuration and code. To really understand what a project is we need to dig a little deeper though. First let me say, projects are simple; just want to set the tone.

What's inside

Let's change directories into our newly created project and run a directory listing.

$ cd full_cycle
$ ls # directory listing on windows you may type dir
_build.cfg  doc  lib

We see three names, one file, _build.cfg, and two directories, doc, and lib. _build.cfg is the project level configuration file. Edit the file to contain the following code:

project : {
  name : full_cycle
  vsn  : "0.1.0"
},

repositories : ["http://repo.erlware.org/pub",
        "http://www.martinjlogan.com/pub"],

_build.cfg has a number of other options allowing you to control all phases of Sinan project interaction. For most cases however the basic file you see above will suffice. The first thing we see is the project meta information, name and version. Following that is a list of repositories that Sinan will seek to download dependencies from; kernel, stdlib, sasl, gas, fslib to name a few. The last bit of configuration for dist is out of the scope of this tutorial.

The doc directory is self explanatory so we will move on to the final directory in our listing which is lib. So change directories into lib and do a listing. You will see that lib contains a single directory, gas. Rename it to hello_world, delete the erl files, and rename all other files with gas in in the name to read hello_world. This tutorial is going to assume you know at least a little something about application structure. If you don't you can either look at our docs on the subject, or just keep going, you should not have a terrible time following.

Building the hello_world Application

Application Specification (hello_world.app)

First we need to edit hello_world.app our application specification file. This file is found in hello_world/ebin/. So lets open it up:

%% This is the application resource file (.app file) for the hello_world,
%% application.
{application, hello_world,
  [{description, "Your Desc HERE"},
   {vsn, "0.1.0"},
   {modules, []},
   {registered,[hw_sup]},
   {applications, [kernel, stdlib]},
   {mod, {hw_app,[]}},
   {start_phases, []}]}.

Replace the value for the description key with "Says hello to the world". Notice that we have two modules already in our module listing. Sinan gen has created our application entry point file "hello_world_app.erl" and our top level supervisor "hello_world_sup" for us. These files are listed in modules because Sinan will only compile files for a given application that are listed within the modules list. The registered list tells OTP which registered processes this application contains in order to avoid conflict with other applications in our release. applications tells Sinan what our dependency set is. If we were dependent on fslib and gas in addition to kernel and stdlib we would list both additional apps under applications. The meaning of the rest of the .app file is left as an exercise to the reader.

Lets write our hello world code now.

Writing our application

Our hello world application is going to be very simple. It will contain a single simple server that is started by the top level supervisor. This server will respond to a message consisting of a single Pid by sending back the message {ok, hello_world}.

Create the file hw_server.erl in full_cycle/lib/hello_world/src and enter the following code into it.

%%%-------------------------------------------------------------------
%%% @doc  The hello world server.
%%% @end
%%%-------------------------------------------------------------------
-module(hw_server).

%%--------------------------------------------------------------------
%% External exports
%%--------------------------------------------------------------------
-export([
         start_link/0,
     init/1
        ]).

%%--------------------------------------------------------------------
%% macro definitions
%%--------------------------------------------------------------------
-define(SERVER, hello_world_server).

%%====================================================================
%% External functions
%%====================================================================

%%--------------------------------------------------------------------
%% @doc Starts the server.
%% @spec start_link() -> {ok, pid()} | {error, Reason}
%% @end
%%--------------------------------------------------------------------
start_link() ->
    proc_lib:start(?MODULE, init, [self()]).

%%====================================================================
%% Callback Functions
%%====================================================================

%%--------------------------------------------------------------------
%% @doc Called before the process start functions can return.
%% @spec init(Parent::pid()) -> void()
%% @end
%%--------------------------------------------------------------------
init(Parent) ->
    register(?SERVER, self()),
    proc_lib:init_ack(Parent, {ok, self()}),
    loop().

%%====================================================================
%% Internal Functions
%%====================================================================
loop() ->
    receive
    Pid when is_pid(Pid) -> Pid ! {ok, hello_world};
    _Msg                 -> ok
    end,
    loop().

Now create the files hw_app and hw_sup which are toe be your application and supervisor behaviour start files. The file contents are as follows:

hw_app.erl

%%%-------------------------------------------------------------------
%%% @author
%%% @doc
%%%
%%% @end
%%%-------------------------------------------------------------------
-module(hw_app).

-behaviour(application).

%% Application callbacks
-export([start/2, stop/1]).

%%%===================================================================
%%% Application callbacks
%%%===================================================================

%%--------------------------------------------------------------------
%% @private
%% @doc
%% This function is called whenever an application is started using
%% application:start/[1,2], and should start the processes of the
%% application. If the application is structured according to the OTP
%% design principles as a supervision tree, this means starting the
%% top supervisor of the tree.
%%
%% @spec start(Type, StartArgs) -> {ok, Pid} |
%%                                 {ok, Pid, State} |
%%                                 {error, Reason}
%% @end
%%--------------------------------------------------------------------
start(_Type, StartArgs) ->
    case 'TopSupervisor':start_link(StartArgs) of
    {ok, Pid} ->
        {ok, Pid};
    Error ->
        Error
    end.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% This function is called whenever an application has stopped. It
%% is intended to be the opposite of Module:start/2 and should do
%% any necessary cleaning up. The return value is ignored.
%%
%% @spec stop(State) -> void()
%% @end
%%--------------------------------------------------------------------
stop(_State) ->
    ok.

and hw_sup.erl

%%%-------------------------------------------------------------------
%%% @author
%%% @copyright (C) 2008,
%%% @doc
%%%
%%% @end
%%%-------------------------------------------------------------------
-module(hw_sup).

-behaviour(supervisor).

%% API
-export([start_link/0]).

%% Supervisor callbacks
-export([init/1]).

-define(SERVER, ?MODULE).

%%%===================================================================
%%% API functions
%%%===================================================================

%%--------------------------------------------------------------------
%% @doc
%% Starts the supervisor
%%
%% @spec start_link() -> {ok, Pid} | ignore | {error, Error}
%% @end
%%--------------------------------------------------------------------
start_link() ->
    supervisor:start_link({local, ?SERVER}, ?MODULE, []).

%%%===================================================================
%%% Supervisor callbacks
%%%===================================================================

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Whenever a supervisor is started using supervisor:start_link/[2,3],
%% this function is called by the new process to find out about
%% restart strategy, maximum restart frequency and child
%% specifications.
%%
%% @spec init(Args) -> {ok, {SupFlags, [ChildSpec]}} |
%%                     ignore |
%%                     {error, Reason}
%% @end
%%--------------------------------------------------------------------
init([]) ->
    RestartStrategy = one_for_one,
    MaxRestarts = 1000,
    MaxSecondsBetweenRestarts = 3600,

    SupFlags = {RestartStrategy, MaxRestarts, MaxSecondsBetweenRestarts},

    Restart = permanent,
    Shutdown = 2000,
    Type = worker,

    AChild = {'AName', {'AModule', start_link, []},
          Restart, Shutdown, Type, ['AModule']},

    {ok, {SupFlags, [AChild]}}.

Now if you recall Sinan will only compile modules that are listed in the modules list within our .app file; ebin/hello_world.app.

{modules, [hw_app, hw_sup, hw_server]}, # add this entry into modules

Next we need to tell our top level supervisor to start our server. Edit hello_world_sup.erl altering the init/1 function to look like the example below:

init([]) ->
    AChild = {hw_server,{hw_server,start_link,[]},
              permanent,2000,worker,[hw_server]},
    {ok,{{one_for_all,0,1}, [AChild]}}.

Now to compile

$ sinan
Using task build
Executing task discover ...
Discovering project layout and structure ...
Executing task check_depends ...
Dependencies are out of date. Should I run dependecies now? yes
Pulling eunit-2.0 from repository if non-local
Pulling stdlib-1.14.4 from repository if non-local
Pulling kernel-2.11.4 from repository if non-local
Executing task build ...
Starting compilation process ...
Building /home/martinjlogan/work/full_cycle/lib/hello_world/src/hw_app.erl
Building /home/martinjlogan/work/full_cycle/lib/hello_world/src/hw_sup.erl
Building /home/martinjlogan/work/full_cycle/lib/hello_world/src/hw_server.erl
run complete

Looks good, our application is complete. At this point we have a working application. Lets assume that we have been good programmers and tested this thing somewhat. Lets further assume that the full_cycle release, which contains our hello_world application among other bits of functionality is something that we want the community at large to be able to download and run similar to the way we downloaded and used Sinan. To do this we need to take our project create a release and then publish it.

Creating and Publishing a Release

Start Up Script

The first thing we will need for a functioning release is a startup script. Create a directory named cmds right underneath the full_cycle directory. We are going to be launching with faxien so we don't really have to worry about any of the platform specific shells.

Lets create the full_cycle executable start script under the bin directory.

#!/bin/sh

#### Fill in values for these variables ####
REL_NAME=full_cycle
REL_VSN=0.1.0
ERTS_VSN=5.5.5
INVOCATION_SUFFIX=""
###########################################

PROG=$0
test -h $0 && PROG=$(readlink $0)
PREFIX=$(cd $(dirname $(dirname $PROG)); pwd)

export ROOTDIR=$PREFIX/application_packages/$ERTS_VSN
export BINDIR=$PREFIX/erts_packages/erts-$ERTS_VSN/bin
export EMU=beam
export PROGNAME=erl
export LD_LIBRARY_PATH=$PREFIX/erts/$ERTS_VSN/lib

REL_DIR=$PREFIX/release_packages/$REL_NAME-$REL_VSN/release

$BINDIR/erlexec -config $REL_DIR/faxien.config -boot $REL_DIR/$REL_NAME $INVOCATION_SUFFIX

Creating the Release Structure

Create a release directory called full_cycle-<vsn> so in this case full_cycle-0.1.0. This version number is essential, you cannot publish a release if your release directory is not of the form <release_name>-<release_vsn>.

$ sinan release

This will prompt Sinan to create a .rel file under the directory full_cycle/_build/development/releases/0.1.0. We are going to need to copy the entire "releases" directory under full_cycle-0.1.0. Now copy the bin directory under full_cycle-0.1.0. Finally copy your LICENSE and README files if you have them into full_cycle-0.1.0.

Running a directory listing in the full_cycle-0.1.0 directory should yield the following:

$ ls
cmds LICENCE  README  releases

To publish our release to the world we type

$ faxien publish ./full_cycle-0.1.0

Wait a minute though, not so fast, unless you configured Faxien to publish to a repository running on your local network someone else doing this tutorial could publish right over your release by the time you try to install and run it.

So lets just pretend we published our version of full_cycle into repo.erlware.org/writable and instead utilize the ability of Faxien to install locally resident release packages just as well as it installs them from remote repositories. To do this we need to take one more step before installing. We need to create a lib directory under full_cycle-0.1.0 and place the hello_world application underneath it. Faxien will first look in the lib directory for applications to install and then in remote repo's for further dependencies that do not exist within the lib directory. hello_world.app specifies that it depends on kernel and stdlib in the applications dependency list which are apps that are definitely present in remote repositories which is why hello_world is the only app we need to place in the lib directory. The compiled version of hello_world can be found in _build/development/apps/hello_world-0.1.0. It is important to note that all apps in published and installed packages must have the - format. Now lets install our release.

$ faxien install-release full_cycle-0.1.0/
Installing Application hello_world-0.1.0 -> ok

Installing Release full_cycle-0.1.0 -> ok
ok

We can now stand up our release and give it a test.

Running and Testing the Release

When we execute the full_cycle command, which sits as a soft link in erlware/bin, we are starting up the entire release we have created. This means that every application in the full_cycle.rel file starts up in the order they are listed.

$ cat /usr/local/erlware/full_cycle-0.1.0/releases/0.1.0/full_cycle.rel
{release,{"full_cycle","0.1.0"},
         {erts,"5.5.5"},
         [{kernel,"2.11.4"},
          {stdlib,"1.14.4"},
          {hello_world,"0.1.0"},
          {eunit,"2.0"}]}.

The apps listed will all be started if they can be, eunit for example is not an app that gets started but in this case it will be loaded into memory. This is a dependency we may want to remove in the future, but for now, it does not hurt. To start and test the release we will run the hello_world command then fire up a Erlang shell to interact with it.

$ full_cycle
Starting Full Cycle
$ erl -name try
Erlang (BEAM) emulator version 5.5.5 [source] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.5.5  (abort with ^G)
(try@test.erlware.org)1> net_adm:ping('full_cycle@test.erlware.org').
pong
(try@test.erlware.org)2> {hello_world_server, 'full_cycle@test.erlware.org'} ! self().
<0.37.0>
(try@test.erlware.org)3> receive Msg -> Msg end.
{ok,hello_world}

Looks like we now have the full_cycle service running on our network.

Last Words

Today we have run through the full cycle of developing, publishing, and installing an Erlang release. If we had actually run the publish command on full_cycle-0.1.0 the rest of the world could have installed it by typing faxien install-release full_cycle. The combination of Sinan and Faxien is quite powerful. We have scratched the surface of what is possible with these Erlware tools we encourage you to dig deeper into the documentation if you want to learn more.

Happy Erlanging.


You can download the source code for the full cycle application and for the full cycle release.