Search code examples
gradlegroovyliquibase

Liquibase configuration in Gradle conventions plugin


I have a multi module Java / Gradle project where each module has identical configuration, so I would like to move that to a convention plugin as a pre-compiled script plugin. The problem is that all of the modules use the liquibase-gradle-plugin, which seems non-trivial to implement.

First, the liquibase plugin implements its own dependency scope liquibaseRuntime, but my plugin does not know that scope. Here's my dependencies block (using a version catalog):

dependencies {
    implementation libs.plugins.jooq.liquibase
    implementation libs.plugins.gradle.liquibase

    liquibaseRuntime libs.postgresql
    liquibaseRuntime libs.liquibase
    liquibaseRuntime libs.jooq
    liquibaseRuntime libs.picocli
}

but that generates the error

Caused by: org.gradle.internal.metaobject.AbstractDynamicObject$CustomMessageMissingMethodException: Could not find method liquibaseRuntime() for arguments [map(valueof(DependencyValueSource))] on object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.
    at org.gradle.internal.metaobject.AbstractDynamicObject$CustomMissingMethodExecutionFailed.<init>(AbstractDynamicObject.java:190)
    at org.gradle.internal.metaobject.AbstractDynamicObject.methodMissingException(AbstractDynamicObject.java:184)
    at org.gradle.internal.metaobject.ConfigureDelegate.invokeMethod(ConfigureDelegate.java:80)

If I change liquibaseRuntime to implementation the error goes away, but I don't want to export these dependencies, they're just for the liquibase plugin.

Second, the actual plugin configuration requires a configuration closure like this:

liquibase{
    activities {
        main {
            changeLogFile "src/main/liquibase/changelog.xml"
            // more config
        }
    }
}

If I write this in my plugin, I get the error

Caused by: java.lang.RuntimeException: 
org.gradle.internal.metaobject.AbstractDynamicObject$CustomMessageMissingMethodException:
Could not find method liquibase() for arguments [precompiled_EsrJooqLiquibase$_run_closure3@664b65b8] on project ':moduleA' of type org.gradle.api.Project.

How can I programmatically create such a configuration closure from the outside?

Update

The accepted answer works nicely, with one caveat: to get the liquibaseRuntime scope to work, I had to register it myself, the way the gradle liquibase plugin does:

project.configure(project) {
    configurations.maybeCreate("liquibaseRuntime")
}
dependencies {
    liquibaseRuntime(libs.postgresql) // etc
}

Solution

  • In order to use external plugins in a pre-compiled script plugin, you need to add the JARs containing those plugins to the classpath of the plugin in the plugin's build script, much like for normal code (documentation).

    If you are writing the plugins in buildSrc, this would be in the file buildSrc/build.gradle.

    The Maven coordinates1 of the plugin JAR are used for this purpose, as for a regular library. These should be added to the implementation configuration:

    // buildSrc/build.gradle
    
    repositories {
        gradlePluginPortal()
    }
    
    dependencies {
        implementation 'org.liquibase:liquibase-gradle-plugin:2.2.1'
    }
    

    We have also registered the Gradle Plugin Portal as a repository so Gradle knows where to find the JAR, since this plugin is available there.

    Now you can apply the plugin in your pre-compiled script plugin and you should have access to all the usual configuration options:

    // Precompiled script plugin code
    
    plugins {
        id 'org.liquibase.gradle'
    }
    

    The plugin version is not necessary here since the plugin code is already on the classpath by virtue of the previous setup.


    1 The Maven coordinates (ie, the groupId:artifactId:version) are different to the plugin id (the latter being the identifier used to apply a plugin in Gradle). The Maven coordinates of a plugin published at the Gradle Plugin Portal are declared on the plugin's page (eg for Liquibase) as the coordinates used for 'legacy plugin application'.