Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@GrabConfig(systemClassLoader = true) Causing No suitable ClassLoader found for grab Exception #779

Closed
dhandley12 opened this issue Dec 18, 2018 · 11 comments
Assignees
Labels
Milestone

Comments

@dhandley12
Copy link

dhandley12 commented Dec 18, 2018

The following example code fails to execute due to the inclusion of @GrabConfig(systemClassLoader = true), which is required for the program to run in production.

I have tried running this using Groovy 2.4.7 on Eclipse Neon and on a clean install of Eclipse Neon running Groovy 2.4.13. I'm confused because it appears the line of code throwing the exception is commented out in the Groovy-Eclipse source code, but perhaps I'm looking in the wrong area. Please let me know if there is any more info I can provide to help solve this issue!

update: I also verified this does not work running the latest version of Eclipse 2018-09 running the 2.4.15 compiler.

Source Code referenced: https://github.com/groovy/groovy-eclipse/blame/337f7d8fe709d0c7d469202425153b6ed1bbfd0c/base/org.codehaus.groovy24/src/groovy/grape/GrapeIvy.groovy#L181

Code:

@Grapes([
	@Grab(group='mysql', module='mysql-connector-java', version='5.1.6'),
	@GrabConfig(systemClassLoader = true)
])
def a
println "Why won't this run?"

Stack Trace:

org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
General error during conversion: No suitable ClassLoader found for grab

java.lang.RuntimeException: No suitable ClassLoader found for grab
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
	at java.lang.reflect.Constructor.newInstance(Unknown Source)
	at org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:83)
	at org.codehaus.groovy.runtime.callsite.ConstructorSite$ConstructorSiteNoUnwrapNoCoerce.callConstructor(ConstructorSite.java:105)
	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:60)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:235)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:247)
	at groovy.grape.GrapeIvy.chooseClassLoader(GrapeIvy.groovy:182)
	at groovy.grape.GrapeIvy$chooseClassLoader.callCurrent(Unknown Source)
	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:52)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:154)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:166)
	at groovy.grape.GrapeIvy.grab(GrapeIvy.groovy:249)
	at groovy.grape.Grape.grab(Grape.java:167)
	at groovy.grape.GrabAnnotationTransformation.visit(GrabAnnotationTransformation.java:377)
	at org.codehaus.groovy.transform.ASTTransformationVisitor$3.call(ASTTransformationVisitor.java:321)
	at org.codehaus.groovy.control.CompilationUnit.applyToSourceUnits(CompilationUnit.java:943)
	at org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:605)
	at org.codehaus.groovy.control.CompilationUnit.processPhaseOperations(CompilationUnit.java:581)
	at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:558)
	at groovy.lang.GroovyClassLoader.doParseClass(GroovyClassLoader.java:298)
	at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:268)
	at groovy.lang.GroovyShell.parseClass(GroovyShell.java:688)
	at groovy.lang.GroovyShell.run(GroovyShell.java:517)
	at groovy.lang.GroovyShell.run(GroovyShell.java:507)
	at groovy.ui.GroovyMain.processOnce(GroovyMain.java:590)
	at groovy.ui.GroovyMain.run(GroovyMain.java:321)
	at groovy.ui.GroovyMain.process(GroovyMain.java:307)
	at groovy.ui.GroovyMain.processArgs(GroovyMain.java:126)
	at groovy.ui.GroovyMain.main(GroovyMain.java:106)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at org.codehaus.groovy.tools.GroovyStarter.rootLoader(GroovyStarter.java:109)
	at org.codehaus.groovy.tools.GroovyStarter.main(GroovyStarter.java:131)

1 error
@eric-milles
Copy link
Member

If you are running this as a Groovy Script, then compilation is deferred until GroovyStarter takes over. This means the Groovy-Eclipse compiler and patched runtime jar are not used. If your script runs successfully in another environment, I'd like to hear a few details about that to understand the differences.

When launching from Eclipse as a Groovy Script, a process typically runs a command line like:

java -Dgroovy.home="${groovy_home}" org.codehaus.groovy.tools.GroovyStarter --classpath "your project's classpath entries" --main groovy.ui.GroovyMain "${resource_loc:/Groovy/src/main/scripts/grab_config.groovy}"

@dhandley12
Copy link
Author

Assuming the code I listed above is in the file GrapesFailing.groovy

When I run:
"%GROOVY_HOME%\bin\groovy.bat" <local_path_to_file>\GrapesFailing.groovy
The code executes with no issues. However when running the same file in eclipse I get the error stated above.

Eclipse failing run configuration information:
Program arguments:
--classpath "${workspace_loc:\SimpleGrapesTest}\src;${workspace_loc:\SimpleGrapesTest}\bin" --main groovy.ui.GroovyMain "${resource_loc:/SimpleGrapesTest/src/GrapesFailing.groovy}"

VM arguments:
-Dgroovy.home="${groovy_home}"

Please let me know if there is any further info I can provide to help track down the issue.

@eric-milles
Copy link
Member

So what command line does the bat file end up running and have you tried the command line that Eclipse uses to launch from a command prompt?

@dhandley12
Copy link
Author

This is what command line is actually called after digging into groovy.bat , which calls startGroovy.bat

"%JAVA_HOME%\bin\java.exe" -Xmx128m -Dprogram.name="%PROGNAME%" -Dgroovy.home="%GROOVY_HOME%" -Dtools.jar="%TOOLS_JAR%" -Dgroovy.starter.conf="%GROOVY_HOME%\conf\groovy-starter.conf" -Dscript.name=<path to workspace>\SimpleGrapesTest\src\GrapesFailing.groovy %JAVA_OPTS% -classpath "%GROOVY_HOME%\lib\groovy-2.4.7.jar" org.codehaus.groovy.tools.GroovyStarter --main groovy.ui.GroovyMain --conf "%GROOVY_HOME%\conf\groovy-starter.conf" <path to workspace>\SimpleGrapesTest\src\GrapesFailing.groovy

