1

I have a project with two subprojects.

One of these subprojects, "A", contains code that is being published to an artifact.

The other subproject, "B", has a task that needs to do exactly what one of the methods in A's code does. I can replicate the logic in groovy, but is there any way I can actually have my task in subproject B call the code that was compiled as part of subproject A?

I'd tried adding a buildscript block in B that added the artifact from A to the classpath:

buildscript {
    dependencies {
        classpath project(':subproject-a')
    }
}

...but this gave me an error:

Cannot use project dependencies in a script classpath definition.

I don't believe I can move subproject-a to buildSrc, as I'm also publishing its artifact to a maven repository for other projects to use.

Laurence Gonsalves
  • 137,896
  • 35
  • 246
  • 299

1 Answers1

1

You have a chicken or egg problem where all of the Gradle project classloaders are resolved before any classes are compiled. This can be resolved using a custom configuration and a Classloader

Eg:

configurations {
   custom 
} 
dependencies {
   custom project(':subproject-a')
} 
task customTask {
   doLast {
      def urls = configurations.custom.files.collect { it.toURI().toURL() } 
      ClassLoader cl = new java.net.URLClassLoader(urls as URL[]) 
      Class myClass = cl.loadClass('com.foo.MyClass')

      // assuming zero args constructor 
      Object myObject = myClass.newInstance()

      // assuming method which accepts single String argument 
      java.lang.reflect.Method myMethod = myClass.getMethod('myMethodName', String.class)  
      myMethod.invoke(myObject, 'methodArg')
   } 
} 
lance-java
  • 25,497
  • 4
  • 59
  • 101
  • Thank you! I needed to make a few tweaks to get this working: 1. I needed to pass `this.class.classLoader` as parent when constructing the ClassLoader, or a ServiceLoader would fail with a "not a subtype" error. 2. I needed to add a `customTask.dependsOn(':subproject-a:assemble') to ensure that subproject-a's jar would actually be built in time. 3. I needed to add some of subproject-a's dependencies to the buildscript dependencies. – Laurence Gonsalves Jun 11 '19 at 22:05
  • 1
    I still don't really understand #3, and that workaround feels like a huge kludge. I tried adding these dependencies as "custom" dependencies in the subproject instead (which still seems like a kludge, but less bad), but it didn't help. I get errors when executing "myMethod" about it being unable to load resources or unable to find classes that are in its dependencies. – Laurence Gonsalves Jun 11 '19 at 22:06
  • 1
    I've updated my answer to invoke the method using java reflection rather than groovy dynamic behavior. The previous solution may have caused the method to be invoked via the project classloader causing the issues you are seeing. Please remove your hacks and try the new solution – lance-java Jun 12 '19 at 08:04
  • Thanks for that update! It looks like the exception was actually coming from inside the constructor. I tried changing this to use reflection's Constructor object, but this didn't have any effect. The exception was coming from inside testcontainers, so I did some poking around to see what could be the cause. It turns out they use the thread's "context class loader". Setting that in the task (and resetting it afterwards) seemed to get past that issue. I'm now getting some other exception where the stack trace is so long that gradle truncates it, so I haven't yet figured out the cause. – Laurence Gonsalves Jun 13 '19 at 20:13
  • Looks like that last issue was due to some missing "custom" dependencies. It now works! – Laurence Gonsalves Jun 13 '19 at 23:14
  • Suppose I tried this and got `java.lang.NoClassDefFoundError: org/gradle/api/internal/DynamicObjectAware` at `loadClass`... something I should be adding to the gradle config to support the class loader? – Milosz Oct 01 '20 at 03:39