【问题标题】:can't find jpackage via ToolProvider无法通过 ToolProvider 找到 jpackage
【发布时间】:2020-04-19 15:38:15
【问题描述】:

短版:我正在尝试从 gradle 任务调用 jpackage,但 ToolProvider 返回 null(或者更好的是失败的 Optional)。 AdoptOpenJDK 14.0.0(sdkman 标识符 14.0.0.hs-adpt)和 Java.net(我认为那是 Oracle OpenJDK!?) 14.0.1(sdkman 标识符 14.0.1-open)就是这种情况。我正在使用 Gradle 6.3(但这感觉不像是 gradle 问题)。

长版: 我正在关注这个talk on jpackage,在 12:12 显示从构建工具调用 jpackage 的代码。 (official jpackage page 还提到:除了命令行界面,jpackage 还可以通过名为“jpackage”的 ToolProvider API (java.util.spi.ToolProvider) 访问。) p>

仍然是我的 (Kotlin) 代码(位于 buildSrc/src/main/kotlin 中)

object JPackage {
  fun buildInstaller( 
    ...
  ): Int {
    val jpackageTool: ToolProvider = ToolProvider.findFirst("jpackage").orElseThrow {
      val javaVersion: String = System.getProperty("java.version")
      IllegalStateException(
        "jpackage not found (expected JDK version: 14 or above, detected: $javaVersion)"
      )
    }
    val arguments: Array<String> = ...
    return jpackageTool.run(System.out, System.err, *arguments)
  }
}

由新的 Gradle 任务调用

tasks {
  register("buildInstaller") {
    JPackage.buildInstaller(
      ...
    )
    dependsOn("build")
  }
}

声明失败

> Could not create task ':buildInstaller'.
> jpackage not found (expected JDK version: 14 or above, detected: 14.0.1)

我应该补充一点,从命令行调用 jpackage 没有问题。

更新:我证实这与 Kotlin 或 Gradle 无关。这个基本的 Java-14 程序产生了同样的异常:

public class Main {
    public static void main(String[] args) {
        java.util.spi.ToolProvider.findFirst("jpackage").orElseThrow(() -> {
            String javaVersion = System.getProperty("java.version");
            return new IllegalStateException("jpackage not found (expected JDK version: 14 or above, detected: " + javaVersion + ")");
        });
        System.out.println("success");
    }
}

解决方案:(结合 Slaw 的回答) 由于 jpackage 处于“孵化”阶段,因此我的非模块化应用程序不容易使用,我决定通过创建一个新进程来调用它:

object JPackage {
  fun buildInstaller( 
    ...
  ): Int {
    val arguments: Array<String> = ...
    return execJpackageViaRuntime(arguments)
  }

  private fun execJpackageViaRuntime(arguments: Array<String>): Int {
    val cmdAndArgs = ArrayList<String>(arguments.size+1).let {
      it.add("jpackage")
      it.addAll(arguments)
      it.toTypedArray()
    }
    return try {
      val process: Process = Runtime.getRuntime().exec(cmdAndArgs)
      process.waitFor(3L, TimeUnit.MINUTES)
      return process.exitValue()
    } catch (e: Exception) {
      1
    }
  }
}

我的任务定义如下所示:

tasks {
    register("buildInstaller") {
        dependsOn("build")

        doLast {
            if (JavaVersion.current() < JavaVersion.VERSION_14) {
                throw GradleException("Require Java 14+ to run 'jpackage' (currently ${JavaVersion.current()})")
            }
            JPackage.buildInstaller(
                ...
            )
        }
    }
}

我无法从 IntelliJ 中执行任务,因为它似乎使用 JDK11 调用 Gradle,它本身捆绑在一起,但至少 IntelliJ 可以编译构建脚本本身(因为版本检查在 doLast-block 中而不是直接在寄存器块中)。或者,您可以更改 IntelliJ 用来调用 Gradle 的 JDK,向下滚动到 Slaw 在他的答案下的评论,看看如何。

顺便说一句:我很确定 Gradle 版本 6.3 是这个工作的硬性要求,因为它是第一个与 Java 14 兼容的 Gradle 版本。

【问题讨论】:

    标签: java kotlin gradle java-14 jpackage


    【解决方案1】:

    问题与JEP 11: Incubator Modules 有关。该 JEP 声明:

    孵化器模块由其模块名称中的 jdk.incubator. 前缀标识,无论模块是导出孵化 API 还是包含孵化工具。

    在 Java 14 中,可能在接下来的几个版本中,jpackage 工具包含在一个名为 jdk.incubator.jpackage 的模块中。从名称的前缀中,我们可以看出该模块是一个孵化器模块。同一个 JEP 后来指出:

    孵化器模块是标准 JDK 构建生成的 JDK 运行时映像的一部分。但是,默认情况下,孵化器模块为类路径上的应用程序解析。 此外,默认情况下,孵化器模块参与类路径或模块路径上的应用程序的服务绑定 [强调]。

    类路径上的应用程序必须使用--add-modules 命令行选项来请求解析孵化器模块。作为模块开发的应用程序可以直接在孵化器模块上指定requiresrequires transitive 依赖关系[sic]。 (一些孵化器模块,例如提供命令行工具的模块,可能会放弃导出任何包,而是提供服务实现,以便可以以编程方式访问工具 [sic]。通常不建议要求一个不导出包的模块,但在这种情况下它是必要的,以便解析孵化器模块,并让其服务提供者参与服务绑定。)

    由于孵化器模块不参与服务绑定,jdk.incubator.jpackage 模块在运行时不会被解析。不幸的是,这意味着无法通过ToolProvider 找到该工具,除非您:

    1. 使用--add-modules jdk.incubator.jpackage 启动Java 进程,
    2. 或者使您的代码模块化并在模块信息文件中包含requires jdk.incubator.jpackage; 指令。

    由于您尝试从 Gradle 任务调用 jpackage,我认为选项二是不可行的。然而,第一种选择似乎是可行的。执行 Gradle 时,您可以指定适当的 org.gradle.jvmargs 系统属性。例如:

    ./gradlew "-Dorg.gradle.jvmargs=--add-modules=jdk.incubator.jpackage" buildInstaller
    

    您不必每次都在命令行中输入。查看this Gradle documentation 以了解您还可以在哪里定义该系统属性。或许也可以通过您的 IDE 做一些事情,但不确定。

    也就是说,也应该可以使用Exec 任务将jpackage 作为另一个进程调用。例如:

    tasks {
        val build by existing
    
        register<Exec>("buildInstaller") {
            if (JavaVersion.current() < JavaVersion.VERSION_14) {
                throw GradleException("Require Java 14+ to run 'jpackage'")
            }
            dependsOn(build)
    
            executable = "jpackage"
            args(/* your args */)
        }
    }
    

    【讨论】:

    • 谢谢,现在可以使用了!参数逻辑非常复杂,所以我最终还是调用了“JPackage.buildInstaller”,但是该代码现在调用了“Runtime.getRuntime().exec(cmdAndArgs)”(您的方法的问题是 IntelliJ 似乎调用了 gradle Java11,即使项目配置为使用 Java14。所以我的 IntelliJ 无法理解 buildscript,因为版本测试是在编译时执行的。虽然我把它移到了 doLast 块中,所以只要我执行来自命令行的 gradle 任务,我很好。)
    • 关于 IntelliJ 在 Gradle 中使用错误的 JDK,请转到 Settings → Build, Execution, Deployment → Build Tools → Gradle 并确保“Gradle JVM”设置为使用项目的 SDK。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-02-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多