The command line generate by eclipse that fails:

java -Dgroovy.home=<path to plugin>/org.codehaus.groovy_2.4.7.xx-201612170628-e46/ -Dfile.encoding=Cp1252 -classpath <workspace path>\SimpleGrapesTest\bin;<plugin path>\org.codehaus.groovy_2.4.7.xx-201612170628-e46\lib\groovy-all-2.4.7.jar;<plugin path>\org.codehaus.groovy_2.4.7.xx-201612170628-e46\lib\bsf-2.4.0.jar;<plugin path>\org.codehaus.groovy_2.4.7.xx-201612170628-e46\lib\ivy-2.4.0.jar;<plugin path>\org.codehaus.groovy_2.4.7.xx-201612170628-e46\lib\servlet-api-2.4.jar;<my profile>\.groovy\greclipse\global_dsld_support;<plugin path>\org.codehaus.groovy_2.4.7.xx-201612170628-e46\plugin_dsld_support\ org.codehaus.groovy.tools.GroovyStarter --classpath <workspace path>\SimpleGrapesTest\src;<workspace path>\SimpleGrapesTest\bin --main groovy.ui.GroovyMain <workspace path>\SimpleGrapesTest\src\GrapesFailing.groovy

In order to make the eclipse command line work I had to make the following changes.
Add these arguments:
-Dtools.jar="%TOOLS_JAR%" -Dgroovy.starter.conf="%GROOVY_HOME%\conf\groovy-starter.conf"
note that %TOOLS_JAR% = %JAVA_HOME%\lib\tools.jar

I also had to modify -Dgroovy.home to point at %GROOVY_HOME% instead of /org.codehaus.groovy_2.4.7.xx-201612170628-e46/`

@eric-milles
Copy link
Member

By setting systemClassLoader=true, this line is run in GrabAnnotationTransformation:

if (systemClassLoader) loader = ClassLoader.getSystemClassLoader();

This prevents the use of the class loader from the source unit on this line of GrabAnnotationTransformation:

basicArgs.put("classLoader", loader != null ? loader : sourceUnit.getClassLoader());

Within GrapeIvy, this runs:

    public grab(Map args, Map... dependencies) {
        ClassLoader loader = null
        grabRecordsForCurrDependencies.clear()

        try {
            // identify the target classloader early, so we fail before checking repositories
            loader = chooseClassLoader(
                classLoader:args.remove('classLoader'),
                refObject:args.remove('refObject'),
                calleeDepth:args.calleeDepth?:DEFAULT_DEPTH,
            )
        ...

    public def chooseClassLoader(Map args) {
        def loader = args.classLoader
        if (!isValidTargetClassLoader(loader)) {
            loader = (args.refObject?.class
                        ?:ReflectionUtils.getCallingClass(args.calleeDepth?:1)
                      )?.classLoader
            while (loader && !isValidTargetClassLoader(loader)) {
                loader = loader.parent
            }
            if (!isValidTargetClassLoader(loader)) {
                throw new RuntimeException("No suitable ClassLoader found for grab")
            }

    private boolean isValidTargetClassLoaderClass(Class loaderClass) {
        return (loaderClass != null) &&
            (
             (loaderClass.name == 'groovy.lang.GroovyClassLoader') ||
             (loaderClass.name == 'org.codehaus.groovy.tools.RootLoader') ||
             isValidTargetClassLoaderClass(loaderClass.superclass)
            )
    }

isValidTargetClassLoaderClass never answers true because the class loader is a jdk.internal.loader.ClassLoaders$AppClassLoader

The class loader returned by ClassLoader.getSystemClassLoader() can be influenced by setting the "java.system.class.loader" system property.

@eric-milles
Copy link
Member

So, if I add this to the JVM arguments, the system class loader is accepted and the script runs: -Djava.system.class.loader=groovy.lang.GroovyClassLoader

@dhandley12
Copy link
Author

Good Morning Eric,

Thank you for the thorough explanation and correction of the root issue. I have verified my test code works on the latest version of the plugin for eclipse 2018-09.

The only remaining issue is Eclipse still records an error in the problems tab when @GrabConfig(systemClassLoader = true) is included in Grapes. While I am able to make a filter to exclude these errors from the problems tab they still popup in the class itself. I have verified this issue with Eclipse 2018-09 and 2018-12. Is there a way to remedy this?

reference:
image

image

Thanks,
Dan

@eric-milles
Copy link
Member

Ahh, yes. If you open your script in the editor, GDT will try and process it through most compiler phases to give better content assist support. If you add a script filter to your project or workspace, the compiler will not try and produce a class file, which should prevent adding errors to the Problems view.

You can see examples of script filters here:
image

@dhandley12
Copy link
Author

I attempted that suggestion and unfortunately it did not resolve the issue. The error is still being flagged in the class.

Additionally, I use grapes in drivers for programs where the driver resides in the same package as the class being run. I don't think I would want to wait till run-time for the classes to be compiled.

Are there any other options you know of to exclude checking for this specific error?

@dhandley12
Copy link
Author

Actually I found a solution that works for us. We only use grapes when running code outside of eclipse, so adding the argument -Dgroovy.grape.enable=false to the eclipse.ini file took care of the issue entirely.

Thanks again for all of your help!

@eric-milles
Copy link
Member

If you want to mark some classes in a package as scripts and compile some others, you can alter the script pattern any way you wish. It need not exclude everything in a package -- like /my/pack/with/scripts/Script*.groovy for example. I would advise excluding your scripts from compile to avoid issues like this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants