Class loaded through configuration is not found when task runs

Hi,

In one of my exsiting gradle plugin, I now need to be able configure some dependencies at runtime. for that purpose, I create a configuration :

    public void apply(Project project) {

         project.getConfigurations().create("myPluginDeps", c -> {
            c.setVisible(false);
            c.setCanBeConsumed(false);
            c.setCanBeResolved(true);
          });
  ...
}

I build the plugin, then use it and configure it in another project :

dependencies {
...
    myPluginDeps 'org.example:archunit-custom-rules:1.0-SNAPSHOT'
...
}

When I check the dependencies for that project, with gradlew :domain:dependencies, it shows the myPluginDeps config, with the jar and its transitive dependencies, as I would expect.

when my plugin task executes, at some point I need to be able to load a class from the classpath. The class may be in the dependency that I configured :

 public static Class<?> loadClassWithContextClassLoader(String className) {
        try {
            return Thread.currentThread().getContextClassLoader().loadClass(className);
        } catch (ClassNotFoundException e) {
            throw ReflectionException.wrap(e);
        }
    }

but this doesn’t work… I end up with a ClassNotFoundException for the class I try to load, when the class is in the extra dependency that I configure.

Is there something else to do that I am missing, to make sure that the task (which is a DefaultTask, not sure if this has an impact) triggered by the plugin has visibility on the plugin configuration, especially its classpath ?

Thanks !

Creating a configuration in the project does not impact the classloader used to execute the plugin/task code. You need to create a new classloader that includes the dependencies resolved from the configuration and then use that to load the class.

One possible solution is to use Gradle’s worker api to create a work queue that has classloader isolation. This will execute the work action with its classloader already configured with the additional entries. I created this example for you, hope it helps; GitHub - oesolutions/gradle-worker-api-example

Thanks a lot for taking the time to publish that example.

Unfortunately, I am not sure that will work in my case : the Gradle plugin is a wrapper over a “core” library, in which the actual class loading happens. There’s not much logic in the plugin itself, mostly some wrapping to bridge between the Gradle concepts and the core" concepts.
That allows me to also have a Maven plugin that just adds a thin adaptive layer over the core, and limit duplication between the Gradle and Maven plugins.

For more clarity / transparency, since it’s open-source :

So I am not sure I can go with your approach without impacting (and complexifying) the core library, creating abstractions to hide the Gradle specific plumbing, while it would bring no value to the Maven plugin, since there it works.

I actually don’t “need” a specific configuration : the plugin applies after the tests have run, so if it can inherit the “testImplementation classpath” or run the task within that existing classpatch, that would also solve the problem at hand (in the same way that it works for Maven actually) : users would declare their extra dependency as part of testImplementation instead of something specific to the plugin.

I’ve also tried to look at that option, but I am reaching the limits of my Gradle knowledge.

do you have any idea ?

Thanks

The general idea is that you would call ruleInvokerService.invokeRules(rules); inside a work action that has been submitted to a work queue that was created with the classloader isolation. When reflection is used the additional classes should be available. I think you might be able to pull this off without touching the common core.

In other words, code using RuleInvokerService ends up in this method.

Thanks, I’ve given it a try (code is visible in this PR : accepting extra dependencies at runtime by vincent-fuchs · Pull Request #24 · societe-generale/arch-unit-gradle-plugin · GitHub ). I think we are not too far, but one issue as arisen :

Any idea how to deal with the issue ?

I made an attempt at making the parameters serializable, but I didn’t have a test project available to try it out.

Let me know if you’re interested in adding some tests that can verify the plugin during its build. I can probably get the basics going pretty quickly and make a PR for it.

working solution, now merged in my project : accepting extra dependencies at runtime by vincent-fuchs · Pull Request #24 · societe-generale/arch-unit-gradle-plugin · GitHub

Thanks a lot for your help !

1 Like