How Gradle Makes Windows and *nix App Deploy Scripts

Thanx to Mk Yong for this idea: http://www.mkyong.com/gradle/gradle-create-a-jar-file-with-dependencies/

Assets for this post are held here: https://github.com/jnorthr/gradleMakeWindowsBat.git


 

Here is a skeleton gradle project template to build an executable java jar plus all distributable assets for a ‘HelloWorld’ java source. This is to deploy your app as a stand-alone app on a client system, something like a GUI, or batch job app – NOT a web service.

The tasks to do this are a freebie when a build.gradle file includes a plugin module named ‘application‘ courtesy of

> apply plugin:’application’

Gradle Build Tool

This project includes a full gradle build tool as a ‘wrapper’ bit inside this download. So there’s no need install gradle on your system. You can run this app once you’ve done a git clone.

Git Source Code Control

Yes, you need Git installed on your system to get started. So change into a new folder/directory on your local system and do this:

git clone https://github.com/jnorthr/gradleMakeWindowsBat.git

This makes a project directory folder named gradleMakeWindowsBat so cd into that.

You could use this approach as a foundation to create several brand-new projects. Sure you would need to change gradleMakeWindowsBat to something else and the main class names would change too, but a lot of the setup work is done for you here.

Build / Check

Now you can just run the gradle wrapper like so:

> gradlew check

or

> bash ./gradlew check

This will ask gradle to download any dependency bits it needs to make it happy, and check that everything is cool on your installed version.

Build

Ok, to make it all happen, run gradlew again without options.

> gradlew

or

> bash ./gradlew

The default tasks are run. These are:

defaultTasks ‘clean’, ‘build’, ‘javadoc’, ‘installApp’, ‘startScripts’, ‘fatJar’, ‘distTar’, ‘distZip’, ‘run’

Tasks to Deploy An App

clean – gradle cleans the environment before starting

– build – when javac – compiles the HelloWorld module

javadoc – the javadoc API document is created with any source code comments

– installApp makes a folder named install and a sub-folder of gradleMakeWindowsBat containing /bin, /docs, /lib folders with all the pieces to do a full install on a client system. Ship the gradleMakeWindowsBat folder and change the client’s OS path variable to include gradleMakeWindowsBat/bin then on the command line you/they can run this job by typing the name of this  batch script file as gradleMakeWindowsBat  – easy-peasy !

startScripts – makes a folder of identical windoze and unix batch script files. These scripts will run the core job noted in the build.gradle file as

mainClassName = "com.jnorthr.DateUtils"

– yeah, you can change that for your own needs. Be sure to add the .jar files in build/libs to the system CLASSPATH variable.

fatJar – my favorite ! makes an executable jar file of the java/groovy/scala/jvm language classes and bits. You can just double click on the gradleMakeWindowsBat-all-1.0.jar file to run your app.

Alternatively from a command line with the jar in the same folder, you can do this:

> java -jar gradleMakeWindowsBat-all-1.0.jar

Everything needed has been included in this ‘fat’ jar !


Tasks to Transfer The Project Build Environment

When we need to move/copy/transfer a complete project that includes all the development tools, build stuff and source code controls, we can use either of these two tasks depending on the target computer system’s OS:

distTar packages up the full folder directory and makes it ready for shipment to a target computer system running Unix/Linux/Ubuntu etc.

– distZip packages up the full project folder directory in a compressed archive format. This is useful as a general-purpose tool to move a complete project around to target computer systems where the OS is unconventional like, say, Windows, IBM, DEC.

Both tasks run as a default set of tasks. Project build times may seem excessive as so many solutions are being produced. To speed things up you could alter the build.gradle file to remove these two tasks if you do not plan to move the project to another system.

Task to Run This App

– the run task starts execution of an app. The app name is declared in the build.gradle file as

> mainClassName = "com.jnorthr.DateUtils"

change to suit your own requirements.

Gradle Properties in Cloud Foundry Closure

See notes here: https://github.com/cloudfoundry/cf-java-client/blob/master/cloudfoundry-gradle-plugin/README.md

1. according to gradle documentation, we can put our PaaS cloud foundry target credentials into

~/.gradle/gradle.properties

like this:

cf.username=’men@work.com’

with single quotes. If we do that, we get this result:

:template-project:cf-login
 Authenticating to 'https://api.run.pivotal.io' with username ''men@work.com''
 POST request for "https://login.run.pivotal.io/oauth/token" resulted in 401 (Unauthorized); invoking error handler
 :template-project:cf-login FAILED

FAILURE: Build failed with an exception.

but removing the single quotes makes it work.

