Feature variants for java library

Hi,

i’m the maintainer of a Java library (a plain Java Unrar library), which has a second project that adds capability on top of it (a RAR provider for Apache Commons VFS), currently in another repo entirely.

It seems to me that it would be a good candidate for the feature variants, but I can’t get it to work.

From what i understand, there are 2 ways to add feature variants:

  1. in the same source set
  2. in a different source set

For 1. i don’t quite understand how this will change anything to the resulting jar, since the code is in the same sourceset, it will be bundled in the same jar, no ?

For 2., which seems a better solution to me, i have a problem because the code of the feature variant depends on the code of the main source set, see here.

When i try to add a new sourceSet with the code of the VFS provider, the build fails because the classes of the main project are not found.

Would someone be able to help me on how best to structure the project to have both the main feature and the VFS feature in the same project, with Gradle metadata correctly set?

Thanks

Hi! I’ve had the same problem, this seems like a very common scenario but it’s completely missing in the documentation. In my case, I have a library that can be used directly, but also offers support for Spring (e.g. Spring Boot auto-configuration) as an optional feature. The Spring support obviously depends on the main library and uses classes from it.

Sometimes the easiest solution would be to declare those extra dependencies “compileOnly” (i.e. optional), just forget about features, and go ahead and put all the code in the main source set. This is easier to configure in Gradle, but has the drawback that the client also has to declare those optional dependencies in order to use those extra classes. If there is more than one or two of those optional dependencies, it is not very feasible.

Option 1 you mentioned (put all features into “main” source set) solves this problem; the client can just declare with a single requireCapability that it is interested in the feature, so all those “optional” dependencies come in. A drawback is that it puts all the code into one JAR, so possibly wasting space if those extra features are not required. I guess another drawback would be that other clients of the library would still see the feature-specific classes that depend on external dependencies of the feature, but would get NoClassDefFoundErrors when they try to use it.

Option 2 (using different source sets) puts each feature code into its own source set. If the features also depend on code from “main”, one way I got this to work is to declare a dependency for the feature’s <feature>Api configuration on project(path), i.e. the project itself but without any required capabilities.

In my case the build.gradle.kts looked like this (Groovy should be very similar)

sourceSets.create("spring")

java {
    registerFeature("spring") {
        usingSourceSet(sourceSets["spring"])
    }
}

dependencies {
    // dependencies for the main/common part
    api( ... )
    implementation( ... )

    "springApi"(project(path))
    
    "springApi"("org.springframework:spring-context")

    // you can use this to inherit all the implementation stuff into springImplementation
    "springImplementation"(configurations["implementation"])
}

I thought that using project dependencies was not recommended for libraries. My understanding is that the dependency will not be versioned properly (like a GAV), and will prevent Gradle/Maven to replace the version if needed if another project is also using this dep.