简体   繁体   English

如何在 jar 中包含 Groovy 扩展方法?

[英]How do I include Groovy Extension methods in jar?

I have a groovy library that I publish as a jar file on a nexus repository.我有一个 groovy 库,我将它作为 jar 文件发布在一个 nexus 存储库上。 When I use extension methods in the library from another project's Gradle script, I get a MissingMethodException .当我从另一个项目的 Gradle 脚本中使用库中的扩展方法时,我得到一个MissingMethodException

As a reference, say I have a String extension method, such as:作为参考,假设我有一个 String 扩展方法,例如:

static boolean containsIgnoreCase(String self, String str) {
    self.toLowerCase().contains(str.toLowerCase())
}

If in my library I call the method using "foobar".containsIgnoreCase("Foo") , I will get the exception.如果在我的库中我使用"foobar".containsIgnoreCase("Foo")调用该方法,我将得到异常。 If I instead call it using StringExtensions.containsIgnoreCase("foobar", "Foo") , it works, no problem.如果我改为使用StringExtensions.containsIgnoreCase("foobar", "Foo")调用它,它可以工作,没问题。

My guess is that this is an issue with publishing the Groovy project without the META-INF file that defines the extensions.我的猜测是,在没有定义扩展的 META-INF 文件的情况下发布 Groovy 项目是一个问题。 Here is the project structure:这是项目结构:

- Library
  - src/main/
    - groovy/ 
      - (here are my sources)
    - resources/META-INF/groovy
      - org.codehaus.groovy.runtime.ExtensionModule (contains details about my extension classes)

My ExtensionModule file looks like this:我的 ExtensionModule 文件如下所示:

moduleName=string-extensions
moduleVersion=1.0
extensionClasses=com.my.project.StringExtensions

In my publish block in build.gradle , I use the following: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
        }
    }

    //...
}

What do I need to include in my publication in order to get the extension methods correctly registered when I use this library in another project's Gradle build script?当我在另一个项目的 Gradle 构建脚本中使用此库时,我需要在我的出版物中包含什么才能正确注册扩展方法? I have added the classpath/repo url for the dependency in my buildscript, and can access methods of the library - these just fail when calling an extension method.我已经为我的构建脚本中的依赖项添加了类路径/repo url,并且可以访问库的方法 - 这些只是在调用扩展方法时失败。

Bad news: there is a bug in Gradle because of which Gradle build scripts don't support Groovy extension modules at the moment.坏消息:Gradle 中存在一个错误,因为 Gradle 构建脚本目前不支持 Groovy 扩展模块。

One thing to note: the Groovy docs seem to be wrong in that they specify to put the module descriptor under META-INF/groovy/ .需要注意的一件事: Groovy 文档似乎是错误的,因为他们指定将模块描述符放在META-INF/groovy/下。 When I do that, I can't even use the extension method of the resulting library in a plain Groovy application.当我这样做时,我什至不能在普通的 Groovy 应用程序中使用生成的库的扩展方法。 I had to put the descriptor under META-INF/services/ instead to make it work.我不得不将描述符放在META-INF/services/下才能使其工作。

That still doesn't help with the Gradle use case.这对 Gradle 用例仍然没有帮助。 However, it shows that building and publishing works correctly: FWIW, I've just set up two tiny projects myself and I can reproduce the issue that you're seeing (with Gradle 6.7.1).但是,它表明构建和发布工作正常:FWIW,我自己刚刚建立了两个小项目,我可以重现您看到的问题(使用 Gradle 6.7.1)。 With the mentioned switch to the META-INF/services/ directory, I could at least get the extension to work in a Groovy application.通过上面提到的切换到META-INF/services/目录,我至少可以让扩展在 Groovy 应用程序中工作。 So, given that you can access the Groovy methods as non-extension methods in your Gradle build, looking at the rest of your question and by switching to META-INF/services/ , I would suppose that your build incl.因此,鉴于您可以在 Gradle 构建中访问 Groovy 方法作为非扩展方法,查看问题的 rest 并切换到您的构建META-INF/services/我会假设您的构建/ the publication should be configured correctly, too.发布也应该正确配置。

Minimal Reproducer Projects最小复制项目

The following two minimal reproducer projects show that the publication works, that the library can be used in a Groovy application and that it fails in a Gradle build.以下两个最小复制器项目表明该出版物有效,该库可用于 Groovy 应用程序,并且在 Gradle 构建中失败。 Gradle Wrapper files are not shown. Gradle 包装文件未显示。

├── 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

First run ./gradlew publish under my_extension_lib/ .首先在my_extension_lib/下运行./gradlew publish Then run ./gradlew run under my_groovy_app .然后在my_groovy_app下运行./gradlew run

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, i.e., extension library JAR was published correctly
      println("foobar".containsIgnoreCase('Foo'))
  }
}

not really and answer - just another portion of information.不是真的和回答 - 只是信息的另一部分。

groovy version 2.5+ takes care about both: META-INF/services and META-INF/groovy groovy 2.5+ 版同时兼顾: META-INF/servicesMETA-INF/groovy

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

before groovy 2.5 only META-INF/services was processed在 groovy 2.5 之前,仅处理META-INF/services

groovy scans resources named org.codehaus.groovy.runtime.ExtensionModule only once when statically creating MetaClassRegistry singleton in GroovySystem.java groovy scans resources named org.codehaus.groovy.runtime.ExtensionModule only once when statically creating MetaClassRegistry singleton in GroovySystem.java

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


to implement it somehow using this approach check how it's done in GrapeIvy.groovy class使用这种方法以某种方式实现它检查它是如何在 GrapeIvy.groovy class 中完成的

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


I think it's much easier to create a plugin for gradle and use metaClass to define custom methods我认为为 gradle 创建插件并使用 metaClass 定义自定义方法要容易得多

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


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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM