[Gradle] How to make a flat source jar

As a C# and C++ developer, I sometimes like to have a different layout for source code and namespaces.
I recently wrote a Java library and was surprised to see that the convention is to name packages after the source folders, whether this makes sense or not.

This article explains why this is a problem and how to work around it with Gradle.

Why package-folder coupling is bad

Packages names and source folders are likely to differ since they target different audience:

  1. The library packages are public and should be organized for usability.
  2. The source files should be organized for readability and maintainability.

Imposing a perfect match between the two is yet another form of coupling.
Like other forms of coupling, it imposes a relationship between things that should be independent.
The consequence is an increased cost of maintenance.

Let's see a concrete example to see that in practice:

  1. You create a small library with a tenth of files in a single folder.
  2. The library grows to the point that you need to tidy up and group files into subfolders.
  3. The library continues to grow to the point where you need to reorganize the folders layout (either moving files or adding a whole new level).

If you follow the Java coding conventions, you need to change the packages names at least twice.
Since there are public classes in these packages, all your users must update all their import statements.

Fixing this can be easy for most users since there are now automated tools to do the job.
But, the user have to change his/her code, this is simply not acceptable to me.
The fact that there is a tool for this doesn't mean that it's a good thing.

Users of a library should not depend on the way the source code is organized. Period.

What happens if you don't respect the convention

Luckily the compiler doesn't pose a problem. It will complain if the file name doesn't match the class name, but that's OK.

Everything is fine until you try to provide a source-JAR aside with the compiled-JAR.

Indeed, Eclipse expects that the folder layout in the source-JAR to match the package names of the compiled-JAR.
That means, that if the source-JAR is not properly laid out, Eclipse will display an error every time your users click to show the source code.

Again, not acceptable to me.

Gradle

Gradle to the rescue

To work around this problem, I found that the simplest solution is to have only one package in my library.
It's good for the users: only one package to import.
It's good for the authors: don't have to think about the mapping.

Now, obviously it's not suitable for every project, but I believe it's appropriate for most small-to-medium-size libraries.

In this configuration, all the files in the source-JAR should be in the same folder which name matches the name of the package.

We can create this source-JAR with Gradle:

  1. We use a Copy task to copy all the files to a new directory.
  2. We use a Jar task to create the archive from this directory.

Here is an extract of the build.gradle for a package named fr.benoitblanchon.blog:

task flatSources(type: Copy) {  
    from sourceSets.main.allSource.files
    into "$buildDir/flatSrc/fr/benoitblanchon/blog"
}

task sourcesJar(type: Jar, dependsOn: flatSources) {  
    classifier = 'sources'
    from "$buildDir/flatSrc"
}

artifacts {  
    archives sourcesJar
}

As you can see, it's the sourceSets.main.allSource.files that flattens the directory.
There is no additional maintenance.