An opinionated way to build Gradle projects

If given a choice I'd pick Gradle over Maven as build tool 9 times out of 10. Gradle is a very extensible tool which has allowed me to customize my builds exactly how I want them to be, however this flexibility comes at a price as there's a lot of configuration that has to applied to make a project follow a certain structure. Let's say you've created a project and release it as Open Source. You'd typically post a release to Maven Central which requires following these guidelines, which include

  • A well defined POM file.
  • Attach a sources JAR.
  • Attach a javadoc JAR.
  • PGP signatures on all artifacts.
  • A Sonatype account.

If using Maven as build tool you'll cover the first point by making sure your POM has the minimum required entries as shown in the guidelines page. For the second and third point you'd have to add two additional plugins to your POM: source-maven-plugin and javadoc-maven-plugin. These plugins let you create matching source and javadoc JAR files, however you'd have to also add more XML content to your POM file should you want these JAR files to be generated automatically during a build, as shown here and here. Generating a PGP signature requires configuring the signing plugin or do it by hand (explanation).

Turning to Gradle you'd have to apply and configure the maven-publish plugin to get the first point done. For the second and third points you'd have to provide custom tasks for packaging sources and javadoc (to be fair you can also use the nebula.source-jar and nebula.javadoc-jar plugins). Signing artifacts is also a matter of additional configuration but you can skip this part if you publish artifacts to Bintray first, then sync to Maven Central, as every Bintray hosted repository can be configured to automatically sign artifacts upon upload.

When configuring the gradle-bintray-plugin you'll notice there's some duplication with the data required by this plugin and the data required by the generated POM. Bintray also requires you to choose a license, which may duplicate data required by the license-gradle-plugin; oh yes, it's important to verify all your files have the proper license headers. And as we continue adding more features to the build we'll notice the growing need of centralized project data and more conventions. Luckily the Gradle plugin system is flexible enough to allow just that.

Based on lessons learned by managing multiple Open Source projects with Gradle across the years while dealing mostly with Maven builds at customer's side I've come up with a series of conventions that suite my style. These conventions also follow best practices found in other popular Open Source projects regardless of their build tool of choice. Of course one person's conventions may not be suited for someone else, please keep that in mind. Without further ado I give you: the kordamp-gradle-plugins project.

This project provides a series of plugins that can be used in isolation or combined with one another. Here's a quick rundown of their responsibilities

Id Responsibility
org.kordamp.gradle.base Provides a config extension and a configuration DSL.
org.kordamp.gradle.build-info Exposes build information such as time/date, JDK, etc.
org.kordamp.gradle.source-jar Generates a source JAR and automatically attaches it to a Maven publication.
org.kordamp.gradle.apidoc Generates a javadoc JAR and automatically attaches it to a Maven publication.
org.kordamp.gradle.minpom Generates a minimum POM that should be packaged under META-INF/maven.
org.kordamp.gradle.jar Updates JAR contents with minpom and Manifest entries with data provided by the build-info plugin.
org.kordamp.gradle.jacoco Configures JaCoCo reports for all Test tasks. Also configures aggregate reports.
org.kordamp.gradle.publishing Configures the POM based on data provided by the DSL.
org.kordamp.gradle.bintray Configures the bintray with data provided by the DSL.
org.kordamp.gradle.integration-test Adds an integration test sourceSet and companion test/report tasks.
org.kordamp.gradle.functional-test Adds a functional test sourceSet and companion test/report tasks.
org.kordamp.gradle.license Configures the license plugin with data provided by the DSL.
org.kordamp.gradle.guide Configures the asciidoctor plugin with data provided by the DSL.
org.kordamp.gradle.project Applies all other projects (except integration, functional, and guide).

Applying the org.kordamp.gradle.project on a multi project build triggers the configuration of every project with most of the plugins found in the table. This allows for minimum configuration like the one shown next

plugins {
    id 'org.kordamp.gradle.project' version '0.3.0'
    id 'org.kordamp.gradle.stats'   version '0.2.2'
}

config {
    release = (rootProject.findProperty('release') ?: false).toBoolean()

    info {
        name          = 'harmonicfx'
        vendor        = 'Kordamp'
        inceptionYear = '2013'
        description   = 'HarmonicFX - Friday Fun components by @hansolo_'
        tags          = ['javafx', 'widgets']

        links {
            website      = 'https://github.com/aalmiray/harmonicfx'
            issueTracker = 'https://github.com/aalmiray/harmonicfx/issues'
            scm          = 'https://github.com/aalmiray/harmonicfx.git'
        }

        licenses {
            license {
                id = org.kordamp.gradle.model.LicenseId.APACHE_2_0
            }
        }

        credentials {
            sonatype {
                // from ~/.gradle/gradle.properties
                username = project.mavenUsername
                password = project.mavenPassword
            }
        }

        bintray {
            credentials {
                // from ~/.gradle/gradle.properties
                username = project.bintrayUsername
                password = project.bintrayApiKey
            }
            repo       = 'kordamp'
            userOrg    = 'aalmiray'
            name       = 'harmonicfx'
            githubRepo = 'aalmiray/harmonicfx'
        }

        people {
            person {
                id    = 'hansolo'
                name  = 'Gerrit Grunwald'
                roles = ['developer', 'author']
            }
            person {
                id    = 'aalmiray'
                name  = 'Andres Almiray'
                roles = ['developer']
            }
        }
    }
}

allprojects {
    apply plugin: 'idea'

    repositories {
        jcenter()
    }
}

subprojects { subproj ->
    apply plugin: 'java'
    apply plugin: 'org.kordamp.gradle.stats'

    license {
        mapping {
            fxml = 'XML_STYLE'
        }
        exclude '**/*.ttf'
        exclude '**/*.otf'
    }
}

There are a few wrinkles that need ironing such as full DSL validation (currently done partially). Other features that are currently in the pipeline are: building a fully operational project website such as this one (site-gradle-plugin) most likely relying on jbake; publishing said content to a Git repository (such as GitHub Pages) leveraging the gradle-git-publish, and more!

Keep on Coding!

Liked it? Take a second to support aalmiray on Patreon!
Become a patron at Patreon!

1 comment

  • Why choose (complicated) gradle over (plain and simple) maven? Especially if you don’t need anything beyond plain and simple? (and majority of the projects are fine with that)

Trackbacks/Pingbacks

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

ˆ Back To Top