Chapter 5
Maven

While Scala has established itself as the de facto language of choice in certain areas, for example streaming analytics, many organizations still carry a technological or political legacy that prevents wholesale tooling and language transitions. In this situation incremental change is your ally. In concrete terms, you might not be able to use SBT and Scala, but if you are already working in a Maven based environment you can add Scala and learn how to coexist with Java source code in the same project with little disruption. Using Scala with Maven requires no additional installation activities, thereby preserving any current Jenkins builds you might have configured.

In this chapter, you will learn how to work with Maven to manage your Scala-based project. Along the way, you can see how Maven is extended to support Scala to a similar level as Java, how you can use Scala at an interactive prompt, and how you can slash compile times via configuration.

This chapter recognizes that no build lifecycle is complete without a dash of testing, so you will find out how to integrate ScalaTest with the assistance of Maven. You will learn how to leverage the power of compilation as a service, and see how Java and Scala sources can be combined in the same project.

GETTING STARTED WITH MAVEN AND SCALA

This chapter assumes you have prior experience working with Maven and you are familiar with the key notions of goals, phases, lifecycles, dependencies, plugin configuration, and the use of Maven from the command line. You should also be familiar with using Maven from your IDE.

If you need a Maven refresher, head off to https://maven.apache.org for a couple of hours and resume this chapter once you are up to speed. As a further preparation, you should have Maven installed, and be able to execute the command shown here.

$ mvn --version
Apache Maven 3.3.9
  (bb52d8502b132ec0a5a3f4c09453c07478323dc5; 2015-11-10T16:41:47+00:00)
Maven home: /usr/share/maven3
Java version: 1.8.0_60, vendor: Oracle Corporation
Java home: /usr/lib/jvm/java-8-oracle/jre
Default locale: en_GB, platform encoding: UTF-8
OS name: "linux", version: "3.13.0-63-generic", arch: "amd64", family: "unix"

Any version of Maven from 3.0.5 onward should be fine.

With these prerequisites met you are ready to work through the examples below to create a Scala aware Maven project.

Step 1. Start by adding a minimal POM at the root level of your project folder as shown below. Name the file pom.xml, which stands for Project Object Model.

