Making a plugin dependency version configurable

I’m working on a Gradle plugin whose goal is to grab a particular zip file from maven central, unzip it, and set an environment variable pointing to the unzipped location for tests.

Currently the version of the zip file is hardcoded in my plugin, but I would ideally like to allow users to override the version as desired. However, I’m not sure if this is possible since plugin dependencies may be resolved before extensions are taken into account. Any help or advice would be appreciated.

class GeodeIntegration implements Plugin<Project> {

    @Override
    void apply(Project project) {
        project.repositories {
            mavenCentral()
            maven { url 'https://repository.apache.org/content/repositories/snapshots' }
            maven { url 'https://repository.apache.org/content/repositories/releases' }
        }

        project.configurations {
            geodeIntegration
        }

        project.dependencies {
            geodeIntegration 'org.apache.geode:apache-geode:1.3.0-SNAPSHOT'
        }

        File geodeDistZip = project.configurations.geodeIntegration.find {
            it.path.contains('org.apache.geode/apache-geode')
        } as File

        File outputDir = new File("${project.buildDir}/unpacked/dist")

        project.task('installGeode', type: Copy) {
            from project.zipTree(geodeDistZip)
            into outputDir
        }

        project.tasks.withType(Test) {
            environment("GEODE_HOME", "${project.buildDir}/unpacked/dist/${geodeDistZip.name.replace(".zip", "")}");
            dependsOn('installGeode')
        }
    }
}

I would like to give the user this sort of flexibility, which would override the 1.3.0-SNAPSHOT version above:

geodeIntegration {
    version = '1.2.0'
}

That’s doable. You can use defaultDependencies {} to specify dependencies that should be used for a given configuration if none are provided. This would let someone use a completely different set of coordinates, if necessary.

A few comments about your plugin so far:

  • It’s frowned upon to define repositories in your plugin unless you absolutely need it. Including repositories makes it more difficult for others to use your plugin because they may have their own mirrors they would prefer to use.
  • You should avoid resolving configurations at configuration time.
    For instance, configurations.geodeIntegration.find {} requires that Gradle resolve and download the geodeIntegration files before it can do anything else.
  • If you can avoid it, don’t pass absolute paths as environment variables or system properties to the Test task as that reduces build cacheability because Gradle will see absolute paths and not allow builds with different absolute paths to share artifacts.
  • If tests will rely on the contents of the outputDir, those files should be an input to the Test tasks too.

Here’s a stab at making the plugin more idiomatic. You can plop this into a build.gradle and you should be able to run gradle test and see it populate build/install/apache-geode.

class GeodeExtension {
    String version = "1.3.0-SNAPSHOT"
}

class GeodeIntegration implements Plugin<Project> {
    @Override
    void apply(Project project) {
        def extension = project.extensions.create("geodeIntegration", GeodeExtension)
        project.configurations {
            geodeIntegration {
                defaultDependencies { dependencies ->
                    dependencies.add(project.dependencies.create("org.apache.geode:apache-geode:${extension.version}@zip"))
                }
            }
        }

        def installGeode = project.task('installGeode', type: Sync) {
            dependsOn project.configurations.geodeIntegration
            from {
                project.zipTree(project.configurations.geodeIntegration.singleFile)
            }
            // Strip version number from base zip name
            eachFile { fcd ->
                fcd.path = (fcd.path - ~/apache-geode-[0-9.]+(-SNAPSHOT)?/)
            }
            into "${project.buildDir}/install/apache-geode/"
        }

        project.tasks.withType(Test) {
            environment GEODE_HOME: installGeode.destinationDir
            dependsOn installGeode
        }
    }
}

// Just for the example to work:
repositories {
    mavenCentral()
    maven { url 'https://repository.apache.org/content/repositories/snapshots' }
    maven { url 'https://repository.apache.org/content/repositories/releases' }
}

apply plugin: GeodeIntegration
apply plugin: 'java'

Hope this helps. Be sure to check out the testing guide for how to test your plugin with TestKit.

3 Likes

Thanks so much Sterling. Your answer goes above and beyond, it was super helpful. Out of curiosity, would you mind explaining what effect the “@zip” suffix has on the dependency? I wasn’t able to find a docs page to explain it. Thanks again!

I can’t link to the subsection, but see “External dependencies” here:
https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.dsl.DependencyHandler.html

Using @zip tells Gradle to look for an artifact with that extension vs the default .jar. There’s also a short section in the user guide about this too (but it uses the “long” notation).