Posted on Friday, 19th December 2008 by charpi

In my previous post, I planned to have a closer look at the ruby’s
world. As the erlang world misses a real compilation tool, I’ve decided
to make a attempt to use rake.

Usually, Erlangers use make to build their
application. It’s a powerful tool but its syntax sucks a bit. With
this complex syntax it’s hard to keep makefiles clean and with time
they become very obscure.
There are a lot of other popular construction tools:

  • Ant : But I don’t like to edit XML files
  • Scons: Written in python. Some erlang tasks exist but I never had
    time to look at it.
  • Rake: The make utility written in ruby. Rails integrates rake
    tasks to automate all the development process.

I tried rake to replace makefiles on my personal projects looking at
those posts as example Building erlang with rake and Erlang – Make, Rake and Emake.

Usually, my erlang projects are organized according to OTP principles.
All applications are under a lib directory, so I decided to put the
Rakefile just under the $TOP_DIR

$TOP_DIR/
        Rakefile
        lib/
                App_1/
                        ebin/
                        src/
                        include/
                        priv/
                App_2/
                        ebin/
                        src/
                        include/
                        priv/

Here are the basic rake rule and task to build erlang beam files

ERL_TOP="Path to your erlang installation"
ERLC_FLAGS = "+warn_unused_vars +warn_unused_import"

ERL_BEAM = FileList['lib/*/src/*.erl'].pathmap("%{src,ebin}X.beam")

ERL_DIRECTORIES = FileList.new('lib/*/src').pathmap("%{src,ebin}X")

ERL_DIRECTORIES.each do |d|
  directory d
  CLEAN.include d
end

rule ".beam" =>  ["%{ebin,src}X.erl"] do |t|
  output = t.name.pathmap("%d")
  sh "#{ERL_TOP}/bin/erlc -Ilib #{ERLC_FLAGS} -o #{output} #{t.source}"
end

desc "Compile Erlang sources"
multitask :erlang_modules => ERL_DIRECTORIES + ERL_BEAM

But an erlang application isn’t only beam files, we got several other
files to produce: .app, .rel, .boot and .script for example.

I made a Rakefile quite quite complete for erlang applications.

# Copyright (c) 2008 Nicolas Charpentier.
# Licence BSD

require 'rake'
require 'rake/clean'

ERL_TOP="Path_to_your_erlang_installation"
ERLC_FLAGS = "+warn_unused_vars +warn_unused_import"

ERL_BEAM = FileList['lib/*/src/*.erl'].pathmap("%{src,ebin}X.beam")

src_to_ebin = "%{src,ebin}X"

ERL_DIRECTORIES = FileList.new('lib/*/src').pathmap(src_to_ebin)

ERL_APPLICATIONS = FileList.new('lib/*/src/*.app.src')\
                            .pathmap(src_to_ebin)

ERL_RELEASE_FILES=FileList.new()
release_files = FileList.new('lib/*/src/*.rel.src')\
                        .pathmap(src_to_ebin)
release_files.each do |d|
  config_file = d.pathmap("%{ebin,src}d/../vsn.config")
  script = "scripts/make_version_info"
  vsn = `#{ERL_TOP}/bin/escript #{script} #{config_file} release_name`
  ERL_RELEASE_FILES.add d.pathmap("%X-#{vsn}.rel")
end

ERL_BOOT_FILES = ERL_RELEASE_FILES.pathmap("%{src,ebin}X.boot")
ERL_RELEASE_ARCHIVES = ERL_RELEASE_FILES.pathmap("distribs/%f")\
                                        .ext(".tar.gz")

ERL_RELEASE_ARCHIVES.each do |d|
  CLEAN.include d
end

directory "distribs"

CLEAN.include "targets"

ERL_DIRECTORIES.each do |d|
  directory d
  CLEAN.include d
end

rule ".beam" =>  ["%{ebin,src}X.erl"] do |t|
  output = t.name.pathmap("%d")
  sh "#{ERL_TOP}/bin/erlc -Ilib #{ERLC_FLAGS} -o #{output} #{t.source}"
end

rule ".app" => ["%{ebin,src}X.app.src",
                "%{ebin,src}d/../vsn.config"] do |t|
  appscript = '\'$$vsn=shift; $$mods=""; while(@ARGV){ $$_=shift;'\
  's/^([A-Z].*)$$/\'\'\'$$1\'\'\'/; if ($$mods) {$$mods.=", "} ;'\
  ' $$mods .= $$_; } while(<>) { s/%VSN%/$$vsn/; s/%MODULES%/$$mods/;'\
  'print; }\''
  script = "scripts/make_version_info"
  configuration = t.name.pathmap("%d/../vsn.config")
  vsn = `#{ERL_TOP}/bin/escript #{script} #{configuration} vsn`
  modules = FileList.new(t.name.pathmap("%d/*.beam"))\
                    .pathmap("%f").ext("")
  sh "perl -e #{appscript} #{vsn} #{modules} < #{t.source} > #{t.name}"
