I added the following dependency to a gradle proje...
# community-support
t
I added the following dependency to a gradle project:
Copy code
implementation 'com.android.tools.build:gradle-api:8.5.1'
Which provided me with all the needed classes after the build. But at runtime I get
ClassNotFoundException
for the classes introduced by this dependency. Can someone help me figure out what might be the reason for it?
v
Hard to say without more information. What kind of project is that, how do you use it, ...
t
Here's the project. All I did on top of this was to add the dependency in the build.gradle andd added
google()
to repositories in the root project build.gradle. It fetched the dependencies, build was successful, my IDE was able to recognize the dependency as well. When I used any class from the dependency such as
AndroidBasePlugin
or
ApplicationExtension
they would result in the
ClassNotFoundException
at runtime. Funny thing about it, when I add a breakpoijt right before any of these classes are used and try to the debug expression it would give me results perfectly, but then if I proceed, bam!
ClassNotFound
.
v
So you are building a Gradle plugin right? When you are there with the debugger, what is the
toString()
of the classloader of the class trying to use that other class, and what is the
toString()
of the classloader of the class it tries to use which you said you can get in the debugger?
t
So you are building a Gradle plugin right?
Yes to be used by the tooling api in the other module.
what is the
toString()
of the classloader of the class trying to use that other class
I am not sure what you mean by this. How do I get the classLoader?
Copy code
public static boolean isAndroidProject(Project project) {
    return findExtension(AndroidBasePlugin.class, project) != null;
  }
It would throw the exception before findExtension is even called. Just mentioning
AndroidBasePlugin.class
causes the exception. Although I can put a breakpoint on the return statement and then evaluate the same expression successfully.
v
How do I get the classLoader?
AndroidBasePlugin.class.getClassLoader()
Also, that snippet you showed is an extremely bad idea
It introduces an ordering requirement between your plugin and the android plugin, in that it only works as intended if you apply your plugin after the android plugin, not before
Assuming you do this at configuration time
Such ordering constraints are discouraged bad practice
t
AndroidBasePlugin.class.getClassLoader()
resulted in the following:
Copy code
"InstrumentingVisitableURLClassLoader(ClassLoaderScopeIdentifier.Id{coreAndPlugins:settings[:]:buildSrc[:]:root-project[:](export)})"
v
And for the class where it is used?
t
It introduces an ordering requirement between your plugin and the android plugin, in that it only works as intended if you apply your plugin after the android plugin, not before
Just to be on the same page. My plugin is a custom plugin to work with the tooling api. This snippet I shared is being used in my plugin for building a custom model. It is supposed to check if the user's project for which we are trying to build a model is an android project. Any articles you might share to help me find more about the issue and how to address them? 🙂
And for the class where it is used?
Copy code
InstrumentingVisitableURLClassLoader(ClassLoaderScopeIdentifier.Id{coreAndPlugins:init-file:/D:/Tanish%20Ranjan/Development/Languages/Java/build-server-for-gradle/server/build/libs/plugins/init.gradle(export)})
v
My plugin is a custom plugin to work with the tooling api. This snippet I shared is being used in my plugin for building a custom model.
Uhm, ok, well, if it is not for "normal" usage and you ensure it is applied after the android plugin it might be ok.
And for the class where it is used?
There you have it, your plugin class is in the init script class loader. The android plugin class is in the root project class loader. So your plugin class cannot see the android class.
t
But the dependency was added in the plugin module's build.gradle. Shouldn't it have had compiled with the plugin and upon invocation both be called from the init script class loader?
v
While this might become problematic, probably yes. How does the GMM file of the plugin look like? And how do you use it in the init script?
Ah, wait, no.
Even if you would find the class on the init script classloader, that wouldn't change anything iirc.
Well, it would change that you do not get a class not found exception
But instead you would always get
false
Iirc, the build script classloader is not a child of the init script classloader and thus cannot access the classes from the init script class loader, so even if the classes would be there, the pluign in the root project classpath would use its own versions of the classes and so your query for the extension would not yield a result as the same class from different class loaders is not the same class
t
Okay. Then how should I use the dependency in my plugin? Because this is how I was planning to fetch the sourceSets for android projects:
Copy code
public static boolean isAndroidProject(Project project) {
    return findExtension(AndroidBasePlugin.class, project) != null;
  }

  public static List<AndroidSourceSet> getAndroidSourceSets(Project project) {

    ApplicationExtension appExtension = findExtension(ApplicationExtension.class, project);

    if (appExtension != null) {
      return new LinkedList<>(appExtension.getSourceSets());
    }

    LibraryExtension libExtension = findExtension(LibraryExtension.class, project);

    if (libExtension != null) {
      return new LinkedList<>(libExtension.getSourceSets());
    }

    return new LinkedList<>();

  }

  public static <T> T findExtension(Class<T> clazz, Project project) {

    // Get from extensions if supported
    if (GradleVersion.current().compareTo(GradleVersion.version("5.0")) >= 0) {
      T extension = project.getExtensions().findByType(clazz);
      if (extension != null) {
        return extension;
      }
    }

    // Fallback
    T extension = project.getConvention().findByType(clazz);
    if (extension != null) {
      return extension;
    }

    return null;

  }
Btw this is how the init script is being used in the build.gradle file from the server module where we use the plugin for the tooling api:
Copy code
task generateInitScript() {
  doLast {
    def initScript = file("$buildDir/libs/plugins/init.gradle")
    initScript.parentFile.mkdirs()
    initScript.write """
      initscript {
        dependencies {
          classpath files('plugin.jar')
        }
      }
      allprojects {
        apply plugin: com.microsoft.java.bs.gradle.plugin.GradleBuildServerPlugin
      }
      """
  }
}

processResources {
  dependsOn(':plugin:copyJar')
  dependsOn copyRuntimeLibs
  dependsOn(':server:generateInitScript')
  duplicatesStrategy = 'include'
  exclude 'NOTICE.txt'
}
Invocation part:
Copy code
BuildActionExecuter<GradleSourceSets> buildExecutor =
          Utils.getBuildActionExecuter(connection, preferenceManager.getPreferences(),
            new GetSourceSetsAction());
      buildExecutor.addProgressListener(reporter,
              OperationType.FILE_DOWNLOAD, OperationType.PROJECT_CONFIGURATION)
          .setStandardError(errorOut)
          .addArguments("--init-script", initScript.getAbsolutePath());
v
Btw this is how the init script is being used in the build.gradle file from the server module where we use the plugin for the tooling api:
Well, you explicitly only add the plugin jar to the classpath, so of course the dependency is not there.
But as I said, even if you would have the classes in the init script classloader, it would not work as expected
Maybe you get it to work if you instead add the plugin to the project build script classpath. The information in the various comments in https://github.com/gradle/gradle/issues/8173 might help somehow.
t
Thanks for the sources. Let me take a look.
👌 1