Wednesday, February 03, 2016

Dynamic Manifest with Gradle

Gradle makes it very easy to add dynamic information to the manifest of jar packages.

If you didn't know a jar is a zip file and the JVM will read the manifest from META-INF/MANIFEST.MF to determine the main class of an application or the classpath requirements of the specific jar and more. It is also useful to include version information to the manifest.

Let's look at an example:

jar {
    manifest {
        def buildId = System.getenv('BUILD_ID')

        if (buildId == null) {
            def hostName = System.getenv('HOSTNAME')
            def df = new java.text.SimpleDateFormat('yyyy-MM-dd\'T\'HH:mm:ss.SSSZ')
            def buildDate = df.format(new Date())
            buildId = "$hostName-$buildDate"
        }
        attributes('Manifest-Version': '1.0',
            'Implementation-Vendor': 'ACME Corp',
            'Implementation-Title': 'Road Runner Tracker Application',
            'Implementation-Version'buildId
        )
    }
}

The code in blue is the portion creating a buildId. In this case we are starting with the BUILD_ID environmental variable as supplied by a Jenkins build. If it is not present we want to use the hostname and the current timestamp.

You end up with a file that looks like this:

Manifest-Version: 1.0
Implementation-Vendor: ACME Corp
Implementation-Title: Road Runner Tracker Application
Implementation-Version: WILE-E-COYOTE-2016-02-03T12:00:00.000+0200


If you have a project with multiple sub-projects you notice all projects are repackaged during a build. This may not be big deal if your project has 5 or even 10 modules, but if your project has 300 modules and a repackage also means a publish to a remote binary repository the time penalty could be huge.

The solution is a simple one. Delay the evaluation of the manifest until the actual package task is executed. Gradle allows you to define closures with doFirst and doLast that are added before or after task execution.

jar {
    doFirst {

        manifest {
            def buildId = System.getenv('BUILD_ID')

            if (buildId == null) {
                def hostName = System.getenv('HOSTNAME')
                def df = new java.text.SimpleDateFormat('yyyy-MM-dd\'T\'HH:mm:ss.SSSZ')
                def buildDate = df.format(new Date())
                buildId = "$hostName-$buildDate"
            }
            attributes('Manifest-Version''1.0',
                'Implementation-Vendor''ACME Corp',
                'Implementation-Title''Road Runner Tracker Application',
                'Implementation-Version'buildId
            )
        }
    }

}


The manifest is only evaluated when the closure doFirst is executed. The manifest model is used and the file created for packaging.


You will not be able to delay evaluation of all DSL this way. There are parts of the model that cannot be changed after the projects have been evaluated. 

Post a Comment