How to pass parameters to a plugin during apply?

Hi folks,

I am writing a Gradle plugin, let’s call it myPlugin, which, amongst other things, declares its own dependency. The Plugin will be used in a build.gradle with apply plugin: 'myPlugin'. It also adds a task, which will be invoked later.

Question is: I don’t want to hard code the version for the dependency in the plugin, but rather pass it from the build.gradle. I tried to use a PluginExtension, but it seems that it is too late, because I need the version already before/during apply.

Here are the code snippets:

The plugin:

class MyPlugin implements Plugin<Project> {

  void apply(Project project) {
    project.dependencies {
      compile 'some.dependency:something-useful:1.0.0:classes'
    }
    project.task("myTask") {
        ...
    }
    ...
}

The gradle.build:

apply plugin: 'myPlugin'

...

jar.dependsOn myTask

Any ideas? Let me know if I should provide more details.

Best regards,
Stefan

Dependencies in Gradle are managed through dependency configurations. Start here.

Your plugin should create a custom dependency configuration during the Gradle Configuration phase, the configuration is not resolved and you can modify it and specify your dependency. For your use-case, it looks like you may benefit from default dependencies.

During the execution phase, you would resolve the configuration to a list of JARs and either use a custom classloader, or the javaExec() API/task to run your tool.

Sorry, I am pretty new to gradle and I think I did not get your idea.

Assuming I have my own configuration, like this:

class MyPlugin implements Plugin<Project> {

      void apply(Project project) {
        project.configurations {
            myConf
        }
        
        project.dependencies {
          myConf 'some.dependency:something-useful:1.0.0:classes'
        }
        project.task("myTask") {
            ...
        }
        ...
    }

How exactly does this help me? How can I modify my dependency from my build.gradle?

I got the idea of default dependencies, but I would not like to force my build.gradle to declare the dependency itself, but rather only pass the version.

If you could provide some code snippets or point me to some example, that would be highly appreciated.

Cheers,
Stefan

See getting input from the build

class MyPluginExtension {
   String toolVersion = '1.2' // default value 
}

void apply(Project project) {
   def myExt = project.extensions.create('myPlugin', MyPluginExtension) 
   project.configurations {
      myConf 
   } 
   project.dependencies {
      myConf "group:artifact:$myExt.toolVersion"
   }

Configurations are evaluated on demand so the dependency closure will be executed after the buildscript has had a chance to overridde the default toolVersion

Thanks, I got your idea!

But since the extension is added when the plugin is applied and this happens after the buildscript, how can the buildscript override the property?

this happens after the buildscript

Incorrect, the buildscript applies the plugin

how can the buildscript override the property

Same as many other plugins, apply the plugin before configuring the extension

apply plugin: 'myplugin' 

myPlugin {
   toolVersion = '1.5'
}

That does not work for me. Here is what i did:

Plugin

class MyPlugin implements Plugin<Project> {

  void apply(Project project) {
    def myExt = project.extensions.create("myPlugin", MyPluginExtension)

    project.configurations {
      myConf
    }

    project.dependencies {
      myConf "some.dependency:something-useful:$myExt.version@war"
    }

    project.task("doSomething") {
      ...
    }
  }
}

class MyPluginExtension {
  String version = "23.5.6"
}

build.gradle

buildscript {
  repositories {
    mavenLocal()
    mavenCentral()
  }

  dependencies {
    classpath // dependency to my plugin jar
  }
}

apply plugin: 'my-plugin'

myPlugin {
  version = '1.0.0'
}

...

jar.dependsOn doSomething

When I build, it will apply the default of my extension class. When I move apply plugin and the override of the version to buildscript section ist won’t find the plugin anymore.

Please use toolVersion rather than version as this is a convention followed by many other plugins (eg jaCoCo)

I’m guessing you are resolving the configuration in the configuration phase. You should only resolve the configuration in the execution phase

Eg:

project.task("doSomething") {
   // don't resolve configurations.myConf here

   doLast {
      // do it here, eg:
      printn "myConf=$project.configurations.myConf.files" 
   } 
}

Ok, enough of those simplified examples, here is my actual stuff:

Plugin:

p ackage org.camunda.bpm.spring.boot.gradle;

import org.gradle.api.Plugin
import org.gradle.api.Project

class CamundaWebjarPlugin implements Plugin<Project> {

  void apply(Project project) {
    def myExt = project.extensions.create("camundaWebjarPlugin", CamundaWebjarPluginExtension)

    project.configurations {
      camundaEE
    }

    project.dependencies {
      camundaEE "org.camunda.bpm.webapp:camunda-webapp-ee-plugins:$myExt.camundaVersion@war"
      compile "org.camunda.bpm.webapp:camunda-webapp-ee-plugins:$myExt.camundaVersion:classes"
    }

    project.task("resolveCamundaEnterpriseWebjar") {
      dependsOn: project.configurations.camundaEE

      doLast {
        project.copy {
          from project.configurations.camundaEE.collect { project.zipTree(it) }
          into "$project.buildDir/resources/main"
        }

        project.delete "$project.buildDir/resources/main/META-INF"

        project.copy {
          from "$project.buildDir/resources/main/WEB-INF/securityFilterRules.json"
          into "$project.buildDir/resources/main"
        }

        project.delete "$project.buildDir/resources/main/WEB-INF"
      }
    }
  }
}

class CamundaWebjarPluginExtension {
  String camundaVersion = "23.5.6"
}

build.gradle:

buildscript {
  repositories {
    mavenLocal()
    mavenCentral()
  }

  dependencies {
    classpath 'org.camunda.bpm.extension.springboot.gradle:camunda-bpm-spring-boot-webjar-gradle-plugin:2.1.0-SNAPSHOT'
  }
}


plugins {
  id "java"
  id "org.springframework.boot" version "1.5.2.RELEASE"
  id "io.spring.dependency-management" version "1.0.1.RELEASE"
}

apply plugin: 'camunda-webjar-plugin'

camundaWebjarPlugin {
  camundaVersion = '7.6.3-ee'
}

repositories {
    mavenLocal()
    mavenCentral()
    maven {
        credentials {
            username camundaRepoUser
            password camundaRepoPassword
        }
        url 'https://app.camunda.com/nexus/content/repositories/camunda-bpm-ee'
    }
}

dependencyManagement {
    imports {
        mavenBom 'org.camunda.bpm.extension.springboot:camunda-bpm-spring-boot-starter-bom:2.0.0'
    }
    imports {
        mavenBom 'org.springframework.boot:spring-boot-dependencies:1.4.4.RELEASE'
    }
    imports {
        mavenBom 'org.camunda.bpm:camunda-bom:7.6.3-ee'
    }
}

dependencies {
    compile 'org.camunda.bpm.extension.springboot:camunda-bpm-spring-boot-starter-webapp-core'
    compile 'com.h2database:h2'

    testCompile 'junit:junit:4.11'
}

jar.dependsOn resolveCamundaEnterpriseWebjar

I tried what you proposed, but without any effect…

Remove the following line, it might be forcing the configuration to be resolved early

dependsOn: project.configurations.camundaEE

Please just try my println version to simplify things. Eg

project.task("resolveCamundaEnterpriseWebjar") {
      doLast {
         println "camundaEE=$project.configurations.camundaEE.files"
      } 
} 

Also I have no idea how dependencyManagement works. Probably best to remove that too as perhaps that’s causing the configuration to be resolved at configuration time… Not sure

Thanks for your help, I tried both, removing the dependsOn and the DependencyManagement, but still:

FAILURE: Build failed with an exception.

* What went wrong:
Could not resolve all dependencies for configuration ':compileClasspath'.
> Could not find org.camunda.bpm.webapp:camunda-webapp-ee-plugins:23.5.6.
  Required by:
      project :

Which is correct, because 23.5.6 does not exist, I took that as default to notice at once when it is applied.

Hmm… You’re right… Do this instead (tested, working)

apply plugin: MyPlugin

repositories { ... }

myPlugin {
	toolVersion = '1'
}

class MyExt {
	String toolVersion = "xxx";
}

class MyPlugin implements Plugin<Project> {
	void apply(Project project) {
		project.with {
			MyExt myExt = extensions.create('myPlugin', MyExt)
			configurations {
				myConfig
			}
			tasks.create("myTask") {
				doLast {
					configurations.myConfig.dependencies.add(dependencies.create("javax.inject:javax.inject:$myExt.toolVersion"))
					println "myConfig=$configurations.myConfig.files"
				}
			}
		}
	}
}
1 Like

Thank you, that was the right hint. I changed it that way and now it is working!

Maybe, one more challenge:

That only works for the camundEE dependency. I cannot add compile dependencies that way:

Cannot change dependencies of configuration ':compile' after it has been included in dependency resolution.

I can work around and move this dependency to my build.gradle, but if you had a solution also for that…?

Perhaps

configurations.compile.incoming.beforeResolve {
   dependencies.add project.dependencies.create("...")
} 

Nope. Dies not complain anymore, but also does not add/resolve the dependency.

Using default deps:

apply plugin: MyPlugin

repositories { jcenter() }

myPlugin {
    // Can override version of default dependency.
    toolVersion = '4.12'
}

// Alternatively, the option is open to the user to manually
// configure the dependencies of the tool and to ignore what
// you've specified.
/*
dependencies {
    // Since there are dependencies declared, the defaults are not applied.
    myConfig 'javax.inject:javax.inject:1'
}
*/

class MyExt {
    String toolVersion = "3.8";
}

class MyPlugin implements Plugin<Project> {
    void apply(Project project) {
        project.with {
            MyExt myExt = extensions.create('myPlugin', MyExt)
            configurations {
                myConfig
            }
            configurations.myConfig.defaultDependencies { deps ->
                deps.add(project.dependencies.create("junit:junit:$myExt.toolVersion"))
            }
            tasks.create("myTask") {
                doLast {
                    println "myConfig=$configurations.myConfig.files"
                }
            }
        }
    }
}

The secret here is that the closure passed to defaultDependencies is not executed until the configuration participates in dependency resolution, if there are no defined dependencies.

1 Like

This works

class MyPlugin implements Plugin<Project> {
	void apply(Project project) {
		project.with {
			apply plugin: 'java'

			MyExt myExt = extensions.create('myPlugin', MyExt)
			configurations {
				myConfig
			}

			configurations.compile.incoming.beforeResolve { ResolvableDependencies deps ->
				configurations.compile.dependencies.add dependencies.create("javax.inject:javax.inject:$myExt.toolVersion")
			}

			configurations.myConfig.incoming.beforeResolve { ResolvableDependencies deps ->
				configurations.myConfig.dependencies.add dependencies.create("javax.inject:javax.inject:$myExt.toolVersion")
			}

			tasks.create("myTask") {
				doLast {
					println "compile=${configurations.compile.files*.name}"
					println "myConfig=${configurations.myConfig.files*.name}"
				}
			}
		}
	}
}

Prints

compile=[javax.inject-1.jar]
myConfig=[javax.inject-1.jar]

Use Project.afterEvaluate callback. See here:

https://docs.gradle.org/current/userguide/build_lifecycle.html#sec:project_evaluation

1 Like

This answer is all what I need, saved me, thanks a lot!!!