How to invoke methods dynamically on plugin extensions?

I’ve tried to use groovys dynamic method invocation with invokeMethod(Sting name, args) on a plugin extension. The groovy test works fine. But when doing with gradle it always fails with:

Could not find method testNg() for arguments ['org.testng', 'testng', '6.1'] on org.plugins.ModuleKitExtension_Decorated@5e8
56a7a.

Please provide more information (source code, output of ‘gradle -v’).

What language are you using? If it’s Groovy, you can just call the dynamic method directly.

Here is the plugin:

class ModuleKitPlugin implements Plugin<Project> {
   @Override
 public void apply(Project project) {
  project.extensions.create("moduleKit", ModuleKitExtension)
 }
   public static void main(String[] args) {
  def kit = new ModuleKitExtension()
  assert kit.testMethod()=="INVOKED: testMethod"
 }
}
  class ModuleKitExtension {
    def invokeMethod(String name, args) {
  "INVOKED: $name"
 }
}

the Gradle script:

apply plugin: 'module-kit'
println moduleKit.testNg() //expected output "INVOKED: testNg"

‘public static void main()’ in a plugin?

this main function is just a short cut in order to proof the code works. In Eclipse I can stressless run this main function by mouse clicking,

I should maybe explain a bit more what I plan to do. Perhaps there is a much simpler solution. With Maven I made quite good experience with the dependencyManagement feature. Which is for defining an external dependencies version in a central location. So all sub-modules use just the dependency without declaring its version.

With Gradle I currently do it by declaring a global map like this:

ext.modules = [
  guiLib: module('org.myorg', 'gui-lib', '1.0'),
  guiLibSources: module(''org.myorg', 'gui-lib', '1.0', 'sources'),
].asImmutable()
def module(String groupId, String artifactId, String version){
  [group: groupId, name: artifactId, version: version].asImmutable()
}
def module(String groupId, String artifactId, String version, String classifier){
 [group:groupId, name:artifactId, version:version, classifier:classifier].asImmutable()
}

But then I need to declare always all combinations, with/without classifier, with/without type. And we need it lots of times e.g. for GWT. So my plan is to create a plugin which takes a property file with module declarations like this:

guiLib: org.myorg:gui-lib:1.0

The classifier and type are specified when using it like this:

dependencies{
  compile(
    moduleKit.guiLib, //property provides the [group: 'org.myorg', name: 'guiLib', version: '1.0']
    moduleKit.guiLib('sources') //method provides [group: 'org.myorg', name: 'guiLib', version: '1.0', classifier: 'sources']
  }
}

This is what I need the invokeMethod and getProperty feature for.

What do you hope to gain from externalizing this information? How is it better than ‘ext.modules = [guiLib: “org.myorg:gui-lib:sources:1.0”]’, which works out of the box? Even if you chose to externalize this, what would you need ‘invokeMethod’ and ‘getProperty’ for?

I’ve debugged Gradle and found that the CompositeDynamicObject queries my decorated extension with hasMethod and hasProperty methods in order to determine whether it can invoke the requested method. Do I need to inherit from some specific base class in order to answer this query as I want?

I’ve separated the modules declarations into a modules.gradle because its a noticeable amount of data which in my opinion naturally qualifies for separation. One nice Gradle feature - among lots of others - is that I can structure even build files instead of heaving one quite huge xml file.

The other reason is when defining dependency versions it would be good to declare only group, name and version but when using it in subprojects I can be more specific about classifier and type. Because the sources and zip secondary artifacts I always need in exactly the same version as the main artifact. What I save is not to declare all secondary artifact variants that I need e.g. guiLib, guiLibSources, guiLibResources etc. Once I’ve declared just guiLib I can use guiLib(‘sources’), guiLib(‘resources’) or just guiLib

Another reason is I could upload this info and use it in dependent projects to share infrastructure interface module versions.