Tuesday, June 14, 2011

Beginning Scala: Building a project with Maven and Eclipse

Let us suppose that after many years languishing in the dark corridors of Java, frantically scrambling to avoid the various framework ghouls, one finally emerged into the light and saw a Scala frolicking in the sun on a much greener grassy field. The terse little Scala looks inviting to the verbose, ragged, framework beridden, somewhat aged Java developer ... but how to start? Well luckily there are a few nice introductions (Scala for Java Refugees, The busy Java developer's guide to Scala) out there and the staircase book (by the language creator no less) is a must if one is serious about learning the language.

Introductions are all well and good but as a Java developer I want some first-class tools and I want to run some code! Until recently first-class tools were seriously lacking. Luckily in recent months even the languages creator has acknowledged the tooling was lacking and has taken aggressive steps to correct the issue.

At the time of writing I use Eclipse 3.6.2 Helios (Java Developer edition) with Scala-IDE 2.0.0.somethingOrOther-beta. For the sake of this example I will assume we wish to build the project using Maven. First step, download the tools:
  1. Eclipse Helios
    1. At the time of writing I favor the Java developer edition as it is somewhat less bloated than the EE version; http://www.eclipse.org/downloads/packages/eclipse-ide-java-developers/heliossr2
  2. Scala IDE 2
    1. The 2.0 release is a major modification and the first solid Eclipse IDE plugin for Scala; the 1.x version was prone to all sorts of "exciting" behaviors
    2. Add to Eclipse using the update site from link on the front page of http://www.scala-ide.org/
  3. m2eclipse
    1. Add to Eclipse using the update site from http://m2eclipse.sonatype.org/installing-m2eclipse.html
  4. Maven 3
    1. Download from http://maven.apache.org/download.html
    2. Make sure if you run mvn -v in command prompt it prints the expected version
Now we have the tools, lets setup a project! First up, create a directory for your project and in that directory create a file called pom.xml similar to:
<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/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.blogspot.whileonefork</groupId>
  <artifactId>mvn-scala-trial1</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>Sample Project for Blog</name>
  <url>http://com.blogspot.whileonefork</url>
</project>

Next, create a directory named src containing a directory named main containing a directory named scala. Your project should now contain the following:
mvn-scala-project
|--pom.xml
`--src
   `--main
      `--scala

In the src/main/scala directory, create a new file HelloWorldJustLikeJava.scala with the simplest hello world implementation possible:
object HelloWorldJustLikeJava {
  def main(args:Array[String]) = {
    println("Hello, World");
  }
}

This, while arguably not the absolute simplest Scala hello world, is the definition closest to the one you would write in Java. Now to get it compiling in Maven. If you open a command prompt in the mvn-scala-project directory and run mvn clean install it will succeed but you will get a warning that no content was marked for inclusion; this occurs because by default Maven doesn't include the plugin necessary to build Scala code.

To compile Scala files we must update our pom.xml; we'll tell Maven what plugin to use to build Scala files and we'll advise it of the existence of a URL where it can find the plugin in question. Our updated pom.xml should look like this:
<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/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.blogspot.whileonefork</groupId>
  <artifactId>mvn-scala-trial1</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>Sample Project for Blog</name>
  <url>http://com.blogspot.whileonefork</url>
  
  <!-- Notify Maven it can download things from here -->
  <repositories>
    <repository>
      <id>scala-tools.org</id>
      <name>Scala-tools Maven2 Repository</name>
      <url>http://scala-tools.org/repo-releases</url>
    </repository>
  </repositories>  
  
  <dependencies>
    <!-- Scala version is very important. Luckily the plugin warns you if you don't specify: 
        [WARNING] you don't define org.scala-lang:scala-library as a dependency of the project -->
    <dependency>
      <groupId>org.scala-lang</groupId>
      <artifactId>scala-library</artifactId>
      <version>2.9.0-1</version>
    </dependency>
  </dependencies>
  
  <build>  
    <!-- add the maven-scala-plugin to the toolchain -->
    <plugins>
      <plugin>
        <groupId>org.scala-tools</groupId>
        <artifactId>maven-scala-plugin</artifactId>
        <version>2.14.2</version>
      </plugin>
    </plugins>
  </build>
</project>

If you run mvn scala:compile in the mvn-scala-project directory the project should build successfully and in /target/classes you should find two .class files: HelloWorldJustLikeJava$.class and HelloWorldJustLikeJava.class. Scala (usually) compiles the .class files to run on the JVM, just like Java.

All the usual Java tools can be used on these .class files; for example if we run javap on HelloWorldJustLikeJava$ we see the Scala source was compiled to Java; in this case fairly predictable Java:
public final class HelloWorldJustLikeJava$ extends java.lang.Object implements scala.ScalaObject{
    public static final HelloWorldJustLikeJava$ MODULE$;
    public static {};
    public void main(java.lang.String[]);
}
Digging a little deeper, using javap -verbose, we can see that our HelloWorldJustLikeJava class invokes HelloWorldJustLikeJava$:
public static final void main(java.lang.String[]);
  Code:
   Stack=2, Locals=1, Args_size=1
   0:   getstatic       #11; //Field HelloWorldJustLikeJava$.MODULE$:LHelloWorldJustLikeJava$;
   3:   aload_0
   4:   invokevirtual   #13; //Method HelloWorldJustLikeJava$.main:([Ljava/lang/String;)V
   7:   return
HelloWorldJustLikeJava$ provides a simple implementation of main and some other odds and ends:

public static final HelloWorldJustLikeJava$ MODULE$;

public static {};
  Code:
   Stack=1, Locals=0, Args_size=0
   0:   new     #9; //class HelloWorldJustLikeJava$
   3:   invokespecial   #12; //Method "":()V
   6:   return

public void main(java.lang.String[]);
  Code:
   Stack=2, Locals=2, Args_size=2
   0:   getstatic       #19; //Field scala/Predef$.MODULE$:Lscala/Predef$;
   3:   ldc     #22; //String Hello, World
   5:   invokevirtual   #26; //Method scala/Predef$.println:(Ljava/lang/Object;)V
   8:   return
  LineNumberTable:
   line 3: 0

  LocalVariableTable:
   Start  Length  Slot  Name   Signature
   0      9      0    this       LHelloWorldJustLikeJava$;
   0      9      1    args       [Ljava/lang/String;

The practice of outputting additional classes beyond what you directly coded is VERY common in Scala. A class using closures and other features of Scala will often output an exciting wack of $whatever classes.

Anyway, at this point the objective of making our project compile with Maven is achieved; we can now move on to getting it setup to edit in Eclipse. In the mvn-scala-project directory (the same one as pom.xml), create a .classpath file and a .project file:

.classpath
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
  <classpathentry kind="src" path="src/main/scala"/>
 <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
 <classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
 <classpathentry kind="con" path="org.scala-ide.sdt.launching.SCALA_CONTAINER"/>
 <classpathentry kind="output" path="target/classes"/>
</classpath>


.project
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
 <name>mvn-scala-project</name>
 <comment></comment>
 <projects>
 </projects>
 <buildSpec>
  <buildCommand>
   <name>org.scala-ide.sdt.core.scalabuilder</name>
   <arguments>
   </arguments>
  </buildCommand>
  <buildCommand>
   <name>org.maven.ide.eclipse.maven2Builder</name>
   <arguments>
   </arguments>
  </buildCommand>
 </buildSpec>
 <natures>
  <nature>org.maven.ide.eclipse.maven2Nature</nature>
  <nature>org.scala-ide.sdt.core.scalanature</nature>
  <nature>org.eclipse.jdt.core.javanature</nature>
 </natures>
</projectDescription>

At this point your project should contain the following:
mvn-scala-project
|--.classpath
|--.project
|--pom.xml
`--src
   `--main
      `--scala
         |--HelloWorldJustLikeJava.scala
`target
   `--classes
      |--HelloWorldJustLikeJava$.class
      |--HelloWorldJustLikeJava.class
If you happened to run mvn clean the target directory may be absent.

In Package Explorer in Eclipse your project should look similar to:


And now we can edit our Scala in Eclipse and compile with Maven, and can therefore easily load our project into just about any continuous integration server. Hooray!

EDIT: Assuming you are using m2eclipse, you will want to make sure your Eclipse IDE boots using a JDK VM; this is accomplished by editing eclipse.ini and adding a -vm argument. Bumping memory up a bit usually helps too; the file I am currently using on a Windows dev box follows:
-startup
plugins/org.eclipse.equinox.launcher_1.1.1.R36x_v20101122_1400.jar
--launcher.library
plugins/org.eclipse.equinox.launcher.win32.win32.x86_1.1.2.R36x_v20101222
-showsplash
org.eclipse.platform
--launcher.XXMaxPermSize
256m
-vm
C:/Program Files/Java/jdk1.6.0_07/bin/javaw.exe
--launcher.defaultAction
openFile
-product
org.eclipse.epp.package.java.product
--launcher.defaultAction
openFile
--launcher.XXMaxPermSize
256M
-vmargs
-Dosgi.requiredJavaVersion=1.5
-Xms40m
-Xmx768m
EDIT2: If you want the project to build a bit more gracefully in Eclipse consider binding Scala compile to run by default (eg when you run mvn clean install instead of the somewhat novel mvn scala:compile). Modify the plugin element to add the execution:
<build>  
    <!-- add the maven-scala-plugin to the toolchain -->
    <plugins>
      <plugin>
        <groupId>org.scala-tools</groupId>
        <artifactId>maven-scala-plugin</artifactId>
        <version>2.15.2</version>
        <executions>
            <execution>
                <goals><goal>compile</goal></goals>
            </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

6 comments:

Colin Sampaleanu said...

It doesn't seem correct to have the Scala container set up in Eclipse (via the "org.scala-ide.sdt.launching.SCALA_CONTAINER" in .classpath), since you are also pulling in Scala dependencies via Maven. I am running essentially the same setup for a couple of projects, minus the Scala container, and it seems to work fine...

Colin Sampaleanu

Rouzbeh Farahmand said...

I was wondering if I have java code this setup works?! i.e.
`--src
`--main
`--scala
|--HelloWorldJustLikeJava.scala
`--java
|-- HelloWorld.java
?!

Should I do anything special like http://mackaz.de/111

Thanks.

GuiSim said...

When I add the "compile" execution entry in the pom.xml I get the following error :

Plugin execution not covered by lifecycle configuration: org.scala-tools:maven-scala-plugin:2.14.2:compile (execution: default, phase: compile)

la Alice said...

blog Wordpress themes

aftabak said...

Really informative and useful information.
best wordpress themes

Naviya Nair said...

I have read your blog its very attractive and impressive. I like it your blog.

Java Online Training Java EE Online Training Java EE Online Training Java 8 online training Core Java 8 online training

Java Online Training from India Java Online Training from India Core Java Training Online Core Java Training Online Java Training InstitutesJava Training Institutes

Post a Comment