使用 Gradle 将依赖项添加到运行时映像

Posted

技术标签:

【中文标题】使用 Gradle 将依赖项添加到运行时映像【英文标题】:Add dependecies to runtime image with Gradle 【发布时间】:2019-05-14 16:58:32 【问题描述】:

我不知道如何添加依赖。我的模块需要 Log4j。我向模块信息添加了要求。我还添加了 gradle 依赖项。我可以运行项目,但无法创建自定义运行时映像。

plugins 
    id 'java'
    id 'application'
    id 'org.openjfx.javafxplugin' version '0.0.5'


group 'eu.sample'
version '2.0'


repositories 
    mavenCentral()


javafx 
    modules = [ 'javafx.controls', 'javafx.fxml' ]


mainClassName = "$moduleName/eu.sample.app.Main"

def lin_java_home = hasProperty('org.gradle.java.home') ? getProperty('org.gradle.java.home') : System.getenv('JAVA_HOME')
def lin_fx_jmods = hasProperty('linux.fx.mods') ? getProperty('linux.fx.mods') : System.getenv('PATH_TO_FX_MODS_LIN')

def win_java_home = hasProperty('windows.java.home') ? getProperty('windows.java.home') : System.getenv('JAVA_HOME_WIN')
def win_fx_jmods = hasProperty('windows.fx.mods') ? getProperty('windows.fx.mods') : System.getenv('PATH_TO_FX_MODS_WIN')

dependencies 
    compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.1'


task jlink(type: Exec) 
    dependsOn 'clean'
    dependsOn 'jar'

    workingDir 'build'

    if (lin_java_home == null) 
        throw new RuntimeException("java_home is not defined.")
    
    if (lin_fx_jmods == null) 
        throw new RuntimeException("fx_jmods is not defined.")
    
    commandLine "$lin_java_home/bin/jlink", '--module-path', "libs$File.pathSeparatorChar$lin_fx_jmods",
            '--add-modules', "$moduleName", '--output', "$moduleName", '--strip-debug',
            '--compress', '2', '--no-header-files', '--no-man-pages'


task jlinkWin(type: Exec) 
    dependsOn 'clean'
    dependsOn 'jar'


    workingDir 'build'

    if (win_java_home == null) 
        throw new RuntimeException("java_home is not defined.")
    
    if (win_fx_jmods == null) 
        throw new RuntimeException("fx_jmods is not defined.")
    
    commandLine "$lin_java_home/bin/jlink", '--module-path', 
            "$win_java_home/jmods$File.pathSeparatorCharlibs$File.pathSeparatorChar$win_fx_jmods",
            '--add-modules', "$moduleName", '--output', "$moduleName", '--strip-debug',
            '--compress', '2', '--no-header-files', '--no-man-pages'

当我触发任务 jlink 时,我得到:

错误:未找到模块 org.apache.logging.log4j,需要 应用

我检查了构建中的 libs 目录,没有 log4j jar。如何告诉 gradle 添加依赖到 jlink 任务?

【问题讨论】:

为什么不使用plugin? 【参考方案1】:

问题

这就是您在 jlink 任务中的内容:

'--module-path', "libs$File.pathSeparatorChar$fx_jmods"

Whats 表示您正在从以下位置添加依赖项:

libs:基本上是你的模块的 jar helloFX。这是jar 任务的结果,该任务仅包括项目的类和资源,而不包括其依赖项。

fx_jmods:这是 JavaFX jmod 的路径。

但是当你运行时,你会得到这个错误:

错误:未找到模块 org.apache.logging.log4j,应用程序需要

该错误表示jlink 命令的module-path 不完整,无法解析所有必需的依赖项。如上所述,我们只包括 module.jar 和 JavaFX (jmods) jar,但不包括 log4j.jar

所以我们需要找到一种方法将该 jar 添加到模块路径中。

有几种可能的解决方案可以在您的自定义映像中包含第三方依赖项。

解决方案 1

我们必须修改jlink 任务,以在我们的运行时配置中包含现有的依赖项。

最直接的解决方案是通过 gradle 在本地 .gradle 存储库中查找 logj4 jar 的存储位置。

'--module-path', "libs$File.pathSeparatorChar$fx_jmods: \
   /Users/<user>/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-api/2.11.1/268..a10/log4j-api-2.11.1.jar: \
   /Users/<user>/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-core/2.11.1/59..e4/log4j-core-2.11.1.jar"

虽然此解决方案有效,但它当然不是最方便的,因为它取决于用户的本地路径。

解决方案 2

一个更好的解决方案,可以添加一个任务来复制运行时依赖项,直接由 gradle 解析到libs 文件夹中,例如:

task libs(type: Copy) 
    into 'build/libs/'
    from configurations.runtime

然后从jlink 任务中调用此任务:

task jlink(type: Exec) 
    dependsOn 'clean'
    dependsOn 'jar'
    dependsOn 'libs'
    ...

如果您运行./gradlew jlink 并检查libs 文件夹,您应该会发现类似这样的内容:

build/libs/hellofx.jar
build/libs/javafx-base-11.0.1.jar
build/libs/javafx-base-11.0.1-$platform.jar
build/libs/javafx-graphics-11.0.1.jar
build/libs/javafx-graphics-11.0.1-$platform.jar
build/libs/javafx-controls-11.0.1.jar
build/libs/javafx-controls-11.0.1-$platform.jar
build/libs/javafx-fxml-11.0.1.jar
build/libs/javafx-fxml-11.0.1-$platform.jar
build/libs/log4j-api-2.11.1.jar
build/libs/log4j-core-2.11.1.jar

