flooose
flooose

Reputation: 529

Apply gradle plugin from source directory

What's the correct way of applying a gradle plugin that's laying in src/main/groovy?

Assuming this (as taken from here) is the contents of src/main/groovy/GreetingPlugin.groovy

class GreetingPlugin implements Plugin<Project> {
    void apply(Project project) {
        // Add the 'greeting' extension object
        project.extensions.create("greeting", GreetingPluginExtension)
        // Add a task that uses the configuration
        project.task('hello') {
            doLast {
                println project.greeting.message
            }
        }
    }
}

What do I need to do in my build.gradle to make the call gradle -q hello work in the terminal? An internet search gave me this, but it didn't help me solve the problem.

Upvotes: 0

Views: 3191

Answers (3)

jerry lawson
jerry lawson

Reputation: 1

I have been able to solve the chicken-and-egg problem for my project. I'm posting the solution here for the OP and anyone else that is looking for a solution.

My gradle plugin is a standalone git project that is meant to be used by our various applications and libraries, to provide a consistent build environment. Basically, this plugin wraps several common plugins (java, groovy, jib, helm-plugin, npm plugin, axion-release, etc.) and provides a common convention over configuration usage model. Applications or libraries wanting to fit into our ecosystem simply apply this plugin and define very minimal information (docker image name, group/module names, artifactory credentials, etc.) and the plugin does all the work that would have previously been explicitly declared in the application's or library's own build.gradle. The plugin is actually 6 separate plugins that can be used independently, so long as the application/library root project first applies the 'core' plugin.

Here is the original directory layout of this plugin:

.
├── README.md
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── plugin
│   ├── build.gradle
│   └── src
│       ├── main
│       │   └── groovy
│       │       └── com
│       │           └── jerry
│       │               └── work
│       │                   └── gradle
│       │                       └── plugin
│       │                           ├── core
│       │                           ├── docker
│       │                           ├── helm
│       │                           ├── java
│       │                           ├── nodejs
│       │                           └── release
│       └── test
│           └── groovy
│               └── com
│                   └── jerry
│                       └── work
│                           └── gradle
│                               └── plugin
├── settings.gradle

The plugin was working fine, but to keep things orderly, the root project's build.gradle first applied an older version of the plugin, so that the plugin would use itself (albeit a previous version) to build a new version. This had its drawbacks, of course, when wanting to add new features, since we'd necessarily have to "pre-build" a dummy version (published to local ~/.m2) and then do a real build.

To solve this problem and allow the plugin to build itself, using itself, without a two-pass build, the following steps were done:

  1. Split out the dependencies block and gradlePlugin block into a separate include-able file called common.gradle.
  2. Create buildSrc/build.gradle to pre-build the plugin, as per normal https://docs.gradle.org/current/userguide/organizing_gradle_projects.html#sec:build_sources, but pointing the sourceSets to the plugin/src folder
  3. Change the root project's build.gradle to simply apply the core plugin by id.

Here's a sanitized version of the root project build.gradle:

./build.gradle:

// Here's where we apply the plugin to itself
apply plugin: 'com.jerry.work.core'

Here's a sanitized version of common.gradle:

// This gradle file is used by both plugins/build.gradle and buildSrc/build.gradle, so there is one place
// where we define dependencies and plugin definitions.