<?xml version="1.0" encoding="UTF-8"?>
<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>com.scalacraft</groupId>
    <artifactId>professional-scala</artifactId>
    <version>0.1.0-SNAPSHOT</version>

    <properties>
        <scala.version>2.11.7</scala.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>${scala.version}</version>
        </dependency>
        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-compiler</artifactId>
            <version>${scala.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>net.alchim31.maven</groupId>
                <artifactId>scala-maven-plugin</artifactId>
                <version>3.2.2</version>
                <executions>
                    <execution>
                        <id>compile</id>
                        <goals>
                            <goal>compile</goal>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

You should take a moment to run this. Only the final part of the output is shown next.

$ mvn clean install

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.739s
[INFO] Finished at: Tue Jan 26 22:18:52 GMT 2016
[INFO] Final Memory: 11M/245M
[INFO] ------------------------------------------------------------------------

Process finished with exit code 0

Provide some Scala source code to put this POM to work.

Step 2. Create a source directory layout as shown here.

$ mkdir -p src/main/scala/com/scalacraft/professionalscala/chapter5
$ mkdir -p src/test/scala/com/scalacraft/professionalscala/chapter5

The first directory will contain your main application code, or the second test code. Now, enter the Scala class Probe as shown below, into the main source code hierarchy.

package com.scalacraft.professionalscala.chapter5

class Probe {
  def touchdown: Unit = println("Hello, New World!")
}

Step 3. Build the project and confirm some compilation took place.

$ mvn clean install

[INFO] BUILD SUCCESS

$ find target/ -name *.class
target/classes/com/scalacraft/professionalscala/chapter5/Probe.class

Before you learn about the details of the Maven plugin that enables the compilation, be sure to jump onto the command line and spark up the Scala REPL via the plugin's console goal. Create an instance of Probe and send a message to it.

$ mvn scala:console
Type in expressions to have them evaluated.
Type :help for more information.

scala> import com.scalacraft.professionalscala.chapter5._
import com.scalacraft.professionalscala.chapter5._

scala> new Probe touchdown
Hello, New World!

scala>

EPFL we have a touchdown!

INTRODUCING SCALA-MAVEN-PLUGIN

By the end of this section you will have a working overview of the essentials of the scala-maven-plugin. Through running some examples, you will gain a compact, but practical overview.

The scala-maven-plugin is the de facto Maven plugin for working with Scala under Maven. Along one dimension it can be thought of as being an adapter sitting between Maven and the Scala compiler. However, that is not the sum of it. In later sections you will see how the plugin goes beyond a simple compiler adapter. First, let's examine an illustration of the adapter aspect. Follow these steps to start unpeeling the plugin behavior beginning with the link from POM configuration through to the Scala compiler.

Step 1. Add some configuration to the plugin execution using the <configuration> element as shown here.

<plugin>
    <groupId>net.alchim31.maven</groupId>
    <artifactId>scala-maven-plugin</artifactId>
    <version>3.2.2</version>
    <executions>
        <execution>
            <id>compile</id>
            <goals>
                <goal>compile</goal>
                <goal>testCompile</goal>
            </goals>
            <configuration>
                <displayCmd>true</displayCmd>
            </configuration>
        </execution>
    </executions>
</plugin>

When displayCmd is true, the command line used to invoke the Scala compiler is dumped to the Maven log.

Step 2. Build the project.

$ mvn clean install

Scrutinize the logging output until you locate the dumped command line. This is formatted below for clarity. The final line is of the most relevance for this discussion.

[INFO] cmd:
C:Program FilesJavajdk1.8.0_66jreinjava
-Xbootclasspath/a:
C:Usersjdb.m2
epositoryorgscala-langscala-library2.11.7
  scala-library-2.11.7.jar;
C:Usersjdb.m2
epositoryorgscala-langscala-compiler2.11.7
  scala-compiler-2.11.7.jar;
C:Usersjdb.m2
epositoryorgscala-langscala-reflect2.11.7
  scala-reflect-2.11.7.jar;
C:Usersjdb.m2
epositoryorgscala-langscala-library2.11.6
  scala-library-2.11.6.jar;
C:Usersjdb.m2
epositoryorgscala-langmodulesscala-parser-combinators_2.11
  1.0.4scala-parser-combinators_2.11-1.0.4.jar;
C:Usersjdb.m2
epositoryorgscala-langscala-library2.11.4
  scala-library-2.11.4.jar;
C:Usersjdb.m2
epositoryorgscala-langmodulesscala-xml_2.11
  1.0.4scala-xml_2.11-1.0.4.jar
 -classpath
C:Usersjdb.m2
epository
etalchim31mavenscala-maven-plugin3.2.2
  scala-maven-plugin-3.2.2.jar
scala_maven_executions.MainWithArgsInFile scala.tools.nsc.Main
C:UsersjdbAppDataLocalTempscala-maven-1801724026178648094.args

You may have spotted the presence of multiple versions of the same libraries in the bootstrap JARs. This is not known to cause any problems. If you see this and are curious you can join the scala-maven-plugin community at https://groups.google.com/forum/#!forum/maven-and-scala and find out more.

The main class is MainWithArgsInFile, which calls scala.tools.nsc.Main, supplying the contents of an args file as the arguments to the compiler class in scala.tools.nsc.Main. This is shown below where you can see the plugin is handling the details of mapping the POM configuration to equivalent compiler options.

-classpath
C:Usersjdb.m2
epositoryorgscala-langscala-library2.11.7
  scala-library-2.11.7.jar;
C:Usersjdb.m2
epositoryorgscala-langscala-compiler2.11.7
  scala-compiler-2.11.7.jar;
C:Usersjdb.m2
epositoryorgscala-langscala-reflect2.11.7
  scala-reflect-2.11.7.jar;
C:Usersjdb.m2
epositoryorgscala-langmodulesscala-xml_2.11
  1.0.4scala-xml_2.11-1.0.4.jar;
C:Usersjdb.m2
epositoryorgscala-langmodulesscala-parser-combinators_2.11
  1.0.4scala-parser-combinators_2.11-1.0.4.jar

-d
C:Usersjdbworkspacesmainprofessional-scala	argetclasses
C:Usersjdbworkspacesmainprofessional-scalasrcmain
  scalacomscalacraftprofessionalscalachapter5Probe.scala

Exercise the link from POM to compiler once more.

Step 3. Add the compiler options shown here to the plugin configuration section.

<configuration>
    <args>
        <arg>-verbose</arg>
        <arg>-Xgenerate-phase-graph</arg>
        <arg>phase-graph</arg>
    </args>
    <displayCmd>true</displayCmd>
</configuration>

Step 4. Build the project.

$ mvn clean install

The logging output is excerpted below showing the effect of adding -verbose.

[INFO] [loaded package loader lang in 14ms
[INFO] [loaded package loader annotation in 1ms
[INFO] [promote the dependency of lazyvals: erasure => posterasure
[INFO] [promote the dependency of explicitouter: tailcalls => specialize
[INFO] Phase graph of 25 components output to phase-graph*.dot.

Step 5. Check the temporary args file to see the POM configuration passed through to the compiler command. The head of the args files is shown:

-verbose
-Xgenerate-phase-graph
phase-graph
-classpath

To see a compiler generated phase diagram, point your browser to https://goo.gl/O7kGiv.

This concludes the nickel tour of the scala-maven-plugin from a compiler adapter perspective. You now know how to dive through the layers from the POM to the compiler should the need ever arise. To see the full range of options applicable to the compiler visit http://davidb.github.io/scala-maven-plugin/compile-mojo.html. You will see further compiler options in use later.

ADDING LIBRARY DEPENDENCIES

Now that you have Maven compiling your code, pull in an additional Scala library by adding a new dependency that you will need for the REPL section. Follow these steps to add atto, an incremental text-parsing library, into your project as a dependency.

Step 1. Isolate the library version into the <properties> section as shown below.

<properties>
    <scala.version>2.11.7</scala.version>
    <atto.version>0.4.2</atto.version>
</properties>

Factoring out library versions into properties is an advisable practice from a maintenance point of view.

Step 2. Navigate to the <dependencies> section and add the new dependency shown here.

<dependency>
    <groupId>org.tpolecat</groupId>
    <artifactId>atto-core</artifactId>
    <version>${atto.version}</version>
</dependency>

Step 3. Build the project.

$ mvn clean install

You will see the POM and JAR for atto-core being downloaded and then the build will complete as before. All of this may appear somewhat pedestrian, and that is in fact the point. Using Scala libraries in Maven is exactly the same as using Java libraries.

USING THE REPL

How else can the plugin power up your everyday development activities? One way is by providing frictionless access to the Scala REPL with a classpath matching your project. Build up your acquaintance with this powerful Scala feature by following these steps. Note that the Scala console is not a Maven feature. What you see in the following is how Maven understands your project dependencies when running the console via a plugin goal.

Step 1. In the root level of your project folder run Maven specifying the console goal, which is provided by the scala-maven-plugin. The console output has been edited for brevity.

$ mvn  scala:console
[INFO] Scanning for projects…
[INFO
[INFO] ------------------------------------------------------------------------
[INFO] Building professional-scala 0.1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO
[INFO] --- scala-maven-plugin:3.2.2:console (default-cli) @ professional-scala ---
Welcome to Scala version 2.11.6 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_60).
Type in expressions to have them evaluated.
Type :help for more information.

scala>

Step 2. Type in these commands. Start with imports that use atto-core:

scala> import atto._
import atto._
scala> import Atto._
import Atto._                                         ^

Now define a val. This is a parser combinator that parses a dot.

scala> val dot = char('.')
dot: atto.Parser[Char] = '.'

Next, build up a version number parser using predefined parsers from the library and your own dot parser.

scala> val versionParser = many(digit) ~ dot ~ many(digit)
versionParser: atto.Parser[((List[Char], Char), List[Char])] =
  ((many(digit)) ~ '.') ~ many(digit)

To cap it all off throw a string at the parser and review the output.

scala> versionParser parseOnly "2.11"
res0: atto.ParseResult[((List[Char], Char), List[Char])] =

 Done(,((List(2),.),List(1, 1)))

The Scala console is a powerful ally and with Maven integration simplifying access, you will be able to explore new libraries interactively with no classpath management beyond the POM.

GETTING HELP

Help is available for the scala-maven-plugin through the standard Maven help plugin. Execute this command to see a summary of the plugin goals. Append -Ddetail to see an expanded version of the help. The results are summarized in Table 5.1.

$ mvn help:describe -Dplugin=net.alchim31.maven:scala-maven-plugin

Table 5.1 Plugin Goals

Goal Description
add-source Add more source directories to the POM.
cc Continuously compile the main and test sources. For use on the CLI only.
cctest Continuously compile the main and test sources then run unit test cases. For use on the CLI only.
compile Compile the main Scala sources.
console Run the Scala console with all project classes and dependencies available.
doc Produce Scala API documentation.
doc-jar Create a jar of non-aggregated Scaladoc for distribution.
help Display the Scala compiler help.
run Run a Scala class using the Scala runtime.
script Run a Scala script.
testCompile Compile the test Scala sources.

RUNNING TESTS

You need to test your space probe before deploying it. Do this now via the ScalaTest Maven plugin. Follow the steps below to add the plugin, create, and run the smoke test.

Step 1. Add a dependency on ScalaTest to your POM. The details are shown below. Drop these changes into the appropriate POM locations.

<properties>
    <scala.version>2.11.7</scala.version>
    <atto.version>0.4.2</atto.version>
    <scalatest.version>2.2.6</scalatest.version>
</properties>

<dependency>
    <groupId>org.scalatest</groupId>
    <artifactId>scalatest_2.11</artifactId>
    <version>${scalatest.version}</version>
</dependency>

Step 2. Configure scalatest-maven-plugin by adding the plugin configuration shown below. Position this <plugin> element after the existing scala-maven-plugin.

<plugin>
    <groupId>org.scalatest</groupId>
    <artifactId>scalatest-maven-plugin</artifactId>
    <version>1.0</version>
    <configuration>
        <reportsDirectory>
            ${project.build.directory}/surefire-reports
        </reportsDirectory>
        <junitxml>.</junitxml>
        <filereports>${project.artifactId}.txt</filereports>
        <!-- W: Suppress ANSI color codes -->
        <!-- T: Failed test reminders with short stack traces -->
        <stdout>WT</stdout>
    </configuration>
    <executions>
        <execution>
            <id>test</id>
            <goals>
                <goal>test</goal>
            </goals>
        </execution>
    </executions>
</plugin> 

Step 3. Enter the unit test shown here into the test source code hierarchy.

package com.scalacraft.professionalscala.chapter5

import org.scalatest.{FlatSpec, Matchers}

class ProbeSpec extends FlatSpec with Matchers {

  behavior of "A Probe"

  it should "touchdown without puffing out smoke" in {
    new Probe touchdown
  }
}

Step 4. Build the project

$ mvn clean install

The test report will complete. Your report will be similar to this one.

Run starting. Expected test count is: 1
ProbeSpec:
A Probe
Hello, New World!
- should touchdown without puffing out smoke
Run completed in 285 milliseconds.

With your probe now trundling toward the launch pad, let's move onto other matters.

JOINT COMPILATION WITH JAVA

Scala is interoperable with Java and you may find you have a requirement to mix Java and Scala source code in the same Maven project. In this case you will be well served by the joint compilation capabilities of the Scala compiler, which is configurable using scala-maven-plugin. Follow these steps to get a feel for the joint compilation set up. Along the way you will encounter the following players:

  • Transmitter: Java interface
  • Probe: Scala class extending Java interface
  • CoreProbe: Java class extending Scala class

Step 1. Create a source directory for your Java code.

$ mkdir -p src/main/java/com/scalacraft/professionalscala/chapter5/

Step 2. Add a Java interface in this directory as shown here.

package com.scalacraft.professionalscala.chapter5;

public interface Transmitter {
    byte[] transmit();
}

Step 3. Implement the Java interface in the Scala Probe class following this example here.

class Probe extends Transmitter {
  def touchdown: Unit = println("Hello, New World!")
  override def transmit(): Array[Byte] = (0xda7a * 0xfeed).toString getBytes
}

Step 4. Build the project and confirm all classes were compiled to bytecode.

$ find -name *class
./target/classes/com/scalacraft/professionalscala/chapter5/Probe.class
./target/classes/com/scalacraft/professionalscala/chapter5/Transmitter.class
./target/test-classes/com/scalacraft/professionalscala/chapter5/
  ProbeSpec$$anonfun$1.class
./target/test-classes/com/scalacraft/professionalscala/chapter5/ProbeSpec.class

At this point you have a Scala class implementing a Java interface. What happens if you then have a requirement to extend a Scala class with a Java class? Try it.

Step 5. Add CoreProbe in the Java source directory as shown below.

package com.scalacraft.professionalscala.chapter5;

public class CoreProbe extends Probe {}

Building the project at this point ends in a compilation error.

 [ERROR] COMPILATION ERROR :
 [ERROR] professional-scalasrcmainjavacomscalacraftprofessionalscala
  chapter5CoreProbe.java:[3,31] error: cannot find symbol

This error occurs because by default maven-compiler-plugin is executed before scala-maven-plugin. To unlock the benefits of joint compilation, you need to instruct Maven to execute the Scala compiler before the Java compiler. Follow the remaining steps to achieve the utopia of joint compilation.

Step 1. Modify the <executions> element of the scala-maven-plugin to stipulate the lifecycle phase to which the two Scala compilation goals are tied. The exact code to achieve this is shown below.

<plugin>
    <groupId>net.alchim31.maven</groupId>
    <artifactId>scala-maven-plugin</artifactId>
    <version>3.2.2</version>
    <executions>
        <execution>
            <id>compile</id>
            <phase>process-resources</phase>
            <goals>
                <goal>compile</goal>
            </goals>
        </execution>
        <execution>
            <id>test-compile</id>
            <phase>process-test-resources</phase>
            <goals>
                <goal>testCompile</goal>
            </goals>
        </execution>
    </executions>
</plugin>

The criteria for selecting the process-resources phase are twofold:

  1. Must be before the compile phase to ensure precedence over the Java compile goal.
  2. Must be as close to the compile phase as possible to preserve the semantics of the build lifecycle as much as possible.

Step 2. Confirm the project builds and check for the expected class files.

$ find -name *class
./target/classes/com/scalacraft/professionalscala/chapter5/CoreProbe.class
./target/classes/com/scalacraft/professionalscala/chapter5/Probe.class
./target/classes/com/scalacraft/professionalscala/chapter5/Transmitter.class
./target/test-classes/com/scalacraft/professionalscala/chapter5/
  ProbeSpec$$anonfun$1.class
./target/test-classes/com/scalacraft/professionalscala/chapter5/ProbeSpec.class

You now have the option to mix Java and Scala freely within the same project. Pause to reflect on the options this brings. You can now test Java code with ScalaTest.

ACCELERATING COMPILATION WITH ZINC

Zinc is a Lightbend project that provides access to compilation as a service. This can reduce your project compilation time significantly. Zinc is based on the incremental compiler that is part of SBT and scala-maven-plugin supports easy integration with the zinc service. In this section, you will work through the steps required to install and start the zinc server, and then configure your project to use the service. At the end are sample compile times charted to show the difference between compiling with and without zinc.

Step 1. Download and install zinc to ˜/Apps or some other directory of your choice. Zinc can be downloaded from https://github.com/typesafehub/zinc.

Step 2. Start zinc as shown here:

$ ~/Apps/zinc-0.3.9/bin/zinc -nailed -scala-home ~/lib/scala-2.11.7 -start

The -nailed option runs the service as a daemon. You should specify the location of a Scala distribution using the -scala-home option. Starting zinc only needs to be done once.

Step 3. Update the scala-maven-plugin compiler configuration to delegate compilation to the zinc server. Follow the configuration below. The pertinent options are recompileMode and useZincServer. Both options must be present for the zinc server to be used.

<configuration>
    <recompileMode>incremental</recompileMode>
    <useZincServer>true</useZincServer>
</configuration>

Step 4. Build your project and look at the compilation logging. It will be similar to the logging output below.

[INFO] Using zinc server for incremental compilation
[warn] Pruning sources from previous analysis, due to incompatible CompileSetup.
[info] Compiling 30 Scala sources to /home/jdb/workspaces/main/professional-scala
  /target/classes…
[warn] there were 5 deprecation warnings; re-run with -deprecation for details
[warn] there was one feature warning; re-run with -feature for details
[warn] two warnings found
[info] Compile success at 31-Jan-2016 21:02:19 [1.602s]

So is it faster? Your mileage may vary, but for your convenience Figure 5.1 charts the time in seconds to compile the project from Chapter 8 with and without zinc. The quicker times are zinc times.

Illustration depicting the time in seconds to compile a project with and without zinc.

Figure 5.1

SUMMARY

Maven supports building mixed Scala and Java projects courtesy of the Scala Maven plugin. This plugin has eleven goals that provide support for compilation, documentation, testing, project configuration, scripting, and a project aware REPL.

A minimum viable POM that gets your Scala project building can be defined with a handful of XML elements. This configuration can be augmented to perform a range of tasks spanning documentation generation, unit test integration and more.

The Scala Maven plugin delegates to the Scala compiler in a way that you can pick apart should the need arise. The plugin provides convenient defaults with many configuration options allowing you to tailor the build to your scenarios. Some configuration relates to Maven while other configuration is specific to the compiler or scaladoc.

With a couple of config additions you can start to win back compilation time by throwing your sources onto the Zinc compiler. Other build tools for Scala include SBT, Gradle, and Pants Build System. These tools are in many ways improvements over Maven. But Maven enjoys the advantage of wide currency within the existing Java development world—a key advantage in a change adverse culture.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset