sábado, 2 de mayo de 2015

Some lessons learned: Gradle



After a year dealing with Gradle in different companies, I think it's high time to start collecting some patterns and suggestions.

1) Avoid redundant folder declaration.

Remember, apply plugin java already sets up some defaults folder, you don't need to declare them if you don't plan to change them.

Recommended
apply plugin: 'java'

Redundant
apply plugin: 'java' 

sourceSets {    
    main {        
        java {            
            srcDir 'src/main/java'        
        }        
        resources {            
            srcDir 'src/main/resources'        
        }    
    }     

    test {        
        java {            
            srcDir 'src/test/java'        
        }        
        resources {            
            srcDir 'src/test/resources'        
        }    
    }
}

Groovy plugin and scala plugin also declares other folders, just read some documentation before start using them.


2) Choose your syntax for dependency management.

Gradle allows you two syntax for dependency declaration.
  • Extended (group: 'ws.lazylogin', name: 'lazylogin-core', version: '1.0.0')
  • Compact ('ws.lazylogin:lazylogin-core:1.0.0').
Be consistent, your users and colleagues will be pleased.

I prefer the compact one :)


3) Your users don't need to know project details to compile it.

Any project should be able to clean + build from the code and a JVM in place. Ideally nothing else should be required.

Although some projects will inevitably break this suggestion, try not to.

Some examples of build breaking customs.
  1. Usage of variables declared in ${user.home}/.gradle/gradle.properties that  don't have default value in build.gradle or exist in project's gradle.properties.
    • This would fail for anyone not owning / not knowing what variables to include / contaminate hi/er user gradle properties file. No no no no no.
  2.  Assumption of executables in the PATH or some predefined location.
Try your harder to make your project easy to build.


4) Prevent incorrect dependency versioning

Declare your dependencies and version in a single place (that was a good idea in Maven's universe) and reuse those dependencies by its variable name.

The risk of not following this advice is the likelihood you'll end up using different versions for the same library in different modules in the same project, and you don't want that, neither do I.

Solution would look like this:

Dependency declaration (they can be declared in a different file)
ext{   
    //VERSIONS
    v = [           
        spring: '3.2.0.RELEASE',
        jme3: '3.1.0-snapshot-github'
    ]   

    deps = [           
        //SPRING           
        spring_core: 'org.springframework:spring-core:'+v.spring,
        spring_beans: 'org.springframework:spring-beans:'+v.spring,
        spring_context: 'org.springframework:spring-context:'+v.spring,
        //JME3           
        jme3_core: 'com.jme3:jme3-core:' + v.jme3,           
        jme3_effects: 'com.jme3:jme3-effects:' + v.jme3
    ]
}

Dependency usage for any subproject
dependencies {   
    compile (           
        project(':mod-api'),      
        deps.jme3_core,           
        deps.jme3_effects,           
        deps.spring_beans,           
        deps.spring_core )
}

Instead of the more error prone way:

Dependency usage for a given subproject A
dependencies {   
    compile (           
        project(':mod-api'),                        
        'com.jme3:jme3-core:3.1.0-snapshot-github',           
        'com.jme3:jme3-effects:3.1.0-snapshot-github',           
        'org.springframework:spring-beans:3.2.0.RELEASE',           
        'org.springframework:spring-core:3.2.0.RELEASE' )
}

Dependency usage for a given subproject B
dependencies {   
    compile (           
        project(':mod-api'),   
        'com.jme3:jme3-core:3.1.0-snapshot-github',           
        'com.jme3:jme3-effects:3.1.0-snapshot-github',           
        'org.springframework:spring-beans:3.1.0.RELEASE',           
        'org.springframework:spring-core:3.2.0.RELEASE' )
}


5) Group your dependency by dependency configuration.

It's nicer :)

Dependencies grouped by configurations
compile (           
    project(':mod-api'),           
    libs.lazylogin_common_context,           
    libs.nifty,           
    libs.jme3_core,           
    libs.jme3_effects,           
    libs.spring_beans,           
    libs.spring_core,           
    libs.jackson_dataformat_yaml,           
    libs.jackson_databind
)
runtime (
    libs.nifty_default_controls,           
    libs.eventbus,           
    libs.auto_value))    

Dependencies ungrouped (and disorganized)
compile project(':mod-api')          
compile libs.lazylogin_common_context,           
compile libs.nifty,           
compile libs.jme3_core,           
compile libs.jme3_effects,           
compile libs.spring_beans,           
compile libs.spring_core,     
runtime libs.nifty_default_controls,      
compile libs.jackson_dataformat_yaml,           
compile libs.jackson_databind,           
runtime libs.eventbus,           
runtime libs.auto_value)

(Not possible when you want to exclude dependencies from dependencies).