asciidoctor / asciidoctor-gradle-plugin

A Gradle plugin that uses Asciidoctor via JRuby to process AsciiDoc source files within the project.
https://asciidoctor.github.io/asciidoctor-gradle-plugin/
Apache License 2.0
286 stars 122 forks source link

Cast error when loading extensions #90

Closed mojavelinux closed 10 years ago

mojavelinux commented 10 years ago

When you use extensions (at least extensions loaded from buildSrc), the plugin fails with a cast error.

Execution failed for task ':asciidoctor'.
> Cannot cast object 'org.asciidoctor.internal.JRubyAsciidoctor@6aadb092' with class 'org.asciidoctor.internal.JRubyAsciidoctor' to class 'org.asciidoctor.gradle.AsciidoctorProxy'

...

Caused by: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'org.asciidoctor.internal.JRubyAsciidoctor@6aadb092' with class 'org.asciidoctor.internal.JRubyAsciidoctor' to class 'org.asciidoctor.gradle.AsciidoctorProxy'
    at org.asciidoctor.gradle.AsciidoctorTask.processAsciidocSources(AsciidoctorTask.groovy:126)
    at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:63)

You can see this error by running gradle asciidoctor on the following project:

https://github.com/mojavelinux/asciidoctor-gradle-example

It's really important that we support extensions, especially extensions written in Groovy. (We even want to get to the point that the extensions can be written shorthand inside build.gradle).

mojavelinux commented 10 years ago

@aalmiray As far as I can see, this is the only blocker for 1.5.0. If there's anything you need from me to help debug it, please feel free to shout it from the top of the Twitterverse ;)

aalmiray commented 10 years ago

Got an SCCE to reproduce the problem?

mojavelinux commented 10 years ago

Only the example project thus far.

aalmiray commented 10 years ago

Yes, sorry. I totally forgot how to read issues. Noticed the link to the project after I hit "Send". Where's my coffee again? too early in the morning over here :P

mojavelinux commented 10 years ago

:)

On a side note, I'm hoping to make that project an official example for the plugin, move it into the asciidoctor organization. Though, I think we should split it into 3 projects so we have super simple, something with custom gems and something with extensions. That way, we don't overwhelm new users with all the options at once.

aalmiray commented 10 years ago

Here's the stacktrace for future reference

Caused by: java.lang.NoClassDefFoundError: org/asciidoctor/gradle/AsciidoctorProxy
    at org.asciidoctor.gradle.AsciidoctorTask.instantiateAsciidoctor(AsciidoctorTask.groovy:178)
    at org.asciidoctor.gradle.AsciidoctorTask.processAsciidocSources(AsciidoctorTask.groovy:156)
    at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:63)
    at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.doExecute(AnnotationProcessingTaskFactory.java:218)
    at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.execute(AnnotationProcessingTaskFactory.java:211)
    at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.execute(AnnotationProcessingTaskFactory.java:200)
    at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:570)
    at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:553)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:80)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:61)
    ... 66 more
Caused by: java.lang.ClassNotFoundException: org.asciidoctor.gradle.AsciidoctorProxy
    ... 76 more

Kind of weird as AsciidoctorProxy belongs to the pluign's jar. This may be a problem of two classLoaders interacting in a bad way. sigh

mojavelinux commented 10 years ago

Yep, that's what it looks like to me. As a first step, is it possible to load an extension without using the service loader mechanism? If so, we could just recommend against using that for now. I also wonder if maybe this only happens when the extension is in the buildSrc. I wonder what would happen if we stuck it in a jar and made it a regular dependency. Not ideal, but helps us pinpoint the conditions of the failure.

aalmiray commented 10 years ago

Methinks we should have an asciidoctor dependency configuration, otherwise the asciidoctor extensions will bleed out into the compile/runtime configurations. The alternative is to write your custom configuration (like we do in Griffon with compileOnly), but then you need additional setup for the IDE's to recognize the extra JARs.

mojavelinux commented 10 years ago

I agree, we are definitely going to want that because I anticipate mature projects loading a number of extensions from the Ruby or Java load paths. There will also be other things like webjars that people may want to include.

aalmiray commented 10 years ago

Silly me, we already have the asciidoctor configuration. I'll setup a multi-project build and plug the custom extension into this configuration.

aalmiray commented 10 years ago

This is definitely one of those silly classloader errors we love in the Java space. A multi-project build yields the following error when invoking the asciidoctor task

