Exec task fails on Windows, but runs fine manually

On Windows my createPackage task fails running the tool jpackage.

Running Gradle with Java 11. The jpackage command is with JDK-13. The runtime used with jpackage is created with JDK11 jlink.
I found the cygpath method in openjdk-jfx build.gradle on GitHub. Otherwise it would fail mixing both Linux and Windows path seperators.

task createPackage(type: Exec) {
    dependsOn createRuntime
    dependsOn installDist

    def outputDir = cygpath("${buildDir}/native")
    outputs.dir(outputDir)

    def inputDir = cygpath(new File(installDist.outputs.files.singleFile, 'lib').path)
    inputs.dir(inputDir)

    def resourceDir = cygpath("${buildDir}/package")
    def runtimeImageDir = cygpath(createRuntime.outputs.files.singleFile.path)

    def installer = ""
    if (OperatingSystem.current().isLinux()) {
        installer = "rpm"
    } else if (OperatingSystem.current().isWindows()) {
        installer = "exe"
    } else if (OperatingSystem.current().isMacOsX()) {
        installer = "pkg"
    }

    commandLine 'jpackage', 'create-installer',
        '--verbose',
        '--overwrite',
        '--installer-type', "${installer}",
        '--name', project.name,
        '--app-version', applicationVersion,
        '--main-jar', jar.archiveName,
        '--jvm-args', '-Xmx2g,-Djdk.gtk.version=2',
        '--arguments', '--laf=nimbus',
        '--runtime-image', runtimeImageDir,
        '--resource-dir', resourceDir,
        '--input', inputDir,
        '--output', outputDir
}

The output:

Custom actions are attached to task ':createPackage'.
Starting process 'command 'jpackage''. Working directory: C:\cygwin64\home\build\workspace\application Command: jpackage create-installer --verbose --overwrite --installer-type exe --name application --app-version 3.0 --main-jar application-3.0-SNAPSHOT.jar --jvm-args -Xmx2g,-Djdk.gtk.version=2 --arguments --laf=nimbus --runtime-image C:/cygwin64/home/build/workspace/application/build/runtime --resource-dir C:/cygwin64/home/build/workspace/application/build/package --input C:/cygwin64/home/build/workspace/application/build/install/application/lib --output C:/cygwin64/home/build/workspace/application/build/native
:createPackage (Thread[Daemon worker Thread 5,5,main]) completed. Took 1.009 secs.

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':createPackage'.
> A problem occurred starting process 'command 'jpackage''

* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':createPackage'.
Caused by: org.gradle.process.internal.ExecException: A problem occurred starting process 'command 'jpackage''
        at org.gradle.process.internal.DefaultExecHandle.execExceptionFor(DefaultExecHandle.java:232)
        at org.gradle.process.internal.DefaultExecHandle.setEndStateInfo(DefaultExecHandle.java:209)
        at org.gradle.process.internal.DefaultExecHandle.failed(DefaultExecHandle.java:356)
        at org.gradle.process.internal.ExecHandleRunner.run(ExecHandleRunner.java:86)
        at org.gradle.internal.operations.CurrentBuildOperationPreservingRunnable.run(CurrentBuildOperationPreservingRunnable.java:42)
        ... 3 more
Caused by: net.rubygrapefruit.platform.NativeException: Could not start 'jpackage'
        at net.rubygrapefruit.platform.internal.DefaultProcessLauncher.start(DefaultProcessLauncher.java:27)
        at net.rubygrapefruit.platform.internal.WindowsProcessLauncher.start(WindowsProcessLauncher.java:22)
        at net.rubygrapefruit.platform.internal.WrapperProcessLauncher.start(WrapperProcessLauncher.java:36)
        at org.gradle.process.internal.ExecHandleRunner.startProcess(ExecHandleRunner.java:97)
        at org.gradle.process.internal.ExecHandleRunner.run(ExecHandleRunner.java:70)
        ... 4 more
Caused by: java.io.IOException: Cannot run program "jpackage" (in directory "C:\cygwin64\home\build\workspace\application"): CreateProcess error=2, The system cannot find the file specified
        at net.rubygrapefruit.platform.internal.DefaultProcessLauncher.start(DefaultProcessLauncher.java:25)
        ... 8 more
