openrewrite / rewrite-gradle-plugin

OpenRewrite's Gradle plugin.
Apache License 2.0
60 stars 37 forks source link

Add support for Android source sets #327

Closed bryceatmoderne closed 2 weeks ago

bryceatmoderne commented 1 month ago

What's changed?

What's your motivation?

These changes will allow rewrite-gradle-plugin to run recipes on android project repositories.

Anything in particular you'd like reviewers to focus on?

Anyone you would like to review specifically?

Have you considered any alternatives or workarounds?

I tried several approaches include adding AGP jars to RewriteClassLoader but could not avoid ClassCastExceptionss when assigned gradle decorated AGP classes to ones loaded by RewriteClassLoader

Any additional context

Checklist

shanman190 commented 2 weeks ago

For the OSS build plugins, the plugin classloader part wouldn't be necessary. However, when testing via the tooling API or loading the plugin via the initscript, it results in a different location as to where to find plugins. To illustrate this a bit more, consider the typical classloader hierarchy below for a Gradle project:

JVM
  Gradle
    Initscript
      Settings
        Root Project
          Subproject A
            Subprojects A.1
              ...
            Subprojects A.2
              ...
          Subproject B
            Subproject B.1
              ...
            Subproject B.2
              ...
          ...

For a moment, let's just focus in on where we would find RewriteClassLoader and Android Gradle Plugin's BaseAppModuleExtension to make things simplier.

When running via the OSS build plugins applied to the root project, then both classes would appear in the "Root Project" classloader above. When they are both in "Subproject A", they would both appear within the "Subproject A" classloader. Now if the OSS build plugin is at the root, but AGP is in "Subproject A", the plugin classloader logic here is necessary because the class is in a child classloader rather than a parent classloader. When the OSS build plugin is applied via an Initscript, it's now a 4th generational parent from the AGP class.

As a result of the placement of the various jars into each of their classloaders for the necessary classpath isolation, this is what is resulting in the differing behavior and ultimately the necessity of loading the AGP classes from a particular classloader. This actually compounds with each additional plugin and is probably one case where Kotlin Multiplatform may not be working fully as an example.

bryceatmoderne commented 1 week ago

For the OSS build plugins, the plugin classloader part wouldn't be necessary. However, when testing via the tooling API or loading the plugin via the initscript, it results in a different location as to where to find plugins. To illustrate this a bit more, consider the typical classloader hierarchy below for a Gradle project:

JVM
  Gradle
    Initscript
      Settings
        Root Project
          Subproject A
            Subprojects A.1
              ...
            Subprojects A.2
              ...
          Subproject B
            Subproject B.1
              ...
            Subproject B.2
              ...
          ...

For a moment, let's just focus in on where we would find RewriteClassLoader and Android Gradle Plugin's BaseAppModuleExtension to make things simplier.

When running via the OSS build plugins applied to the root project, then both classes would appear in the "Root Project" classloader above. When they are both in "Subproject A", they would both appear within the "Subproject A" classloader. Now if the OSS build plugin is at the root, but AGP is in "Subproject A", the plugin classloader logic here is necessary because the class is in a child classloader rather than a parent classloader. When the OSS build plugin is applied via an Initscript, it's now a 4th generational parent from the AGP class.

As a result of the placement of the various jars into each of their classloaders for the necessary classpath isolation, this is what is resulting in the differing behavior and ultimately the necessity of loading the AGP classes from a particular classloader. This actually compounds with each additional plugin and is probably one case where Kotlin Multiplatform may not be working fully as an example.

Thank you for these insights @shanman190. Much appreciated!