问题描述
我有一个 groovy 库,我将它作为 jar 文件发布到 nexus 存储库上。当我从另一个项目的 Gradle 脚本使用库中的扩展方法时,我得到一个 MissingMethodException。
static boolean containsIgnoreCase(String self,String str) {
self.toLowerCase().contains(str.toLowerCase())
}
如果在我的库中使用 "foobar".containsIgnoreCase("Foo")
调用该方法,我将收到异常。如果我改为使用 StringExtensions.containsIgnoreCase("foobar","Foo")
调用它,它可以工作,没问题。
我的猜测是,这是在没有定义扩展的 meta-inf 文件的情况下发布 Groovy 项目的问题。这是项目结构:
- Library
- src/main/
- groovy/
- (here are my sources)
- resources/meta-inf/groovy
- org.codehaus.groovy.runtime.ExtensionModule (contains details about my extension classes)
我的 ExtensionModule 文件如下所示:
moduleName=string-extensions
moduLeversion=1.0
extensionClasses=com.my.project.StringExtensions
在 build.gradle 的发布块中,我使用以下内容:
plugins {
id 'groovy'
id 'java'
id 'maven-publish'
}
//...
sourceSets {
main.groovy.outputDir = sourceSets.main.java.outputDir
test.groovy.outputDir = sourceSets.test.java.outputDir
}
//...
publishing {
publications {
library(MavenPublication) {
from components.java
}
}
//...
}
当我在另一个项目的 Gradle 构建脚本中使用这个库时,我需要在我的出版物中包含什么才能正确注册扩展方法?我在我的构建脚本中添加了依赖项的类路径/repo url,并且可以访问库的方法 - 这些只是在调用扩展方法时失败。
解决方法
坏消息:a bug in Gradle 因为 Gradle 构建脚本目前不支持 Groovy 扩展模块。
需要注意的一点: the Groovy docs 似乎是错误的,因为他们指定将模块描述符放在 META-INF/groovy/
下。当我这样做时,我什至无法在普通的 Groovy 应用程序中使用生成的库的扩展方法。我不得不将描述符放在 META-INF/services/
下才能使其工作。
这对 Gradle 用例仍然没有帮助。但是,它表明构建和发布工作正常:FWIW,我自己刚刚设置了两个小项目,我可以重现您看到的问题(使用 Gradle 6.7.1)。通过上述切换到 META-INF/services/
目录,我至少可以让扩展在 Groovy 应用程序中工作。因此,鉴于您可以在 Gradle 构建中将 Groovy 方法作为非扩展方法访问,查看问题的其余部分并切换到 META-INF/services/
,我认为您的构建包含。发布也应该正确配置。
最小复制项目
以下两个最小的复制器项目表明该出版物有效,该库可以在 Groovy 应用程序中使用,但在 Gradle 构建中失败。 Gradle Wrapper 文件未显示。
├── my_extension_lib
│ ├── build.gradle
│ └── src
│ └── main
│ ├── groovy
│ │ └── MyExtension.groovy
│ └── resources
│ └── META-INF
│ └── services
│ └── org.codehaus.groovy.runtime.ExtensionModule
└── my_groovy_app
├── build.gradle
└── src
└── main
└── groovy
└── Test.groovy
首先在 ./gradlew publish
下运行 my_extension_lib/
。然后在 ./gradlew run
下运行 my_groovy_app
。
my_extension_lib/build.gradle
plugins {
id 'groovy'
id 'maven-publish'
}
group = 'com.example'
version = '1.0'
repositories {
jcenter()
}
dependencies {
implementation 'org.codehaus.groovy:groovy-all:2.4.15'
}
publishing {
publications {
library(MavenPublication) {
from components.java
}
}
repositories {
maven {
name = 'test'
url = 'file:///tmp/my_test_repo'
}
}
}
my_extension_lib/src/main/groovy/MyExtension.groovy
class MyExtension {
static boolean containsIgnoreCase(String self,String str) {
self.toLowerCase().contains(str.toLowerCase())
}
}
my_extension_lib/src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule
moduleName=My Test
moduleVersion=1.0
extensionClasses=MyExtension
my_groovy_app/build.gradle
/* Test extension in Gradle */
buildscript {
repositories {
maven {
url = 'file:///tmp/my_test_repo'
}
jcenter()
}
dependencies {
classpath 'com.example:my_extension_lib:1.0'
}
}
// works,i.e.,the publication is ok
println(MyExtension.containsIgnoreCase("foobar","Foo"))
// doesn’t work due to Gradle bug
//println("foobar".containsIgnoreCase('Foo'))
/* Test extension in Groovy application */
apply plugin: 'groovy'
apply plugin: 'application'
application {
mainClass = 'Test'
}
repositories {
maven {
url = 'file:///tmp/my_test_repo'
}
jcenter()
}
dependencies {
implementation 'org.codehaus.groovy:groovy-all:2.4.15'
implementation 'com.example:my_extension_lib:1.0'
}
my_groovy_app/src/main/groovy/Test.groovy
class Test {
static void main(String... args) {
// works,extension library JAR was published correctly
println("foobar".containsIgnoreCase('Foo'))
}
}
,
不是真的和答案 - 只是另一部分信息。
groovy version 2.5+ 兼顾:META-INF/services
和 META-INF/groovy
在 groovy 2.5 之前只处理了 META-INF/services
在 GroovySystem.java 中静态创建 MetaClassRegistry 单例时,groovy 仅扫描名为 org.codehaus.groovy.runtime.ExtensionModule
的资源一次
要使用这种方法以某种方式实现它,请检查它是如何在 GrapeIvy.groovy 类中完成的
我认为为gradle创建插件并使用metaClass定义自定义方法要容易得多
String.metaClass.up={ delegate.toUpperCase() }
task x{
doLast{
println( "hello".up() )
}
}