openrewrite / rewrite-gradle-plugin

OpenRewrite's Gradle plugin.
Apache License 2.0
56 stars 34 forks source link

Issue in ClasspathScanningLoader.scanClasses #306

Open k3code06 opened 1 month ago

k3code06 commented 1 month ago

What version of OpenRewrite are you using?

I am using

Gradle plugin versions 6.1.9 with Gradle v8.25.0 I am extending the gradle plugin by adding some filter for the active recipes for my usecase. I am extending the DefaultProjectParser class to override the getActiveRecipes method. It occurs when I run ./gradlew rewriteDiscover

Caused by: java.lang.RuntimeException: java.lang.ClassCastException: class org.openrewrite.java.style.IntelliJ cannot be cast to class org.openrewrite.style.NamedStyles (org.openrewrite.java.style.IntelliJ is in unnamed module of loader org.openrewrite.gradle.RewriteClassLoader @5a5ff82f; org.openrewrite.style.NamedStyles is in unnamed module of loader org.gradle.internal.classloader.VisitableURLClassLoader$InstrumentingVisitableURLClassLoader @15e672fd)
        at org.openrewrite.config.ClasspathScanningLoader.scanClasses(ClasspathScanningLoader.java:153)
        at org.openrewrite.config.ClasspathScanningLoader.<init>(ClasspathScanningLoader.java:71)
        at org.openrewrite.config.Environment$Builder.scanClassLoader(Environment.java:236)
        at org.openrewrite.gradle.isolated.DefaultProjectParser.environment(DefaultProjectParser.java:571)
        at org.openrewrite.gradle.isolated.DefaultProjectParser.listRecipeDescriptors(DefaultProjectParser.java:231)
timtebeek commented 1 month ago

Oh wow, that's not a use case we see often to extend our plugin. Could you describe a bit more what you need? Or seems to trip up done of the classpath isolation we do.

k3code06 commented 1 month ago

I am adding some tag in my declarative recipes and based on tags filtering the active recipes. Can you suggest the workaround for that?

timtebeek commented 1 month ago

We don't have anything for that right now, but I do quite like the idea. The only question then becomes does order for recipes with that tag matter? Because if you're selecting recipes with a certain tag it might not be predictable which runs first

k3code06 commented 1 month ago

So as per my usecase for a specific tag, order does not matter. Can you suggest me a way so that I can extend the gradle plugin for my usecase?

k3code06 commented 1 month ago

I am trying to get list of all the available recipes. Below is the code in which I am trying to get all the available recipes.

DefaultProjectParser defaultProjectParser = new DefaultProjectParser(project, extension);
List<String> s = defaultProjectParser.listRecipeDescriptors().stream()
                .peek(recipe -> System.out.println(recipe.getName()))
                .map(RecipeDescriptor::getName)
                .collect(Collectors.toList());

Here is the code of entire class for reference-

public class RewritePlugin implements Plugin<Project> {

