I have a Groovy library made available as a Global shared library:
package com.example
@Grab(group="org.apache.httpcomponents", module="httpclient", version="[4.5.3,)")
import org.apache.http.HttpHost
import org.apache.http.impl.client.HttpClients
class MyClass implements Serializable {
static def run() {
return HttpClients.custom()
.setProxy(new HttpHost("proxy.example.com", 3128))
.build()
}
static def debug() {
return ("""
this: ${this.classLoader.class.toString()} ${this.classLoader.hashCode().toString()}
HttpHost: ${HttpHost.class.classLoader.class.toString()} ${HttpHost.class.classLoader.hashCode()}
HttpClients: ${HttpClients.class.classLoader.class.toString()} ${HttpClients.class.classLoader.hashCode()}
""")
}
}
And a Jenkins scripted pipeline job using this library:
@Library('example') _
node {
echo "${com.example.MyClass.debug()}"
com.example.MyClass.run()
}
When the job is run, I get the following output from debug()
, followed by an error from run()
:
this: class org.jenkinsci.plugins.workflow.cps.CpsGroovyShell$CleanGroovyClassLoader 765101363
HttpHost: class hudson.ClassicPluginStrategy$AntClassLoader2 804623541
HttpClients: class hudson.ClassicPluginStrategy$AntClassLoader2 1870591909
hudson.remoting.ProxyException: groovy.lang.MissingMethodException: No signature of method: org.apache.http.impl.client.HttpClientBuilder.setProxy() is applicable for argument types: (org.apache.http.HttpHost) values: [http://proxy.example.com:3128]
Possible solutions: setProxy(org.apache.http.HttpHost)
The following classes appear as argument class and as parameter class, but are defined by different class loader
It is clear to me that some Jenkins plugin already has a dependency on httpcomponents, and the following seem true:
- My
@Grab
annotation caused the requested version of httpclient to be downloaded (as observed in~/.groovy/grapes
). - But, that version is not being loaded or used by the Groovy library, but some other version that is a dependency of some Jenkins plugin.
- And, even more annoyingly,
HttpHost
andHttpClients
are being loaded from different classloaders such that I cannot even use the plugin's version that leaked into my Groovy code's classloader.
Versions
- Jenkins: 2.20
Plugins Versions
- Groovy: 2.0
- Pipeline: 2.5
- Pipeline: Groovy: 2.30
- Pipeline: Shared Groovy Libraries: 2.8
Is there a way to run my Groovy in a classloader that is isolated from that of Jenkins plugins? How do Jenkins and the Groovy shared library code organize the classloaders? Is this leaking of classes pulled in by plugins intentional?
Is this a bug or am I doing something wrong? I realize I am several versions behind on Jenkins, so that's one thing to try.
As is, the system is unusable unless I'm lucky enough to have dependencies that no other plugin has, or lucky to be compatible with whatever version the classloader happens to find.