• Blog
  • À propos
  • Mes dessins
  • Contact

Building and running Scala programs with Gradle

PhiLho tech web log

Mon blog technique, artistique, d’humeur... Continuer »

Building and running Scala programs with Gradle - 1 November 2010

I recently started to learn Scala, an interesting language in the (very strongly) statically typed category. So far, I only compiled single files with little or no dependency, so the command line was good enough. Now, I start to try small sample files with a rather complex classpath (using Piccolo2D and Pivot), so I searched something more sophisticated.

The main constraint was to be able to compile and run Scala programs, using my local copy of Scala compiler, and the local libraries where I gather them. I soon discovered it is now a quite unusual setup...

So, what are my options? Shell files (well, .cmd in my case, as I am on Windows) are quite limited.

Ant, I prefer to avoid. Had to use it at work, on a not-so-trivial build, and got an XML allergy... (at least when one try to program with an XML dialect! Conditionals and loops are awful!).
Same for Maven, and from what I understood, the latter imposes a given project structure, which I dislike: I like flexibility. Beside, for my simple needs, I don't really need a configuration management (yet?).

SBT (simple build tool) is praised in the Scala community, so it was a natural choice: being natively wirtten in Scala, using this language for its scripts, it seems to be just right.
So, I tried it. But whatever I tried, following the Wiki instructions, I couldn't prevent it from downloading Scala 2.7.7 (while I want to use 2.8.0) on each start. So I looked around for alternatives.

Later, I was told the sbt-launch-0.7.4.jar is only an installer... It is supposed to download and install SBT on first run, along with its dependencies, which includes the full Scala 2.7.7. It is supposed not to do that on next projects.
So I tried it again. And indeed, on a second project, it didn't download Scala 2.7.7... but installed it from its cache!
Mmm, I don't see the usefulness to have a copy of the tool on each micro-project I do (even if I see how it can be useful on bigger, shared/open source projects, perhaps using the tool at various versions), so exit for me. At least for now.

I saw good words on Buildr, but if I can avoid it, I don't want a simple tool to depend on a multi-megabyte language (OK, I made an exception for Bazaar...).

At this stage, I have spent some time to look at existing build tools. I was astonished to see the number of such tools, from makefile generators to dependency analyzers from compiler outputs (filesystem watchers), from simple one file portable scripts to big tools made only for Unix... I see lot of these tools are made in Python. Lot of them only or mostly target C or C++ languages, and using them for something else might be hard. Particularly for Java which can generate several .class files for a single .java file, and even more for Scala which can make files whose name is totally unrelated to the source files and their path...

I saw some tools are just a thin wrapper of an interpreted language (Ruby, Python...), accessing the Ant library, getting the power of the tool without the irky language. I briefly thought I could make a binding of Ant libraries with LuaJ (because it makes easy to call Java stuff), calling the result Luant for example... It is doable, but it would need some work (thus time...) and I wanted a quick result, so despite the interest of the project, I dropped the idea. For now...

Soo, I bite the bullet and went back to test Buildr... A quick look at the docs, it looks nice.
I have to install it, so I follow the instructions. First, I went to http://jruby.org/ (I use this one, supposed to be faster than plain Ruby, and at least I remain on the JVM ecosystem...) and clicked on the download button. Bam, a 19MB (expanding to 24MB) file on my disk! :-)
After installation, I try to get Buildr:

> jruby -S gem install buildr
JRuby limited openssl loaded. http://jruby.org/openssl
gem install jruby-openssl for full support.
ERROR:  Error installing buildr:
        buildr requires builder (= 2.1.2, runtime)

Grr, that's not a good start. Let's try to be smart:

> jruby -S gem install builder
> jruby -S gem install buildr
JRuby limited openssl loaded. http://jruby.org/openssl
gem install jruby-openssl for full support.
ERROR:  Error installing buildr:
        buildr requires net-ssh (= 2.0.23, runtime)

I went on, installing various packages are they were reported missing, slow and tedious process. Until it complained because I installed the newest version of a library and it wanted an older one. I gave up at this stage...

Then an article on Hudson project going from Maven to Gradle reminded this tool. I went to the site, saw it can be used to compile Java and Scala, among other languages, that's nice. Its scripts are based on Groovy, yet another language to install and (more or less) learn. Oh well. The doc seems nice.

So, I download a 22MB archive (v.0.8, I go for the stable package) expanding to a 42MB folder...
At least, 14MB of this is for doc, which is good sign.
And 20MB is for the jar files, as it appears that Gradle has lot of dependencies... At least, it groups them in the same place, no pain to get parts like I had with Buildr. Even Groovy comes bundled with the package.

Update: after some days of use, I saw lot of people already switched to the beta 0.9 version, so I started to use the 0.9-rc-2 version, which is probably stable enough for my humble needs...
For the record: archive is 34MB, unzipping to 49MB. Inflation mostly come from the libraries.
Among interesting improvements, we have incremental building and colorful console output...

So, I go to the command line and add Gradle's bat file to the path:

> set path=%path%;C:\Java\gradle-0.8\bin
> gradle -v

------------------------------------------------------------
Gradle 0.8
------------------------------------------------------------

Gradle buildtime: Monday, September 28, 2009 2:01:59 PM CEST
Groovy: 1.6.4
Ant: Apache Ant version 1.7.0 compiled on December 13 2006
Ivy: 2.1.0-rc2
Java: 1.6.0_18
JVM: 16.0-b13
JVM Vendor: Sun Microsystems Inc.
OS Name: Windows XP

Good.
I follow the simple tutorial, playing with tasks and their dependencies. Not much work done, but the syntax is pleasant.

I am trying to build a simple Scala project with a single source file and no dependencies yet, beside the Scala library. I don't want to download these from some repository, just use my installed jars. I will care for proper dependencies management if I make a project to share officially.

I found a useful thread showing how to use local resources: Compile using jars from a lib directory.

I end up with a working project:

group = 'org.philhosoft'
mainClass = "${group}.experiments.swing.HelloWorldSwing"
version = '1.0'
if (!System.properties.'release') {
	// Using gradle -Drelease=true xxxTask
    version += '-SNAPSHOT'
}

// Get access to environment variables
env = System.getenv()
// Like this:
scalaHome = env['SCALA_HOME']
// Grab all jars in Scala's lib dir
scalaTree = fileTree(dir: "$scalaHome/lib", includes: [ '*.jar' ])

// To compile Scala
// usePlugin 'scala' // v.0.8
apply plugin: 'scala' // v0.9

// Calling gradle without parameter, it will compile the project
// Must be "compileScala" otherwise if using just "compile", it complains as being ambiguous
// (I might want to compile Java classes within the project.)
defaultTasks 'compileScala'

So far, not too hard to understand... :-) I probably do lot of things redundant with default Gradle behavior (which seems to do "the right thing" by default) but it is a build file for experimentations, testing changes from these defaults.
So, let's define my local repository.

// Tell the dependency management where to find the libraries
repositories {
    flatDir name: 'localRepository', dirs: 'C:/Java/libraries'
}

Then I define the dependencies, particularly to define where to find Scala tools:

dependencies {
    // Declares scalaTools so that Gradle will know how to compile Scala code
    scalaTools scalaTree
//~     scalaTools fileTree(dir: "$scalaHome/lib").matching { include '*.jar' }

    // Libraries to use when compiling (could have added scalaTree to repositories, I suppose)
    compile scalaTree
	// Just declare the name of the library, it knows where to find it from the 'repositories' part
	compile name: 'jyaml-1.3' // Name of the jar, without the extension

	// Libraries to use when running -- actually not necessary as it inherits from compile dependencies.
	// It is probably more used to declare additional dependencies (like a DB driver, loaded by reflection).
    runtime scalaTree
	runtime name: 'jyaml', version: '1.3' // Alternative syntax
}

And I define a custom (yet classical) directory structure:

// Custom directory layout
sourceSets {
    main {
        scala {
            srcDir 'src/scala'
        }
    }
    test {
        scala {
            srcDir 'test/scala'
        }
    }
}
// Build dir is the same for all source sets, which makes sense, I suppose
buildDir = 'bin' // Change from default 'build' folder -- in 0.8, was buildDirName

