27 kwietnia 2008

Zestawienie środowiska do programowania w Scali z Maven 2 i Eclipse IDE

Skoro można pisać programy javowe w Scali, tj. piszę je w Scali, a wynik uruchamiam jakby były napisane w Javie za pomocą interpretera javy, to do pełni szczęścia pozostaje odnaleźć wtyczki wspierające tworzenie aplikacji w Scali dla Eclipse bądź NetBeans oraz możliwość automatycznej kompilacji i uruchamiania aplikacji wspartej o Apache Maven 2. Pamiętałem, że gdzieś kiedyś czytałem o wtyczce Maven 2 do kompilacji aplikacji napisanych w Scala, więc chwila z Google i mam - maven-scala-plugin. Więcej informacji o samym repozytorium m2 dla narzędzi Scali w wątku [ANN] scala-tools.org maven2 repository. Wymaganiem do uruchomienia aplikacji scala z użyciem wtyczki m2 jest jedynie sam Maven oraz Java 5. Jak napisano w Requirements - No need to install scala! Dodatkowo jako zachęta do użycia wtyczki maven-scala-plugin napisano, że The plugin is used by /Lift/, który jest szkieletem webowym napisanym w Scali. Jakby tego było mało już na stronie Lifta - Lift borrows from the best of existing frameworks, providing: Wicket's designer-friendly templating style (see Lift View First). I już mi się podoba!

Zacznę spokojniej - od utworzenia środowiska opartego o maven, gdzie skorzystam z wtyczki maven-scala-plugin do kompilacji i uruchomienia aplikacji. Zacznę od utworzenia projektu mavenowego z użyciem Eclipse (mógłbym i z ręki, ale po co?! Mógłbym również w NetBeans, ale trochę czasu minęło od ostatniego spotkania z Eclipse, więc będzie tym razem o nim).

Po zainstalowaniu wtyczki m2eclipse - Maven Integration for Eclipse - Eclipse umożliwia stworzenie projektu mavenowego.

Najpierw przedstawienie wersji Eclipse.


Ctrl+N i wpisuję maven i wybieram Maven project.


Wciskam przycisk Next dwukrotnie i w kolejnym panelu wybieram maven-archetype-quickstart.


Next i podaję dane projektu:
  • Group Id: pl.jaceklaskowski.scala
  • Artifact Id: scala-witaj-swiecie
  • Version: 1.0.0
  • Package: pl.jaceklaskowski.scala

Finish i projekt scala-witaj-swiecie utworzony.


Pora na zmiany w związku z wykorzystaniem Scali jako języka programowania w projekcie. W pliku pom.xml dodaję zmiany zgodnie z wytycznymi na Using the plugin (common).

Zawsze cieszy mnie, kiedy poznając jedną rzecz dowiaduję się innych, równie istotnych informacji, i tym razem nie obyło się inaczej. W dokumentacji maven-scala-plugin wspomniano o kolejnej cesze mavena, gdzie brak określenia wersji wtyczki przy jej braku w lokalnym repozytorium jest możliwe wyłącznie, jeśli uruchomiono mvn z opcją -U. Pamiętam, że kiedyś miałem z tym problem i właśnie byłem zmuszony określić wersję wtyczki korzystając z http://mvnrepository.com/. Teraz mam i na to rozwiązanie.

I kolejna rzecz poboczna, o której wiedziałem, ale nigdy nie stosowałem. W Eclipse można edytować pliki xmlowe w edytorze XML, który zazwyczaj włączony jest w trybie Source, gdzie do dyspozycji mamy Ctrl+Spacja upraszczająca dodanie nowych elementów, bądź rozszerzenie istniejących. Tym razem postanowiłem przyjrzeć się trybowi (zakładce) Design (zakładki dostępne są w lewym dolnym rogu edytora). Jeśli teraz wybiorę element project i wcisnę prawy klawisz myszy pojawi się menu, za pomocą którego mogę dodać nowy element do pliku (a mam ich kilka do dodania związanych z wtyczką maven-scala-plugin). Przełączając się między trybami Design i Source można połączyć wyznaczenie odpowiedniego miejsca dla nowego elementu z pomocą trybu Design, a uzupełnić go techniką Copy-Paste mając gwarancję, że plik jest zgodny z DTD czy XML Schema (po kilku chwilach okazało się, że nowy element zawsze dodawany jest na końcu pliku, więc moja wiara o poprawne ułożenie elementów zgodnie z xsd zdaje się być nieuprawniona).


Ostatecznie plik pom.xml projektu prezentuje się następująco:
 <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.jaceklaskowski.scala</groupId>
<artifactId>scala-witaj-swiecie</artifactId>
<packaging>jar</packaging>
<version>1.0.0</version>
<name>scala-witaj-swiecie</name>
<url>http://www.jaceklaskowski.pl</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<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>
<build>
<sourceDirectory>src/main/scala</sourceDirectory>
<testSourceDirectory>src/test/scala</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.scala-tools</groupId>
<artifactId>maven-scala-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Kolejnym krokiem opisanym w Using the plugin (common) jest Displaying scala help and version. Tutaj wydaje mi się, że szybciej uruchomiłbym polecenie z poziomu linii poleceń, ale dla dobra sprawy przemogłem się i zdefiniowałem wykonanie polecenia w Eclipse w External Tools Configurations (Run > External Tools). Definiuję nowe polecenie


z moją własną instalacją Apache Maven 2.0.9 w c:/apps/apache-maven.


Wciśnięcie Run i...


czyli pracuję z Scala compiler version 2.6.0-final -- (c) 2002-2007 LAMP/EPFL. Dla zwrócenia uwagi ostatnia dostępna wersja to Scala 2.7.1.RC2 (z 21. kwietnia 2008), a w międzyczasie pojawiła się i Scala 2.7.0-final (z 6. marca 2008).

Tak mi się przypomniało, kiedy przeglądałem ustawienia Eclipse, że podczas ostatniego szkolenia IBM WebSphere Process Servera i IBM WebSphere Integration Developera, o którym wspominałem w Doświadczenia szkoleniowe i trochę o jpcap oraz Ubuntu Beryl poznałem kolejną cechę Eclipse, gdzie przełączanie się widoków w trakcie uruchamiania serwera można wyłączyć w sekcji Console (Window > Preferences > Run/Debug).


Jeśli wyłączyłbym Show when program writes to standard out oraz Show when program writes to standard error to ciągłe przełączanie się okien zostałoby raz na zawsze wyłączone. Tym razem nie ma to wielkiej wartości, bo nie będę uruchamiał żadnego serwera, ale w przyszłości na pewno i wrócę do tego ustawienia.

A wracając do Scali, jest jeszcze krok Displaying the command line used


co nie zakończyło się czymś imponującym


oraz Changing the scala version, gdzie korzysta się ze zmiennej ${scala.version}, ale nic poza tym, gdzie i jak się ją ustawia (za moment o tym).

W Usage zaprezentowano również sposób konfiguracji wtyczki do tworzenia raportów, ale na razie jakie raporty są dostępne jest poza moją wiedzą i zainteresowaniem. Mimo wszystko dodaję sekcję reporting do pom.xml.
 <reporting>
<plugins>
<plugin>
<groupId>org.scala-tools</groupId>
<artifactId>maven-scala-plugin</artifactId>
</plugin>
</plugins>
</reporting>
Okazało się, że więcej informacji jest na stronie Scaladoc. Jak dla mnie zaczyna mi to wyglądać na lekki bałagan w dokumentacji wtyczki.

Podczas kopiowania zawartości reporting ze strony okazało się, że brakuje w niej elementów końcowych dla groupId, artifactId oraz version. Już miałem zgłosić błąd w bazie błędów, ale strona Project Information > Issue Tracking kończy się No issue management system is defined. Please check back at a later date. Niech będzie zatem, zgłoszę się później.

Zgodnie z Compiling źródła umieszczam w src/main/scala, a źródła testów w src/test/scala (sekcja sourceDirectory oraz testSourceDirectory w build w pom.xml). Do uruchomienia aplikacji korzystam z Running, gdzie w konfiguracji maven-scala-plugin określa się klasę główną za pomocą parametru konfiguracyjnego mainClass.
 <mainClass>pl.jaceklaskowski.scala.WitajSwiecie</mainClass>
Definiuję skrót mvn scala-run w Eclipse


i mam już gotowe środowisko do pracy. Na razie wykonanie mvn scala:run kończy się BUILD FAILURE


ale i tym się zaraz zajmę.

Definiuję nowy katalog ze źródłami - src/main/scala, gdzie tworzę klasę pl.jaceklaskowski.scala.WitajSwiecie. Bez specjalizowanej wtyczki dla Eclipse dla projektów Scala na razie obchodzę tę niedogodność tworząc katalog, a w nim zwykły plik o rozszerzeniu scala. Jako wsparcie merytoryczne skorzystam z Getting Started with Scala oraz Scala By Example.
 package pl.jaceklaskowski.scala

object WitajSwiecie {
def main(args: Array[String]) {
println("Witaj Świecie!")
}
}
W międzyczasie zdefiniowałem zmienną scala.version z wartością 2.7.1-rc2.
 <properties>
<scala.version>2.7.1-rc2</scala.version>
</properties>
Jestem gotów do uruchomienia pierwszej aplikacji WitajSwiecie z pakietu pl.jaceklaskowski.scala (dodanie pakietu było konieczne, aby umieścić klasę w katalogu pl/jaceklaskowski/scala, podobnie jak miałoby to miejsce w Javie).

Wybieram mvn scala-run z External Tools


i po pobraniu 6-ciomegowego scala-compiler-2.7.1-rc2.jar oraz 3-megowego scala-library-2.7.1-rc2.jar uruchomiłem pierwszą aplikację Scala z poziomu Eclipse.

Nie tak prędko jednak przyszło mi zobaczyć poprawnie uruchomioną aplikację, bo świadomie użyłem polskiej litery w Witaj Świecie i kompilacja zakończyła się błędem:
 [INFO] IO error while decoding 
C:\.eclipse\scala-witaj-swiecie\src\main\scala\pl\jaceklaskowski\scala\WitajSwiecie.scala with UTF-8
[INFO] Please try specifying another one using the -encoding option
I tutaj zaskoczenie, że mimo, że kompilacja nie zakończyła się poprawnie, to komunikat jest na poziomie INFO i nie kończy wykonania wtyczki (!) Zmieniam ustawienia Eclipse, tak aby domyślnym kodowaniem znaków był UTF-8 (Window > Preferences... > General > Workspace - parametr Text file encoding).


i ponownie uruchomienie mvn scala-run.

Znowu BUILD FAILURE (!)
 [INFO] [scala:run]
[WARNING] Exception in thread "main" java.lang.NoClassDefFoundError: scala/ScalaObject
[WARNING] at java.lang.ClassLoader.defineClass1(Native Method)
[WARNING] at java.lang.ClassLoader.defineClass(ClassLoader.java:620)
[WARNING] at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:124)
[WARNING] at java.net.URLClassLoader.defineClass(URLClassLoader.java:260)
[WARNING] at java.net.URLClassLoader.access$100(URLClassLoader.java:56)
[WARNING] at java.net.URLClassLoader$1.run(URLClassLoader.java:195)
[WARNING] at java.security.AccessController.doPrivileged(Native Method)
[WARNING] at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
[WARNING] at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
[WARNING] at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:268)
[WARNING] at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
[WARNING] at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)
[WARNING] at pl.jaceklaskowski.scala.WitajSwiecie.main(WitajSwiecie.scala)
[INFO] ------------------------------------------------------------------------
[ERROR] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
To już jednak wygląda znajomo po wczorajszym spotkaniu ze Scalą - Pierwsze spotkanie ze Scalą - lektura Scala Tutorial for Java programmers - brakuje scala-library.jar, ale skoro jestem w środowisku m2, to wystarczy jedynie zadeklarować zależność i voila...powinno wystartować poprawnie. Moment, przecież do uruchomienia Scali korzystam z narzędzia scala, które powinno mi zadbać o tego typu zależności automatycznie (poprzednio błąd pojawiał się jedynie w momencie uruchomienia za pomocą polecenia java).

Uruchomienie polecenia mvn scala-run z ustawionym parametrem <displayCmd>true</displayCmd> w pom.xml:
 <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>
<!-- http://scala-tools.org/mvnsites/maven-scala-plugin/run-mojo.html -->
<displayCmd>true</displayCmd>
<mainClass>pl.jaceklaskowski.scala.WitajSwiecie</mainClass>
<scalaVersion>${scala.version}</scalaVersion>
</configuration>
</plugin>
</plugins>
ukazuje jednak całą prawdę i tylko prawdę:
 [INFO] [scala:run]
