[ 
https://issues.apache.org/jira/browse/GROOVY-12683?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Paul King resolved GROOVY-12683.
--------------------------------
    Fix Version/s: 7.0.0-alpha-1
         Assignee: Paul King
       Resolution: Fixed

> Extension modules whose classes need an optional dependency break all 
> compilation when that dependency is missing (groovy-fx in the distribution)
> -------------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: GROOVY-12683
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12683
>             Project: Groovy
>          Issue Type: Bug
>          Components: Extension methods, Grape
>            Reporter: Paul King
>            Assignee: Paul King
>            Priority: Major
>             Fix For: 7.0.0-alpha-1
>
>
> h3. Problem
> The {{groovy-fx}} module added by GROOVY-11434 is an optional module, but it 
> is still copied into {{lib}} of the binary distribution and the 
> {{installGroovy}} task output. JavaFX is a compile-only dependency of that 
> module and is not bundled. On a JDK without JavaFX, that one jar makes 
> *every* compilation fail, even a trivial script:
> {noformat}
> $ groovy -e 'println "x".toUpperCase()'
> SEVERE [groovy.ui.GroovyMain] Caught: {0}
> java.lang.NoClassDefFoundError: Unable to configure 
> org.apache.groovy.groovyfx.extensions.FXExtensions due to missing dependency 
> javafx/beans/binding/NumberBinding
> {noformat}
> Removing {{groovy-fx-*.jar}} from {{lib}} makes the error go away.
> h3. Cause
> The module descriptor registers {{FXExtensions}}, whose method signatures 
> mention JavaFX types. The runtime already tolerates an extension class it 
> cannot link: {{SimpleExtensionModule.getMetaMethods}} catches 
> {{LinkageError}} per class and logs a warning. The compile-time cache does 
> not. {{AbstractExtensionMethodCache.scan}}, which backs both static type 
> checking and the macro transform, builds a {{ClassNode}} for every extension 
> class visible to the loader. When one class cannot be configured, the error 
> escapes and stops the compilation. The macro transform is a global transform, 
> so this happens for every script, not just ones that use {{@CompileStatic}}.
> GroovyFX is the first module we ship whose descriptor-registered extension 
> class depends on a compile-only dependency. A third-party extension module 
> with an optional dependency fails in exactly the same way.
> h3. Proposed fix
> # *Backstop:* make {{AbstractExtensionMethodCache.scan}} catch 
> {{LinkageError}} per extension class and log a warning that names the class, 
> matching the runtime. A single unusable module can then no longer stop all 
> compilation.
> # *Declared prerequisites:* add an optional {{requiredClasses}} key to the 
> extension module descriptor. If any listed class cannot be loaded from the 
> loader that defines the module's extension classes, the module is deferred 
> instead of loaded. It is not registered, a FINE message reports the deferral, 
> and its descriptor is remembered per class loader.
> # *Late activation:* after each {{@Grab}}, Grape (Ivy and Maven) re-checks 
> the deferred modules. It registers any whose required classes are now 
> loadable, using the existing GROOVY-5543 metaclass refresh. It then 
> invalidates the compile-time extension method caches so static type checking 
> and macros see the new methods. Grape does not invalidate those caches today, 
> even for an ordinary grabbed extension module.
> # groovy-fx declares {{requiredClasses=javafx.beans.binding.NumberBinding}} 
> through the build-logic {{moduleDescriptor}} DSL.
> Users who supply JavaFX through a build file, a JavaFX-bundling JDK or 
> {{--module-path}} are unaffected: the probe succeeds at startup and the 
> module registers as before.
> h3. Class loader caveat
> A jar in {{lib}} is defined by the launcher's {{RootLoader}}, so its classes 
> can only link against classes that loader can see. A plain {{@Grab}} adds 
> JavaFX to the script's child {{GroovyClassLoader}}, which the module cannot 
> use. In that case the module stays deferred and a warning suggests 
> {{@GrabConfig(systemClassLoader=true)}}, which works end to end. The same 
> rule already applies to {{SceneGraphBuilder}} and every other class in the 
> jar. The GroovyFX Grape recipe in the user guide should use 
> {{systemClassLoader=true}}.
> h3. Verified with a prototype
> * A new forked-JVM test, {{DeferredExtensionModuleTest}}, covers four cases. 
> A module is deferred, then activates for dynamic and {{@CompileStatic}} code. 
> A module with its required class present loads normally. A module stays 
> deferred when the class is visible only to a child loader. A module with an 
> unlinkable signature no longer breaks compilation. That last case fails on 
> master with the error above.
> * On an installed build that has groovy-fx in {{lib}} and no JavaFX, plain 
> and {{@CompileStatic}} scripts run.
> * With {{@GrabConfig(systemClassLoader=true)}} and {{@Grab}} of 
> {{javafx-base}}, the module activates, and {{SimpleIntegerProperty + 
> SimpleIntegerProperty}} works both dynamically and under {{@CompileStatic}}.
> * The existing extension-module, {{DgmMethodLookupTest}}, Grape, groovy-macro 
> and groovy-fx tests still pass.
> h3. Notes
> * The change adds new public API, all {{@since 7.0.0}}: the descriptor key 
> constant, two static methods on {{ExtensionModuleScanner}}, and 
> {{AbstractExtensionMethodCache.invalidateAllCaches()}}. The scanner methods 
> could be made internal instead.
> * The descriptor key is additive. Older Groovy versions ignore it.
> * A simpler stopgap for 7.0.0-alpha-1 is to exclude groovy-fx from the 
> distribution's {{lib}}. That stops the breakage without the new mechanism.
> * Separate cosmetic bug seen above: {{GroovyMain}} logs {{"Caught: {0}"}} 
> without formatting, so the literal {{{0}}} is shown.



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to