The compiled files will go to bin/classes/main.
It works fine! I was able to build my simple Scala-Swing program. Now, to run it with Gradle.
I searched around, and if I found ant.java() to run a Java class, I could find nothing equivalent for Scala.
So I looked at scala.bat, and found out it was just running it with java.exe. Aah... I first tried to run it via scala.tools.nsc.MainGenericRunner as done in the bat file. But it was lost, couldn't find my package. Perhaps because I defined the class folder as relative, and it was running from the Scala install dir, or something like that.
Anyway, looking at this MainGenericRunner source, I found out it wasn't really needed, as it is mostly used to run the interpreter mode or the REPL, or with plain Java, depending on context. So I skipped it altogether, making a simpler run config.:

task run << {
    ant.java(
        classname: mainClass,
        failOnError: 'true',
        fork: 'true'
    ) {
        jvmarg(value: '-Xmx256M')
        jvmarg(value: '-Xms32M')
        jvmarg(value: "-Dscala.home=$scalaHome")
        arg(value: '42') // I don't use these args, actually...
        arg(value: 'foo')
        classpath {
            // My .class files
            pathElement(location: 'bin/classes/main')
            // The runtime class path defined in the configurations
            pathElement(path: configurations.runtime.asPath)
        }
    }
}

To explore a bit more the capabilities of Gradle, I made two packaging tasks:

// Will create a jar file using the project name and the 'version' property to make the name
// The file goes to $buildDir/libs
task makeJar(type: Jar, dependsOn: compileScala) {
    from "$buildDir/classes/main"
    // Change base name from project name
    baseName = 'gradleTest'
    manifest {
        attributes(
            'Main-Class': mainClass,
            'Version': version,
            'Class-Path': 'scala-library.jar scala-swing.jar jyaml-1.3.jar'
        )
    }
}

// Zip the sources
task zipSrc(type: Zip) {
    // Zip goes into $buildDir/distributions
    if (!System.properties['release']) {
        // Specify full name
        // Can also use 'appendix' (eg. 'core') and 'classifier' (eg. 'src') properties
        archiveName = "SomethingElse-${version}-src.zip"
    }
    // Else, use project name + version
    from(sourceSets.main.scala.srcDirs) {
        into archivesBaseName
        fileMode = 0755
        include '**/*.scala'
    }
}