    @Override
    public void apply(Project project) {
        boolean isRootProject = project == project.getRootProject();

        RewriteExtension extension = project.getExtensions().create("rewrite", DefaultRewriteExtension.class, project);

        Configuration rewriteConf = project.getConfigurations().maybeCreate("rewrite");
        if(project.hasProperty("rewrite")) {
            String rewrite = project.property("rewrite").toString();
            rewriteConf.defaultDependencies(dependency -> dependency.add(project.getDependencies().create(rewrite)));
        }
        ResolveRewriteDependenciesTask resolveRewriteDependenciesTask = project.getTasks().create("rewriteResolveDependencies", ResolveRewriteDependenciesTask.class)
                .setExtension(extension)
                .setConfiguration(rewriteConf);

        RewriteRunTask rewriteRun = project.getTasks().create("rewriteRun", RewriteRunTask.class)
                .setExtension(extension)
                .setResolveDependenciesTask(resolveRewriteDependenciesTask);
        rewriteRun.dependsOn(rewriteConf);

        RewriteVersionDryRunTask rewriteDryRun = project.getTasks().create("rewriteDryRun", RewriteVersionDryRunTask.class)
                .setExtension(extension)
                .setResolveDependenciesTask(resolveRewriteDependenciesTask);
        rewriteDryRun.dependsOn(rewriteConf);

        RewriteDiscoverTask rewriteDiscover = project.getTasks().create("rewriteDiscover", RewriteDiscoverTask.class)
                .setExtension(extension)
                .setResolveDependenciesTask(resolveRewriteDependenciesTask);
        rewriteDiscover.dependsOn(rewriteConf);

        DefaultProjectParser defaultProjectParser = new DefaultProjectParser(project, extension);
        List<String> s = defaultProjectParser.listRecipeDescriptors().stream()
                .peek(recipe -> System.out.println("**"+recipe.getName()))
                .map(RecipeDescriptor::getName)
                .collect(Collectors.toList());
        if (isRootProject) {
            project.allprojects(subproject -> configureProject(subproject, extension, rewriteDryRun, rewriteRun));
        } else {
            configureProject(project, extension, rewriteDryRun, rewriteRun);
        }
    }

Using this code I am unable to get all the available recipes but I am able to get all the available recipes by rewriteDiscover command. Can you tell me what can be the issue? Is it in the way I am initialising the DefaultProjectParser object.

shanman190 commented 1 month ago

So if you notice in the Gradle plugin, DefaultProjectParser is only ever created within the isolated classpath. The reason for this is that Gradle's plugin classpath is one shared version across all plugins and inherits from the Gradle daemon. This results directly in conflicts with various libraries that both use.

Based upon the code snippet and stacktrace snippet, I'm suspecting that you're trying to use OpenRewrite directly within the Gradle classloader and that's leading directly to your class casting issues.

k3code06 commented 1 month ago

As of now we have rewrite discover to print list of all available recipes. But in my use case I need list of all available recipes among these in turn I will filter out based on some tag and make them as active recipes. Is it possible that I can get list of recipes?

shanman190 commented 1 month ago

It is definitely possible. You'd need to at least be extending the DefaultProjectParser, the DelegatingProjectParser, and extending or creating a task that would do the discovery+filtering by tags and then setting them as active on the extension or JVM property.

At that point though, any reason to not just try to achieve the desired changes without extending and contribute those via a pull request here? It sounds like a neat idea, though I'm curious how it would work out in practice. It would also probably involve a whole lot less code for you.

k3code06 commented 1 month ago

I am following the same steps as you mentioned but getting this error-

Caused by: java.lang.ClassCastException: class org.openrewrite.java.style.IntelliJ cannot be cast to class org.openrewrite.style.NamedStyles (org.openrewrite.java.style.IntelliJ is in unnamed module of loader org.openrewrite.gradle.RewriteClassLoader @e89abb5; org.openrewrite.style.NamedStyles is in unnamed module of loader org.gradle.internal.classloader.VisitableURLClassLoader$InstrumentingVisitableURLClassLoader @16f55f99)

at org.openrewrite.config.ClasspathScanningLoader.scanClasses(ClasspathScanningLoader.java:151)
at org.openrewrite.config.ClasspathScanningLoader.<init>(ClasspathScanningLoader.java:71)
at org.openrewrite.config.Environment$Builder.scanClassLoader(Environment.java:236)
at org.openrewrite.gradle.isolated.DefaultProjectParser.environment(DefaultProjectParser.java:571)

I am adding dependencies in build.gradle like this-


dependencies {
    implementation("org.codehaus.groovy:groovy-all:3.0.17")
    implementation("org.openrewrite:plugin:6.1.19")
    implementation("org.openrewrite:rewrite-core:8.25.0")
    implementation("org.openrewrite:rewrite-bom:8.25.0")
    compileOnly("org.jetbrains.kotlin:kotlin-gradle-plugin:latest.release")
    testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.0")
    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.0")
    testImplementation("org.assertj:assertj-core:3.21.0")
    implementation(kotlin("stdlib-jdk8"))
}
k3code06 commented 1 month ago

I am now able to find all the active recipes filtered by tag name by doing this-

import org.gradle.api.Project;
import org.openrewrite.Recipe;
import org.openrewrite.gradle.RewriteExtension;
import org.openrewrite.gradle.isolated.DefaultProjectParser;

import java.util.List;
import java.util.stream.Collectors;

public class DefaultVersionProjectParser extends DefaultProjectParser {
    private static final String TAG_PREFIX = "sample-tag";
    public DefaultVersionProjectParser(Project project, RewriteExtension extension) {
        super(project, extension);
    }
    @Override
    public List<String> getActiveRecipes() {
        return environment().listRecipes().stream()
                .filter(recipe -> recipe.getTags().stream().anyMatch(tag -> tag.contains(TAG_PREFIX)))
                .map(Recipe::getName)
                .collect(Collectors.toList());
    }
}

This works perfectly fine when I only have org.openrewrite:rewrite-java as dependency. The below issue occurs when I also include org.openrewrite:rewrite-core as a dependency which I need for rewriteRun command.

Caused by: java.lang.ClassCastException: class org.openrewrite.java.style.IntelliJ cannot be cast to class org.openrewrite.style.NamedStyles (org.openrewrite.java.style.IntelliJ is in unnamed module of loader org.openrewrite.gradle.RewriteClassLoader @e89abb5; org.openrewrite.style.NamedStyles is in unnamed module of loader org.gradle.internal.classloader.VisitableURLClassLoader$InstrumentingVisitableURLClassLoader @16f55f99)

at org.openrewrite.config.ClasspathScanningLoader.scanClasses(ClasspathScanningLoader.java:151)
at org.openrewrite.config.ClasspathScanningLoader.<init>(ClasspathScanningLoader.java:71)
at org.openrewrite.config.Environment$Builder.scanClassLoader(Environment.java:236)
at org.openrewrite.gradle.isolated.DefaultProjectParser.environment(DefaultProjectParser.java:571)
shanman190 commented 1 month ago

Based on what you're sharing here, I'm suspecting that your DefaultVersionProjectParser is being loaded in the Gradle plugin classloader rather than being nested into the rewrite classloader as is done by the OpenRewrite Gradle Plugin. As a result, your implementation dependencies are being loaded into the Gradle plugin classloader, then making their extensions in the RewriteClassLoader invalid from a type standpoint. RewriteClassLoader only allows a very, very small number of classes to be parent loaded due to the need to have total control of the classpath for parsing code and running recipes.

k3code06 commented 1 month ago

I am getting this error and I can observe multiple version of rewrite-core and rewrite-java are getting installed (8.1.12 version of these dependencies are getting installed everytime).

java.lang.ClassCastException: class org.openrewrite.java.style.IntelliJ cannot be cast to class org.openrewrite.style.NamedStyles (org.openrewrite.java.style.IntelliJ is in unnamed module of loader org.openrewrite.gradle.RewriteClassLoader @3b65f51a; org.openrewrite.style.NamedStyles is in unnamed module of loader org.gradle.internal.classloader.VisitableURLClassLoader$InstrumentingVisitableURLClassLoader @36c05ed6)

This is how my plugin class looks like-

public class RewritePlugin implements Plugin<Project> {