NOTE: Gripe !! the credentials in gradle.properties take precedence over those declared in our gradle script closure !!! So if we deploy the same app to several different PaaS targets we can not declare credentials in gradle.properties we just need a build.gradle script for each PaaS target  😛

2. plugin cf-push where there is already an existing service leaves orphan services that were originally attached. Is this because of the “rediscloud-${randomWord}” random word declaration in the cloud foundry closure ?
3. same as 2. but also looses the uri as there is no/or a faulty rebind of uri to our app

 

4. cf-push of new app that does not already exist appears to work, correctly builds services and binds the uri correctly.

 

5. also note that ~/.gradle/gradle.properties needs a password too, so:

cf.password=porkypig

Gradle Zip All Feature

Had a need to zip an entire website, all the bits and pieces, images, you name it. Had tried to write a gradle script that would do it from a folder like this:

folder layout of my web app
folder layout of my web app

and you would have thought it’d be easy-peasy. Well not so on my aging windows XP system. Any script using a gradle (type:Zip) task would bomb with ‘portion of file in use by another task’ – drat !

So dumped whole web app to a USB key and took it to my Apple Macbook 10.6.8. Using the same gradlew command this worked a charm:

 task zipAll(type: Zip) {
 from projectDir
 exclude 'build/**'
 }

Basically, it says from the root of the directory folder of my web app, thats the ‘projectDir‘ declaration, zip it all but don’t include the sub-folder build/ or it’s contents either. Giving me:

Screen shot 2014-03-17 at 7.13.48 AM

Well now, that’s more like it ! And since i did not say where to store the zip file, gradle puts it into the build/distributions/ folder :

Screen shot 2014-03-17 at 7.15.52 AM

but also demonstrates why i excluded the build/ folder from the zip task as it would create a circular reference issue, i.e. zip into the build folder that gets included in the zip archive, which gets … 😛

Mixed Groovy and Java Class in Gradle Build

Have seen this question often before. If we have a groovy script that builds an object that a java class needs, why is it not seen during compile time ? Looked for ages for the answer to this. Unless both/all java and groovy sources live together in sourceSets.src.main.groovy directory, the groovy compile cannot ‘see’ the class from the groovy source to pass tho that java compiler when requested. Using

apply plugin: ‘groovy’

takes precedence over the java plugin. Any  xxx.java source files are passed to the java compiler by the groovy mechanism. The groovy classes are invisible to the java compiler. Assume this example

src/main/groovy/Person.groovy

src/main/java/PersonManager.java

the Person class created in the groovy step cannot be seen by the java compiler. Drat ! But there is a work-around until gradle change this behaviour. Try this in build.gradle :

sourceSets.main.java.srcDirs = []
sourceSets.main.groovy.srcDir 'src/main/java'

hope it works for you :)

A Spock and Gradle Test Session

SpockDemo
=========
 
A brief gradle build sample including some Java and groovy classes, then all tested with Spock test tool
 
You only need a working java JVM on your system, version 1.5+ and you do not need to install gradle or groovy or java JDK
 
From command terminal, do these commands :
 
mkdir xxx
cd xxx
cd SpockDemo
 
gradlew tasks            // to see what tasks are available in gradle
 
gradlew run              // run the gradle wrapper which reads the build.gradle file
:compileJava
:compileGroovy
:processResources UP-TO-DATE
:classes
:run
JavaSample               // since mainClassName = “JavaSample” that’s what is run giving Hello World
Hello World
 
gradlew clean build     // recompiles everything and runs tests declared in JavaSampleTests.groovy
:clean
:compileJava
:compileGroovy
:processResources UP-TO-DATE
:classes
:jar
:assemble
:compileTestJava UP-TO-DATE
:compileTestGroovy
:processTestResources UP-TO-DATE
:testClasses
:test
 
CourseServiceSpec > Create new course with teacher and description FAILED
    org.spockframework.runtime.SpockComparisonFailure at groovydemoTests.groovy:19
 
JavaSampleTests > Test 2 should getName for Andy FAILED
    org.spockframework.runtime.ConditionNotSatisfiedError at JavaSampleTests.groovy:50
 
JavaSampleTests > Test 3 should getName for Leanne FAILED
    org.spockframework.runtime.ConditionNotSatisfiedError at JavaSampleTests.groovy:50
 
JavaSampleTests > Test 4 should keep Fred FAILED
    org.spockframework.runtime.ConditionNotSatisfiedError at JavaSampleTests.groovy:69
 
JavaSampleTests > Test 5 should keep Andy FAILED
    org.spockframework.runtime.ConditionNotSatisfiedError at JavaSampleTests.groovy:69
 
