Showing posts with label gradle. Show all posts
Showing posts with label gradle. Show all posts

Thursday, June 26, 2014

Migrating from ANT and IVY to Gradle

Related to the previous post, Migrating from Maven to Gradle, here are some things I found when attempting to migrate an ANT / IVY build to Gradle.

Advantages over ANT/IVY
  • XML is not for humans - Gradle's DSL is much more readable and more concise.   No need for 'ivy.xml' and 'build.xml' and tons of 'properties files'.
  • Conventions -  Avoid re-inventing the wheel.   If you use the conventions for the Gradle plugins, this eliminates a great deal of code and makes your project look 'normal' to other people.  They can just dive right in and be productive.  "You are not special"  ;)
  • Declarative - Gradle is more declarative and eliminates a ton of boring, boilerplate code compared to ANT.
  • Plugins - Eliminate even more boilerplate code, and gain some conventions. 
    • Get dependencies.
    • Compile  the main code and the test code.  Process any resources.   Compile dependencies (multi-module).
    • Run the test suite and generate reports.
    • Jar up the main code.
  • Self install - Gradle self-installs from VCS via the gradle wrapper.
  • 'one kind of stuff' - Dependencies are declared right in the build file.
  • Daemon mode!
Getting started
  • Add build.gradle and settings.gradle to the root directory.   Can be empty files at first.
  • Gotcha #1: If you are using Subversion with the standard layout, Gradle will think that the project is named 'trunk' (or whatever the branch directory is... Subversion really sucks at branches!).

    To fix this, simply add rootProject.name='the-real-project-name' in settings.gradle.
  • Re-open the IDEA project.  IDEA will import the gradle project.
    Eclipse probably has something similar.
  • For a Java project, apply the Java plugin in build.gradle: apply plugin: 'java'
    This will automatically add the expected tasks for compiling, running tests, packaging as a jar, etc.  You don't have to write this boring stuff!
  • Custom source locations - Let's say the project has the sources in src and test_src.  This is not the standard layout for the java plugin, so we'll need to configure that in build.gradle:

    sourceSets {
        main {
            java {
                srcDir 'src'
            }
            resources {
                srcDir 'conf'
            }
        }
        test {
            java {
                srcDir 'test_src'
            }
        }
    }
    
  • Now we need to add the dependencies.   Since Gradle is based on Groovy, it's easy to make a simple converter:
    task convertIvyDeps << {
        def ivyXml = new XmlParser().parse(new File("ivy.xml"))
    
        println "dependencies {"
        ivyXml.dependencies.dependency.each {
            def scope = it.@conf?.contains("test") ? "testCompile" : "compile"
            println("\t$scope \"${it.@org}:${it.@name}:${it.@rev}\"")
        }
        println "}"
    }
    

    Just run the task and paste the output into the dependencies closure.

    We can also do something more radical: Parse the ivy.xml and populate the dependencies that way, see this post.
  •  Gocha #2: If you are using a properties file to define versions in ivy.xml, this will be a little different in Gradle.
    • Gradle supports 'extra properties' typically defined in an 'ext' closure.   These can be referenced inside double quoted strings in the dependencies closure.
    • Gradle doesn't like dots in the extra property names.   I changed them to underscore.  For example:
       
      ext {
        version_junit="4.11"
      }
      
      dependencies {
         ... blah blah blah...
         testCompile "junit:junit:${version_junit}"
      }
      
    • It's nice having everything defined in one place. :)
  • At this point, you have a basic build with compilation, testing, test reports, and all that.

Friday, June 20, 2014

Migrating from Maven to Gradle

I thought I'd share some of my experiences with migrating from Maven to Gradle for a small Java open source project.

The Strategy

First, what's the best way to do this?   The project is a fairly straightforward Java project without complex Maven pom.xml files, so maybe the best way forward is to just create a Gradle build along side the Maven one.

Some advantages over Maven


 Here are some of the advantages I found when using Gradle:
  • The 'java' plugin does almost all the work.   It defines something equivalent to the Maven lifecycle in terms of compilation, testing, and packaging.
  • Much smaller configuration.  No more verbose pom.xml files!
  • A multi-module project can be configured from the top-level build.gradle file.
  • Dependency specifications are more terse and also more readable.
  • It's much straightforward to get Gradle to use libraries that are not in the Maven repositories, e.g. in version control.   (However, I do believe that it's best to make a private repository with Artifactory or Nexus and install the libraries there, rather than keeping them in version control).
  • Dependencies between sub-modules is also very easy.
  • The whole parent/aggregator/dep-management thing in Maven is a bit clunky.   Gradle makes this much easier.  You can even do a multi-module build with a single Gradle build file if you want.

 First Attempt

