Tag Archives: build

Example usage of sbt build system

The sbt build system

This is a writeup explaining basic usage of the Simple Build Tool system for Scala. The complete build system is fairly comprehensive so I will only be focusing on aspects like setting up the project and using basic commands. A nice property of the build tool is its support for continuous compilation and testing. While you’re working on sourcefiles, the sbt tool will monitor changes then trigger recompilation and retesting whenever you modify code. By the time you need to check for errors in compilation or tests, a report might already be waiting for you.

For this example I’ll refer to a standard text editor and console. Alternatively, there exists sbt integration for IDEs like Eclipse for those that prefer that.

Getting started

Beginning, make sure you have a properly configured Java. Once that is in place you then need to download the sbt-launch.jar (0.13.1) library file. There are two options when using sbt. You can either install it system-wide, or include it with your project. I opted for the last one in this post.

With the download ready you can setup the project structure as follows:

project-name/
  src/
    main/scala/  -- your project sourcefiles goes here
    test/scala/  -- your project testfiles goes here
  lib/  -- manually handled library jars goes here
  build.sbt  -- your project's sbt config file
  sbt  -- sbt launchscript for unix
  sbt.cmd  -- sbt launchscript for windows
  sbt-launch.jar  -- the sbt library

Content of sbt file (if Unix mark it as executable using chmod u+x sbt):

#!/bin/bash
java -Xmx512M -jar $(dirname $(readlink -f "$0"))/sbt-launch.jar run "$@"

Content of sbt.cmd file:

@echo off
set SCRIPT_DIR=%~dp0
java -Xmx512M -jar "%SCRIPT_DIR%sbt-launch.jar" %*

Content of build.sbt file:

// Notes on syntax
//  - settings are initialized with :=
//  - dependency paths given by %
//  - dependency paths against specific scala versions are given by %%
name := "Project"
 
version := "1.0"
 
scalaVersion := "2.10.3"
 
libraryDependencies += "org.scalatest" % "scalatest_2.10" % "2.0" % "test"

With that the project ready for use. All that remains is running sbt from console to pull in required library dependencies (or later manually with update):

D:\test-scala3>sbt
Getting org.fusesource.jansi jansi 1.11 ...
downloading http://repo1.maven.org/maven2/org/fusesource/jansi/jansi/1.11/jansi-1.11.jar ...
        [SUCCESSFUL ] org.fusesource.jansi#jansi;1.11!jansi.jar (639ms)
:: retrieving :: org.scala-sbt#boot-jansi
        confs: [default]
        1 artifacts copied, 0 already retrieved (111kB/26ms)
Getting org.scala-sbt sbt 0.13.1 ...
...

Once started sbt will continue to accept commands (if you need to quit it, simply write exit).

Lets add the sourcefile /src/main/scala/demo/Demo.scala and compile it:

package demo
 
object Demo {
  def clamp(value: Double, min: Double, max: Double): Double = {
    if (value < min) min else if (value > max) max else value
  }
}
> compile
[info] Updating {file:}...
...
[info] Done updating.
[info] Compiling 1 Scala source to target\scala-2.10\classes...
[success] Total time: 2 s, completed 22.jan.2014 18:09:53

Lets also add a test /src/test/scala/demo/DemoSpec.scala and test our assumptions:

package demo
 
import org.scalatest.FlatSpec
import org.scalatest.matchers.ShouldMatchers
import Demo.clamp
 
class DemoSpec extends FlatSpec {
  "clamp" should "handle minimum cases" in {
    assert(clamp(-1.0, 0.0, 1.0) == 0.0)
  }
}
> test
[info] Compiling 1 Scala source to target\scala-2.10\test-classes...
[info] DemoSpec:
[info] clamp
[info] - should handle minimum cases
[info] Run completed in 339 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 2 s, completed 22.jan.2014 18:12:13

Instead of issuing these commands manually each iteration, lets instead enable automatic testing with ~test (the tilde tells sbt to run the command continuously).

> ~test
[info] DemoSpec:
[info] clamp
[info] - should handle minimum cases
[info] Run completed in 488 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 1 s, completed 22.jan.2014 18:14:15