JavaSampleTests > Test 6 should keep Leanne FAILED
    org.spockframework.runtime.ConditionNotSatisfiedError at JavaSampleTests.groovy:69
 
JavaSampleTests > Test 7 should use groovy.property logic to get name as Fred FAILED
    org.spockframework.runtime.ConditionNotSatisfiedError at JavaSampleTests.groovy:87
 
JavaSampleTests > Test 9 should use groovy.property logic to get name as Leanne FAILED
    org.spockframework.runtime.ConditionNotSatisfiedError at JavaSampleTests.groovy:87
 
JavaSampleTests > Test 10 should use groovy.property logic to get name as Fred FAILED
    org.spockframework.runtime.ConditionNotSatisfiedError at JavaSampleTests.groovy:105
 
JavaSampleTests > Test 11 should use groovy.property logic to get name as Andy FAILED
    org.spockframework.runtime.ConditionNotSatisfiedError at JavaSampleTests.groovy:105
 
JavaSampleTests > Test 12 should use groovy.property logic to get name as Leanne FAILED
    org.spockframework.runtime.ConditionNotSatisfiedError at JavaSampleTests.groovy:105
 
15 tests completed, 11 failed
:test FAILED
 
FAILURE: Build failed with an exception.
 
* What went wrong:
Execution failed for task ‘:test’.
> There were failing tests. See the report at: file:///Volumes/PENDRIVE/gradledemo/SpockDemo/build/reports/tests/index.html
 
/*
 
Follow directions above to try for yourself. I left a few bugs in the test just for demo purposes. 
 
When Spock tests fail, Spock creates a set of html pages to describe the failures. Copy the “file:///Vol…” name into a browser address bar, press enter key to see what Spock found — 
 
Enjoy !
 
 

Sample Gradle Tasks and Notes

www.gradleware.com
Well worth a look-see for more Gradle inspiration: http://evgeny-goldin.com/wiki/Gradle

Sample Gradle Zip Task

task zip(type: Zip) {
    from jar.outputs.files
    from('scripts/') {
      fileMode = 0755
      include '**/*.sh'
      include '**/*.bat'
    }
    from('lib/') {
        include '**/*.jar'
        into('lib')
    }
    from('.') {
        include 'project.config'
    }
}

Custom Task Types

extend DefaultTask

Actions:

@org.gradle.api.tasks.TaskAction
class FtpTask extends DefaultTask 
{
    String host = 'docs.mycompany.com'
    @TaskAction
    def ftp() {
        println host
        // do something complicated
    }
}

OR

class FtpTask extends DefaultTask {
    String host = 'docs.mycompany.com'
    String user
    String password

    @TaskAction
    def ftp() { println host }
}

task dosomething( type: FtpTask, dependsOn: ... ) {
    user     = '...'
    password = '...'
}

List Method Pointers and DSL

Groovy provides a way to have a reference to an object’s method :

What a great way to create your own DSL !

def shoppingList = []
def add = shoppingList.&add
def remove = shoppingList.&remove

add "Milk"
add "Bread"
add "Beer"
remove "Beer"
add "Apples"
print shopping

Plugins

Plugins == Build Scripts

    Two Flavors:

  • Another build script (local or remote) (Script Plugin)
  • A class implementing org.gradle.api.Plugin (Binary Plugin)
apply from: 'otherScript.gradle'
apply from: 'http://mycomp.com/otherScript.gradle'

Applying Plugins

  • Any gradle script can be a plugin.
  • Binary plugins must be in the build script classpath
  • can have id’s (meta properties in the jar).

The built-in plugins are by default in the build script classpath.

apply plugin: org.gradle.api.plugins.JavaPlugin
apply plugin: 'java'

What Plugins Can Do

  • Configure the project object (e.g. add task instances)
  • Add other classes to classpath (e.g. custom task types)
  • Add props and methods to the project object (extend DSL).
  • Build Script Decomposition
  • Separate Imperative from Declarative
  • Modularization
  • Code Reuse

Test Task Example

Tests auto-detected in sourceSets.test.classes

test {
    jvmArgs ["-Xmx512M"]
    include "**/tests/special/**/*Test.class" // Disables Auto Detection of which tests to run
    exclude "**/Old*Test.class"
    forkEvery = 30
    maxParallelForks = guessMaxForks()
}

def guessMaxForks() {
    int processors =
    Runtime.runtime.availableProcessors()
    return Math.max(2, (int) (processors / 2))
}

Ant Tasks

Gradle provides an instance of the Groovy AntBuilder