Here are the steps I took.
  • Using IDEA, create a new Gradle project where the existing sources are.  Set the location of the Gradle installation.   You should see the Gradle tab on the right side panel.
  •  Create a build.gradle file and a settings.gradle file in the project root directory.
  • The basic multi-module structure can be the same as a Maven multi-module build:
    • A 'main' build.gradle file in the root directory.   Along with a settings.gradle file that has the overall settings.
    • Sub-directories for each module.
    • Each module directory has it's own build.gradle file.
    • NOTE: If the module dependencies are defined correctly, building a module will also build the other dependent modules when you are in the module sub-directory!   Major win over Maven here, IMO.
  • Apply the plugins for a Java project, set the group and version, add repositories.  In this case I have a multi-module project so I'm putting all of that in the allprojects closure:

    allprojects {
      apply plugin: 'java'
      group = 'org.jegrid'
      version = '1.0-SNAPSHOT'
      repositories {
        mavenCentral()
        maven {
          url 'http://repository.jboss.org/nexus/content/groups/public'
        }
        flatDir {
          dirs "$rootDir/lib" // If we use just 'lib', the dir will be relative.
        }
      }
    }
    

    I also have some libraries in the lib directory at the top level because they are not in the global Maven repos, or in the JBoss repo. The flatDir closure will allow Gradle to look in this directory to resolve dependencies. 
  • Add dependencies.   For a multi-module build this is done inside each project closure.   Use the 'compileJava' task to make sure they are right.
In the end, this project didn't really work with Gradle because the dependencies are too old.   So, I will need to rebuild the project from the ground up anyway.   Some of the basic libraries have undergone many significant changes since the project started, so it's time to upgrade!

Basic Gradle Multi-Module Java Project Structure

Okay, so in creating a brand new project, the canonical structure is much like a Maven project.

  • In the root directory (an 'aggregator' project) there is a main build.gradle file and a settings.gradle file.   This is roughly equivalent to the root pom.xml file.
  • In each sub-project directory (module) there is a build.gradle file.   This is roughly equivalent to the module pom.xml files.
  • The settings.gradle file has an include for each sub-project.   This is roughly equivalent to the '<modules>' section of the root pom.xml file.
  • An allprojects closure in the root build.gradle file can contain dependencies to be used for all modules.   This is similar to a 'parent pom.xml' (but much easier to read!).
One thing I wanted to do right away is to create the source directories in a brand new module.  This is pretty darn easy with Gradle.   Just add a new task that iterates through the source sets and creates the directories:

  task createSourceDirectories << {
    sourceSets.all { set -> set.allSource.srcDirs.each { 
      println "creating $it ... "
      it.mkdirs() 
      }
    }
  }

I added this in the alllprojects closure, and boom! - I have the task for all of the modules.  Neato!   I can now run this on each sub-project as needed.

Porting The Code


One I had the directory layout and basic project files I can begin moving in some of the code.    I started with the basic utility code for the project and the unit tests.   Like I mentioned, this was using a very old version of JUnit, so I needed to upgrade the tests.

Diversion One - Upgrading to JUnit 4.x

Upgrading to JUnit 4.x is actually pretty easy.   For the most part it retains backwards compatibility.   There are a few reasons you might want to upgrade the tests.
  • I prefer annotations over extending TestCase.   This is a pretty simple transform:
    1. Remove 'extends TestCase'
    2. Remove the constructor that calls super.
    3. Remove the import for TestCase
    4. Add 'import static org.junit.Assert.*'
    5. Add @Test to each test method.
  • (already mentioned) Take advantage of 'import static'! import static org.junit.Assert.*
  • Expected exceptions:
    @Test(expected=java.lang.ArrayIndexOutOfBoundsException.class)
     
  • @BeforeClass and @AfterClass annotations to replace setUp() and tearDown().

Diversion Two - Using Guice or Dagger instead of PicoContainer?

I really enjoy using DI containers.  It takes so much of the boilerplate 'factory pattern' code out of the project and makes for easy de-coupling and configuring of components.   In the previous version of the project I had used PicoContainer.   

  • Pico - Pro: Good lifecycle support.   Really small JAR file.   Con: Not as type safe.  Project seems to have stalled.
  • Guice - Pro: Not as small as Pico, but still very small.   More type safe.  Large community.  Con: Bigger jar than Pico (but not too bad... without AOP its smaller).  No real lifecycle support.
  • Dagger - Pro: Really small, with a compiler! Con: Gradle doesn't have a built in plugin for running the dagger compiler (well, as far as I can tell).
I think I'll give Dagger a try as it will cause me to learn how to make a Gradle plugin.   Even if I don't succeed, I'll learn more about how Gradle works.

See also: