使用Gradle向运行时镜像添加依赖项

6

我不知道如何添加依赖项。我的模块需要使用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.pathSeparatorChar}libs${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 任务中添加依赖项?


为什么不使用一个插件呢? - madhead
1个回答

7

问题

在您的 jlink 任务中,您拥有以下内容:

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

你正在添加的依赖项是什么意思:
- `libs`:基本上是你的模块 `helloFX` 的 jar 文件。这是 `jar` 任务的结果,它只包括项目的类和资源,但不包括其依赖项。 - `fx_jmods`:这是 JavaFX jmods 的路径。
但当你运行时,会出现以下错误:
“错误:找不到模块 org.apache.logging.log4j,需要 app”
这个错误意味着 `jlink` 命令的 `module-path` 不完整,并且无法解析所有必需的依赖项。如上所述,我们只包括模块.jar 和 JavaFX(jmods) jars,但没有 `log4j.jar`。
所以我们需要找到一种方法将该 jar 添加到模块路径中。
有几种可能的解决方案可以在自定义镜像中包含第三方依赖项。
解决方案 1:
我们必须修改 `jlink` 任务,以在我们的运行时配置中包含现有的依赖项。
最直接的解决方案是找到 logj4 jars 在本地 .gradle 存储库中存储的位置。
'--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的编辑:
如评论中所述,log4j依赖项尚未与模块兼容。在Apache问题跟踪器上有一个公开问题
目前,基于给定的自动模块,您无法在想要使用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...

正如前面提到的,使用来自第三个解决方案的插件运行 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。插件文档中列出了一个示例成功地使用了它。

总之,需要进行以下操作:

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!");

请从这里获取logging.properties文件,从这里获取logback.xml文件,并将它们与您的主类一起使用。无论是运行./gradlew run还是./gradlew jlink,或者build/image/bin/HelloFX,都可以正常工作并将消息记录到控制台。

编辑(2)

将问题报告给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
}

点击此处查看完整的IT技术示例。


第二个解决方案似乎更加优雅和清晰。我已经尝试了它,但在Task :createMergedModule期间出现了错误。堆栈跟踪显示:package ....log4j.spi is not visible provides ....log4j.spi.Provider with ....log4j.core.impl.Log4jProvider,以及ackage ....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)。我正在努力解决这个问题。 - GarryMoveOut
我知道,我也注意到了。我已经使用这个插件几次了,没有遇到任何问题,但是对于非模块化依赖项,它可能会有一些问题,因为它试图在它们之上创建一个合并的模块... - José Pereda
是的,方案3和替代方案都有效。感谢您的时间和非常详细的答案。 - GarryMoveOut
这是一个使用jlink插件和log4j 2.11.1的示例项目,链接在此处。 另请参阅我在GitHub问题上的评论。 - siordache
@siordache 太棒了!速度很快。我刚刚测试过了,对我来说运行得很好。我会编辑我的答案,包括你的插件的新版本。 - José Pereda
显示剩余2条评论

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