So, after looking around some information (I haven't fully read the manual yet...), I was quite quickly able to get the result I wanted. I appreciate that I can drop a build.gradle file on an existing project, adjust some settings, and get what I want.

I contrast that with Maven or even SBT which impose (at least by default) a project structure, want to manage dependencies themselves (hard to get the hand), and so on.
I don't discard this approach, very nice for a new project (quick setup on a logical structure) and to share it (one can find quickly its way in a random project). I can also see the interest of defining resources (libraries) on a repository and getting them from there: there is no need to hunt them on the Net, risking to get a newer version that could be incompatible.
But I don't believe in "one size fits all": sometime we have to work on a legacy project, I want to make lot of little unrelated projects without wanting to have a full copy of my libraries on each (I am a dinosaur, coming from a world where hard disk space was spare... :-)).

I found Gadle easy to use, yet apparently powerful, making me able to reuse my Ant knowledge, flexible and not in the way. If, later, I want to be nice to others and use dependency management, I can do that (eg. using Ivy or Maven stuff). So far, I am sold on this tool.

In the Maven vs. Gradle vs. Ant article, we have a nice interview of Hans Dockter, the main man behind Gradle, explaining the philosophy behind the tool. I particularly appreciate the quotation of Erich Gamma against Frameworkitis, meeting my thoughts here...

Update 2: I tried with Maven-style build. As expected, much longer (see below), but of course, that's only the first time, it goes into a cache. And, unlike what I feared (from my SBT experience), the downloaded jars just remain in the cache, there is no copy per project. Wew!

> gradle -PuseMavenStyle
Using Maven-style dependencies
:compileJava
Download http://repo1.maven.org/maven2/org/scala-lang/scala-library/2.8.0/scala-library-2.8.0.pom
Download http://repo1.maven.org/maven2/org/scala-lang/scala-swing/2.8.0/scala-swing-2.8.0.pom
Download http://repo1.maven.org/maven2/org/jyaml/jyaml/1.3/jyaml-1.3.pom
Download http://repo1.maven.org/maven2/org/scala-lang/scala-library/2.8.0/scala-library-2.8.0.jar
Download http://repo1.maven.org/maven2/org/scala-lang/scala-swing/2.8.0/scala-swing-2.8.0.jar
Download http://repo1.maven.org/maven2/org/jyaml/jyaml/1.3/jyaml-1.3.jar
:compileScala
Download http://repo1.maven.org/maven2/org/scala-lang/scala-compiler/2.8.0/scala-compiler-2.8.0.pom
Download http://repo1.maven.org/maven2/org/scala-lang/scala-compiler/2.8.0/scala-compiler-2.8.0.jar

BUILD SUCCESSFUL

Total time: 4 mins 7.986 secs
// Testing project properties
// Set this one with gradle -PuseMavenStyle
useMavenStyle = hasProperty('useMavenStyle')
if (useMavenStyle) {
    println 'Using Maven-style dependencies'
}

// Tell the dependency management where to find the libraries
repositories {
    if (useMavenStyle) {
        // Classical form: online
        mavenCentral()
        // Can probably be omitted, as it is already in mavenCentral
        mavenRepo urls: 'http://scala-tools.org/repo-releases/'
    } else {
        // My way: use the stuff I downloaded manually!
        flatDir name: 'localRepository', dirs: 'C:/Java/libraries'
    }
}

dependencies {
    // Declares scalaTools so that Gradle will know how to compile Scala code
    if (useMavenStyle) {
        // Libraries needed to run the Scala tools
        scalaTools 'org.scala-lang:scala-compiler:2.8.0'
        scalaTools 'org.scala-lang:scala-library:2.8.0'

        // Libraries needed for Scala API
        compile 'org.scala-lang:scala-library:2.8.0'
        compile 'org.scala-lang:scala-swing:2.8.0'
        compile group: 'org.jyaml', name: 'jyaml', version: '1.3'
    } else {
        scalaTools scalaTree
//~     scalaTools fileTree(dir: "$scalaHome/lib").matching { include '*.jar' }

        // Libraries to use when compiling
        compile scalaTree

        // Just declare the name of the library, it knows where to find it from the 'repositories' part
        compile name: 'jyaml-1.3' // Name of the jar, without the extension
    }

    // Libraries to use when running -- actually not necessary as it inherits from compile dependencies
    // It is probably more used to declare additional dependencies (like a DB driver, loaded by reflection).
//~ 	runtime scalaTree
    runtime name: 'jyaml', version: '1.3' // Alternative syntax
}

Update 1: 2010-11-04 - Using Gradle 0.9-rc-2 and more experiments.
Update 2: 2010-11-04 - Testing Maven-like dependencies.

Philippe Lhostein Scala   Monday 1 November 2010 à 17:54
Add Comment Link to entry

Trackbacks
Trackback specific URI for this entry

No Trackbacks

Comments
Display comments as (Linear | Threaded)

No comments

Add Comment

Standard emoticons like :-) and ;-) are converted to images.
E-Mail addresses will not be displayed and will only be used for E-Mail notifications

To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.
CAPTCHA

 
 
 
Submitted comments will be subject to moderation before being displayed.
 
 

Catégories

  • XML Agacements typographiques & irritations sémantiques (5)
  • XML Programmation
  • XML Lua
  • XML C/C++
  • XML Java (2)
  • XML AutoHotkey
  • XML Expressions régulières
  • XML Processing (2)
  • XML JavaFX (8)
  • XML Scala (2)
  • XML Logiciels
  • XML SciTE
  • XML Arts (2)
  • XML BD
  • XML Animation
  • XML Illustration
  • XML Musique
  • XML Général (2)
  • XML Web (1)
  • XML JavaScript
  • XML PHP
  • XML (X)HTML, CSS
  • XML Humour, humeur et autres opinions (2)
  • XML Technique (1)


All categories

Calendar

« March '21 »  
Mo Tu We Th Fr Sa Su
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        

Archives

  • March 2021 (0)
  • February 2021 (0)
  • January 2021 (0)
  • Recent...
  • Older...

Syndiquer ce Blog

  • XML RSS 1.0 feed
  • XML RSS 2.0 feed
  • ATOM/XML ATOM 1.0 feed
  • XML RSS 2.0 Comments
  • Add to Google

© PhiLho / PhiLhoSoft | Site principal      Design par PhiLho (basé sur un template de ceejay/Carl Galloway) | Motorisé par SerendipityAdmin