    @Override
    public void apply(Project project) {
        boolean isRootProject = project == project.getRootProject();
        RewriteExtension extension = project.getExtensions().create("rewrite", DefaultRewriteExtension.class, project);
        Configuration rewriteConf = project.getConfigurations().maybeCreate("rewrite");
        if(project.hasProperty("rewrite")) {
            String rewrite = project.property("rewrite").toString();
            rewriteConf.defaultDependencies(dependency -> dependency.add(project.getDependencies().create(rewrite)));
        }
        ResolveRewriteDependenciesTask resolveRewriteDependenciesTask = project.getTasks().create("rewriteResolveDependencies", ResolveRewriteDependenciesTask.class)
                .setExtension(extension)
                .setConfiguration(rewriteConf);

        RewriteRunTask rewriteRun = project.getTasks().create("rewriteRun", RewriteRunTask.class)
                .setExtension(extension)
                .setResolveDependenciesTask(resolveRewriteDependenciesTask);
        rewriteRun.dependsOn(rewriteConf);

        RewriteDryRunTask rewriteDryRun = project.getTasks().create("rewriteDryRun", RewriteVersionDryRunTask.class)
                .setExtension(extension)
                .setResolveDependenciesTask(resolveRewriteDependenciesTask);
        rewriteDryRun.dependsOn(rewriteConf);

        RewriteDiscoverTask rewriteDiscover = project.getTasks().create("rewriteDiscover", RewriteVersionDiscoverTask.class)
                .setExtension(extension)
                .setResolveDependenciesTask(resolveRewriteDependenciesTask);
        rewriteDiscover.dependsOn(rewriteConf);
        if (isRootProject) {
            project.allprojects(subproject -> configureProject(subproject, extension, rewriteDryRun, rewriteRun));
        } else {
            configureProject(project, extension, rewriteDryRun, rewriteRun);
        }
    }