Makefiles making erlang easy

 

Update (01/04/2010): Added a github repos with a project template for starting new projects here: http://github.com/auser/erlproject_template.

The Rakefile in a ruby project is almost just as important as the code itself. Ask any rubyish to show you their project and you can bet your bottom dollar that nine out of every 10 projects of theirs has a Rakefile (most of the time, it's 10/10). This is one thing that can make starting an erlang project painful... the Makefile (bum bum buuuummmm). Today, I'll share a Makefile (for my own future reference too!) that works really well for me and my projects.

I've attached a sample project directory to get started (for the impatient, you can them here if you'd like to follow along). It includes a sample application file, a sample gen_server and of course all the files in this post. Let's get started:

First, the Makefile:

# Makefile
LIBDIR      = `erl -eval \
  'io:format("~s~n", [code:lib_dir()])' -s init stop -noshell`
VERSION     = 0.0.1
CC              = erlc
ERL         = erl
EBIN            = ebin
CFLAGS      = -I include -pa $(EBIN)
COMPILE     = $(CC) $(CFLAGS) -o $(EBIN)
EBIN_DIRS = $(wildcard deps/*/ebin)

all: mochi ebin compile
all_boot: all make_boot
start: all start_all

mochi:
  @(cd deps/mochiweb;$(MAKE))

compile:
  @$(ERL) -pa $(EBIN_DIRS) -noinput +B \
  -eval 'case make:all() of up_to_date ->; halt(0); \
        error ->; halt(1) end.'

edoc:
  @echo Generating $(APP) documentation from srcs
  @erl -noinput -eval 'edoc:application($(APP), "./", \
        [{doc, "doc/"}, {files, "src/"}])' -s erlang halt

make_boot:
  (cd ebin; erl -pa ebin -noshell \
    -run make_boot write_scripts rest_app)

start_all:
  (cd ebin; erl -pa ebin -noshell -sname _name_ -boot _name_)

ebin:
  @mkdir ebin

clean:
  rm -rf ebin/*.beam ebin/erl_crash.dump erl_crash.dump
  rm -rf ebin/*.boot ebin/*.rel ebin/*.script
  rm -rf doc/*.html doc/*.css doc/erlang.png doc/edoc-info

This particular project (not yet announced) uses mochiweb (and it's a good example to show dependencies, so I left it), so we have a task called mochi so that we compile all of the mochiweb sources. Before showing the EMakefile, which is what drives the compile task, it's important to note that there is also a make_boot task that creates a boot file for the project. This is pretty interesting, so we'll dive into that real quick:

% make_boot.erl
-module(make_boot).
-export([write_scripts/1]).

write_scripts(Args) ->; 
  [Name] = Args,
  io:format("write_scripts for ~p~n", [Name]),
  Erts = erlang:system_info(version),
  application:load(sasl),
  Version = "0.1",
  {value, {kernel, _, Kernel}} = lists:keysearch(kernel, 1,
    application:loaded_applications()),
  {value, {stdlib, _, Stdlib}} = lists:keysearch(stdlib, 1,
    application:loaded_applications()),
  {value, {sasl, _, Sasl}} = lists:keysearch(sasl, 1,
    application:loaded_applications()),

  Rel = "{release, {\"~s\", \"~s\"}, {erts, \"~s\"}, ["
          "{kernel, \"~s\"}, {stdlib, \"~s\"}, 
          {sasl, \"~s\"}, {~s, \"~s\"}]}.",

  Lowername = string:to_lower(Name),

  Filename = lists:flatten(Lowername ++ ".rel"),
  io:format("Writing to ~p (as ~s)~n", [Filename, Lowername]),
  {ok, Fs} = file:open(Filename, [write]),

  io:format(Fs, Rel, [Name, 
                      Version, 
                      Erts, 
                      Kernel, 
                      Stdlib, 
                      Sasl, 
                      Lowername, 
                      Version]),
  file:close(Fs),

  systools:make_script(Lowername, [local]),
  halt().

To actually write a bootfile, we need to supply the name of the bootfile to the method call, so we call this like so:

% shell command
erl -pa ebin -noshell -run make_boot write_scripts rest_app

Finally, let's make sure we have a .app file in the ebin/ directory, sample one below:

% _name_.app
{application, _name_, [
  {description, "_Name_"},
  {vsn, "0.1"},
  {modules, [_modules_]},
  {env, [
    {port, 9999}
  ]},
  {registered, [_name_]},
  {applications, [kernel, stdlib]},
  {mod, {_name_, []}}
]}.

Lastly, break out the EMakefile so that we can actually compile the project:

% EMakefile
% -*- mode: erlang -*-
{["src/*", "src/*/*", "src/*/*/*"],
 [{i, "include"},
  {outdir, "ebin"},
  debug_info]
}.

Hurray, we are almost set. Now let's make a start.sh file so we can just call that to start the application:

#!/bin/sh
# start.sh
cd `dirname $0`
erl -pa $PWD/ebin -pa $PWD/deps/*/ebin \
    -sname alice -s reloader -boot rest_app $1

Easy as pie.

To show you why this is a great setup, simply navigate to the project directory and type

make && start.sh

And your server should start right up.

A quick-tip I've picked up... get rstakeout so that anytime you change a file in the src/ directory, your application will recompile:

rstakeout "make" "src/**/*"

Lemme know if this helps, I love to hear feedback!

Download project files here

Update

I've added a generic makefile generator in my erlang snippet project: Skelerl. Get it on github and type:

makefile app_name