[INFO] cmd: c:\apps\java5\jre\bin\java
-classpath C:\.eclipse\scala-witaj-swiecie\target\classes
pl.jaceklaskowski.scala.WitajSwiecie
[WARNING] Exception in thread "main" java.lang.NoClassDefFoundError: scala/ScalaObject
[WARNING] at java.lang.ClassLoader.defineClass1(Native Method)
[WARNING] at java.lang.ClassLoader.defineClass(ClassLoader.java:620)
[WARNING] at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:124)
[WARNING] at java.net.URLClassLoader.defineClass(URLClassLoader.java:260)
[WARNING] at java.net.URLClassLoader.access$100(URLClassLoader.java:56)
[WARNING] at java.net.URLClassLoader$1.run(URLClassLoader.java:195)
[WARNING] at java.security.AccessController.doPrivileged(Native Method)
[WARNING] at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
[WARNING] at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
[WARNING] at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:268)
[WARNING] at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
[WARNING] at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)
[WARNING] at pl.jaceklaskowski.scala.WitajSwiecie.main(WitajSwiecie.scala)
[INFO] ------------------------------------------------------------------------
[ERROR] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
czyli uruchomienie aplikacji WitajSwiecie odbywa się z interpreterem java bez pliku scala-library.jar. Dodaję zależność org.scala-lang:scala-library:2.7.1-rc2 oraz przy okazji podnoszę wersję junit do 4.4.
 <dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>2.7.1-rc2</version>
</dependency>
</dependencies>
Tym razem uruchomienie mvn scala-run zakończyło się pomyślnie!
 [INFO] [scala:run]
[INFO] cmd: c:\apps\java5\jre\bin\java
-classpath C:\.eclipse\scala-witaj-swiecie\target\classes;C:\.m2\org\scala-lang\scala-library\2.7.1-rc2\scala-library-2.7.1-rc2.jar
pl.jaceklaskowski.scala.WitajSwiecie
[INFO] Witaj Świecie!
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
Pora spróbować zbudować ziarno EJB z wykorzystaniem Scali - to już nie powinno stanowić problemu skoro wynikiem kompilacji jest postać binarna zrozumiała dla interpretera javy. Zostawiam jako zadanie domowe. Aby podnieść stawkę pierwszemu kto wyśle mi gotowy projekt m2 z ziarnem EJB wystawię podziękowania w moim Notatniku i opublikuję rozwiązanie (jak są inne propozycje nagród chętnie ich wysłucham).

Kompletny plik pom.xml wygląda następująco:
 <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.jaceklaskowski.scala</groupId>
<artifactId>scala-witaj-swiecie</artifactId>
<packaging>jar</packaging>
<version>1.0.0</version>
<name>scala-witaj-swiecie</name>
<url>http://www.jaceklaskowski.pl</url>
<properties>
<scala.version>2.7.1-rc2</scala.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>2.7.1-rc2</version>
</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>
<!-- http://scala-tools.org/mvnsites/maven-scala-plugin/run-mojo.html -->
<displayCmd>true</displayCmd>
<mainClass>pl.jaceklaskowski.scala.WitajSwiecie</mainClass>
<scalaVersion>${scala.version}</scalaVersion>
</configuration>
</plugin>
</plugins>
</build>
<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>
<reporting>
<plugins>
<plugin>
<groupId>org.scala-tools</groupId>
<artifactId>maven-scala-plugin</artifactId>
</plugin>
</plugins>
</reporting>
</project>
Z lektury Scala By Example dzisiaj nici, ale jeszcze do niej wrócę. Pora zabrać się za trochę książek, których właśnie stałem się szczęśliwym posiadaczem, a w nich ta wymarzona Wicket in Action autorstwa Martijn Dashorst oraz Eelco Hillenius z Manning.

I na koniec pytanie, tym razem opatrzone przykładem napisanym w Scali: Dlaczego poniższa aplikacja zakończyła się komunikatem HelloWorld.main is not static?
 jlaskowski@work /cygdrive/c/scala-sandbox
$ c:/apps/scala/bin/scalac HelloWorld.scala

jlaskowski@work /cygdrive/c/scala-sandbox
$ c:/apps/scala/bin/scala HelloWorld
java.lang.NoSuchMethodException: HelloWorld.main is not static
Odpowiedź można umieścić w komentarzu bądź wysłać bezpośrednio do mnie. Nagród nie ma, ale możemy wspólnie o nich pomyśleć (ciekawie byłoby zaangażować jakąś firmę, która byłaby sponsorem niektórych z pytań, w zamian oferując jakąś ciekawą nagrodę dla aktywnych. Ech, się rozmarzyłem).

Gotowy projekt eclipsowy scala-witaj-swiecie jest do pobrania jako scala-witaj-swiecie.zip.