Java Package-Info Annotation Generation with Gradle

Posted on March 26, 2017
Share article:

Elegance and cleanliness in programming are important to me. Having a lot of package-info.java files scattered everywhere is an eyesore. However, that is exactly what I’ve prescribed in my earlier post about Java default nullability annotation set by a package.
To define default package contracts throughout your project, you need to copy the same package-info file in all packages. This does not only cloud visibility in the project view panel but also adds duplicate files in the project repo. Mind you, these files only differ by package name.

Solution

Fortunately, IDEs recognize the package-info.java files by package structure regardless of the base source folder. My suggested solution includes the following:

  1. Add a generated source folder to the classpath to host just package-info.java files
  2. Generate all package-info.java files from a template into that source folder, following the package structures from all other source folders
  3. Ignore generated sources using the VCS ignore file

Gradle Default Package-Info Generator

Below you can find the Gradle generator that takes a package-info template and applies it to all detected packages. It copies the Java info files into the package hierarchy it creates within the generated source directory specified.

Just add the below code to your root gradle.build:

import groovy.io.FileType
import groovy.text.SimpleTemplateEngine

def generatedPackageInfoDir = 'generated'

apply plugin: 'java'

repositories {
    mavenCentral()
}

sourceSets.main.java.srcDirs generatedPackageInfoDir

clean.doLast { generatePackageInfo }

task generatePackageInfo {
    new File(generatedPackageInfoDir).deleteDir()

    def packages = [] as Set

    new File('.').eachFileRecurse(FileType.FILES) {
        if (it.name.endsWith('.java')) {
            packages << ((it.text =~ "package (.+);")[0][1])
        }
    }

    packages.each {
        def dir = mkdir(generatedPackageInfoDir + '/' + it.replaceAll('\\.', '/'))
        def outputFile = new File(dir.absolutePath, 'package-info.java')
        String templateOutput = applyPackageInfoTemplate(it)
        outputFile << templateOutput
    }
}

private static String applyPackageInfoTemplate(packageName) {
    def engine = new SimpleTemplateEngine()
    def templateText = new File('package-info.template').text
    def templateParams = ['packageName': packageName]
    engine.createTemplate(templateText).make(templateParams).toString()
}

Configuration

Within the above script you’ll need to take care of the following:

  1. Define the path for the generated source folder
    def generatedPackageInfoDir = 'generated'
  2. Add the generated source folder to the source path
    sourceSets.main.java.srcDirs generatedPackageInfoDir
  3. Create a package-info.template file besides the root gradle.build file
    @MyAnnotation 
    package $packageName; 
    
    import com.company.MyAnnotation; 
    //The annotation you need to apply to all packages
  4. After you now run gradle clean, the generatePackageInfo will execute. Tweak that behavior by changing:
    clean.doLast { generatePackageInfo }

Example

Using this technique, I’ve created a sample project for setting Java Default Package Nullability as described in my previous post.

package tutorial; 

import com.opensymphony.xwork2.ActionSupport; 

public class HelloWorld extends ActionSupport { 
    private String name; public String getName() { 
        return name; 
    } 

    public void setName(String name) { 
        this.name = name; 
    } 

    public String execute() { 
        name = "Hello, " + name + "!"; return SUCCESS; 
    } 
}
Share article:

2 Replies to "Java Package-Info Annotation Generation with Gradle"

  • Jared Burrows
    December 21, 2017 (18:27)
    Reply

    Thanks for the great article. I had update the code to handle the following cases:
    – Handle Android and Java projects
    – Inline imports to make it easier for people to use
    – Add “doLast” to “generatePackageInfo” task so the code does not execute each time

    https://gist.github.com/jaredsburrows/0182ce5c7c35a3ab5514770f4a8fbe9a

    • Cristian S.
      December 31, 2017 (10:50)
      Reply

      Hi Jared! I’m glad you liked the post.
      Your version with `android.support.annotation.NonNull` looks great! A nice adaptation for null-conscious Android developers 😉

      All the best!


What do you think? Share your thoughts!

26 + = 31