如何在 jar 中包含 Groovy 扩展方法? 最小复制项目

问题描述

我有一个 groovy 库,我将它作为 jar 文件发布到 nexus 存储库上。当我从另一个项目的 Gradle 脚本使用库中的扩展方法时,我得到一个 MissingMethodException

作为参考,假设我有一个String扩展方法,比如:

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/servicesMETA-INF/groovy

链接:https://github.com/apache/groovy/blob/1c358e84e427b3a6ff808533a93e1d76f4fa0d67/src/main/java/org/codehaus/groovy/runtime/m12n/ExtensionModuleScanner.java#L42

在 groovy 2.5 之前只处理了 META-INF/services

在 GroovySystem.java 中静态创建 MetaClassRegistry 单例时,groovy 仅扫描名为 org.codehaus.groovy.runtime.ExtensionModule 的资源一次

链接:https://github.com/apache/groovy/blob/1c358e84e427b3a6ff808533a93e1d76f4fa0d67/src/main/java/groovy/lang/GroovySystem.java#L37


要使用这种方法以某种方式实现它,请检查它是如何在 GrapeIvy.groovy 类中完成的

链接:https://github.com/apache/groovy/blob/1c358e84e427b3a6ff808533a93e1d76f4fa0d67/src/main/groovy/groovy/grape/GrapeIvy.groovy#L292


我认为为gradle创建插件并使用metaClass定义自定义方法要容易得多

String.metaClass.up={ delegate.toUpperCase() }


task x{
    doLast{
        println( "hello".up() )
    }
}