Using Gradle Setup Info Outside of Gradle

There is a Dev/Ops at my company that is always trying to be clever. His ultimate goal is reduce build times, so I can’t get upset. Recently, he was trying to figure out what project were building and where they were located. He generated this list by making some assumptions about how our Gradle build was determining subprojects and created a script to generate a list of those locations. The problem with that is, if the scheme for subproject generation changed he would have to manually maintain his script.

In comes a little cheat.

Most project that you work on will have more than one subproject. It is good to separate concerns even if all the parts of the project are required to work together. This eliminates pesky problems like circular dependencies, full recompilation, etc. (See, Chapter 57.5) You can use the settings.gradle file to setup a multiproject build. A typical settings.gradle file looks like so: (Actually, this one comes from the Gradle github page.)

include 'distributions'
.
.
.
include 'modelGroovy'
include 'cunit'
include 'platformPlay'
 
rootProject.name = 'gradle'
rootProject.children.each {project ->
    String fileBaseName = project.name
    String projectDirName = "subprojects/$fileBaseName"
    project.projectDir = new File(settingsDir, projectDirName)
    project.buildFileName = "${fileBaseName}.gradle"
}

This is typical of a multiproject build–create a bunch of subprojects and set their properties. In fact, instead of listing each subproject individually, you can set a standard location for projects and resolve them dynamically.

new File("$settingsDir/subprojects/").eachDir {
    include it.name
}

Where include is the method to specify new subprojects.

The goal of the Dev/Ops is to get the list of projects and their locations. There are two ways of accomplishing this task: 1) Create a task in Gradle (that is essentially only a task with a doLast block). 2) Or, use a Groovy script to read the settings file. The problem with the first option is that once your company wide Gradle system starts to grow, so does the configuration time. This configuration time is inconsequential with the respect to the rest of the build. But you might become impatient if all you want is a quick list of projects and project locations. The problem with the second one is that it hacky. But sometimes that fun.

I’ll assume you know how to use Gradle to get this task done and just show the groovy script.

The secret is Groovy Bindings. The script goes in the root directory next to settings.gradle file. It uses bindings to mock out the key pieces that are usually found in the settings file (settindsDir, rootProject, include, project). After creating the mock data you call evaluate on the settings.gradle file. In the end the fake Project object will hold each project.


Binding binding = new Binding()
def workingDir =  new File().getCanonicalPath()

binding.setVariable("settingsDir", workingDir)

binding.setVariable('rootProject', ['name':''])
def include = { component ->
}
def projects = []
def project = {projectName->
    def currentProject = new Project(projectName)
    projects << currentProject 
    
    return  currentProject 
}
binding.setVariable("include", include)
binding.setVariable("project", project)
GroovyShell shell = new GroovyShell(binding)

shell.evaluate(new File(workingDir, 'settings.gradle'))

class Project{
    def name
    def projectDir
    def buildFileName
    def Project(def name){
        this.name = name
    }
}
def getProjects() {
    return projects
}

Gradle – Create a New File From Text

This feels like a feature that should be baked into Gradle or at least common, but nothing comes up when I search how to create a file on the fly. The requirements:

  • Create a file where one does not exist from generated text
  • Incrementallity

The following is my solution:

project.task('createFile') {
    inputs.property "myTextProperty", "any old text"
    outputs.file outputFile
    doLast{
        outputFile.write ''
        outputFile.write inputs.properties.myTextProperty
    }
}

Using the inputs and outputs makes the task incremental and the doLast is required if you don’t want the file to be created during theĀ configuration phase. You might also need to set the input property in the doLast block if the information is not available until execution. This was not necessary the last two time I was creating a file: Creating a version file from a hard coded string, and the last time I was using the Gradle classpath to generate a file.

How do you create a file on the fly?

Edit:
Here is another solution I found.

First Experience with Bintray

When I started to learn Gradle, I wrote a simple plugin. It was a fairly useless adapter for JavaExec. It automatically set up the classpath and created an extension for pointing to the main class. This was a exercise.

project.extensions.create("runSimple", RunSimpleExtension)

project.task('runSimple', type: JavaExec ) {
    project.afterEvaluate{
        main = project.runSimple.mainClass
        classpath = project.sourceSets.main.runtimeClasspath
    }
}

Recently, I’ve been beefing up my development process in Vim and installed Syntastic. This plugin provides syntax checking by running external checkers, two of which I needed–JSHint and javac. Out-of-the-box, Syntastic works great with Java, until you start adding external libraries. Fortunately, I use Gradle on all of my projects and Gradle makes it easy to determine you dependencies.

project.sourceSets.each { srcSet ->
    srcSet.java.srcDirs.each { dir ->
        classpathFiles.add(dir.absolutePath)
    }
}

So I added this functionality to my original plugin and called it gradle-utils. The problem was the hassle of using the plugin from one computer to the next. I’d have to pull the project from GitHub and publish it locally (using the maven-publish plugin). Not to mention if I made changes the whole process would start over.

In Walks jCenter

This was a perfect opportunity to try out BinTray. I’d had an account, but other than signing up, it satĀ dormant. Here are a list of the things learned while uploading my first artifact.

  • Don’t forget you have to push up your source as well as the complied classes if you want to attach you package to the public jCenter repo. I’m using the gradle maven-publish plugin and accomplish that like so:
    task sourceJar(type: Jar) {
        from sourceSets.main.groovy
        from sourceSets.main.resources
    }
    artifacts {
        archives jar, sourceJar
    }
    publishing {
        publications {
            maven(MavenPublication) {
                from components.java
                artifact sourceJar {
                    classifier "sources"
                }
            }
        }
    }
    
  • Gradle 2.1’s new developer plugin index makes include the Bintray plugin a snap. (Example of this below.)
  • In order to include your package in the the publicly accessible jCenter you have to ask. It took me longer than I would like to admit to find how to do this. I assumed that the option would be located somewhere within the package you were attempting to release, but it actually on the home page of jCenterbintray

A Personal Plugin for Personal Use

This plugin is very “me” centric, but it’s really easy to get it setup, assuming you already have the Syntastic plugin working in Vim. There are two things you need, 1) set Syntastic so that it creates a config file, and 2) add the gradle-utils plugin to your build.gradle file.

1) .vimrc

let g:syntastic_java_checkers=['checkstyle', 'javac']
let g:syntastic_java_javac_config_file_enabled = 1

2) build.gradle

buildscript {
    repositories {
      jcenter()
    }
    dependencies {
       classpath group: 'com.scuilion.gradle', name: 'utils', version: '0.2'
    }
}
apply plugin: 'utils'

Note: This is a post process plugin and should be applied at the end of your build file.

Screenshot from 2014-07-19 17:18:02
When junit is commented out of the build file, Syntastic shows that it can’t compile this particular file.

An aside: I used Gradle 2.1’s developer index to include the BinTray plugin. So instead of:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:0.5"
    }
}
apply plugin: "com.jfrog.bintray"
plugins {
    id "com.jfrog.bintray" version "0.5"
}

Pretty cool!