Scala + Maven – pierwsze kroki
Przedwczoraj pisałem, że biorę się za Scalę, a dziś pokażę wam podstawy. Nie będę wnikał w podstawy związane z linią poleceń i kompilatorem scalac, ponieważ uznaję to za bezproduktywne. Zresztą jak masz zainstalowane JDK to scalac działa jak javac, a scala jak linia poleceń sqlplusa.
Zakładam, że masz zainstalowaną Scalę i ustawione zmienne systemowe. Jeżeli nie to pod tym adresem znajdziesz odpowiednie informacje i pliki.
Maven
Tak jak w temacie. Projekt zaczniemy od wygenerowania prostej aplikacji za pomocą mavena. Wpisujemy:
Listing 1. generacja aplikacji Scala w maven
bartlomiejk@Bartlomiejk /c/sandbox
$ mvn archetype:generate
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'archetype'.
[INFO] org.apache.maven.plugins: checking for updates from gwt-maven
[INFO] org.apache.maven.plugins: checking for updates from central
[INFO] org.codehaus.mojo: checking for updates from gwt-maven
[INFO] org.codehaus.mojo: checking for updates from central
[INFO] artifact org.apache.maven.plugins:maven-archetype-plugin: checking for updates from gwt-maven
[INFO] artifact org.apache.maven.plugins:maven-archetype-plugin: checking for updates from central
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Default Project
[INFO] task-segment: [archetype:generate] (aggregator-style)
[INFO] ------------------------------------------------------------------------
[INFO] Preparing archetype:generate
[INFO] No goals needed for project - skipping
[INFO] Setting property: classpath.resource.loader.class => 'org.codehaus.plexus.velocity.ContextClassLoaderResourceLoader'.
[INFO] Setting property: velocimacro.messages.on => 'false'.
[INFO] Setting property: resource.loader => 'classpath'.
[INFO] Setting property: resource.manager.logwhenfound => 'false'.
[INFO] [archetype:generate]
[INFO] Generating project in Interactive mode
[INFO] No archetype defined. Using maven-archetype-quickstart (org.apache.maven.archetypes:maven-archetype-quickstart:1.0)
Choose archetype:
1: internal -> appfuse-basic-jsf (AppFuse archetype for creating a web application with Hibernate, Spring and JSF)
2: internal -> appfuse-basic-spring (AppFuse archetype for creating a web application with Hibernate, Spring and Spring MVC)
3: internal -> appfuse-basic-struts (AppFuse archetype for creating a web application with Hibernate, Spring and Struts 2)
4: internal -> appfuse-basic-tapestry (AppFuse archetype for creating a web application with Hibernate, Spring and Tapestry 4)
5: internal -> appfuse-core (AppFuse archetype for creating a jar application with Hibernate and Spring and XFire)
6: internal -> appfuse-modular-jsf (AppFuse archetype for creating a modular application with Hibernate, Spring and JSF)
7: internal -> appfuse-modular-spring (AppFuse archetype for creating a modular application with Hibernate, Spring and Spring MVC)
8: internal -> appfuse-modular-struts (AppFuse archetype for creating a modular application with Hibernate, Spring and Struts 2)
9: internal -> appfuse-modular-tapestry (AppFuse archetype for creating a modular application with Hibernate, Spring and Tapestry 4)
10: internal -> maven-archetype-j2ee-simple (A simple J2EE Java application)
11: internal -> maven-archetype-marmalade-mojo (A Maven plugin development project using marmalade)
12: internal -> maven-archetype-mojo (A Maven Java plugin development project)
13: internal -> maven-archetype-portlet (A simple portlet application)
14: internal -> maven-archetype-profiles ()
15: internal -> maven-archetype-quickstart ()
16: internal -> maven-archetype-site-simple (A simple site generation project)
17: internal -> maven-archetype-site (A more complex site project)
18: internal -> maven-archetype-webapp (A simple Java web application)
19: internal -> jini-service-archetype (Archetype for Jini service project creation)
20: internal -> softeu-archetype-seam (JSF+Facelets+Seam Archetype)
21: internal -> softeu-archetype-seam-simple (JSF+Facelets+Seam (no persistence) Archetype)
22: internal -> softeu-archetype-jsf (JSF+Facelets Archetype)
23: internal -> jpa-maven-archetype (JPA application)
24: internal -> spring-osgi-bundle-archetype (Spring-OSGi archetype)
25: internal -> confluence-plugin-archetype (Atlassian Confluence plugin archetype)
26: internal -> jira-plugin-archetype (Atlassian JIRA plugin archetype)
27: internal -> maven-archetype-har (Hibernate Archive)
28: internal -> maven-archetype-sar (JBoss Service Archive)
29: internal -> wicket-archetype-quickstart (A simple Apache Wicket project)
30: internal -> scala-archetype-simple (A simple scala project)
31: internal -> lift-archetype-blank (A blank/empty liftweb project)
32: internal -> lift-archetype-basic (The basic (liftweb) project)
33: internal -> cocoon-22-archetype-block-plain ([http://cocoon.apache.org/2.2/maven-plugins/])
34: internal -> cocoon-22-archetype-block ([http://cocoon.apache.org/2.2/maven-plugins/])
35: internal -> cocoon-22-archetype-webapp ([http://cocoon.apache.org/2.2/maven-plugins/])
36: internal -> myfaces-archetype-helloworld (A simple archetype using MyFaces)
37: internal -> myfaces-archetype-helloworld-facelets (A simple archetype usingMyFaces and facelets)
38: internal -> myfaces-archetype-trinidad (A simple archetype using Myfaces and Trinidad)
39: internal -> myfaces-archetype-jsfcomponents (A simple archetype for create custom JSF components using MyFaces)
40: internal -> gmaven-archetype-basic (Groovy basic archetype)
41: internal -> gmaven-archetype-mojo (Groovy mojo archetype)
Choose a number: (1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/34/35/36/37/38/39/40/41) 15: :30
Define value for groupId: : pl.koziolekweb.scala.learn
Define value for artifactId: : LearnScala
Define value for version: 1.0-SNAPSHOT: :
Define value for package: pl.koziolekweb.scala.learn: :
Confirm properties configuration:
groupId: pl.koziolekweb.scala.learn
artifactId: LearnScala
version: 1.0-SNAPSHOT
package: pl.koziolekweb.scala.learn
Y: :
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating OldArchetype: scala-archetype-simple:1.2
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: pl.koziolekweb.scala.learn
[INFO] Parameter: packageName, Value: pl.koziolekweb.scala.learn
[INFO] Parameter: package, Value: pl.koziolekweb.scala.learn
[INFO] Parameter: artifactId, Value: LearnScala
[INFO] Parameter: basedir, Value: /c/sandbox
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] ********************* End of debug info from resources from generated POM ***********************
[INFO] OldArchetype created in dir: /c/sandbox/LearnScala
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2 minutes 23 seconds
[INFO] Finished at: Wed Jun 10 08:55:36 CEST 2009
[INFO] Final Memory: 8M/508M
[INFO] ------------------------------------------------------------------------
Jak widać proces ten jest praktycznie bezbolesny. Warto zwrócić uwagę, że maven potrafi zapytać o to co chcemy wygenerować. Nie ma więc już potrzeby pisania kilometrowych poleceń. Dotyczy to oczywiście tylko niektórych archetypów. Nie znalazłem jeszcze metody modyfikacji tej listy, ale dla chcącego nic trudnego.
Skoro mamy już wygenerowany projekt to należy jeszcze zrobić kilka rzeczy na poziomie mavena. Po pierwsze w pom.xml lub settings.xml należy podać inną wersję kompilatora Scala niż domyślna 2.7. Najnowszą oficjalna i wspieraną w mavenie jest 2.7.5. Jeżeli masz problem ze skonfigurowaniem ustawień w settings.xml przeczytaj ten wpis. Po drugie należy dodać nasz projekt do ulubionego IDE i tamże zaimportować.
Hello World w Scali i konfiguracja pom.xml
Po zaimporotwaniu projektu do IDE otrzymamy trzy pliki. Pierwszy App.scala;
Listing 2. App.scala – Hello World
package pl.koziolekweb.scala
/**
* Hello world!
*
*/
object App extends Application {
println( "Hello World!" )
}
Jest to równoważne z zapisem;
Listing 3. App.scala – Hello World inaczej
package pl.koziolekweb.scala
/**
* Hello world!
*
*/
object App extends Application {
def main(args: Array[String]) : Unit = {
println( "Hello World!" )
}
}
Jeżeli chcemy uruchamiać naszą aplikację z poziomu Mavena warto jeszcze w pliku pom.xml wprowadzić kilka zmian. Kompletny plik poniżej;
Listing 4. kompletny pom.xml
<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>pl.koziolekweb.scala.learn</groupId>
<artifactId>LearnScala</artifactId>
<version>0.1-SNAPSHOT</version>
<inceptionYear>2008</inceptionYear>
<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>
<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>
<mainClass>pl.koziolekweb.scala.learn.App</mainClass>
<scalaVersion>${scala.version}</scalaVersion>
<args>
<arg>-target:jvm-1.5</arg>
</args>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-eclipse-plugin</artifactId>
<configuration>
<downloadSources>true</downloadSources>
<buildcommands>
<buildcommand>ch.epfl.lamp.sdt.core.scalabuilder</buildcommand>
</buildcommands>
<additionalProjectnatures>
<projectnature>ch.epfl.lamp.sdt.core.scalanature</projectnature>
</additionalProjectnatures>
<classpathContainers>
<classpathContainer>org.eclipse.jdt.launching.JRE_CONTAINER</classpathContainer>
<classpathContainer>ch.epfl.lamp.sdt.launching.SCALA_CONTAINER</classpathContainer>
</classpathContainers>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.2</version>
<configuration>
<archive>
<manifest>
<mainClass>pl.koziolekweb.scala.learn.App</mainClass>
<addClasspath>true</addClasspath>
<classpathPrefix>dependencies/</classpathPrefix>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.1</version>
</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>
Przede wszystkim należy podać mainClass
Kolejnymi plikami są dwa pliki testowe. AppTest.scala:
Listing 5. Pliki testowe AppTest.scala
package pl.koziolekweb.scala.learn
import org.junit._
import Assert._
@Test
class AppTest {
@Test
def testOK() = assertTrue(true)
// @Test
// def testKO() = assertTrue(false)
}
oraz MySpec.scala:
Listing 6. Pliki testowe MySpec.scala
package pl.koziolekweb.scala.learn
import org.specs._
import org.specs.runner.{ConsoleRunner, JUnit4}
class MySpecTest extends JUnit4(MySpec)
//class MySpecSuite extends ScalaTestSuite(MySpec)
object MySpecRunner extends ConsoleRunner(MySpec)
object MySpec extends Specification {
"This wonderful system" should {
"save the world" in {
val list = Nil
list must beEmpty
}
}
}
O testowaniu jeszcze będę pisał, ale na razie zajmiemy się naszą aplikacją.
Na koniec w linii poleceń wywołujemy mvn scala:run i cieszymy się jak głupki.
Podsumowanie
Scala jest bardzo przyjemnym narzędziem, a jak widzicie testy mogą dać duże pole do popisu. Choć w tym wpisie nie pokazałem wam nic ponad podstawy podstaw to warto zaznaczyć, że najwięcej osób odpada zazwyczaj właśnie na takich podstawach. Bezbolesne przygotowanie się do dalszej pracy z językiem to podstawa.