end

rule ".rel" => [proc {|a| a.split('-')[0..-2].join('-')\
                  .pathmap("%{ebin,src}X.rel.src")}] do |t|
  script = "scripts/make_version_info"
  configuration = t.name.pathmap("%d/../vsn.config")
  vsn = `#{ERL_TOP}/bin/escript #{script} #{configuration} release_name`
  output = t.name.pathmap("%X.rel")
  sh "#{ERL_TOP}/bin/escript scripts/make_release_file "\
     "#{t.source} #{output} #{vsn} #{ERL_DIRECTORIES}"
 end

rule ".boot" => [".rel"] do |t|
  output = t.name.pathmap("%d")
  source = t.source.ext("")
  script = "scripts/make_script"
  sh "#{ERL_TOP}/bin/escript #{script} distribs #{source} #{output}"
end

rule ".tar.gz" => [proc {|a|
                     FileList.new(a.ext("").ext("")\
                                  .pathmap("lib/*/ebin/%f.rel"))},
                   "distribs"] do |t|
  source = t.source.ext("")
  script = "scripts/make_release"
  sh "#{ERL_TOP}/bin/escript #{script} #{source} distribs without "\
  "#{ERL_TOP} #{ERL_DIRECTORIES}"
end

desc "Compile Erlang sources"
multitask :erlang_modules => ERL_DIRECTORIES + ERL_BEAM

desc "Build application resource file"
task :erlang_applications => [:erlang_modules] + ERL_APPLICATIONS

desc "Build erlang boot files"
task :erlang_release_files => [:erlang_applications] +
  ERL_RELEASE_FILES  + ERL_BOOT_FILES

desc "Build release tarball"
task :erlang_releases => [:erlang_release_files] + ERL_RELEASE_ARCHIVES

desc "Build release tarball with erts"
task :erlang_target_systems, :n, :needs=> [:erlang_release_files] +
  ERL_RELEASE_ARCHIVES do |t, args|
  source = FileList.new("lib/*/ebin/#{args.n}*.rel").ext("")
  mkdir "targets" rescue has_errors = true
  script = "scripts/make_release"
  sh "#{ERL_TOP}/bin/escript #{script} #{source} targets with "
  "#{ERL_TOP} #{ERL_DIRECTORIES}"
end

CLEAN.include "lib/*/doc/*.html"
CLEAN.include "lib/*/doc/*.css"
CLEAN.include "lib/*/doc/*.png"
CLEAN.include "lib/*/doc/edoc-info"

desc "Buid Application documentation"
task :edoc, :name, :needs => [:erlang_applications] do |t,args|
  script = "scripts/make_doc"
  sh "#{ERL_TOP}/bin/escript #{script} #{args.name} #{ERL_DIRECTORIES}"
end

desc "Buid all application documentation"
task :edocs => [:erlang_applications] do |t,args|
  ERL_APPLICATIONS.each do |application|
    name = application.pathmap("%f").ext("")
    script = "scripts/make_doc"
    sh "#{ERL_TOP}/bin/escript #{script} #{name} #{ERL_DIRECTORIES}"
  end
end

desc "Compile all project"
task :compile => [:erlang_modules, :erlang_applications]

task :default => [:erlang_releases]

To be usable you must add to your source tree some files:

$TOP_DIR/
        Rakefile
        scripts/
                make_doc
                make_release
                make_release_file
                make_script
                make_version_info
        lib/
                App_1/
                        vsn.config
                        ebin/
                        src/
                        include/
                        priv/
                App_2/
                        vsn.config
                        ebin/
                        src/
                        include/
                        priv/

All files in the scripts directory are escript files. You’ll find them
in [download#7]
The vsn.config file is an erlang config file containing version
information.

{vsn,"0.1"}. %% Application version
{release_name, "Name of the release"}. %% Only needed for Apps
                                       %% containing a release

It’s still a work in progress but it’s usable. This Rakefile misses
several things:

  • Dependency between .erl and .hrl
  • Task and rule to launch unit tests (eunit or extremeforge)
  • Driver and port compilation
  • A better packaging of the application (start scripts, ….)

Of course, I’ll be glad to receive your comments and improvements.

Tags: ,
Posted in Uncategorized | Comments (3,884)

Comments are closed.