无法通过 ToolProvider 找到 jpackage

can't find jpackage via ToolProvider

简短版本: 我正在尝试从 gradle 任务调用 jpackage,但 ToolProvider returns 为 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, where at 12:12 the code to invoke jpackage from a build tool is displayed. (The official jpackage page还提到:除了命令行界面,jpackage可以通过名称 "jpackage".)

下的 ToolProvider API (java.util.spi.ToolProvider)

还有我的 (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 在 "incubating" 中,因此我的非模块化应用程序不容易使用,我决定通过创建一个新进程来调用它:

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 块中而不是直接在寄存器块中)。或者,您可以更改 IntelliJ 用来调用 Gradle 的 JDK,向下滚动到 Slaw 在他的回答下的评论,看看如何。

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

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

An incubator module is identified by the jdk.incubator. prefix in its module name, whether the module exports an incubating API or contains an incubating tool.

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

Incubator modules are part of the JDK run-time image produced by the standard JDK build. However, by default, incubator modules are not resolved for applications on the class path. Furthermore, by default, incubator modules do not participate in service binding for applications on the class path or the module path [emphasis added].

Applications on the class path must use the --add-modules command-line option to request that an incubator module be resolved. Applications developed as modules can specify requires or requires transitive dependences [sic] upon an incubator module directly. (Some incubator modules, such as those offering command-line tools, may forego exporting any packages, and instead provide service implementations so that tools can be accessed programatically [sic]. It is usually not recommended to require a module that exports no packages, but it is necessary in this case in order to resolve the incubator module, and have its service providers participate in service binding.)

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

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

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

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

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

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

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 */)
    }
}