Caused by: java.io.IOException: CreateProcess error=2, The system cannot find the file specified
        ... 9 more

I am not sure what directory it complains about, but the one in the stack trace does exist.

If I take this command exactly and run manually it works fine:

jpackage create-installer --verbose --overwrite --installer-type exe --name application --app-version 3.0 --main-jar application-3.0-SNAPSHOT.jar --jvm-args -Xmx2g,-Djdk.gtk.version=2 --arguments --laf=nimbus --runtime-image C:/cygwin64/home/build/workspace/application/build/runtime --resource-dir C:/cygwin64/home/build/workspace/application/build/package --input C:/cygwin64/home/build/workspace/application/build/install/application/lib --output C:/cygwin64/home/build/workspace/application/build/native

It seems the problem is that Gradle on Windows cannot find the executable jpackage. I get same error when I change the executable name

Caused by: java.io.IOException: Cannot run program "jpackaga" (in directory "C:\cygwin64\home\build\workspace\application"): CreateProcess error=2, The system cannot find the file specified
        at net.rubygrapefruit.platform.internal.DefaultProcessLauncher.start(DefaultProcessLauncher.java:25)
        ... 8 more
Caused by: java.io.IOException: CreateProcess error=2, The system cannot find the file specified
        ... 9 more

The jpackage executable is on the path in cygwin, but not on Windows Path.
I cannot put the JDK13 on Windows Path, because then it would preceed the JAVA_HOME JDK11 path.

Don’t use ‘commandLine’ specify the executable and args instead. If you do that you can drop the cygpath, which doesn’t help you anyway. Better specify the full path and filename - don’t rely on the path and comspec. Alternatively invoke the shell explicitly.

Further notes:

Read https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Exec.html

Make sure you register the dirs and files as task’s inputs and outputs.

Drop the ornamental quotes and string interp from “${installer}”

Instead of new File() use project.file()

Using executable instead of commandLine fails just the same

executable = “/usr/bin/jpackage”

Caused by: java.io.IOException: Cannot run program "/usr/bin/jpackage" (in directory "C:\cygwin64\home\build\workspace\application"): CreateProcess error=2, The system cannot find the file specified
        at net.rubygrapefruit.platform.internal.DefaultProcessLauncher.start(DefaultProcessLauncher.java:25)
        ... 8 more
Caused by: java.io.IOException: CreateProcess error=2, The system cannot find the file specified
        ... 9 more

executable = “jpackage”

Caused by: java.io.IOException: Cannot run program "jpackage" (in directory "C:\cygwin64\home\build\workspace\application"): CreateProcess error=2, The system cannot find the file specified
        at net.rubygrapefruit.platform.internal.DefaultProcessLauncher.start(DefaultProcessLauncher.java:25)
        ... 8 more
Caused by: java.io.IOException: CreateProcess error=2, The system cannot find the file specified
        ... 9 more

Using executable instead of commandLine

executable = "jpackage"

args = [
    "create-installer",
    "--verbose",
    "--overwrite",
    "--installer-type", installer,
    "--name", project.name,
    "--app-version", applicationVersion,
    "--main-jar", jar.archiveName,
    "--jvm-args", "-Xmx2g,-Djdk.gtk.version=2",
    "--arguments", "--laf=nimbus",
    "--runtime-image", runtimeImageDir,
    "--resource-dir", resourceDir,
    "--input", inputDir,
    "--output", outputDir,
]

Instead of new File() use project.file()

project.file does not seem to support a second parameter for adding “lib” like new File(parent, child) does.

You are running on windows, is your jpackage really at the path you specify? For a start specify the full, canonical path, including the file extension.

For file() just use Unix slashes and string interpolation - Java accepts them on Windows just fine.

Please post a full example project demonstrating the problem and provide your targeted Gradle version if you would like further help.

I have made some changes, but it still fails.

My createRuntime task which executes jlink works fine.
My createPackage task which executes jpackage still fails. It still cannot find the jpackage executable.

Caused by: java.io.IOException: Cannot run program "jpackage" (in directory "C:\cygwin64\home\build\workspace\application"): CreateProcess error=2, The system cannot find the file specified
    at net.rubygrapefruit.platform.internal.DefaultProcessLauncher.start(DefaultProcessLauncher.java:25)
    ... 8 more
Caused by: java.io.IOException: CreateProcess error=2, The system cannot find the file specified
    ... 9 more

Why it can find jlink, but not jpackage I don’t know.
The executable jlink points to the same JDK running with Gradle

/usr/bin/jlink > /usr/java/jdk-11/bin/jlink

The executable jpackage points to a different JDK.

/usr/bin/jpackage > /usr/java/jdk-13/bin/jpackage

Neither of these two executables are “known” to Windows, only in Cygwin.

My packager.gradle

import org.apache.tools.ant.filters.ReplaceTokens
import org.gradle.internal.os.OperatingSystem

task processPackageResources(type: Copy) {
    def outputDir = cygpath("${buildDir}/package")
    into(outputDir)

    from ('src/main/package')

    from ('src/main/package') {
        exclude "**/*.png"
        filter ReplaceTokens, tokens: [
            projectName: project.name,
            projectVersion: project.version,
            projectDescription: project.description,
            applicationPrefix: applicationPrefix,
            applicationName: applicationName,
            applicationVersion: applicationVersion,
            applicationRelease: applicationRelease,
            vendorName: vendorName,
            vendorPackager: vendorPackager,
            vendorUrl: vendorUrl
        ]
    }
}
tasks.processResources.finalizedBy processPackageResources

task createRuntime(type: Exec) {
    def outputDir = file("${buildDir}/runtime")
    outputs.dir(outputDir)

    doFirst {
        // Because jlink does not have an overwrite option
        delete outputDir
    }

    executable = "jlink"

    args = [
        '--add-modules', 'java.base,java.desktop,java.logging,java.scripting',
        '--bind-services',
        '--no-header-files',
        '--no-man-pages',
        '--compress=2',
        '--strip-debug',
        '--output', outputDir.path
    ]
}

task createPackage(type: Exec) {
    dependsOn createRuntime
    dependsOn installDist

    def inputDir = file(installDist.outputs.files.singleFile.path + "/lib")
    inputs.dir(inputDir)

    def resourceDir = processPackageResources.outputs.files.singleFile
    inputs.dir(resourceDir)

    def runtimeImageDir = createRuntime.outputs.files.singleFile
    inputs.dir(runtimeImageDir)

    def outputDir = file("${buildDir}/native")
    outputs.dir(outputDir)

    def installer = ""
    if (OperatingSystem.current().isLinux()) {
        installer = "rpm"
    } else if (OperatingSystem.current().isWindows()) {
        installer = "exe"
    } else if (OperatingSystem.current().isMacOsX()) {
        installer = "pkg"
    }

    executable = "jpackage"

    args = [
        "create-installer",
        "--verbose",
        "--overwrite",
        "--installer-type", installer,
        "--name", project.name,
        "--app-version", applicationVersion,
        "--main-jar", jar.archiveName,
        "--jvm-args", "-Xmx2g,-Djdk.gtk.version=2",
        "--arguments", "--laf=nimbus",
        "--runtime-image", runtimeImageDir.path,
        "--resource-dir", resourceDir.path,
        "--input", inputDir.path,
        "--output", outputDir.path,
    ]
}

I tried the Windows approach from the Exec documentation

commandLine "cmd","/c", "C:\\cygwin64\\usr\\java\\jdk-13\\bin\\jpackage.exe",
    "create-installer",
    "--verbose",
    "--overwrite",
    "--installer-type", installer,
    "--name", project.name,
    "--app-version", applicationVersion,
    "--main-jar", jar.archiveName,
    "--jvm-args", "-Xmx2g,-Djdk.gtk.version=2",
    "--arguments", "--laf=nimbus",
    "--runtime-image", runtimeImageDir.path,
    "--resource-dir", resourceDir.path,
    "--input", inputDir.path,
    "--output", outputDir.path

