├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle.kts ├── doc └── examples │ ├── ModularFullRuntime.md │ └── Non-ModularApplication.md ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src ├── main └── java │ └── org │ └── panteleyev │ └── jpackage │ ├── CommandLineParameter.java │ ├── DirectoryUtil.java │ ├── EnumParameter.java │ ├── ImageType.java │ ├── JDKVersionUtil.java │ ├── JPackageGradlePlugin.java │ ├── JPackageTask.java │ ├── Launcher.java │ ├── OsUtil.java │ └── StringUtil.java └── test └── java └── org └── panteleyev └── jpackage ├── JDKVersionUtilTest.java ├── JPackageGradlePluginTest.java ├── LauncherTest.java └── StringUtilTest.java /.gitattributes: -------------------------------------------------------------------------------- 1 | # 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | # 4 | # These are explicitly windows files and should use crlf 5 | *.bat text eol=crlf 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Gradle project-specific cache directory 2 | .gradle 3 | 4 | # Ignore Gradle build output directory 5 | build 6 | 7 | # IDEA 8 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020-2025, Petr Panteleyev 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 17 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JPackage Gradle Plugin 2 | 3 | Gradle plugin for [jpackage](https://openjdk.java.net/jeps/343) tool available since JDK-14. 4 | 5 | [![Gradle Plugin Portal](https://img.shields.io/maven-metadata/v/https/plugins.gradle.org/m2/org/panteleyev/jpackageplugin/org.panteleyev.jpackageplugin.gradle.plugin/maven-metadata.xml.svg?label=Gradle%20Plugin)](https://plugins.gradle.org/plugin/org.panteleyev.jpackageplugin) 6 | [![Gradle](https://img.shields.io/badge/Gradle-7.4%2B-green)](https://gradle.org/) 7 | [![Java](https://img.shields.io/badge/Java-8-orange?logo=java)](https://www.oracle.com/java/technologies/javase-downloads.html) 8 | [![GitHub](https://img.shields.io/github/license/petr-panteleyev/jpackage-gradle-plugin)](LICENSE) 9 | 10 | ## Finding jpackage 11 | 12 | Plugin searches for ```jpackage``` executable using the following priority list: 13 | 14 | 1. Configured toolchain 15 | 16 | 2. ```java.home``` system property. 17 | 18 | ## Configuration 19 | 20 | There are generic ```jpackage``` parameters as well as OS-specific parameters for OS X, Linux, Windows. 21 | OS-specific parameters are processed when build is done on the corresponding OS. 22 | 23 | If some generic parameters should have different values based on OS then they should be placed into configuration blocks: 24 | 25 | * windows 26 | * mac 27 | * linux 28 | 29 | *Example:* 30 | 31 | ```kotlin 32 | // Windows specific parameters will be processed only during Windows build 33 | winMenu = true 34 | winDirChooser = true 35 | 36 | mac { 37 | // Generic parameter value for OS X build 38 | icon = "icons/icons.icns" 39 | } 40 | 41 | windows { 42 | // Generic parameter value for Windows build 43 | icon = "icons/icons.ico" 44 | } 45 | ``` 46 | 47 | ### Parameters 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 |
ParameterTypeJPackage ArgumentMin VersionMax Version
Generic
aboutUrlString--about-url <url>17*
addModulesList<String>--add-modules <module>[,<module>]14*
appDescriptionString--description <description string>14*
appContentList<String> (*)--app-content additional-content[,additional-content...]18*
appImageString (*)--app-image <name>14*
appNameString--name <name>14*
appVersionString--app-version <version>14*
argumentsList<String>--arguments <main class arguments>14*
bindServicesBoolean--bind-services1415
copyrightString--copyright <copyright string>14*
destinationString (*)--dest <destination path>14*
fileAssociationsList<String> (*)--file-associations <file association property file>14*
iconString (*)--icon <icon file path>14*
inputString (*)--input <input path>14*
installDirString--install-dir <file path>14*
javaOptionsList<String>--java-options <options>14*
jLinkOptionsList<String>--jlink-options <options>16*
launchersList<Launcher> (*)--add-launcher <name>=<property file>14*
launcherAsServiceBoolean--launcher-as-service19*
licenseFileString (*)--license-file <license file path>14*
mainClassString--main-class <class name>14*
mainJarString--main-jar <main jar file>14*
moduleString--module <module name>[/<main class>]14*
modulePathsList<String> (*)--module-path <module path>14*
resourceDirString (*)--resource-dir <resource dir path>14*
runtimeImageString (*)--runtime-image <file path>14*
tempString (*)--temp <temp dir path>14*
typeImageType--type <type>14*
vendorString--vendor <vendor string>14*
verboseBoolean--verbose14*
Windows
winConsoleBoolean--win-console14*
winDirChooserBoolean--win-dir-chooser14*
winHelpUrlString--win-help-url <url>17*
winMenuBoolean--win-menu14*
winMenuGroupString--win-menu-group <menu group name>14*
winPerUserInstallBoolean--win-per-user-install14*
winShortcutBoolean--win-shortcut14*
winShortcutPromptBoolean--win-shortcut-prompt17*
winUpdateUrlString--win-update-url <url>17*
winUpgradeUuidString--win-upgrade-uuid <id string>14*
OS X
macAppCategoryString-mac-app-category <category string>17*
macAppStoreBoolean--mac-app-store17*
macBundleSigningPrefixString--mac-bundle-signing-prefix <prefix string>1416
macDmgContentList<String> (*)--mac-dmg-content additional-content[,additional-content...]18*
macEntitlementsString (*)--mac-entitlements <file path>17*
macPackageIdentifierString--mac-package-identifier <ID string>14*
macPackageNameString--mac-package-name <name string>14*
macPackageSigningPrefixString--mac-package-signing-prefix <prefix string>17*
macSignBoolean--mac-sign14*
macSigningKeychainString (*)--mac-signing-keychain <file path>14*
macSigningKeyUserNameString--mac-signing-key-user-name <team name>14*
Linux
linuxAppCategoryString--linux-app-category <category value>14*
linuxAppReleaseString--linux-app-release <release value>14*
linuxDebMaintainerString--linux-deb-maintainer <email address>14*
linuxMenuGroupString--linux-menu-group <menu-group-name>14*
linuxPackageNameString--linux-package-name <package name>14*
linuxPackageDepsBoolean--linux-package-deps14*
linuxRpmLicenseTypeString--linux-rpm-license-type <type string>14*
linuxShortcutBoolean--linux-shortcut14*
119 | 120 | (*) - these parameters represent file or directory path and are resolved relative to the project root unless 121 | they contain an absolute path. 122 | 123 | ### Image Type 124 | 125 | |Plugin Value|JPackage Type| 126 | |---|---| 127 | |DEFAULT|Default image type, OS specific| 128 | |APP_IMAGE|app-image| 129 | |DMG|dmg| 130 | |PKG|pkg| 131 | |EXE|exe| 132 | |MSI|msi| 133 | |RPM|rpm| 134 | |DEB|deb| 135 | 136 | ### Destination Directory 137 | 138 | ```jpackage``` utility fails if generated binary already exists. In order to work around this behaviour there is plugin 139 | boolean option ```removeDestination```. If ```true``` plugin will try to delete directory specified by ```destination```. 140 | This might be useful to relaunch ```jpackage``` task without rebuilding an entire project. 141 | 142 | For safety reasons plugin will not process ```removeDestination``` if ```destination``` points to a location outside of 143 | ```${layout.buildDirectory}``` (or deprecated ```${buildDir}```). 144 | 145 | _Example:_ 146 | 147 | ```kotlin 148 | jpackage { 149 | destination = "${layout.buildDirectory.get()}/dist" 150 | removeDestination = true 151 | } 152 | ``` 153 | 154 | ### Default Command-Line Arguments 155 | 156 | Default command line arguments are passed to the main class when the application is started without providing arguments. 157 | 158 | _Example:_ 159 | 160 | ```kotlin 161 | arguments = listOf( 162 | "SomeArgument", 163 | "Argument with spaces", 164 | "Argument with \"quotes\"" 165 | ) 166 | ``` 167 | 168 | ### JVM Options 169 | 170 | Options that are passed to the JVM when the application is started. 171 | 172 | _Example:_ 173 | 174 | ```kotlin 175 | javaOptions = listOf( 176 | "-Xms2m", 177 | "-Xmx10m" 178 | ) 179 | ``` 180 | 181 | ### jlink options 182 | 183 | Options that are passed to underlying jlink call. 184 | 185 | _Example:_ 186 | 187 | ```kotlin 188 | jLinkOptions = listOf( 189 | "--strip-native-commands", 190 | "--strip-debug" 191 | ) 192 | ``` 193 | 194 | ### jpackage Environment Variables 195 | 196 | Optionally environment variables can be passed to ```jpackage``` executable process. 197 | 198 | _Example:_ 199 | 200 | ```kotlin 201 | jpackageEnvironment = mapOf( 202 | "GRADLE_DIR" to project.projectDir.absolutePath, 203 | "BUILD_DIR" to project.buildDir.absolutePath 204 | ) 205 | ``` 206 | 207 | ```null``` values as well as ```null``` or empty keys are ignored. 208 | 209 | 210 | ## Logging 211 | 212 | Plugin uses ```LogLevel.INFO``` to print various information about toolchain, jpackage parameters, etc. Use gradle 213 | option ```--info``` to check this output. 214 | 215 | ## Dry Run Mode 216 | 217 | To execute plugin tasks in dry run mode without calling ```jpackage``` set property```jpackage.dryRun``` to true. 218 | 219 | _Example:_ 220 | 221 | ```shell 222 | $ ./gradlew clean build jpackage --info -Djpackage.dryRun=true 223 | ``` 224 | 225 | ## Configuration Cache 226 | 227 | This plugin is not compatible with Gradle [configuration cache](https://docs.gradle.org/current/userguide/configuration_cache.html). 228 | 229 | ## Examples 230 | 231 | * [Modular Application with Full Runtime](doc/examples/ModularFullRuntime.md) 232 | * [Non-Modular Application](doc/examples/Non-ModularApplication.md) 233 | 234 | ## References 235 | 236 | [Packaging Tool User's Guide](https://docs.oracle.com/en/java/javase/19/jpackage/packaging-tool-user-guide.pdf) 237 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020-2025 Petr Panteleyev 3 | SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | group = "org.panteleyev" 6 | version = "1.6.1" 7 | 8 | plugins { 9 | java 10 | `java-gradle-plugin` 11 | `maven-publish` 12 | id("com.gradle.plugin-publish") version "1.3.1" 13 | } 14 | 15 | repositories { 16 | mavenCentral() 17 | } 18 | 19 | dependencies { 20 | testImplementation("org.junit.jupiter:junit-jupiter:5.11.4") 21 | } 22 | 23 | java { 24 | toolchain { 25 | languageVersion.set(JavaLanguageVersion.of(8)) 26 | } 27 | } 28 | 29 | gradlePlugin { 30 | val jpackage by plugins.creating { 31 | id = "org.panteleyev.jpackageplugin" 32 | version = project.version 33 | displayName = "JPackage Gradle Plugin" 34 | description = "A plugin that executes jpackage tool from JDK-14+" 35 | implementationClass = "org.panteleyev.jpackage.JPackageGradlePlugin" 36 | } 37 | } 38 | 39 | pluginBundle { 40 | website = "https://github.com/petr-panteleyev/jpackage-gradle-plugin" 41 | vcsUrl = "https://github.com/petr-panteleyev/jpackage-gradle-plugin.git" 42 | tags = listOf("jpackage") 43 | } 44 | 45 | tasks.withType { 46 | useJUnitPlatform() 47 | } 48 | -------------------------------------------------------------------------------- /doc/examples/ModularFullRuntime.md: -------------------------------------------------------------------------------- 1 | # Modular Application with Full Runtime 2 | 3 | ```kotlin 4 | task("copyDependencies", Copy::class) { 5 | from(configurations.runtimeClasspath).into("$buildDir/jmods") 6 | } 7 | 8 | task("copyJar", Copy::class) { 9 | from(tasks.jar).into("$buildDir/jmods") 10 | } 11 | 12 | tasks.jpackage { 13 | dependsOn("build", "copyDependencies", "copyJar") 14 | 15 | appName = "Application Name" 16 | appVersion = project.version.toString() 17 | vendor = "app.org" 18 | copyright = "Copyright (c) 2020 Vendor" 19 | runtimeImage = System.getProperty("java.home") 20 | module = "org.app.module/org.app.MainClass" 21 | modulePaths = listOf(File("$buildDir/jmods")) 22 | destination = "$buildDir/dist" 23 | javaOptions = listOf("-Dfile.encoding=UTF-8") 24 | 25 | mac { 26 | icon = "icons/icons.icns" 27 | } 28 | 29 | windows { 30 | icon = "icons/icons.ico" 31 | winMenu = true 32 | winDirChooser = true 33 | } 34 | } 35 | ``` 36 | -------------------------------------------------------------------------------- /doc/examples/Non-ModularApplication.md: -------------------------------------------------------------------------------- 1 | # Non-Modular Application 2 | 3 | ```kotlin 4 | task("copyDependencies", Copy::class) { 5 | from(configurations.runtimeClasspath).into("$buildDir/jars") 6 | } 7 | 8 | task("copyJar", Copy::class) { 9 | from(tasks.jar).into("$buildDir/jars") 10 | } 11 | 12 | tasks.jpackage { 13 | dependsOn("build", "copyDependencies", "copyJar") 14 | 15 | input = "$buildDir/jars" 16 | destination = "$buildDir/dist" 17 | 18 | appName = "Non-Modular Application" 19 | vendor = "app.org" 20 | 21 | mainJar = tasks.jar.get().archiveFileName.get() 22 | mainClass = "org.app.MainClass" 23 | 24 | javaOptions = listOf("-Dfile.encoding=UTF-8") 25 | 26 | windows { 27 | winConsole = true 28 | } 29 | } 30 | 31 | ``` 32 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/petr-panteleyev/jpackage-gradle-plugin/530bb5a5a256fc8e7a6f9500e98d02bb8ec632ab/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 Petr Panteleyev 3 | SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | rootProject.name = "jpackage-gradle-plugin" 7 | -------------------------------------------------------------------------------- /src/main/java/org/panteleyev/jpackage/CommandLineParameter.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022-2023 Petr Panteleyev 3 | SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | package org.panteleyev.jpackage; 6 | 7 | import org.gradle.api.GradleException; 8 | 9 | import static org.panteleyev.jpackage.JPackageTask.EXECUTABLE; 10 | 11 | enum CommandLineParameter { 12 | NAME("--name"), 13 | DESTINATION("--dest"), 14 | VERBOSE("--verbose"), 15 | TYPE("--type"), 16 | APP_VERSION("--app-version"), 17 | COPYRIGHT("--copyright"), 18 | DESCRIPTION("--description"), 19 | RUNTIME_IMAGE("--runtime-image"), 20 | INPUT("--input"), 21 | INSTALL_DIR("--install-dir"), 22 | RESOURCE_DIR("--resource-dir"), 23 | VENDOR("--vendor"), 24 | MODULE("--module"), 25 | MAIN_CLASS("--main-class"), 26 | MAIN_JAR("--main-jar"), 27 | TEMP("--temp"), 28 | ICON("--icon"), 29 | LICENSE_FILE("--license-file"), 30 | APP_IMAGE("--app-image"), 31 | MODULE_PATH("--module-path"), 32 | ADD_MODULES("--add-modules"), 33 | JAVA_OPTIONS("--java-options"), 34 | ARGUMENTS("--arguments"), 35 | FILE_ASSOCIATIONS("--file-associations"), 36 | ADD_LAUNCHER("--add-launcher"), 37 | BIND_SERVICES("--bind-services", 14, 15), 38 | JLINK_OPTIONS("--jlink-options", 16), 39 | ABOUT_URL("--about-url", 17), 40 | APP_CONTENT("--app-content", 18), 41 | LAUNCHER_AS_SERVICE("--launcher-as-service", 19), 42 | // Mac 43 | MAC_PACKAGE_IDENTIFIER("--mac-package-identifier"), 44 | MAC_PACKAGE_NAME("--mac-package-name"), 45 | MAC_BUNDLE_SIGNING_PREFIX("--mac-bundle-signing-prefix", 14, 16), 46 | MAC_PACKAGE_SIGNING_PREFIX("--mac-package-signing-prefix", 17), 47 | MAC_APP_STORE("--mac-app-store", 17), 48 | MAC_ENTITLEMENTS("--mac-entitlements", 17), 49 | MAC_APP_CATEGORY("--mac-app-category", 17), 50 | MAC_SIGN("--mac-sign"), 51 | MAC_SIGNING_KEYCHAIN("--mac-signing-keychain"), 52 | MAC_SIGNING_KEY_USER_NAME("--mac-signing-key-user-name"), 53 | MAC_DMG_CONTENT("--mac-dmg-content", 18), 54 | // Windows 55 | WIN_CONSOLE("--win-console"), 56 | WIN_DIR_CHOOSER("--win-dir-chooser"), 57 | WIN_HELP_URL("--win-help-url", 17), 58 | WIN_MENU("--win-menu"), 59 | WIN_MENU_GROUP("--win-menu-group"), 60 | WIN_PER_USER_INSTALL("--win-per-user-install"), 61 | WIN_SHORTCUT("--win-shortcut"), 62 | WIN_SHORTCUT_PROMPT("--win-shortcut-prompt", 17), 63 | WIN_UPDATE_URL("--win-update-url", 17), 64 | WIN_UPGRADE_UUID("--win-upgrade-uuid"), 65 | // Linux 66 | LINUX_PACKAGE_NAME("--linux-package-name"), 67 | LINUX_DEB_MAINTAINER("--linux-deb-maintainer"), 68 | LINUX_MENU_GROUP("--linux-menu-group"), 69 | LINUX_PACKAGE_DEPS("--linux-package-deps"), 70 | LINUX_RPM_LICENSE_TYPE("--linux-rpm-license-type"), 71 | LINUX_APP_RELEASE("--linux-app-release"), 72 | LINUX_APP_CATEGORY("--linux-app-category"), 73 | LINUX_SHORTCUT("--linux-shortcut"); 74 | 75 | private final String name; 76 | private final int minVersion; 77 | private final int maxVersion; 78 | 79 | CommandLineParameter(String name, int minVersion, int maxVersion) { 80 | this.name = name; 81 | this.minVersion = minVersion; 82 | this.maxVersion = maxVersion; 83 | } 84 | 85 | CommandLineParameter(String name, int minVersion) { 86 | this(name, minVersion, Integer.MAX_VALUE); 87 | } 88 | 89 | CommandLineParameter(String name) { 90 | this(name, 14, Integer.MAX_VALUE); 91 | } 92 | 93 | public String getName() { 94 | return name; 95 | } 96 | 97 | public void checkVersion(int version) throws GradleException { 98 | if (version == 0) { 99 | return; 100 | } 101 | 102 | if (version < minVersion || version > maxVersion) { 103 | throw new GradleException( 104 | "Parameter \"" 105 | + name 106 | + "\" requires " 107 | + EXECUTABLE 108 | + " versions: [" 109 | + minVersion 110 | + ".." 111 | + (maxVersion == Integer.MAX_VALUE ? "*" : maxVersion) 112 | + "]" 113 | ); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/org/panteleyev/jpackage/DirectoryUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2025 Petr Panteleyev 3 | SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | 6 | package org.panteleyev.jpackage; 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.io.UncheckedIOException; 11 | import java.nio.file.Files; 12 | import java.nio.file.Path; 13 | import java.util.Comparator; 14 | import java.util.stream.Stream; 15 | 16 | final class DirectoryUtil { 17 | 18 | static boolean isNestedDirectory(Path parent, Path child) { 19 | Path absoluteParent = parent.toAbsolutePath(); 20 | Path absoluteChild = child.toAbsolutePath(); 21 | return absoluteChild.startsWith(absoluteParent); 22 | } 23 | 24 | static void removeDirectory(Path dir) { 25 | if (!dir.toFile().exists()) { 26 | return; 27 | } 28 | 29 | try (Stream paths = Files.walk(dir)) { 30 | paths.sorted(Comparator.reverseOrder()) 31 | .map(Path::toFile) 32 | .forEach(File::delete); 33 | } catch (IOException ex) { 34 | throw new UncheckedIOException(ex); 35 | } 36 | } 37 | 38 | private DirectoryUtil() { 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/panteleyev/jpackage/EnumParameter.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) Petr Panteleyev. All rights reserved. 3 | Licensed under the BSD license. See LICENSE file in the project root for full license information. 4 | */ 5 | package org.panteleyev.jpackage; 6 | 7 | public interface EnumParameter { 8 | String getValue(); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/org/panteleyev/jpackage/ImageType.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) Petr Panteleyev. All rights reserved. 3 | Licensed under the BSD license. See LICENSE file in the project root for full license information. 4 | */ 5 | package org.panteleyev.jpackage; 6 | 7 | public enum ImageType implements EnumParameter { 8 | DEFAULT(""), 9 | APP_IMAGE("app-image"), 10 | DMG("dmg"), 11 | PKG("pkg"), 12 | EXE("exe"), 13 | MSI("msi"), 14 | RPM("rpm"), 15 | DEB("deb"); 16 | 17 | private final String value; 18 | 19 | ImageType(String value) { 20 | this.value = value; 21 | } 22 | 23 | @Override 24 | public String getValue() { 25 | return value; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/panteleyev/jpackage/JDKVersionUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 Petr Panteleyev 3 | SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | package org.panteleyev.jpackage; 6 | 7 | import java.io.BufferedReader; 8 | import java.io.InputStreamReader; 9 | import java.util.Arrays; 10 | import java.util.List; 11 | 12 | import static org.panteleyev.jpackage.JPackageTask.EXECUTABLE; 13 | 14 | final class JDKVersionUtil { 15 | private static final String JAVA_VERSION_PATTERN = "java.version = "; 16 | 17 | public static int getJDKMajorVersion(String jpackageCmd) { 18 | String cmd = buildJavaExecutable(jpackageCmd); 19 | List parameters = Arrays.asList( 20 | cmd.contains(" ") ? "\"" + cmd + "\"" : cmd, 21 | "-XshowSettings:properties", 22 | "-version" 23 | ); 24 | 25 | try { 26 | ProcessBuilder processBuilder = new ProcessBuilder(); 27 | Process process = processBuilder 28 | .redirectErrorStream(true) 29 | .command(parameters) 30 | .start(); 31 | 32 | int result = 0; 33 | 34 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { 35 | String line; 36 | while ((line = reader.readLine()) != null) { 37 | int index = line.indexOf(JAVA_VERSION_PATTERN); 38 | if (index == -1) { 39 | continue; 40 | } 41 | String[] parts = line.substring(index + JAVA_VERSION_PATTERN.length()).split("\\."); 42 | result = Integer.parseInt(parts[0]); 43 | break; 44 | } 45 | } 46 | 47 | int status = process.waitFor(); 48 | if (status != 0) { 49 | return 0; 50 | } else { 51 | return result; 52 | } 53 | } catch (Exception e) { 54 | return 0; 55 | } 56 | } 57 | 58 | static String buildJavaExecutable(String jpackageExecutable) { 59 | int index = jpackageExecutable.indexOf(EXECUTABLE); 60 | return jpackageExecutable.substring(0, index) 61 | + "java" 62 | + jpackageExecutable.substring(index + EXECUTABLE.length()); 63 | } 64 | 65 | private JDKVersionUtil() { 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/org/panteleyev/jpackage/JPackageGradlePlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021-2023 Petr Panteleyev 3 | SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | package org.panteleyev.jpackage; 6 | 7 | import org.gradle.api.Plugin; 8 | import org.gradle.api.Project; 9 | 10 | /** 11 | * JPackage Gradle Plugin. 12 | */ 13 | public class JPackageGradlePlugin implements Plugin { 14 | private static final String GROUP = "Distribution"; 15 | private static final String DESCRIPTION = "Creates application bundle using jpackage."; 16 | 17 | @Override 18 | public void apply(Project target) { 19 | target.getTasks().register("jpackage", JPackageTask.class, task -> { 20 | task.setGroup(GROUP); 21 | task.setDescription(DESCRIPTION); 22 | task.notCompatibleWithConfigurationCache("Will not support configuration cache due to feature set"); 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/panteleyev/jpackage/JPackageTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021-2025 Petr Panteleyev 3 | SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | package org.panteleyev.jpackage; 6 | 7 | import org.gradle.api.DefaultTask; 8 | import org.gradle.api.GradleException; 9 | import org.gradle.api.plugins.JavaPluginExtension; 10 | import org.gradle.api.provider.Provider; 11 | import org.gradle.api.tasks.Input; 12 | import org.gradle.api.tasks.TaskAction; 13 | import org.gradle.jvm.toolchain.JavaLauncher; 14 | import org.gradle.jvm.toolchain.JavaToolchainService; 15 | import org.gradle.jvm.toolchain.JavaToolchainSpec; 16 | 17 | import java.io.BufferedReader; 18 | import java.io.File; 19 | import java.io.InputStreamReader; 20 | import java.nio.file.Path; 21 | import java.util.ArrayList; 22 | import java.util.Collection; 23 | import java.util.List; 24 | import java.util.Map; 25 | import java.util.Optional; 26 | 27 | import static org.panteleyev.jpackage.CommandLineParameter.ABOUT_URL; 28 | import static org.panteleyev.jpackage.CommandLineParameter.ADD_LAUNCHER; 29 | import static org.panteleyev.jpackage.CommandLineParameter.ADD_MODULES; 30 | import static org.panteleyev.jpackage.CommandLineParameter.APP_CONTENT; 31 | import static org.panteleyev.jpackage.CommandLineParameter.APP_IMAGE; 32 | import static org.panteleyev.jpackage.CommandLineParameter.APP_VERSION; 33 | import static org.panteleyev.jpackage.CommandLineParameter.ARGUMENTS; 34 | import static org.panteleyev.jpackage.CommandLineParameter.BIND_SERVICES; 35 | import static org.panteleyev.jpackage.CommandLineParameter.COPYRIGHT; 36 | import static org.panteleyev.jpackage.CommandLineParameter.DESCRIPTION; 37 | import static org.panteleyev.jpackage.CommandLineParameter.DESTINATION; 38 | import static org.panteleyev.jpackage.CommandLineParameter.FILE_ASSOCIATIONS; 39 | import static org.panteleyev.jpackage.CommandLineParameter.ICON; 40 | import static org.panteleyev.jpackage.CommandLineParameter.INPUT; 41 | import static org.panteleyev.jpackage.CommandLineParameter.INSTALL_DIR; 42 | import static org.panteleyev.jpackage.CommandLineParameter.JAVA_OPTIONS; 43 | import static org.panteleyev.jpackage.CommandLineParameter.JLINK_OPTIONS; 44 | import static org.panteleyev.jpackage.CommandLineParameter.LAUNCHER_AS_SERVICE; 45 | import static org.panteleyev.jpackage.CommandLineParameter.LICENSE_FILE; 46 | import static org.panteleyev.jpackage.CommandLineParameter.LINUX_APP_CATEGORY; 47 | import static org.panteleyev.jpackage.CommandLineParameter.LINUX_APP_RELEASE; 48 | import static org.panteleyev.jpackage.CommandLineParameter.LINUX_DEB_MAINTAINER; 49 | import static org.panteleyev.jpackage.CommandLineParameter.LINUX_MENU_GROUP; 50 | import static org.panteleyev.jpackage.CommandLineParameter.LINUX_PACKAGE_DEPS; 51 | import static org.panteleyev.jpackage.CommandLineParameter.LINUX_PACKAGE_NAME; 52 | import static org.panteleyev.jpackage.CommandLineParameter.LINUX_RPM_LICENSE_TYPE; 53 | import static org.panteleyev.jpackage.CommandLineParameter.LINUX_SHORTCUT; 54 | import static org.panteleyev.jpackage.CommandLineParameter.MAC_APP_CATEGORY; 55 | import static org.panteleyev.jpackage.CommandLineParameter.MAC_APP_STORE; 56 | import static org.panteleyev.jpackage.CommandLineParameter.MAC_BUNDLE_SIGNING_PREFIX; 57 | import static org.panteleyev.jpackage.CommandLineParameter.MAC_DMG_CONTENT; 58 | import static org.panteleyev.jpackage.CommandLineParameter.MAC_ENTITLEMENTS; 59 | import static org.panteleyev.jpackage.CommandLineParameter.MAC_PACKAGE_IDENTIFIER; 60 | import static org.panteleyev.jpackage.CommandLineParameter.MAC_PACKAGE_NAME; 61 | import static org.panteleyev.jpackage.CommandLineParameter.MAC_PACKAGE_SIGNING_PREFIX; 62 | import static org.panteleyev.jpackage.CommandLineParameter.MAC_SIGN; 63 | import static org.panteleyev.jpackage.CommandLineParameter.MAC_SIGNING_KEYCHAIN; 64 | import static org.panteleyev.jpackage.CommandLineParameter.MAC_SIGNING_KEY_USER_NAME; 65 | import static org.panteleyev.jpackage.CommandLineParameter.MAIN_CLASS; 66 | import static org.panteleyev.jpackage.CommandLineParameter.MAIN_JAR; 67 | import static org.panteleyev.jpackage.CommandLineParameter.MODULE; 68 | import static org.panteleyev.jpackage.CommandLineParameter.MODULE_PATH; 69 | import static org.panteleyev.jpackage.CommandLineParameter.NAME; 70 | import static org.panteleyev.jpackage.CommandLineParameter.RESOURCE_DIR; 71 | import static org.panteleyev.jpackage.CommandLineParameter.RUNTIME_IMAGE; 72 | import static org.panteleyev.jpackage.CommandLineParameter.TEMP; 73 | import static org.panteleyev.jpackage.CommandLineParameter.TYPE; 74 | import static org.panteleyev.jpackage.CommandLineParameter.VENDOR; 75 | import static org.panteleyev.jpackage.CommandLineParameter.VERBOSE; 76 | import static org.panteleyev.jpackage.CommandLineParameter.WIN_CONSOLE; 77 | import static org.panteleyev.jpackage.CommandLineParameter.WIN_DIR_CHOOSER; 78 | import static org.panteleyev.jpackage.CommandLineParameter.WIN_HELP_URL; 79 | import static org.panteleyev.jpackage.CommandLineParameter.WIN_MENU; 80 | import static org.panteleyev.jpackage.CommandLineParameter.WIN_MENU_GROUP; 81 | import static org.panteleyev.jpackage.CommandLineParameter.WIN_PER_USER_INSTALL; 82 | import static org.panteleyev.jpackage.CommandLineParameter.WIN_SHORTCUT; 83 | import static org.panteleyev.jpackage.CommandLineParameter.WIN_SHORTCUT_PROMPT; 84 | import static org.panteleyev.jpackage.CommandLineParameter.WIN_UPDATE_URL; 85 | import static org.panteleyev.jpackage.CommandLineParameter.WIN_UPGRADE_UUID; 86 | import static org.panteleyev.jpackage.DirectoryUtil.isNestedDirectory; 87 | import static org.panteleyev.jpackage.DirectoryUtil.removeDirectory; 88 | import static org.panteleyev.jpackage.JDKVersionUtil.getJDKMajorVersion; 89 | import static org.panteleyev.jpackage.OsUtil.isLinux; 90 | import static org.panteleyev.jpackage.OsUtil.isMac; 91 | import static org.panteleyev.jpackage.OsUtil.isWindows; 92 | import static org.panteleyev.jpackage.StringUtil.escape; 93 | 94 | @SuppressWarnings({"SameParameterValue", "unused"}) 95 | public class JPackageTask extends DefaultTask { 96 | static final String EXECUTABLE = "jpackage"; 97 | 98 | private boolean verbose; 99 | private ImageType type = ImageType.DEFAULT; 100 | private String appName; 101 | private String appImage; 102 | private String appVersion = getProject().getVersion().toString(); 103 | private String vendor; 104 | private String icon; 105 | private String runtimeImage; 106 | private String input; 107 | private String installDir; 108 | private String destination; 109 | private String module; 110 | private String mainClass; 111 | private String mainJar; 112 | private String copyright; 113 | private String appDescription; 114 | private List modulePaths; 115 | private String licenseFile; 116 | private String resourceDir; 117 | private String temp; 118 | private List javaOptions; 119 | private List arguments; 120 | private List fileAssociations; 121 | private List launchers; 122 | private List addModules; 123 | private boolean bindServices; 124 | private List jLinkOptions; 125 | private String aboutUrl; 126 | private boolean launcherAsService; 127 | private List appContent; 128 | 129 | private Boolean removeDestination; 130 | 131 | // Windows specific parameters 132 | private boolean winMenu; 133 | private boolean winDirChooser; 134 | private String winUpgradeUuid; 135 | private String winMenuGroup; 136 | private boolean winShortcut; 137 | private boolean winPerUserInstall; 138 | private boolean winConsole; 139 | private String winHelpUrl; 140 | private boolean winShortcutPrompt; 141 | private String winUpdateUrl; 142 | 143 | // OS X specific parameters 144 | private String macPackageIdentifier; 145 | private String macPackageName; 146 | private String macPackageSigningPrefix; 147 | private String macBundleSigningPrefix; 148 | private boolean macSign; 149 | private String macSigningKeychain; 150 | private String macSigningKeyUserName; 151 | private boolean macAppStore; 152 | private String macAppCategory; 153 | private String macEntitlements; 154 | private List macDmgContent; 155 | 156 | // Linux specific parameters 157 | private String linuxPackageName; 158 | private String linuxDebMaintainer; 159 | private String linuxMenuGroup; 160 | private String linuxRpmLicenseType; 161 | private String linuxAppRelease; 162 | private String linuxAppCategory; 163 | private boolean linuxShortcut; 164 | private boolean linuxPackageDeps; 165 | 166 | // Additional parameters 167 | private List additionalParameters = new ArrayList<>(); 168 | 169 | // JPackage process environment variables 170 | private Map jpackageEnvironment; 171 | 172 | // Plugin internal options 173 | private final boolean dryRun; 174 | 175 | public JPackageTask() { 176 | dryRun = Boolean.getBoolean("jpackage.dryRun"); 177 | } 178 | 179 | @Input 180 | public boolean getVerbose() { 181 | return verbose; 182 | } 183 | 184 | public void setVerbose(boolean verbose) { 185 | this.verbose = verbose; 186 | } 187 | 188 | @Input 189 | public ImageType getType() { 190 | return type; 191 | } 192 | 193 | public void setType(ImageType type) { 194 | this.type = type; 195 | } 196 | 197 | @Input 198 | @org.gradle.api.tasks.Optional 199 | public String getAppName() { 200 | return appName; 201 | } 202 | 203 | public void setAppName(String appName) { 204 | this.appName = appName; 205 | } 206 | 207 | @Input 208 | @org.gradle.api.tasks.Optional 209 | public String getAppImage() { 210 | return appImage; 211 | } 212 | 213 | public void setAppImage(String appImage) { 214 | this.appImage = appImage; 215 | } 216 | 217 | @Input 218 | public String getAppVersion() { 219 | return appVersion; 220 | } 221 | 222 | public void setAppVersion(String appVersion) { 223 | this.appVersion = appVersion; 224 | } 225 | 226 | @Input 227 | @org.gradle.api.tasks.Optional 228 | public String getVendor() { 229 | return vendor; 230 | } 231 | 232 | public void setVendor(String vendor) { 233 | this.vendor = vendor; 234 | } 235 | 236 | @Input 237 | @org.gradle.api.tasks.Optional 238 | public String getIcon() { 239 | return icon; 240 | } 241 | 242 | public void setIcon(String icon) { 243 | this.icon = icon; 244 | } 245 | 246 | @Input 247 | @org.gradle.api.tasks.Optional 248 | public String getRuntimeImage() { 249 | return runtimeImage; 250 | } 251 | 252 | public void setRuntimeImage(String runtimeImage) { 253 | this.runtimeImage = runtimeImage; 254 | } 255 | 256 | @Input 257 | @org.gradle.api.tasks.Optional 258 | public String getInput() { 259 | return input; 260 | } 261 | 262 | public void setInput(String input) { 263 | this.input = input; 264 | } 265 | 266 | @Input 267 | @org.gradle.api.tasks.Optional 268 | public String getInstallDir() { 269 | return installDir; 270 | } 271 | 272 | public void setInstallDir(String installDir) { 273 | this.installDir = installDir; 274 | } 275 | 276 | @Input 277 | @org.gradle.api.tasks.Optional 278 | public String getDestination() { 279 | return destination; 280 | } 281 | 282 | public void setDestination(String destination) { 283 | this.destination = destination; 284 | } 285 | 286 | @Input 287 | @org.gradle.api.tasks.Optional 288 | public String getModule() { 289 | return module; 290 | } 291 | 292 | public void setModule(String module) { 293 | this.module = module; 294 | } 295 | 296 | @Input 297 | @org.gradle.api.tasks.Optional 298 | public String getMainClass() { 299 | return mainClass; 300 | } 301 | 302 | public void setMainClass(String mainClass) { 303 | this.mainClass = mainClass; 304 | } 305 | 306 | @Input 307 | @org.gradle.api.tasks.Optional 308 | public String getMainJar() { 309 | return mainJar; 310 | } 311 | 312 | public void setMainJar(String mainJar) { 313 | this.mainJar = mainJar; 314 | } 315 | 316 | @Input 317 | @org.gradle.api.tasks.Optional 318 | public String getCopyright() { 319 | return copyright; 320 | } 321 | 322 | public void setCopyright(String copyright) { 323 | this.copyright = copyright; 324 | } 325 | 326 | @Input 327 | @org.gradle.api.tasks.Optional 328 | public String getAppDescription() { 329 | return appDescription; 330 | } 331 | 332 | public void setAppDescription(String appDescription) { 333 | this.appDescription = appDescription; 334 | } 335 | 336 | @Input 337 | @org.gradle.api.tasks.Optional 338 | public List getModulePaths() { 339 | return modulePaths; 340 | } 341 | 342 | public void setModulePaths(List modulePaths) { 343 | this.modulePaths = modulePaths; 344 | } 345 | 346 | @Input 347 | @org.gradle.api.tasks.Optional 348 | public String getLicenseFile() { 349 | return licenseFile; 350 | } 351 | 352 | public void setLicenseFile(String licenseFile) { 353 | this.licenseFile = licenseFile; 354 | } 355 | 356 | @Input 357 | @org.gradle.api.tasks.Optional 358 | public String getResourceDir() { 359 | return resourceDir; 360 | } 361 | 362 | public void setResourceDir(String resourceDir) { 363 | this.resourceDir = resourceDir; 364 | } 365 | 366 | @Input 367 | @org.gradle.api.tasks.Optional 368 | public String getTemp() { 369 | return temp; 370 | } 371 | 372 | public void setTemp(String temp) { 373 | this.temp = temp; 374 | } 375 | 376 | @Input 377 | @org.gradle.api.tasks.Optional 378 | public List getJavaOptions() { 379 | return javaOptions; 380 | } 381 | 382 | public void setJavaOptions(List javaOptions) { 383 | this.javaOptions = javaOptions; 384 | } 385 | 386 | @Input 387 | @org.gradle.api.tasks.Optional 388 | public List getArguments() { 389 | return arguments; 390 | } 391 | 392 | public void setArguments(List arguments) { 393 | this.arguments = arguments; 394 | } 395 | 396 | @Input 397 | @org.gradle.api.tasks.Optional 398 | public List getFileAssociations() { 399 | return fileAssociations; 400 | } 401 | 402 | public void setFileAssociations(List fileAssociations) { 403 | this.fileAssociations = fileAssociations; 404 | } 405 | 406 | @Input 407 | @org.gradle.api.tasks.Optional 408 | public List getLaunchers() { 409 | return launchers; 410 | } 411 | 412 | public void setLaunchers(List launchers) { 413 | this.launchers = launchers; 414 | } 415 | 416 | @Input 417 | @org.gradle.api.tasks.Optional 418 | public List getAddModules() { 419 | return addModules; 420 | } 421 | 422 | public void setAddModules(List addModules) { 423 | this.addModules = addModules; 424 | } 425 | 426 | @Input 427 | public boolean getBindServices() { 428 | return bindServices; 429 | } 430 | 431 | public void setBindServices(boolean bindServices) { 432 | this.bindServices = bindServices; 433 | } 434 | 435 | @Input 436 | @org.gradle.api.tasks.Optional 437 | public List getJLinkOptions() { 438 | return jLinkOptions; 439 | } 440 | 441 | public void setJLinkOptions(List jLinkOptions) { 442 | this.jLinkOptions = jLinkOptions; 443 | } 444 | 445 | @Input 446 | @org.gradle.api.tasks.Optional 447 | public String getAboutUrl() { 448 | return aboutUrl; 449 | } 450 | 451 | public void setAboutUrl(String aboutUrl) { 452 | this.aboutUrl = aboutUrl; 453 | } 454 | 455 | @Input 456 | public boolean getLauncherAsService() { 457 | return launcherAsService; 458 | } 459 | 460 | public void setLauncherAsService(boolean launcherAsService) { 461 | this.launcherAsService = launcherAsService; 462 | } 463 | 464 | @Input 465 | @org.gradle.api.tasks.Optional 466 | public List getAppContent() { 467 | return appContent; 468 | } 469 | 470 | public void setAppContent(List appContent) { 471 | this.appContent = appContent; 472 | } 473 | 474 | @Input 475 | @org.gradle.api.tasks.Optional 476 | public Boolean getRemoveDestination() { 477 | return removeDestination; 478 | } 479 | 480 | public void setRemoveDestination(Boolean removeDestination) { 481 | this.removeDestination = removeDestination; 482 | } 483 | 484 | @Input 485 | public boolean getWinMenu() { 486 | return winMenu; 487 | } 488 | 489 | public void setWinMenu(boolean winMenu) { 490 | this.winMenu = winMenu; 491 | } 492 | 493 | @Input 494 | public boolean getWinDirChooser() { 495 | return winDirChooser; 496 | } 497 | 498 | public void setWinDirChooser(boolean winDirChooser) { 499 | this.winDirChooser = winDirChooser; 500 | } 501 | 502 | @Input 503 | @org.gradle.api.tasks.Optional 504 | public String getWinUpgradeUuid() { 505 | return winUpgradeUuid; 506 | } 507 | 508 | public void setWinUpgradeUuid(String winUpgradeUuid) { 509 | this.winUpgradeUuid = winUpgradeUuid; 510 | } 511 | 512 | @Input 513 | @org.gradle.api.tasks.Optional 514 | public String getWinMenuGroup() { 515 | return winMenuGroup; 516 | } 517 | 518 | public void setWinMenuGroup(String winMenuGroup) { 519 | this.winMenuGroup = winMenuGroup; 520 | } 521 | 522 | @Input 523 | public boolean getWinShortcut() { 524 | return winShortcut; 525 | } 526 | 527 | public void setWinShortcut(boolean winShortcut) { 528 | this.winShortcut = winShortcut; 529 | } 530 | 531 | @Input 532 | public boolean getWinPerUserInstall() { 533 | return winPerUserInstall; 534 | } 535 | 536 | public void setWinPerUserInstall(boolean winPerUserInstall) { 537 | this.winPerUserInstall = winPerUserInstall; 538 | } 539 | 540 | @Input 541 | public boolean getWinConsole() { 542 | return winConsole; 543 | } 544 | 545 | public void setWinConsole(boolean winConsole) { 546 | this.winConsole = winConsole; 547 | } 548 | 549 | @Input 550 | @org.gradle.api.tasks.Optional 551 | public String getWinHelpUrl() { 552 | return winHelpUrl; 553 | } 554 | 555 | public void setWinHelpUrl(String winHelpUrl) { 556 | this.winHelpUrl = winHelpUrl; 557 | } 558 | 559 | @Input 560 | public boolean getWinShortcutPrompt() { 561 | return winShortcutPrompt; 562 | } 563 | 564 | public void setWinShortcutPrompt(boolean winShortcutPrompt) { 565 | this.winShortcutPrompt = winShortcutPrompt; 566 | } 567 | 568 | @Input 569 | @org.gradle.api.tasks.Optional 570 | public String getWinUpdateUrl() { 571 | return winUpdateUrl; 572 | } 573 | 574 | public void setWinUpdateUrl(String winUpdateUrl) { 575 | this.winUpdateUrl = winUpdateUrl; 576 | } 577 | 578 | @Input 579 | @org.gradle.api.tasks.Optional 580 | public String getMacPackageIdentifier() { 581 | return macPackageIdentifier; 582 | } 583 | 584 | public void setMacPackageIdentifier(String macPackageIdentifier) { 585 | this.macPackageIdentifier = macPackageIdentifier; 586 | } 587 | 588 | @Input 589 | @org.gradle.api.tasks.Optional 590 | public String getMacPackageName() { 591 | return macPackageName; 592 | } 593 | 594 | public void setMacPackageName(String macPackageName) { 595 | this.macPackageName = macPackageName; 596 | } 597 | 598 | @Input 599 | @org.gradle.api.tasks.Optional 600 | public String getMacPackageSigningPrefix() { 601 | return macPackageSigningPrefix; 602 | } 603 | 604 | public void setMacPackageSigningPrefix(String macPackageSigningPrefix) { 605 | this.macPackageSigningPrefix = macPackageSigningPrefix; 606 | } 607 | 608 | @Input 609 | @org.gradle.api.tasks.Optional 610 | public String getMacBundleSigningPrefix() { 611 | return macBundleSigningPrefix; 612 | } 613 | 614 | public void setMacBundleSigningPrefix(String macBundleSigningPrefix) { 615 | this.macBundleSigningPrefix = macBundleSigningPrefix; 616 | } 617 | 618 | @Input 619 | public boolean getMacSign() { 620 | return macSign; 621 | } 622 | 623 | public void setMacSign(boolean macSign) { 624 | this.macSign = macSign; 625 | } 626 | 627 | @Input 628 | @org.gradle.api.tasks.Optional 629 | public String getMacSigningKeychain() { 630 | return macSigningKeychain; 631 | } 632 | 633 | public void setMacSigningKeychain(String macSigningKeychain) { 634 | this.macSigningKeychain = macSigningKeychain; 635 | } 636 | 637 | @Input 638 | @org.gradle.api.tasks.Optional 639 | public String getMacSigningKeyUserName() { 640 | return macSigningKeyUserName; 641 | } 642 | 643 | public void setMacSigningKeyUserName(String macSigningKeyUserName) { 644 | this.macSigningKeyUserName = macSigningKeyUserName; 645 | } 646 | 647 | @Input 648 | public boolean getMacAppStore() { 649 | return macAppStore; 650 | } 651 | 652 | public void setMacAppStore(boolean macAppStore) { 653 | this.macAppStore = macAppStore; 654 | } 655 | 656 | @Input 657 | @org.gradle.api.tasks.Optional 658 | public String getMacAppCategory() { 659 | return macAppCategory; 660 | } 661 | 662 | public void setMacAppCategory(String macAppCategory) { 663 | this.macAppCategory = macAppCategory; 664 | } 665 | 666 | @Input 667 | @org.gradle.api.tasks.Optional 668 | public String getMacEntitlements() { 669 | return macEntitlements; 670 | } 671 | 672 | public void setMacEntitlements(String macEntitlements) { 673 | this.macEntitlements = macEntitlements; 674 | } 675 | 676 | @Input 677 | @org.gradle.api.tasks.Optional 678 | public List getMacDmgContent() { 679 | return macDmgContent; 680 | } 681 | 682 | public void setMacDmgContent(List macDmgContent) { 683 | this.macDmgContent = macDmgContent; 684 | } 685 | 686 | @Input 687 | @org.gradle.api.tasks.Optional 688 | public String getLinuxPackageName() { 689 | return linuxPackageName; 690 | } 691 | 692 | public void setLinuxPackageName(String linuxPackageName) { 693 | this.linuxPackageName = linuxPackageName; 694 | } 695 | 696 | @Input 697 | @org.gradle.api.tasks.Optional 698 | public String getLinuxDebMaintainer() { 699 | return linuxDebMaintainer; 700 | } 701 | 702 | public void setLinuxDebMaintainer(String linuxDebMaintainer) { 703 | this.linuxDebMaintainer = linuxDebMaintainer; 704 | } 705 | 706 | @Input 707 | @org.gradle.api.tasks.Optional 708 | public String getLinuxMenuGroup() { 709 | return linuxMenuGroup; 710 | } 711 | 712 | public void setLinuxMenuGroup(String linuxMenuGroup) { 713 | this.linuxMenuGroup = linuxMenuGroup; 714 | } 715 | 716 | @Input 717 | @org.gradle.api.tasks.Optional 718 | public String getLinuxRpmLicenseType() { 719 | return linuxRpmLicenseType; 720 | } 721 | 722 | public void setLinuxRpmLicenseType(String linuxRpmLicenseType) { 723 | this.linuxRpmLicenseType = linuxRpmLicenseType; 724 | } 725 | 726 | @Input 727 | @org.gradle.api.tasks.Optional 728 | public String getLinuxAppRelease() { 729 | return linuxAppRelease; 730 | } 731 | 732 | public void setLinuxAppRelease(String linuxAppRelease) { 733 | this.linuxAppRelease = linuxAppRelease; 734 | } 735 | 736 | @Input 737 | @org.gradle.api.tasks.Optional 738 | public String getLinuxAppCategory() { 739 | return linuxAppCategory; 740 | } 741 | 742 | public void setLinuxAppCategory(String linuxAppCategory) { 743 | this.linuxAppCategory = linuxAppCategory; 744 | } 745 | 746 | @Input 747 | public boolean getLinuxShortcut() { 748 | return linuxShortcut; 749 | } 750 | 751 | public void setLinuxShortcut(boolean linuxShortcut) { 752 | this.linuxShortcut = linuxShortcut; 753 | } 754 | 755 | @Input 756 | public boolean getLinuxPackageDeps() { 757 | return linuxPackageDeps; 758 | } 759 | 760 | public void setLinuxPackageDeps(boolean linuxPackageDeps) { 761 | this.linuxPackageDeps = linuxPackageDeps; 762 | } 763 | 764 | @Input 765 | public List getAdditionalParameters() { 766 | return additionalParameters; 767 | } 768 | 769 | public void setAdditionalParameters(List additionalParameters) { 770 | this.additionalParameters = additionalParameters; 771 | } 772 | 773 | @Input 774 | @org.gradle.api.tasks.Optional 775 | public Map getJpackageEnvironment() { 776 | return jpackageEnvironment; 777 | } 778 | 779 | public void setJpackageEnvironment(Map jpackageEnvironment) { 780 | this.jpackageEnvironment = jpackageEnvironment; 781 | } 782 | 783 | @TaskAction 784 | public void action() { 785 | if (dryRun) { 786 | getLogger().lifecycle("Executing jpackage plugin in dry run mode"); 787 | } 788 | 789 | String jpackage = getJPackageFromToolchain() 790 | .orElseGet(() -> getJPackageFromJavaHome() 791 | .orElseThrow(() -> new GradleException("Could not detect " + EXECUTABLE))); 792 | 793 | execute(jpackage); 794 | } 795 | 796 | private Optional buildExecutablePath(String home) { 797 | String executable = home + File.separator + "bin" + File.separator + EXECUTABLE + (isWindows() ? ".exe" : ""); 798 | if (new File(executable).exists()) { 799 | return Optional.of(executable); 800 | } else { 801 | getLogger().warn("File {} does not exist", executable); 802 | return Optional.empty(); 803 | } 804 | } 805 | 806 | private Optional getJPackageFromToolchain() { 807 | getLogger().info("Looking for {} in toolchain", EXECUTABLE); 808 | try { 809 | JavaToolchainSpec toolchain = getProject().getExtensions().getByType(JavaPluginExtension.class).getToolchain(); 810 | JavaToolchainService service = getProject().getExtensions().getByType(JavaToolchainService.class); 811 | Provider defaultLauncher = service.launcherFor(toolchain); 812 | String home = defaultLauncher.get().getMetadata().getInstallationPath().getAsFile().getAbsolutePath(); 813 | 814 | getLogger().info("toolchain: " + home); 815 | return buildExecutablePath(home); 816 | } catch (Exception ex) { 817 | getLogger().info("Toolchain is not configured"); 818 | return Optional.empty(); 819 | } 820 | } 821 | 822 | private Optional getJPackageFromJavaHome() { 823 | getLogger().info("Getting {} from java.home", EXECUTABLE); 824 | String javaHome = System.getProperty("java.home"); 825 | if (javaHome == null) { 826 | getLogger().error("java.home is not set"); 827 | return Optional.empty(); 828 | } 829 | return buildExecutablePath(javaHome); 830 | } 831 | 832 | private void buildParameters(Collection parameters, int version) { 833 | addParameter(parameters, ABOUT_URL, aboutUrl, version); 834 | if (launchers != null) { 835 | for (Launcher launcher : launchers) { 836 | File launcherFile = getProject().file(launcher.getFilePath()); 837 | if (!launcherFile.exists()) { 838 | throw new GradleException("Launcher file " + launcherFile.getAbsolutePath() + " does not exist"); 839 | } 840 | addParameter(parameters, ADD_LAUNCHER, 841 | launcher.getName() + "=" + launcherFile.getAbsolutePath(), version); 842 | } 843 | } 844 | if (addModules != null && !addModules.isEmpty()) { 845 | addParameter(parameters, ADD_MODULES, String.join(",", addModules), version); 846 | } 847 | if (appContent != null) { 848 | for (Object appContentElement : appContent) { 849 | addFileParameter(parameters, APP_CONTENT, appContentElement.toString(), true, version); 850 | } 851 | } 852 | addFileParameter(parameters, APP_IMAGE, appImage, version); 853 | addParameter(parameters, APP_VERSION, appVersion, version); 854 | if (arguments != null) { 855 | for (Object arg : arguments) { 856 | addParameter(parameters, ARGUMENTS, escape(arg.toString()), version); 857 | } 858 | } 859 | addParameter(parameters, BIND_SERVICES, bindServices, version); 860 | addParameter(parameters, COPYRIGHT, copyright, version); 861 | addParameter(parameters, DESCRIPTION, appDescription, version); 862 | addFileParameter(parameters, DESTINATION, destination, false, version); 863 | if (fileAssociations != null) { 864 | for (Object association : fileAssociations) { 865 | addFileParameter(parameters, FILE_ASSOCIATIONS, association.toString(), true, version); 866 | } 867 | } 868 | addFileParameter(parameters, ICON, icon, true, version); 869 | addFileParameter(parameters, INPUT, input, true, version); 870 | addParameter(parameters, INSTALL_DIR, installDir, version); 871 | if (javaOptions != null) { 872 | for (Object option : javaOptions) { 873 | addParameter(parameters, JAVA_OPTIONS, escape(option.toString()), version); 874 | } 875 | } 876 | if (jLinkOptions != null && !jLinkOptions.isEmpty()) { 877 | addParameter(parameters, JLINK_OPTIONS, String.join(" ", jLinkOptions), version); 878 | } 879 | addParameter(parameters, LAUNCHER_AS_SERVICE, launcherAsService, version); 880 | addFileParameter(parameters, LICENSE_FILE, licenseFile, true, version); 881 | addParameter(parameters, MAIN_CLASS, mainClass, version); 882 | addParameter(parameters, MAIN_JAR, mainJar, version); 883 | addParameter(parameters, MODULE, module, version); 884 | if (modulePaths != null) { 885 | for (Object path : modulePaths) { 886 | addFileParameter(parameters, MODULE_PATH, path.toString(), version); 887 | } 888 | } 889 | addParameter(parameters, NAME, appName, version); 890 | addFileParameter(parameters, RESOURCE_DIR, resourceDir, true, version); 891 | addFileParameter(parameters, RUNTIME_IMAGE, runtimeImage, true, version); 892 | addFileParameter(parameters, TEMP, temp, false, version); 893 | if (type != ImageType.DEFAULT) { 894 | addParameter(parameters, TYPE, type, version); 895 | } 896 | addParameter(parameters, VENDOR, vendor, version); 897 | addParameter(parameters, VERBOSE, verbose, version); 898 | 899 | if (isMac()) { 900 | addParameter(parameters, MAC_APP_CATEGORY, macAppCategory, version); 901 | addParameter(parameters, MAC_APP_STORE, macAppStore, version); 902 | addParameter(parameters, MAC_BUNDLE_SIGNING_PREFIX, macBundleSigningPrefix, version); 903 | if (macDmgContent != null) { 904 | for (Object dmgContent : macDmgContent) { 905 | addFileParameter(parameters, MAC_DMG_CONTENT, dmgContent.toString(), true, version); 906 | } 907 | } 908 | addFileParameter(parameters, MAC_ENTITLEMENTS, macEntitlements, true, version); 909 | addParameter(parameters, MAC_PACKAGE_IDENTIFIER, macPackageIdentifier, version); 910 | addParameter(parameters, MAC_PACKAGE_NAME, macPackageName, version); 911 | addParameter(parameters, MAC_PACKAGE_SIGNING_PREFIX, macPackageSigningPrefix, version); 912 | addParameter(parameters, MAC_SIGN, macSign, version); 913 | addParameter(parameters, MAC_SIGNING_KEY_USER_NAME, macSigningKeyUserName, version); 914 | addFileParameter(parameters, MAC_SIGNING_KEYCHAIN, macSigningKeychain, true, version); 915 | } else if (isWindows()) { 916 | addParameter(parameters, WIN_CONSOLE, winConsole, version); 917 | addParameter(parameters, WIN_DIR_CHOOSER, winDirChooser, version); 918 | addParameter(parameters, WIN_HELP_URL, winHelpUrl, version); 919 | addParameter(parameters, WIN_MENU, winMenu, version); 920 | addParameter(parameters, WIN_MENU_GROUP, winMenuGroup, version); 921 | addParameter(parameters, WIN_PER_USER_INSTALL, winPerUserInstall, version); 922 | addParameter(parameters, WIN_SHORTCUT, winShortcut, version); 923 | addParameter(parameters, WIN_SHORTCUT_PROMPT, winShortcutPrompt, version); 924 | addParameter(parameters, WIN_UPDATE_URL, winUpdateUrl, version); 925 | addParameter(parameters, WIN_UPGRADE_UUID, winUpgradeUuid, version); 926 | } else if (isLinux()) { 927 | addParameter(parameters, LINUX_APP_CATEGORY, linuxAppCategory, version); 928 | addParameter(parameters, LINUX_APP_RELEASE, linuxAppRelease, version); 929 | addParameter(parameters, LINUX_DEB_MAINTAINER, linuxDebMaintainer, version); 930 | addParameter(parameters, LINUX_MENU_GROUP, linuxMenuGroup, version); 931 | addParameter(parameters, LINUX_PACKAGE_DEPS, linuxPackageDeps, version); 932 | addParameter(parameters, LINUX_PACKAGE_NAME, linuxPackageName, version); 933 | addParameter(parameters, LINUX_RPM_LICENSE_TYPE, linuxRpmLicenseType, version); 934 | addParameter(parameters, LINUX_SHORTCUT, linuxShortcut, version); 935 | } 936 | 937 | // Additional options 938 | for (Object option : additionalParameters) { 939 | addAdditionalParameter(parameters, option.toString()); 940 | } 941 | } 942 | 943 | private void execute(String cmd) { 944 | int version = getJDKMajorVersion(cmd); 945 | if (version == 0) { 946 | getLogger().warn("Could not determine " + EXECUTABLE + " version, parameter check will be skipped"); 947 | getLogger().info("Using: {}", cmd); 948 | } else { 949 | getLogger().info("Using: {}, major version: {}", cmd, version); 950 | } 951 | 952 | List parameters = new ArrayList<>(); 953 | parameters.add(cmd.contains(" ") ? "\"" + cmd + "\"" : cmd); 954 | buildParameters(parameters, version); 955 | 956 | if (dryRun) { 957 | return; 958 | } 959 | 960 | Path destinationPath = new File(destination).toPath().toAbsolutePath(); 961 | if (Boolean.TRUE.equals(removeDestination)) { 962 | if (!isNestedDirectory(getProject().getBuildDir().toPath(), destinationPath)) { 963 | getLogger().error("Cannot remove destination folder, must belong to {}", getProject().getBuildDir().toPath()); 964 | } else { 965 | getLogger().warn("Trying to remove destination {}", destinationPath); 966 | removeDirectory(destinationPath); 967 | } 968 | } 969 | 970 | try { 971 | ProcessBuilder processBuilder = new ProcessBuilder(); 972 | 973 | if (jpackageEnvironment != null && !jpackageEnvironment.isEmpty()) { 974 | getLogger().info(EXECUTABLE + " environment:"); 975 | 976 | Map environment = processBuilder.environment(); 977 | for (Map.Entry entry : jpackageEnvironment.entrySet()) { 978 | String key = entry.getKey(); 979 | String value = entry.getValue(); 980 | 981 | if (key == null || key.trim().isEmpty() || value == null) { 982 | // Silently skip null or empty keys or null values 983 | continue; 984 | } 985 | 986 | environment.put(key, value); 987 | getLogger().info(" " + key + " = " + value); 988 | } 989 | } 990 | 991 | Process process = processBuilder 992 | .redirectErrorStream(true) 993 | .command(parameters) 994 | .start(); 995 | 996 | getLogger().info(EXECUTABLE + " output:"); 997 | 998 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { 999 | String line; 1000 | while ((line = reader.readLine()) != null) { 1001 | getLogger().info(line); 1002 | } 1003 | } 1004 | 1005 | int status = process.waitFor(); 1006 | if (status != 0) { 1007 | throw new GradleException("Error while executing " + EXECUTABLE); 1008 | } 1009 | } catch (Exception ex) { 1010 | throw new GradleException("Error while executing " + EXECUTABLE, ex); 1011 | } 1012 | } 1013 | 1014 | /** 1015 | * Executes windows specific configuration block. 1016 | * 1017 | * @param block configuration block 1018 | */ 1019 | public void windows(Runnable block) { 1020 | if (isWindows()) { 1021 | block.run(); 1022 | } 1023 | } 1024 | 1025 | /** 1026 | * Executes OS X specific configuration block. 1027 | * 1028 | * @param block configuration block 1029 | */ 1030 | public void mac(Runnable block) { 1031 | if (isMac()) { 1032 | block.run(); 1033 | } 1034 | } 1035 | 1036 | /** 1037 | * Executes Linux specific configuration block. 1038 | * 1039 | * @param block configuration block 1040 | */ 1041 | public void linux(Runnable block) { 1042 | if (isLinux()) { 1043 | block.run(); 1044 | } 1045 | } 1046 | 1047 | private void addParameter(Collection params, String name, String value) { 1048 | if (value == null || value.isEmpty()) { 1049 | return; 1050 | } 1051 | 1052 | getLogger().info(" " + name + " " + value); 1053 | params.add(name); 1054 | params.add(value); 1055 | } 1056 | 1057 | private void addParameter(Collection params, CommandLineParameter parameter, String value, int version) { 1058 | if (value == null || value.isEmpty()) { 1059 | return; 1060 | } 1061 | 1062 | parameter.checkVersion(version); 1063 | 1064 | getLogger().info(" " + parameter.getName() + " " + value); 1065 | params.add(parameter.getName()); 1066 | params.add(value); 1067 | } 1068 | 1069 | private void addFileParameter(Collection params, CommandLineParameter parameter, String value, int version) { 1070 | addFileParameter(params, parameter, value, true, version); 1071 | } 1072 | 1073 | private void addFileParameter(Collection params, CommandLineParameter parameter, String value, boolean mustExist, int version) { 1074 | if (value == null || value.isEmpty()) { 1075 | return; 1076 | } 1077 | 1078 | parameter.checkVersion(version); 1079 | 1080 | File file = getProject().file(value); 1081 | if (mustExist && !file.exists()) { 1082 | throw new GradleException("File or directory " + file.getAbsolutePath() + " does not exist"); 1083 | } 1084 | 1085 | addParameter(params, parameter.getName(), file.getAbsolutePath()); 1086 | } 1087 | 1088 | private void addParameter(Collection params, String name, boolean value) { 1089 | if (!value) { 1090 | return; 1091 | } 1092 | 1093 | getLogger().info(" " + name); 1094 | params.add(name); 1095 | } 1096 | 1097 | private void addParameter(Collection params, CommandLineParameter parameter, boolean value, int version) { 1098 | if (!value) { 1099 | return; 1100 | } 1101 | 1102 | parameter.checkVersion(version); 1103 | 1104 | getLogger().info(" " + parameter.getName()); 1105 | params.add(parameter.getName()); 1106 | } 1107 | 1108 | private void addParameter(Collection params, CommandLineParameter parameter, EnumParameter value, int version) { 1109 | if (value == null) { 1110 | return; 1111 | } 1112 | 1113 | parameter.checkVersion(version); 1114 | 1115 | addParameter(params, parameter.getName(), value.getValue()); 1116 | } 1117 | 1118 | private void addAdditionalParameter(Collection params, String parameter) { 1119 | if (parameter == null || parameter.isEmpty()) { 1120 | return; 1121 | } 1122 | getLogger().info(" " + parameter); 1123 | params.add(parameter); 1124 | } 1125 | } 1126 | -------------------------------------------------------------------------------- /src/main/java/org/panteleyev/jpackage/Launcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) Petr Panteleyev. All rights reserved. 3 | Licensed under the BSD license. See LICENSE file in the project root for full license information. 4 | */ 5 | package org.panteleyev.jpackage; 6 | 7 | import org.gradle.api.GradleException; 8 | import java.util.Objects; 9 | 10 | public final class Launcher { 11 | private final String name; 12 | private final String filePath; 13 | 14 | public Launcher(String name, String filePath) { 15 | if (name == null || name.isEmpty() || filePath == null || filePath.isEmpty()) { 16 | throw new GradleException("Launcher parameters cannot be null or empty"); 17 | } 18 | 19 | this.name = name; 20 | this.filePath = filePath; 21 | } 22 | 23 | public String getName() { 24 | return name; 25 | } 26 | 27 | public String getFilePath() { 28 | return filePath; 29 | } 30 | 31 | @Override 32 | public int hashCode() { 33 | return Objects.hash(name, filePath); 34 | } 35 | 36 | @Override 37 | public boolean equals(Object o) { 38 | if (this == o) { 39 | return true; 40 | } 41 | 42 | if (o instanceof Launcher) { 43 | Launcher that = (Launcher) o; 44 | return Objects.equals(this.name, that.name) 45 | && Objects.equals(this.filePath, that.filePath); 46 | } else { 47 | return false; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/panteleyev/jpackage/OsUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) Petr Panteleyev. All rights reserved. 3 | Licensed under the BSD license. See LICENSE file in the project root for full license information. 4 | */ 5 | package org.panteleyev.jpackage; 6 | 7 | import static org.gradle.internal.os.OperatingSystem.current; 8 | 9 | abstract class OsUtil { 10 | static boolean isWindows() { 11 | return current().isWindows(); 12 | } 13 | 14 | static boolean isMac() { 15 | return current().isMacOsX(); 16 | } 17 | 18 | static boolean isLinux() { 19 | return current().isLinux(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/panteleyev/jpackage/StringUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Petr Panteleyev. All rights reserved. 3 | * Licensed under the BSD license. See LICENSE file in the project root for full license information. 4 | */ 5 | package org.panteleyev.jpackage; 6 | 7 | import java.util.regex.Matcher; 8 | import static org.panteleyev.jpackage.OsUtil.isWindows; 9 | 10 | abstract class StringUtil { 11 | private static final String REPLACER = Matcher.quoteReplacement(isWindows() ? "\\\\\\\"" : "\\\""); 12 | private static final String SPACE_WRAPPER = isWindows() ? "\\\"" : "\""; 13 | 14 | static String escape(String arg) { 15 | arg = arg.replaceAll("\"", REPLACER); 16 | return arg.contains(" ") ? SPACE_WRAPPER + arg + SPACE_WRAPPER : arg; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/org/panteleyev/jpackage/JDKVersionUtilTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 Petr Panteleyev 3 | SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | package org.panteleyev.jpackage; 6 | 7 | import org.junit.jupiter.params.ParameterizedTest; 8 | import org.junit.jupiter.params.provider.Arguments; 9 | import org.junit.jupiter.params.provider.MethodSource; 10 | 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | import static org.junit.jupiter.api.Assertions.assertEquals; 15 | 16 | public class JDKVersionUtilTest { 17 | 18 | private static List javaCmdArguments() { 19 | return Arrays.asList( 20 | Arguments.of("/opt/jdk-15.0.2/bin/jpackage", "/opt/jdk-15.0.2/bin/java"), 21 | Arguments.of("C:\\Program Files\\jdk-15.0.2\\bin\\jpackage.exe", 22 | "C:\\Program Files\\jdk-15.0.2\\bin\\java.exe") 23 | ); 24 | } 25 | 26 | @ParameterizedTest 27 | @MethodSource("javaCmdArguments") 28 | public void testBuildJavaExecutable(String given, String expected) { 29 | assertEquals(expected, JDKVersionUtil.buildJavaExecutable(given)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/org/panteleyev/jpackage/JPackageGradlePluginTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 Petr Panteleyev 3 | SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | package org.panteleyev.jpackage; 6 | 7 | import org.gradle.api.Project; 8 | import org.gradle.testfixtures.ProjectBuilder; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertNotNull; 12 | 13 | public class JPackageGradlePluginTest { 14 | @Test 15 | public void pluginRegistersTask() { 16 | Project project = ProjectBuilder.builder().build(); 17 | project.getPlugins().apply("org.panteleyev.jpackageplugin"); 18 | 19 | assertNotNull(project.getTasks().findByName("jpackage")); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/org/panteleyev/jpackage/LauncherTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 Petr Panteleyev 3 | SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | package org.panteleyev.jpackage; 6 | 7 | import org.gradle.api.GradleException; 8 | import org.junit.jupiter.api.Test; 9 | import org.junit.jupiter.params.ParameterizedTest; 10 | import org.junit.jupiter.params.provider.Arguments; 11 | import org.junit.jupiter.params.provider.MethodSource; 12 | 13 | import java.util.Arrays; 14 | import java.util.List; 15 | 16 | import static org.junit.jupiter.api.Assertions.*; 17 | 18 | public class LauncherTest { 19 | 20 | private static List constructorExceptions() { 21 | return Arrays.asList( 22 | Arguments.of("", ""), 23 | Arguments.of(null, null), 24 | Arguments.of("", null), 25 | Arguments.of(null, "") 26 | ); 27 | } 28 | 29 | @ParameterizedTest 30 | @MethodSource("constructorExceptions") 31 | public void testConstructorException(String name, String filePath) { 32 | assertThrows(GradleException.class, () -> { 33 | new Launcher(name, filePath); 34 | }); 35 | } 36 | 37 | @Test 38 | public void testEquals() { 39 | Launcher l1 = new Launcher("123", "345"); 40 | Launcher l2 = new Launcher("123", "345"); 41 | Launcher l3 = new Launcher("345", "123"); 42 | 43 | assertEquals(l1, l1); 44 | assertEquals(l2, l1); 45 | assertNotEquals(l3, l2); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/org/panteleyev/jpackage/StringUtilTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 Petr Panteleyev 3 | SPDX-License-Identifier: BSD-2-Clause 4 | */ 5 | package org.panteleyev.jpackage; 6 | 7 | import org.junit.jupiter.params.ParameterizedTest; 8 | import org.junit.jupiter.params.provider.Arguments; 9 | import org.junit.jupiter.params.provider.MethodSource; 10 | 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | import static org.junit.jupiter.api.Assertions.assertEquals; 15 | import static org.panteleyev.jpackage.OsUtil.isWindows; 16 | import static org.panteleyev.jpackage.StringUtil.escape; 17 | 18 | public class StringUtilTest { 19 | 20 | private static List dataProvider() { 21 | if (isWindows()) { 22 | return Arrays.asList( 23 | Arguments.of("", ""), 24 | Arguments.of("123", "123"), 25 | Arguments.of("-DAppOption=text string", "\\\"-DAppOption=text string\\\""), 26 | Arguments.of("-XX:OnError=\"userdump.exe %p\"", 27 | "\\\"-XX:OnError=\\\\\\\"userdump.exe %p\\\\\\\"\\\"") 28 | ); 29 | } else { 30 | return Arrays.asList( 31 | Arguments.of("", ""), 32 | Arguments.of("123", "123"), 33 | Arguments.of("-DAppOption=text string", "\"-DAppOption=text string\""), 34 | Arguments.of("-XX:OnError=\"userdump.exe %p\"", 35 | "\"-XX:OnError=\\\"userdump.exe %p\\\"\"") 36 | ); 37 | } 38 | } 39 | 40 | @ParameterizedTest 41 | @MethodSource("dataProvider") 42 | public void testEscape(String arg, String expected) { 43 | assertEquals(expected, escape(arg)); 44 | } 45 | } 46 | --------------------------------------------------------------------------------