Reputation: 851
short version: I'm trying to invoke jpackage from a gradle task but the ToolProvider returns null (or better a failed Optional). This is the case on AdoptOpenJDK 14.0.0 (sdkman identifier 14.0.0.hs-adpt) as well as on Java.net (i think that's Oracle OpenJDK!?) 14.0.1 (sdkman identifier 14.0.1-open). I'm using Gradle 6.3 (but this doesn't feel like a gradle issue).
long version: I'm following this talk on jpackage, where at 12:12 the code to invoke jpackage from a build tool is displayed. (The official jpackage page also mentions: In addition to the command-line interface, jpackage is accessible via the ToolProvider API (java.util.spi.ToolProvider) under the name "jpackage".)
And still my (Kotlin) code (lying in 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)
}
}
invoked by a new Gradle task
tasks {
register("buildInstaller") {
JPackage.buildInstaller(
...
)
dependsOn("build")
}
}
fails with stating
> Could not create task ':buildInstaller'.
> jpackage not found (expected JDK version: 14 or above, detected: 14.0.1)
I should add that I have no issues invoking jpackage from the command line.
UPDATE: I verified that this has nothing to do with either Kotlin or Gradle. This basic Java-14 program produces the same exception:
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");
}
}
Solution: (incorporating Slaw's answer) Since jpackage is in "incubating" and therefor not readily available for my non-modular application I decided to invoke it by creating a new process:
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
}
}
}
and my task definition looks like this:
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(
...
)
}
}
}
I can't execute the task from within IntelliJ since it seems to invoke Gradle with the JDK11 it itself comes bundled, but at least IntelliJ can compile the build script itself (since the version check is in the doLast-block and not directly in the register-block). Alternatively you can change the JDK that IntelliJ uses to invoke Gradle, scroll down to Slaw's comment under his answer to see how.
Btw: I'm pretty sure Gradle version 6.3 is a hard requirement for this to work, since it's the first Gradle version to be compatible with Java 14.
Upvotes: 4
Views: 544
Reputation: 45806
The problem is related to JEP 11: Incubator Modules. That JEP states:
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.
In Java 14, and probably for the next few releases, the jpackage tool is contained in a module named jdk.incubator.jpackage
. From the name's prefix, we can see that module is an incubator module. The same JEP later states:
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 specifyrequires
orrequires 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.)
Since incubator modules do not participate in service binding, the jdk.incubator.jpackage
module is not resolved at runtime. This unfortunately means that the tool can not be located via ToolProvider
unless you either:
--add-modules jdk.incubator.jpackage
,requires jdk.incubator.jpackage;
directive in the module-info file.Since you're attempting to invoke jpackage from a Gradle task I presume option two is not feasible. However, the first option does seem to be viable; when executing Gradle you can specify an appropriate org.gradle.jvmargs
system property. For example:
./gradlew "-Dorg.gradle.jvmargs=--add-modules=jdk.incubator.jpackage" buildInstaller
You don't necessarily have to type that in the command-line every time. Check out this Gradle documentation to see where else you can define that system property. Might be able to do something through your IDE as well, though not sure.
That said, it should also be possible to invoke jpackage
as another process using an Exec
task. For example:
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 */)
}
}
Upvotes: 3