dependencies {
    implementation (group: 'io.spring.gradle', name: 'dependency-management-plugin', version: dependency_management_plugin_version)
    implementation (group: 'org.sonarsource.scanner.gradle', name: 'sonarqube-gradle-plugin', version: sonar_qube_plugin_version)
    implementation (group: 'org.unbroken-dome.gradle-plugins.helm', name: 'gradle-helm-plugin', version: helm_plugin_version)
    implementation (group: 'pl.allegro.tech.build', name: 'axion-release-plugin', version: axion_release_plugin_version)
    implementation (group: 'com.github.jk1', name: 'gradle-license-report', version: gradle_license_plugin_version)
    implementation (group: 'org.ajoberstar.grgit', name: 'grgit-core', version: grgit_plugin_version) {
        exclude group: 'org.codehaus.groovy', module: 'groovy'
    }
    implementation (group: 'org.ajoberstar.grgit', name: 'grgit-gradle', version: grgit_plugin_version) {
        exclude group: 'org.codehaus.groovy', module: 'groovy'
    }
    implementation (group: 'gradle.plugin.com.google.cloud.tools', name: 'jib-gradle-plugin', version: jib_plugin_version)
    implementation (group: 'com.github.node-gradle', name: 'gradle-node-plugin', version: node_plugin_version)
    implementation (group: 'org.jacoco', name: 'org.jacoco.agent', version: jacoco_version)
    implementation (group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib', version: kotlin_stdlib_version)
    implementation (group: 'org.jfrog.buildinfo', name: 'build-info-extractor-gradle', version: jfrog_version)
    testImplementation (group: 'org.jacoco', name: 'org.jacoco.agent', version: jacoco_version)
    testImplementation (group: 'org.ajoberstar', name: 'grgit', version: grgit_lib_version) {
        exclude group: 'org.eclipse.jgit', module: 'org.eclipse.jgit.ui'
        exclude group: 'org.eclipse.jgit', module: 'org.eclipse.jgit'
    }
    // Use the awesome Spock testing and specification framework
    testImplementation ('org.spockframework:spock-core:1.3-groovy-2.5')
}

gradlePlugin {
    // Define the plugin
    plugins {
        core {
            id = 'com.jerry.work.core'
            implementationClass = 'com.jerry.work.gradle.plugin.core.GradleCorePlugin'
        }
        release {
            id = 'com.jerry.work.release'
            implementationClass = 'com.jerry.work.gradle.plugin.release.GradleReleasePlugin'
        }
        helm {
            id = 'com.jerry.work.helm'
            implementationClass = 'com.jerry.work.gradle.plugin.helm.GradleHelmPlugin'
        }
        javaapp {
            id = 'com.jerry.work.javaapp'
            implementationClass = 'com.jerry.work.gradle.plugin.java.GradleJavaAppPlugin'
        }
        javajar {
            id = 'com.jerry.work.javajar'
            implementationClass = 'com.jerry.work.gradle.plugin.java.GradleJavaLibPlugin'
        }
        gradlejar {
            id = 'com.jerry.work.gradlejar'
            implementationClass = 'com.jerry.work.gradle.plugin.java.GradleGradlePlugin'
        }
        nodejs {
            id = 'com.jerry.work.nodejs'
            implementationClass = 'com.jerry.work.gradle.plugin.nodejs.GradleNodejsPlugin'
        }
    }
}

if (project.hasProperty('validatePlugins_version) {
    // NpmInstallTask has warnings that we can't get rid of, so disable failing the build during validation phase.
    validatePlugins {
        failOnWarning = false
    }
}

Here's the plugin/build.gradle:

apply plugin: 'com.jerry.work.gradlejar'

my_custom_dsl {
    jar {
        groupId = 'com.jerry.work'
        name = 'jerry-gradle-plugin'
    }
}

apply from: '../common.gradle'

// Add a source set for the functional test suite
sourceSets {
    functionalTest {
    }
}

gradlePlugin.testSourceSets(sourceSets.functionalTest)
configurations.functionalTestImplementation.extendsFrom(configurations.testImplementation)

// Add a task to run the functional tests
task functionalTest(type: Test) {
    testClassesDirs = sourceSets.functionalTest.output.classesDirs
    classpath = sourceSets.functionalTest.runtimeClasspath
}

check {
    // Run the functional tests as part of `check`
    dependsOn(tasks.functionalTest)
}

Here's the buildSrc/build.gradle:

// Standard artifactory repo resolution, to download whatever dependencies we need at build time.
repositories {
    mavenLocal()
    maven {
        url "https://${System.env.ARTIFACTORY_DNS ?: artifactory_dns}"
        credentials {
            username = System.env.ARTIFACTORY_USER ?: artifactory_user
            password = System.env.ARTIFACTORY_API_KEY ?: artifactory_api_key
        }
    }
}

// We need groovy and gradle plugin support
apply plugin: 'groovy'
apply plugin: 'java-gradle-plugin'

dependencies {
    compile gradleApi()
    compile localGroovy()
}

// load root project's gradle.properties, since gradle won't do it automatically.
Properties properties = new Properties()
FileInputStream input = new FileInputStream(project.file('../gradle.properties'))
properties.load(input)
input.close()

for (String key : properties.stringPropertyNames()) {
    this.project.ext.set(key, properties.getProperty(key))
}

// Let groovy compiler know where to find the source files.
sourceSets {
    main {
        groovy {
            srcDirs = ['../plugin/src/main/groovy']
        }
    }
}

apply from: '../common.gradle'

Here's the gradle.properties:

release.disableRemoteCheck=true
dependency_management_plugin_version=1.0.8.RELEASE
sonar_qube_plugin_version=2.8
helm_plugin_version=0.4.4
axion_release_plugin_version=1.10.2
grgit_plugin_version=4.0.0
grgit_lib_version=1.7.2
jib_plugin_version=1.8.0
jacoco_version=0.8.2
gradle_license_plugin_version=1.12
node_plugin_version=2.1.1
kotlin_stdlib_version=1.3.11
jfrog_version=4.11.0

And lastly, the settings.gradle:

rootProject.name = 'jerry-gradle-plugin'
include ':plugin'

Now, to build and publish the gradle plugin, it's simply a matter of using

./gradlew publish

(the publish task is explicitly 'finalizedBy' the build task in the plugin code)

Also note that we're using the axion-release-plugin, which manages the plugin versioning via git tags.

I haven't disclosed exactly all the standard and 3rd-party plugins that this plugin automatically applies, but the reader can discern what we use by looking at the dependencies declaration in the common.gradle file listed above.

Upvotes: 0

Alexiy
Alexiy

Reputation: 2030

You can put your plugins into 'buildSrc' directory at the top level (you might need to create it). This directory is almost like other gradle projects. Create the usual 'src/main/groovy' or 'src/main/java' inside it and put your plugin source into there. Then, you can apply it in any project: apply plugin: GreetingPlugin.

Upvotes: 0

Vampire
Vampire

Reputation: 38639

You cannot solve the hen-and-egg problem. This means you cannot add a plugin that you define in your production sources in your build script, as you need your build script to build the plugin, but the build script needs the plugin. See what I mean?

If this project is about building that plugin, and you also want to use that plugin in its own build, you can always only use a formerly released / built version to build the next version.

If your build is not about building that plugin, but about building something else and you want to use that plugin for building the project, you have the sources in the wrong place. If you want to use this plugin in multiple builds, make it an own project that you build and release and then use in your projects. If it is only relevant for this project, either just define the plugin in your build script, then apply it, or stuff it into the buildSrc project.

The buildSrc project is in a subfolder called buildSrc or your root project and is an own full multi-project Gradle build where you can have plugins, tasks, and so on, that you want to use to build your main project. The buildSrc build is automatically built before the main build is started and added to its classpath.

Upvotes: 1

Related Questions