如何使用Gradle 4.4创建包含所有依赖项的jar文件?

4
这个问题与这个问题相关联——但是由于compile方法已经被弃用,推荐使用implementation方法。它能够加载通过compile方法声明的依赖项。但是,由于其已经被弃用,不能再使用该方法(而且当该方法被移除时会回到之前的状态)。
我有一个Gradle任务:
task fatJar(type: Jar) {
    manifest {
        attributes 'Implementation-Title': 'rpi-sense-hat-lib',
                'Implementation-Version': version,
                'Main-Class': 'io.github.lunarwatcher.pi.sensehat.Tests'
    }
    baseName = project.name
    from { 
        configurations.compile.collect {
            it.isDirectory() ? it : zipTree(it)
        }
    }
    with jar
}

只有一个依赖项,不考虑测试依赖项:

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
    testImplementation group: 'junit', name: 'junit', version: '4.12'
}

在IDE中运行正常。但是,当我部署到我的树莓派上(或者使用本地的gradlew fatJar结果),我会收到以下异常:

$ java -jar java-sense-hat-1.0a.jar
Exception in thread "main" java.lang.NoClassDefFoundError: kotlin/jvm/internal/Intrinsics
    at io.github.lunarwatcher.pi.sensehat.UtilsKt.getSenseHat(Utils.kt:18)
    at io.github.lunarwatcher.pi.sensehat.SenseHat.<init>(SenseHat.java:12)
    at io.github.lunarwatcher.pi.sensehat.Tests.main(Tests.java:9)
Caused by: java.lang.ClassNotFoundException: kotlin.jvm.internal.Intrinsics
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(Unknown Source)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(Unknown Source)
    at java.base/java.lang.ClassLoader.loadClass(Unknown Source)
    ... 3 more

这是由以下代码触发的:

return Optional.empty()

在返回一个 Optional<File> 的 Kotlin 方法中,相关问题的答案指出:

要么将 kotlin-runtime 加入 classpath 并使用 $ echo $CLASSPATH 进行验证。

或者将 kotlin-runtime 添加到 maven 中,然后使用 mvn compile assembly:single 将其汇编到 jar 文件中。

这意味着 kotlin-runtime 没有包含在 classpath 中。在回答“将 kotlin-runtime 添加到依赖项中”之前,请注意它是 stdlib 的一部分:

Kotlin Runtime(已弃用,改用 kotlin-stdlib)

我使用了 kotlin-stdlib-jdk8,在 IDE 中可以正常工作。仅供测试目的,使用 kotlin-stdlib 不会改变任何内容。

此外,将 implementation 替换为 compile 可以解决问题。

在我链接到问题顶部的帖子中,建议在 fatJar 任务中使用 runtime。因此:

task fatJar(type: Jar) {
    manifest {
        attributes 'Implementation-Title': 'rpi-sense-hat-lib',
                'Implementation-Version': version,
                'Main-Class': 'io.github.lunarwatcher.pi.sensehat.Tests'
    }
    baseName = project.name
    from {
        configurations.compile.collect {
            it.isDirectory() ? it : zipTree(it)
        }
        configurations.runtime.collect {
            it.isDirectory() ? it : zipTree(it)
        }
    }
    with jar
}

依赖项仍未包含在内,导致程序崩溃。

那么为什么不将实现添加为配置项以进行复制呢?

我尝试了,但最终得到了这个结果:

task fatJar(type: Jar) {
    manifest {
        attributes 'Implementation-Title': 'rpi-sense-hat-lib',
                'Implementation-Version': version,
                'Main-Class': 'io.github.lunarwatcher.pi.sensehat.Tests'
    }
    baseName = project.name
    from {
        configurations.compile.collect {
            it.isDirectory() ? it : zipTree(it)
        }
        configurations.runtime.collect {
            it.isDirectory() ? it : zipTree(it)
        }
        configurations.implementation.collect {
            it.isDirectory() ? it : zipTree(it)
        }
    }
    with jar
}

同时还有一个异常:

不允许直接解决配置“implementation”

考虑到:

  • compile 可以替换 implementation
  • 运行时异常是由于 Kotlin 不在类路径中导致的
  • 在 IDE 中可以正常工作
  • implementation 语句添加到 fatJar 任务会导致编译崩溃并出现单独的异常

当使用 implementation 关键字时,如何在 Gradle 4.4 中生成包含所有依赖项的 jar 包?


1
可能是使用Gradle构建带有依赖项的jar的重复问题。 - Shafin Mahmud
@ShafinMahmud 请仔细阅读问题。它不能与implementation关键字一起使用。 - Zoe stands with Ukraine
如果您重新措辞问题标题以更具体地描述问题,那将是很好的。 - Shafin Mahmud
@ShafinMahmud,你是因为标题的原因标记为重复吗?帖子的内容涵盖了整个问题,包括为什么链接的那个不起作用。 - Zoe stands with Ukraine
但是你的问题标题应该与现有的区分开来。不是针对你,但是应该有一个清晰的标题来概括你的问题,而不是如果已经有关于那个问题的内容,就使用一些通用的东西。这将帮助人们更好地帮助你。 - Shafin Mahmud
1个回答

15

你尝试过使用像Shadow插件这样的工具吗?

shadowJar {
  manifest {
     attributes 'Implementation-Title': 'rpi-sense-hat-lib',
            'Implementation-Version': version,
            'Main-Class': 'io.github.lunarwatcher.pi.sensehat.Tests'
  }
  configurations = [project.configurations.compile, project.configurations.runtime]
}

编辑:

你也可以按照这个问题的回答描述来实现此操作:

configurations {
    fatJar
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
    testImplementation group: 'junit', name: 'junit', version: '4.12'

    fatJar "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
}

task fatJar(type: Jar) {
    manifest {
        attributes 'Implementation-Title': 'rpi-sense-hat-lib',
                'Implementation-Version': version,
                'Main-Class': 'io.github.lunarwatcher.pi.sensehat.Tests'
    }
    baseName = project.name
    from {
        configurations.fatJar.collect {
            it.isDirectory() ? it : zipTree(it)
        }
    }
    with jar
}

但你需要将所有的实现依赖项重复作为fatJar依赖项。对于您当前的项目来说,这很好,因为您只有一个依赖项,但对于任何更大的项目来说,这将变得混乱不堪...

编辑2:

正如@Zoe在评论中指出的那样,这也是一种可接受的解决方案:

task fatJar(type: Jar) {
    manifest {
        attributes 'Implementation-Title': 'rpi-sense-hat-lib',
                   'Implementation-Version': version,
                   'Main-Class': 'io.github.lunarwatcher.pi.sensehat.Tests'
    }
    baseName = project.name
    from {
        configurations.runtimeClasspath.collect {
            it.isDirectory() ? it : zipTree(it)
        }
    }
    with jar
}

然而需要注意的是,根据源代码显示,runtimeClasspathruntimeOnlyruntimeimplementation 的组合,具体是否需要取决于情景——例如,您可能不需要包括 runtime 依赖项,因为它们由容器提供。


当我移除configurations并添加with jar时,它可以工作。否则它仍然会因为缺少库而崩溃。难道没有不使用插件的方法吗? - Zoe stands with Ukraine
发现了一些东西(将其作为评论发布,因为您已经有了答案)。我发现了这个。您的编辑提醒我配置在某个地方被定义,并且我还看到了一个关于java-library插件的图表(虽然我没有使用它),这让我想起可能存在继承。 - Zoe stands with Ukraine
该链接包含实现的定义和其他一些配置,但是该行本身是针对runtimeClasspath的。在fatJar中使用runtimeClasspath可以解决问题。当然,使用插件也是一种选择,但我不太喜欢。 - Zoe stands with Ukraine

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接