Caused by: java.lang.NoClassDefFoundError: org/asciidoctor/extension/spi/ExtensionRegistry
    at org.gradle.internal.classloader.MultiParentClassLoader.loadClass(MultiParentClassLoader.java:63)
    at org.gradle.internal.classloader.CachingClassLoader.loadClass(CachingClassLoader.java:41)
    at org.asciidoctor.extension.internal.ExtensionRegistryExecutor.registerAllExtensions(ExtensionRegistryExecutor.java:20)
    at org.asciidoctor.internal.JRubyAsciidoctor.registerExtensions(JRubyAsciidoctor.java:91)
    at org.asciidoctor.internal.JRubyAsciidoctor.create(JRubyAsciidoctor.java:71)
    at org.asciidoctor.Asciidoctor$Factory.create(Asciidoctor.java:662)
    at org.asciidoctor.gradle.AsciidoctorTask.instantiateAsciidoctor(AsciidoctorTask.groovy:178)
    at org.asciidoctor.gradle.AsciidoctorTask.processAsciidocSources(AsciidoctorTask.groovy:156)
    at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:63)
    at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.doExecute(AnnotationProcessingTaskFactory.java:218)
    at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.execute(AnnotationProcessingTaskFactory.java:211)
    at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.execute(AnnotationProcessingTaskFactory.java:200)
    at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:570)
    at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:553)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:80)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:61)
    ... 66 more
Caused by: java.lang.ClassNotFoundException: org.asciidoctor.extension.spi.ExtensionRegistry
    ... 82 more

Notice how Gradle's MultiParentClassLoader can't locate the classes. The root of the problem may be https://github.com/asciidoctor/asciidoctor-gradle-plugin/blob/master/src/main/groovy/org/asciidoctor/gradle/AsciidoctorTask.groovy#L359-L367 a custom classloader used to load AsciidoctorJ without having it spreading into other configurations/classpaths. I'll investigate a different way to make this work.

aalmiray commented 10 years ago

Was able to get a bit further by moving the extension to its own project (le sigh) because of the previous stacktrace, however this time I hit a problem on the Ruby side

Caused by: org.gradle.api.GradleException: Error running Asciidoctor
    at org.asciidoctor.gradle.AsciidoctorTask.processDocumentsAndResources(AsciidoctorTask.groovy:204)
    at org.asciidoctor.gradle.AsciidoctorTask.this$4$processDocumentsAndResources(AsciidoctorTask.groovy)
    at org.asciidoctor.gradle.AsciidoctorTask$this$4$processDocumentsAndResources.callCurrent(Unknown Source)
    at org.asciidoctor.gradle.AsciidoctorTask.processAsciidocSources(AsciidoctorTask.groovy:170)
    at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:63)
    at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.doExecute(AnnotationProcessingTaskFactory.java:218)
    at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.execute(AnnotationProcessingTaskFactory.java:211)
    at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.execute(AnnotationProcessingTaskFactory.java:200)
    at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:570)
    at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:553)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:80)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:61)
    ... 66 more
