Sunday, August 23, 2009

Playing with Scala and Maven

Recently, I've taken up an interest in Scala and wanted to try it out with Maven. Luckily, people have already blazed this path for us by developing a Scala plugin for Maven. In this post, writing as I go, I'm going to play with what we can do with this plugin to get started.

I began looking for a Scala project archetype and wanted to generate an instace of it to get started.

To begin, I added the following repositores to my Maven Settings via ~/.m2/settings.xml:

<settings>
<activeProfiles>
<activeProfile>repos</activeProfile>
</activeProfiles>
<profiles>
<profile>
<id>repos</id>
<repositories>
<repository>
<id>scala-tools.org</id>
<name>Scala-tools Maven2 Repository</name>
<url>http://scala-tools.org/repo-releases</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>scala-tools.org</id>
<name>Scala-tools Maven2 Repository</name>
<url>http://scala-tools.org/repo-releases</url>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
</settings>
I then ran:
  $ mvn archetype-generate
And was presented with a Scala project:
  35: internal -> scala-archetype-simple (A simple scala project)
Using the option, I answered the usual set of questions and a sample project was created:
Define value for groupId: : prystasj.scala
Define value for artifactId: : maven-test
Define value for version: 1.0-SNAPSHOT: :
Define value for package: prystasj.scala: :
Confirm properties configuration:
groupId: prystasj.scala
artifactId: maven-test
version: 1.0-SNAPSHOT
package: prystasj.scala
Y: :
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating OldArchetype: scala-archetype-simple:1.2
[INFO] ----------------------------------------------------------------------------
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
The resulting project structure:
  ./pom.xml
./src/test/scala/prystasj/scala/AppTest.scala
./src/test/scala/prystasj/scala/MySpec.scala
./src/main/scala/prystasj/scala/App.scala
The resulting POM file added the repositores I defined in my settings file earlier so I removed them. In addition, the Maven Eclipse Plugin was present in the list of plugins used in the build section, so I also removed that (not because I don't like Eclipse, but rather I want a simpler POM to play with here), leaving me with:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>prystasj.scala</groupId>
<artifactId>maven-test</artifactId>
<version>1.0-SNAPSHOT</version>
<inceptionYear>2009</inceptionYear>
<properties>
<scala.version>2.7.0</scala.version>
</properties>
<dependencies>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.specs</groupId>
<artifactId>specs</artifactId>
<version>1.2.5</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/scala</sourceDirectory>
<testSourceDirectory>src/test/scala</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.scala-tools</groupId>
<artifactId>maven-scala-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
<configuration>
<scalaVersion>${scala.version}</scalaVersion>
<args>
<arg>-target:jvm-1.5</arg>
</args>
</configuration>
</plugin>
</plugins>
</build>
<reporting>
<plugins>
<plugin>
<groupId>org.scala-tools</groupId>
<artifactId>maven-scala-plugin</artifactId>
<configuration>
<scalaVersion>${scala.version}</scalaVersion>
</configuration>
</plugin>
</plugins>
</reporting>
</project>

Without touching anything, else I'm going to package her up, here I go:

  $ mvn package
...
[INFO] [scala:testCompile {execution: default}]
[INFO] Compiling 2 source files to /home/prystasj/workspace/prystasj/scala/maven-test/target/test-classes
[INFO] use java command with args in file forced : false
[INFO] [surefire:test]

------------------------------------------------------
T E S T S
-------------------------------------------------------
Running prystasj.scala.MySpecTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.093 sec
Running prystasj.scala.AppTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.044 sec

Results :

Tests run: 2, Failures: 0, Errors: 0, Skipped: 0

[INFO] [jar:jar]
[INFO] Building jar: /home/prystasj/workspace/prystasj/scala/maven-test/target/maven-test-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------

After Maven downloaded what was needed, the source compiled and the tests run, a JAR was built in the target directory.

I was curious about the configuration of the Scala plugin, namely:

  <configuration>
<scalaVersion>${scala.version}</scalaVersion>
<args>
<arg>-target:jvm-1.5</arg>
</args>
</configuration>

The target argument is a parameter passed to scalac to determine which target JVM the source files should be built for. Since I'm using Java 1.6, I could update the configuration, or give completely removing it a shot. The usage examples on the plugin web page do not include its use, so I opted to remove it to see what happened. After doing so, I rebuilt my project and everything ran just fine, although I probably should investigate more and compare the results more deeply.

Turning to the dependencies defined in the POM, Specs is a behavior-driven-design framework along the lines of others like EasyB. It also pulls in several other dependencies used for testing:

  $ mvn dependency-tree
[INFO] [dependency:tree]
[INFO] prystasj.scala:maven-test:jar:1.0-SNAPSHOT
[INFO] +- org.scala-lang:scala-library:jar:2.7.0:compile
[INFO] +- junit:junit:jar:4.4:test
[INFO] \- org.specs:specs:jar:1.2.5:test
[INFO] +- org.scalatest:scalatest:jar:0.9.1:test
[INFO] +- org.scalacheck:scalacheck:jar:1.2:test
[INFO] \- org.jmock:jmock:jar:2.4.0:test
[INFO] +- org.hamcrest:hamcrest-core:jar:1.1:test
[INFO] \- org.hamcrest:hamcrest-library:jar:1.1:test

Since I'm not going to go that route with my first test project (as I haven't had the change to play with Specs yet), I replaced the dependency with dependencies on scalatest and scalacheck:

    <dependency>