Updating /src/test/scala/demo/DemoSpec.scala with additional test-cases now triggers recompile and retesting:

  it should "handle maximum cases" in {
    assert(clamp(2.0, 0.0, 1.0) == 1.0)
  }
[info] Compiling 1 Scala source to target\scala-2.10\test-classes...
[info] DemoSpec:
[info] clamp
[info] - should handle minimum cases
[info] - should handle maximum cases
[info] Run completed in 346 milliseconds.
[info] Total number of tests run: 2
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 2, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 2 s, completed 22.jan.2014 18:16:16
  it should "handle inrange cases" in {
    assert(clamp(0.9, 0.0, 1.0) == 0.5)
  }
[info] Compiling 1 Scala source to target\scala-2.10\test-classes...
[info] DemoSpec:
[info] clamp
[info] - should handle minimum cases
[info] - should handle maximum cases
[info] - should handle inrange cases *** FAILED ***
[info]   0.9 did not equal 0.5 (DemoSpec.scala:15)
[info] Run completed in 411 milliseconds.
[info] Total number of tests run: 3
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 2, failed 1, canceled 0, ignored 0, pending 0
[info] *** 1 TEST FAILED ***
[error] Failed tests:
[error]         demo.DemoSpec
[error] (test:test) sbt.TestsFailedException: Tests unsuccessful
[error] Total time: 2 s, completed 22.jan.2014 18:16:40

Here the tests did not complete. The error message points to where corrections are needed:

  it should "handle inrange cases" in {
    assert(clamp(0.5, 0.0, 1.0) == 0.5)
  }
[info] Compiling 1 Scala source to target\scala-2.10\test-classes...
[info] DemoSpec:
[info] clamp
[info] - should handle minimum cases
[info] - should handle maximum cases
[info] - should handle inrange cases
[info] Run completed in 372 milliseconds.
[info] Total number of tests run: 3
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 3, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 2 s, completed 22.jan.2014 18:17:59
5. Waiting for source changes... (press enter to interrupt)

If you want API documentation generated, simply issue the doc command then locate them in /target/scala-version/api:

> doc
[info] Main Scala API documentation to target\scala-2.10\api...
model contains 3 documentable templates
[info] Main Scala API documentation successful.
[success] Total time: 1 s, completed 22.jan.2014 18:18:25

Once editing of sourcecode is done you can issue package commands to generate the final libraries:

> package
[info] Packaging target\scala-2.10\project_2.10-1.0.jar ...
[info] Done packaging.
[success] Total time: 0 s, completed 22.jan.2014 18:18:39
> package-doc
[info] Packaging target\scala-2.10\project_2.10-1.0-javadoc.jar ...
[info] Done packaging.
[success] Total time: 0 s, completed 22.jan.2014 18:18:52
> package-src
[info] Packaging target\scala-2.10\project_2.10-1.0-sources.jar ...
[info] Done packaging.
[success] Total time: 0 s, completed 22.jan.2014 18:19:06

Further pointers

Presented here was just the absolute basics of sbt to get you started. There are tons of commands available within sbt (use tab-completion to show them all), additional configuration options, and plugins which extend it. If you want more in-depth information about sbt then visit its documentation page. Development on sbt is still ongoing so expect some changes in the future, but by including the sbt build system with your project you can largely ignore upgrades until you’re ready for them.

Setting up an Ant build script for your Scala project

Introduction

This is a small writeup which gives one way of setting up a build system for your Scala project. Here Apache Ant is used for managing this process, where the whole configuration is contained within a single build.xml file placed in the project’s root directory. Execution is done from command line or within your IDE environment.

Prerequisites

Install and configure Scala:

  • Define and set the SCALA_HOME variable in the OS environment
  • Update the PATH variable in the OS environment to include SCALA_HOME\bin directory

Install and configure Ant:

  • Define and set the ANT_HOME variable in the OS environment
  • Update the PATH variable in the OS environment to include ANT_HOME\bin directory
  • Optional: If you encounter memory problems, define and set ANT_OPTS=-Xmx500M with JVM memory configuration in the OS environment

Install and configure Java:

  • Define and set the JAVA_HOME variable in the OS environment
  • Update the PATH variable in the OS environment to include JAVA_HOME\bin directory

Find a suitable file structure for the project:

project-name/
  build/
  lib/
  src/
  test/
  build.xml

The Ant build script

Order of Ant targets:

  • build <- package <- doc <- compile <- init
  • clean <- init

Configure project-name/build.xml:

<?xml version="1.0" encoding="UTF-8"?>
 
<project name="ProjectName" default="build" basedir=".">
  <description>Project Build Script</description>
 
  <!-- targets -->
  <target name="build" depends="package" description="Build whole project"/>
 
  <target name="clean" depends="init" description="Remove previous build files">
    <delete dir="${build.dir}" includeemptydirs="true" quiet="true"/>
  </target>
 
  <target name="init">
    <property environment="env"/>
 
    <!-- check for required tools -->
    <fail message="Missing SCALA_HOME variable in OS environment">
      <condition><isset property="${env.SCALA_HOME}"/></condition>
    </fail>
    <fail message="Missing JAVA_HOME variable in OS environment">
      <condition><isset property="${env.JAVA_HOME}"/></condition>
    </fail>
 
    <!-- variables for paths and files -->
    <property name="src.dir" location="${basedir}/src"/>
    <property name="lib.dir" location="${basedir}/lib"/>
    <property name="build.dir" location="${basedir}/build"/>
    <property name="build-classes.dir" location="${build.dir}/classes"/>
    <property name="build-lib.dir" location="${build.dir}/lib"/>
    <property name="build-doc.dir" location="${build.dir}/doc"/>
    <property name="java.dir" location="${env.JAVA_HOME}"/>
    <property name="scala.dir" location="${env.SCALA_HOME}"/>
    <property name="scala-library.jar" location="${scala.dir}/lib/scala-library.jar"/>
    <property name="scala-reflect.jar" location="${scala.dir}/lib/scala-reflect.jar"/>
    <property name="scala-compiler.jar" location="${scala.dir}/lib/scala-compiler.jar"/>
 
    <path id="project.classpath">
      <pathelement location="${scala-library.jar}"/>
      <pathelement location="${build-classes.dir}"/> <!-- used during recompilation -->
    </path>
 
    <path id="scala.classpath">
      <pathelement location="${scala-compiler.jar}"/>
      <pathelement location="${scala-reflect.jar}"/>
      <pathelement location="${scala-library.jar}"/>
    </path>	
 
    <!-- load scala's ant tasks -->
    <taskdef resource="scala/tools/ant/antlib.xml" classpathref="scala.classpath"/>
 
    <!-- print where this project will get scala and java from -->
    <echo message="Init project"/>
    <echo message=" with scala.dir = ${scala.dir}"/>
    <echo message=" with java.dir = ${java.dir}"/>
 
    <!-- check if any files has been modified since last build -->
    <uptodate property="build.uptodate" targetfile="${build.dir}/build.done">
      <srcfiles dir= "${src.dir}" includes="**"/>
      <srcfiles dir= "${lib.dir}" includes="**"/>
    </uptodate>
  </target>
 
  <target name="compile" depends="init" unless="build.uptodate">
    <mkdir dir="${build-classes.dir}"/>
    <scalac
      destdir="${build-classes.dir}"
      classpathref="project.classpath">
      <include name="**/*.scala"/>
      <src><pathelement location="${src.dir}"/></src>
    </scalac>
  </target>
 
  <target name="doc" depends="compile" unless="build.uptodate">
    <mkdir dir="${build-doc.dir}"/>
    <scaladoc
      srcdir="${src.dir}"
      destdir="${build-doc.dir}"
      doctitle="Project API documentation"
      classpathref="project.classpath">
      <include name="**/*.scala"/>
    </scaladoc>
  </target>
 
  <target name="package" depends="doc" unless="build.uptodate">
    <mkdir dir="${build-lib.dir}"/>
    <jar destfile="${build-lib.dir}/project.jar">
      <fileset dir="${build-classes.dir}"/>
    </jar>
    <jar destfile="${build-lib.dir}/project-src.jar">
      <fileset dir="${src.dir}" includes="**/*.scala"/>
    </jar>
    <jar destfile="${build-lib.dir}/project-doc.jar">
      <fileset dir="${build-doc.dir}"/>
    </jar>
    <touch file="${build.dir}/build.done"/> <!-- mark build as up-to-date -->
  </target>
</project>

References