This one worked fine.

So, why does this other one work with jlink?

executable = "jlink"

Cygwin is not Linux. Unless your JDK is compiled from the Linux sources and linked against cygwin.dll (likely requiring extensive patches), the Unix-y path will not work. Do yourself a favor and stay away from Cygwin for non interactive use cases.

BTW, you don’t need the cmd /c - just the actual windows path to the executable should work.

I use the Windows JDK, unpacked from ZIP.
For /usr/java/jdk-11, set as JAVA_HOME, using executable = “jlink” works fine.
I find it strange that that it cannot find the jpackage executable, which is on /usr/jdk-13/bin/jpackage.exe. Could it have something to do with Gradle using the JDK 11 as JAVA_HOME.
The Linux paths works on Cygwin bash shell. Accessing jpackage works fine in Cygwin without Gradle.

My JDK 11 and JDK 13 is unpacked under
C:\cygwin64\usr\java
I think I will be moving them to C:\Program Files\Java, considering this is the same place where Java is installed on Windows. For those other JDK installed with EXE I have symlinked,
/usr/java/jdk1.8.0_202 > /c/cygdrive/Program Files/Java/jdk1.8.0_202

Using Cygwin is easier to connect Windows to Jenkins. It makes the pipeline build script easier, using linux shell.

I did try executable with the same path I had on commandLine, but it did not work. Shall double check it again tomorrow.

If it works then just use it :wink:

I tried to explain why your Gradle build doesn’t work as you expect, but you dismiss the explanations and keep trying the same thing in slightly different shapes. I guess people learn in different ways, so keep going.

Well, I could not see any explanation.

Why does this work?

executable = "jlink"

And this does not

executable = "jpackage"

With these task configuration it is working on Windows
task createRuntime(type: Exec) {
    def outputDir = file("${buildDir}/runtime")
    outputs.dir(outputDir)

    doFirst {
        // Because jlink does not have an overwrite option
        delete outputDir
    }

    executable = "jlink"

    args = [
        '--add-modules', 'java.base,java.desktop,java.logging,java.scripting',
        '--bind-services',
        '--no-header-files',
        '--no-man-pages',
        '--compress=2',
        '--strip-debug',
        '--output', outputDir.path
    ]
}

task createPackage(type: Exec) {
    dependsOn createRuntime
    dependsOn installDist

    def inputDir = file(installDist.outputs.files.singleFile.path + "/lib")
    inputs.dir(inputDir)

    def resourceDir = processPackageResources.outputs.files.singleFile
    inputs.dir(resourceDir)

    def runtimeImageDir = createRuntime.outputs.files.singleFile
    inputs.dir(runtimeImageDir)

    def outputDir = file("${buildDir}/native")
    outputs.dir(outputDir)

    def installer = ""
    if (OperatingSystem.current().isLinux()) {
        installer = "rpm"
    } else if (OperatingSystem.current().isWindows()) {
        installer = "exe"
    } else if (OperatingSystem.current().isMacOsX()) {
        installer = "pkg"
    }

    if (OperatingSystem.current().isWindows()) {
        executable = "C:\\Program Files\\Java\\jdk-13\\bin\\jpackage.exe"
    } else {
        executable = "jpackage"
    }

    args = [
        "create-installer",
        "--verbose",
        "--overwrite",
        "--installer-type", installer,
        "--name", project.name,
        "--app-version", applicationVersion,
        "--main-jar", jar.archiveName,
        "--jvm-args", "-Xmx2g,-Djdk.gtk.version=2",
        "--arguments", "--laf=nimbus",
        "--runtime-image", runtimeImageDir.path,
        "--resource-dir", resourceDir.path,
        "--input", inputDir.path,
        "--output", outputDir.path
    ]
}

I can only guess, but considering you are not specifying full paths for any of them, one must be on the path, the other one not. And by “on the path”, I mean in one of these 2 registry keys:

HKLM\System\CurrentControlSet\Control\Session Manager\Environment
HKCU\System\CurrentControlSet\Control\Session Manager\Environment

In particular the path you see in the Cygwin’s bash is not taken into account.