<groupId>org.scalatest</groupId>
<artifactId>scalatest</artifactId>
<version>0.9.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.scalacheck</groupId>
<artifactId>scalacheck</artifactId>
<version>1.2</version>
<scope>test</scope>
</dependency>
The resulting dependency tree is as follows:
  [INFO] [dependency:tree]
[INFO] prystasj.scala:maven-test:jar:1.0-SNAPSHOT
[INFO] +- org.scala-lang:scala-library:jar:2.7.0:compile
[INFO] +- junit:junit:jar:4.4:test
[INFO] +- org.scalatest:scalatest:jar:0.9.1:test
[INFO] \- org.scalacheck:scalacheck:jar:1.2:test

Since src/test/scala/prystasj/scala/MySpec.scala uses the Spec library, I also removed the test class from the project.

Turning my attention to what's left, App.scala is a simple Singleton Object:

package prystasj.scala

object App extends Application {
println( "Hello World!" )
}

A singleton object of course has one and only instance. If we had a class named App, the singleton object would be called a companion to that class, but here, it's just a standalone object. A singleton object can be seen as Scala's answer to static members, as Scala cannot have them, making Scala more object-oriented.

The singleton object uses the Application trait, which removes the need to write the Hello World example in a way that may more familar to Java programmers:

object App {
def main(args: Array[String]) {
println("Hello World!")
}
}
But this entry is supposed to be about Scala & Maven, so I might be getting off track.

Back to using Maven, let's see if we can run the App class from the command line with the Exec Plugin:

$ mvn package exec:java -Dexec.mainClass="prystasj.scala.App"
[INFO] Scanning for projects...
..
[INFO] [exec:java]
Hello World!
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------

...And we see the greeting in the output.

What about site generation? The POM currently doesn't have a location defined to distribute the web site too, but let's place it in the directory we're working in by adding a distrubution management section to the POM:

  <distributionManagement>
<site>
<url>file://${user.dir}/site</url>
</site>
</distributionManagement>

...And let's generate a web site for the project:

  $ mvn site-deploy

[INFO] Generating "ScalaDocs" report.
...
[INFO] [site:deploy]
file:///home/prystasj/workspace/scala/maven-test/site - Session: Opened
file:///home/prystasj/workspace/scala/maven-test/site - Session: Disconnecting
file:///home/prystasj/workspace/scala/maven-test/site - Session: Disconnected
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
From the newly created website, a ScalaDocs report was created, here's a small sample:

  Object Summary
    object App extends scala.Application


Ah, that's a lot of writing for this sitting (for me at least). I'll leave with the POM I have so far for reference:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>prystasj.scala</groupId>
<artifactId>maven-test</artifactId>
<version>1.0-SNAPSHOT</version>
<inceptionYear>2008</inceptionYear>
<properties>
<scala.version>2.7.0</scala.version>
<scala.plugin.version>2.11</scala.plugin.version>
</properties>
<dependencies>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.scalatest</groupId>
<artifactId>scalatest</artifactId>
<version>0.9.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.scalacheck</groupId>
<artifactId>scalacheck</artifactId>
<version>1.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/scala</sourceDirectory>
<testSourceDirectory>src/test/scala</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.scala-tools</groupId>
<artifactId>maven-scala-plugin</artifactId>
<version>${scala.plugin.version}</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<reporting>
<plugins>
<plugin>
<groupId>org.scala-tools</groupId>
<artifactId>maven-scala-plugin</artifactId>
<version>${scala.plugin.version}</version>
<configuration>
<scalaVersion>${scala.version}</scalaVersion>
</configuration>
</plugin>
</plugins>
</reporting>
<distributionManagement>
<site>
<url>file://${user.dir}/site</url>
</site>
</distributionManagement>
</project>

Resources:

8 comments:

  1. Hi,

    I just want to add about that there should be no mandatory specs dependencies. I don't remember what was the state of 1.2.5 to that respect but the current version (1.6.0-SNAPSHOT) indeed has only optional dependencies which you only need to get if you need the corresponding feature.

    Eric.

    ReplyDelete
  2. You should always state the version of the plugin. In your example the scala plugin is unversioned (resulting in non deterministic builds over long periods of time)

    The declaration of SourceDirectory and testSourceDirectory should not be in general necessary (might be useful in some IDEs, eg. Netbeans 6.7 Scala plugin will not be able to have separate source root for Java and for Scala files)

    For NetBeans (primarily for upcoming 6.8, but should work in 6.7 as well) I've created a code generation plugin that generates all that is necessary to use Scala in any existing Maven project. It will do some evaluation of the existing project before generating, so for example in "pom" packaging projects, it will only generate the pluginManagement section Or when you already declare the version in parent pom, it will omit it in child projects. See http://kenai.com/projects/nb-maven-generators

    ReplyDelete
  3. I agree completely with declaring the plugin version, in this case it's 2.11. Thanks for catching it. I've updated the entry to include it.

    ReplyDelete
  4. Hi John,

    We should update that archetype. It is using a very old version of specs, and should probably have ScalaTest and ScalaCheck in it already, at the most recent releases. You by hand brought in ScalaTest 0.9.1, but the latest is 0.9.5. You can grab it from:

    groupId: org.scala-tools/testing
    artifactId: scalatest
    version: 0.9.5

    ScalaTest 1.0 will be out soon. There's a snapshot of it at scala-tools.org now as well under the org.scalatest groupId.

    ReplyDelete
  5. Thanks for the info Bill, I'll be sure to try those suggestions out.

    ReplyDelete
  6. To run app by just "mvn exec:java" add the following to the POM inside //build/plugins:

    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-exec-plugin</artifactId>
    <configuration>
    <mainClass>example.scala.storepos.App</mainclass>
    </configuration>
    </plugin>

    ReplyDelete
  7. The new Scala archetypes start with "net.liftweb"

    See: http://scala-tools.org/repo-releases/net/liftweb/

    Too bad the simple (non-Lift) archetype is gone now.

    ReplyDelete
  8. I want to learn Scala , hearing lot of buzz about it , Can you please suggest where to start and some useful links thanks in advance.

    Javin
    What is Garbage collection in Java ? How GC works in Java

    ReplyDelete