Caused by: org.jruby.exceptions.RaiseException: (NoMethodError) undefined method `include?' for nil:NilClass
    at RUBY.registered_for_block?(jar:file:/Users/aalmiray/.gradle/caches/modules-2/files-2.1/org.asciidoctor/asciidoctorj/1.5.0/192df5660f72a0fb76966dcc64193b94fba65f99/asciidoctorj-1.5.0.jar!/gems/asciidoctor-1.5.0/lib/asciidoctor/extensions.rb:800)
    at RUBY.next_block(jar:file:/Users/aalmiray/.gradle/caches/modules-2/files-2.1/org.asciidoctor/asciidoctorj/1.5.0/192df5660f72a0fb76966dcc64193b94fba65f99/asciidoctorj-1.5.0.jar!/gems/asciidoctor-1.5.0/lib/asciidoctor/parser.rb:675)
    at RUBY.next_section(jar:file:/Users/aalmiray/.gradle/caches/modules-2/files-2.1/org.asciidoctor/asciidoctorj/1.5.0/192df5660f72a0fb76966dcc64193b94fba65f99/asciidoctorj-1.5.0.jar!/gems/asciidoctor-1.5.0/lib/asciidoctor/parser.rb:303)
    at RUBY.next_section(jar:file:/Users/aalmiray/.gradle/caches/modules-2/files-2.1/org.asciidoctor/asciidoctorj/1.5.0/192df5660f72a0fb76966dcc64193b94fba65f99/asciidoctorj-1.5.0.jar!/gems/asciidoctor-1.5.0/lib/asciidoctor/parser.rb:291)
    at RUBY.parse(jar:file:/Users/aalmiray/.gradle/caches/modules-2/files-2.1/org.asciidoctor/asciidoctorj/1.5.0/192df5660f72a0fb76966dcc64193b94fba65f99/asciidoctorj-1.5.0.jar!/gems/asciidoctor-1.5.0/lib/asciidoctor/parser.rb:52)
    at RUBY.parse(jar:file:/Users/aalmiray/.gradle/caches/modules-2/files-2.1/org.asciidoctor/asciidoctorj/1.5.0/192df5660f72a0fb76966dcc64193b94fba65f99/asciidoctorj-1.5.0.jar!/gems/asciidoctor-1.5.0/lib/asciidoctor/document.rb:448)
    at RUBY.load(jar:file:/Users/aalmiray/.gradle/caches/modules-2/files-2.1/org.asciidoctor/asciidoctorj/1.5.0/192df5660f72a0fb76966dcc64193b94fba65f99/asciidoctorj-1.5.0.jar!/gems/asciidoctor-1.5.0/lib/asciidoctor.rb:1337)
    at RUBY.convert(jar:file:/Users/aalmiray/.gradle/caches/modules-2/files-2.1/org.asciidoctor/asciidoctorj/1.5.0/192df5660f72a0fb76966dcc64193b94fba65f99/asciidoctorj-1.5.0.jar!/gems/asciidoctor-1.5.0/lib/asciidoctor.rb:1415)
    at RUBY.convert_file(jar:file:/Users/aalmiray/.gradle/caches/modules-2/files-2.1/org.asciidoctor/asciidoctorj/1.5.0/192df5660f72a0fb76966dcc64193b94fba65f99/asciidoctorj-1.5.0.jar!/gems/asciidoctor-1.5.0/lib/asciidoctor.rb:1528)
    at RUBY.convertFile(<script>:62)
    at org.jruby.gen.InterfaceImpl679234251.convertFile(org/jruby/gen/InterfaceImpl679234251.gen:13)
    at org.asciidoctor.gradle.AsciidoctorTask.processSingleFile(AsciidoctorTask.groovy:236)
    at org.asciidoctor.gradle.AsciidoctorTask.processSourceDir(AsciidoctorTask.groovy:220)
    at org.asciidoctor.gradle.AsciidoctorTask$_processDocumentsAndResources_closure5.doCall(AsciidoctorTask.groovy:201)
    at org.asciidoctor.gradle.AsciidoctorTask$_eachFileRecurse_closure8.doCall(AsciidoctorTask.groovy:249)
    at org.asciidoctor.gradle.AsciidoctorTask.eachFileRecurse(AsciidoctorTask.groovy:244)
    at org.asciidoctor.gradle.AsciidoctorTask.processDocumentsAndResources(AsciidoctorTask.groovy:200)
    at org.asciidoctor.gradle.AsciidoctorTask.processAsciidocSources(AsciidoctorTask.groovy:170)

I've posted the setup at https://github.com/aalmiray/asciidoctor-gradle-example You'll have to build things manually for the time being

  1. cd extension && gradle jar
  2. cd ../core && gradle asciidoctor
mojavelinux commented 10 years ago

Aha! That's easy enough to fix. That's just a required config setting for the extension. I'll fill in the gap.

mojavelinux commented 10 years ago

Fixed! Now the extension works. So the question now is, can we move it to buildSrc?

aalmiray commented 10 years ago

Unfortunately we're back to almost the same place where we started if the extension is placed under buildSrc. The latest exception is

Caused by: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'org.asciidoctor.internal.JRubyAsciidoctor@25416916' with class 'org.asciidoctor.internal.JRubyAsciidoctor' to class 'org.asciidoctor.gradle.AsciidoctorProxy'
    at org.asciidoctor.gradle.AsciidoctorTask.instantiateAsciidoctor(AsciidoctorTask.groovy:183)
    at org.asciidoctor.gradle.AsciidoctorTask.processAsciidocSources(AsciidoctorTask.groovy:158)
    at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:63)
    at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.doExecute(AnnotationProcessingTaskFactory.java:219)
    at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.execute(AnnotationProcessingTaskFactory.java:212)
    at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.execute(AnnotationProcessingTaskFactory.java:201)
    at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:533)
    at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:516)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:80)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:61)
    ... 66 more

Which makes me think we have two ClassLoaders in the mix.

mojavelinux commented 10 years ago

Would this be solved if you could pass the classloader to Asciidoctor.Factory.create? If so, we could introduce an overloaded method that takes both the gemPath and classloader for now, then implement the config object idea after the 1.5.0 plugin release.

aalmiray commented 10 years ago

It's worth a shot. I think this is what we're missing and the reason why I suggested an overloaded version for create().

mojavelinux commented 10 years ago

:+1:

Go ahead and hack your local install of AsciidoctorJ and see if that change works. Then, we can do a pull request.

I have a feeling we're going to want to base the Gradle 1.5.0 plugin on AsciidoctorJ 1.5.1.

aalmiray commented 10 years ago

I agree too. We can push asciidoctor-gradle 1.5.0 to be in sync with 1.5.0 with the caveat that "in-build" extensions will not work. There are much more other goodies in 1.5.0 that people can take advantage if we release the plugin asap.

mojavelinux commented 10 years ago

Sure, but we can also just release 1.5.1 later today / early tomorrow with the fixes we need (same for the Maven plugin). We'll still announce it as 1.5.0...but use that ever important semantic versioning.

aalmiray commented 10 years ago

If it's the same to you I'd rather go with 1.5.0 as it currently stands. Will finish up documenting what's changed for this release (deprecations, behavior compatibility, etc). Wednesday will be a busy, busy day :smile: off to catch some :sleeping:

mojavelinux commented 10 years ago

Solid work! I knew my prayers to the beer gods would pay off :)