通过 Grab 添加的外部库的可选依赖项不可用

Posted

技术标签:

【中文标题】通过 Grab 添加的外部库的可选依赖项不可用【英文标题】:Optional dependencies of external library added via Grab not available 【发布时间】:2020-09-04 20:03:37 【问题描述】:

无法在 Jenkins 的共享库中正确使用具有可选依赖项的外部第三方库。

我有一个共享库,它使用Commons Configurations 2 来读取各种配置文件,主要是作为 YAML 文档编写的。

Commons 配置使用 SnakeYAML 读取 YAML 文档,对 SnakeYAML 的依赖定义为可选,如下所示:

 <dependency>
      <groupId>org.yaml</groupId>
      <artifactId>snakeyaml</artifactId>
      <version>1.26</version>
      <optional>true</optional>
 </dependency>      

根据documentation of Maven how optional dependencies work,默认情况下不会将可选依赖项添加到类路径中。如果一个人想要依赖于可选依赖的库的一部分,它必须将此依赖添加到他自己的 POM 中。

由于我打算将 Commons Configuration 2 与 SnakeYAML 结合使用,因此我在 vars/readConfig.groovy 中定义了以下变量,如下所示:

@Grapes([
  @Grab(group = "org.apache.commons", module = "commons-configuration2", version = "2.7"),
  @Grab(group = "org.yaml", module = "snakeyaml", version = "1.26")
])
import org.apache.commons.configuration2.BaseConfiguration
import org.apache.commons.configuration2.Configuration
import org.apache.commons.configuration2.YAMLConfiguration

def call() 
    Configuration config = new BaseConfiguration();
    YAMLConfiguration yamlConfiguration = new YAMLConfiguration();  

从共享库调用 readConfig() 会生成带有以下消息的 java.lang.ClassNotFoundException

java.lang.ClassNotFoundException: org.yaml.snakeyaml.DumperOptions
    at jenkins.util.AntClassLoader.findClassInComponents(AntClassLoader.java:1387)
    at jenkins.util.AntClassLoader.findClass(AntClassLoader.java:1342)
    at jenkins.util.AntClassLoader.loadClass(AntClassLoader.java:1089)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:352)
    at java.lang.Class.getDeclaredMethods0(Native Method)
    at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
    at java.lang.Class.privateGetPublicMethods(Class.java:2902)
    at java.lang.Class.getMethods(Class.java:1615)
    at java.beans.Introspector.getPublicDeclaredMethods(Introspector.java:1336)
    at java.beans.Introspector.getTargetMethodInfo(Introspector.java:1197)
    at java.beans.Introspector.getBeanInfo(Introspector.java:426)
    at java.beans.Introspector.getBeanInfo(Introspector.java:173)
    at groovy.lang.MetaClassImpl$15.run(MetaClassImpl.java:3313)
    at java.security.AccessController.doPrivileged(Native Method)
    at groovy.lang.MetaClassImpl.addProperties(MetaClassImpl.java:3311)
    at groovy.lang.MetaClassImpl.initialize(MetaClassImpl.java:3288)
    at org.codehaus.groovy.reflection.ClassInfo.getMetaClassUnderLock(ClassInfo.java:260)
    at org.codehaus.groovy.reflection.ClassInfo.getMetaClass(ClassInfo.java:302)
    ...

我还检查了目录 ~/.groovy/grapes 是否存在所有需要的 jar,它们都在那里。

jenkins@cb765137c926:~/.groovy$ find . -name "*.jar"
./grapes/commons-logging/commons-logging/jars/commons-logging-1.2.jar
./grapes/org.apache.commons/commons-configuration2/jars/commons-configuration2-2.7.jar
./grapes/org.apache.commons/commons-lang3/jars/commons-lang3-3.9.jar
./grapes/org.apache.commons/commons-text/jars/commons-text-1.8.jar
./grapes/org.yaml/snakeyaml/jars/snakeyaml-1.26.jar

为了进行交叉检查,我编写了以下 Groovy 脚本,并且能够在我的计算机上成功执行它。

@Grapes([
  @Grab(group = 'org.apache.commons', module = 'commons-configuration2', version = '2.7'),
  @Grab(group = 'org.yaml', module = 'snakeyaml', version = '1.26'),
  @GrabConfig(systemClassLoader = true)
])

import org.apache.commons.configuration2.*

println("Start")

YAMLConfiguration y = new YAMLConfiguration()

println y

所以,我无法猜测这个问题的原因,因为我对 Jenkins 的内部结构不太熟悉。但是很高兴知道是否有办法让它按预期工作。

【问题讨论】:

尝试导入并创建org.yaml.snakeyaml.DumperOptions 嗨@daggett,可以创建org.yaml.snakeyaml.DumperOptions,也可以创建org.apache.commons.text.similarity.JaroWinklerDistance 的实例。这个类来自 Commons Text,它是 Commons Configurations 2 的非可选依赖。 在 Jenkins bugtracker 中针对这个问题创建了一个问题:issues.jenkins-ci.org/browse/JENKINS-62387 这可能是commons-configuration2类加载器的问题 【参考方案1】:

@Grab 在沙箱中不起作用,因此这只适用于全局配置中定义的共享库。即使这样做,您也很可能会遇到类加载问题,因为 Jenkins 为您的脚本创建运行环境的方式。

Cloudbees 建议不要在共享库中使用@Grab,原因是出于这个原因,也是出于性能原因。当你让它工作时,这段代码将做的是下载这些依赖项,在阻塞调用中,通过互联网,在该主节点上的每个构建开始时。如果网络很慢,您可以通过这种方式很快耗尽执行程序。

最佳做法是安装Pipeline Utility Steps Plugin,它提供了readYaml 方法。它只是基于 SnakeYaml 源,它的工作原理是一样的。

【讨论】:

嗨@Mzzl,谢谢你的回答。我运行了几个测试,库只会下载一次。初始下载后,它保存在.groovy/grapes 目录中,并且每次后续调用都使用下载的库工件。【参考方案2】:

Grab 建立在 Ivy 之上。我们在 Ivy 配置中获取可选依赖项的方法是将 optional 添加到注释的元素 conf 中。

@Grab(group='org.apache.commons', module='commons-configuration2', 
      version='2.7', conf='default,optional')

【讨论】:

以上是关于通过 Grab 添加的外部库的可选依赖项不可用的主要内容,如果未能解决你的问题,请参考以下文章

CLion:通过添加源文件启用外部库的调试

Odoo 14 CE 中的可选依赖项

在 JS 中工作时,在 Typescript 项目中添加本地依赖项不起作用

失败的可选依赖项/ chokidar / fsevents:

搜索依赖项不返回任何内容

npm 的“跳过失败的可选依赖项”是啥意思?