$platform 是您的运行平台。

请注意,libs 文件夹现在包含 所有 模块路径所需的依赖项,并且 JavaFX-*-$platform jars 包含本机库,因此,在 module-path 选项中不再需要 jmods。这样就足够了:

'--module-path', "libs"

所以你的命令行将是:

commandLine "$java_home/bin/jlink", '--module-path', "libs",
        '--add-modules', "$moduleName", '--output', "$moduleName", '--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages'

您现在可以成功运行jlink 任务了。

解决方案 3

正如@madhead 评论所建议的,您可以为jlink 任务使用另一个插件:所谓的 badass-jlink-plugin。

将您的构建修改为:

plugins 
    id 'application'
    id 'org.openjfx.javafxplugin' version '0.0.5'
    id 'org.beryx.jlink' version '2.1.8'


repositories 
    mavenCentral()


dependencies 
    compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.1'


javafx 
    modules = ['javafx.controls', 'javafx.fxml']


mainClassName = "$moduleName/eu.sample.app.Main"

jlink 
    options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
    launcher 
        name = 'helloFX'
    

另请注意,该插件可以选择为其他平台创建图像(请参阅targetPlatform)。

编辑 log4j

正如 cmets 中所述,log4j 依赖项还不能很好地与模块配合使用。在 Apache 问题跟踪器中有一个 open issue:

目前基于给定的自动模块,您不能在您喜欢使用 jlink 生成运行时映像的项目中使用 log4j-core。

我已将此添加到我的主类中:

LogManager.getLogger(MainApp.class).info("hellofx!");

我已将log4j2.xml 文件添加到src/main/resources

运行./gradlew run,工作:

> Task :run
18:24:26.362 [JavaFX Application Thread] INFO  eu.sample.app.Main - hellofx!

但是,从解决方案 2 运行 jlink 会创建自定义图像,我可以验证是否包含 xml 文件,但是从图像运行时我得到:

build/hellofx/bin/java -m hellofx/eu.sample.app.Main
ERROR StatusLogger Log4j2 could not find a logging implementation. \
    Please add log4j-core to the classpath. Using SimpleLogger to log to the console...

如上所述,使用解决方案 3 中的插件运行 jlink createMergedModule 任务中失败

 error: package org.apache.logging.log4j.spi is not visible
   provides org.apache.logging.log4j.spi.Provider with org.apache.logging.log4j.core.impl.Log4jProvider;

[参见 EDIT(2),自 2.1.9 版起已修复此问题]

另类

此时,可能的替代方法是使用 Slf4j。插件文档中列出了一个sample,它成功使用了它。

总之,这是必需的:

Gradle 文件:

dependencies 
    compile 'org.slf4j:slf4j-api:1.8.0-beta2'
    compile('ch.qos.logback:logback-classic:1.3.0-alpha4') 
        exclude module: "activation"
    

模块信息:

requires org.slf4j;
requires ch.qos.logback.classic;
requires java.naming;

主类:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

private static final Logger logger = LoggerFactory.getLogger(MainApp.class);

...
logger.info("hellofx!");

来自here 的logging.properties 和来自here 的logback.xml,与您的主要课程。

运行./gradlew./gradlew jlinkbuild/image/bin/HelloFX 都运行,消息被记录到控制台。

编辑(2)

在reporting badass-jlink-plugin 的问题跟踪器的问题之后,这个问题得到了解决,从 2.1.19 版本开始,它应该可以正常创建自定义图像。

由于log4j 是多版本jar,所以需要做一件事:

plugins 
    id 'application'
    id 'org.openjfx.javafxplugin' version '0.0.5'
    id 'org.beryx.jlink' version '2.1.9'


...

jlink 
    options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
    launcher 
       name = 'helloFX'
    
    forceMerge('log4j-api')     // <---- this is required for Log4j

查看完整的工作示例here。

【讨论】:

第二种解决方案似乎更加优雅和清晰。我尝试过这个。但是我在Task :createMergedModule 期间遇到了错误。 Stacktrace 说:package ....log4j.spi is not visible provides ....log4j.spi.Provider with ....log4j.core.impl.Log4jProviderackage ....log4j.message is not visible provides .log4j.message.ThreadDumpMessage.ThreadInfoFactory with ....log4j.core.message.ExtendedThreadInfoFactory; (package ....log4j.message is declared in module ....log4j, but module eu.sample.merged.module does not read it) 我正在努力解决这个问题。 我知道,我也注意到了。我已经使用这个插件几次没有问题,但是对于非模块化依赖,它可能会出现一些问题,因为它试图在它们之上创建一个合并模块...... 我已经编辑了我的答案,有这些问题。我还使用 Slf4j 添加了一个工作示例。 是的,解决方案 3 和替代方案正在运行。感谢您的宝贵时间和非常详细的回答。 很高兴知道它对您有用。我已经提交了issue 以查看jlink 插件是否可以使 Log4j 工作。

以上是关于使用 Gradle 将依赖项添加到运行时映像的主要内容,如果未能解决你的问题,请参考以下文章

查找未使用的gradle运行时依赖项

运行 Spring Boot 简单应用程序时找不到 Oracle 驱动程序

在运行时更改gradle依赖中的代码

注入 Maven 插件依赖项运行时

使用gradle的NoClassDefFound异常

无法将 Gradle 依赖项添加到我的 Codename One 项目