ant.delete dir: 'someDir' 
ant {     
    ftp(server: "ftp.comp.org", userid: 'me', ...) 
    { 
      fileset(dir: "htdocs/manual") 
      { 
        include name: "**/*.html" 
      } // high end

      myFileTree.addToAntBuilder(ant, 'fileset') 
    } 
    mkdir dir: 'someDir' 
} // end of ant

Picture 1

Dependencies & Java Plugin

Picture 3

apply plugin: 'java'
configurations { myConf.extendsFrom compile }
dependencies {
    compile "junit:junit:4.4"
    runtime org:'asm', name:'asm-all', version:'3.2'
    testCompile files('file1.jar')
    myConf "log4j:log4j:1.2.9"
}
  • The Java plugin adds configurations.
  • Many Java plugin tasks use those configurations as default input values (e.g. test).
  • Configurations can extend each other.

Picture 2

Repositories

Any Maven/Ivy repository can be accessed.

Very flexible layouts are possible for non Maven repositories.

repositories {
    mavenCentral()
    mavenCentral(urls: ['http://repo.com'])
    mavenRepo(urls: ['http://repo1.com',
                     'http://repo2.com'])
    flatDir(dirs: ['core', 'template-project'])
}

Global Properties

myDocsDestDir = "$buildDir/myDocs"

task myDocs << {
copy {
    from 'someDir'
    into myDocsDestDir
  }
}

task zip(type: Zip) {
    from myDocsDestDir
}

Adds a dynamic property inside task definition

task myDocs {
destDir = "$buildDir/myDocs"
doFirst {
  copy 
  {
   from 'someDir'
   into destDir
  } 
 } 
}

task zip(type: Zip) {
    from myDocs.destDir // ref.to task's destDir !!
}

Adds a Dynamic Method

task bar { 
   serviceUrl = ... 
   domainGroup = { getGroup(serviceUrl) } 
} 
task foo { 
   fooProp = bar.domainGroup() // use new method of bar task 
}

Domain Object Container

Configuration Rules

Provided by the domain object container

tasks.allObjects { task ->
    task.doFirst { println 'rule for all tasks' }
}
tasks.withType(Jar).allObjects { jar ->
    jar.destinationDir = 'somePath'
    jar.doLast { /* do something */ }
}
tasks.whenAdded { task ->; ... }

Init Scripts

Init scripts are run before the build starts. This allows us to:

  • Set up properties based on the current environment
  • Define machine specific details, such as where JDKs are installed.
  • Register build listeners.
  • Enhance builds you don’t want to touch.
  • GRADLE_USER_HOME/init.gradle is automatically applied as an init script.

You can specify any init script via the -I command line option.

>gradle assemble -I ci-init.gradle

Sample Init Script

initscript {
    repositories {
      mavenCentral()
    }
    dependencies {
      classpath 'org.apache.commons:commons-math:2.0'
    }
}
gradle.startParamter // do something with them
gradle.addBuildListener ...

Task/Project Paths

For projects and tasks there is a fully qualified path notation:

  • :root project
  • :clean (the clean task of the root project)
  • :api (the api project)
  • :services:webservice (the webservice project)
  • :services:webservice:clean (the clean task of webservice)

>gradle :api:classes

Multi-Project Builds

Picture 5

    Caelyf Multiproject Layout

  1. caelyfCore
  2. api
  3. website
  4. project-template
  5. shared
Configuration Injection
subprojects {
    apply plugin: 'java'
    dependencies {
        compile "commons-lang:commons-lang:3.1"
        testCompile "junit:junit:4.4"
    }
    test {
        jvmArgs: ['Xmx512M']
    }
}
Filtered injection
configure(nonWebProjects()) {
    jar.manifest.attributes
    Implementor: 'Gradle-Man'
}

def nonWebProjects() {
    subprojects.findAll {project ->
        !project.name.startsWith('web')
    }
}

Project Dependencies & Partial builds

dependencies {
   compile "commons-lang:commons-lang:3.1", project(':shared')
}

>gradle build
>gradle buildDependents
>gradle buildNeeded

or name matching:

>gradle build
>gradle classes
>gradle war

For projects and tasks there is a fully qualified path notation:

 :root project
 :clean (the clean task of the root project)
 :api (the api project)
 :website:webservice (the webservice project)
 :website:webservice:clean (the clean task of webservice)
 >gradle :api:classes

Separate Config/Execution Hierarchy

..

EVEN MORE

  • Smart Merging
  • Smart Exclusion
  • Skipping Tasks
  • Conditional Tasks
  • Hooks
  • Ivy
  • Custom Tasks