--- a/make/CompileDemos.gmk Tue Nov 12 12:19:24 2019 +0100
+++ b/make/CompileDemos.gmk Tue Nov 12 10:09:40 2019 -0500
@@ -219,6 +219,11 @@
MAIN_CLASS := transparentruler.Ruler, \
))
+$(eval $(call SetupBuildDemo, JNLPConverter, \
+ DEMO_SUBDIR := jpackage, \
+ MAIN_CLASS := jnlp.converter.Main, \
+))
+
################################################################################
# Copy html and README files.
--- a/make/CompileJavaModules.gmk Tue Nov 12 12:19:24 2019 +0100
+++ b/make/CompileJavaModules.gmk Tue Nov 12 10:09:40 2019 -0500
@@ -380,6 +380,13 @@
################################################################################
+jdk.incubator.jpackage_COPY += .gif .png .txt .spec .script .prerm .preinst .postrm .postinst .list .sh \
+ .desktop .copyright .control .plist .template .icns .scpt .entitlements .wxs .wxl .wxi .ico .bmp
+
+jdk.incubator.jpackage_CLEAN += .properties
+
+################################################################################
+
jdk.jconsole_COPY += .gif .png
jdk.jconsole_CLEAN_FILES += $(wildcard \
--- a/make/common/Modules.gmk Tue Nov 12 12:19:24 2019 +0100
+++ b/make/common/Modules.gmk Tue Nov 12 10:09:40 2019 -0500
@@ -128,6 +128,7 @@
JRE_TOOL_MODULES += \
jdk.jdwp.agent \
+ jdk.incubator.jpackage \
jdk.pack \
jdk.scripting.nashorn.shell \
#
@@ -149,6 +150,7 @@
jdk.editpad \
jdk.hotspot.agent \
jdk.httpserver \
+ jdk.incubator.jpackage \
jdk.jartool \
jdk.javadoc \
jdk.jcmd \
@@ -243,6 +245,13 @@
endif
################################################################################
+# jpackage is only on windows, macosx, and linux
+
+ifeq ($(call isTargetOs, windows macosx linux), false)
+ MODULES_FILTER += jdk.incubator.jpackage
+endif
+
+################################################################################
# Module list macros
# Use append so that the custom extension may add to these variables
--- a/make/common/NativeCompilation.gmk Tue Nov 12 12:19:24 2019 +0100
+++ b/make/common/NativeCompilation.gmk Tue Nov 12 10:09:40 2019 -0500
@@ -397,6 +397,7 @@
# ARFLAGS the archiver flags to be used
# OBJECT_DIR the directory where we store the object files
# OUTPUT_DIR the directory where the resulting binary is put
+# SYMBOLS_DIR the directory where the debug symbols are put, defaults to OUTPUT_DIR
# INCLUDES only pick source from these directories
# EXCLUDES do not pick source from these directories
# INCLUDE_FILES only compile exactly these files!
@@ -533,8 +534,6 @@
$$(call SetIfEmpty, $1_SYSROOT_CFLAGS, $$($$($1_TOOLCHAIN)_SYSROOT_CFLAGS))
$$(call SetIfEmpty, $1_SYSROOT_LDFLAGS, $$($$($1_TOOLCHAIN)_SYSROOT_LDFLAGS))
- # Make sure the dirs exist.
- $$(call MakeDir, $$($1_OBJECT_DIR) $$($1_OUTPUT_DIR))
$$(foreach d, $$($1_SRC), $$(if $$(wildcard $$d), , \
$$(error SRC specified to SetupNativeCompilation $1 contains missing directory $$d)))
@@ -911,30 +910,31 @@
ifeq ($$($1_COPY_DEBUG_SYMBOLS), true)
ifneq ($$($1_DEBUG_SYMBOLS), false)
+ $$(call SetIfEmpty, $1_SYMBOLS_DIR, $$($1_OUTPUT_DIR))
# Only copy debug symbols for dynamic libraries and programs.
ifneq ($$($1_TYPE), STATIC_LIBRARY)
# Generate debuginfo files.
ifeq ($(call isTargetOs, windows), true)
- $1_EXTRA_LDFLAGS += -debug "-pdb:$$($1_OUTPUT_DIR)/$$($1_NOSUFFIX).pdb" \
- "-map:$$($1_OUTPUT_DIR)/$$($1_NOSUFFIX).map"
- $1_DEBUGINFO_FILES := $$($1_OUTPUT_DIR)/$$($1_NOSUFFIX).pdb \
- $$($1_OUTPUT_DIR)/$$($1_NOSUFFIX).map
+ $1_EXTRA_LDFLAGS += -debug "-pdb:$$($1_SYMBOLS_DIR)/$$($1_NOSUFFIX).pdb" \
+ "-map:$$($1_SYMBOLS_DIR)/$$($1_NOSUFFIX).map"
+ $1_DEBUGINFO_FILES := $$($1_SYMBOLS_DIR)/$$($1_NOSUFFIX).pdb \
+ $$($1_SYMBOLS_DIR)/$$($1_NOSUFFIX).map
else ifeq ($(call isTargetOs, linux solaris), true)
- $1_DEBUGINFO_FILES := $$($1_OUTPUT_DIR)/$$($1_NOSUFFIX).debuginfo
+ $1_DEBUGINFO_FILES := $$($1_SYMBOLS_DIR)/$$($1_NOSUFFIX).debuginfo
# Setup the command line creating debuginfo files, to be run after linking.
# It cannot be run separately since it updates the original target file
$1_CREATE_DEBUGINFO_CMDS := \
$$($1_OBJCOPY) --only-keep-debug $$($1_TARGET) $$($1_DEBUGINFO_FILES) $$(NEWLINE) \
- $(CD) $$($1_OUTPUT_DIR) && \
+ $(CD) $$($1_SYMBOLS_DIR) && \
$$($1_OBJCOPY) --add-gnu-debuglink=$$($1_DEBUGINFO_FILES) $$($1_TARGET)
else ifeq ($(call isTargetOs, macosx), true)
$1_DEBUGINFO_FILES := \
- $$($1_OUTPUT_DIR)/$$($1_BASENAME).dSYM/Contents/Info.plist \
- $$($1_OUTPUT_DIR)/$$($1_BASENAME).dSYM/Contents/Resources/DWARF/$$($1_BASENAME)
+ $$($1_SYMBOLS_DIR)/$$($1_BASENAME).dSYM/Contents/Info.plist \
+ $$($1_SYMBOLS_DIR)/$$($1_BASENAME).dSYM/Contents/Resources/DWARF/$$($1_BASENAME)
$1_CREATE_DEBUGINFO_CMDS := \
- $(DSYMUTIL) --out $$($1_OUTPUT_DIR)/$$($1_BASENAME).dSYM $$($1_TARGET)
+ $(DSYMUTIL) --out $$($1_SYMBOLS_DIR)/$$($1_BASENAME).dSYM $$($1_TARGET)
endif
# Since the link rule creates more than one file that we want to track,
@@ -956,14 +956,14 @@
$1 += $$($1_DEBUGINFO_FILES)
ifeq ($$($1_ZIP_EXTERNAL_DEBUG_SYMBOLS), true)
- $1_DEBUGINFO_ZIP := $$($1_OUTPUT_DIR)/$$($1_NOSUFFIX).diz
+ $1_DEBUGINFO_ZIP := $$($1_SYMBOLS_DIR)/$$($1_NOSUFFIX).diz
$1 += $$($1_DEBUGINFO_ZIP)
# The dependency on TARGET is needed for debuginfo files
# to be rebuilt properly.
$$($1_DEBUGINFO_ZIP): $$($1_DEBUGINFO_FILES) $$($1_TARGET)
- $(CD) $$($1_OUTPUT_DIR) && \
- $(ZIPEXE) -q -r $$@ $$(subst $$($1_OUTPUT_DIR)/,, $$($1_DEBUGINFO_FILES))
+ $(CD) $$($1_SYMBOLS_DIR) && \
+ $(ZIPEXE) -q -r $$@ $$(subst $$($1_SYMBOLS_DIR)/,, $$($1_DEBUGINFO_FILES))
endif
endif # !STATIC_LIBRARY
@@ -999,6 +999,7 @@
$$($1_TARGET): $$($1_TARGET_DEPS)
$$(call LogInfo, Building static library $$($1_BASENAME))
+ $$(call MakeDir, $$($1_OUTPUT_DIR) $$($1_SYMBOLS_DIR))
$$(call ExecuteWithLog, $$($1_OBJECT_DIR)/$$($1_SAFE_NAME)_link, \
$$($1_AR) $$($1_ARFLAGS) $(AR_OUT_OPTION)$$($1_TARGET) $$($1_ALL_OBJS) \
$$($1_RES))
@@ -1100,7 +1101,9 @@
# Keep as much as possible on one execution line for best performance
# on Windows
$$(call LogInfo, Linking $$($1_BASENAME))
+ $$(call MakeDir, $$($1_OUTPUT_DIR) $$($1_SYMBOLS_DIR))
ifeq ($(call isTargetOs, windows), true)
+
$$(call ExecuteWithLog, $$($1_OBJECT_DIR)/$$($1_SAFE_NAME)_link, \
$$($1_LD) $$($1_LDFLAGS) $$($1_EXTRA_LDFLAGS) $$($1_SYSROOT_LDFLAGS) \
$(LD_OUT_OPTION)$$($1_TARGET) $$($1_LD_OBJ_ARG) $$($1_RES) $$(GLOBAL_LIBS) \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/make/launcher/Launcher-jdk.incubator.jpackage.gmk Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,30 @@
+#
+# Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+
+include LauncherCommon.gmk
+
+$(eval $(call SetupBuildLauncher, jpackage, \
+ MAIN_CLASS := jdk.incubator.jpackage.main.Main, \
+))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/make/lib/Lib-jdk.incubator.jpackage.gmk Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,140 @@
+#
+# Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+
+include LibCommon.gmk
+
+################################################################################
+
+# Output app launcher library in resources dir, and symbols in the object dir
+$(eval $(call SetupJdkLibrary, BUILD_LIB_APPLAUNCHER, \
+ NAME := applauncher, \
+ OUTPUT_DIR := $(JDK_OUTPUTDIR)/modules/$(MODULE)/jdk/incubator/jpackage/internal/resources, \
+ SYMBOLS_DIR := $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/libapplauncher, \
+ TOOLCHAIN := TOOLCHAIN_LINK_CXX, \
+ OPTIMIZATION := LOW, \
+ CFLAGS := $(CXXFLAGS_JDKLIB), \
+ CFLAGS_windows := -EHsc -DUNICODE -D_UNICODE, \
+ LDFLAGS := $(LDFLAGS_JDKLIB) $(LDFLAGS_CXX_JDK) \
+ $(call SET_SHARED_LIBRARY_ORIGIN), \
+ LIBS := $(LIBCXX), \
+ LIBS_windows := user32.lib shell32.lib advapi32.lib ole32.lib, \
+ LIBS_linux := -ldl -lpthread, \
+ LIBS_macosx := -ldl -framework Cocoa, \
+))
+
+$(BUILD_LIB_APPLAUNCHER): $(call FindLib, java.base, java)
+
+TARGETS += $(BUILD_LIB_APPLAUNCHER)
+
+JPACKAGE_APPLAUNCHER_SRC := \
+ $(TOPDIR)/src/jdk.incubator.jpackage/$(OPENJDK_TARGET_OS)/native/jpackageapplauncher
+
+# Output app launcher executable in resources dir, and symbols in the object dir
+$(eval $(call SetupJdkExecutable, BUILD_JPACKAGE_APPLAUNCHEREXE, \
+ NAME := jpackageapplauncher, \
+ OUTPUT_DIR := $(JDK_OUTPUTDIR)/modules/$(MODULE)/jdk/incubator/jpackage/internal/resources, \
+ SYMBOLS_DIR := $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/jpackageapplauncher, \
+ SRC := $(JPACKAGE_APPLAUNCHER_SRC), \
+ TOOLCHAIN := TOOLCHAIN_LINK_CXX, \
+ OPTIMIZATION := LOW, \
+ CFLAGS := $(CXXFLAGS_JDKEXE), \
+ CFLAGS_windows := -EHsc -DLAUNCHERC -DUNICODE -D_UNICODE, \
+ LDFLAGS := $(LDFLAGS_JDKEXE), \
+ LIBS_macosx := -framework Cocoa, \
+ LIBS := $(LIBCXX), \
+ LIBS_linux := -ldl, \
+ LIBS_windows := user32.lib shell32.lib advapi32.lib, \
+))
+
+TARGETS += $(BUILD_JPACKAGE_APPLAUNCHEREXE)
+
+################################################################################
+
+ifeq ($(call isTargetOs, windows), true)
+
+ $(eval $(call SetupJdkLibrary, BUILD_LIB_JPACKAGE, \
+ NAME := jpackage, \
+ OPTIMIZATION := LOW, \
+ CFLAGS := $(CXXFLAGS_JDKLIB), \
+ CFLAGS_windows := -EHsc -DUNICODE -D_UNICODE, \
+ LDFLAGS := $(LDFLAGS_JDKLIB) $(LDFLAGS_CXX_JDK) \
+ $(call SET_SHARED_LIBRARY_ORIGIN), \
+ LIBS := $(LIBCXX), \
+ LIBS_windows := user32.lib shell32.lib advapi32.lib ole32.lib, \
+ ))
+
+ TARGETS += $(BUILD_LIB_JPACKAGE)
+
+ # Build Wix custom action helper
+ # Output library in resources dir, and symbols in the object dir
+ $(eval $(call SetupJdkLibrary, BUILD_LIB_WIXHELPER, \
+ NAME := wixhelper, \
+ OUTPUT_DIR := $(JDK_OUTPUTDIR)/modules/$(MODULE)/jdk/incubator/jpackage/internal/resources, \
+ SYMBOLS_DIR := $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/libwixhelper, \
+ OPTIMIZATION := LOW, \
+ CFLAGS := $(CXXFLAGS_JDKLIB), \
+ CFLAGS_windows := -EHsc -DUNICODE -D_UNICODE -MT, \
+ LDFLAGS := $(LDFLAGS_JDKLIB) $(LDFLAGS_CXX_JDK), \
+ LIBS := $(LIBCXX), \
+ LIBS_windows := msi.lib Shlwapi.lib User32.lib, \
+ ))
+
+ TARGETS += $(BUILD_LIB_WIXHELPER)
+
+ # Build exe installer wrapper for msi installer
+ $(eval $(call SetupJdkExecutable, BUILD_JPACKAGE_MSIWRAPPER, \
+ NAME := msiwrapper, \
+ OUTPUT_DIR := $(JDK_OUTPUTDIR)/modules/$(MODULE)/jdk/incubator/jpackage/internal/resources, \
+ SYMBOLS_DIR := $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/msiwrapper, \
+ SRC := $(TOPDIR)/src/jdk.incubator.jpackage/$(OPENJDK_TARGET_OS)/native/msiwrapper, \
+ EXTRA_FILES := $(addprefix $(TOPDIR)/src/jdk.incubator.jpackage/$(OPENJDK_TARGET_OS)/native/libjpackage/, \
+ FileUtils.cpp Log.cpp WinSysInfo.cpp tstrings.cpp WinErrorHandling.cpp ErrorHandling.cpp), \
+ CFLAGS := $(CXXFLAGS_JDKEXE) -MT \
+ $(addprefix -I$(TOPDIR)/src/jdk.incubator.jpackage/$(OPENJDK_TARGET_OS)/native/, msiwrapper libjpackage), \
+ CFLAGS_windows := -EHsc -DUNICODE -D_UNICODE, \
+ LDFLAGS := $(LDFLAGS_JDKEXE), \
+ LIBS := $(LIBCXX), \
+ ))
+
+ TARGETS += $(BUILD_JPACKAGE_MSIWRAPPER)
+
+ # Build non-console version of launcher
+ $(eval $(call SetupJdkExecutable, BUILD_JPACKAGE_APPLAUNCHERWEXE, \
+ NAME := jpackageapplauncherw, \
+ OUTPUT_DIR := $(JDK_OUTPUTDIR)/modules/$(MODULE)/jdk/incubator/jpackage/internal/resources, \
+ SYMBOLS_DIR := $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/jpackageapplauncherw, \
+ SRC := $(JPACKAGE_APPLAUNCHER_SRC), \
+ TOOLCHAIN := TOOLCHAIN_LINK_CXX, \
+ OPTIMIZATION := LOW, \
+ CFLAGS := $(CXXFLAGS_JDKEXE), \
+ CFLAGS_windows := -EHsc -DUNICODE -D_UNICODE, \
+ LDFLAGS := $(LDFLAGS_JDKEXE), \
+ LIBS := $(LIBCXX), \
+ LIBS_windows := user32.lib shell32.lib advapi32.lib, \
+ ))
+
+ TARGETS += $(BUILD_JPACKAGE_APPLAUNCHERWEXE)
+
+endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/README.txt Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,23 @@
+About JNLPConverter
+===================
+
+JNLPConverter is a standalone tool which uses jpackage to create bundles from
+Java Web Start(TM) Applications and helps to migrate from JNLP to jpackage.
+JNLPConverter will locate and use the jpackage tool from the same JDK as used
+to run JNLPConverter. JNLPConverter supports HTTP/HTTPS and FILE protocol.
+
+Running JNLPConverter
+=====================
+
+To run the JNLPConverter:
+
+ java -jar JNLPConverter.jar <mode> <options>
+
+To get help on JNLPConverter options:
+
+ java -jar JNLPConverter.jar --help
+
+These instructions assume that this installation's version of the java command
+is in your path. If it isn't, then you should either specify the complete path
+to the java command or update your PATH environment variable as described
+in the installation instructions for the Java(TM) SE Development Kit.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/HTTPHelper.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jnlp.converter;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import jnlp.converter.parser.GeneralUtil;
+
+public class HTTPHelper {
+
+ public static final int BUFFER_SIZE = 4096;
+
+ public static String downloadFile(String url, String destFolder, String destFileName) throws MalformedURLException, IOException {
+ HttpURLConnection connection = null;
+ String destFile = null;
+
+ try {
+ if (url.contains(" ")) {
+ url = url.replace(" ", "%20");
+ }
+ if (url.contains("\\")) {
+ url = url.replace("\\", "/");
+ }
+
+ URL resource = new URL(url);
+ connection = (HttpURLConnection) resource.openConnection();
+
+ int responseCode = connection.getResponseCode();
+ if (responseCode == HttpURLConnection.HTTP_OK) {
+ destFile = destFolder + File.separator + destFileName;
+ Log.verbose("Downloading " + url + " to " + destFile);
+
+ try (InputStream inputStream = connection.getInputStream();
+ OutputStream outputStream = new FileOutputStream(destFile)) {
+ byte[] buffer = new byte[BUFFER_SIZE];
+
+ int length;
+ do {
+ length = inputStream.read(buffer);
+ if (length > 0) {
+ outputStream.write(buffer, 0, length);
+ }
+ } while (length > 0);
+ }
+ } else {
+ HTTPHelperException e = new HTTPHelperException("Error: Cannot download " + url + ". Server response code: " + responseCode);
+ e.setResponseCode(responseCode);
+ throw e;
+ }
+ } catch (IOException e) {
+ if (e instanceof HTTPHelperException) {
+ throw e;
+ } else {
+ throw new HTTPHelperException("Error: Cannot download " + url + ". " + e.getClass().getSimpleName() + ": " + e.getMessage());
+ }
+ } finally {
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
+
+ return destFile;
+ }
+
+ public static String copyFile(String url, String destFolder, String destFileName) throws Exception {
+ if (url.contains(" ")) {
+ url = url.replace(" ", "%20");
+ }
+
+ URI sourceURI = new URI(url);
+
+ String sourceFile = sourceURI.getPath();
+ File file = new File(sourceFile);
+ if (!file.exists()) {
+ throw new FileNotFoundException("Error: " + sourceFile + " does not exist.");
+ }
+
+ String destFile = destFolder + File.separator + destFileName;
+ file = new File(destFile);
+ if (file.exists()) {
+ file.delete();
+ }
+
+ Path sourcePath = Paths.get(sourceURI);
+ Path destPath = Paths.get(destFile);
+ Log.verbose("Copying " + url + " to " + destFile);
+ Files.copy(sourcePath, destPath);
+
+ return destFile;
+ }
+
+ public static boolean isHTTPUrl(String url) {
+ return (url.startsWith("http://") || url.startsWith("https://"));
+ }
+
+ public static byte[] getJNLPBits(String versionedJNLP, String jnlp) throws Exception {
+ String jnlpFilePath = null;
+ byte[] bits = null;
+
+ if (isHTTPUrl(jnlp)) {
+ try {
+ jnlpFilePath = downloadFile(versionedJNLP, JNLPConverter.getJnlpDownloadFolderStatic(), getFileNameFromURL(jnlp));
+ } catch (HTTPHelperException ex) {
+ if (ex.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND &&
+ !versionedJNLP.equals(jnlp)) {
+ Log.warning("Downloading versioned JNLP from " + versionedJNLP + " failed.");
+ Log.warning(ex.getMessage());
+ Log.warning("Downloading " + jnlp + " instead.");
+ jnlpFilePath = downloadFile(jnlp, JNLPConverter.getJnlpDownloadFolderStatic(), getFileNameFromURL(jnlp));
+ } else {
+ throw ex;
+ }
+ }
+ JNLPConverter.markFileToDelete(jnlpFilePath);
+ } else {
+ try {
+ jnlpFilePath = copyFile(versionedJNLP, JNLPConverter.getJnlpDownloadFolderStatic(), getFileNameFromURL(jnlp));
+ } catch (FileNotFoundException ex) {
+ System.out.println("Error copying versioned JNLP from " + versionedJNLP);
+ System.out.println(ex.getMessage());
+ System.out.println("Copying " + jnlp + " instead.");
+ jnlpFilePath = HTTPHelper.copyFile(jnlp, JNLPConverter.getJnlpDownloadFolderStatic(), getFileNameFromURL(jnlp));
+ }
+ JNLPConverter.markFileToDelete(jnlpFilePath);
+ }
+
+ File jnlpFile = new File(jnlpFilePath);
+ if (jnlpFile.exists()) {
+ bits = GeneralUtil.readBytes(new FileInputStream(jnlpFile), jnlpFile.length());
+ }
+
+ return bits;
+ }
+
+ public static String getFileNameFromURL(String url) throws IOException {
+ int index;
+ int index1 = url.lastIndexOf('/');
+ int index2 = url.lastIndexOf('\\');
+
+ if (index1 >= index2) {
+ index = index1;
+ } else {
+ index = index2;
+ }
+
+ if (index != -1) {
+ String name = url.substring(index + 1, url.length());
+ name = name.replace("%20", " ");
+ if (name.endsWith(".jnlp") || name.endsWith(".jar")) { // JNLP or JAR
+ return name;
+ } else if (name.endsWith(".ico")) { // Icons
+ return name;
+ } else {
+ throw new IOException("Error: Unsupported file extension for " + url);
+ }
+ } else {
+ throw new IOException("Error: URL (" + url + ") should end with file name.");
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/HTTPHelperException.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jnlp.converter;
+
+import java.io.IOException;
+
+public class HTTPHelperException extends IOException {
+ private int responseCode = -1;
+
+ public HTTPHelperException(String msg) {
+ super(msg);
+ }
+
+ public void setResponseCode(int code) {
+ responseCode = code;
+ }
+
+ public int getResponseCode() {
+ return responseCode;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/JNLPConverter.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,841 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jnlp.converter;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import jnlp.converter.parser.JNLPDesc;
+import jnlp.converter.parser.JNLPDesc.AssociationDesc;
+import jnlp.converter.parser.JNLPDesc.IconDesc;
+import jnlp.converter.parser.ResourcesDesc.JARDesc;
+import jnlp.converter.parser.XMLFormat;
+
+public class JNLPConverter {
+
+ private final Options options;
+ private JNLPDesc jnlpd = null;
+ private final List<String> launchArgs = new ArrayList<>();
+
+ private String downloadFolder = null;
+ private String jnlpDownloadFolder = null;
+ private static String jnlpDownloadFolderStatic;
+ private String jarDownloadFolder = null;
+ private String iconDownloadFolder = null;
+ private String propDownloadFolder = null;
+
+ private static String jpackagePath = null;
+
+ private static boolean markFileToDelete = false;
+
+ private static final String FA_EXTENSIONS = "extension";
+ private static final String FA_CONTENT_TYPE = "mime-type";
+ private static final String FA_DESCRIPTION = "description";
+ private static final String FA_ICON = "icon";
+
+ public JNLPConverter(Options options) {
+ this.options = options;
+ jnlpDownloadFolderStatic = getJnlpDownloadFolder();
+ markFileToDelete = (options.keep() == null);
+ }
+
+ public String [] getLaunchArgs() {
+ return launchArgs.toArray(new String[0]);
+ }
+
+ public void convert() {
+ try {
+ loadJNLPDesc();
+ downloadResources();
+ validate();
+ buildLaunchArgs();
+ saveLaunchArgs();
+ runJPackage();
+ } catch (Exception ex) {
+ Log.error(ex.getMessage());
+ }
+ }
+
+ private JNLPDesc getJNLPD(String jnlp) throws Exception {
+ URL codebase = getCodeBase(jnlp);
+ byte[] bits = HTTPHelper.getJNLPBits(jnlp, jnlp);
+ return XMLFormat.parse(bits, codebase, jnlp);
+ }
+
+ private void loadJNLPDesc() throws Exception {
+ String jnlp = options.getJNLP();
+ jnlpd = getJNLPD(jnlp);
+
+ // Check for required options in case of FX
+ if (jnlpd.isFXApp()) {
+ if (!options.isRuntimeImageSet()) {
+ throw new Exception("This is a JavaFX Web-Start application which requires a runtime image capable of running JavaFX applications, which can be specified by the jpackage option --runtime-image (using --jpackage-options).");
+ }
+ }
+
+ // Check href. It can be same as URL we provided or new one
+ // if JNLP has different href or codebase. We assume that
+ // XMLFormat.parse() will handle any errors in href and codebase
+ // correctly.
+ String href = jnlpd.getHref();
+ if (href != null && !href.equalsIgnoreCase(jnlp)) {
+ if (href.startsWith("file:")) {
+ URI hrefURI = new URI(href);
+ URI jnlpURI = new URI(jnlp);
+
+ String hrefPath = hrefURI.getPath();
+ String jnlpPath = jnlpURI.getPath();
+
+ if (!hrefPath.equalsIgnoreCase(jnlpPath)) {
+ jnlp = href;
+ jnlpd = getJNLPD(jnlp);
+ }
+ } else {
+ jnlp = href;
+ jnlpd = getJNLPD(jnlp);
+ }
+ }
+
+ if (jnlpd.getName() == null) {
+ jnlpd.setName(getNameFromURL(jnlp));
+ }
+ }
+
+ private static String getNameFromURL(String url) throws IOException {
+ int index;
+ int index1 = url.lastIndexOf('/');
+ int index2 = url.lastIndexOf('\\');
+
+ if (index1 >= index2) {
+ index = index1;
+ } else {
+ index = index2;
+ }
+
+ if (index != -1) {
+ String name = url.substring(index + 1, url.length());
+ if (name.endsWith(".jnlp")) {
+ return name.substring(0, name.length() - 5);
+ }
+ }
+
+ return null;
+ }
+
+ private URL getCodeBase(String jnlp) throws Exception {
+ int index = jnlp.lastIndexOf('/');
+ if (index != -1) {
+ if (HTTPHelper.isHTTPUrl(jnlp)) {
+ return new URL(jnlp.substring(0, index + 1));
+ } else {
+ String codeBasePath = jnlp.substring(0, index);
+ if (!codeBasePath.endsWith("/")) {
+ codeBasePath += "/";
+ }
+ return new URI(codeBasePath).toURL();
+ }
+ }
+
+ return null;
+ }
+
+ public static void markFileToDelete(String file) {
+ if (file == null || file.isEmpty()) {
+ return;
+ }
+
+ if (markFileToDelete) {
+ try {
+ File f = new File(file);
+ f.deleteOnExit();
+ } catch (Exception e) {
+ // Print exception, but do not fail conversion.
+ Log.warning(e.getLocalizedMessage());
+ }
+ }
+ }
+
+ public static void deleteFile(String file) {
+ try {
+ File f = new File(file);
+ f.delete();
+ } catch (Exception e) {
+ Log.warning(e.getLocalizedMessage());
+ }
+ }
+
+ private void downloadResources() throws Exception {
+ List<JARDesc> jars = jnlpd.getResources();
+ for (JARDesc jar : jars) {
+ if (jar.getVersion() != null) {
+ if (!jnlpd.isVersionEnabled()) {
+ throw new Exception("Error: Version based download protocol is not supported without -Djnlp.versionEnabled=true.");
+ }
+ }
+
+ String destFile = null;
+ if (HTTPHelper.isHTTPUrl(jar.getLocation().toString())) {
+ if (jar.getVersion() != null) {
+ try {
+ destFile = HTTPHelper.downloadFile(jar.getVersionLocation().toString(), getJarDownloadFolder(), HTTPHelper.getFileNameFromURL(jar.getLocation().toString()));
+ } catch (HTTPHelperException ex) {
+ if (ex.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
+ System.out.println("Error downloading versioned JAR from " + jar.getVersionLocation());
+ System.out.println(ex.getMessage());
+ System.out.println("Downloading " + jar.getLocation() + " instead.");
+ destFile = HTTPHelper.downloadFile(jar.getLocation().toString(), getJarDownloadFolder(), HTTPHelper.getFileNameFromURL(jar.getLocation().toString()));
+ } else {
+ throw ex;
+ }
+ }
+ } else {
+ destFile = HTTPHelper.downloadFile(jar.getLocation().toString(), getJarDownloadFolder(), HTTPHelper.getFileNameFromURL(jar.getLocation().toString()));
+ }
+ markFileToDelete(destFile);
+ } else {
+ if (jar.getVersion() != null) {
+ try {
+ destFile = HTTPHelper.copyFile(jar.getVersionLocation().toString(), getJarDownloadFolder(), HTTPHelper.getFileNameFromURL(jar.getLocation().toString()));
+ } catch (FileNotFoundException ex) {
+ System.out.println("Error copying versioned JAR from " + jar.getVersionLocation());
+ System.out.println(ex.getMessage());
+ System.out.println("Copying " + jar.getLocation() + " instead.");
+ destFile = HTTPHelper.copyFile(jar.getLocation().toString(), getJarDownloadFolder(), HTTPHelper.getFileNameFromURL(jar.getLocation().toString()));
+ }
+ } else {
+ destFile = HTTPHelper.copyFile(jar.getLocation().toString(), getJarDownloadFolder(), HTTPHelper.getFileNameFromURL(jar.getLocation().toString()));
+ }
+ markFileToDelete(destFile);
+ }
+
+ if (jar.isNativeLib()) {
+ unpackNativeLib(destFile);
+ deleteFile(destFile);
+ } else {
+ jnlpd.addFile(jar.getName());
+ }
+ }
+
+ IconDesc icon = jnlpd.getIcon();
+ if (icon != null) {
+ String destFile;
+
+ if (HTTPHelper.isHTTPUrl(icon.getLocation())) {
+ destFile = HTTPHelper.downloadFile(icon.getLocation(), getIconDownloadFolder(), HTTPHelper.getFileNameFromURL(icon.getLocation()));
+ } else {
+ destFile = HTTPHelper.copyFile(icon.getLocation(), getIconDownloadFolder(), HTTPHelper.getFileNameFromURL(icon.getLocation()));
+ }
+
+ markFileToDelete(destFile);
+ icon.setLocalLocation(destFile);
+ }
+
+ AssociationDesc [] associations = jnlpd.getAssociations();
+ if (associations != null) {
+ for (AssociationDesc association : associations) {
+ if (association.getIconUrl() != null) {
+ String destFile;
+ if (HTTPHelper.isHTTPUrl(association.getIconUrl())) {
+ destFile = HTTPHelper.downloadFile(association.getIconUrl(), getIconDownloadFolder(), HTTPHelper.getFileNameFromURL(association.getIconUrl()));
+ } else {
+ destFile = HTTPHelper.copyFile(association.getIconUrl(), getIconDownloadFolder(), HTTPHelper.getFileNameFromURL(association.getIconUrl()));
+ }
+
+ markFileToDelete(destFile);
+ association.setIconLocalLocation(destFile);
+ }
+ }
+ }
+ }
+
+ public void unpackNativeLib(String file) throws IOException {
+ try (JarFile jarFile = new JarFile(file)) {
+ Enumeration entries = jarFile.entries();
+
+ while (entries.hasMoreElements()) {
+ JarEntry entry = (JarEntry) entries.nextElement();
+
+ // Skip directories
+ if (entry.isDirectory()) {
+ continue;
+ }
+
+ String entryName = entry.getName();
+ // Skip anything in sub-directories
+ if (entryName.contains("\\") || entryName.contains("/")) {
+ continue;
+ }
+
+ // Skip anything not ending with .dll, .dylib or .so
+ if (!entryName.endsWith(".dll") && !entryName.endsWith(".dylib") && !entryName.endsWith(".so")) {
+ continue;
+ }
+
+ File destFile = new File(getJarDownloadFolder(), entryName);
+ if (destFile.exists()) {
+ Log.warning(destFile.getAbsolutePath() + " already exist and will not be overwriten by native library from " + file + ".");
+ continue;
+ }
+
+ InputStream inputStream = jarFile.getInputStream(entry);
+ FileOutputStream outputStream = new FileOutputStream(destFile);
+
+ byte[] buffer = new byte[HTTPHelper.BUFFER_SIZE];
+ int length;
+ do {
+ length = inputStream.read(buffer);
+ if (length > 0) {
+ outputStream.write(buffer, 0, length);
+ }
+ } while (length > 0);
+
+ jnlpd.addFile(entryName);
+ }
+ }
+ }
+
+ private void validate() {
+ if (jnlpd.getMainJar() == null) {
+ Log.error("Cannot find main jar");
+ }
+
+ if (jnlpd.getMainClass() == null) {
+ Log.error("Cannot find main class");
+ }
+ }
+
+ private void addLaunchArg(String arg, List<String> launchArgs) {
+ if (arg != null && !arg.isEmpty()) {
+ if (!options.isOptionPresent(arg)){
+ launchArgs.add(arg);
+ } else {
+ Log.info(arg + " generated by JNLPConverter is dropped, since it is overwriten via --jpackage-options");
+ }
+ }
+ }
+
+ private void addLaunchArg(String arg, String value, List<String> launchArgs) {
+ if (arg != null && !arg.isEmpty() && value != null && !value.isEmpty()) {
+ if (!options.isOptionPresent(arg)){
+ launchArgs.add(arg);
+ launchArgs.add(value);
+ } else {
+ Log.info(arg + "=" + value +" generated by JNLPConverter is dropped, since it is overwriten via --jpackage-options");
+ }
+ }
+ }
+
+ private void displayLaunchArgs() {
+ if (Log.isVerbose()) {
+ System.out.println();
+ System.out.println("jpackage launch arguments (each argument starts on new line):");
+ launchArgs.forEach((arg) -> {
+ System.out.println(arg);
+ });
+ }
+ }
+
+ private static int fileAssociationsCount = 0;
+ private String getFileAssociationsFile() {
+ String file = getPropDownloadFolder();
+ file += File.separator;
+ file += "fileAssociation";
+ file += String.valueOf(fileAssociationsCount);
+ file += ".properties";
+
+ fileAssociationsCount++;
+
+ return file;
+ }
+
+ private void buildLaunchArgs() {
+ if (options.createAppImage()) {
+ addLaunchArg("create-app-image", launchArgs);
+ } else if (options.createInstaller()) {
+ if (options.getInstallerType() == null) {
+ addLaunchArg("create-installer", launchArgs);
+ } else {
+ addLaunchArg("create-installer", launchArgs);
+ if (options.getInstallerType() != null) {
+ addLaunchArg("--installer-type", options.getInstallerType(), launchArgs);
+ }
+ }
+ }
+
+ // Set verbose for jpackage if it is set for us.
+ if (options.verbose()) {
+ addLaunchArg("--verbose", launchArgs);
+ }
+
+ addLaunchArg("--input", getJarDownloadFolder(), launchArgs);
+ addLaunchArg("--output", options.getOutput(), launchArgs);
+ addLaunchArg("--name", jnlpd.getName(), launchArgs);
+ addLaunchArg("--app-version", jnlpd.getVersion(), launchArgs);
+ addLaunchArg("--vendor", jnlpd.getVendor(), launchArgs);
+ addLaunchArg("--description", jnlpd.getDescription(), launchArgs);
+ addLaunchArg("--icon", jnlpd.getIconLocation(), launchArgs);
+ addLaunchArg("--main-jar", jnlpd.getMainJar(), launchArgs);
+ addLaunchArg("--main-class", jnlpd.getMainClass(), launchArgs);
+
+ addArguments(launchArgs);
+ addJVMArgs(launchArgs);
+
+ if (options.createInstaller()) {
+ if (jnlpd.isDesktopHint()) {
+ if (Platform.isWindows()) {
+ addLaunchArg("--win-shortcut", launchArgs);
+ } else {
+ Log.warning("Ignoring shortcut hint, since it is not supported on current platform.");
+ }
+ }
+
+ if (jnlpd.isMenuHint()) {
+ if (Platform.isWindows()) {
+ addLaunchArg("--win-menu", launchArgs);
+ addLaunchArg("--win-menu-group", jnlpd.getSubMenu(), launchArgs);
+ } else {
+ Log.warning("Ignoring menu hint, since it is not supported on current platform.");
+ }
+ }
+
+ AssociationDesc[] associations = jnlpd.getAssociations();
+ if (associations != null) {
+ for (AssociationDesc association : associations) {
+ String file = getFileAssociationsFile();
+ markFileToDelete(file);
+
+ try (PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)))) {
+ if (association.getExtensions() != null && association.getMimeType() != null) {
+ out.println(FA_EXTENSIONS + "=" + quote(association.getExtensions()));
+ out.println(FA_CONTENT_TYPE + "=" + quote(association.getMimeType()));
+
+ if (association.getMimeDescription() != null) {
+ out.println(FA_DESCRIPTION + "=" + association.getMimeDescription());
+ }
+
+ if (association.getIconLocalLocation() != null) {
+ out.println(FA_ICON + "=" + quote(association.getIconLocalLocation()));
+ }
+
+ addLaunchArg("--file-associations", file, launchArgs);
+ }
+ } catch (Exception ex) {
+ Log.warning(ex.toString());
+ if (association.getExtensions() != null) {
+ Log.warning("File assoication for " + association.getExtensions() + " will be ignored due to exception above.");
+ }
+ }
+ }
+ }
+ }
+
+ // Add options from --jpackage-options
+ List<String> jpackageOptions = options.getJPackageOptions();
+ jpackageOptions.forEach((option) -> {
+ launchArgs.add(option);
+ });
+
+ displayLaunchArgs();
+ }
+
+ private String getCommandFileName() {
+ Platform platform = Platform.getPlatform();
+ switch (platform) {
+ case WINDOWS:
+ return "run_jpackage.bat";
+ case LINUX:
+ return "run_jpackage.sh";
+ case MAC:
+ return "run_jpackage.sh";
+ default:
+ Log.error("Cannot determine platform type.");
+ return "";
+ }
+ }
+
+ private void saveLaunchArgs() {
+ if (options.keep() != null) {
+ File keepFolder = new File(options.keep());
+ String cmdFile = keepFolder.getAbsolutePath() + File.separator + getCommandFileName();
+ try (PrintWriter out = new PrintWriter(cmdFile)) {
+ out.print(getJPackagePath());
+ launchArgs.forEach((arg) -> {
+ out.print(" ");
+
+ if (arg.contains(" ")) {
+ int len = arg.length();
+ if (len >= 1) {
+ if (arg.charAt(0) != '"' && arg.charAt(len - 1) != '"') {
+ out.print("\"" + arg + "\"");
+ } else {
+ if (Platform.isWindows()) {
+ out.print(arg);
+ } else {
+ arg = escapeQuote(arg);
+ out.print("\"" + arg + "\"");
+ }
+ }
+ }
+ } else {
+ out.print(arg);
+ }
+ });
+ } catch (FileNotFoundException ex) {
+ Log.error("Cannot save file with command line: " + ex.getLocalizedMessage());
+ }
+ }
+ }
+
+ private void runJPackage() {
+ List<String> command = new ArrayList<>();
+ command.add(getJPackagePath());
+ command.addAll(launchArgs);
+
+ ProcessBuilder builder = new ProcessBuilder();
+ builder.inheritIO();
+ builder.command(command);
+
+ try {
+ Process process = builder.start();
+ int exitCode = process.waitFor();
+ if (exitCode != 0) {
+ Log.warning("jpackage retrun non zero code: " + exitCode);
+ }
+ } catch (IOException | InterruptedException ex) {
+ Log.error(ex.getMessage());
+ }
+ }
+
+ private void addArguments(List<String> launchArgs) {
+ List<String> arguments = jnlpd.getArguments();
+ if (arguments.isEmpty()) {
+ return;
+ }
+
+ String argsStr = "";
+ for (int i = 0; i < arguments.size(); i++) {
+ String arg = arguments.get(i);
+ argsStr += quote(arg);
+ if ((i + 1) != arguments.size()) {
+ argsStr += " ";
+ }
+ }
+
+ launchArgs.add("--arguments");
+ if (Platform.isWindows()) {
+ if (argsStr.contains(" ")) {
+ if (argsStr.contains("\"")) {
+ argsStr = escapeQuote(argsStr);
+ }
+ argsStr = "\"" + argsStr + "\"";
+ }
+ }
+ launchArgs.add(argsStr);
+ }
+
+ private void addJVMArgs(List<String> launchArgs) {
+ List<String> jvmArgs = jnlpd.getVMArgs();
+ if (jvmArgs.isEmpty()) {
+ return;
+ }
+
+ String jvmArgsStr = "";
+ for (int i = 0; i < jvmArgs.size(); i++) {
+ String arg = jvmArgs.get(i);
+ jvmArgsStr += quote(arg);
+ if ((i + 1) != jvmArgs.size()) {
+ jvmArgsStr += " ";
+ }
+ }
+
+ launchArgs.add("--java-options");
+ if (Platform.isWindows()) {
+ if (jvmArgsStr.contains(" ")) {
+ if (jvmArgsStr.contains("\"")) {
+ jvmArgsStr = escapeQuote(jvmArgsStr);
+ }
+ jvmArgsStr = "\"" + jvmArgsStr + "\"";
+ }
+ }
+ launchArgs.add(jvmArgsStr);
+ }
+
+ private String quote(String in) {
+ if (in == null) {
+ return null;
+ }
+
+ if (in.isEmpty()) {
+ return "";
+ }
+
+ if (!in.contains("=")) {
+ // Not a property
+ if (in.contains(" ")) {
+ in = escapeQuote(in);
+ return "\"" + in + "\"";
+ }
+ return in;
+ }
+
+ if (!in.contains(" ")) {
+ return in; // No need to quote
+ }
+
+ int paramIndex = in.indexOf("=");
+ if (paramIndex <= 0) {
+ return in; // Something wrong, just skip quoting
+ }
+
+ String param = in.substring(0, paramIndex);
+ String value = in.substring(paramIndex + 1);
+
+ if (value.length() == 0) {
+ return in; // No need to quote
+ }
+
+ value = escapeQuote(value);
+
+ return param + "=" + "\"" + value + "\"";
+ }
+
+ private String escapeQuote(String in) {
+ if (in == null) {
+ return null;
+ }
+
+ if (in.isEmpty()) {
+ return "";
+ }
+
+ if (in.contains("\"")) {
+ // Use code points to preserve non-ASCII chars
+ StringBuilder sb = new StringBuilder();
+ int codeLen = in.codePointCount(0, in.length());
+ for (int i = 0; i < codeLen; i++) {
+ int code = in.codePointAt(i);
+ // Note: No need to escape '\' on Linux or OS X.
+ // jpackage expects us to pass arguments and properties with quotes and spaces as a map
+ // with quotes being escaped with additional \ for internal quotes.
+ // So if we want two properties below:
+ // -Djnlp.Prop1=Some "Value" 1
+ // -Djnlp.Prop2=Some Value 2
+ // jpackage will need:
+ // "-Djnlp.Prop1=\"Some \\"Value\\" 1\" -Djnlp.Prop2=\"Some Value 2\""
+ // but since we using ProcessBuilder to run jpackage we will need to escape
+ // our escape symbols as well, so we will need to pass string below to ProcessBuilder:
+ // "-Djnlp.Prop1=\\\"Some \\\\\\\"Value\\\\\\\" 1\\\" -Djnlp.Prop2=\\\"Some Value 2\\\""
+ switch (code) {
+ case '"':
+ // " -> \" -> \\\"
+ if (i == 0 || in.codePointAt(i - 1) != '\\') {
+ sb.appendCodePoint('\\');
+ sb.appendCodePoint(code);
+ }
+ break;
+ case '\\':
+ // We need to escape already escaped symbols as well
+ if ((i + 1) < codeLen) {
+ int nextCode = in.codePointAt(i + 1);
+ if (nextCode == '"') {
+ // \" -> \\\"
+ sb.appendCodePoint('\\');
+ sb.appendCodePoint('\\');
+ sb.appendCodePoint('\\');
+ sb.appendCodePoint(nextCode);
+ } else {
+ sb.appendCodePoint('\\');
+ sb.appendCodePoint(code);
+ }
+ } else {
+ sb.appendCodePoint(code);
+ }
+ break;
+ default:
+ sb.appendCodePoint(code);
+ break;
+ }
+ }
+ return sb.toString();
+ }
+
+ return in;
+ }
+
+ public synchronized String getDownloadFolder() {
+ if (downloadFolder == null) {
+ try {
+ File file;
+ if (options.keep() == null) {
+ Path path = Files.createTempDirectory("JNLPConverter");
+ file = path.toFile();
+ file.deleteOnExit();
+ } else {
+ file = new File(options.keep());
+ if (!file.exists()) {
+ file.mkdir();
+ }
+ }
+
+ downloadFolder = file.getAbsolutePath();
+ } catch (IOException e) {
+ Log.error(e.getMessage());
+ }
+ }
+
+ return downloadFolder;
+ }
+
+ public final synchronized String getJnlpDownloadFolder() {
+ if (jnlpDownloadFolder == null) {
+ File file = new File(getDownloadFolder() + File.separator + "jnlp");
+ file.mkdir();
+ markFileToDelete(getDownloadFolder() + File.separator + "jnlp");
+ jnlpDownloadFolder = file.getAbsolutePath();
+ }
+
+ return jnlpDownloadFolder;
+ }
+
+ public static String getJnlpDownloadFolderStatic() {
+ return jnlpDownloadFolderStatic;
+ }
+
+ public synchronized String getJarDownloadFolder() {
+ if (jarDownloadFolder == null) {
+ File file = new File(getDownloadFolder() + File.separator + "jar");
+ file.mkdir();
+ markFileToDelete(getDownloadFolder() + File.separator + "jar");
+ jarDownloadFolder = file.getAbsolutePath();
+ }
+
+ return jarDownloadFolder;
+ }
+
+ public synchronized String getIconDownloadFolder() {
+ if (iconDownloadFolder == null) {
+ File file = new File(getDownloadFolder() + File.separator + "icon");
+ file.mkdir();
+ markFileToDelete(getDownloadFolder() + File.separator + "icon");
+ iconDownloadFolder = file.getAbsolutePath();
+ }
+
+ return iconDownloadFolder;
+ }
+
+ public synchronized String getPropDownloadFolder() {
+ if (propDownloadFolder == null) {
+ File file = new File(getDownloadFolder() + File.separator + "prop");
+ file.mkdir();
+ markFileToDelete(getDownloadFolder() + File.separator + "prop");
+ propDownloadFolder = file.getAbsolutePath();
+ }
+
+ return propDownloadFolder;
+ }
+
+ public synchronized static String getJPackagePath() {
+ if (jpackagePath == null) {
+ jpackagePath = System.getProperty("java.home");
+ jpackagePath += File.separator;
+ jpackagePath += "bin";
+ jpackagePath += File.separator;
+
+ Platform platform = Platform.getPlatform();
+ switch (platform) {
+ case WINDOWS:
+ jpackagePath += "jpackage.exe";
+ break;
+ case LINUX:
+ jpackagePath += "jpackage";
+ break;
+ case MAC:
+ jpackagePath += "jpackage";
+ break;
+ default:
+ Log.error("Cannot determine platform type.");
+ break;
+ }
+
+ Log.verbose("jpackage: " + jpackagePath);
+ }
+
+ return jpackagePath;
+ }
+
+ public static String getIconFormat(String icon) {
+ // GIF, JPEG, ICO, or PNG
+ if (icon.toLowerCase().endsWith(".gif")) {
+ return "GIF";
+ } else if (icon.toLowerCase().endsWith(".jpg")) {
+ return "JPEG";
+ } else if (icon.toLowerCase().endsWith(".ico")) {
+ return "ICO";
+ } else if (icon.toLowerCase().endsWith(".png")) {
+ return "PNG";
+ }
+
+ return "UNKNOWN";
+ }
+
+ public static boolean isIconSupported(String icon) {
+ Platform platform = Platform.getPlatform();
+ switch (platform) {
+ case WINDOWS:
+ if (icon.endsWith(".ico")) {
+ return true;
+ } else {
+ Log.warning("Icon file format (" + getIconFormat(icon) + ") is not supported on Windows for file " + icon + ".");
+ return false;
+ }
+ case LINUX:
+ if (icon.endsWith(".png")) {
+ return true;
+ } else {
+ Log.warning("Icon file format (" + getIconFormat(icon) + ") is not supported on Linux for file " + icon + ".");
+ return false;
+ }
+ case MAC:
+ Log.warning("Icon file format (" + getIconFormat(icon) + ") is not supported on OS X for file " + icon + ".");
+ return false;
+ }
+
+ return false;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/Log.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
+ */
+package jnlp.converter;
+
+public class Log {
+ private static boolean verbose = false;
+
+ public static void setVerbose(boolean verbose) {
+ Log.verbose = verbose;
+ }
+
+ public static boolean isVerbose() {
+ return verbose;
+ }
+
+ public static void verbose(String msg) {
+ if (verbose) {
+ System.out.println(msg);
+ }
+ }
+
+ public static void info(String msg) {
+ System.out.println("Info: " + msg);
+ }
+
+ public static void warning(String msg) {
+ System.err.println("Warning: " + msg);
+ }
+
+ public static void error(String msg) {
+ System.err.println("Error: " + msg);
+ System.exit(1);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/Main.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jnlp.converter;
+
+import java.io.File;
+
+public class Main {
+
+ private static void showHelp() {
+ Options.showHelp();
+ }
+
+ private static void showVersion() {
+ System.out.println("Version: 1.0");
+ }
+
+ private static void createBundle(Options options) {
+ Log.verbose("Creating bundle for JNLP: " + options.getJNLP());
+ Log.verbose("Output folder: " + options.getOutput());
+
+ JNLPConverter converter = new JNLPConverter(options);
+ converter.convert();
+ }
+
+ private static void validateJDK() {
+ String jpackagePath = JNLPConverter.getJPackagePath();
+ File file = new File(jpackagePath);
+ if (!file.exists()) {
+ Log.error("Cannot find " + jpackagePath + ". Make sure you running JNLPConverter with supported JDK version.");
+ }
+ }
+
+ public static void main(String[] args) {
+ Options options = Options.parseArgs(args); // Only valid options will be returned
+
+ Log.setVerbose(options.verbose());
+
+ validateJDK();
+
+ if (options.help()) {
+ showHelp();
+ } else if (options.version()) {
+ showVersion();
+ } else {
+ createBundle(options);
+ }
+
+ System.exit(0);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/Options.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,373 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jnlp.converter;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Options {
+
+ private boolean createAppImage = false;
+ private boolean createInstaller = false;
+ private String installerType = null;
+ private String jnlp = null;
+ private String output = null;
+ private String keep = null;
+ private boolean help = false;
+ private boolean verbose = false;
+ private boolean version = false;
+ private final List<String> jpackageOptions = new ArrayList<>();
+ private boolean isRuntimeImageSet = false;
+
+ private static final String JNLP_OPTION_PREFIX = "--jnlp=";
+ private static final String OUTPUT_OPTION_PREFIX = "--output=";
+ private static final String KEEP_OPTION_PREFIX = "--keep=";
+ private static final String JNLP_OPTION_SHORT_PREFIX = "-j";
+ private static final String OUTPUT_OPTION_SHORT_PREFIX = "-o";
+ private static final String KEEP_OPTION_SHORT_PREFIX = "-k";
+
+ private static final String [] INSTALLER_TYPES = {"msi", "exe", "dmg", "pkg",
+ "rpm", "deb"};
+
+ // --output, -o, --input, -i, --main-jar, --main-class
+ private static final String [] BLOCKED_JPACKAGE_OPTIONS = {"--output", "-o", "--input", "-i",
+ "--main-jar", "--main-class"};
+
+ private static final String RUNTIME_IMAGE_OPTION = "--runtime-image";
+
+ private static final String ERR_UNKNOWN_OPTION = "Unknown option: ";
+ private static final String ERR_MISSING_VALUE = "Value is required for option ";
+ private static final String ERR_MISSING_MODE = "Error: create-app-image or create-installer mode is required";
+ private static final String ERR_MISSING_JNLP = "Error: --jnlp is required";
+ private static final String ERR_MISSING_OUTPUT = "Error: --output is required";
+ private static final String ERR_OUTPUT_EXISTS = "Error: output folder already exists";
+ private static final String ERR_KEEP_EXISTS = "Error: folder for --keep argument already exists";
+ private static final String ERR_INVALID_PROTOCOL_JNLP = "Error: Invalid protocol for JNLP file. Only HTTP, HTTPS and FILE protocols are supported.";
+
+ public boolean createAppImage() {
+ return createAppImage;
+ }
+
+ public boolean createInstaller() {
+ return createInstaller;
+ }
+
+ public String getInstallerType() {
+ return installerType;
+ }
+
+ public String getJNLP() {
+ return jnlp;
+ }
+
+ public String getOutput() {
+ return output;
+ }
+
+ public String keep() {
+ return keep;
+ }
+
+ public boolean help() {
+ return help;
+ }
+
+ public boolean verbose() {
+ return verbose;
+ }
+
+ public boolean version() {
+ return version;
+ }
+
+ public List<String> getJPackageOptions() {
+ return jpackageOptions;
+ }
+
+ public boolean isRuntimeImageSet() {
+ return isRuntimeImageSet;
+ }
+
+ // Helper method to dump all options
+ private void display() {
+ System.out.println("Options:");
+ System.out.println("createAppImage: " + createAppImage);
+ System.out.println("createInstaller: " + createInstaller);
+ System.out.println("installerType: " + installerType);
+ System.out.println("jnlp: " + jnlp);
+ System.out.println("output: " + output);
+ System.out.println("keep: " + keep);
+ System.out.println("help: " + help);
+ System.out.println("verbose: " + verbose);
+ System.out.println("version: " + version);
+ for (int i = 0; i < jpackageOptions.size(); i++) {
+ System.out.println("jpackageOptions[" + i + "]: " + jpackageOptions.get(i));
+ }
+ }
+
+ private void validate() {
+ if (help || version) {
+ return;
+ }
+
+ if (!createAppImage && !createInstaller) {
+ optionError(ERR_MISSING_MODE);
+ }
+
+ if (jnlp == null) {
+ optionError(ERR_MISSING_JNLP);
+ } else {
+ int index = jnlp.indexOf(":");
+ if (index == -1 || index == 0) {
+ optionError(ERR_INVALID_PROTOCOL_JNLP);
+ } else {
+ String protocol = jnlp.substring(0, index);
+ if (!protocol.equalsIgnoreCase("http") &&
+ !protocol.equalsIgnoreCase("https") &&
+ !protocol.equalsIgnoreCase("file")) {
+ optionError(ERR_INVALID_PROTOCOL_JNLP);
+ }
+ }
+ }
+
+ if (output == null) {
+ optionError(ERR_MISSING_OUTPUT);
+ } else {
+ File file = new File(output);
+ if (file.exists()) {
+ optionErrorNoHelp(ERR_OUTPUT_EXISTS);
+ }
+ }
+
+ if (keep != null) {
+ File file = new File(keep);
+ if (file.exists()) {
+ optionErrorNoHelp(ERR_KEEP_EXISTS);
+ }
+ }
+
+ jpackageOptions.forEach((option) -> {
+ if (isBlockedOption(option)) {
+ Log.error(option + " is not allowed via --jpackage-options, since it will conflict with "
+ + "same option generated by JNLPConverter.");
+ }
+ });
+ }
+
+ public boolean isOptionPresent(String option) {
+ for (String jpackageOption : jpackageOptions) {
+ if (jpackageOption.equalsIgnoreCase(option)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean isBlockedOption(String option) {
+ for (String blockedOption : BLOCKED_JPACKAGE_OPTIONS) {
+ if (blockedOption.equalsIgnoreCase(option)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static void showHelp() {
+// System.out.println("********* Help should not be longer then 80 characters as per JEP-293 *********");
+ System.out.println("Usage: java -jar JNLPConverter.jar <mode> <options>");
+ System.out.println("");
+ System.out.println("where mode is one of:");
+ System.out.println(" create-app-image");
+ System.out.println(" Generates a platform-specific application image.");
+ System.out.println(" create-installer");
+ System.out.println(" Generates a platform-specific installer for the application.");
+ System.out.println("");
+ System.out.println("Possible options include:");
+ System.out.println(" -j, --jnlp <path>");
+ System.out.println(" Full path to JNLP file. Supported protocols are HTTP/HTTPS/FILE.");
+ System.out.println(" -o, --output <path>");
+ System.out.println(" Name of the directory where generated output files are placed.");
+ System.out.println(" -k, --keep <path>");
+ System.out.println(" Keep JNLP, JARs and command line arguments for jpackage");
+ System.out.println(" in directory provided.");
+ System.out.println(" --jpackage-options <options>");
+ System.out.println(" Specify additional jpackage options or overwrite provided by JNLPConverter.");
+ System.out.println(" All jpackage options can be specified except: --output -o, --input -i,");
+ System.out.println(" --main-jar -j and --class -c.");
+ System.out.println(" --installer-type <type>");
+ System.out.println(" The type of the installer to create");
+ System.out.println(" Valid values are: {\"exe\", \"msi\", \"rpm\", \"deb\", \"pkg\", \"dmg\"}");
+ System.out.println(" If this option is not specified (in create-installer mode) all");
+ System.out.println(" supported types of installable packages for the current");
+ System.out.println(" platform will be created.");
+ System.out.println(" -h, --help, -?");
+ System.out.println(" Print this help message");
+ System.out.println(" -v, --verbose");
+ System.out.println(" Enable verbose output.");
+ System.out.println(" --version");
+ System.out.println(" Version information.");
+ System.out.println("To specify an argument for a long option, you can use --<name>=<value> or");
+ System.out.println("--<name> <value>.");
+ System.out.println("To specify proxy server use standard Java properties http.proxyHost and http.proxyPort.");
+ }
+
+ private static boolean isInstallerType(String type) {
+ for (String installerType : INSTALLER_TYPES) {
+ if (installerType.equals(type)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static Options parseArgs(String[] args) {
+ Options options = new Options();
+
+ int index = 0;
+ if (args.length >= 1) {
+ switch (args[0]) {
+ case "create-app-image":
+ options.createAppImage = true;
+ index = 1;
+ break;
+ case "create-installer":
+ options.createInstaller = true;
+ index = 1;
+ break;
+ case "-h":
+ case "--help":
+ case "-?":
+ case "--version":
+ break;
+ default:
+ optionError(Options.ERR_MISSING_MODE);
+ break;
+ }
+ }
+
+ for (int i = index; i < args.length; i++) {
+ String arg = args[i];
+
+ if (arg.equals("--jnlp")) {
+ if (++i >= args.length) {
+ optionError(Options.ERR_MISSING_VALUE, "--jnlp");
+ }
+ options.jnlp = args[i];
+ } else if (arg.startsWith(JNLP_OPTION_PREFIX)) {
+ options.jnlp = arg.substring(JNLP_OPTION_PREFIX.length());
+ } else if (arg.equals("--output")) {
+ if (++i >= args.length) {
+ optionError(Options.ERR_MISSING_VALUE, "--output");
+ }
+ options.output = args[i];
+ } else if (arg.startsWith(OUTPUT_OPTION_PREFIX)) {
+ options.output = arg.substring(OUTPUT_OPTION_PREFIX.length());
+ } else if (arg.equals("--keep")) {
+ if (++i >= args.length) {
+ optionError(Options.ERR_MISSING_VALUE, "--keep");
+ }
+ options.keep = args[i];
+ } else if (arg.startsWith(KEEP_OPTION_PREFIX)) {
+ options.keep = arg.substring(KEEP_OPTION_PREFIX.length());
+ } else if (arg.equals("--help")) {
+ options.help = true;
+ } else if (arg.equals("--verbose")) {
+ options.verbose = true;
+ } else if (arg.equals("--version")) {
+ options.version = true;
+ } else if (arg.equals("-j")) { // short options
+ if (++i >= args.length) {
+ optionError(Options.ERR_MISSING_VALUE, "-j");
+ }
+ options.jnlp = args[i];
+ } else if (arg.startsWith(JNLP_OPTION_SHORT_PREFIX)) {
+ options.jnlp = arg.substring(JNLP_OPTION_SHORT_PREFIX.length());
+ } else if (arg.equals("-o")) {
+ if (++i >= args.length) {
+ optionError(Options.ERR_MISSING_VALUE, "-o");
+ }
+ options.output = args[i];
+ } else if (arg.startsWith(OUTPUT_OPTION_SHORT_PREFIX)) {
+ options.output = arg.substring(OUTPUT_OPTION_SHORT_PREFIX.length());
+ } else if (arg.equals("-k")) {
+ if (++i >= args.length) {
+ optionError(Options.ERR_MISSING_VALUE, "-k");
+ }
+ options.keep = args[i];
+ } else if (arg.startsWith(KEEP_OPTION_SHORT_PREFIX)) {
+ options.keep = arg.substring(KEEP_OPTION_SHORT_PREFIX.length());
+ } else if (arg.equals("-h") || arg.equals("-?")) {
+ options.help = true;
+ } else if (arg.equals("-v")) {
+ options.verbose = true;
+ } else if (arg.equals("--jpackage-options")) {
+ for (i = (i + 1); i < args.length; i++) {
+ if (!options.isRuntimeImageSet) {
+ if (args[i].equals(RUNTIME_IMAGE_OPTION)) {
+ options.isRuntimeImageSet = true;
+ }
+ }
+ options.jpackageOptions.add(args[i]);
+ }
+ } else if (arg.equals("--installer-type")) {
+ if ((i + 1) < args.length) {
+ if (isInstallerType(args[i + 1])) {
+ options.installerType = args[i + 1];
+ i++;
+ }
+ }
+ } else {
+ optionError(ERR_UNKNOWN_OPTION, arg);
+ }
+ }
+
+ //options.display(); // For testing only
+ options.validate();
+
+ return options;
+ }
+
+ private static void optionErrorNoHelp(String msg) {
+ System.out.println(msg);
+ System.exit(1);
+ }
+
+ private static void optionError(String msg) {
+ System.out.println(msg);
+ System.out.println();
+ showHelp();
+ System.exit(1);
+ }
+
+ private static void optionError(String msg, String option) {
+ System.out.println(msg + option);
+ System.out.println();
+ showHelp();
+ System.exit(1);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/Platform.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jnlp.converter;
+
+import java.util.regex.Pattern;
+
+public enum Platform {
+ UNKNOWN, WINDOWS, LINUX, MAC;
+ private static final Platform platform;
+ private static final int majorVersion;
+ private static final int minorVersion;
+
+ static {
+ String os = System.getProperty("os.name").toLowerCase();
+
+ if (os.contains("win")) {
+ platform = Platform.WINDOWS;
+ } else if (os.contains("nix") || os.contains("nux")) {
+ platform = Platform.LINUX;
+ } else if (os.contains("mac")) {
+ platform = Platform.MAC;
+ } else {
+ platform = Platform.UNKNOWN;
+ }
+
+ String version = System.getProperty("os.version");
+ String[] parts = version.split(Pattern.quote("."));
+
+ if (parts.length > 0) {
+ majorVersion = Integer.parseInt(parts[0]);
+
+ if (parts.length > 1) {
+ minorVersion = Integer.parseInt(parts[0]);
+ } else {
+ minorVersion = -1;
+ }
+ } else {
+ majorVersion = -1;
+ minorVersion = -1;
+ }
+ }
+
+ private Platform() {
+ }
+
+ public static Platform getPlatform() {
+ return platform;
+ }
+
+ public static boolean isWindows() {
+ return (platform == Platform.WINDOWS);
+ }
+
+ public static int getMajorVersion() {
+ return majorVersion;
+ }
+
+ public static int getMinorVersion() {
+ return minorVersion;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/GeneralUtil.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,415 @@
+/*
+ * Copyright (c) 2006, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jnlp.converter.parser;
+
+import java.util.Locale;
+import java.util.ArrayList;
+import java.util.StringTokenizer;
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * Handy class to add some utility methods for dealing with property matching
+ * etc.
+ */
+public class GeneralUtil {
+
+ public static boolean prefixMatchStringList(String[] prefixList, String target) {
+ // No prefixes matches everything
+ if (prefixList == null) {
+ return true;
+ }
+ // No target, but a prefix list does not match anything
+ if (target == null) {
+ return false;
+ }
+ for (String prefix : prefixList) {
+ if (target.startsWith(prefix)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static String getOSArch() {
+ return System.getProperty("os.arch");
+ }
+
+ public static boolean prefixMatchArch(String[] prefixList) {
+ // No prefixes matches everything
+ if (prefixList == null) {
+ return true;
+ }
+
+ // check for the current arch
+ String arch = getOSArch();
+ for (String prefix : prefixList) {
+ if (arch.startsWith(prefix)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Converts a space delimited string to a list of strings
+ */
+ public static String[] getStringList(String str) {
+ if (str == null) {
+ return null;
+ }
+ ArrayList<String> list = new ArrayList<>();
+ int i = 0;
+ int length = str.length();
+ StringBuffer sb = null;
+ while (i < length) {
+ char ch = str.charAt(i);
+ switch (ch) {
+ case ' ':
+ // A space was hit. Add string to list
+ if (sb != null) {
+ list.add(sb.toString());
+ sb = null;
+ }
+ break;
+ case '\\':
+ // It is a delimiter. Add next character
+ if (i + 1 < length) {
+ ch = str.charAt(++i);
+ if (sb == null) {
+ sb = new StringBuffer();
+ }
+ sb.append(ch);
+ }
+ break;
+ default:
+ if (sb == null) {
+ sb = new StringBuffer();
+ } sb.append(ch);
+ break;
+ }
+ i++; // Next character
+ }
+ // Make sure to add the last part to the list too
+ if (sb != null) {
+ list.add(sb.toString());
+ }
+ if (list.isEmpty()) {
+ return null;
+ }
+ String[] results = new String[list.size()];
+ return list.toArray(results);
+ }
+
+ /**
+ * Checks if string list matches default locale
+ */
+ public static boolean matchLocale(String[] localeList, Locale locale) {
+ // No locale specified matches everything
+ if (localeList == null) {
+ return true;
+ }
+ for (String localeList1 : localeList) {
+ if (matchLocale(localeList1, locale)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks if string matches default locale
+ */
+ public static boolean matchLocale(String localeStr, Locale locale) {
+ if (localeStr == null || localeStr.length() == 0) {
+ return true;
+ }
+
+ // Compare against default locale
+ String language;
+ String country;
+ String variant;
+
+ // The locale string is of the form language_country_variant
+ StringTokenizer st = new StringTokenizer(localeStr, "_", false);
+ if (st.hasMoreElements() && locale.getLanguage().length() > 0) {
+ language = st.nextToken();
+ if (!language.equalsIgnoreCase(locale.getLanguage())) {
+ return false;
+ }
+ }
+ if (st.hasMoreElements() && locale.getCountry().length() > 0) {
+ country = st.nextToken();
+ if (!country.equalsIgnoreCase(locale.getCountry())) {
+ return false;
+ }
+ }
+ if (st.hasMoreElements() && locale.getVariant().length() > 0) {
+ variant = st.nextToken();
+ if (!variant.equalsIgnoreCase(locale.getVariant())) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static long heapValToLong(String heapValue) {
+ if (heapValue == null) {
+ return -1;
+ }
+ long multiplier = 1;
+ if (heapValue.toLowerCase().lastIndexOf('m') != -1) {
+ // units are megabytes, 1 megabyte = 1024 * 1024 bytes
+ multiplier = 1024 * 1024;
+ heapValue = heapValue.substring(0, heapValue.length() - 1);
+ } else if (heapValue.toLowerCase().lastIndexOf('k') != -1) {
+ // units are kilobytes, 1 kilobyte = 1024 bytes
+ multiplier = 1024;
+ heapValue = heapValue.substring(0, heapValue.length() - 1);
+ }
+ long theValue;
+ try {
+ theValue = Long.parseLong(heapValue);
+ theValue = theValue * multiplier;
+ } catch (NumberFormatException e) {
+ theValue = -1;
+ }
+ return theValue;
+ }
+
+ public static byte[] readBytes(InputStream is, long size) throws IOException {
+ // Sanity on file size (restrict to 1M)
+ if (size > 1024 * 1024) {
+ throw new IOException("File too large");
+ }
+
+ BufferedInputStream bis;
+ if (is instanceof BufferedInputStream) {
+ bis = (BufferedInputStream) is;
+ } else {
+ bis = new BufferedInputStream(is);
+ }
+
+ if (size <= 0) {
+ size = 10 * 1024; // Default to 10K
+ }
+ byte[] b = new byte[(int) size];
+ int n;
+ int bytesRead = 0;
+ n = bis.read(b, bytesRead, b.length - bytesRead);
+ while (n != -1) {
+ bytesRead += n;
+ // Still room in array
+ if (b.length == bytesRead) {
+ byte[] bb = new byte[b.length * 2];
+ System.arraycopy(b, 0, bb, 0, b.length);
+ b = bb;
+ }
+ // Read next line
+ n = bis.read(b, bytesRead, b.length - bytesRead);
+ }
+ bis.close();
+ is.close();
+
+ if (bytesRead != b.length) {
+ byte[] bb = new byte[bytesRead];
+ System.arraycopy(b, 0, bb, 0, bytesRead);
+ b = bb;
+ }
+ return b;
+ }
+
+ public static String getOSFullName() {
+ return System.getProperty("os.name");
+ }
+
+ /**
+ * Makes sure a URL is a path URL, i.e., ends with '/'
+ */
+ public static URL asPathURL(URL url) {
+ if (url == null) {
+ return null;
+ }
+
+ String path = url.getFile();
+ if (path != null && !path.endsWith("/")) {
+ try {
+ return new URL(url.getProtocol(),
+ url.getHost(),
+ url.getPort(),
+ url.getFile() + "/");
+ } catch (MalformedURLException mue) {
+ // Should not happen
+ }
+ }
+ // Just return same URl
+ return url;
+ }
+
+ public static Locale getDefaultLocale() {
+ return Locale.getDefault();
+ }
+
+ public static String toNormalizedString(URL u) {
+ if (u == null) {
+ return "";
+ }
+
+ try {
+ if (u.getPort() == u.getDefaultPort()) {
+ u = new URL(u.getProtocol().toLowerCase(),
+ u.getHost().toLowerCase(), -1, u.getFile());
+ } else {
+ u = new URL(u.getProtocol().toLowerCase(),
+ u.getHost().toLowerCase(), u.getPort(), u.getFile());
+ }
+ } catch (MalformedURLException ex) {
+ }
+ return u.toExternalForm();
+ }
+
+ public static boolean sameURLs(URL u1, URL u2) {
+ if (u1 == null || u2 == null || (u1 == u2)) {
+ return (u1 == u2);
+ }
+ //NB: do not use URL.sameFile() as it will do DNS lookup
+ // Also, do quick check before slow string comparisons
+ String f1 = u1.getFile();
+ String f2 = u2.getFile();
+ return (f1.length() == f2.length()) && sameBase(u1, u2)
+ && f1.equalsIgnoreCase(f2);
+ }
+
+ public static boolean sameBase(URL u1, URL u2) {
+ return u1 != null && u2 != null &&
+ sameHost(u1, u2) && samePort(u1, u2) && sameProtocol(u1, u2);
+ }
+
+ private static boolean sameProtocol(URL u1, URL u2) {
+ //protocols are known to be lowercase
+ return u1.getProtocol().equals(u2.getProtocol());
+ }
+
+ private static boolean sameHost(URL u1, URL u2) {
+ String host = u1.getHost();
+ String otherHost = u2.getHost();
+ if (host == null || otherHost == null) {
+ return (host == null && otherHost == null);
+ } else {
+ //avoid slow comparison for strings of different length
+ return ((host.length() == otherHost.length())
+ && host.equalsIgnoreCase(otherHost));
+ }
+ }
+
+ private static boolean samePort(URL u1, URL u2) {
+ return getPort(u1) == getPort(u2);
+ }
+
+ public static int getPort(URL u) {
+ if (u.getPort() != -1) {
+ return u.getPort();
+ } else {
+ return u.getDefaultPort();
+ }
+ }
+
+ public static URL getBase(URL url) {
+ if (url == null) return null;
+ String file = url.getFile();
+ if (file != null) {
+ int idx = file.lastIndexOf('/');
+ if (idx != -1 ) {
+ file = file.substring(0, idx + 1);
+ }
+ try {
+ return new URL(
+ url.getProtocol(),
+ url.getHost(),
+ url.getPort(),
+ file);
+ } catch(MalformedURLException mue) {
+ System.err.println(mue.getMessage());
+ }
+ }
+ // Just return same URL
+ return url;
+ }
+
+ private static String getEmbeddedVersionPath(String path, String version) {
+ int index = path.lastIndexOf("/");
+ String filename = path.substring(index + 1);
+ path = path.substring(0, index + 1);
+
+ String ext = null;
+ index = filename.lastIndexOf(".");
+ if (index != -1) {
+ ext = filename.substring(index + 1);
+ filename = filename.substring(0, index);
+ }
+
+ StringBuilder filenameSB = new StringBuilder(filename);
+ if (version != null) {
+ filenameSB.append("__V");
+ filenameSB.append(version);
+ }
+ if (ext != null) {
+ filenameSB.append(".");
+ filenameSB.append(ext);
+ }
+
+ path += filenameSB.toString();
+ return path;
+ }
+
+ public static URL getEmbeddedVersionURL(URL u, String version) throws Exception {
+ if (u == null) {
+ return null;
+ }
+
+ if (version == null || version.indexOf("*") != -1
+ || version.indexOf("+") != -1) {
+ // Do not support * or + in version string
+ return u;
+ }
+
+ URL versionURL = null;
+
+ String protocol = u.getProtocol();
+ String host = u.getHost();
+ int port = u.getPort();
+ String path = u.getPath();
+
+ path = getEmbeddedVersionPath(path, version);
+
+ versionURL = new URL(protocol, host, port, path);
+
+ return versionURL;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/JNLPDesc.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,617 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jnlp.converter.parser;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Properties;
+import jnlp.converter.Log;
+
+import jnlp.converter.parser.ResourcesDesc.JARDesc;
+import jnlp.converter.parser.ResourcesDesc.JREDesc;
+
+public class JNLPDesc {
+ private String specVersion = null;
+ private String codebase = null;
+ private String version = null;
+ private String href = null;
+ private String name = null;
+ private String title = null;
+ private String vendor = null;
+ private String mainJar = null;
+ private String [] descriptions = null;
+ private IconDesc [] icons = null;
+ private ShortcutDesc shortcuts = null;
+ private AssociationDesc [] associations = null;
+ private String mainClass = null;
+ private final List<String> arguments = new ArrayList<>();
+ private final List<String> files = new ArrayList<>();
+ private final List<JARDesc> resources = new ArrayList<>();
+ private final List<String> vmArgs = new ArrayList<>();
+ private boolean isLibrary = false;
+ private boolean isInstaller = false;
+ private boolean isJRESet = false;
+ private ResourcesDesc resourcesDesc;
+ private boolean isVersionEnabled = false;
+ private boolean isSandbox = true;
+ private boolean isFXApp = false;
+
+ public void setSpecVersion(String specVersion) {
+ this.specVersion = specVersion;
+
+ // Valid values are 1.0, 1.5, 6.0, 6.0.10, 6.0.18, 7.0, 8.20, 9 or a wildcard such as 1.0+.
+ if (!specVersion.startsWith("1.0") &&
+ !specVersion.startsWith("1.5") &&
+ !specVersion.startsWith("6.0") &&
+ !specVersion.startsWith("6.0.10") &&
+ !specVersion.startsWith("6.0.18") &&
+ !specVersion.startsWith("7.0") &&
+ !specVersion.startsWith("8.20") &&
+ !specVersion.startsWith("9")) {
+ System.out.println("Warning: Invalid version of the JNLP specification found: "
+ + specVersion + ". Valid values are 1.0, 1.5, 6.0, 6.0.10, 6.0.18, 7.0,"
+ + " 8.20, 9 or a wildcard such as 1.0+.");
+ }
+ }
+
+ public String getSpecVersion() {
+ return specVersion;
+ }
+
+ public void setCodebase(String codebase) {
+ this.codebase = codebase;
+ }
+
+ public String getCodebase() {
+ return codebase;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setHref(String href) {
+ this.href = href;
+ }
+
+ public String getHref() {
+ return href;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setVendor(String vendor) {
+ this.vendor = vendor;
+ }
+
+ public String getVendor() {
+ return vendor;
+ }
+
+ public void setMainJar(String mainJar) {
+ if (this.mainJar == null) {
+ this.mainJar = mainJar;
+ } else {
+ Log.warning("Main jar already set to '" + this.mainJar + "'. "
+ + "Attempt to set main jar to '" + mainJar + "' will be ignored.");
+ }
+ }
+
+ public String getMainJar() {
+ return mainJar;
+ }
+
+ public void setDescriptions(String [] descriptions) {
+ this.descriptions = descriptions;
+ }
+
+ public String getDescription() {
+ String description = null;
+ if (descriptions != null) {
+ if (descriptions[InformationDesc.DESC_DEFAULT] != null) {
+ description = descriptions[InformationDesc.DESC_DEFAULT];
+ } else if (descriptions[InformationDesc.DESC_SHORT] != null) {
+ description = descriptions[InformationDesc.DESC_SHORT];
+ } else if (descriptions[InformationDesc.DESC_ONELINE] != null) {
+ description = descriptions[InformationDesc.DESC_ONELINE];
+ } else if (descriptions[InformationDesc.DESC_TOOLTIP] != null) {
+ description = descriptions[InformationDesc.DESC_TOOLTIP];
+ }
+
+ if (description != null) {
+ if (description.contains("\r") || description.contains("\n")) {
+ Log.warning("Multiple lines of text in description is not supported and description will be converted to single line by replacing new lines with spaces.");
+ Log.warning("Original description:");
+ Log.warning(description);
+ String descs[] = description.split("\n");
+ description = "";
+ for (String desc : descs) {
+ desc = desc.trim();
+ if (desc.endsWith("\r")) { // In case new line was \r\n
+ if (desc.length() != 1) {
+ desc = desc.substring(0, desc.length() - 1);
+ } else {
+ continue;
+ }
+ }
+
+ if (desc.isEmpty()) {
+ continue;
+ }
+
+ if (!description.isEmpty()) {
+ description += " ";
+ }
+
+ description += desc;
+ }
+ Log.warning("Converted description:");
+ Log.warning(description);
+ }
+ }
+ }
+
+ return description;
+ }
+
+ public void setIcons(IconDesc [] icons) {
+ this.icons = icons;
+ }
+
+ public IconDesc getIcon() {
+ for (IconDesc icon : icons) {
+ if (icon.getKind() == IconDesc.ICON_KIND_DEFAULT) {
+ return icon;
+ }
+ }
+
+ for (IconDesc icon : icons) {
+ if (icon.getKind() == IconDesc.ICON_KIND_SHORTCUT) {
+ return icon;
+ }
+ }
+
+ return null;
+ }
+
+ public String getIconLocation() {
+ IconDesc icon = getIcon();
+ if (icon != null) {
+ return icon.getLocalLocation();
+ }
+
+ return null;
+ }
+
+ public void setShortcuts(ShortcutDesc shortcuts) {
+ this.shortcuts = shortcuts;
+ }
+
+ public boolean isDesktopHint() {
+ if (shortcuts != null) {
+ return shortcuts.getDesktop();
+ }
+
+ return false;
+ }
+
+ public boolean isMenuHint() {
+ if (shortcuts != null) {
+ return shortcuts.getMenu();
+ }
+
+ return false;
+ }
+
+ public String getSubMenu() {
+ if (shortcuts != null) {
+ return shortcuts.getSubmenu();
+ }
+
+ return null;
+ }
+
+ public void setAssociations(AssociationDesc [] associations) {
+ this.associations = associations;
+ }
+
+ public AssociationDesc [] getAssociations() {
+ return associations;
+ }
+
+ public void setMainClass(String mainClass, boolean isJavafxDesc) {
+ if (isJavafxDesc) {
+ this.mainClass = mainClass;
+ } else if (this.mainClass == null) {
+ this.mainClass = mainClass;
+ }
+ }
+
+ public String getMainClass() {
+ return mainClass;
+ }
+
+ public void addArguments(String argument) {
+ if (argument != null && !argument.isEmpty()) {
+ arguments.add(argument);
+ }
+ }
+
+ public List<String> getArguments() {
+ return arguments;
+ }
+
+ public void setProperty(String name, String value) {
+ if (name.equalsIgnoreCase("jnlp.versionEnabled") && value.equalsIgnoreCase("true")) {
+ isVersionEnabled = true;
+ return;
+ }
+
+ addVMArg("-D" + name + "=" + value);
+ }
+
+ public boolean isVersionEnabled() {
+ return isVersionEnabled;
+ }
+
+ public boolean isSandbox() {
+ return isSandbox;
+ }
+
+ public void setIsSandbox(boolean value) {
+ isSandbox = value;
+ }
+
+ public boolean isFXApp() {
+ return isFXApp;
+ }
+
+ public void setIsFXApp(boolean value) {
+ isFXApp = value;
+ }
+
+ public void addFile(String file) {
+ if (file != null) {
+ files.add(file);
+ }
+ }
+
+ public List<String> getFiles() {
+ return files;
+ }
+
+ private boolean isResourceExists(JARDesc resource) {
+ for (JARDesc r : resources) {
+ if (r.getLocation().equals(resource.getLocation())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public void addResource(JARDesc resource) {
+ if (resource != null) {
+ if (isResourceExists(resource)) {
+ Log.warning("Ignoring repeated resource " + resource.getLocation());
+ return;
+ }
+ resources.add(resource);
+ }
+ }
+
+ public List<JARDesc> getResources() {
+ return resources;
+ }
+
+ public void addVMArg(String arg) {
+ if (arg != null) {
+ vmArgs.add(arg);
+ }
+ }
+
+ public List<String> getVMArgs() {
+ return vmArgs;
+ }
+
+ public void setIsLibrary(boolean isLibrary) {
+ this.isLibrary = isLibrary;
+ }
+
+ public boolean isLibrary() {
+ return isLibrary;
+ }
+
+ public void setIsInstaller(boolean isInstaller) {
+ this.isInstaller = isInstaller;
+ }
+
+ public boolean isInstaller() {
+ return isInstaller;
+ }
+
+ public void setIsJRESet(boolean isJRESet) {
+ this.isJRESet = isJRESet;
+ }
+
+ public boolean isJRESet() {
+ return isJRESet;
+ }
+
+ public void setResourcesDesc(ResourcesDesc resourcesDesc) {
+ this.resourcesDesc = resourcesDesc;
+ }
+
+ public ResourcesDesc getResourcesDesc() {
+ return resourcesDesc;
+ }
+
+ public void parseResourceDesc() throws Exception {
+ if (resourcesDesc != null && !resourcesDesc.isEmpty()) {
+ setMainJar(resourcesDesc.getMainJar().getName());
+
+ JARDesc[] jars = resourcesDesc.getAllJarDescs();
+ for (JARDesc jar : jars) {
+ addResource(jar);
+ }
+
+ JREDesc jreDesc = resourcesDesc.getJreDesc();
+ if (jreDesc != null) {
+ String [] args = jreDesc.getVmArgsList();
+ if (args != null) {
+ for (String arg : args) {
+ addVMArg(arg);
+ }
+ }
+
+ if (jreDesc.getMinHeap() != -1) {
+ addVMArg("-Xms" + jreDesc.getMinHeap());
+ }
+
+ if (jreDesc.getMaxHeap() != -1) {
+ addVMArg("-Xmx" + jreDesc.getMaxHeap());
+ }
+ }
+
+ Properties props = resourcesDesc.getResourceProperties();
+ Enumeration e = props.propertyNames();
+ while (e.hasMoreElements()) {
+ String key = (String) e.nextElement();
+ String value = props.getProperty(key);
+ setProperty(key, value);
+ }
+ }
+ }
+
+ public static class InformationDesc {
+
+ private final String _title;
+ private final String _vendor;
+ private final String[] _descriptions;
+ private final IconDesc[] _icons;
+ private ShortcutDesc _shortcutHints;
+ private AssociationDesc[] _associations;
+
+ public InformationDesc(String title, String vendor,
+ String[] descriptions,
+ IconDesc[] icons,
+ ShortcutDesc shortcutHints,
+ AssociationDesc[] associations) {
+ _title = (title == null) ? "" : title;
+ _vendor = (vendor == null) ? "" : vendor;
+ if (descriptions == null) {
+ descriptions = new String[NOF_DESC];
+ }
+ _descriptions = descriptions;
+ _icons = icons;
+ _shortcutHints = shortcutHints;
+ _associations = associations;
+ }
+
+ /**
+ * Constants for the getInfoDescription
+ */
+ final public static int DESC_DEFAULT = 0;
+ final public static int DESC_SHORT = 1;
+ final public static int DESC_ONELINE = 2;
+ final public static int DESC_TOOLTIP = 3;
+ final public static int NOF_DESC = 4;
+
+ /**
+ * Information
+ */
+ public String getTitle() {
+ return _title;
+ }
+
+ public String getVendor() {
+ return _vendor;
+ }
+
+ public IconDesc[] getIcons() {
+ return _icons;
+ }
+
+ public ShortcutDesc getShortcut() {
+ if (_shortcutHints == null) {
+ return null;
+ }
+ return new ShortcutDesc(_shortcutHints.getDesktop(), _shortcutHints.getMenu(), _shortcutHints.getSubmenu());
+ }
+
+ public AssociationDesc[] getAssociations() {
+ return _associations;
+ }
+
+ /**
+ * Sets new shortcut hints.
+ *
+ * @param shortcutDesc the new shortcut hints to set
+ */
+ public void setShortcut(ShortcutDesc shortcut) {
+ _shortcutHints = shortcut;
+ }
+
+ /**
+ * Sets new associations.
+ *
+ * @param assoc the association to set
+ */
+ public void setAssociation(AssociationDesc assoc) {
+ if (assoc == null) {
+ _associations = null;
+ } else {
+ _associations = new AssociationDesc[]{assoc};
+ }
+ }
+
+ /**
+ * Returns the description of the given kind. will return null if none
+ * there
+ */
+ public String getDescription(int kind) {
+ return _descriptions[kind];
+ }
+
+ public String[] getDescription() {
+ return _descriptions;
+ }
+ }
+
+ public static class IconDesc {
+
+ private final String _location;
+ private String _localLocation;
+ private final int _kind;
+
+ final public static int ICON_KIND_DEFAULT = 0;
+ final public static int ICON_KIND_SHORTCUT = 5;
+
+ public IconDesc(URL location, int kind) {
+ _location = location.toExternalForm();
+ _kind = kind;
+ }
+
+ public String getLocation() {
+ return _location;
+ }
+
+ public void setLocalLocation(String localLocation) {
+ _localLocation = localLocation;
+ }
+
+ public String getLocalLocation() {
+ return _localLocation;
+ }
+
+ public int getKind() {
+ return _kind;
+ }
+ }
+
+ public static class ShortcutDesc {
+
+ private final boolean _desktop;
+ private final boolean _menu;
+ private final String _submenu;
+
+ public ShortcutDesc(boolean desktop, boolean menu, String submenu) {
+ _desktop = desktop;
+ _menu = menu;
+ _submenu = submenu;
+ }
+
+ public boolean getDesktop() {
+ return _desktop;
+ }
+
+ public boolean getMenu() {
+ return _menu;
+ }
+
+ public String getSubmenu() {
+ return _submenu;
+ }
+ }
+
+ public static class AssociationDesc {
+
+ private final String _extensions;
+ private final String _mimeType;
+ private final String _description;
+ private final String _icon;
+ private String _iconLocalLocation;
+
+ public AssociationDesc(String extensions, String mimeType, String description, URL icon) {
+ _extensions = extensions;
+ _mimeType = mimeType;
+ _description = description;
+ _icon = (icon != null) ? icon.toExternalForm() : null;
+ }
+
+ public void setIconLocalLocation(String localLocation) {
+ _iconLocalLocation = localLocation;
+ }
+
+ public String getIconLocalLocation() {
+ return _iconLocalLocation;
+ }
+
+ public String getExtensions() {
+ return _extensions;
+ }
+
+ public String getMimeType() {
+ return _mimeType;
+ }
+
+ public String getMimeDescription() {
+ return _description;
+ }
+
+ public String getIconUrl() {
+ return _icon;
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/ResourceType.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2006, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jnlp.converter.parser;
+
+/*
+ * Public super class for all resource entries
+ */
+interface ResourceType {
+ /** Visit this specific type */
+ void visit(ResourceVisitor visitor) throws Exception;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/ResourceVisitor.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2006, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jnlp.converter.parser;
+
+import jnlp.converter.parser.ResourcesDesc.JARDesc;
+import jnlp.converter.parser.ResourcesDesc.PropertyDesc;
+import jnlp.converter.parser.ResourcesDesc.JREDesc;
+import jnlp.converter.parser.ResourcesDesc.ExtensionDesc;
+
+/**
+ * A visitor class for the various ResourceType objects
+ * with dummy visit methods for all types.
+ */
+public class ResourceVisitor {
+
+ public void visitJARDesc(JARDesc jad) {
+ }
+
+ public void visitPropertyDesc(PropertyDesc prd) {
+ }
+
+ public void visitExtensionDesc(ExtensionDesc ed) throws Exception {
+ }
+
+ public void visitJREDesc(JREDesc jrd) {
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/ResourcesDesc.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,665 @@
+/*
+ * Copyright (c) 2006, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jnlp.converter.parser;
+
+import java.net.URL;
+import java.util.Properties;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import jnlp.converter.HTTPHelper;
+
+/**
+ * This class contains information about the codebase and properties, i.e., how
+ * to locate the classes and optional-packages
+ */
+public class ResourcesDesc implements ResourceType {
+
+ private final List<ResourceType> _list;
+ private volatile JNLPDesc _parent = null;
+
+ /**
+ * Create empty resource list
+ */
+ public ResourcesDesc() {
+ _list = new CopyOnWriteArrayList<>();
+ }
+
+ public JNLPDesc getParent() {
+ return _parent;
+ }
+
+ void setParent(JNLPDesc parent) {
+ _parent = parent;
+ for (int i = 0; i < _list.size(); i++) {
+ Object o = _list.get(i);
+ if (o instanceof JREDesc) {
+ JREDesc jredesc = (JREDesc) o;
+ if (jredesc.getNestedResources() != null) {
+ jredesc.getNestedResources().setParent(parent);
+ }
+ }
+ }
+ }
+
+ public void addResource(ResourceType rd) {
+ if (rd != null) {
+ _list.add(rd);
+ }
+ }
+
+ boolean isEmpty() {
+ return _list.isEmpty();
+ }
+
+ public JARDesc[] getLocalJarDescs() {
+ ArrayList<JARDesc> jds = new ArrayList<>(_list.size());
+ for (ResourceType rt : _list) {
+ if (rt instanceof JARDesc) {
+ jds.add((JARDesc) rt);
+ }
+ }
+ return jds.toArray(new JARDesc[jds.size()]);
+ }
+
+ public JREDesc getJreDesc() {
+ for (ResourceType rt : _list) {
+ if (rt instanceof JREDesc) {
+ return (JREDesc)rt;
+ }
+ }
+
+ return null;
+ }
+
+ public ExtensionDesc[] getExtensionDescs() throws Exception {
+ final ArrayList<ExtensionDesc> extList = new ArrayList<>();
+ visit(new ResourceVisitor() {
+ @Override
+ public void visitExtensionDesc(ExtensionDesc ed) throws Exception {
+ // add all extensiondesc recursively
+ addExtToList(extList);
+ }
+ });
+ return extList.toArray(new ExtensionDesc[extList.size()]);
+ }
+
+ public JARDesc[] getAllJarDescs() throws Exception {
+ List<JARDesc> jarList = new ArrayList<>();
+ addJarsToList(jarList);
+ return jarList.toArray(new JARDesc[jarList.size()]);
+ }
+
+ /**
+ * Add to a list of all the ExtensionDesc. This method goes recusivly through
+ * all ExtensionDesc
+ */
+ private void addExtToList(final List<ExtensionDesc> list) throws Exception {
+ // Iterate through list an add ext jnlp to the list.
+ visit(new ResourceVisitor() {
+ @Override
+ public void visitExtensionDesc(ExtensionDesc ed) throws Exception {
+ if (ed.getExtensionDesc() != null) {
+ ed.getExtensionDesc().getMainJar();
+ ResourcesDesc rd = ed.getExtensionDesc().getResourcesDesc();
+ if (rd != null) {
+ rd.addExtToList(list);
+ }
+ }
+ list.add(ed);
+ }
+ });
+ }
+
+ private void addJarsToList(final List<JARDesc> list) throws Exception {
+
+ // Iterate through list an add resources to the list.
+ // The ordering of resources are preserved
+ visit(new ResourceVisitor() {
+ @Override
+ public void visitJARDesc(JARDesc jd) {
+ list.add(jd);
+ }
+
+ @Override
+ public void visitExtensionDesc(ExtensionDesc ed) throws Exception {
+ if (ed.getExtensionDesc() != null) {
+ ResourcesDesc rd = ed.getExtensionDesc().getResourcesDesc();
+ if (rd != null) {
+ rd.addJarsToList(list);
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Get all the resources needed when a specific resource is requested.
+ * Returns null if no resource was found
+ */
+ public JARDesc[] getResource(final URL location) throws Exception {
+ final JARDesc[] resources = new JARDesc[1];
+ // Find the given resource
+ visit(new ResourceVisitor() {
+ @Override
+ public void visitJARDesc(JARDesc jd) {
+ if (GeneralUtil.sameURLs(jd.getLocation(), location)) {
+ resources[0] = jd;
+ }
+ }
+ });
+
+ // Found no resource?
+ if (resources[0] == null) {
+ return null;
+ }
+
+ // No part, so just one resource
+ return resources;
+ }
+
+ /* Returns the Expected Main Jar
+ * first jar with attribute main="true"
+ * else first jar if none has that attribute
+ * will look in extensions, and nested resource blocks if matching
+ */
+ protected JARDesc getMainJar() throws Exception {
+ // Normal trick to get around final arguments to inner classes
+ final JARDesc[] results = new JARDesc[2];
+
+ visit(new ResourceVisitor() {
+ @Override
+ public void visitJARDesc(JARDesc jd) {
+ if (jd.isJavaFile()) {
+ // Keep track of first Java File
+ if (results[0] == null || results[0].isNativeLib()) {
+ results[0] = jd;
+ }
+ // Keep tack of Java File marked main
+ if (jd.isMainJarFile()) {
+ results[1] = jd;
+ }
+ } else if (jd.isNativeLib()) {
+ // if jnlp extension has only native lib
+ if (results[0] == null) {
+ results[0] = jd;
+ }
+ }
+ }
+
+ @Override
+ public void visitExtensionDesc(ExtensionDesc ed) throws Exception {
+ // only check if no main yet and it is not an installer
+ if (results[1] == null && !ed.isInstaller()) {
+ JNLPDesc extLd = ed.getExtensionDesc();
+ if (extLd != null && extLd.isLibrary()) {
+ ResourcesDesc rd = extLd.getResourcesDesc();
+ if (rd != null) {
+ // look for main jar in extension resource
+ rd.visit(this);
+ }
+ }
+ }
+ }
+ });
+
+ // Default is the first, if none is specified as main. This might
+ // return NULL if there is no JAR resources.
+ JARDesc first = results[0];
+ JARDesc main = results[1];
+
+ // if main is null then return first;
+ // libraries have no such thing as a main jar, so return first;
+ // otherwise return main
+ // only returns null if there are no jars.
+ return (main == null) ? first : main;
+ }
+
+ /*
+ * Get the properties defined for this object
+ */
+ public Properties getResourceProperties() throws Exception {
+ final Properties props = new Properties();
+ visit(new ResourceVisitor() {
+ @Override
+ public void visitPropertyDesc(PropertyDesc pd) {
+ props.setProperty(pd.getKey(), pd.getValue());
+ }
+
+ @Override
+ public void visitExtensionDesc(ExtensionDesc ed) throws Exception {
+ JNLPDesc jnlpd = ed.getExtensionDesc();
+ ResourcesDesc rd = jnlpd.getResourcesDesc();
+ if (rd != null) {
+ Properties extProps = rd.getResourceProperties();
+ Enumeration e = extProps.propertyNames();
+ while (e.hasMoreElements()) {
+ String key = (String) e.nextElement();
+ String value = extProps.getProperty(key);
+ props.setProperty(key, value);
+ }
+ }
+ }
+ });
+ return props;
+ }
+
+ /*
+ * Get the properties defined for this object, in the right order.
+ */
+ public List<Property> getResourcePropertyList() throws Exception {
+ final LinkedList<Property> propList = new LinkedList<>();
+ visit(new ResourceVisitor() {
+ @Override
+ public void visitPropertyDesc(PropertyDesc pd) {
+ propList.add(new Property(pd.getKey(), pd.getValue()));
+ }
+ });
+ return propList;
+ }
+
+ /**
+ * visitor dispatch
+ */
+ @Override
+ public void visit(ResourceVisitor rv) throws Exception {
+ for (int i = 0; i < _list.size(); i++) {
+ ResourceType rt = _list.get(i);
+ rt.visit(rv);
+ }
+ }
+
+ public void addNested(ResourcesDesc nested) throws Exception {
+ if (nested != null) {
+ nested.visit(new ResourceVisitor() {
+ @Override
+ public void visitJARDesc(JARDesc jd) {
+ _list.add(jd);
+ }
+
+ @Override
+ public void visitPropertyDesc(PropertyDesc pd) {
+ _list.add(pd);
+ }
+
+ @Override
+ public void visitExtensionDesc(ExtensionDesc ed) {
+ _list.add(ed);
+ }
+ });
+ }
+
+ }
+
+ public static class JARDesc implements ResourceType {
+
+ private URL _location;
+ private String _locationString;
+ private String _version;
+ private boolean _isNativeLib;
+ private boolean _isMainFile; // Only used for Java JAR files (a main JAR file is implicitly eager)
+ private ResourcesDesc _parent; // Back-pointer to the Resources that contains this JAR
+
+ public JARDesc(URL location, String version, boolean isMainFile, boolean isNativeLib, ResourcesDesc parent) {
+ _location = location;
+ _locationString = GeneralUtil.toNormalizedString(location);
+ _version = version;
+ _isMainFile = isMainFile;
+ _isNativeLib = isNativeLib;
+ _parent = parent;
+ }
+
+ /**
+ * Type of JAR resource
+ */
+ public boolean isNativeLib() {
+ return _isNativeLib;
+ }
+
+ public boolean isJavaFile() {
+ return !_isNativeLib;
+ }
+
+ /**
+ * Returns URL/version for JAR file
+ */
+ public URL getVersionLocation() throws Exception {
+ if (getVersion() == null) {
+ return _location;
+ } else {
+ return GeneralUtil.getEmbeddedVersionURL(getLocation(), getVersion());
+ }
+ }
+
+ public URL getLocation() {
+ return _location;
+ }
+
+ public String getVersion() {
+ return _version;
+ }
+
+ public String getName() {
+ // File can be separated by '/' or '\\'
+ int index;
+ int index1 = _locationString.lastIndexOf('/');
+ int index2 = _locationString.lastIndexOf('\\');
+
+ if (index1 >= index2) {
+ index = index1;
+ } else {
+ index = index2;
+ }
+
+ if (index != -1) {
+ return _locationString.substring(index + 1, _locationString.length());
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns if this is the main JAR file
+ */
+ public boolean isMainJarFile() {
+ return _isMainFile;
+ }
+
+ /**
+ * Get parent LaunchDesc
+ */
+ public ResourcesDesc getParent() {
+ return _parent;
+ }
+
+ /**
+ * Visitor dispatch
+ */
+ public void visit(ResourceVisitor rv) {
+ rv.visitJARDesc(this);
+ }
+ }
+
+ public static class PropertyDesc implements ResourceType {
+
+ private String _key;
+ private String _value;
+
+ public PropertyDesc(String key, String value) {
+ _key = key;
+ _value = value;
+ }
+
+ // Accessors
+ public String getKey() {
+ return _key;
+ }
+
+ public String getValue() {
+ return _value;
+ }
+
+ /**
+ * Visitor dispatch
+ */
+ public void visit(ResourceVisitor rv) {
+ rv.visitPropertyDesc(this);
+ }
+
+ }
+
+ public static class JREDesc implements ResourceType {
+
+ private String _version;
+ private long _maxHeap;
+ private long _minHeap;
+ private String _vmargs;
+ private ResourcesDesc _resourceDesc;
+ private JNLPDesc _extensioDesc;
+ private String _archList;
+
+ /*
+ * Constructor to create new instance based on the requirements from JNLP file.
+ */
+ public JREDesc(String version, long minHeap, long maxHeap, String vmargs,
+ ResourcesDesc resourcesDesc, String archList) {
+
+ _version = version;
+ _maxHeap = maxHeap;
+ _minHeap = minHeap;
+ _vmargs = vmargs;
+ _resourceDesc = resourcesDesc;
+ _extensioDesc = null;
+ _archList = archList;
+ }
+
+ public String[] getArchList() {
+ return GeneralUtil.getStringList(_archList);
+ }
+
+ public String getVersion() {
+ return _version;
+ }
+
+ public long getMinHeap() {
+ return _minHeap;
+ }
+
+ public long getMaxHeap() {
+ return _maxHeap;
+ }
+
+ public String getVmArgs() {
+ return _vmargs;
+ }
+
+ public String[] getVmArgsList() {
+ return GeneralUtil.getStringList(_vmargs);
+ }
+
+ public ResourcesDesc getNestedResources() {
+ return _resourceDesc;
+ }
+
+ public JNLPDesc getExtensionDesc() {
+ return _extensioDesc;
+ }
+
+ public void setExtensionDesc(JNLPDesc ld) {
+ _extensioDesc = ld;
+ }
+
+ /* visitor dispatch */
+ public void visit(ResourceVisitor rv) {
+ rv.visitJREDesc(this);
+ }
+ }
+
+ public static class Property implements Cloneable {
+
+ public static final String JNLP_VERSION_ENABLED = "jnlp.versionEnabled";
+
+ String key;
+ String value;
+
+ public Property(String spec) {
+ spec = spec.trim();
+ if (!spec.startsWith("-D") || spec.length() < 3) {
+ throw new IllegalArgumentException("Property invalid");
+ }
+
+ int endKey = spec.indexOf("=");
+ if (endKey < 0) {
+ // it's legal to have no assignment
+ this.key = spec.substring(2); // skip "-D"
+ this.value = "";
+ } else {
+ this.key = spec.substring(2, endKey);
+ this.value = spec.substring(endKey + 1);
+ }
+ }
+
+ public static Property createProperty(String spec) {
+ Property prop = null;
+ try {
+ prop = new Property(spec);
+ } catch (IllegalArgumentException iae) {
+ }
+ return prop;
+ }
+
+ public Property(String key, String value) {
+ this.key = key;
+ if (value != null) {
+ this.value = value;
+ } else {
+ this.value = "";
+ }
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ // @return String representation, unquoted, unified presentation
+ public String toString() {
+ if (value.length() == 0) {
+ return "-D" + key;
+ }
+ return "-D" + key + "=" + value;
+ }
+
+ public void addTo(Properties props) {
+ props.setProperty(key, value);
+ }
+
+ // Hash Object
+ public boolean equals(Object o) {
+ if (!(o instanceof Property)) {
+ return false;
+ }
+ Property op = (Property) o;
+ int hashTheirs = op.hashCode();
+ int hashThis = hashCode();
+ return hashTheirs == hashThis;
+ }
+
+ public int hashCode() {
+ return key.hashCode();
+ }
+
+ private static List<Object> jnlpProps = Arrays.asList(new Object[]{
+ JNLP_VERSION_ENABLED
+ });
+
+ public static boolean isJnlpProperty(String spec) {
+ try {
+ Property p = new Property(spec);
+ return isJnlpPropertyKey(p.getKey());
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ public static boolean isJnlpPropertyKey(String key) {
+ return key != null && jnlpProps.contains(key);
+ }
+ }
+
+ public static class ExtensionDesc implements ResourceType {
+ // Tag elements
+
+ private final URL _location;
+ private final String _locationString;
+ private final String _version;
+ private final URL _codebase;
+
+ // Link to launchDesc
+ private JNLPDesc _extensionLd; // Link to launchDesc for extension
+
+ public ExtensionDesc(URL location, String version) {
+ _location = location;
+ _locationString = GeneralUtil.toNormalizedString(location);
+ _version = version;
+ _codebase = GeneralUtil.asPathURL(GeneralUtil.getBase(location));
+ _extensionLd = null;
+ }
+
+ public boolean isInstaller() throws Exception {
+ if (getExtensionDesc() != null) {
+ return _extensionLd.isInstaller();
+ }
+ return false;
+ }
+
+ public URL getLocation() {
+ return _location;
+ }
+
+ public String getVersionLocation() throws Exception {
+ if (getVersion() == null) {
+ return _locationString;
+ } else {
+ return GeneralUtil.toNormalizedString(GeneralUtil.getEmbeddedVersionURL(getLocation(), getVersion()));
+ }
+ }
+
+ public String getVersion() {
+ return _version;
+ }
+
+ public URL getCodebase() {
+ return _codebase;
+ }
+
+ /*
+ * Information about the resources
+ */
+ public JNLPDesc getExtensionDesc() throws Exception {
+ if (_extensionLd == null) {
+ byte[] bits = HTTPHelper.getJNLPBits(getVersionLocation(), _locationString);
+ _extensionLd = XMLFormat.parse(bits, getCodebase(), getVersionLocation());
+ }
+ return _extensionLd;
+ }
+
+ public void setExtensionDesc(JNLPDesc desc) {
+ _extensionLd = desc;
+ }
+
+ /**
+ * Visitor dispatch
+ */
+ public void visit(ResourceVisitor rv) throws Exception {
+ rv.visitExtensionDesc(this);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/VersionID.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,313 @@
+/*
+ * Copyright (c) 2006, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jnlp.converter.parser;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * VersionID contains a JNLP version ID.
+ *
+ * The VersionID also contains a prefix indicator that can
+ * be used when stored with a VersionString
+ *
+ */
+public class VersionID implements Comparable<VersionID> {
+ private final String[] _tuple; // Array of Integer or String objects
+ private final boolean _usePrefixMatch; // star (*) prefix
+ private final boolean _useGreaterThan; // plus (+) greather-than
+ private final boolean _isCompound; // and (&) operator
+ private final VersionID _rest; // remaining part after the &
+
+ /**
+ * Creates a VersionID object from a given <code>String</code>.
+ * @param str version string to parse
+ */
+ public VersionID(String str) {
+ if (str == null || str.length() == 0) {
+ _tuple = new String[0];
+ _useGreaterThan = false;
+ _usePrefixMatch = false;
+ _isCompound = false;
+ _rest = null;
+ return;
+ }
+
+ // Check for compound
+ int amp = str.indexOf("&");
+ if (amp >= 0) {
+ _isCompound = true;
+ VersionID firstPart = new VersionID(str.substring(0, amp));
+ _rest = new VersionID(str.substring(amp+1));
+ _tuple = firstPart._tuple;
+ _usePrefixMatch = firstPart._usePrefixMatch;
+ _useGreaterThan = firstPart._useGreaterThan;
+ } else {
+ _isCompound = false;
+ _rest = null;
+ // Check for postfix
+ if (str.endsWith("+")) {
+ _useGreaterThan = true;
+ _usePrefixMatch = false;
+ str = str.substring(0, str.length() - 1);
+ } else if (str.endsWith("*")) {
+ _useGreaterThan = false;
+ _usePrefixMatch = true;
+ str = str.substring(0, str.length() - 1);
+ } else {
+ _useGreaterThan = false;
+ _usePrefixMatch = false;
+ }
+
+ ArrayList<String> list = new ArrayList<>();
+ int start = 0;
+ for (int i = 0; i < str.length(); i++) {
+ // Split at each separator character
+ if (".-_".indexOf(str.charAt(i)) != -1) {
+ if (start < i) {
+ String value = str.substring(start, i);
+ list.add(value);
+ }
+ start = i + 1;
+ }
+ }
+ if (start < str.length()) {
+ list.add(str.substring(start, str.length()));
+ }
+ _tuple = list.toArray(new String[0]);
+ }
+ }
+
+ /** @return true if no flags are set */
+ public boolean isSimpleVersion() {
+ return !_useGreaterThan && !_usePrefixMatch && !_isCompound;
+ }
+
+ /** Match 'this' versionID against vid.
+ * The _usePrefixMatch/_useGreaterThan flag is used to determine if a
+ * prefix match of an exact match should be performed
+ * if _isCompound, must match _rest also.
+ */
+ public boolean match(VersionID vid) {
+ if (_isCompound) {
+ if (!_rest.match(vid)) {
+ return false;
+ }
+ }
+ return (_usePrefixMatch) ? this.isPrefixMatchTuple(vid) :
+ (_useGreaterThan) ? vid.isGreaterThanOrEqualTuple(this) :
+ matchTuple(vid);
+ }
+
+ /** Compares if two version IDs are equal */
+ @Override
+ public boolean equals(Object o) {
+ if (matchTuple(o)) {
+ VersionID ov = (VersionID) o;
+ if (_rest == null || _rest.equals(ov._rest)) {
+ if ((_useGreaterThan == ov._useGreaterThan) &&
+ (_usePrefixMatch == ov._usePrefixMatch)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /** Computes a hash code for a VersionID */
+ @Override
+ public int hashCode() {
+ boolean first = true;
+ int hashCode = 0;
+ for (String tuple : _tuple) {
+ if (first) {
+ first = false;
+ hashCode = tuple.hashCode();
+ } else {
+ hashCode = hashCode ^ tuple.hashCode();
+ }
+ }
+ return hashCode;
+ }
+
+ /** Compares if two version IDs are equal */
+ private boolean matchTuple(Object o) {
+ // Check for null and type
+ if (o == null || !(o instanceof VersionID)) {
+ return false;
+ }
+ VersionID vid = (VersionID) o;
+
+ // Normalize arrays
+ String[] t1 = normalize(_tuple, vid._tuple.length);
+ String[] t2 = normalize(vid._tuple, _tuple.length);
+
+ // Check contents
+ for (int i = 0; i < t1.length; i++) {
+ Object o1 = getValueAsObject(t1[i]);
+ Object o2 = getValueAsObject(t2[i]);
+ if (!o1.equals(o2)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private Object getValueAsObject(String value) {
+ if (value.length() > 0 && value.charAt(0) != '-') {
+ try {
+ return Integer.valueOf(value);
+ } catch (NumberFormatException nfe) {
+ /* fall through */
+ }
+ }
+ return value;
+ }
+
+ public boolean isGreaterThan(VersionID vid) {
+ if (vid == null) {
+ return false;
+ }
+ return isGreaterThanOrEqualHelper(vid, false, true);
+ }
+
+ public boolean isGreaterThanOrEqual(VersionID vid) {
+ if (vid == null) {
+ return false;
+ }
+ return isGreaterThanOrEqualHelper(vid, true, true);
+ }
+
+ boolean isGreaterThanOrEqualTuple(VersionID vid) {
+ return isGreaterThanOrEqualHelper(vid, true, false);
+ }
+
+ /** Compares if 'this' is greater than vid */
+ private boolean isGreaterThanOrEqualHelper(VersionID vid,
+ boolean allowEqual, boolean useRest) {
+
+ if (useRest && _isCompound) {
+ if (!_rest.isGreaterThanOrEqualHelper(vid, allowEqual, true)) {
+ return false;
+ }
+ }
+ // Normalize the two strings
+ String[] t1 = normalize(_tuple, vid._tuple.length);
+ String[] t2 = normalize(vid._tuple, _tuple.length);
+
+ for (int i = 0; i < t1.length; i++) {
+ // Compare current element
+ Object e1 = getValueAsObject(t1[i]);
+ Object e2 = getValueAsObject(t2[i]);
+ if (e1.equals(e2)) {
+ // So far so good
+ } else {
+ if (e1 instanceof Integer && e2 instanceof Integer) {
+ // if both can be parsed as ints, compare ints
+ return ((Integer)e1).intValue() > ((Integer)e2).intValue();
+ } else {
+ if (e1 instanceof Integer) {
+ return false; // e1 (numeric) < e2 (non-numeric)
+ } else if (e2 instanceof Integer) {
+ return true; // e1 (non-numeric) > e2 (numeric)
+ }
+
+ String s1 = t1[i];
+ String s2 = t2[i];
+
+ return s1.compareTo(s2) > 0;
+ }
+
+ }
+ }
+ // If we get here, they are equal
+ return allowEqual;
+ }
+
+ /** Checks if 'this' is a prefix of vid */
+ private boolean isPrefixMatchTuple(VersionID vid) {
+
+ // Make sure that vid is at least as long as the prefix
+ String[] t2 = normalize(vid._tuple, _tuple.length);
+
+ for (int i = 0; i < _tuple.length; i++) {
+ Object e1 = _tuple[i];
+ Object e2 = t2[i];
+ if (e1.equals(e2)) {
+ // So far so good
+ } else {
+ // Not a prefix
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /** Normalize an array to a certain lengh */
+ private String[] normalize(String[] list, int minlength) {
+ if (list.length < minlength) {
+ // Need to do padding
+ String[] newlist = new String[minlength];
+ System.arraycopy(list, 0, newlist, 0, list.length);
+ Arrays.fill(newlist, list.length, newlist.length, "0");
+ return newlist;
+ } else {
+ return list;
+ }
+ }
+
+ @Override
+ public int compareTo(VersionID o) {
+ if (o == null || !(o instanceof VersionID)) {
+ return -1;
+ }
+ VersionID vid = o;
+ return equals(vid) ? 0 : (isGreaterThanOrEqual(vid) ? 1 : -1);
+ }
+
+ /** Show it as a string */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < _tuple.length - 1; i++) {
+ sb.append(_tuple[i]);
+ sb.append('.');
+ }
+ if (_tuple.length > 0) {
+ sb.append(_tuple[_tuple.length - 1]);
+ }
+ if (_useGreaterThan) {
+ sb.append('+');
+ }
+ if (_usePrefixMatch) {
+ sb.append('*');
+ }
+ if (_isCompound) {
+ sb.append("&");
+ sb.append(_rest);
+ }
+ return sb.toString();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/VersionString.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2006, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jnlp.converter.parser;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.StringTokenizer;
+
+/*
+ * Utility class that knows to handle version strings
+ * A version string is of the form:
+ *
+ * (version-id ('+'?) ' ') *
+ *
+ */
+public class VersionString {
+ private final ArrayList<VersionID> _versionIds;
+
+ /** Constructs a VersionString object from string */
+ public VersionString(String vs) {
+ _versionIds = new ArrayList<>();
+ if (vs != null) {
+ StringTokenizer st = new StringTokenizer(vs, " ", false);
+ while (st.hasMoreElements()) {
+ // Note: The VersionID class takes care of a postfixed '+'
+ _versionIds.add(new VersionID(st.nextToken()));
+ }
+ }
+ }
+
+ public VersionString(VersionID id) {
+ _versionIds = new ArrayList<>();
+ if (id != null) {
+ _versionIds.add(id);
+ }
+ }
+
+ public boolean isSimpleVersion() {
+ if (_versionIds.size() == 1) {
+ return _versionIds.get(0).isSimpleVersion();
+ }
+ return false;
+ }
+
+ /** Check if this VersionString object contains the VersionID m */
+ public boolean contains(VersionID m) {
+ for (int i = 0; i < _versionIds.size(); i++) {
+ VersionID vi = _versionIds.get(i);
+ boolean check = vi.match(m);
+ if (check) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Check if this VersionString object contains the VersionID m, given as a string */
+ public boolean contains(String versionid) {
+ return contains(new VersionID(versionid));
+ }
+
+ /** Check if this VersionString object contains anything greater than m */
+ public boolean containsGreaterThan(VersionID m) {
+ for (int i = 0; i < _versionIds.size(); i++) {
+ VersionID vi = _versionIds.get(i);
+ boolean check = vi.isGreaterThan(m);
+ if (check) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Check if this VersionString object contains anything greater than the VersionID m, given as a string */
+ public boolean containsGreaterThan(String versionid) {
+ return containsGreaterThan(new VersionID(versionid));
+ }
+
+ /** Check if the versionString 'vs' contains the VersionID 'vi' */
+ public static boolean contains(String vs, String vi) {
+ return (new VersionString(vs)).contains(vi);
+ }
+
+ /** Pretty-print object */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < _versionIds.size(); i++) {
+ sb.append(_versionIds.get(i).toString());
+ sb.append(' ');
+ }
+ return sb.toString();
+ }
+
+ public List<VersionID> getAllVersionIDs() {
+ return _versionIds;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/XMLFormat.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,659 @@
+/*
+ * Copyright (c) 2006, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jnlp.converter.parser;
+
+import java.net.URL;
+import java.util.Arrays;
+import java.util.ArrayList;
+import jnlp.converter.JNLPConverter;
+import jnlp.converter.parser.exception.MissingFieldException;
+import jnlp.converter.parser.exception.BadFieldException;
+import jnlp.converter.parser.exception.JNLParseException;
+import jnlp.converter.parser.xml.XMLEncoding;
+import jnlp.converter.parser.xml.XMLParser;
+import jnlp.converter.parser.xml.XMLNode;
+import jnlp.converter.parser.JNLPDesc.AssociationDesc;
+import jnlp.converter.parser.JNLPDesc.IconDesc;
+import jnlp.converter.parser.JNLPDesc.InformationDesc;
+import jnlp.converter.parser.JNLPDesc.ShortcutDesc;
+import jnlp.converter.parser.ResourcesDesc.JARDesc;
+import jnlp.converter.parser.ResourcesDesc.JREDesc;
+import jnlp.converter.Log;
+import jnlp.converter.parser.ResourcesDesc.ExtensionDesc;
+import jnlp.converter.parser.ResourcesDesc.PropertyDesc;
+import org.xml.sax.SAXParseException;
+
+public class XMLFormat {
+
+ public static XMLNode parseBits(byte[] bits) throws JNLParseException {
+ return parse(decode(bits));
+ }
+
+ private static String decode(byte[] bits) throws JNLParseException {
+ try {
+ return XMLEncoding.decodeXML(bits);
+ } catch (Exception e) {
+ throw new JNLParseException(e,
+ "exception determining encoding of jnlp file", 0);
+ }
+ }
+
+ private static XMLNode parse(String source) throws JNLParseException {
+ try {
+ return (new XMLParser(source).parse());
+ } catch (SAXParseException spe) {
+ throw new JNLParseException(spe,
+ "exception parsing jnlp file", spe.getLineNumber());
+ } catch (Exception e) {
+ throw new JNLParseException(e,
+ "exception parsing jnlp file", 0);
+ }
+ }
+
+ /**
+ * thisCodebase, if set, is used to determine the codebase,
+ * if JNLP codebase is not absolute.
+ *
+ * @param thisCodebase base URL of this JNLPDesc location
+ */
+ public static JNLPDesc parse(byte[] bits, URL thisCodebase, String jnlp)
+ throws Exception {
+
+ JNLPDesc jnlpd = new JNLPDesc();
+ String source = decode(bits).trim();
+ XMLNode root = parse(source);
+
+ if (root == null || root.getName() == null) {
+ throw new JNLParseException(null, null, 0);
+ }
+
+ // Check that root element is a <jnlp> tag
+ if (!root.getName().equals("jnlp")) {
+ throw (new MissingFieldException(source, "<jnlp>"));
+ }
+
+ // Read <jnlp> attributes (path is empty, i.e., "")
+ // (spec, version, codebase, href)
+ String specVersion = XMLUtils.getAttribute(root, "", "spec", "1.0+");
+ jnlpd.setSpecVersion(specVersion);
+ String version = XMLUtils.getAttribute(root, "", "version");
+ jnlpd.setVersion(version);
+
+ // Make sure the codebase URL ends with a '/'.
+ //
+ // Regarding the JNLP spec,
+ // the thisCodebase is used to determine the codebase.
+ // codebase = new URL(thisCodebase, codebase)
+ URL codebase = GeneralUtil.asPathURL(XMLUtils.getAttributeURL(source,
+ thisCodebase, root, "", "codebase"));
+ if (codebase == null && thisCodebase != null) {
+ codebase = thisCodebase;
+ }
+ jnlpd.setCodebase(codebase.toExternalForm());
+
+ // Get href for JNLP file
+ URL href = XMLUtils.getAttributeURL(source, codebase, root, "", "href");
+ jnlpd.setHref(href.toExternalForm());
+
+ // Read <security> attributes
+ if (XMLUtils.isElementPath(root, "<security><all-permissions>")) {
+ jnlpd.setIsSandbox(false);
+ } else if (XMLUtils.isElementPath(root,
+ "<security><j2ee-application-client-permissions>")) {
+ jnlpd.setIsSandbox(false);
+ }
+
+ // We can be fxapp, and also be applet, or application, or neither
+ boolean isFXApp = false;
+ boolean isApplet = false;
+ if (XMLUtils.isElementPath(root, "<javafx-desc>")) {
+ // no new type for javafx-desc - needs one of the others
+ buildFXAppDesc(source, root, "<javafx-desc>", jnlpd);
+ jnlpd.setIsFXApp(true);
+ isFXApp = true;
+ }
+
+ /*
+ * Note - the jnlp specification says there must be exactly one of
+ * the descriptor types. This code has always violated (or at least
+ * not checked for) that condition.
+ * Instead it uses precedent order app, component, installer, applet
+ * and ignores any other descriptors given.
+ */
+ if (XMLUtils.isElementPath(root, "<application-desc>")) {
+ buildApplicationDesc(source, root, jnlpd);
+ } else if (XMLUtils.isElementPath(root, "<component-desc>")) {
+ jnlpd.setIsLibrary(true);
+ } else if (XMLUtils.isElementPath(root, "<installer-desc>")) {
+ Log.warning("<installer-desc> is not supported and will be ignored in " + jnlp);
+ jnlpd.setIsInstaller(true);
+ } else if (XMLUtils.isElementPath(root, "<applet-desc>")) {
+ isApplet = true;
+ } else {
+ if (!isFXApp) {
+ throw (new MissingFieldException(source,
+ "<jnlp>(<application-desc>|<applet-desc>|" +
+ "<installer-desc>|<component-desc>)"));
+ }
+ }
+
+ if (isApplet && !isFXApp) {
+ Log.error("Applet based applications deployed with <applet-desc> element are not supported.");
+ }
+
+ if (!jnlpd.isLibrary() && !jnlpd.isInstaller()) {
+ buildInformationDesc(source, codebase, root, jnlpd);
+ }
+
+ if (!jnlpd.isInstaller()) {
+ buildResourcesDesc(source, codebase, root, false, jnlpd);
+ }
+
+ if (!jnlpd.isLibrary() && !jnlpd.isInstaller()) {
+ jnlpd.parseResourceDesc();
+ }
+
+ if (!jnlpd.isInstaller()) {
+ if (jnlpd.isSandbox()) {
+ if (jnlpd.isLibrary()) {
+ Log.warning(jnlp + " is sandbox extension. JNLPConverter does not support sandbox environment and converted application will run without security manager.");
+ } else {
+ Log.warning("This is sandbox Web-Start application. JNLPConverter does not support sandbox environment and converted application will run without security manager.");
+ }
+ }
+ }
+
+ return jnlpd;
+ }
+
+ /**
+ * Create a combine informationDesc in the two informationDesc.
+ * The information present in id1 overwrite the information present in id2
+ */
+ private static InformationDesc combineInformationDesc(
+ InformationDesc id1, InformationDesc id2) {
+ if (id1 == null) {
+ return id2;
+ }
+ if (id2 == null) {
+ return id1;
+ }
+
+ String t1 = id1.getTitle();
+ String title = (t1 != null && t1.length() > 0) ?
+ t1 : id2.getTitle();
+ String v1 = id1.getVendor();
+ String vendor = (v1 != null && v1.length() > 0) ?
+ v1 : id2.getVendor();
+
+ /** Copy descriptions */
+ String[] descriptions = new String[InformationDesc.NOF_DESC];
+ for (int i = 0; i < descriptions.length; i++) {
+ descriptions[i] = (id1.getDescription(i) != null)
+ ? id1.getDescription(i) : id2.getDescription(i);
+ }
+
+ /** Icons */
+ ArrayList<IconDesc> iconList = new ArrayList<>();
+ if (id2.getIcons() != null) {
+ iconList.addAll(Arrays.asList(id2.getIcons()));
+ }
+ if (id1.getIcons() != null) {
+ iconList.addAll(Arrays.asList(id1.getIcons()));
+ }
+ IconDesc[] icons = new IconDesc[iconList.size()];
+ icons = iconList.toArray(icons);
+
+ ShortcutDesc hints = (id1.getShortcut() != null) ?
+ id1.getShortcut() : id2.getShortcut();
+
+ AssociationDesc[] asd = ( AssociationDesc[] ) addArrays(
+ (Object[])id1.getAssociations(), (Object[])id2.getAssociations());
+
+ return new InformationDesc(title,
+ vendor,
+ descriptions,
+ icons,
+ hints,
+ asd);
+ }
+
+ /** Extract data from <information> tag */
+ private static void buildInformationDesc(final String source, final URL codebase, XMLNode root, JNLPDesc jnlpd)
+ throws MissingFieldException, BadFieldException {
+ final ArrayList<InformationDesc> list = new ArrayList<>();
+
+ // Iterates over all <information> nodes ignoring the type
+ XMLUtils.visitElements(root,
+ "<information>", new XMLUtils.ElementVisitor() {
+ @Override
+ public void visitElement(XMLNode e) throws
+ BadFieldException, MissingFieldException {
+
+ // Check for right os, arch, and locale
+ String[] os = GeneralUtil.getStringList(
+ XMLUtils.getAttribute(e, "", "os", null));
+ String[] arch = GeneralUtil.getStringList(
+ XMLUtils.getAttribute(e, "", "arch", null));
+ String[] locale = GeneralUtil.getStringList(
+ XMLUtils.getAttribute(e, "", "locale", null));
+ if (GeneralUtil.prefixMatchStringList(
+ os, GeneralUtil.getOSFullName()) &&
+ GeneralUtil.prefixMatchArch(arch) &&
+ matchDefaultLocale(locale))
+ {
+ // Title, vendor
+ String title = XMLUtils.getElementContents(e, "<title>");
+ String vendor = XMLUtils.getElementContents(e, "<vendor>");
+
+ // Descriptions
+ String[] descriptions =
+ new String[InformationDesc.NOF_DESC];
+ descriptions[InformationDesc.DESC_DEFAULT] =
+ XMLUtils.getElementContentsWithAttribute(
+ e, "<description>", "kind", "", null);
+ descriptions[InformationDesc.DESC_ONELINE] =
+ XMLUtils.getElementContentsWithAttribute(
+ e, "<description>", "kind", "one-line", null);
+ descriptions[InformationDesc.DESC_SHORT] =
+ XMLUtils.getElementContentsWithAttribute(
+ e, "<description>", "kind", "short", null);
+ descriptions[InformationDesc.DESC_TOOLTIP] =
+ XMLUtils.getElementContentsWithAttribute(
+ e, "<description>", "kind", "tooltip", null);
+
+ // Icons
+ IconDesc[] icons = getIconDescs(source, codebase, e);
+
+ // Shortcut hints
+ ShortcutDesc shortcuts = getShortcutDesc(e);
+
+ // Association hints
+ AssociationDesc[] associations = getAssociationDesc(
+ source, codebase, e);
+
+ list.add(new InformationDesc(
+ title, vendor, descriptions, icons,
+ shortcuts, associations));
+ }
+ }
+ });
+
+ /* Combine all information desc. information in a single one for
+ * the current locale using the following priorities:
+ * 1. locale == language_country_variant
+ * 2. locale == lauguage_country
+ * 3. locale == lauguage
+ * 4. no or empty locale
+ */
+ InformationDesc normId = new InformationDesc(null, null, null, null, null, null);
+ for (InformationDesc id : list) {
+ normId = combineInformationDesc(id, normId);
+ }
+
+ jnlpd.setTitle(normId.getTitle());
+ jnlpd.setVendor(normId.getVendor());
+ jnlpd.setDescriptions(normId.getDescription());
+ jnlpd.setIcons(normId.getIcons());
+ jnlpd.setShortcuts(normId.getShortcut());
+ jnlpd.setAssociations(normId.getAssociations());
+ }
+
+ private static Object[] addArrays (Object[] a1, Object[] a2) {
+ if (a1 == null) {
+ return a2;
+ }
+ if (a2 == null) {
+ return a1;
+ }
+ ArrayList<Object> list = new ArrayList<>();
+ int i;
+ for (i=0; i<a1.length; list.add(a1[i++]));
+ for (i=0; i<a2.length; list.add(a2[i++]));
+ return list.toArray(a1);
+ }
+
+ public static boolean matchDefaultLocale(String[] localeStr) {
+ return GeneralUtil.matchLocale(localeStr, GeneralUtil.getDefaultLocale());
+ }
+
+ /** Extract data from <resources> tag. There is only one. */
+ static void buildResourcesDesc(final String source,
+ final URL codebase, XMLNode root, final boolean ignoreJres, JNLPDesc jnlpd)
+ throws MissingFieldException, BadFieldException {
+ // Extract classpath directives
+ final ResourcesDesc rdesc = new ResourcesDesc();
+
+ // Iterate over all entries
+ XMLUtils.visitElements(root, "<resources>",
+ new XMLUtils.ElementVisitor() {
+ @Override
+ public void visitElement(XMLNode e)
+ throws MissingFieldException, BadFieldException {
+ // Check for right os, archictecture, and locale
+ String[] os = GeneralUtil.getStringList(
+ XMLUtils.getAttribute(e, "", "os", null));
+ final String arch = XMLUtils.getAttribute(e, "", "arch", null);
+ String[] locale = GeneralUtil.getStringList(
+ XMLUtils.getAttribute(e, "", "locale", null));
+ if (GeneralUtil.prefixMatchStringList(
+ os, GeneralUtil.getOSFullName())
+ && matchDefaultLocale(locale)) {
+ // Now visit all children in this node
+ XMLUtils.visitChildrenElements(e,
+ new XMLUtils.ElementVisitor() {
+ @Override
+ public void visitElement(XMLNode e2)
+ throws MissingFieldException, BadFieldException {
+ handleResourceElement(source, codebase,
+ e2, rdesc, ignoreJres, arch, jnlpd);
+ }
+ });
+ }
+ }
+ });
+
+ if (!rdesc.isEmpty()) {
+ jnlpd.setResourcesDesc(rdesc);
+ }
+ }
+
+ private static IconDesc[] getIconDescs(final String source,
+ final URL codebase, XMLNode e)
+ throws MissingFieldException, BadFieldException {
+ final ArrayList<IconDesc> answer = new ArrayList<>();
+ XMLUtils.visitElements(e, "<icon>", new XMLUtils.ElementVisitor() {
+ @Override
+ public void visitElement(XMLNode icon) throws
+ MissingFieldException, BadFieldException {
+ String kindStr = XMLUtils.getAttribute(icon, "", "kind", "");
+ URL href = XMLUtils.getRequiredURL(source, codebase, icon, "", "href");
+
+ if (href != null) {
+ if (!JNLPConverter.isIconSupported(href.toExternalForm())) {
+ return;
+ }
+ }
+
+ int kind;
+ if (kindStr == null || kindStr.isEmpty() || kindStr.equals("default")) {
+ kind = IconDesc.ICON_KIND_DEFAULT;
+ } else if (kindStr.equals("shortcut")) {
+ kind = IconDesc.ICON_KIND_SHORTCUT;
+ } else {
+ Log.warning("Ignoring unsupported icon \"" + href + "\" with kind \"" + kindStr + "\".");
+ return;
+ }
+
+ answer.add(new IconDesc(href, kind));
+ }
+ });
+ return answer.toArray(new IconDesc[answer.size()]);
+ }
+
+ private static ShortcutDesc getShortcutDesc(XMLNode e)
+ throws MissingFieldException, BadFieldException {
+ final ArrayList<ShortcutDesc> shortcuts = new ArrayList<>();
+
+ XMLUtils.visitElements(e, "<shortcut>", new XMLUtils.ElementVisitor() {
+ @Override
+ public void visitElement(XMLNode shortcutNode)
+ throws MissingFieldException, BadFieldException {
+ boolean desktopHinted =
+ XMLUtils.isElementPath(shortcutNode, "<desktop>");
+ boolean menuHinted =
+ XMLUtils.isElementPath(shortcutNode, "<menu>");
+ String submenuHinted =
+ XMLUtils.getAttribute(shortcutNode, "<menu>", "submenu");
+ shortcuts.add(new ShortcutDesc(desktopHinted, menuHinted, submenuHinted));
+ }
+ });
+
+ if (shortcuts.size() > 0) {
+ return shortcuts.get(0);
+ }
+ return null;
+ }
+
+ private static AssociationDesc[] getAssociationDesc(final String source,
+ final URL codebase, XMLNode e)
+ throws MissingFieldException, BadFieldException {
+ final ArrayList<AssociationDesc> answer = new ArrayList<>();
+ XMLUtils.visitElements(e, "<association>",
+ new XMLUtils.ElementVisitor() {
+ @Override
+ public void visitElement(XMLNode node)
+ throws MissingFieldException, BadFieldException {
+
+ String extensions = XMLUtils.getAttribute(
+ node, "", "extensions");
+
+ String mimeType = XMLUtils.getAttribute(
+ node, "", "mime-type");
+ String description = XMLUtils.getElementContents(
+ node, "<description>");
+
+ URL icon = XMLUtils.getAttributeURL(
+ source, codebase, node, "<icon>", "href");
+
+ if (!JNLPConverter.isIconSupported(icon.toExternalForm())) {
+ icon = null;
+ }
+
+ if (extensions == null && mimeType == null) {
+ throw new MissingFieldException(source,
+ "<association>(<extensions><mime-type>)");
+ } else if (extensions == null) {
+ throw new MissingFieldException(source,
+ "<association><extensions>");
+ } else if (mimeType == null) {
+ throw new MissingFieldException(source,
+ "<association><mime-type>");
+ }
+
+ // don't support uppercase extension and mime-type on gnome.
+ if ("gnome".equals(System.getProperty("sun.desktop"))) {
+ extensions = extensions.toLowerCase();
+ mimeType = mimeType.toLowerCase();
+ }
+
+ answer.add(new AssociationDesc(extensions, mimeType,
+ description, icon));
+ }
+ });
+ return answer.toArray(
+ new AssociationDesc[answer.size()]);
+ }
+
+ /** Handle the individual entries in a resource desc */
+ private static void handleResourceElement(String source, URL codebase,
+ XMLNode e, ResourcesDesc rdesc, boolean ignoreJres, String arch, JNLPDesc jnlpd)
+ throws MissingFieldException, BadFieldException {
+
+ String tag = e.getName();
+
+ boolean matchArch = GeneralUtil.prefixMatchArch(
+ GeneralUtil.getStringList(arch));
+
+
+ if (matchArch && (tag.equals("jar") || tag.equals("nativelib"))) {
+ /*
+ * jar/nativelib elements
+ */
+ URL href = XMLUtils.getRequiredURL(source, codebase, e, "", "href");
+ String version = XMLUtils.getAttribute(e, "", "version", null);
+
+ String mainStr = XMLUtils.getAttribute(e, "", "main");
+ boolean isNativeLib = tag.equals("nativelib");
+
+ boolean isMain = "true".equalsIgnoreCase(mainStr);
+
+ JARDesc jd = new JARDesc(href, version, isMain, isNativeLib, rdesc);
+ rdesc.addResource(jd);
+ } else if (matchArch && tag.equals("property")) {
+ /*
+ * property tag
+ */
+ String name = XMLUtils.getRequiredAttribute(source, e, "", "name");
+ String value = XMLUtils.getRequiredAttributeEmptyOK(
+ source, e, "", "value");
+
+ rdesc.addResource(new PropertyDesc(name, value));
+ } else if (matchArch && tag.equals("extension")) {
+ URL href = XMLUtils.getRequiredURL(source, codebase, e, "", "href");
+ String version = XMLUtils.getAttribute(e, "", "version", null);
+ rdesc.addResource(new ExtensionDesc(href, version));
+ } else if ((tag.equals("java") || tag.equals("j2se")) && !ignoreJres) {
+ /*
+ * j2se element
+ */
+ String version =
+ XMLUtils.getRequiredAttribute(source, e, "", "version");
+ String minheapstr =
+ XMLUtils.getAttribute(e, "", "initial-heap-size");
+ String maxheapstr =
+ XMLUtils.getAttribute(e, "", "max-heap-size");
+
+ String vmargs =
+ XMLUtils.getAttribute(e, "", "java-vm-args");
+
+ if (jnlpd.isJRESet()) {
+ if (vmargs == null) {
+ vmargs = "none";
+ }
+ Log.warning("Ignoring repeated element <" + tag + "> with version " + version +
+ " and java-vm-args: " + vmargs);
+ return;
+ }
+
+ long minheap = GeneralUtil.heapValToLong(minheapstr);
+ long maxheap = GeneralUtil.heapValToLong(maxheapstr);
+
+ ResourcesDesc cbs = null;
+ buildResourcesDesc(source, codebase, e, true, null);
+
+ // JRE
+ JREDesc jreDesc = new JREDesc(
+ version,
+ minheap,
+ maxheap,
+ vmargs,
+ cbs,
+ arch);
+
+ rdesc.addResource(jreDesc);
+
+ jnlpd.setIsJRESet(true);
+ }
+ }
+
+ /** Extract data from the application-desc tag */
+ private static void buildApplicationDesc(final String source,
+ XMLNode root, JNLPDesc jnlpd) throws MissingFieldException, BadFieldException {
+
+ String mainclass = XMLUtils.getClassName(source, root,
+ "<application-desc>", "main-class", false);
+ String appType = XMLUtils.getAttribute(root, "<application-desc>",
+ "type", "Java");
+ String progressclass = XMLUtils.getClassName(source, root,
+ "<application-desc>", "progress-class", false);
+ if (progressclass != null && !progressclass.isEmpty()) {
+ Log.warning("JNLPConverter does not support progress indication. \"" + progressclass + "\" will not be loaded and will be ignored.");
+ }
+
+ if (!("Java".equalsIgnoreCase(appType) ||
+ "JavaFx".equalsIgnoreCase(appType))) {
+ throw new BadFieldException(source, XMLUtils.getPathString(root) +
+ "<application-desc>type", appType);
+ }
+
+ if ("JavaFx".equalsIgnoreCase(appType)) {
+ jnlpd.setIsFXApp(true);
+ }
+
+ XMLUtils.visitElements(root, "<application-desc><argument>", new XMLUtils.ElementVisitor() {
+ @Override
+ public void visitElement(XMLNode e) throws MissingFieldException, BadFieldException {
+ String arg = XMLUtils.getElementContents(e, "", null);
+ if (arg == null) {
+ throw new BadFieldException(source, XMLUtils.getPathString(e), "");
+ }
+ jnlpd.addArguments(arg);
+ }
+ });
+
+ XMLUtils.visitElements(root, "<application-desc><param>",
+ new XMLUtils.ElementVisitor() {
+ @Override
+ public void visitElement(XMLNode e) throws MissingFieldException,
+ BadFieldException {
+ String pn = XMLUtils.getRequiredAttribute(
+ source, e, "", "name");
+ String pv = XMLUtils.getRequiredAttributeEmptyOK(
+ source, e, "", "value");
+ jnlpd.setProperty(pn, pv);
+ }
+ });
+ jnlpd.setMainClass(mainclass, false);
+ }
+
+ /** Extract data from the javafx-desc tag */
+ private static void buildFXAppDesc(final String source,
+ XMLNode root, String element, JNLPDesc jnlpd)
+ throws MissingFieldException, BadFieldException {
+ String mainclass = XMLUtils.getClassName(source, root, element,
+ "main-class", true);
+ String name = XMLUtils.getRequiredAttribute(source, root,
+ "<javafx-desc>", "name");
+
+ /* extract arguments */
+ XMLUtils.visitElements(root, "<javafx-desc><argument>", new XMLUtils.ElementVisitor() {
+ @Override
+ public void visitElement(XMLNode e) throws MissingFieldException, BadFieldException {
+ String arg = XMLUtils.getElementContents(e, "", null);
+ if (arg == null) {
+ throw new BadFieldException(source, XMLUtils.getPathString(e), "");
+ }
+ jnlpd.addArguments(arg);
+ }
+ });
+
+ /* extract parameters */
+ XMLUtils.visitElements(root, "<javafx-desc><param>",
+ new XMLUtils.ElementVisitor() {
+ @Override
+ public void visitElement(XMLNode e) throws MissingFieldException,
+ BadFieldException {
+ String pn = XMLUtils.getRequiredAttribute(
+ source, e, "", "name");
+ String pv = XMLUtils.getRequiredAttributeEmptyOK(
+ source, e, "", "value");
+ jnlpd.setProperty(pn, pv);
+ }
+ });
+
+ jnlpd.setMainClass(mainclass, true);
+ jnlpd.setName(name);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/XMLUtils.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,328 @@
+/*
+ * Copyright (c) 2006, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jnlp.converter.parser;
+
+import java.net.URL;
+import java.net.MalformedURLException;
+
+import jnlp.converter.parser.exception.BadFieldException;
+import jnlp.converter.parser.exception.MissingFieldException;
+import jnlp.converter.parser.xml.XMLNode;
+
+/** Contains handy methods for looking up information
+ * stored in XMLNodes.
+ */
+public class XMLUtils {
+
+ /** Returns the value of an integer attribute */
+ public static int getIntAttribute(String source, XMLNode root, String path, String name, int defaultvalue)
+ throws BadFieldException {
+ String value = getAttribute(root, path, name);
+ if (value == null) {
+ return defaultvalue;
+ }
+
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException nfe) {
+ throw new BadFieldException(source, getPathString(root) + path + name, value);
+ }
+ }
+
+ /** Returns the value of a given attribute, or null if not set */
+ public static String getAttribute(XMLNode root, String path, String name)
+ throws BadFieldException {
+ return getAttribute(root, path, name, null);
+ }
+
+ /** Returns the value of a given attribute */
+ public static String getRequiredAttributeEmptyOK(String source,
+ XMLNode root, String path, String name) throws MissingFieldException {
+ String value = null;
+ XMLNode elem = findElementPath(root, path);
+ if (elem != null) {
+ value = elem.getAttribute(name);
+ }
+ if (value == null) {
+ throw new MissingFieldException(source,
+ getPathString(root)+ path + name);
+ }
+ return value;
+ }
+
+ /*** Returns the value of an attribute, which must be a valid class name */
+ public static String getClassName(String source, XMLNode root,
+ String path, String name, boolean required)
+ throws BadFieldException, MissingFieldException {
+
+ String className;
+ if (required) {
+ className = getRequiredAttribute(source, root, path, name);
+ } else {
+ className = getAttribute(root, path, name);
+ }
+ if (className != null && className.endsWith(".class")) {
+ int i = className.lastIndexOf(".class");
+ String cname = className.substring(0, i);
+ return cname;
+ }
+ return className;
+ }
+
+ /** Returns the value of a given attribute, or null if not set */
+ public static String getRequiredAttribute(String source, XMLNode root,
+ String path, String name) throws MissingFieldException, BadFieldException {
+ String s = getAttribute(root, path, name, null);
+ if (s == null) {
+ throw new MissingFieldException(source, getPathString(root) + path + name);
+ }
+ s = s.trim();
+ return (s.length() == 0) ? null : s;
+ }
+
+ /** Returns the value of a given attribute, or the default value 'def' if not set */
+ public static String getAttribute(XMLNode root, String path, String name,
+ String def) throws BadFieldException {
+ XMLNode elem = findElementPath(root, path);
+ if (elem == null) {
+ return def;
+ }
+ String value = elem.getAttribute(name);
+ return (value == null || value.length() == 0) ? def : value;
+ }
+
+ /** Expands a URL into an absolute URL from a relative URL */
+ public static URL getAttributeURL(String source, URL base, XMLNode root, String path, String name) throws BadFieldException {
+ String value = getAttribute(root, path, name);
+ if (value == null) return null;
+ try {
+ if (value.startsWith("jar:")) {
+ int bang = value.indexOf("!/");
+ if (bang > 0) {
+ String entry = value.substring(bang);
+ String urlString = value.substring(4, bang);
+ URL url = (base == null) ?
+ new URL(urlString) : new URL(base, urlString);
+ return new URL("jar:" + url.toString() + entry);
+ }
+ }
+ return (base == null) ? new URL(value) : new URL(base, value);
+ } catch(MalformedURLException mue) {
+ if (mue.getMessage().contains("https")) {
+ throw new BadFieldException(source, "<jnlp>", "https");
+ }
+ throw new BadFieldException(source, getPathString(root) + path + name, value);
+ }
+ }
+
+ /** Returns the value of an attribute as a URL or null if not set */
+ public static URL getAttributeURL(String source, XMLNode root, String path, String name) throws BadFieldException {
+ return getAttributeURL(source, null, root, path, name);
+ }
+
+ public static URL getRequiredURL(String source, URL base, XMLNode root, String path, String name) throws BadFieldException, MissingFieldException {
+ URL url = getAttributeURL(source, base, root, path, name);
+ if (url == null) {
+ throw new MissingFieldException(source, getPathString(root) + path + name);
+ }
+ return url;
+ }
+
+ /** Returns the value of an attribute as a URL. Throws a MissingFieldException if the
+ * attribute is not defined
+ */
+ public static URL getRequiredURL(String source, XMLNode root, String path, String name) throws BadFieldException, MissingFieldException {
+ return getRequiredURL(source, null, root, path, name);
+ }
+
+ /** Returns true if the path exists in the document, otherwise false */
+ public static boolean isElementPath(XMLNode root, String path) {
+ return findElementPath(root, path) != null;
+ }
+
+ public static URL getElementURL(String source, XMLNode root, String path) throws BadFieldException {
+ String value = getElementContents(root, path);
+ try {
+ return new URL(value);
+ } catch(MalformedURLException mue) {
+ throw new BadFieldException(source, getPathString(root) + path, value);
+ }
+ }
+
+ /** Returns a string describing the current location in the DOM */
+ public static String getPathString(XMLNode e) {
+ return (e == null || !(e.isElement())) ? "" : getPathString(e.getParent()) + "<" + e.getName() + ">";
+ }
+
+ /** Returns the contents of an element with the given path and an attribute matching a specific value. Returns
+ * NULL if not found
+ */
+ public static String getElementContentsWithAttribute(XMLNode root, String path, String attr, String val, String defaultvalue)
+ throws BadFieldException, MissingFieldException {
+ XMLNode e = getElementWithAttribute(root, path, attr, val);
+ if (e == null) {
+ return defaultvalue;
+ }
+ return getElementContents(e, "", defaultvalue);
+ }
+
+ public static URL getAttributeURLWithAttribute(String source, XMLNode root, String path, String attrcond, String val,
+ String name, URL defaultvalue)
+ throws BadFieldException, MissingFieldException {
+ XMLNode e = getElementWithAttribute(root, path, attrcond, val);
+ if (e == null) {
+ return defaultvalue;
+ }
+ URL url = getAttributeURL(source, e, "", name);
+ if (url == null) {
+ return defaultvalue;
+ }
+ return url;
+ }
+
+ /** Returns an element with the given path and an attribute matching a specific value. Returns
+ * NULL if not found
+ */
+ public static XMLNode getElementWithAttribute(XMLNode root, String path, final String attr, final String val)
+ throws BadFieldException, MissingFieldException {
+ final XMLNode[] result = {null};
+ visitElements(root, path, new ElementVisitor() {
+ public void visitElement(XMLNode e) throws BadFieldException, MissingFieldException {
+ if (result[0] == null && e.getAttribute(attr).equals(val)) {
+ result[0] = e;
+ }
+ }
+ });
+ return result[0];
+ }
+
+ /** Like getElementContents(...) but with a defaultValue of null */
+ public static String getElementContents(XMLNode root, String path) {
+ return getElementContents(root, path, null);
+ }
+
+ /** Returns the value of the last element tag in the path, e.g., <..><tag>value</tag>. The DOM is assumes
+ * to be normalized. If no value is found, the defaultvalue is returned
+ */
+ public static String getElementContents(XMLNode root, String path, String defaultvalue) {
+ XMLNode e = findElementPath(root, path);
+ if (e == null) {
+ return defaultvalue;
+ }
+ XMLNode n = e.getNested();
+ if (n != null && !n.isElement()) {
+ return n.getName();
+ }
+ return defaultvalue;
+ }
+
+ /** Parses a path string of the form <tag1><tag2><tag3> and returns the specific Element
+ * node for that tag, or null if it does not exist. If multiple elements exists with same
+ * path the first is returned
+ */
+ public static XMLNode findElementPath(XMLNode elem, String path) {
+ // End condition. Root null -> path does not exist
+ if (elem == null) {
+ return null;
+ }
+
+ // End condition. String empty, return current root
+ if (path == null || path.length() == 0) {
+ return elem;
+ }
+
+ // Strip of first tag
+ int idx = path.indexOf('>');
+ if (!(path.charAt(0) == '<')) {
+ throw new IllegalArgumentException("bad path. Missing begin tag");
+ }
+ if (idx == -1) {
+ throw new IllegalArgumentException("bad path. Missing end tag");
+ }
+ String head = path.substring(1, idx);
+ String tail = path.substring(idx + 1);
+ return findElementPath(findChildElement(elem, head), tail);
+ }
+
+ /** Returns an child element with the current tag name or null. */
+ public static XMLNode findChildElement(XMLNode elem, String tag) {
+ XMLNode n = elem.getNested();
+ while (n != null) {
+ if (n.isElement() && n.getName().equals(tag)) {
+ return n;
+ }
+ n = n.getNext();
+ }
+ return null;
+ }
+
+ /** Iterator class */
+ public abstract static class ElementVisitor {
+ abstract public void visitElement(XMLNode e) throws BadFieldException, MissingFieldException;
+ }
+
+ /** Visits all elements which matches the <path>. The iteration is only
+ * done on the last element in the path.
+ */
+ public static void visitElements(XMLNode root, String path, ElementVisitor ev)
+ throws BadFieldException, MissingFieldException {
+ // Get last element in path
+ int idx = path.lastIndexOf('<');
+ if (idx == -1) {
+ throw new IllegalArgumentException(
+ "bad path. Must contain atleast one tag");
+ }
+ if (path.length() == 0 || path.charAt(path.length() - 1) != '>') {
+ throw new IllegalArgumentException("bad path. Must end with a >");
+ }
+ String head = path.substring(0, idx);
+ String tag = path.substring(idx + 1, path.length() - 1);
+
+ XMLNode elem = findElementPath(root, head);
+ if (elem == null) {
+ return;
+ }
+
+ // Iterate through all child nodes
+ XMLNode n = elem.getNested();
+ while (n != null) {
+ if (n.isElement() && n.getName().equals(tag)) {
+ ev.visitElement(n);
+ }
+ n = n.getNext();
+ }
+ }
+
+ public static void visitChildrenElements(XMLNode elem, ElementVisitor ev)
+ throws BadFieldException, MissingFieldException {
+ // Iterate through all child nodes
+ XMLNode n = elem.getNested();
+ while (n != null) {
+ if (n.isElement()) {
+ ev.visitElement(n);
+ }
+ n = n.getNext();
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/exception/BadFieldException.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2006, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jnlp.converter.parser.exception;
+
+public class BadFieldException extends Exception {
+
+ private final String _field;
+ private final String _value;
+
+ public BadFieldException(String source, String field, String value) {
+ super();
+ _value = value;
+ _field = field;
+ }
+
+ /**
+ * Returns the name of the offending field
+ */
+ public String getField() {
+ return _field;
+ }
+
+ /**
+ * Returns the value of the offending field
+ */
+ public String getValue() {
+ return _value;
+ }
+
+ /**
+ * toString implementation
+ */
+ @Override
+ public String toString() {
+ return "BadFieldException[ " + getField() + "," + getValue() + "]";
+ }
+
+ @Override
+ public String getMessage(){
+ return toString();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/exception/JNLParseException.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2006, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jnlp.converter.parser.exception;
+
+/**
+ * Exception thrown if a parse error occurred when interpreting
+ * the launch descriptor
+ */
+
+public class JNLParseException extends Exception {
+
+ private final String _msg;
+ private final int _line;
+
+ public JNLParseException(Exception exception, String msg, int line) {
+ super(exception);
+ _msg = msg;
+ _line = line;
+ }
+
+ @Override
+ public String getMessage() {
+ return _msg;
+ }
+
+ private int getLine() {
+ return _line;
+ }
+
+ @Override
+ public String toString() {
+ return "JNLParseException[ " + getMessage() + "] at " + getLine();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/exception/MissingFieldException.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2006, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jnlp.converter.parser.exception;
+
+public class MissingFieldException extends Exception {
+
+ private final String _field;
+
+ public MissingFieldException(String source, String field) {
+ super();
+ _field = field;
+ }
+
+ /**
+ * Returns the name of the offending field
+ */
+ private String getField() {
+ return _field;
+ }
+
+ /**
+ * toString implementation
+ */
+ @Override
+ public String toString() {
+ return "MissingFieldException[ " + getField() + "]";
+ }
+
+ @Override
+ public String getMessage(){
+ return toString();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/xml/XMLAttribute.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2006, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jnlp.converter.parser.xml;
+
+/**
+ * Class that contains information about a specific attribute
+ */
+public class XMLAttribute {
+
+ private final String _name;
+ private final String _value;
+ private XMLAttribute _next;
+
+ public XMLAttribute(String name, String value) {
+ _name = XMLNode.stripNameSpace(name);
+ _value = value;
+ }
+
+ public String getName() {
+ return _name;
+ }
+
+ public String getValue() {
+ return _value;
+ }
+
+ public XMLAttribute getNext() {
+ return _next;
+ }
+
+ public void setNext(XMLAttribute next) {
+ _next = next;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/xml/XMLEncoding.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,342 @@
+/*
+ * Copyright (c) 2006, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jnlp.converter.parser.xml;
+
+import java.io.ByteArrayInputStream;
+import java.io.EOFException;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+
+public class XMLEncoding {
+ /**
+ * Decodes a byte stream into a String by testing for a Byte Order Mark
+ * (BOM) or an XML declaration.
+ * <br />
+ * Detection begins by examining the first four octets of the stream for a
+ * BOM. If a BOM is not found, then an encoding declaration is looked for
+ * at the beginning of the stream. If the encoding still can not be
+ * determined at this point, then UTF-8 is assumed.
+ *
+ * @param data an array of bytes containing an encoded XML document.
+ *
+ * @return A string containing the decoded XML document.
+ */
+ public static String decodeXML(byte [] data) throws IOException {
+ int start = 0;
+ String encoding;
+
+ if (data.length < BOM_LENGTH) {
+ throw (new EOFException("encoding.error.not.xml"));
+ }
+ // no else required; successfully read stream
+ int firstFour = ((0xff000000 & ((int) data[0] << 24)) |
+ (0x00ff0000 & ((int) data[1] << 16)) |
+ (0x0000ff00 & ((int) data[2] << 8)) |
+ (0x000000ff & (int) data[3]));
+
+ // start by examining the first four bytes for a BOM
+ switch (firstFour) {
+ case EBCDIC:
+ // examine the encoding declaration
+ encoding = examineEncodingDeclaration(data, IBM037_ENC);
+ break;
+
+ case XML_DECLARATION:
+ // assume UTF-8, but examine the encoding declaration
+ encoding = examineEncodingDeclaration(data, UTF_8_ENC);
+ break;
+
+ case UTF_16BE:
+ encoding = UTF_16BE_ENC;
+ break;
+
+ case UTF_16LE:
+ encoding = UTF_16LE_ENC;
+ break;
+
+ case UNUSUAL_OCTET_1:
+ case UNUSUAL_OCTET_2:
+ throw (new UnsupportedEncodingException("encoding.error.unusual.octet"));
+
+ case UTF_32_BE_BOM:
+ case UTF_32_LE_BOM:
+ encoding = UTF_32_ENC;
+ break;
+
+ default:
+ int firstThree = firstFour & 0xffffff00;
+
+ switch (firstThree) {
+ case UTF_8_BOM:
+ // the InputStreamReader class doen't properly handle
+ // the Byte Order Mark (BOM) in UTF-8 streams, so don't
+ // putback those 3 bytes.
+ start = 3;
+ encoding = UTF_8_ENC;
+ break;
+
+ default:
+ int firstTwo = firstFour & 0xffff0000;
+
+ switch (firstTwo) {
+ case UTF_16_BE_BOM:
+ case UTF_16_LE_BOM:
+ encoding = UTF_16_ENC;
+ break;
+
+ default:
+ // this is probably UTF-8 without the encoding
+ // declaration
+ encoding = UTF_8_ENC;
+ break;
+ }
+ break;
+ }
+ break;
+ }
+
+ return (new String(data, start, data.length - start, encoding));
+ }
+
+ /**
+ * [3] S ::= ( #x20 | #x09 | #x0d | #x0a )
+ * [23] XMLDecl ::= '<?xml' VersionInfo EncodingDecl? SDDecl? S? '?>'
+ * [24] VersionInfo ::= S 'version' Eq ( '"' VersionNum '"' |
+ * "'" VersionNum "'" )
+ * [25] Eq ::= S? '=' S?
+ * [26] VersionNum ::= ([a-zA-Z0-9_.:] | '-')+
+ * [80] EncodingDecl ::= S 'encoding' Eq ( '"' EncName '"' |
+ * "'" EncName "'" )
+ * [81] EncName ::= [a-zA-Z] ([a-zA-Z0-9_.] | '-')*
+ */
+ private static String examineEncodingDeclaration(byte [] data,
+ String encoding) throws IOException {
+ boolean loop = false;
+ boolean recognized = false;
+ boolean almost = false;
+ boolean question = false;
+ boolean done = false;
+ boolean found = false;
+ int pos = 0;
+ int ch = -1;
+ Reader reader = null;
+ String result = ((encoding != null) ? encoding : UTF_8_ENC);
+
+ reader = new InputStreamReader(new ByteArrayInputStream(data), result);
+ ch = reader.read();
+
+ // if this is an XML declaration, it will start with the text '<?xml'
+ for (int i = 0; ((i < XML_DECL_START.length()) && (done == false)); i++) {
+ if (ch != XML_DECL_START.charAt(i)) {
+ // This doesn't look like an XML declaration. This method
+ // should only be called if the stream contains an XML
+ // declaration in the encoding that is passed into the method.
+ done = true;
+ break;
+ }
+ // no else required; still matches
+ ch = reader.read();
+ }
+
+ // there must be at least one whitespace character next.
+ loop = true;
+ while ((loop == true) && (done == false)) {
+ switch (ch) {
+ case SPACE:
+ case TAB: // intentional
+ case LINEFEED: // fall
+ case RETURN: // through
+ ch = reader.read();
+ break;
+
+ case -1:
+ // unexpected EOF
+ done = true;
+ break;
+
+ default:
+ // non-whitespace
+ loop = false;
+ break;
+ }
+ }
+
+ // now look for the text 'encoding', but if the end of the XML
+ // declaration (signified by the text '?>') comes first, then
+ // assume the encoding is UTF-8
+ loop = true;
+ while ((loop == true) && (done == false)) {
+ if (ch == -1) {
+ // unexpected EOF
+ done = true;
+ break;
+ } else if (recognized == true) {
+ // this is the encoding declaration as long as the next few
+ // characters are whitespace and/or the equals ('=') sign
+ switch (ch) {
+ case SPACE: // intentional
+ case TAB: // fall
+ case LINEFEED: // through
+ case RETURN:
+ // don't need to do anything
+ break;
+
+ case EQUAL:
+ if (almost == false) {
+ // got the equal, now find a quote
+ almost = true;
+ } else {
+ // this is not valid XML, so punt
+ recognized = false;
+ done = true;
+ }
+ break;
+
+ case DOUBLE_QUOTE: // intentional
+ case SINGLE_QUOTE: // fall through
+ if (almost == true) {
+ // got the quote, so move on to get the value
+ loop = false;
+ } else {
+ // got a quote before the equal; this is not valid
+ // XML, so punt
+ recognized = false;
+ done = true;
+ }
+ break;
+
+ default:
+ // non-whitespace
+ recognized = false;
+ if (almost == true) {
+ // this is not valid XML, so punt
+ done = true;
+ }
+ // no else required; this wasn't the encoding
+ // declaration
+ break;
+ }
+
+ if (recognized == false) {
+ // this isn't the encoding declaration, so go back to the
+ // top without reading the next character
+ pos = 0;
+ continue;
+ }
+ // no else required; still looking good
+ } else if (ch == ENCODING_DECL.charAt(pos++)) {
+ if (ENCODING_DECL.length() == pos) {
+ // this looks like the encoding declaration
+ recognized = true;
+ }
+ // no else required; this might be the encoding declaration
+ } else if (ch == '?') {
+ question = true;
+ pos = 0;
+ } else if ((ch == '>') && (question == true)) {
+ // there is no encoding declaration, so assume that the initial
+ // encoding guess was correct
+ done = true;
+ continue;
+ } else {
+ // still searching for the encoding declaration
+ pos = 0;
+ }
+
+ ch = reader.read();
+ }
+
+ if (done == false) {
+ StringBuilder buffer = new StringBuilder(MAX_ENC_NAME);
+
+ if (((ch >= 'a') && (ch <= 'z')) |
+ ((ch >= 'A') && (ch <= 'Z'))) {
+ // add the character to the result
+ buffer.append((char) ch);
+
+ loop = true;
+ while ((loop == true) && (done == false)) {
+ ch = reader.read();
+
+ if (((ch >= 'a') && (ch <= 'z')) ||
+ ((ch >= 'A') && (ch <= 'Z')) ||
+ ((ch >= '0') && (ch <= '9')) ||
+ (ch == '_') || (ch == '.') || (ch == '-')) {
+ // add the character to the result
+ buffer.append((char) ch);
+ } else if ((ch == DOUBLE_QUOTE) || (ch == SINGLE_QUOTE)) {
+ // finished!
+ found = true;
+ done = true;
+ result = buffer.toString();
+ } else {
+ // this is not a valid encoding name, so punt
+ done = true;
+ }
+ }
+ } else {
+ // this is not a valid encoding name, so punt
+ done = true;
+ }
+ }
+ // no else required; already failed to find the encoding somewhere else
+
+ return (result);
+ }
+
+ private static final int BOM_LENGTH = 4;
+ private static final int MAX_ENC_NAME = 512;
+
+ private static final int SPACE = 0x00000020;
+ private static final int TAB = 0x00000009;
+ private static final int LINEFEED = 0x0000000a;
+ private static final int RETURN = 0x0000000d;
+ private static final int EQUAL = '=';
+ private static final int DOUBLE_QUOTE = '\"';
+ private static final int SINGLE_QUOTE = '\'';
+
+ private static final int UTF_32_BE_BOM = 0x0000feff;
+ private static final int UTF_32_LE_BOM = 0xfffe0000;
+ private static final int UTF_16_BE_BOM = 0xfeff0000;
+ private static final int UTF_16_LE_BOM = 0xfffe0000;
+ private static final int UTF_8_BOM = 0xefbbbf00;
+ private static final int UNUSUAL_OCTET_1 = 0x00003c00;
+ private static final int UNUSUAL_OCTET_2 = 0x003c0000;
+ private static final int UTF_16BE = 0x003c003f;
+ private static final int UTF_16LE = 0x3c003f00;
+ private static final int EBCDIC = 0x4c6fa794;
+ private static final int XML_DECLARATION = 0x3c3f786d;
+
+ private static final String UTF_32_ENC = "UTF-32";
+ private static final String UTF_16_ENC = "UTF-16";
+ private static final String UTF_16BE_ENC = "UTF-16BE";
+ private static final String UTF_16LE_ENC = "UTF-16LE";
+ private static final String UTF_8_ENC = "UTF-8";
+ private static final String IBM037_ENC = "IBM037";
+
+ private static final String XML_DECL_START = "<?xml";
+ private static final String ENCODING_DECL = "encoding";
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/xml/XMLNode.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2006, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jnlp.converter.parser.xml;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Class that contains information about an XML Node
+ */
+public class XMLNode {
+ private final boolean _isElement; // Element/PCTEXT
+ private final String _name;
+ private final XMLAttribute _attr;
+ private XMLNode _parent; // Parent Node
+ private XMLNode _nested; // Nested XML tags
+ private XMLNode _next; // Following XML tag on the same level
+
+ public final static String WILDCARD = "*";
+
+ /** Creates a PCTEXT node */
+ public XMLNode(String name) {
+ _isElement = false;
+ _name = name;
+ _attr = null;
+ _nested = null;
+ _next = null;
+ _parent = null;
+ }
+
+ /** Creates a ELEMENT node */
+ public XMLNode(String name, XMLAttribute attr) {
+ _isElement = true;
+ _name = stripNameSpace(name);
+ _attr = attr;
+ _nested = null;
+ _next = null;
+ _parent = null;
+ }
+
+ public String getName() {
+ return _name;
+ }
+
+ public XMLAttribute getAttributes() {
+ return _attr;
+ }
+
+ public XMLNode getNested() {
+ return _nested;
+ }
+
+ public XMLNode getNext() {
+ return _next;
+ }
+
+ public boolean isElement() {
+ return _isElement;
+ }
+
+ public void setParent(XMLNode parent) {
+ _parent = parent;
+ }
+
+ public XMLNode getParent() {
+ return _parent;
+ }
+
+ public void setNext(XMLNode next) {
+ _next = next;
+ }
+
+ public void setNested(XMLNode nested) {
+ _nested = nested;
+ }
+
+ public static String stripNameSpace(String name) {
+ if (name != null && !name.startsWith("xmlns:")) {
+ int i = name.lastIndexOf(":");
+ if (i >= 0 && i < name.length()) {
+ return name.substring(i+1);
+ }
+ }
+ return name;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 3;
+ hash = 83 * hash + (this._name != null ? this._name.hashCode() : 0);
+ hash = 83 * hash + (this._attr != null ? this._attr.hashCode() : 0);
+ hash = 83 * hash + (this._nested != null ? this._nested.hashCode() : 0);
+ hash = 83 * hash + (this._next != null ? this._next.hashCode() : 0);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || !(o instanceof XMLNode)) return false;
+ XMLNode other = (XMLNode)o;
+ boolean result =
+ match(_name, other._name) &&
+ match(_attr, other._attr) &&
+ match(_nested, other._nested) &&
+ match(_next, other._next);
+ return result;
+ }
+
+ public String getAttribute(String name) {
+ XMLAttribute cur = _attr;
+ while(cur != null) {
+ if (name.equals(cur.getName())) return cur.getValue();
+ cur = cur.getNext();
+ }
+ return "";
+ }
+
+ private static boolean match(Object o1, Object o2) {
+ if (o1 == null) {
+ return (o2 == null);
+ }
+ return o1.equals(o2);
+ }
+
+ public void printToStream(PrintWriter out) {
+ printToStream(out, false);
+ }
+
+ public void printToStream(PrintWriter out, boolean trim) {
+ printToStream(out, 0, trim);
+ }
+
+ public void printToStream(PrintWriter out, int n, boolean trim) {
+ if (!isElement()) {
+ String value = _name; // value node (where name is data of parent)
+ if (trim && value.length() > 512) {
+ value = "...";
+ }
+ out.print(value);
+ } else {
+ if (_nested == null) {
+ String attrString = (_attr == null) ? "" : (" " + _attr.toString());
+ lineln(out, n, "<" + _name + attrString + "/>");
+ } else {
+ String attrString = (_attr == null) ? "" : (" " + _attr.toString());
+ lineln(out, n, "<" + _name + attrString + ">");
+ _nested.printToStream(out, n + 1, trim);
+ if (_nested.isElement()) {
+ lineln(out, n, "</" + _name + ">");
+ } else {
+ out.print("</" + _name + ">");
+ }
+ }
+ }
+ if (_next != null) {
+ _next.printToStream(out, n, trim);
+ }
+ }
+
+ private static void lineln(PrintWriter out, int indent, String s) {
+ out.println("");
+ for(int i = 0; i < indent; i++) {
+ out.print(" ");
+ }
+ out.print(s);
+ }
+
+ @Override
+ public String toString() {
+ return toString(false);
+ }
+
+ public String toString(boolean hideLongElementValue) {
+ StringWriter sw = new StringWriter(1000);
+ PrintWriter pw = new PrintWriter(sw);
+ printToStream(pw, hideLongElementValue);
+ pw.close();
+ return sw.toString();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/xml/XMLParser.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jnlp.converter.parser.xml;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.xml.sax.InputSource;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.io.StringReader;
+import java.io.IOException;
+import java.util.Stack;
+
+public class XMLParser extends DefaultHandler {
+
+ private XMLNode _root;
+ private final String _source;
+ private Stack<XMLNode> _inProgress;
+ private String _characters;
+
+ // although defined in com.sun.org.apache.xerces.internal.impl.Constants,
+ // we should not be able to access that, so defined here
+ private final static String DTD_DOWNLOAD =
+ "http://apache.org/xml/features/nonvalidating/load-external-dtd";
+
+ private static final SAXParserFactory SPF = SAXParserFactory.newInstance();
+
+ /*
+ * Construct an <code>XMLParser</code>.
+ *
+ * @param source - the source text to parse.
+ */
+ public XMLParser(String source) {
+ _source = source.trim();
+ }
+
+ public XMLNode parse() throws SAXException {
+ // normally we parse without validating, but leave option to parse
+ // with validation, possibly controlled by config option.
+ return parse(false);
+ }
+
+ public XMLNode parse(boolean validating) throws SAXException {
+ _root = null;
+ _inProgress = new Stack<>();
+
+ try {
+ InputSource is = new InputSource(new StringReader(_source));
+ SPF.setValidating(validating);
+ // only download dtd file from DOCTYPE if we are doing validation
+ try {
+ SPF.setFeature(DTD_DOWNLOAD, validating);
+ } catch (Exception e) {
+ }
+ SAXParser sp = SPF.newSAXParser();
+ sp.parse(is, this);
+ } catch (ParserConfigurationException | IOException pce) {
+ throw new SAXException(pce);
+ }
+
+ return _root;
+ }
+
+ @Override
+ public void startElement(String uri, String localeName, String qName,
+ Attributes attributes) throws SAXException {
+
+ XMLAttribute first = null;
+ XMLAttribute last = null;
+ int len = attributes.getLength();
+
+ for (int i = 0; i < len; i++) {
+ XMLAttribute att = new XMLAttribute(
+ // in old implementation attribute names and values were trimmed
+ attributes.getQName(i).trim(), attributes.getValue(i).trim());
+ if (first == null) {
+ first = att;
+ }
+ if (last != null) {
+ last.setNext(att);
+ }
+ last = att;
+ }
+ _inProgress.push(new XMLNode(qName, first));
+ _characters = null;
+ }
+
+ @Override
+ public void endElement(String uri, String localeName, String elementName)
+ throws SAXException {
+ XMLNode node = _inProgress.pop();
+ // <information>
+ // <title>Title</title>
+ // <vendor>Vendor</vendor> "Some whitespaces"
+ // </information>
+ // In example above when we receive end of <information> we will
+ // have _characters set to whitespace and new line and it will be
+ // added as child node to <information>. This will break our cache code
+ // which will think that JNLP file changed on server even if it is not
+ // and thus we might not load properly.
+ //
+ // <application-desc name="HelloWorld" main-class="HelloWorld">
+ // <argument> test with whitespaces </argument>
+ // </application-desc>
+ // From example above we want to include whitespaces for <argument>.
+ //
+ // <node1>
+ // <node2>abc</node2>
+ // xyz (might be whitespaces)
+ // </node1>
+ // In JNLP spec we do not have cases when node have nested nodes as
+ // well as text which is whitespaces only.
+ //
+ // So to fix it lets check if ending node have nested nodes, then do
+ // not add whitespaces only node.
+ if (node != null && node.getNested() != null && _characters != null) {
+ String trimCharacters = _characters.trim();
+ if ((trimCharacters == null) || (trimCharacters.length() == 0)) {
+ _characters = null; // No need to add whitespaces only
+ }
+ }
+ if ((_characters != null) && (_characters.trim().length() > 0)) {
+ addChild(node, new XMLNode(_characters));
+ }
+
+ if (_inProgress.isEmpty()) {
+ _root = node;
+ } else {
+ addChild(_inProgress.peek(), node);
+ }
+ _characters = null;
+ }
+
+ @Override
+ public void characters(char[] chars, int start, int length)
+ throws SAXException {
+ String s = new String(chars, start, length);
+ _characters = ((_characters == null) ? s : _characters + s);
+ }
+
+ @Override
+ public void ignorableWhitespace(char[] chars, int start, int length)
+ throws SAXException {
+ String s = new String(chars, start, length);
+ _characters = ((_characters == null) ? s : _characters + s);
+ }
+
+ private void addChild(XMLNode parent, XMLNode child) {
+ child.setParent(parent);
+
+ XMLNode sibling = parent.getNested();
+ if (sibling == null) {
+ parent.setNested(child); // set us as only child
+ } else {
+ while (sibling.getNext() != null) {
+ sibling = sibling.getNext();
+ }
+ sibling.setNext(child); // sets us as youngest child
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/nbproject/jpackage/JNLPConverter/build.xml Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- You may freely edit this file. See commented blocks below for -->
+<!-- some examples of how to customize the build. -->
+<!-- (If you delete it and reopen the project it will be recreated.) -->
+<!-- By default, only the Clean and Build commands use this build script. -->
+<!-- Commands such as Run, Debug, and Test only use this build script if -->
+<!-- the Compile on Save feature is turned off for the project. -->
+<!-- You can turn off the Compile on Save (or Deploy on Save) setting -->
+<!-- in the project's Project Properties dialog box.-->
+<project name="JNLPConverter" default="default" basedir=".">
+ <description>Builds, tests, and runs the project JNLPConverter.</description>
+ <import file="nbproject/build-impl.xml"/>
+ <!--
+
+ There exist several targets which are by default empty and which can be
+ used for execution of your tasks. These targets are usually executed
+ before and after some main targets. They are:
+
+ -pre-init: called before initialization of project properties
+ -post-init: called after initialization of project properties
+ -pre-compile: called before javac compilation
+ -post-compile: called after javac compilation
+ -pre-compile-single: called before javac compilation of single file
+ -post-compile-single: called after javac compilation of single file
+ -pre-compile-test: called before javac compilation of JUnit tests
+ -post-compile-test: called after javac compilation of JUnit tests
+ -pre-compile-test-single: called before javac compilation of single JUnit test
+ -post-compile-test-single: called after javac compilation of single JUunit test
+ -pre-jar: called before JAR building
+ -post-jar: called after JAR building
+ -post-clean: called after cleaning build products
+
+ (Targets beginning with '-' are not intended to be called on their own.)
+
+ Example of inserting an obfuscator after compilation could look like this:
+
+ <target name="-post-compile">
+ <obfuscate>
+ <fileset dir="${build.classes.dir}"/>
+ </obfuscate>
+ </target>
+
+ For list of available properties check the imported
+ nbproject/build-impl.xml file.
+
+
+ Another way to customize the build is by overriding existing main targets.
+ The targets of interest are:
+
+ -init-macrodef-javac: defines macro for javac compilation
+ -init-macrodef-junit: defines macro for junit execution
+ -init-macrodef-debug: defines macro for class debugging
+ -init-macrodef-java: defines macro for class execution
+ -do-jar: JAR building
+ run: execution of project
+ -javadoc-build: Javadoc generation
+ test-report: JUnit report generation
+
+ An example of overriding the target for project execution could look like this:
+
+ <target name="run" depends="JNLPConverter-impl.jar">
+ <exec dir="bin" executable="launcher.exe">
+ <arg file="${dist.jar}"/>
+ </exec>
+ </target>
+
+ Notice that the overridden target depends on the jar target and not only on
+ the compile target as the regular run target does. Again, for a list of available
+ properties which you can use, check the target you are overriding in the
+ nbproject/build-impl.xml file.
+
+ -->
+ <target name="-post-jar">
+ <copy file="${dist.jar}" todir="../../../jpackage/jnlpconverter"/>
+ </target>
+
+ <target name="-post-clean">
+ <delete file="../../../jpackage/jnlpconverter/JNLPConverter.jar"/>
+ </target>
+</project>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/nbproject/jpackage/JNLPConverter/manifest.mf Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+X-COMMENT: Main-Class will be added automatically by build
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/nbproject/jpackage/JNLPConverter/nbproject/build-impl.xml Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,1403 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+*** GENERATED FROM project.xml - DO NOT EDIT ***
+*** EDIT ../build.xml INSTEAD ***
+
+For the purpose of easier reading the script
+is divided into following sections:
+
+ - initialization
+ - compilation
+ - jar
+ - execution
+ - debugging
+ - javadoc
+ - test compilation
+ - test execution
+ - test debugging
+ - applet
+ - cleanup
+
+ -->
+<project xmlns:j2seproject1="http://www.netbeans.org/ns/j2se-project/1" xmlns:j2seproject3="http://www.netbeans.org/ns/j2se-project/3" xmlns:jaxrpc="http://www.netbeans.org/ns/j2se-project/jax-rpc" basedir=".." default="default" name="JNLPConverter-impl">
+ <fail message="Please build using Ant 1.8.0 or higher.">
+ <condition>
+ <not>
+ <antversion atleast="1.8.0"/>
+ </not>
+ </condition>
+ </fail>
+ <target depends="test,jar,javadoc" description="Build and test whole project." name="default"/>
+ <!--
+ ======================
+ INITIALIZATION SECTION
+ ======================
+ -->
+ <target name="-pre-init">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="-pre-init" name="-init-private">
+ <property file="nbproject/private/config.properties"/>
+ <property file="nbproject/private/configs/${config}.properties"/>
+ <property file="nbproject/private/private.properties"/>
+ </target>
+ <target depends="-pre-init,-init-private" name="-init-user">
+ <property file="${user.properties.file}"/>
+ <!-- The two properties below are usually overridden -->
+ <!-- by the active platform. Just a fallback. -->
+ <property name="default.javac.source" value="1.6"/>
+ <property name="default.javac.target" value="1.6"/>
+ </target>
+ <target depends="-pre-init,-init-private,-init-user" name="-init-project">
+ <property file="nbproject/configs/${config}.properties"/>
+ <property file="nbproject/project.properties"/>
+ </target>
+ <target depends="-pre-init,-init-private,-init-user,-init-project,-init-macrodef-property" name="-do-init">
+ <property name="platform.java" value="${java.home}/bin/java"/>
+ <available file="${manifest.file}" property="manifest.available"/>
+ <condition property="splashscreen.available">
+ <and>
+ <not>
+ <equals arg1="${application.splash}" arg2="" trim="true"/>
+ </not>
+ <available file="${application.splash}"/>
+ </and>
+ </condition>
+ <condition property="main.class.available">
+ <and>
+ <isset property="main.class"/>
+ <not>
+ <equals arg1="${main.class}" arg2="" trim="true"/>
+ </not>
+ </and>
+ </condition>
+ <condition property="profile.available">
+ <and>
+ <isset property="javac.profile"/>
+ <length length="0" string="${javac.profile}" when="greater"/>
+ <matches pattern="((1\.[89])|9)(\..*)?" string="${javac.source}"/>
+ </and>
+ </condition>
+ <condition property="do.archive">
+ <or>
+ <not>
+ <istrue value="${jar.archive.disabled}"/>
+ </not>
+ <istrue value="${not.archive.disabled}"/>
+ </or>
+ </condition>
+ <condition property="do.mkdist">
+ <and>
+ <isset property="do.archive"/>
+ <isset property="libs.CopyLibs.classpath"/>
+ <not>
+ <istrue value="${mkdist.disabled}"/>
+ </not>
+ </and>
+ </condition>
+ <condition property="do.archive+manifest.available">
+ <and>
+ <isset property="manifest.available"/>
+ <istrue value="${do.archive}"/>
+ </and>
+ </condition>
+ <condition property="do.archive+main.class.available">
+ <and>
+ <isset property="main.class.available"/>
+ <istrue value="${do.archive}"/>
+ </and>
+ </condition>
+ <condition property="do.archive+splashscreen.available">
+ <and>
+ <isset property="splashscreen.available"/>
+ <istrue value="${do.archive}"/>
+ </and>
+ </condition>
+ <condition property="do.archive+profile.available">
+ <and>
+ <isset property="profile.available"/>
+ <istrue value="${do.archive}"/>
+ </and>
+ </condition>
+ <condition property="have.tests">
+ <or/>
+ </condition>
+ <condition property="have.sources">
+ <or>
+ <available file="${src.src.dir}"/>
+ </or>
+ </condition>
+ <condition property="netbeans.home+have.tests">
+ <and>
+ <isset property="netbeans.home"/>
+ <isset property="have.tests"/>
+ </and>
+ </condition>
+ <condition property="no.javadoc.preview">
+ <and>
+ <isset property="javadoc.preview"/>
+ <isfalse value="${javadoc.preview}"/>
+ </and>
+ </condition>
+ <property name="run.jvmargs" value=""/>
+ <property name="run.jvmargs.ide" value=""/>
+ <property name="javac.compilerargs" value=""/>
+ <property name="work.dir" value="${basedir}"/>
+ <condition property="no.deps">
+ <and>
+ <istrue value="${no.dependencies}"/>
+ </and>
+ </condition>
+ <property name="javac.debug" value="true"/>
+ <property name="javadoc.preview" value="true"/>
+ <property name="application.args" value=""/>
+ <property name="source.encoding" value="${file.encoding}"/>
+ <property name="runtime.encoding" value="${source.encoding}"/>
+ <property name="manifest.encoding" value="${source.encoding}"/>
+ <condition property="javadoc.encoding.used" value="${javadoc.encoding}">
+ <and>
+ <isset property="javadoc.encoding"/>
+ <not>
+ <equals arg1="${javadoc.encoding}" arg2=""/>
+ </not>
+ </and>
+ </condition>
+ <property name="javadoc.encoding.used" value="${source.encoding}"/>
+ <property name="includes" value="**"/>
+ <property name="excludes" value=""/>
+ <property name="do.depend" value="false"/>
+ <condition property="do.depend.true">
+ <istrue value="${do.depend}"/>
+ </condition>
+ <path id="endorsed.classpath.path" path="${endorsed.classpath}"/>
+ <condition else="" property="endorsed.classpath.cmd.line.arg" value="-Xbootclasspath/p:'${toString:endorsed.classpath.path}'">
+ <and>
+ <isset property="endorsed.classpath"/>
+ <not>
+ <equals arg1="${endorsed.classpath}" arg2="" trim="true"/>
+ </not>
+ </and>
+ </condition>
+ <condition else="" property="javac.profile.cmd.line.arg" value="-profile ${javac.profile}">
+ <isset property="profile.available"/>
+ </condition>
+ <condition else="false" property="jdkBug6558476">
+ <and>
+ <matches pattern="1\.[56]" string="${java.specification.version}"/>
+ <not>
+ <os family="unix"/>
+ </not>
+ </and>
+ </condition>
+ <condition else="false" property="javac.fork">
+ <or>
+ <istrue value="${jdkBug6558476}"/>
+ <istrue value="${javac.external.vm}"/>
+ </or>
+ </condition>
+ <property name="jar.index" value="false"/>
+ <property name="jar.index.metainf" value="${jar.index}"/>
+ <property name="copylibs.rebase" value="true"/>
+ <available file="${meta.inf.dir}/persistence.xml" property="has.persistence.xml"/>
+ <condition property="junit.available">
+ <or>
+ <available classname="org.junit.Test" classpath="${run.test.classpath}"/>
+ <available classname="junit.framework.Test" classpath="${run.test.classpath}"/>
+ </or>
+ </condition>
+ <condition property="testng.available">
+ <available classname="org.testng.annotations.Test" classpath="${run.test.classpath}"/>
+ </condition>
+ <condition property="junit+testng.available">
+ <and>
+ <istrue value="${junit.available}"/>
+ <istrue value="${testng.available}"/>
+ </and>
+ </condition>
+ <condition else="testng" property="testng.mode" value="mixed">
+ <istrue value="${junit+testng.available}"/>
+ </condition>
+ <condition else="" property="testng.debug.mode" value="-mixed">
+ <istrue value="${junit+testng.available}"/>
+ </condition>
+ <property name="java.failonerror" value="true"/>
+ </target>
+ <target name="-post-init">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="-pre-init,-init-private,-init-user,-init-project,-do-init" name="-init-check">
+ <fail unless="src.src.dir">Must set src.src.dir</fail>
+ <fail unless="build.dir">Must set build.dir</fail>
+ <fail unless="dist.dir">Must set dist.dir</fail>
+ <fail unless="build.classes.dir">Must set build.classes.dir</fail>
+ <fail unless="dist.javadoc.dir">Must set dist.javadoc.dir</fail>
+ <fail unless="build.test.classes.dir">Must set build.test.classes.dir</fail>
+ <fail unless="build.test.results.dir">Must set build.test.results.dir</fail>
+ <fail unless="build.classes.excludes">Must set build.classes.excludes</fail>
+ <fail unless="dist.jar">Must set dist.jar</fail>
+ </target>
+ <target name="-init-macrodef-property">
+ <macrodef name="property" uri="http://www.netbeans.org/ns/j2se-project/1">
+ <attribute name="name"/>
+ <attribute name="value"/>
+ <sequential>
+ <property name="@{name}" value="${@{value}}"/>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-init-ap-cmdline-properties" if="ap.supported.internal" name="-init-macrodef-javac-with-processors">
+ <macrodef name="javac" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${src.src.dir}" name="srcdir"/>
+ <attribute default="${build.classes.dir}" name="destdir"/>
+ <attribute default="${javac.classpath}" name="classpath"/>
+ <attribute default="${javac.processorpath}" name="processorpath"/>
+ <attribute default="${build.generated.sources.dir}/ap-source-output" name="apgeneratedsrcdir"/>
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="${javac.debug}" name="debug"/>
+ <attribute default="${empty.dir}" name="sourcepath"/>
+ <attribute default="${empty.dir}" name="gensrcdir"/>
+ <element name="customize" optional="true"/>
+ <sequential>
+ <property location="${build.dir}/empty" name="empty.dir"/>
+ <mkdir dir="${empty.dir}"/>
+ <mkdir dir="@{apgeneratedsrcdir}"/>
+ <javac debug="@{debug}" deprecation="${javac.deprecation}" destdir="@{destdir}" encoding="${source.encoding}" excludes="@{excludes}" fork="${javac.fork}" includeantruntime="false" includes="@{includes}" source="${javac.source}" sourcepath="@{sourcepath}" srcdir="@{srcdir}" target="${javac.target}" tempdir="${java.io.tmpdir}">
+ <src>
+ <dirset dir="@{gensrcdir}" erroronmissingdir="false">
+ <include name="*"/>
+ </dirset>
+ </src>
+ <classpath>
+ <path path="@{classpath}"/>
+ </classpath>
+ <compilerarg line="${endorsed.classpath.cmd.line.arg}"/>
+ <compilerarg line="${javac.profile.cmd.line.arg}"/>
+ <compilerarg line="${javac.compilerargs}"/>
+ <compilerarg value="-processorpath"/>
+ <compilerarg path="@{processorpath}:${empty.dir}"/>
+ <compilerarg line="${ap.processors.internal}"/>
+ <compilerarg line="${annotation.processing.processor.options}"/>
+ <compilerarg value="-s"/>
+ <compilerarg path="@{apgeneratedsrcdir}"/>
+ <compilerarg line="${ap.proc.none.internal}"/>
+ <customize/>
+ </javac>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-init-ap-cmdline-properties" name="-init-macrodef-javac-without-processors" unless="ap.supported.internal">
+ <macrodef name="javac" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${src.src.dir}" name="srcdir"/>
+ <attribute default="${build.classes.dir}" name="destdir"/>
+ <attribute default="${javac.classpath}" name="classpath"/>
+ <attribute default="${javac.processorpath}" name="processorpath"/>
+ <attribute default="${build.generated.sources.dir}/ap-source-output" name="apgeneratedsrcdir"/>
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="${javac.debug}" name="debug"/>
+ <attribute default="${empty.dir}" name="sourcepath"/>
+ <attribute default="${empty.dir}" name="gensrcdir"/>
+ <element name="customize" optional="true"/>
+ <sequential>
+ <property location="${build.dir}/empty" name="empty.dir"/>
+ <mkdir dir="${empty.dir}"/>
+ <javac debug="@{debug}" deprecation="${javac.deprecation}" destdir="@{destdir}" encoding="${source.encoding}" excludes="@{excludes}" fork="${javac.fork}" includeantruntime="false" includes="@{includes}" source="${javac.source}" sourcepath="@{sourcepath}" srcdir="@{srcdir}" target="${javac.target}" tempdir="${java.io.tmpdir}">
+ <src>
+ <dirset dir="@{gensrcdir}" erroronmissingdir="false">
+ <include name="*"/>
+ </dirset>
+ </src>
+ <classpath>
+ <path path="@{classpath}"/>
+ </classpath>
+ <compilerarg line="${endorsed.classpath.cmd.line.arg}"/>
+ <compilerarg line="${javac.profile.cmd.line.arg}"/>
+ <compilerarg line="${javac.compilerargs}"/>
+ <customize/>
+ </javac>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-init-macrodef-javac-with-processors,-init-macrodef-javac-without-processors" name="-init-macrodef-javac">
+ <macrodef name="depend" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${src.src.dir}" name="srcdir"/>
+ <attribute default="${build.classes.dir}" name="destdir"/>
+ <attribute default="${javac.classpath}" name="classpath"/>
+ <sequential>
+ <depend cache="${build.dir}/depcache" destdir="@{destdir}" excludes="${excludes}" includes="${includes}" srcdir="@{srcdir}">
+ <classpath>
+ <path path="@{classpath}"/>
+ </classpath>
+ </depend>
+ </sequential>
+ </macrodef>
+ <macrodef name="force-recompile" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${build.classes.dir}" name="destdir"/>
+ <sequential>
+ <fail unless="javac.includes">Must set javac.includes</fail>
+ <pathconvert pathsep="${line.separator}" property="javac.includes.binary">
+ <path>
+ <filelist dir="@{destdir}" files="${javac.includes}"/>
+ </path>
+ <globmapper from="*.java" to="*.class"/>
+ </pathconvert>
+ <tempfile deleteonexit="true" property="javac.includesfile.binary"/>
+ <echo file="${javac.includesfile.binary}" message="${javac.includes.binary}"/>
+ <delete>
+ <files includesfile="${javac.includesfile.binary}"/>
+ </delete>
+ <delete>
+ <fileset file="${javac.includesfile.binary}"/>
+ </delete>
+ </sequential>
+ </macrodef>
+ </target>
+ <target if="${junit.available}" name="-init-macrodef-junit-init">
+ <condition else="false" property="nb.junit.batch" value="true">
+ <and>
+ <istrue value="${junit.available}"/>
+ <not>
+ <isset property="test.method"/>
+ </not>
+ </and>
+ </condition>
+ <condition else="false" property="nb.junit.single" value="true">
+ <and>
+ <istrue value="${junit.available}"/>
+ <isset property="test.method"/>
+ </and>
+ </condition>
+ </target>
+ <target name="-init-test-properties">
+ <property name="test.binaryincludes" value="<nothing>"/>
+ <property name="test.binarytestincludes" value=""/>
+ <property name="test.binaryexcludes" value=""/>
+ </target>
+ <target if="${nb.junit.single}" name="-init-macrodef-junit-single" unless="${nb.junit.batch}">
+ <macrodef name="junit" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="**" name="testincludes"/>
+ <attribute default="" name="testmethods"/>
+ <element name="customize" optional="true"/>
+ <sequential>
+ <property name="junit.forkmode" value="perTest"/>
+ <junit dir="${work.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" forkmode="${junit.forkmode}" showoutput="true" tempdir="${build.dir}">
+ <test methods="@{testmethods}" name="@{testincludes}" todir="${build.test.results.dir}"/>
+ <syspropertyset>
+ <propertyref prefix="test-sys-prop."/>
+ <mapper from="test-sys-prop.*" to="*" type="glob"/>
+ </syspropertyset>
+ <formatter type="brief" usefile="false"/>
+ <formatter type="xml"/>
+ <jvmarg value="-ea"/>
+ <customize/>
+ </junit>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-init-test-properties" if="${nb.junit.batch}" name="-init-macrodef-junit-batch" unless="${nb.junit.single}">
+ <macrodef name="junit" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="**" name="testincludes"/>
+ <attribute default="" name="testmethods"/>
+ <element name="customize" optional="true"/>
+ <sequential>
+ <property name="junit.forkmode" value="perTest"/>
+ <junit dir="${work.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" forkmode="${junit.forkmode}" showoutput="true" tempdir="${build.dir}">
+ <batchtest todir="${build.test.results.dir}">
+ <fileset dir="${build.test.classes.dir}" excludes="@{excludes},${excludes},${test.binaryexcludes}" includes="${test.binaryincludes}">
+ <filename name="${test.binarytestincludes}"/>
+ </fileset>
+ </batchtest>
+ <syspropertyset>
+ <propertyref prefix="test-sys-prop."/>
+ <mapper from="test-sys-prop.*" to="*" type="glob"/>
+ </syspropertyset>
+ <formatter type="brief" usefile="false"/>
+ <formatter type="xml"/>
+ <jvmarg value="-ea"/>
+ <customize/>
+ </junit>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-init-macrodef-junit-init,-init-macrodef-junit-single, -init-macrodef-junit-batch" if="${junit.available}" name="-init-macrodef-junit"/>
+ <target if="${testng.available}" name="-init-macrodef-testng">
+ <macrodef name="testng" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="**" name="testincludes"/>
+ <attribute default="" name="testmethods"/>
+ <element name="customize" optional="true"/>
+ <sequential>
+ <condition else="" property="testng.methods.arg" value="@{testincludes}.@{testmethods}">
+ <isset property="test.method"/>
+ </condition>
+ <union id="test.set"/>
+ <taskdef classname="org.testng.TestNGAntTask" classpath="${run.test.classpath}" name="testng"/>
+ <testng classfilesetref="test.set" failureProperty="tests.failed" listeners="org.testng.reporters.VerboseReporter" methods="${testng.methods.arg}" mode="${testng.mode}" outputdir="${build.test.results.dir}" suitename="JNLPConverter" testname="TestNG tests" workingDir="${work.dir}">
+ <xmlfileset dir="${build.test.classes.dir}" includes="@{testincludes}"/>
+ <propertyset>
+ <propertyref prefix="test-sys-prop."/>
+ <mapper from="test-sys-prop.*" to="*" type="glob"/>
+ </propertyset>
+ <customize/>
+ </testng>
+ </sequential>
+ </macrodef>
+ </target>
+ <target name="-init-macrodef-test-impl">
+ <macrodef name="test-impl" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="**" name="testincludes"/>
+ <attribute default="" name="testmethods"/>
+ <element implicit="true" name="customize" optional="true"/>
+ <sequential>
+ <echo>No tests executed.</echo>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-init-macrodef-junit" if="${junit.available}" name="-init-macrodef-junit-impl">
+ <macrodef name="test-impl" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="**" name="testincludes"/>
+ <attribute default="" name="testmethods"/>
+ <element implicit="true" name="customize" optional="true"/>
+ <sequential>
+ <j2seproject3:junit excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
+ <customize/>
+ </j2seproject3:junit>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-init-macrodef-testng" if="${testng.available}" name="-init-macrodef-testng-impl">
+ <macrodef name="test-impl" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="**" name="testincludes"/>
+ <attribute default="" name="testmethods"/>
+ <element implicit="true" name="customize" optional="true"/>
+ <sequential>
+ <j2seproject3:testng excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
+ <customize/>
+ </j2seproject3:testng>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-init-macrodef-test-impl,-init-macrodef-junit-impl,-init-macrodef-testng-impl" name="-init-macrodef-test">
+ <macrodef name="test" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="**" name="testincludes"/>
+ <attribute default="" name="testmethods"/>
+ <sequential>
+ <j2seproject3:test-impl excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
+ <customize>
+ <classpath>
+ <path path="${run.test.classpath}"/>
+ </classpath>
+ <jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
+ <jvmarg line="${run.jvmargs}"/>
+ <jvmarg line="${run.jvmargs.ide}"/>
+ </customize>
+ </j2seproject3:test-impl>
+ </sequential>
+ </macrodef>
+ </target>
+ <target if="${junit.available}" name="-init-macrodef-junit-debug" unless="${nb.junit.batch}">
+ <macrodef name="junit-debug" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="**" name="testincludes"/>
+ <attribute default="" name="testmethods"/>
+ <element name="customize" optional="true"/>
+ <sequential>
+ <property name="junit.forkmode" value="perTest"/>
+ <junit dir="${work.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" forkmode="${junit.forkmode}" showoutput="true" tempdir="${build.dir}">
+ <test methods="@{testmethods}" name="@{testincludes}" todir="${build.test.results.dir}"/>
+ <syspropertyset>
+ <propertyref prefix="test-sys-prop."/>
+ <mapper from="test-sys-prop.*" to="*" type="glob"/>
+ </syspropertyset>
+ <formatter type="brief" usefile="false"/>
+ <formatter type="xml"/>
+ <jvmarg value="-ea"/>
+ <jvmarg line="${debug-args-line}"/>
+ <jvmarg value="-Xrunjdwp:transport=${debug-transport},address=${jpda.address}"/>
+ <customize/>
+ </junit>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-init-test-properties" if="${nb.junit.batch}" name="-init-macrodef-junit-debug-batch">
+ <macrodef name="junit-debug" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="**" name="testincludes"/>
+ <attribute default="" name="testmethods"/>
+ <element name="customize" optional="true"/>
+ <sequential>
+ <property name="junit.forkmode" value="perTest"/>
+ <junit dir="${work.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" forkmode="${junit.forkmode}" showoutput="true" tempdir="${build.dir}">
+ <batchtest todir="${build.test.results.dir}">
+ <fileset dir="${build.test.classes.dir}" excludes="@{excludes},${excludes},${test.binaryexcludes}" includes="${test.binaryincludes}">
+ <filename name="${test.binarytestincludes}"/>
+ </fileset>
+ </batchtest>
+ <syspropertyset>
+ <propertyref prefix="test-sys-prop."/>
+ <mapper from="test-sys-prop.*" to="*" type="glob"/>
+ </syspropertyset>
+ <formatter type="brief" usefile="false"/>
+ <formatter type="xml"/>
+ <jvmarg value="-ea"/>
+ <jvmarg line="${debug-args-line}"/>
+ <jvmarg value="-Xrunjdwp:transport=${debug-transport},address=${jpda.address}"/>
+ <customize/>
+ </junit>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-init-macrodef-junit-debug,-init-macrodef-junit-debug-batch" if="${junit.available}" name="-init-macrodef-junit-debug-impl">
+ <macrodef name="test-debug-impl" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="**" name="testincludes"/>
+ <attribute default="" name="testmethods"/>
+ <element implicit="true" name="customize" optional="true"/>
+ <sequential>
+ <j2seproject3:junit-debug excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
+ <customize/>
+ </j2seproject3:junit-debug>
+ </sequential>
+ </macrodef>
+ </target>
+ <target if="${testng.available}" name="-init-macrodef-testng-debug">
+ <macrodef name="testng-debug" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${main.class}" name="testClass"/>
+ <attribute default="" name="testMethod"/>
+ <element name="customize2" optional="true"/>
+ <sequential>
+ <condition else="-testclass @{testClass}" property="test.class.or.method" value="-methods @{testClass}.@{testMethod}">
+ <isset property="test.method"/>
+ </condition>
+ <condition else="-suitename JNLPConverter -testname @{testClass} ${test.class.or.method}" property="testng.cmd.args" value="@{testClass}">
+ <matches pattern=".*\.xml" string="@{testClass}"/>
+ </condition>
+ <delete dir="${build.test.results.dir}" quiet="true"/>
+ <mkdir dir="${build.test.results.dir}"/>
+ <j2seproject3:debug classname="org.testng.TestNG" classpath="${debug.test.classpath}">
+ <customize>
+ <customize2/>
+ <jvmarg value="-ea"/>
+ <arg line="${testng.debug.mode}"/>
+ <arg line="-d ${build.test.results.dir}"/>
+ <arg line="-listener org.testng.reporters.VerboseReporter"/>
+ <arg line="${testng.cmd.args}"/>
+ </customize>
+ </j2seproject3:debug>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-init-macrodef-testng-debug" if="${testng.available}" name="-init-macrodef-testng-debug-impl">
+ <macrodef name="testng-debug-impl" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${main.class}" name="testClass"/>
+ <attribute default="" name="testMethod"/>
+ <element implicit="true" name="customize2" optional="true"/>
+ <sequential>
+ <j2seproject3:testng-debug testClass="@{testClass}" testMethod="@{testMethod}">
+ <customize2/>
+ </j2seproject3:testng-debug>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-init-macrodef-junit-debug-impl" if="${junit.available}" name="-init-macrodef-test-debug-junit">
+ <macrodef name="test-debug" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="**" name="testincludes"/>
+ <attribute default="" name="testmethods"/>
+ <attribute default="${main.class}" name="testClass"/>
+ <attribute default="" name="testMethod"/>
+ <sequential>
+ <j2seproject3:test-debug-impl excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
+ <customize>
+ <classpath>
+ <path path="${run.test.classpath}"/>
+ </classpath>
+ <jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
+ <jvmarg line="${run.jvmargs}"/>
+ <jvmarg line="${run.jvmargs.ide}"/>
+ </customize>
+ </j2seproject3:test-debug-impl>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-init-macrodef-testng-debug-impl" if="${testng.available}" name="-init-macrodef-test-debug-testng">
+ <macrodef name="test-debug" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="**" name="testincludes"/>
+ <attribute default="" name="testmethods"/>
+ <attribute default="${main.class}" name="testClass"/>
+ <attribute default="" name="testMethod"/>
+ <sequential>
+ <j2seproject3:testng-debug-impl testClass="@{testClass}" testMethod="@{testMethod}">
+ <customize2>
+ <syspropertyset>
+ <propertyref prefix="test-sys-prop."/>
+ <mapper from="test-sys-prop.*" to="*" type="glob"/>
+ </syspropertyset>
+ </customize2>
+ </j2seproject3:testng-debug-impl>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-init-macrodef-test-debug-junit,-init-macrodef-test-debug-testng" name="-init-macrodef-test-debug"/>
+ <!--
+ pre NB7.2 profiling section; consider it deprecated
+ -->
+ <target depends="-profile-pre-init, init, -profile-post-init, -profile-init-macrodef-profile, -profile-init-check" if="profiler.info.jvmargs.agent" name="profile-init"/>
+ <target if="profiler.info.jvmargs.agent" name="-profile-pre-init">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target if="profiler.info.jvmargs.agent" name="-profile-post-init">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target if="profiler.info.jvmargs.agent" name="-profile-init-macrodef-profile">
+ <macrodef name="resolve">
+ <attribute name="name"/>
+ <attribute name="value"/>
+ <sequential>
+ <property name="@{name}" value="${env.@{value}}"/>
+ </sequential>
+ </macrodef>
+ <macrodef name="profile">
+ <attribute default="${main.class}" name="classname"/>
+ <element name="customize" optional="true"/>
+ <sequential>
+ <property environment="env"/>
+ <resolve name="profiler.current.path" value="${profiler.info.pathvar}"/>
+ <java classname="@{classname}" dir="${profiler.info.dir}" failonerror="${java.failonerror}" fork="true" jvm="${profiler.info.jvm}">
+ <jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
+ <jvmarg value="${profiler.info.jvmargs.agent}"/>
+ <jvmarg line="${profiler.info.jvmargs}"/>
+ <env key="${profiler.info.pathvar}" path="${profiler.info.agentpath}:${profiler.current.path}"/>
+ <arg line="${application.args}"/>
+ <classpath>
+ <path path="${run.classpath}"/>
+ </classpath>
+ <syspropertyset>
+ <propertyref prefix="run-sys-prop."/>
+ <mapper from="run-sys-prop.*" to="*" type="glob"/>
+ </syspropertyset>
+ <customize/>
+ </java>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-profile-pre-init, init, -profile-post-init, -profile-init-macrodef-profile" if="profiler.info.jvmargs.agent" name="-profile-init-check">
+ <fail unless="profiler.info.jvm">Must set JVM to use for profiling in profiler.info.jvm</fail>
+ <fail unless="profiler.info.jvmargs.agent">Must set profiler agent JVM arguments in profiler.info.jvmargs.agent</fail>
+ </target>
+ <!--
+ end of pre NB7.2 profiling section
+ -->
+ <target depends="-init-debug-args" name="-init-macrodef-nbjpda">
+ <macrodef name="nbjpdastart" uri="http://www.netbeans.org/ns/j2se-project/1">
+ <attribute default="${main.class}" name="name"/>
+ <attribute default="${debug.classpath}" name="classpath"/>
+ <attribute default="" name="stopclassname"/>
+ <sequential>
+ <nbjpdastart addressproperty="jpda.address" name="@{name}" stopclassname="@{stopclassname}" transport="${debug-transport}">
+ <classpath>
+ <path path="@{classpath}"/>
+ </classpath>
+ </nbjpdastart>
+ </sequential>
+ </macrodef>
+ <macrodef name="nbjpdareload" uri="http://www.netbeans.org/ns/j2se-project/1">
+ <attribute default="${build.classes.dir}" name="dir"/>
+ <sequential>
+ <nbjpdareload>
+ <fileset dir="@{dir}" includes="${fix.classes}">
+ <include name="${fix.includes}*.class"/>
+ </fileset>
+ </nbjpdareload>
+ </sequential>
+ </macrodef>
+ </target>
+ <target name="-init-debug-args">
+ <property name="version-output" value="java version "${ant.java.version}"/>
+ <condition property="have-jdk-older-than-1.4">
+ <or>
+ <contains string="${version-output}" substring="java version "1.0"/>
+ <contains string="${version-output}" substring="java version "1.1"/>
+ <contains string="${version-output}" substring="java version "1.2"/>
+ <contains string="${version-output}" substring="java version "1.3"/>
+ </or>
+ </condition>
+ <condition else="-Xdebug" property="debug-args-line" value="-Xdebug -Xnoagent -Djava.compiler=none">
+ <istrue value="${have-jdk-older-than-1.4}"/>
+ </condition>
+ <condition else="dt_socket" property="debug-transport-by-os" value="dt_shmem">
+ <os family="windows"/>
+ </condition>
+ <condition else="${debug-transport-by-os}" property="debug-transport" value="${debug.transport}">
+ <isset property="debug.transport"/>
+ </condition>
+ </target>
+ <target depends="-init-debug-args" name="-init-macrodef-debug">
+ <macrodef name="debug" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${main.class}" name="classname"/>
+ <attribute default="${debug.classpath}" name="classpath"/>
+ <element name="customize" optional="true"/>
+ <sequential>
+ <java classname="@{classname}" dir="${work.dir}" failonerror="${java.failonerror}" fork="true">
+ <jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
+ <jvmarg line="${debug-args-line}"/>
+ <jvmarg value="-Xrunjdwp:transport=${debug-transport},address=${jpda.address}"/>
+ <jvmarg value="-Dfile.encoding=${runtime.encoding}"/>
+ <redirector errorencoding="${runtime.encoding}" inputencoding="${runtime.encoding}" outputencoding="${runtime.encoding}"/>
+ <jvmarg line="${run.jvmargs}"/>
+ <jvmarg line="${run.jvmargs.ide}"/>
+ <classpath>
+ <path path="@{classpath}"/>
+ </classpath>
+ <syspropertyset>
+ <propertyref prefix="run-sys-prop."/>
+ <mapper from="run-sys-prop.*" to="*" type="glob"/>
+ </syspropertyset>
+ <customize/>
+ </java>
+ </sequential>
+ </macrodef>
+ </target>
+ <target name="-init-macrodef-java">
+ <macrodef name="java" uri="http://www.netbeans.org/ns/j2se-project/1">
+ <attribute default="${main.class}" name="classname"/>
+ <attribute default="${run.classpath}" name="classpath"/>
+ <attribute default="jvm" name="jvm"/>
+ <element name="customize" optional="true"/>
+ <sequential>
+ <java classname="@{classname}" dir="${work.dir}" failonerror="${java.failonerror}" fork="true">
+ <jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
+ <jvmarg value="-Dfile.encoding=${runtime.encoding}"/>
+ <redirector errorencoding="${runtime.encoding}" inputencoding="${runtime.encoding}" outputencoding="${runtime.encoding}"/>
+ <jvmarg line="${run.jvmargs}"/>
+ <jvmarg line="${run.jvmargs.ide}"/>
+ <classpath>
+ <path path="@{classpath}"/>
+ </classpath>
+ <syspropertyset>
+ <propertyref prefix="run-sys-prop."/>
+ <mapper from="run-sys-prop.*" to="*" type="glob"/>
+ </syspropertyset>
+ <customize/>
+ </java>
+ </sequential>
+ </macrodef>
+ </target>
+ <target name="-init-macrodef-copylibs">
+ <macrodef name="copylibs" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${manifest.file}" name="manifest"/>
+ <element name="customize" optional="true"/>
+ <sequential>
+ <property location="${build.classes.dir}" name="build.classes.dir.resolved"/>
+ <pathconvert property="run.classpath.without.build.classes.dir">
+ <path path="${run.classpath}"/>
+ <map from="${build.classes.dir.resolved}" to=""/>
+ </pathconvert>
+ <pathconvert pathsep=" " property="jar.classpath">
+ <path path="${run.classpath.without.build.classes.dir}"/>
+ <chainedmapper>
+ <flattenmapper/>
+ <filtermapper>
+ <replacestring from=" " to="%20"/>
+ </filtermapper>
+ <globmapper from="*" to="lib/*"/>
+ </chainedmapper>
+ </pathconvert>
+ <taskdef classname="org.netbeans.modules.java.j2seproject.copylibstask.CopyLibs" classpath="${libs.CopyLibs.classpath}" name="copylibs"/>
+ <copylibs compress="${jar.compress}" excludeFromCopy="${copylibs.excludes}" index="${jar.index}" indexMetaInf="${jar.index.metainf}" jarfile="${dist.jar}" manifest="@{manifest}" manifestencoding="UTF-8" rebase="${copylibs.rebase}" runtimeclasspath="${run.classpath.without.build.classes.dir}">
+ <fileset dir="${build.classes.dir}" excludes="${dist.archive.excludes}"/>
+ <manifest>
+ <attribute name="Class-Path" value="${jar.classpath}"/>
+ <customize/>
+ </manifest>
+ </copylibs>
+ </sequential>
+ </macrodef>
+ </target>
+ <target name="-init-presetdef-jar">
+ <presetdef name="jar" uri="http://www.netbeans.org/ns/j2se-project/1">
+ <jar compress="${jar.compress}" index="${jar.index}" jarfile="${dist.jar}" manifestencoding="UTF-8">
+ <j2seproject1:fileset dir="${build.classes.dir}" excludes="${dist.archive.excludes}"/>
+ </jar>
+ </presetdef>
+ </target>
+ <target name="-init-ap-cmdline-properties">
+ <property name="annotation.processing.enabled" value="true"/>
+ <property name="annotation.processing.processors.list" value=""/>
+ <property name="annotation.processing.processor.options" value=""/>
+ <property name="annotation.processing.run.all.processors" value="true"/>
+ <property name="javac.processorpath" value="${javac.classpath}"/>
+ <property name="javac.test.processorpath" value="${javac.test.classpath}"/>
+ <condition property="ap.supported.internal" value="true">
+ <not>
+ <matches pattern="1\.[0-5](\..*)?" string="${javac.source}"/>
+ </not>
+ </condition>
+ </target>
+ <target depends="-init-ap-cmdline-properties" if="ap.supported.internal" name="-init-ap-cmdline-supported">
+ <condition else="" property="ap.processors.internal" value="-processor ${annotation.processing.processors.list}">
+ <isfalse value="${annotation.processing.run.all.processors}"/>
+ </condition>
+ <condition else="" property="ap.proc.none.internal" value="-proc:none">
+ <isfalse value="${annotation.processing.enabled}"/>
+ </condition>
+ </target>
+ <target depends="-init-ap-cmdline-properties,-init-ap-cmdline-supported" name="-init-ap-cmdline">
+ <property name="ap.cmd.line.internal" value=""/>
+ </target>
+ <target depends="-pre-init,-init-private,-init-user,-init-project,-do-init,-post-init,-init-check,-init-macrodef-property,-init-macrodef-javac,-init-macrodef-test,-init-macrodef-test-debug,-init-macrodef-nbjpda,-init-macrodef-debug,-init-macrodef-java,-init-presetdef-jar,-init-ap-cmdline" name="init"/>
+ <!--
+ ===================
+ COMPILATION SECTION
+ ===================
+ -->
+ <target name="-deps-jar-init" unless="built-jar.properties">
+ <property location="${build.dir}/built-jar.properties" name="built-jar.properties"/>
+ <delete file="${built-jar.properties}" quiet="true"/>
+ </target>
+ <target if="already.built.jar.${basedir}" name="-warn-already-built-jar">
+ <echo level="warn" message="Cycle detected: JNLPConverter was already built"/>
+ </target>
+ <target depends="init,-deps-jar-init" name="deps-jar" unless="no.deps">
+ <mkdir dir="${build.dir}"/>
+ <touch file="${built-jar.properties}" verbose="false"/>
+ <property file="${built-jar.properties}" prefix="already.built.jar."/>
+ <antcall target="-warn-already-built-jar"/>
+ <propertyfile file="${built-jar.properties}">
+ <entry key="${basedir}" value=""/>
+ </propertyfile>
+ </target>
+ <target depends="init,-check-automatic-build,-clean-after-automatic-build" name="-verify-automatic-build"/>
+ <target depends="init" name="-check-automatic-build">
+ <available file="${build.classes.dir}/.netbeans_automatic_build" property="netbeans.automatic.build"/>
+ </target>
+ <target depends="init" if="netbeans.automatic.build" name="-clean-after-automatic-build">
+ <antcall target="clean"/>
+ </target>
+ <target depends="init,deps-jar" name="-pre-pre-compile">
+ <mkdir dir="${build.classes.dir}"/>
+ </target>
+ <target name="-pre-compile">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target if="do.depend.true" name="-compile-depend">
+ <pathconvert property="build.generated.subdirs">
+ <dirset dir="${build.generated.sources.dir}" erroronmissingdir="false">
+ <include name="*"/>
+ </dirset>
+ </pathconvert>
+ <j2seproject3:depend srcdir="${src.src.dir}:${build.generated.subdirs}"/>
+ </target>
+ <target depends="init,deps-jar,-pre-pre-compile,-pre-compile, -copy-persistence-xml,-compile-depend" if="have.sources" name="-do-compile">
+ <j2seproject3:javac gensrcdir="${build.generated.sources.dir}"/>
+ <copy todir="${build.classes.dir}">
+ <fileset dir="${src.src.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
+ </copy>
+ </target>
+ <target if="has.persistence.xml" name="-copy-persistence-xml">
+ <mkdir dir="${build.classes.dir}/META-INF"/>
+ <copy todir="${build.classes.dir}/META-INF">
+ <fileset dir="${meta.inf.dir}" includes="persistence.xml orm.xml"/>
+ </copy>
+ </target>
+ <target name="-post-compile">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="init,deps-jar,-verify-automatic-build,-pre-pre-compile,-pre-compile,-do-compile,-post-compile" description="Compile project." name="compile"/>
+ <target name="-pre-compile-single">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="init,deps-jar,-pre-pre-compile" name="-do-compile-single">
+ <fail unless="javac.includes">Must select some files in the IDE or set javac.includes</fail>
+ <j2seproject3:force-recompile/>
+ <j2seproject3:javac excludes="" gensrcdir="${build.generated.sources.dir}" includes="${javac.includes}" sourcepath="${src.src.dir}"/>
+ </target>
+ <target name="-post-compile-single">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="init,deps-jar,-verify-automatic-build,-pre-pre-compile,-pre-compile-single,-do-compile-single,-post-compile-single" name="compile-single"/>
+ <!--
+ ====================
+ JAR BUILDING SECTION
+ ====================
+ -->
+ <target depends="init" name="-pre-pre-jar">
+ <dirname file="${dist.jar}" property="dist.jar.dir"/>
+ <mkdir dir="${dist.jar.dir}"/>
+ </target>
+ <target name="-pre-jar">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="init" if="do.archive" name="-do-jar-create-manifest" unless="manifest.available">
+ <tempfile deleteonexit="true" destdir="${build.dir}" property="tmp.manifest.file"/>
+ <touch file="${tmp.manifest.file}" verbose="false"/>
+ </target>
+ <target depends="init" if="do.archive+manifest.available" name="-do-jar-copy-manifest">
+ <tempfile deleteonexit="true" destdir="${build.dir}" property="tmp.manifest.file"/>
+ <copy encoding="${manifest.encoding}" file="${manifest.file}" outputencoding="UTF-8" tofile="${tmp.manifest.file}"/>
+ </target>
+ <target depends="init,-do-jar-create-manifest,-do-jar-copy-manifest" if="do.archive+main.class.available" name="-do-jar-set-mainclass">
+ <manifest encoding="UTF-8" file="${tmp.manifest.file}" mode="update">
+ <attribute name="Main-Class" value="${main.class}"/>
+ </manifest>
+ </target>
+ <target depends="init,-do-jar-create-manifest,-do-jar-copy-manifest" if="do.archive+profile.available" name="-do-jar-set-profile">
+ <manifest encoding="UTF-8" file="${tmp.manifest.file}" mode="update">
+ <attribute name="Profile" value="${javac.profile}"/>
+ </manifest>
+ </target>
+ <target depends="init,-do-jar-create-manifest,-do-jar-copy-manifest" if="do.archive+splashscreen.available" name="-do-jar-set-splashscreen">
+ <basename file="${application.splash}" property="splashscreen.basename"/>
+ <mkdir dir="${build.classes.dir}/META-INF"/>
+ <copy failonerror="false" file="${application.splash}" todir="${build.classes.dir}/META-INF"/>
+ <manifest encoding="UTF-8" file="${tmp.manifest.file}" mode="update">
+ <attribute name="SplashScreen-Image" value="META-INF/${splashscreen.basename}"/>
+ </manifest>
+ </target>
+ <target depends="init,-init-macrodef-copylibs,compile,-pre-pre-jar,-pre-jar,-do-jar-create-manifest,-do-jar-copy-manifest,-do-jar-set-mainclass,-do-jar-set-profile,-do-jar-set-splashscreen" if="do.mkdist" name="-do-jar-copylibs">
+ <j2seproject3:copylibs manifest="${tmp.manifest.file}"/>
+ <echo level="info">To run this application from the command line without Ant, try:</echo>
+ <property location="${dist.jar}" name="dist.jar.resolved"/>
+ <echo level="info">java -jar "${dist.jar.resolved}"</echo>
+ </target>
+ <target depends="init,compile,-pre-pre-jar,-pre-jar,-do-jar-create-manifest,-do-jar-copy-manifest,-do-jar-set-mainclass,-do-jar-set-profile,-do-jar-set-splashscreen" if="do.archive" name="-do-jar-jar" unless="do.mkdist">
+ <j2seproject1:jar manifest="${tmp.manifest.file}"/>
+ <property location="${build.classes.dir}" name="build.classes.dir.resolved"/>
+ <property location="${dist.jar}" name="dist.jar.resolved"/>
+ <pathconvert property="run.classpath.with.dist.jar">
+ <path path="${run.classpath}"/>
+ <map from="${build.classes.dir.resolved}" to="${dist.jar.resolved}"/>
+ </pathconvert>
+ <condition else="" property="jar.usage.message" value="To run this application from the command line without Ant, try:${line.separator}${platform.java} -cp ${run.classpath.with.dist.jar} ${main.class}">
+ <isset property="main.class.available"/>
+ </condition>
+ <condition else="debug" property="jar.usage.level" value="info">
+ <isset property="main.class.available"/>
+ </condition>
+ <echo level="${jar.usage.level}" message="${jar.usage.message}"/>
+ </target>
+ <target depends="-do-jar-copylibs" if="do.archive" name="-do-jar-delete-manifest">
+ <delete>
+ <fileset file="${tmp.manifest.file}"/>
+ </delete>
+ </target>
+ <target depends="init,compile,-pre-pre-jar,-pre-jar,-do-jar-create-manifest,-do-jar-copy-manifest,-do-jar-set-mainclass,-do-jar-set-profile,-do-jar-set-splashscreen,-do-jar-jar,-do-jar-delete-manifest" name="-do-jar-without-libraries"/>
+ <target depends="init,compile,-pre-pre-jar,-pre-jar,-do-jar-create-manifest,-do-jar-copy-manifest,-do-jar-set-mainclass,-do-jar-set-profile,-do-jar-set-splashscreen,-do-jar-copylibs,-do-jar-delete-manifest" name="-do-jar-with-libraries"/>
+ <target name="-post-jar">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="init,compile,-pre-jar,-do-jar-without-libraries,-do-jar-with-libraries,-post-jar" name="-do-jar"/>
+ <target depends="init,compile,-pre-jar,-do-jar,-post-jar" description="Build JAR." name="jar"/>
+ <!--
+ =================
+ EXECUTION SECTION
+ =================
+ -->
+ <target depends="init,compile" description="Run a main class." name="run">
+ <j2seproject1:java>
+ <customize>
+ <arg line="${application.args}"/>
+ </customize>
+ </j2seproject1:java>
+ </target>
+ <target name="-do-not-recompile">
+ <property name="javac.includes.binary" value=""/>
+ </target>
+ <target depends="init,compile-single" name="run-single">
+ <fail unless="run.class">Must select one file in the IDE or set run.class</fail>
+ <j2seproject1:java classname="${run.class}"/>
+ </target>
+ <target depends="init,compile-test-single" name="run-test-with-main">
+ <fail unless="run.class">Must select one file in the IDE or set run.class</fail>
+ <j2seproject1:java classname="${run.class}" classpath="${run.test.classpath}"/>
+ </target>
+ <!--
+ =================
+ DEBUGGING SECTION
+ =================
+ -->
+ <target depends="init" if="netbeans.home" name="-debug-start-debugger">
+ <j2seproject1:nbjpdastart name="${debug.class}"/>
+ </target>
+ <target depends="init" if="netbeans.home" name="-debug-start-debugger-main-test">
+ <j2seproject1:nbjpdastart classpath="${debug.test.classpath}" name="${debug.class}"/>
+ </target>
+ <target depends="init,compile" name="-debug-start-debuggee">
+ <j2seproject3:debug>
+ <customize>
+ <arg line="${application.args}"/>
+ </customize>
+ </j2seproject3:debug>
+ </target>
+ <target depends="init,compile,-debug-start-debugger,-debug-start-debuggee" description="Debug project in IDE." if="netbeans.home" name="debug"/>
+ <target depends="init" if="netbeans.home" name="-debug-start-debugger-stepinto">
+ <j2seproject1:nbjpdastart stopclassname="${main.class}"/>
+ </target>
+ <target depends="init,compile,-debug-start-debugger-stepinto,-debug-start-debuggee" if="netbeans.home" name="debug-stepinto"/>
+ <target depends="init,compile-single" if="netbeans.home" name="-debug-start-debuggee-single">
+ <fail unless="debug.class">Must select one file in the IDE or set debug.class</fail>
+ <j2seproject3:debug classname="${debug.class}"/>
+ </target>
+ <target depends="init,compile-single,-debug-start-debugger,-debug-start-debuggee-single" if="netbeans.home" name="debug-single"/>
+ <target depends="init,compile-test-single" if="netbeans.home" name="-debug-start-debuggee-main-test">
+ <fail unless="debug.class">Must select one file in the IDE or set debug.class</fail>
+ <j2seproject3:debug classname="${debug.class}" classpath="${debug.test.classpath}"/>
+ </target>
+ <target depends="init,compile-test-single,-debug-start-debugger-main-test,-debug-start-debuggee-main-test" if="netbeans.home" name="debug-test-with-main"/>
+ <target depends="init" name="-pre-debug-fix">
+ <fail unless="fix.includes">Must set fix.includes</fail>
+ <property name="javac.includes" value="${fix.includes}.java"/>
+ </target>
+ <target depends="init,-pre-debug-fix,compile-single" if="netbeans.home" name="-do-debug-fix">
+ <j2seproject1:nbjpdareload/>
+ </target>
+ <target depends="init,-pre-debug-fix,-do-debug-fix" if="netbeans.home" name="debug-fix"/>
+ <!--
+ =================
+ PROFILING SECTION
+ =================
+ -->
+ <!--
+ pre NB7.2 profiler integration
+ -->
+ <target depends="profile-init,compile" description="Profile a project in the IDE." if="profiler.info.jvmargs.agent" name="-profile-pre72">
+ <fail unless="netbeans.home">This target only works when run from inside the NetBeans IDE.</fail>
+ <nbprofiledirect>
+ <classpath>
+ <path path="${run.classpath}"/>
+ </classpath>
+ </nbprofiledirect>
+ <profile/>
+ </target>
+ <target depends="profile-init,compile-single" description="Profile a selected class in the IDE." if="profiler.info.jvmargs.agent" name="-profile-single-pre72">
+ <fail unless="profile.class">Must select one file in the IDE or set profile.class</fail>
+ <fail unless="netbeans.home">This target only works when run from inside the NetBeans IDE.</fail>
+ <nbprofiledirect>
+ <classpath>
+ <path path="${run.classpath}"/>
+ </classpath>
+ </nbprofiledirect>
+ <profile classname="${profile.class}"/>
+ </target>
+ <target depends="profile-init,compile-single" if="profiler.info.jvmargs.agent" name="-profile-applet-pre72">
+ <fail unless="netbeans.home">This target only works when run from inside the NetBeans IDE.</fail>
+ <nbprofiledirect>
+ <classpath>
+ <path path="${run.classpath}"/>
+ </classpath>
+ </nbprofiledirect>
+ <profile classname="sun.applet.AppletViewer">
+ <customize>
+ <arg value="${applet.url}"/>
+ </customize>
+ </profile>
+ </target>
+ <target depends="profile-init,compile-test-single" if="profiler.info.jvmargs.agent" name="-profile-test-single-pre72">
+ <fail unless="netbeans.home">This target only works when run from inside the NetBeans IDE.</fail>
+ <nbprofiledirect>
+ <classpath>
+ <path path="${run.test.classpath}"/>
+ </classpath>
+ </nbprofiledirect>
+ <junit dir="${profiler.info.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" jvm="${profiler.info.jvm}" showoutput="true">
+ <env key="${profiler.info.pathvar}" path="${profiler.info.agentpath}:${profiler.current.path}"/>
+ <jvmarg value="${profiler.info.jvmargs.agent}"/>
+ <jvmarg line="${profiler.info.jvmargs}"/>
+ <test name="${profile.class}"/>
+ <classpath>
+ <path path="${run.test.classpath}"/>
+ </classpath>
+ <syspropertyset>
+ <propertyref prefix="test-sys-prop."/>
+ <mapper from="test-sys-prop.*" to="*" type="glob"/>
+ </syspropertyset>
+ <formatter type="brief" usefile="false"/>
+ <formatter type="xml"/>
+ </junit>
+ </target>
+ <!--
+ end of pre NB72 profiling section
+ -->
+ <target if="netbeans.home" name="-profile-check">
+ <condition property="profiler.configured">
+ <or>
+ <contains casesensitive="true" string="${run.jvmargs.ide}" substring="-agentpath:"/>
+ <contains casesensitive="true" string="${run.jvmargs.ide}" substring="-javaagent:"/>
+ </or>
+ </condition>
+ </target>
+ <target depends="-profile-check,-profile-pre72" description="Profile a project in the IDE." if="profiler.configured" name="profile" unless="profiler.info.jvmargs.agent">
+ <startprofiler/>
+ <antcall target="run"/>
+ </target>
+ <target depends="-profile-check,-profile-single-pre72" description="Profile a selected class in the IDE." if="profiler.configured" name="profile-single" unless="profiler.info.jvmargs.agent">
+ <fail unless="run.class">Must select one file in the IDE or set run.class</fail>
+ <startprofiler/>
+ <antcall target="run-single"/>
+ </target>
+ <target depends="-profile-test-single-pre72" description="Profile a selected test in the IDE." name="profile-test-single"/>
+ <target depends="-profile-check" description="Profile a selected test in the IDE." if="profiler.configured" name="profile-test" unless="profiler.info.jvmargs">
+ <fail unless="test.includes">Must select some files in the IDE or set test.includes</fail>
+ <startprofiler/>
+ <antcall target="test-single"/>
+ </target>
+ <target depends="-profile-check" description="Profile a selected class in the IDE." if="profiler.configured" name="profile-test-with-main">
+ <fail unless="run.class">Must select one file in the IDE or set run.class</fail>
+ <startprofiler/>
+ <antcall target="run-test-with-main"/>
+ </target>
+ <target depends="-profile-check,-profile-applet-pre72" if="profiler.configured" name="profile-applet" unless="profiler.info.jvmargs.agent">
+ <fail unless="applet.url">Must select one file in the IDE or set applet.url</fail>
+ <startprofiler/>
+ <antcall target="run-applet"/>
+ </target>
+ <!--
+ ===============
+ JAVADOC SECTION
+ ===============
+ -->
+ <target depends="init" if="have.sources" name="-javadoc-build">
+ <mkdir dir="${dist.javadoc.dir}"/>
+ <condition else="" property="javadoc.endorsed.classpath.cmd.line.arg" value="-J${endorsed.classpath.cmd.line.arg}">
+ <and>
+ <isset property="endorsed.classpath.cmd.line.arg"/>
+ <not>
+ <equals arg1="${endorsed.classpath.cmd.line.arg}" arg2=""/>
+ </not>
+ </and>
+ </condition>
+ <condition else="" property="bug5101868workaround" value="*.java">
+ <matches pattern="1\.[56](\..*)?" string="${java.version}"/>
+ </condition>
+ <javadoc additionalparam="-J-Dfile.encoding=${file.encoding} ${javadoc.additionalparam}" author="${javadoc.author}" charset="UTF-8" destdir="${dist.javadoc.dir}" docencoding="UTF-8" encoding="${javadoc.encoding.used}" failonerror="true" noindex="${javadoc.noindex}" nonavbar="${javadoc.nonavbar}" notree="${javadoc.notree}" private="${javadoc.private}" source="${javac.source}" splitindex="${javadoc.splitindex}" use="${javadoc.use}" useexternalfile="true" version="${javadoc.version}" windowtitle="${javadoc.windowtitle}">
+ <classpath>
+ <path path="${javac.classpath}"/>
+ </classpath>
+ <fileset dir="${src.src.dir}" excludes="${bug5101868workaround},${excludes}" includes="${includes}">
+ <filename name="**/*.java"/>
+ </fileset>
+ <fileset dir="${build.generated.sources.dir}" erroronmissingdir="false">
+ <include name="**/*.java"/>
+ <exclude name="*.java"/>
+ </fileset>
+ <arg line="${javadoc.endorsed.classpath.cmd.line.arg}"/>
+ </javadoc>
+ <copy todir="${dist.javadoc.dir}">
+ <fileset dir="${src.src.dir}" excludes="${excludes}" includes="${includes}">
+ <filename name="**/doc-files/**"/>
+ </fileset>
+ <fileset dir="${build.generated.sources.dir}" erroronmissingdir="false">
+ <include name="**/doc-files/**"/>
+ </fileset>
+ </copy>
+ </target>
+ <target depends="init,-javadoc-build" if="netbeans.home" name="-javadoc-browse" unless="no.javadoc.preview">
+ <nbbrowse file="${dist.javadoc.dir}/index.html"/>
+ </target>
+ <target depends="init,-javadoc-build,-javadoc-browse" description="Build Javadoc." name="javadoc"/>
+ <!--
+ =========================
+ TEST COMPILATION SECTION
+ =========================
+ -->
+ <target depends="init,compile" if="have.tests" name="-pre-pre-compile-test">
+ <mkdir dir="${build.test.classes.dir}"/>
+ </target>
+ <target name="-pre-compile-test">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target if="do.depend.true" name="-compile-test-depend">
+ <j2seproject3:depend classpath="${javac.test.classpath}" destdir="${build.test.classes.dir}" srcdir=""/>
+ </target>
+ <target depends="init,deps-jar,compile,-pre-pre-compile-test,-pre-compile-test,-compile-test-depend" if="have.tests" name="-do-compile-test">
+ <j2seproject3:javac apgeneratedsrcdir="${build.test.classes.dir}" classpath="${javac.test.classpath}" debug="true" destdir="${build.test.classes.dir}" processorpath="${javac.test.processorpath}" srcdir=""/>
+ <copy todir="${build.test.classes.dir}"/>
+ </target>
+ <target name="-post-compile-test">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="init,compile,-pre-pre-compile-test,-pre-compile-test,-do-compile-test,-post-compile-test" name="compile-test"/>
+ <target name="-pre-compile-test-single">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="init,deps-jar,compile,-pre-pre-compile-test,-pre-compile-test-single" if="have.tests" name="-do-compile-test-single">
+ <fail unless="javac.includes">Must select some files in the IDE or set javac.includes</fail>
+ <j2seproject3:force-recompile destdir="${build.test.classes.dir}"/>
+ <j2seproject3:javac apgeneratedsrcdir="${build.test.classes.dir}" classpath="${javac.test.classpath}" debug="true" destdir="${build.test.classes.dir}" excludes="" includes="${javac.includes}" processorpath="${javac.test.processorpath}" sourcepath="" srcdir=""/>
+ <copy todir="${build.test.classes.dir}"/>
+ </target>
+ <target name="-post-compile-test-single">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="init,compile,-pre-pre-compile-test,-pre-compile-test-single,-do-compile-test-single,-post-compile-test-single" name="compile-test-single"/>
+ <!--
+ =======================
+ TEST EXECUTION SECTION
+ =======================
+ -->
+ <target depends="init" if="have.tests" name="-pre-test-run">
+ <mkdir dir="${build.test.results.dir}"/>
+ </target>
+ <target depends="init,compile-test,-pre-test-run" if="have.tests" name="-do-test-run">
+ <j2seproject3:test includes="${includes}" testincludes="**/*Test.java"/>
+ </target>
+ <target depends="init,compile-test,-pre-test-run,-do-test-run" if="have.tests" name="-post-test-run">
+ <fail if="tests.failed" unless="ignore.failing.tests">Some tests failed; see details above.</fail>
+ </target>
+ <target depends="init" if="have.tests" name="test-report"/>
+ <target depends="init" if="netbeans.home+have.tests" name="-test-browse"/>
+ <target depends="init,compile-test,-pre-test-run,-do-test-run,test-report,-post-test-run,-test-browse" description="Run unit tests." name="test"/>
+ <target depends="init" if="have.tests" name="-pre-test-run-single">
+ <mkdir dir="${build.test.results.dir}"/>
+ </target>
+ <target depends="init,compile-test-single,-pre-test-run-single" if="have.tests" name="-do-test-run-single">
+ <fail unless="test.includes">Must select some files in the IDE or set test.includes</fail>
+ <j2seproject3:test excludes="" includes="${test.includes}" testincludes="${test.includes}"/>
+ </target>
+ <target depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single" if="have.tests" name="-post-test-run-single">
+ <fail if="tests.failed" unless="ignore.failing.tests">Some tests failed; see details above.</fail>
+ </target>
+ <target depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single,-post-test-run-single" description="Run single unit test." name="test-single"/>
+ <target depends="init,compile-test-single,-pre-test-run-single" if="have.tests" name="-do-test-run-single-method">
+ <fail unless="test.class">Must select some files in the IDE or set test.class</fail>
+ <fail unless="test.method">Must select some method in the IDE or set test.method</fail>
+ <j2seproject3:test excludes="" includes="${javac.includes}" testincludes="${test.class}" testmethods="${test.method}"/>
+ </target>
+ <target depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single-method" if="have.tests" name="-post-test-run-single-method">
+ <fail if="tests.failed" unless="ignore.failing.tests">Some tests failed; see details above.</fail>
+ </target>
+ <target depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single-method,-post-test-run-single-method" description="Run single unit test." name="test-single-method"/>
+ <!--
+ =======================
+ TEST DEBUGGING SECTION
+ =======================
+ -->
+ <target depends="init,compile-test-single,-pre-test-run-single" if="have.tests" name="-debug-start-debuggee-test">
+ <fail unless="test.class">Must select one file in the IDE or set test.class</fail>
+ <j2seproject3:test-debug excludes="" includes="${javac.includes}" testClass="${test.class}" testincludes="${javac.includes}"/>
+ </target>
+ <target depends="init,compile-test-single,-pre-test-run-single" if="have.tests" name="-debug-start-debuggee-test-method">
+ <fail unless="test.class">Must select one file in the IDE or set test.class</fail>
+ <fail unless="test.method">Must select some method in the IDE or set test.method</fail>
+ <j2seproject3:test-debug excludes="" includes="${javac.includes}" testClass="${test.class}" testMethod="${test.method}" testincludes="${test.class}" testmethods="${test.method}"/>
+ </target>
+ <target depends="init,compile-test" if="netbeans.home+have.tests" name="-debug-start-debugger-test">
+ <j2seproject1:nbjpdastart classpath="${debug.test.classpath}" name="${test.class}"/>
+ </target>
+ <target depends="init,compile-test-single,-debug-start-debugger-test,-debug-start-debuggee-test" name="debug-test"/>
+ <target depends="init,compile-test-single,-debug-start-debugger-test,-debug-start-debuggee-test-method" name="debug-test-method"/>
+ <target depends="init,-pre-debug-fix,compile-test-single" if="netbeans.home" name="-do-debug-fix-test">
+ <j2seproject1:nbjpdareload dir="${build.test.classes.dir}"/>
+ </target>
+ <target depends="init,-pre-debug-fix,-do-debug-fix-test" if="netbeans.home" name="debug-fix-test"/>
+ <!--
+ =========================
+ APPLET EXECUTION SECTION
+ =========================
+ -->
+ <target depends="init,compile-single" name="run-applet">
+ <fail unless="applet.url">Must select one file in the IDE or set applet.url</fail>
+ <j2seproject1:java classname="sun.applet.AppletViewer">
+ <customize>
+ <arg value="${applet.url}"/>
+ </customize>
+ </j2seproject1:java>
+ </target>
+ <!--
+ =========================
+ APPLET DEBUGGING SECTION
+ =========================
+ -->
+ <target depends="init,compile-single" if="netbeans.home" name="-debug-start-debuggee-applet">
+ <fail unless="applet.url">Must select one file in the IDE or set applet.url</fail>
+ <j2seproject3:debug classname="sun.applet.AppletViewer">
+ <customize>
+ <arg value="${applet.url}"/>
+ </customize>
+ </j2seproject3:debug>
+ </target>
+ <target depends="init,compile-single,-debug-start-debugger,-debug-start-debuggee-applet" if="netbeans.home" name="debug-applet"/>
+ <!--
+ ===============
+ CLEANUP SECTION
+ ===============
+ -->
+ <target name="-deps-clean-init" unless="built-clean.properties">
+ <property location="${build.dir}/built-clean.properties" name="built-clean.properties"/>
+ <delete file="${built-clean.properties}" quiet="true"/>
+ </target>
+ <target if="already.built.clean.${basedir}" name="-warn-already-built-clean">
+ <echo level="warn" message="Cycle detected: JNLPConverter was already built"/>
+ </target>
+ <target depends="init,-deps-clean-init" name="deps-clean" unless="no.deps">
+ <mkdir dir="${build.dir}"/>
+ <touch file="${built-clean.properties}" verbose="false"/>
+ <property file="${built-clean.properties}" prefix="already.built.clean."/>
+ <antcall target="-warn-already-built-clean"/>
+ <propertyfile file="${built-clean.properties}">
+ <entry key="${basedir}" value=""/>
+ </propertyfile>
+ </target>
+ <target depends="init" name="-do-clean">
+ <delete dir="${build.dir}"/>
+ <delete dir="${dist.dir}" followsymlinks="false" includeemptydirs="true"/>
+ </target>
+ <target name="-post-clean">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="init,deps-clean,-do-clean,-post-clean" description="Clean build products." name="clean"/>
+ <target name="-check-call-dep">
+ <property file="${call.built.properties}" prefix="already.built."/>
+ <condition property="should.call.dep">
+ <and>
+ <not>
+ <isset property="already.built.${call.subproject}"/>
+ </not>
+ <available file="${call.script}"/>
+ </and>
+ </condition>
+ </target>
+ <target depends="-check-call-dep" if="should.call.dep" name="-maybe-call-dep">
+ <ant antfile="${call.script}" inheritall="false" target="${call.target}">
+ <propertyset>
+ <propertyref prefix="transfer."/>
+ <mapper from="transfer.*" to="*" type="glob"/>
+ </propertyset>
+ </ant>
+ </target>
+</project>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/nbproject/jpackage/JNLPConverter/nbproject/genfiles.properties Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,8 @@
+build.xml.data.CRC32=9017e41e
+build.xml.script.CRC32=5cf818d6
+build.xml.stylesheet.CRC32=8064a381@1.80.1.48
+# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
+# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
+nbproject/build-impl.xml.data.CRC32=9017e41e
+nbproject/build-impl.xml.script.CRC32=d0290d6d
+nbproject/build-impl.xml.stylesheet.CRC32=830a3534@1.80.1.48
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/nbproject/jpackage/JNLPConverter/nbproject/project.properties Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,96 @@
+annotation.processing.enabled=true
+annotation.processing.enabled.in.editor=false
+annotation.processing.processors.list=
+annotation.processing.run.all.processors=true
+annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output
+application.title=JNLPConverter
+application.vendor=
+auxiliary.org-netbeans-spi-editor-hints-projects.perProjectHintSettingsFile=nbproject/cfg_hints.xml
+build.classes.dir=${build.dir}/classes
+build.classes.excludes=**/*.java,**/*.form
+# This directory is removed when the project is cleaned:
+build.dir=build
+build.generated.dir=${build.dir}/generated
+build.generated.sources.dir=${build.dir}/generated-sources
+# Only compile against the classpath explicitly listed here:
+build.sysclasspath=ignore
+build.test.classes.dir=${build.dir}/test/classes
+build.test.results.dir=${build.dir}/test/results
+# Uncomment to specify the preferred debugger connection transport:
+#debug.transport=dt_socket
+debug.classpath=\
+ ${run.classpath}
+debug.test.classpath=\
+ ${run.test.classpath}
+# Files in build.classes.dir which should be excluded from distribution jar
+dist.archive.excludes=
+# This directory is removed when the project is cleaned:
+dist.dir=dist
+dist.jar=${dist.dir}/JNLPConverter.jar
+dist.javadoc.dir=${dist.dir}/javadoc
+endorsed.classpath=
+excludes=
+file.reference.jnlpconverter-src=../../../jpackage/jnlpconverter/src
+includes=**
+jar.archive.disabled=${jnlp.enabled}
+jar.compress=false
+jar.index=${jnlp.enabled}
+javac.classpath=
+# Space-separated list of extra javac options
+javac.compilerargs=
+javac.deprecation=false
+javac.external.vm=true
+javac.processorpath=\
+ ${javac.classpath}
+javac.source=1.8
+javac.target=1.8
+javac.test.classpath=\
+ ${javac.classpath}:\
+ ${build.classes.dir}
+javac.test.processorpath=\
+ ${javac.test.classpath}
+javadoc.additionalparam=
+javadoc.author=false
+javadoc.encoding=${source.encoding}
+javadoc.noindex=false
+javadoc.nonavbar=false
+javadoc.notree=false
+javadoc.private=false
+javadoc.splitindex=true
+javadoc.use=true
+javadoc.version=false
+javadoc.windowtitle=
+jnlp.codebase.type=no.codebase
+jnlp.descriptor=application
+jnlp.enabled=false
+jnlp.mixed.code=default
+jnlp.offline-allowed=false
+jnlp.signed=false
+jnlp.signing=
+jnlp.signing.alias=
+jnlp.signing.keystore=
+main.class=jnlp.converter.Main
+# Optional override of default Application-Library-Allowable-Codebase attribute identifying the locations where your signed RIA is expected to be found.
+manifest.custom.application.library.allowable.codebase=
+# Optional override of default Caller-Allowable-Codebase attribute identifying the domains from which JavaScript code can make calls to your RIA without security prompts.
+manifest.custom.caller.allowable.codebase=
+# Optional override of default Codebase manifest attribute, use to prevent RIAs from being repurposed
+manifest.custom.codebase=
+# Optional override of default Permissions manifest attribute (supported values: sandbox, all-permissions)
+manifest.custom.permissions=
+manifest.file=manifest.mf
+meta.inf.dir=${src.dir}/META-INF
+mkdist.disabled=false
+platform.active=default_platform
+run.classpath=\
+ ${javac.classpath}:\
+ ${build.classes.dir}
+# Space-separated list of JVM arguments used when running the project.
+# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value.
+# To set system properties for unit tests define test-sys-prop.name=value:
+run.jvmargs=
+run.test.classpath=\
+ ${javac.test.classpath}:\
+ ${build.test.classes.dir}
+source.encoding=UTF-8
+src.src.dir=${file.reference.jnlpconverter-src}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/nbproject/jpackage/JNLPConverter/nbproject/project.xml Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://www.netbeans.org/ns/project/1">
+ <type>org.netbeans.modules.java.j2seproject</type>
+ <configuration>
+ <data xmlns="http://www.netbeans.org/ns/j2se-project/3">
+ <name>JNLPConverter</name>
+ <source-roots>
+ <root id="src.src.dir" name="Source Packages"/>
+ </source-roots>
+ <test-roots/>
+ </data>
+ </configuration>
+</project>
--- a/src/java.base/share/classes/module-info.java Tue Nov 12 12:19:24 2019 +0100
+++ b/src/java.base/share/classes/module-info.java Tue Nov 12 10:09:40 2019 -0500
@@ -203,7 +203,8 @@
java.management.rmi,
jdk.jartool,
jdk.jfr,
- jdk.jlink;
+ jdk.jlink,
+ jdk.incubator.jpackage;
exports jdk.internal.perf to
java.management,
jdk.management.agent,
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/DesktopIntegration.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,503 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.incubator.jpackage.internal;
+
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.imageio.ImageIO;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import static jdk.incubator.jpackage.internal.LinuxAppBundler.ICON_PNG;
+import static jdk.incubator.jpackage.internal.LinuxAppImageBuilder.DEFAULT_ICON;
+import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+
+/**
+ * Helper to create files for desktop integration.
+ */
+final class DesktopIntegration {
+
+ static final String DESKTOP_COMMANDS_INSTALL = "DESKTOP_COMMANDS_INSTALL";
+ static final String DESKTOP_COMMANDS_UNINSTALL = "DESKTOP_COMMANDS_UNINSTALL";
+ static final String UTILITY_SCRIPTS = "UTILITY_SCRIPTS";
+
+ DesktopIntegration(PlatformPackage thePackage,
+ Map<String, ? super Object> params) {
+
+ associations = FileAssociation.fetchFrom(params).stream()
+ .filter(fa -> !fa.mimeTypes.isEmpty())
+ .map(LinuxFileAssociation::new)
+ .collect(Collectors.toUnmodifiableList());
+
+ launchers = ADD_LAUNCHERS.fetchFrom(params);
+
+ this.thePackage = thePackage;
+
+ final File customIconFile = ICON_PNG.fetchFrom(params);
+
+ iconResource = createResource(DEFAULT_ICON, params)
+ .setCategory(I18N.getString("resource.menu-icon"))
+ .setExternal(customIconFile);
+ desktopFileResource = createResource("template.desktop", params)
+ .setCategory(I18N.getString("resource.menu-shortcut-descriptor"))
+ .setPublicName(APP_NAME.fetchFrom(params) + ".desktop");
+
+ // XDG recommends to use vendor prefix in desktop file names as xdg
+ // commands copy files to system directories.
+ // Package name should be a good prefix.
+ final String desktopFileName = String.format("%s-%s.desktop",
+ thePackage.name(), APP_NAME.fetchFrom(params));
+ final String mimeInfoFileName = String.format("%s-%s-MimeInfo.xml",
+ thePackage.name(), APP_NAME.fetchFrom(params));
+
+ mimeInfoFile = new DesktopFile(mimeInfoFileName);
+
+ if (!associations.isEmpty() || SHORTCUT_HINT.fetchFrom(params) || customIconFile != null) {
+ //
+ // Create primary .desktop file if one of conditions is met:
+ // - there are file associations configured
+ // - user explicitely requested to create a shortcut
+ // - custom icon specified
+ //
+ desktopFile = new DesktopFile(desktopFileName);
+ iconFile = new DesktopFile(APP_NAME.fetchFrom(params)
+ + IOUtils.getSuffix(Path.of(DEFAULT_ICON)));
+ } else {
+ desktopFile = null;
+ iconFile = null;
+ }
+
+ desktopFileData = Collections.unmodifiableMap(
+ createDataForDesktopFile(params));
+
+ nestedIntegrations = launchers.stream().map(
+ launcherParams -> new DesktopIntegration(thePackage,
+ launcherParams)).collect(Collectors.toList());
+ }
+
+ List<String> requiredPackages() {
+ return Stream.of(List.of(this), nestedIntegrations).flatMap(
+ List::stream).map(DesktopIntegration::requiredPackagesSelf).flatMap(
+ List::stream).distinct().collect(Collectors.toList());
+ }
+
+ Map<String, String> create() throws IOException {
+ associations.forEach(assoc -> assoc.data.verify());
+
+ if (iconFile != null) {
+ // Create application icon file.
+ iconResource.saveToFile(iconFile.srcPath());
+ }
+
+ Map<String, String> data = new HashMap<>(desktopFileData);
+
+ final ShellCommands shellCommands;
+ if (desktopFile != null) {
+ // Create application desktop description file.
+ createDesktopFile(data);
+
+ // Shell commands will be created only if desktop file
+ // should be installed.
+ shellCommands = new ShellCommands();
+ } else {
+ shellCommands = null;
+ }
+
+ if (!associations.isEmpty()) {
+ // Create XML file with mime types corresponding to file associations.
+ createFileAssociationsMimeInfoFile();
+
+ shellCommands.setFileAssociations();
+
+ // Create icon files corresponding to file associations
+ addFileAssociationIconFiles(shellCommands);
+ }
+
+ // Create shell commands to install/uninstall integration with desktop of the app.
+ if (shellCommands != null) {
+ shellCommands.applyTo(data);
+ }
+
+ boolean needCleanupScripts = !associations.isEmpty();
+
+ // Take care of additional launchers if there are any.
+ // Process every additional launcher as the main application launcher.
+ // Collect shell commands to install/uninstall integration with desktop
+ // of the additional launchers and append them to the corresponding
+ // commands of the main launcher.
+ List<String> installShellCmds = new ArrayList<>(Arrays.asList(
+ data.get(DESKTOP_COMMANDS_INSTALL)));
+ List<String> uninstallShellCmds = new ArrayList<>(Arrays.asList(
+ data.get(DESKTOP_COMMANDS_UNINSTALL)));
+ for (var integration: nestedIntegrations) {
+ if (!integration.associations.isEmpty()) {
+ needCleanupScripts = true;
+ }
+
+ Map<String, String> launcherData = integration.create();
+
+ installShellCmds.add(launcherData.get(DESKTOP_COMMANDS_INSTALL));
+ uninstallShellCmds.add(launcherData.get(
+ DESKTOP_COMMANDS_UNINSTALL));
+ }
+
+ data.put(DESKTOP_COMMANDS_INSTALL, stringifyShellCommands(
+ installShellCmds));
+ data.put(DESKTOP_COMMANDS_UNINSTALL, stringifyShellCommands(
+ uninstallShellCmds));
+
+ if (needCleanupScripts) {
+ // Pull in utils.sh scrips library.
+ try (InputStream is = OverridableResource.readDefault("utils.sh");
+ InputStreamReader isr = new InputStreamReader(is);
+ BufferedReader reader = new BufferedReader(isr)) {
+ data.put(UTILITY_SCRIPTS, reader.lines().collect(
+ Collectors.joining(System.lineSeparator())));
+ }
+ } else {
+ data.put(UTILITY_SCRIPTS, "");
+ }
+
+ return data;
+ }
+
+ private List<String> requiredPackagesSelf() {
+ if (desktopFile != null) {
+ return List.of("xdg-utils");
+ }
+ return Collections.emptyList();
+ }
+
+ private Map<String, String> createDataForDesktopFile(
+ Map<String, ? super Object> params) {
+ Map<String, String> data = new HashMap<>();
+ data.put("APPLICATION_NAME", APP_NAME.fetchFrom(params));
+ data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params));
+ data.put("APPLICATION_ICON",
+ iconFile != null ? iconFile.installPath().toString() : null);
+ data.put("DEPLOY_BUNDLE_CATEGORY", MENU_GROUP.fetchFrom(params));
+ data.put("APPLICATION_LAUNCHER",
+ thePackage.installedApplicationLayout().launchersDirectory().resolve(
+ LinuxAppImageBuilder.getLauncherName(params)).toString());
+
+ return data;
+ }
+
+ /**
+ * Shell commands to integrate something with desktop.
+ */
+ private class ShellCommands {
+
+ ShellCommands() {
+ registerIconCmds = new ArrayList<>();
+ unregisterIconCmds = new ArrayList<>();
+
+ registerDesktopFileCmd = String.join(" ", "xdg-desktop-menu",
+ "install", desktopFile.installPath().toString());
+ unregisterDesktopFileCmd = String.join(" ", "xdg-desktop-menu",
+ "uninstall", desktopFile.installPath().toString());
+ }
+
+ void setFileAssociations() {
+ registerFileAssociationsCmd = String.join(" ", "xdg-mime",
+ "install",
+ mimeInfoFile.installPath().toString());
+ unregisterFileAssociationsCmd = String.join(" ", "xdg-mime",
+ "uninstall", mimeInfoFile.installPath().toString());
+
+ //
+ // Add manual cleanup of system files to get rid of
+ // the default mime type handlers.
+ //
+ // Even after mime type is unregisterd with `xdg-mime uninstall`
+ // command and desktop file deleted with `xdg-desktop-menu uninstall`
+ // command, records in
+ // `/usr/share/applications/defaults.list` (Ubuntu 16) or
+ // `/usr/local/share/applications/defaults.list` (OracleLinux 7)
+ // files remain referencing deleted mime time and deleted
+ // desktop file which makes `xdg-mime query default` output name
+ // of non-existing desktop file.
+ //
+ String cleanUpCommand = String.join(" ",
+ "uninstall_default_mime_handler",
+ desktopFile.installPath().getFileName().toString(),
+ String.join(" ", getMimeTypeNamesFromFileAssociations()));
+
+ unregisterFileAssociationsCmd = stringifyShellCommands(
+ unregisterFileAssociationsCmd, cleanUpCommand);
+ }
+
+ void addIcon(String mimeType, Path iconFile) {
+ addIcon(mimeType, iconFile, getSquareSizeOfImage(iconFile.toFile()));
+ }
+
+ void addIcon(String mimeType, Path iconFile, int imgSize) {
+ imgSize = normalizeIconSize(imgSize);
+ final String dashMime = mimeType.replace('/', '-');
+ registerIconCmds.add(String.join(" ", "xdg-icon-resource",
+ "install", "--context", "mimetypes", "--size",
+ Integer.toString(imgSize), iconFile.toString(), dashMime));
+ unregisterIconCmds.add(String.join(" ", "xdg-icon-resource",
+ "uninstall", dashMime, "--size", Integer.toString(imgSize)));
+ }
+
+ void applyTo(Map<String, String> data) {
+ List<String> cmds = new ArrayList<>();
+
+ cmds.add(registerDesktopFileCmd);
+ cmds.add(registerFileAssociationsCmd);
+ cmds.addAll(registerIconCmds);
+ data.put(DESKTOP_COMMANDS_INSTALL, stringifyShellCommands(cmds));
+
+ cmds.clear();
+ cmds.add(unregisterDesktopFileCmd);
+ cmds.add(unregisterFileAssociationsCmd);
+ cmds.addAll(unregisterIconCmds);
+ data.put(DESKTOP_COMMANDS_UNINSTALL, stringifyShellCommands(cmds));
+ }
+
+ private String registerDesktopFileCmd;
+ private String unregisterDesktopFileCmd;
+
+ private String registerFileAssociationsCmd;
+ private String unregisterFileAssociationsCmd;
+
+ private List<String> registerIconCmds;
+ private List<String> unregisterIconCmds;
+ }
+
+ /**
+ * Desktop integration file. xml, icon, etc.
+ * Resides somewhere in application installation tree.
+ * Has two paths:
+ * - path where it should be placed at package build time;
+ * - path where it should be installed by package manager;
+ */
+ private class DesktopFile {
+
+ DesktopFile(String fileName) {
+ installPath = thePackage
+ .installedApplicationLayout()
+ .destktopIntegrationDirectory().resolve(fileName);
+ srcPath = thePackage
+ .sourceApplicationLayout()
+ .destktopIntegrationDirectory().resolve(fileName);
+ }
+
+ private final Path installPath;
+ private final Path srcPath;
+
+ Path installPath() {
+ return installPath;
+ }
+
+ Path srcPath() {
+ return srcPath;
+ }
+ }
+
+ private void appendFileAssociation(XMLStreamWriter xml,
+ FileAssociation assoc) throws XMLStreamException {
+
+ for (var mimeType : assoc.mimeTypes) {
+ xml.writeStartElement("mime-type");
+ xml.writeAttribute("type", mimeType);
+
+ final String description = assoc.description;
+ if (description != null && !description.isEmpty()) {
+ xml.writeStartElement("comment");
+ xml.writeCharacters(description);
+ xml.writeEndElement();
+ }
+
+ for (String ext : assoc.extensions) {
+ xml.writeStartElement("glob");
+ xml.writeAttribute("pattern", "*." + ext);
+ xml.writeEndElement();
+ }
+
+ xml.writeEndElement();
+ }
+ }
+
+ private void createFileAssociationsMimeInfoFile() throws IOException {
+ IOUtils.createXml(mimeInfoFile.srcPath(), xml -> {
+ xml.writeStartElement("mime-info");
+ xml.writeDefaultNamespace(
+ "http://www.freedesktop.org/standards/shared-mime-info");
+
+ for (var assoc : associations) {
+ appendFileAssociation(xml, assoc.data);
+ }
+
+ xml.writeEndElement();
+ });
+ }
+
+ private void addFileAssociationIconFiles(ShellCommands shellCommands)
+ throws IOException {
+ Set<String> processedMimeTypes = new HashSet<>();
+ for (var assoc : associations) {
+ if (assoc.iconSize <= 0) {
+ // No icon.
+ continue;
+ }
+
+ for (var mimeType : assoc.data.mimeTypes) {
+ if (processedMimeTypes.contains(mimeType)) {
+ continue;
+ }
+
+ processedMimeTypes.add(mimeType);
+
+ // Create icon name for mime type from mime type.
+ DesktopFile faIconFile = new DesktopFile(mimeType.replace(
+ File.separatorChar, '-') + IOUtils.getSuffix(
+ assoc.data.iconPath));
+
+ IOUtils.copyFile(assoc.data.iconPath.toFile(),
+ faIconFile.srcPath().toFile());
+
+ shellCommands.addIcon(mimeType, faIconFile.installPath(),
+ assoc.iconSize);
+ }
+ }
+ }
+
+ private void createDesktopFile(Map<String, String> data) throws IOException {
+ List<String> mimeTypes = getMimeTypeNamesFromFileAssociations();
+ data.put("DESKTOP_MIMES", "MimeType=" + String.join(";", mimeTypes));
+
+ // prepare desktop shortcut
+ desktopFileResource
+ .setSubstitutionData(data)
+ .saveToFile(desktopFile.srcPath());
+ }
+
+ private List<String> getMimeTypeNamesFromFileAssociations() {
+ return associations.stream()
+ .map(fa -> fa.data.mimeTypes)
+ .flatMap(List::stream)
+ .collect(Collectors.toUnmodifiableList());
+ }
+
+ private static int getSquareSizeOfImage(File f) {
+ try {
+ BufferedImage bi = ImageIO.read(f);
+ return Math.max(bi.getWidth(), bi.getHeight());
+ } catch (IOException e) {
+ Log.verbose(e);
+ }
+ return 0;
+ }
+
+ private static int normalizeIconSize(int iconSize) {
+ // If register icon with "uncommon" size, it will be ignored.
+ // So find the best matching "common" size.
+ List<Integer> commonIconSizes = List.of(16, 22, 32, 48, 64, 128);
+
+ int idx = Collections.binarySearch(commonIconSizes, iconSize);
+ if (idx < 0) {
+ // Given icon size is greater than the largest common icon size.
+ return commonIconSizes.get(commonIconSizes.size() - 1);
+ }
+
+ if (idx == 0) {
+ // Given icon size is less or equal than the smallest common icon size.
+ return commonIconSizes.get(idx);
+ }
+
+ int commonIconSize = commonIconSizes.get(idx);
+ if (iconSize < commonIconSize) {
+ // It is better to scale down original icon than to scale it up for
+ // better visual quality.
+ commonIconSize = commonIconSizes.get(idx - 1);
+ }
+
+ return commonIconSize;
+ }
+
+ private static String stringifyShellCommands(String... commands) {
+ return stringifyShellCommands(Arrays.asList(commands));
+ }
+
+ private static String stringifyShellCommands(List<String> commands) {
+ return String.join(System.lineSeparator(), commands.stream().filter(
+ s -> s != null && !s.isEmpty()).collect(Collectors.toList()));
+ }
+
+ private static class LinuxFileAssociation {
+ LinuxFileAssociation(FileAssociation fa) {
+ this.data = fa;
+ if (fa.iconPath != null && Files.isReadable(fa.iconPath)) {
+ iconSize = getSquareSizeOfImage(fa.iconPath.toFile());
+ } else {
+ iconSize = -1;
+ }
+ }
+
+ final FileAssociation data;
+ final int iconSize;
+ }
+
+ private final PlatformPackage thePackage;
+
+ private final List<LinuxFileAssociation> associations;
+
+ private final List<Map<String, ? super Object>> launchers;
+
+ private final OverridableResource iconResource;
+ private final OverridableResource desktopFileResource;
+
+ private final DesktopFile mimeInfoFile;
+ private final DesktopFile desktopFile;
+ private final DesktopFile iconFile;
+
+ private final List<DesktopIntegration> nestedIntegrations;
+
+ private final Map<String, String> desktopFileData;
+
+ private static final BundlerParamInfo<String> MENU_GROUP =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.LINUX_MENU_GROUP.getId(),
+ String.class,
+ params -> I18N.getString("param.menu-group.default"),
+ (s, p) -> s
+ );
+
+ private static final StandardBundlerParam<Boolean> SHORTCUT_HINT =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.LINUX_SHORTCUT_HINT.getId(),
+ Boolean.class,
+ params -> false,
+ (s, p) -> (s == null || "null".equalsIgnoreCase(s))
+ ? false : Boolean.valueOf(s)
+ );
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/LibProvidersLookup.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.incubator.jpackage.internal;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.function.Predicate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Builds list of packages providing dynamic libraries for the given set of files.
+ */
+final public class LibProvidersLookup {
+ static boolean supported() {
+ return (new ToolValidator(TOOL_LDD).validate() == null);
+ }
+
+ public LibProvidersLookup() {
+ }
+
+ LibProvidersLookup setPackageLookup(PackageLookup v) {
+ packageLookup = v;
+ return this;
+ }
+
+ List<String> execute(Path root) throws IOException {
+ // Get the list of files in the root for which to look up for needed shared libraries
+ List<Path> allPackageFiles;
+ try (Stream<Path> stream = Files.walk(root)) {
+ allPackageFiles = stream.filter(Files::isRegularFile).filter(
+ LibProvidersLookup::canDependOnLibs).collect(
+ Collectors.toList());
+ }
+
+ Collection<Path> neededLibs = getNeededLibsForFiles(allPackageFiles);
+
+ // Get the list of unique package names.
+ List<String> neededPackages = neededLibs.stream().map(libPath -> {
+ try {
+ List<String> packageNames = packageLookup.apply(libPath).filter(
+ Objects::nonNull).filter(Predicate.not(String::isBlank)).distinct().collect(
+ Collectors.toList());
+ Log.verbose(String.format("%s is provided by %s", libPath, packageNames));
+ return packageNames;
+ } catch (IOException ex) {
+ // Ignore and keep going
+ Log.verbose(ex);
+ List<String> packageNames = Collections.emptyList();
+ return packageNames;
+ }
+ }).flatMap(List::stream).sorted().distinct().collect(Collectors.toList());
+
+ return neededPackages;
+ }
+
+ private static List<Path> getNeededLibsForFile(Path path) throws IOException {
+ List<Path> result = new ArrayList<>();
+ int ret = Executor.of(TOOL_LDD, path.toString()).setOutputConsumer(lines -> {
+ lines.map(line -> {
+ Matcher matcher = LIB_IN_LDD_OUTPUT_REGEX.matcher(line);
+ if (matcher.find()) {
+ return matcher.group(1);
+ }
+ return null;
+ }).filter(Objects::nonNull).map(Path::of).forEach(result::add);
+ }).execute();
+
+ if (ret != 0) {
+ // objdump failed. This is OK if the tool was applied to not a binary file
+ return Collections.emptyList();
+ }
+
+ return result;
+ }
+
+ private static Collection<Path> getNeededLibsForFiles(List<Path> paths) {
+ // Depending on tool used, the set can contain full paths (ldd) or
+ // only file names (objdump).
+ Set<Path> allLibs = paths.stream().map(path -> {
+ List<Path> libs;
+ try {
+ libs = getNeededLibsForFile(path);
+ } catch (IOException ex) {
+ Log.verbose(ex);
+ libs = Collections.emptyList();
+ }
+ return libs;
+ }).flatMap(List::stream).collect(Collectors.toSet());
+
+ // `allLibs` contains names of all .so needed by files from `paths` list.
+ // If there are mutual dependencies between binaries from `paths` list,
+ // then names or full paths to these binaries are in `allLibs` set.
+ // Remove these items from `allLibs`.
+ Set<Path> excludedNames = paths.stream().map(Path::getFileName).collect(
+ Collectors.toSet());
+ Iterator<Path> it = allLibs.iterator();
+ while (it.hasNext()) {
+ Path libName = it.next().getFileName();
+ if (excludedNames.contains(libName)) {
+ it.remove();
+ }
+ }
+
+ return allLibs;
+ }
+
+ private static boolean canDependOnLibs(Path path) {
+ return path.toFile().canExecute() || path.toString().endsWith(".so");
+ }
+
+ @FunctionalInterface
+ public interface PackageLookup {
+ Stream<String> apply(Path path) throws IOException;
+ }
+
+ private PackageLookup packageLookup;
+
+ private static final String TOOL_LDD = "ldd";
+
+ //
+ // Typical ldd output:
+ //
+ // ldd: warning: you do not have execution permission for `/tmp/jdk.incubator.jpackage17911687595930080396/images/opt/simplepackagetest/lib/runtime/lib/libawt_headless.so'
+ // linux-vdso.so.1 => (0x00007ffce6bfd000)
+ // libawt.so => /tmp/jdk.incubator.jpackage17911687595930080396/images/opt/simplepackagetest/lib/runtime/lib/libawt.so (0x00007f4e00c75000)
+ // libjvm.so => not found
+ // libjava.so => /tmp/jdk.incubator.jpackage17911687595930080396/images/opt/simplepackagetest/lib/runtime/lib/libjava.so (0x00007f4e00c41000)
+ // libm.so.6 => /lib64/libm.so.6 (0x00007f4e00834000)
+ // libdl.so.2 => /lib64/libdl.so.2 (0x00007f4e00630000)
+ // libc.so.6 => /lib64/libc.so.6 (0x00007f4e00262000)
+ // libjvm.so => not found
+ // libjvm.so => not found
+ // libverify.so => /tmp/jdk.incubator.jpackage17911687595930080396/images/opt/simplepackagetest/lib/runtime/lib/libverify.so (0x00007f4e00c2e000)
+ // /lib64/ld-linux-x86-64.so.2 (0x00007f4e00b36000)
+ // libjvm.so => not found
+ //
+ private static final Pattern LIB_IN_LDD_OUTPUT_REGEX = Pattern.compile(
+ "^\\s*\\S+\\s*=>\\s*(\\S+)\\s+\\(0[xX]\\p{XDigit}+\\)");
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/LinuxAppBundler.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.File;
+import java.text.MessageFormat;
+import java.util.*;
+
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+
+public class LinuxAppBundler extends AbstractImageBundler {
+
+ static final BundlerParamInfo<File> ICON_PNG =
+ new StandardBundlerParam<>(
+ "icon.png",
+ File.class,
+ params -> {
+ File f = ICON.fetchFrom(params);
+ if (f != null && !f.getName().toLowerCase().endsWith(".png")) {
+ Log.error(MessageFormat.format(
+ I18N.getString("message.icon-not-png"), f));
+ return null;
+ }
+ return f;
+ },
+ (s, p) -> new File(s));
+
+ static final BundlerParamInfo<String> LINUX_INSTALL_DIR =
+ new StandardBundlerParam<>(
+ "linux-install-dir",
+ String.class,
+ params -> {
+ String dir = INSTALL_DIR.fetchFrom(params);
+ if (dir != null) {
+ if (dir.endsWith("/")) {
+ dir = dir.substring(0, dir.length()-1);
+ }
+ return dir;
+ }
+ return "/opt";
+ },
+ (s, p) -> s
+ );
+
+ static final BundlerParamInfo<String> LINUX_PACKAGE_DEPENDENCIES =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.LINUX_PACKAGE_DEPENDENCIES.getId(),
+ String.class,
+ params -> {
+ return "";
+ },
+ (s, p) -> s
+ );
+
+ @Override
+ public boolean validate(Map<String, ? super Object> params)
+ throws ConfigException {
+ try {
+ Objects.requireNonNull(params);
+ return doValidate(params);
+ } catch (RuntimeException re) {
+ if (re.getCause() instanceof ConfigException) {
+ throw (ConfigException) re.getCause();
+ } else {
+ throw new ConfigException(re);
+ }
+ }
+ }
+
+ private boolean doValidate(Map<String, ? super Object> params)
+ throws ConfigException {
+
+ imageBundleValidation(params);
+
+ return true;
+ }
+
+ File doBundle(Map<String, ? super Object> params, File outputDirectory,
+ boolean dependentTask) throws PackagerException {
+ if (StandardBundlerParam.isRuntimeInstaller(params)) {
+ return PREDEFINED_RUNTIME_IMAGE.fetchFrom(params);
+ } else {
+ return doAppBundle(params, outputDirectory, dependentTask);
+ }
+ }
+
+ private File doAppBundle(Map<String, ? super Object> params,
+ File outputDirectory, boolean dependentTask)
+ throws PackagerException {
+ try {
+ File rootDirectory = createRoot(params, outputDirectory,
+ dependentTask, APP_NAME.fetchFrom(params));
+ AbstractAppImageBuilder appBuilder = new LinuxAppImageBuilder(
+ params, outputDirectory.toPath());
+ if (PREDEFINED_RUNTIME_IMAGE.fetchFrom(params) == null ) {
+ JLinkBundlerHelper.execute(params, appBuilder);
+ } else {
+ StandardBundlerParam.copyPredefinedRuntimeImage(
+ params, appBuilder);
+ }
+ return rootDirectory;
+ } catch (PackagerException pe) {
+ throw pe;
+ } catch (Exception ex) {
+ Log.verbose(ex);
+ throw new PackagerException(ex);
+ }
+ }
+
+ @Override
+ public String getName() {
+ return I18N.getString("app.bundler.name");
+ }
+
+ @Override
+ public String getID() {
+ return "linux.app";
+ }
+
+ @Override
+ public String getBundleType() {
+ return "IMAGE";
+ }
+
+ @Override
+ public File execute(Map<String, ? super Object> params,
+ File outputParentDir) throws PackagerException {
+ return doBundle(params, outputParentDir, false);
+ }
+
+ @Override
+ public boolean supported(boolean runtimeInstaller) {
+ return true;
+ }
+
+ @Override
+ public boolean isDefault() {
+ return false;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/LinuxAppImageBuilder.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
+
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+
+public class LinuxAppImageBuilder extends AbstractAppImageBuilder {
+
+ private static final String LIBRARY_NAME = "libapplauncher.so";
+ final static String DEFAULT_ICON = "java32.png";
+
+ private final ApplicationLayout appLayout;
+
+ public static final BundlerParamInfo<File> ICON_PNG =
+ new StandardBundlerParam<>(
+ "icon.png",
+ File.class,
+ params -> {
+ File f = ICON.fetchFrom(params);
+ if (f != null && !f.getName().toLowerCase().endsWith(".png")) {
+ Log.error(MessageFormat.format(I18N.getString(
+ "message.icon-not-png"), f));
+ return null;
+ }
+ return f;
+ },
+ (s, p) -> new File(s));
+
+ private static ApplicationLayout createAppLayout(Map<String, Object> params,
+ Path imageOutDir) {
+ return ApplicationLayout.linuxAppImage().resolveAt(
+ imageOutDir.resolve(APP_NAME.fetchFrom(params)));
+ }
+
+ public LinuxAppImageBuilder(Map<String, Object> params, Path imageOutDir)
+ throws IOException {
+ super(params, createAppLayout(params, imageOutDir).runtimeDirectory());
+
+ appLayout = createAppLayout(params, imageOutDir);
+ }
+
+ private void writeEntry(InputStream in, Path dstFile) throws IOException {
+ Files.createDirectories(dstFile.getParent());
+ Files.copy(in, dstFile);
+ }
+
+ public static String getLauncherName(Map<String, ? super Object> params) {
+ return APP_NAME.fetchFrom(params);
+ }
+
+ private Path getLauncherCfgPath(Map<String, ? super Object> params) {
+ return appLayout.appDirectory().resolve(
+ APP_NAME.fetchFrom(params) + ".cfg");
+ }
+
+ @Override
+ public Path getAppDir() {
+ return appLayout.appDirectory();
+ }
+
+ @Override
+ public Path getAppModsDir() {
+ return appLayout.appModsDirectory();
+ }
+
+ @Override
+ protected String getCfgAppDir() {
+ return Path.of("$ROOTDIR").resolve(
+ ApplicationLayout.linuxAppImage().appDirectory()).toString()
+ + File.separator;
+ }
+
+ @Override
+ protected String getCfgRuntimeDir() {
+ return Path.of("$ROOTDIR").resolve(
+ ApplicationLayout.linuxAppImage().runtimeDirectory()).toString();
+ }
+
+ @Override
+ public void prepareApplicationFiles(Map<String, ? super Object> params)
+ throws IOException {
+ Map<String, ? super Object> originalParams = new HashMap<>(params);
+
+ appLayout.roots().stream().forEach(dir -> {
+ try {
+ IOUtils.writableOutputDir(dir);
+ } catch (PackagerException pe) {
+ throw new RuntimeException(pe);
+ }
+ });
+
+ // create the primary launcher
+ createLauncherForEntryPoint(params);
+
+ // Copy library to the launcher folder
+ try (InputStream is_lib = getResourceAsStream(LIBRARY_NAME)) {
+ writeEntry(is_lib, appLayout.dllDirectory().resolve(LIBRARY_NAME));
+ }
+
+ // create the additional launchers, if any
+ List<Map<String, ? super Object>> entryPoints
+ = StandardBundlerParam.ADD_LAUNCHERS.fetchFrom(params);
+ for (Map<String, ? super Object> entryPoint : entryPoints) {
+ createLauncherForEntryPoint(
+ AddLauncherArguments.merge(originalParams, entryPoint));
+ }
+
+ // Copy class path entries to Java folder
+ copyApplication(params);
+
+ // Copy icon to Resources folder
+ copyIcon(params);
+ }
+
+ @Override
+ public void prepareJreFiles(Map<String, ? super Object> params)
+ throws IOException {}
+
+ private void createLauncherForEntryPoint(
+ Map<String, ? super Object> params) throws IOException {
+ // Copy executable to launchers folder
+ Path executableFile = appLayout.launchersDirectory().resolve(getLauncherName(params));
+ try (InputStream is_launcher =
+ getResourceAsStream("jpackageapplauncher")) {
+ writeEntry(is_launcher, executableFile);
+ }
+
+ executableFile.toFile().setExecutable(true, false);
+ executableFile.toFile().setWritable(true, true);
+
+ writeCfgFile(params, getLauncherCfgPath(params).toFile());
+ }
+
+ private void copyIcon(Map<String, ? super Object> params)
+ throws IOException {
+
+ Path iconTarget = appLayout.destktopIntegrationDirectory().resolve(
+ APP_NAME.fetchFrom(params) + IOUtils.getSuffix(Path.of(
+ DEFAULT_ICON)));
+
+ createResource(DEFAULT_ICON, params)
+ .setCategory("icon")
+ .setExternal(ICON_PNG.fetchFrom(params))
+ .saveToFile(iconTarget);
+ }
+
+ private void copyApplication(Map<String, ? super Object> params)
+ throws IOException {
+ for (RelativeFileSet appResources :
+ APP_RESOURCES_LIST.fetchFrom(params)) {
+ if (appResources == null) {
+ throw new RuntimeException("Null app resources?");
+ }
+ File srcdir = appResources.getBaseDirectory();
+ for (String fname : appResources.getIncludedFiles()) {
+ copyEntry(appLayout.appDirectory(), srcdir, fname);
+ }
+ }
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/LinuxDebBundler.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,487 @@
+/*
+ * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.*;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.text.MessageFormat;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import static jdk.incubator.jpackage.internal.LinuxAppBundler.LINUX_INSTALL_DIR;
+import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
+
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+
+
+public class LinuxDebBundler extends LinuxPackageBundler {
+
+ // Debian rules for package naming are used here
+ // https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Source
+ //
+ // Package names must consist only of lower case letters (a-z),
+ // digits (0-9), plus (+) and minus (-) signs, and periods (.).
+ // They must be at least two characters long and
+ // must start with an alphanumeric character.
+ //
+ private static final Pattern DEB_PACKAGE_NAME_PATTERN =
+ Pattern.compile("^[a-z][a-z\\d\\+\\-\\.]+");
+
+ private static final BundlerParamInfo<String> PACKAGE_NAME =
+ new StandardBundlerParam<> (
+ Arguments.CLIOptions.LINUX_BUNDLE_NAME.getId(),
+ String.class,
+ params -> {
+ String nm = APP_NAME.fetchFrom(params);
+
+ if (nm == null) return null;
+
+ // make sure to lower case and spaces/underscores become dashes
+ nm = nm.toLowerCase().replaceAll("[ _]", "-");
+ return nm;
+ },
+ (s, p) -> {
+ if (!DEB_PACKAGE_NAME_PATTERN.matcher(s).matches()) {
+ throw new IllegalArgumentException(new ConfigException(
+ MessageFormat.format(I18N.getString(
+ "error.invalid-value-for-package-name"), s),
+ I18N.getString(
+ "error.invalid-value-for-package-name.advice")));
+ }
+
+ return s;
+ });
+
+ private final static String TOOL_DPKG_DEB = "dpkg-deb";
+ private final static String TOOL_DPKG = "dpkg";
+ private final static String TOOL_FAKEROOT = "fakeroot";
+
+ private final static String DEB_ARCH;
+ static {
+ String debArch;
+ try {
+ debArch = Executor.of(TOOL_DPKG, "--print-architecture").saveOutput(
+ true).executeExpectSuccess().getOutput().get(0);
+ } catch (IOException ex) {
+ debArch = null;
+ }
+ DEB_ARCH = debArch;
+ }
+
+ private static final BundlerParamInfo<String> FULL_PACKAGE_NAME =
+ new StandardBundlerParam<>(
+ "linux.deb.fullPackageName", String.class, params -> {
+ return PACKAGE_NAME.fetchFrom(params)
+ + "_" + VERSION.fetchFrom(params)
+ + "-" + RELEASE.fetchFrom(params)
+ + "_" + DEB_ARCH;
+ }, (s, p) -> s);
+
+ private static final BundlerParamInfo<String> EMAIL =
+ new StandardBundlerParam<> (
+ Arguments.CLIOptions.LINUX_DEB_MAINTAINER.getId(),
+ String.class,
+ params -> "Unknown",
+ (s, p) -> s);
+
+ private static final BundlerParamInfo<String> MAINTAINER =
+ new StandardBundlerParam<> (
+ BundleParams.PARAM_MAINTAINER,
+ String.class,
+ params -> VENDOR.fetchFrom(params) + " <"
+ + EMAIL.fetchFrom(params) + ">",
+ (s, p) -> s);
+
+ private static final BundlerParamInfo<String> SECTION =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.LINUX_CATEGORY.getId(),
+ String.class,
+ params -> "misc",
+ (s, p) -> s);
+
+ private static final BundlerParamInfo<String> LICENSE_TEXT =
+ new StandardBundlerParam<> (
+ "linux.deb.licenseText",
+ String.class,
+ params -> {
+ try {
+ String licenseFile = LICENSE_FILE.fetchFrom(params);
+ if (licenseFile != null) {
+ return Files.readString(Path.of(licenseFile));
+ }
+ } catch (IOException e) {
+ Log.verbose(e);
+ }
+ return "Unknown";
+ },
+ (s, p) -> s);
+
+ public LinuxDebBundler() {
+ super(PACKAGE_NAME);
+ }
+
+ @Override
+ public void doValidate(Map<String, ? super Object> params)
+ throws ConfigException {
+
+ // Show warning if license file is missing
+ if (LICENSE_FILE.fetchFrom(params) == null) {
+ Log.verbose(I18N.getString("message.debs-like-licenses"));
+ }
+ }
+
+ @Override
+ protected List<ToolValidator> getToolValidators(
+ Map<String, ? super Object> params) {
+ return Stream.of(TOOL_DPKG_DEB, TOOL_DPKG, TOOL_FAKEROOT).map(
+ ToolValidator::new).collect(Collectors.toList());
+ }
+
+ @Override
+ protected File buildPackageBundle(
+ Map<String, String> replacementData,
+ Map<String, ? super Object> params, File outputParentDir) throws
+ PackagerException, IOException {
+
+ prepareProjectConfig(replacementData, params);
+ adjustPermissionsRecursive(createMetaPackage(params).sourceRoot().toFile());
+ return buildDeb(params, outputParentDir);
+ }
+
+ private static final Pattern PACKAGE_NAME_REGEX = Pattern.compile("^(^\\S+):");
+
+ @Override
+ protected void initLibProvidersLookup(
+ Map<String, ? super Object> params,
+ LibProvidersLookup libProvidersLookup) {
+
+ //
+ // `dpkg -S` command does glob pattern lookup. If not the absolute path
+ // to the file is specified it might return mltiple package names.
+ // Even for full paths multiple package names can be returned as
+ // it is OK for multiple packages to provide the same file. `/opt`
+ // directory is such an example. So we have to deal with multiple
+ // packages per file situation.
+ //
+ // E.g.: `dpkg -S libc.so.6` command reports three packages:
+ // libc6-x32: /libx32/libc.so.6
+ // libc6:amd64: /lib/x86_64-linux-gnu/libc.so.6
+ // libc6-i386: /lib32/libc.so.6
+ // `:amd64` is architecture suffix and can (should) be dropped.
+ // Still need to decide what package to choose from three.
+ // libc6-x32 and libc6-i386 both depend on libc6:
+ // $ dpkg -s libc6-x32
+ // Package: libc6-x32
+ // Status: install ok installed
+ // Priority: optional
+ // Section: libs
+ // Installed-Size: 10840
+ // Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
+ // Architecture: amd64
+ // Source: glibc
+ // Version: 2.23-0ubuntu10
+ // Depends: libc6 (= 2.23-0ubuntu10)
+ //
+ // We can dive into tracking dependencies, but this would be overly
+ // complicated.
+ //
+ // For simplicity lets consider the following rules:
+ // 1. If there is one item in `dpkg -S` output, accept it.
+ // 2. If there are multiple items in `dpkg -S` output and there is at
+ // least one item with the default arch suffix (DEB_ARCH),
+ // accept only these items.
+ // 3. If there are multiple items in `dpkg -S` output and there are
+ // no with the default arch suffix (DEB_ARCH), accept all items.
+ // So lets use this heuristics: don't accept packages for whom
+ // `dpkg -p` command fails.
+ // 4. Arch suffix should be stripped from accepted package names.
+ //
+
+ libProvidersLookup.setPackageLookup(file -> {
+ Set<String> archPackages = new HashSet<>();
+ Set<String> otherPackages = new HashSet<>();
+
+ Executor.of(TOOL_DPKG, "-S", file.toString())
+ .saveOutput(true).executeExpectSuccess()
+ .getOutput().forEach(line -> {
+ Matcher matcher = PACKAGE_NAME_REGEX.matcher(line);
+ if (matcher.find()) {
+ String name = matcher.group(1);
+ if (name.endsWith(":" + DEB_ARCH)) {
+ // Strip arch suffix
+ name = name.substring(0,
+ name.length() - (DEB_ARCH.length() + 1));
+ archPackages.add(name);
+ } else {
+ otherPackages.add(name);
+ }
+ }
+ });
+
+ if (!archPackages.isEmpty()) {
+ return archPackages.stream();
+ }
+ return otherPackages.stream();
+ });
+ }
+
+ @Override
+ protected List<ConfigException> verifyOutputBundle(
+ Map<String, ? super Object> params, Path packageBundle) {
+ List<ConfigException> errors = new ArrayList<>();
+
+ String controlFileName = "control";
+
+ List<PackageProperty> properties = List.of(
+ new PackageProperty("Package", PACKAGE_NAME.fetchFrom(params),
+ "APPLICATION_PACKAGE", controlFileName),
+ new PackageProperty("Version", String.format("%s-%s",
+ VERSION.fetchFrom(params), RELEASE.fetchFrom(params)),
+ "APPLICATION_VERSION-APPLICATION_RELEASE",
+ controlFileName),
+ new PackageProperty("Architecture", DEB_ARCH, "APPLICATION_ARCH",
+ controlFileName));
+
+ List<String> cmdline = new ArrayList<>(List.of(TOOL_DPKG_DEB, "-f",
+ packageBundle.toString()));
+ properties.forEach(property -> cmdline.add(property.name));
+ try {
+ Map<String, String> actualValues = Executor.of(cmdline.toArray(String[]::new))
+ .saveOutput(true)
+ .executeExpectSuccess()
+ .getOutput().stream()
+ .map(line -> line.split(":\\s+", 2))
+ .collect(Collectors.toMap(
+ components -> components[0],
+ components -> components[1]));
+ properties.forEach(property -> errors.add(property.verifyValue(
+ actualValues.get(property.name))));
+ } catch (IOException ex) {
+ // Ignore error as it is not critical. Just report it.
+ Log.verbose(ex);
+ }
+
+ return errors;
+ }
+
+ /*
+ * set permissions with a string like "rwxr-xr-x"
+ *
+ * This cannot be directly backport to 22u which is built with 1.6
+ */
+ private void setPermissions(File file, String permissions) {
+ Set<PosixFilePermission> filePermissions =
+ PosixFilePermissions.fromString(permissions);
+ try {
+ if (file.exists()) {
+ Files.setPosixFilePermissions(file.toPath(), filePermissions);
+ }
+ } catch (IOException ex) {
+ Log.error(ex.getMessage());
+ Log.verbose(ex);
+ }
+
+ }
+
+ public static boolean isDebian() {
+ // we are just going to run "dpkg -s coreutils" and assume Debian
+ // or deritive if no error is returned.
+ try {
+ Executor.of(TOOL_DPKG, "-s", "coreutils").executeExpectSuccess();
+ return true;
+ } catch (IOException e) {
+ // just fall thru
+ }
+ return false;
+ }
+
+ private void adjustPermissionsRecursive(File dir) throws IOException {
+ Files.walkFileTree(dir.toPath(), new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(Path file,
+ BasicFileAttributes attrs)
+ throws IOException {
+ if (file.endsWith(".so") || !Files.isExecutable(file)) {
+ setPermissions(file.toFile(), "rw-r--r--");
+ } else if (Files.isExecutable(file)) {
+ setPermissions(file.toFile(), "rwxr-xr-x");
+ }
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir, IOException e)
+ throws IOException {
+ if (e == null) {
+ setPermissions(dir.toFile(), "rwxr-xr-x");
+ return FileVisitResult.CONTINUE;
+ } else {
+ // directory iteration failed
+ throw e;
+ }
+ }
+ });
+ }
+
+ private class DebianFile {
+
+ DebianFile(Path dstFilePath, String comment) {
+ this.dstFilePath = dstFilePath;
+ this.comment = comment;
+ }
+
+ DebianFile setExecutable() {
+ permissions = "rwxr-xr-x";
+ return this;
+ }
+
+ void create(Map<String, String> data, Map<String, ? super Object> params)
+ throws IOException {
+ createResource("template." + dstFilePath.getFileName().toString(),
+ params)
+ .setCategory(I18N.getString(comment))
+ .setSubstitutionData(data)
+ .saveToFile(dstFilePath);
+ if (permissions != null) {
+ setPermissions(dstFilePath.toFile(), permissions);
+ }
+ }
+
+ private final Path dstFilePath;
+ private final String comment;
+ private String permissions;
+ }
+
+ private void prepareProjectConfig(Map<String, String> data,
+ Map<String, ? super Object> params) throws IOException {
+
+ Path configDir = createMetaPackage(params).sourceRoot().resolve("DEBIAN");
+ List<DebianFile> debianFiles = new ArrayList<>();
+ debianFiles.add(new DebianFile(
+ configDir.resolve("control"),
+ "resource.deb-control-file"));
+ debianFiles.add(new DebianFile(
+ configDir.resolve("preinst"),
+ "resource.deb-preinstall-script").setExecutable());
+ debianFiles.add(new DebianFile(
+ configDir.resolve("prerm"),
+ "resource.deb-prerm-script").setExecutable());
+ debianFiles.add(new DebianFile(
+ configDir.resolve("postinst"),
+ "resource.deb-postinstall-script").setExecutable());
+ debianFiles.add(new DebianFile(
+ configDir.resolve("postrm"),
+ "resource.deb-postrm-script").setExecutable());
+
+ if (!StandardBundlerParam.isRuntimeInstaller(params)) {
+ debianFiles.add(new DebianFile(
+ getConfig_CopyrightFile(params).toPath(),
+ "resource.copyright-file"));
+ }
+
+ for (DebianFile debianFile : debianFiles) {
+ debianFile.create(data, params);
+ }
+ }
+
+ @Override
+ protected Map<String, String> createReplacementData(
+ Map<String, ? super Object> params) throws IOException {
+ Map<String, String> data = new HashMap<>();
+
+ data.put("APPLICATION_MAINTAINER", MAINTAINER.fetchFrom(params));
+ data.put("APPLICATION_SECTION", SECTION.fetchFrom(params));
+ data.put("APPLICATION_COPYRIGHT", COPYRIGHT.fetchFrom(params));
+ data.put("APPLICATION_LICENSE_TEXT", LICENSE_TEXT.fetchFrom(params));
+ data.put("APPLICATION_ARCH", DEB_ARCH);
+ data.put("APPLICATION_INSTALLED_SIZE", Long.toString(
+ createMetaPackage(params).sourceApplicationLayout().sizeInBytes() >> 10));
+
+ return data;
+ }
+
+ private File getConfig_CopyrightFile(Map<String, ? super Object> params) {
+ PlatformPackage thePackage = createMetaPackage(params);
+ return thePackage.sourceRoot().resolve(Path.of(".",
+ LINUX_INSTALL_DIR.fetchFrom(params), PACKAGE_NAME.fetchFrom(
+ params), "share/doc/copyright")).toFile();
+ }
+
+ private File buildDeb(Map<String, ? super Object> params,
+ File outdir) throws IOException {
+ File outFile = new File(outdir,
+ FULL_PACKAGE_NAME.fetchFrom(params)+".deb");
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.outputting-to-location"), outFile.getAbsolutePath()));
+
+ PlatformPackage thePackage = createMetaPackage(params);
+
+ List<String> cmdline = new ArrayList<>();
+ cmdline.addAll(List.of(TOOL_FAKEROOT, TOOL_DPKG_DEB));
+ if (Log.isVerbose()) {
+ cmdline.add("--verbose");
+ }
+ cmdline.addAll(List.of("-b", thePackage.sourceRoot().toString(),
+ outFile.getAbsolutePath()));
+
+ // run dpkg
+ Executor.of(cmdline.toArray(String[]::new)).executeExpectSuccess();
+
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.output-to-location"), outFile.getAbsolutePath()));
+
+ return outFile;
+ }
+
+ @Override
+ public String getName() {
+ return I18N.getString("deb.bundler.name");
+ }
+
+ @Override
+ public String getID() {
+ return "deb";
+ }
+
+ @Override
+ public boolean supported(boolean runtimeInstaller) {
+ return Platform.isLinux() && (new ToolValidator(TOOL_DPKG_DEB).validate() == null);
+ }
+
+ @Override
+ public boolean isDefault() {
+ return isDebian();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/LinuxPackageBundler.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,357 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.incubator.jpackage.internal;
+
+import java.io.*;
+import java.nio.file.InvalidPathException;
+import java.nio.file.Path;
+import java.text.MessageFormat;
+import java.util.*;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import static jdk.incubator.jpackage.internal.DesktopIntegration.*;
+import static jdk.incubator.jpackage.internal.LinuxAppBundler.LINUX_INSTALL_DIR;
+import static jdk.incubator.jpackage.internal.LinuxAppBundler.LINUX_PACKAGE_DEPENDENCIES;
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+
+
+abstract class LinuxPackageBundler extends AbstractBundler {
+
+ LinuxPackageBundler(BundlerParamInfo<String> packageName) {
+ this.packageName = packageName;
+ }
+
+ @Override
+ final public boolean validate(Map<String, ? super Object> params)
+ throws ConfigException {
+
+ // run basic validation to ensure requirements are met
+ // we are not interested in return code, only possible exception
+ APP_BUNDLER.fetchFrom(params).validate(params);
+
+ validateInstallDir(LINUX_INSTALL_DIR.fetchFrom(params));
+
+ validateFileAssociations(FILE_ASSOCIATIONS.fetchFrom(params));
+
+ // If package name has some restrictions, the string converter will
+ // throw an exception if invalid
+ packageName.getStringConverter().apply(packageName.fetchFrom(params),
+ params);
+
+ for (var validator: getToolValidators(params)) {
+ ConfigException ex = validator.validate();
+ if (ex != null) {
+ throw ex;
+ }
+ }
+
+ withFindNeededPackages = LibProvidersLookup.supported();
+ if (!withFindNeededPackages) {
+ final String advice;
+ if ("deb".equals(getID())) {
+ advice = "message.deb-ldd-not-available.advice";
+ } else {
+ advice = "message.rpm-ldd-not-available.advice";
+ }
+ // Let user know package dependencies will not be generated.
+ Log.error(String.format("%s\n%s", I18N.getString(
+ "message.ldd-not-available"), I18N.getString(advice)));
+ }
+
+ // Packaging specific validation
+ doValidate(params);
+
+ return true;
+ }
+
+ @Override
+ final public String getBundleType() {
+ return "INSTALLER";
+ }
+
+ @Override
+ final public File execute(Map<String, ? super Object> params,
+ File outputParentDir) throws PackagerException {
+ IOUtils.writableOutputDir(outputParentDir.toPath());
+
+ PlatformPackage thePackage = createMetaPackage(params);
+
+ Function<File, ApplicationLayout> initAppImageLayout = imageRoot -> {
+ ApplicationLayout layout = appImageLayout(params);
+ layout.pathGroup().setPath(new Object(),
+ AppImageFile.getPathInAppImage(Path.of("")));
+ return layout.resolveAt(imageRoot.toPath());
+ };
+
+ try {
+ File appImage = StandardBundlerParam.getPredefinedAppImage(params);
+
+ // we either have an application image or need to build one
+ if (appImage != null) {
+ initAppImageLayout.apply(appImage).copy(
+ thePackage.sourceApplicationLayout());
+ } else {
+ appImage = APP_BUNDLER.fetchFrom(params).doBundle(params,
+ thePackage.sourceRoot().toFile(), true);
+ ApplicationLayout srcAppLayout = initAppImageLayout.apply(
+ appImage);
+ if (appImage.equals(PREDEFINED_RUNTIME_IMAGE.fetchFrom(params))) {
+ // Application image points to run-time image.
+ // Copy it.
+ srcAppLayout.copy(thePackage.sourceApplicationLayout());
+ } else {
+ // Application image is a newly created directory tree.
+ // Move it.
+ srcAppLayout.move(thePackage.sourceApplicationLayout());
+ if (appImage.exists()) {
+ // Empty app image directory might remain after all application
+ // directories have been moved.
+ appImage.delete();
+ }
+ }
+ }
+
+ if (!StandardBundlerParam.isRuntimeInstaller(params)) {
+ desktopIntegration = new DesktopIntegration(thePackage, params);
+ } else {
+ desktopIntegration = null;
+ }
+
+ Map<String, String> data = createDefaultReplacementData(params);
+ if (desktopIntegration != null) {
+ data.putAll(desktopIntegration.create());
+ } else {
+ Stream.of(DESKTOP_COMMANDS_INSTALL, DESKTOP_COMMANDS_UNINSTALL,
+ UTILITY_SCRIPTS).forEach(v -> data.put(v, ""));
+ }
+
+ data.putAll(createReplacementData(params));
+
+ File packageBundle = buildPackageBundle(Collections.unmodifiableMap(
+ data), params, outputParentDir);
+
+ verifyOutputBundle(params, packageBundle.toPath()).stream()
+ .filter(Objects::nonNull)
+ .forEachOrdered(ex -> {
+ Log.verbose(ex.getLocalizedMessage());
+ Log.verbose(ex.getAdvice());
+ });
+
+ return packageBundle;
+ } catch (IOException ex) {
+ Log.verbose(ex);
+ throw new PackagerException(ex);
+ }
+ }
+
+ private List<String> getListOfNeededPackages(
+ Map<String, ? super Object> params) throws IOException {
+
+ PlatformPackage thePackage = createMetaPackage(params);
+
+ final List<String> xdgUtilsPackage;
+ if (desktopIntegration != null) {
+ xdgUtilsPackage = desktopIntegration.requiredPackages();
+ } else {
+ xdgUtilsPackage = Collections.emptyList();
+ }
+
+ final List<String> neededLibPackages;
+ if (withFindNeededPackages) {
+ LibProvidersLookup lookup = new LibProvidersLookup();
+ initLibProvidersLookup(params, lookup);
+
+ neededLibPackages = lookup.execute(thePackage.sourceRoot());
+ } else {
+ neededLibPackages = Collections.emptyList();
+ }
+
+ // Merge all package lists together.
+ // Filter out empty names, sort and remove duplicates.
+ List<String> result = Stream.of(xdgUtilsPackage, neededLibPackages).flatMap(
+ List::stream).filter(Predicate.not(String::isEmpty)).sorted().distinct().collect(
+ Collectors.toList());
+
+ Log.verbose(String.format("Required packages: %s", result));
+
+ return result;
+ }
+
+ private Map<String, String> createDefaultReplacementData(
+ Map<String, ? super Object> params) throws IOException {
+ Map<String, String> data = new HashMap<>();
+
+ data.put("APPLICATION_PACKAGE", createMetaPackage(params).name());
+ data.put("APPLICATION_VENDOR", VENDOR.fetchFrom(params));
+ data.put("APPLICATION_VERSION", VERSION.fetchFrom(params));
+ data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params));
+ data.put("APPLICATION_RELEASE", RELEASE.fetchFrom(params));
+
+ String defaultDeps = String.join(", ", getListOfNeededPackages(params));
+ String customDeps = LINUX_PACKAGE_DEPENDENCIES.fetchFrom(params).strip();
+ if (!customDeps.isEmpty() && !defaultDeps.isEmpty()) {
+ customDeps = ", " + customDeps;
+ }
+ data.put("PACKAGE_DEFAULT_DEPENDENCIES", defaultDeps);
+ data.put("PACKAGE_CUSTOM_DEPENDENCIES", customDeps);
+
+ return data;
+ }
+
+ abstract protected List<ConfigException> verifyOutputBundle(
+ Map<String, ? super Object> params, Path packageBundle);
+
+ abstract protected void initLibProvidersLookup(
+ Map<String, ? super Object> params,
+ LibProvidersLookup libProvidersLookup);
+
+ abstract protected List<ToolValidator> getToolValidators(
+ Map<String, ? super Object> params);
+
+ abstract protected void doValidate(Map<String, ? super Object> params)
+ throws ConfigException;
+
+ abstract protected Map<String, String> createReplacementData(
+ Map<String, ? super Object> params) throws IOException;
+
+ abstract protected File buildPackageBundle(
+ Map<String, String> replacementData,
+ Map<String, ? super Object> params, File outputParentDir) throws
+ PackagerException, IOException;
+
+ final protected PlatformPackage createMetaPackage(
+ Map<String, ? super Object> params) {
+ return new PlatformPackage() {
+ @Override
+ public String name() {
+ return packageName.fetchFrom(params);
+ }
+
+ @Override
+ public Path sourceRoot() {
+ return IMAGES_ROOT.fetchFrom(params).toPath().toAbsolutePath();
+ }
+
+ @Override
+ public ApplicationLayout sourceApplicationLayout() {
+ return appImageLayout(params).resolveAt(
+ applicationInstallDir(sourceRoot()));
+ }
+
+ @Override
+ public ApplicationLayout installedApplicationLayout() {
+ return appImageLayout(params).resolveAt(
+ applicationInstallDir(Path.of("/")));
+ }
+
+ private Path applicationInstallDir(Path root) {
+ Path installDir = Path.of(LINUX_INSTALL_DIR.fetchFrom(params),
+ name());
+ if (installDir.isAbsolute()) {
+ installDir = Path.of("." + installDir.toString()).normalize();
+ }
+ return root.resolve(installDir);
+ }
+ };
+ }
+
+ private ApplicationLayout appImageLayout(
+ Map<String, ? super Object> params) {
+ if (StandardBundlerParam.isRuntimeInstaller(params)) {
+ return ApplicationLayout.javaRuntime();
+ }
+ return ApplicationLayout.linuxAppImage();
+ }
+
+ private static void validateInstallDir(String installDir) throws
+ ConfigException {
+ if (installDir.startsWith("/usr/") || installDir.equals("/usr")) {
+ throw new ConfigException(MessageFormat.format(I18N.getString(
+ "error.unsupported-install-dir"), installDir), null);
+ }
+
+ if (installDir.isEmpty()) {
+ throw new ConfigException(MessageFormat.format(I18N.getString(
+ "error.invalid-install-dir"), "/"), null);
+ }
+
+ boolean valid = false;
+ try {
+ final Path installDirPath = Path.of(installDir);
+ valid = installDirPath.isAbsolute();
+ if (valid && !installDirPath.normalize().toString().equals(
+ installDirPath.toString())) {
+ // Don't allow '/opt/foo/..' or /opt/.
+ valid = false;
+ }
+ } catch (InvalidPathException ex) {
+ }
+
+ if (!valid) {
+ throw new ConfigException(MessageFormat.format(I18N.getString(
+ "error.invalid-install-dir"), installDir), null);
+ }
+ }
+
+ private static void validateFileAssociations(
+ List<Map<String, ? super Object>> associations) throws
+ ConfigException {
+ // only one mime type per association, at least one file extention
+ int assocIdx = 0;
+ for (var assoc : associations) {
+ ++assocIdx;
+ List<String> mimes = FA_CONTENT_TYPE.fetchFrom(assoc);
+ if (mimes == null || mimes.isEmpty()) {
+ String msgKey = "error.no-content-types-for-file-association";
+ throw new ConfigException(
+ MessageFormat.format(I18N.getString(msgKey), assocIdx),
+ I18N.getString(msgKey + ".advise"));
+
+ }
+
+ if (mimes.size() > 1) {
+ String msgKey = "error.too-many-content-types-for-file-association";
+ throw new ConfigException(
+ MessageFormat.format(I18N.getString(msgKey), assocIdx),
+ I18N.getString(msgKey + ".advise"));
+ }
+ }
+ }
+
+ private final BundlerParamInfo<String> packageName;
+ private boolean withFindNeededPackages;
+ private DesktopIntegration desktopIntegration;
+
+ private static final BundlerParamInfo<LinuxAppBundler> APP_BUNDLER =
+ new StandardBundlerParam<>(
+ "linux.app.bundler",
+ LinuxAppBundler.class,
+ (params) -> new LinuxAppBundler(),
+ null
+ );
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/LinuxRpmBundler.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,323 @@
+/*
+ * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.*;
+import java.nio.file.Path;
+import java.text.MessageFormat;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+import static jdk.incubator.jpackage.internal.LinuxAppBundler.LINUX_INSTALL_DIR;
+import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
+
+/**
+ * There are two command line options to configure license information for RPM
+ * packaging: --linux-rpm-license-type and --license-file. Value of
+ * --linux-rpm-license-type command line option configures "License:" section
+ * of RPM spec. Value of --license-file command line option specifies a license
+ * file to be added to the package. License file is a sort of documentation file
+ * but it will be installed even if user selects an option to install the
+ * package without documentation. --linux-rpm-license-type is the primary option
+ * to set license information. --license-file makes little sense in case of RPM
+ * packaging.
+ */
+public class LinuxRpmBundler extends LinuxPackageBundler {
+
+ // Fedora rules for package naming are used here
+ // https://fedoraproject.org/wiki/Packaging:NamingGuidelines?rd=Packaging/NamingGuidelines
+ //
+ // all Fedora packages must be named using only the following ASCII
+ // characters. These characters are displayed here:
+ //
+ // abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._+
+ //
+ private static final Pattern RPM_PACKAGE_NAME_PATTERN =
+ Pattern.compile("[a-z\\d\\+\\-\\.\\_]+", Pattern.CASE_INSENSITIVE);
+
+ public static final BundlerParamInfo<String> PACKAGE_NAME =
+ new StandardBundlerParam<> (
+ Arguments.CLIOptions.LINUX_BUNDLE_NAME.getId(),
+ String.class,
+ params -> {
+ String nm = APP_NAME.fetchFrom(params);
+ if (nm == null) return null;
+
+ // make sure to lower case and spaces become dashes
+ nm = nm.toLowerCase().replaceAll("[ ]", "-");
+
+ return nm;
+ },
+ (s, p) -> {
+ if (!RPM_PACKAGE_NAME_PATTERN.matcher(s).matches()) {
+ String msgKey = "error.invalid-value-for-package-name";
+ throw new IllegalArgumentException(
+ new ConfigException(MessageFormat.format(
+ I18N.getString(msgKey), s),
+ I18N.getString(msgKey + ".advice")));
+ }
+
+ return s;
+ }
+ );
+
+ public static final BundlerParamInfo<String> LICENSE_TYPE =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.LINUX_RPM_LICENSE_TYPE.getId(),
+ String.class,
+ params -> I18N.getString("param.license-type.default"),
+ (s, p) -> s
+ );
+
+ public static final BundlerParamInfo<String> GROUP =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.LINUX_CATEGORY.getId(),
+ String.class,
+ params -> null,
+ (s, p) -> s);
+
+ private final static String DEFAULT_SPEC_TEMPLATE = "template.spec";
+
+ public final static String TOOL_RPM = "rpm";
+ public final static String TOOL_RPMBUILD = "rpmbuild";
+ public final static DottedVersion TOOL_RPMBUILD_MIN_VERSION = DottedVersion.lazy(
+ "4.0");
+
+ public LinuxRpmBundler() {
+ super(PACKAGE_NAME);
+ }
+
+ @Override
+ public void doValidate(Map<String, ? super Object> params)
+ throws ConfigException {
+ }
+
+ private static ToolValidator createRpmbuildToolValidator() {
+ Pattern pattern = Pattern.compile(" (\\d+\\.\\d+)");
+ return new ToolValidator(TOOL_RPMBUILD).setMinimalVersion(
+ TOOL_RPMBUILD_MIN_VERSION).setVersionParser(lines -> {
+ String versionString = lines.limit(1).collect(
+ Collectors.toList()).get(0);
+ Matcher matcher = pattern.matcher(versionString);
+ if (matcher.find()) {
+ return matcher.group(1);
+ }
+ return null;
+ });
+ }
+
+ @Override
+ protected List<ToolValidator> getToolValidators(
+ Map<String, ? super Object> params) {
+ return List.of(createRpmbuildToolValidator());
+ }
+
+ @Override
+ protected File buildPackageBundle(
+ Map<String, String> replacementData,
+ Map<String, ? super Object> params, File outputParentDir) throws
+ PackagerException, IOException {
+
+ Path specFile = specFile(params);
+
+ // prepare spec file
+ createResource(DEFAULT_SPEC_TEMPLATE, params)
+ .setCategory(I18N.getString("resource.rpm-spec-file"))
+ .setSubstitutionData(replacementData)
+ .saveToFile(specFile);
+
+ return buildRPM(params, outputParentDir.toPath()).toFile();
+ }
+
+ @Override
+ protected Map<String, String> createReplacementData(
+ Map<String, ? super Object> params) throws IOException {
+ Map<String, String> data = new HashMap<>();
+
+ data.put("APPLICATION_DIRECTORY", Path.of(LINUX_INSTALL_DIR.fetchFrom(
+ params), PACKAGE_NAME.fetchFrom(params)).toString());
+ data.put("APPLICATION_SUMMARY", APP_NAME.fetchFrom(params));
+ data.put("APPLICATION_LICENSE_TYPE", LICENSE_TYPE.fetchFrom(params));
+
+ String licenseFile = LICENSE_FILE.fetchFrom(params);
+ if (licenseFile != null) {
+ licenseFile = Path.of(licenseFile).toAbsolutePath().normalize().toString();
+ }
+ data.put("APPLICATION_LICENSE_FILE", licenseFile);
+ data.put("APPLICATION_GROUP", GROUP.fetchFrom(params));
+
+ return data;
+ }
+
+ @Override
+ protected void initLibProvidersLookup(
+ Map<String, ? super Object> params,
+ LibProvidersLookup libProvidersLookup) {
+ libProvidersLookup.setPackageLookup(file -> {
+ return Executor.of(TOOL_RPM,
+ "-q", "--queryformat", "%{name}\\n",
+ "-q", "--whatprovides", file.toString())
+ .saveOutput(true).executeExpectSuccess().getOutput().stream();
+ });
+ }
+
+ @Override
+ protected List<ConfigException> verifyOutputBundle(
+ Map<String, ? super Object> params, Path packageBundle) {
+ List<ConfigException> errors = new ArrayList<>();
+
+ String specFileName = specFile(params).getFileName().toString();
+
+ try {
+ List<PackageProperty> properties = List.of(
+ new PackageProperty("Name", PACKAGE_NAME.fetchFrom(params),
+ "APPLICATION_PACKAGE", specFileName),
+ new PackageProperty("Version", VERSION.fetchFrom(params),
+ "APPLICATION_VERSION", specFileName),
+ new PackageProperty("Release", RELEASE.fetchFrom(params),
+ "APPLICATION_RELEASE", specFileName),
+ new PackageProperty("Arch", rpmArch(), null, specFileName));
+
+ List<String> actualValues = Executor.of(TOOL_RPM, "-qp", "--queryformat",
+ properties.stream().map(entry -> String.format("%%{%s}",
+ entry.name)).collect(Collectors.joining("\\n")),
+ packageBundle.toString()).saveOutput(true).executeExpectSuccess().getOutput();
+
+ Iterator<String> actualValuesIt = actualValues.iterator();
+ properties.forEach(property -> errors.add(property.verifyValue(
+ actualValuesIt.next())));
+ } catch (IOException ex) {
+ // Ignore error as it is not critical. Just report it.
+ Log.verbose(ex);
+ }
+
+ return errors;
+ }
+
+ /**
+ * Various ways to get rpm arch. Needed to address JDK-8233143. rpmbuild is
+ * mandatory for rpm packaging, try it first. rpm is optional and may not be
+ * available, use as the last resort.
+ */
+ private enum RpmArchReader {
+ Rpmbuild(TOOL_RPMBUILD, "--eval=%{_target_cpu}"),
+ Rpm(TOOL_RPM, "--eval=%{_target_cpu}");
+
+ RpmArchReader(String... cmdline) {
+ this.cmdline = cmdline;
+ }
+
+ String getRpmArch() throws IOException {
+ Executor exec = Executor.of(cmdline).saveOutput(true);
+ if (this == values()[values().length - 1]) {
+ exec.executeExpectSuccess();
+ } else if (exec.execute() != 0) {
+ return null;
+ }
+
+ return exec.getOutput().get(0);
+ }
+
+ private final String[] cmdline;
+ }
+
+ private String rpmArch() throws IOException {
+ if (rpmArch == null) {
+ for (var rpmArchReader : RpmArchReader.values()) {
+ rpmArch = rpmArchReader.getRpmArch();
+ if (rpmArch != null) {
+ break;
+ }
+ }
+ }
+ return rpmArch;
+ }
+
+ private Path specFile(Map<String, ? super Object> params) {
+ return TEMP_ROOT.fetchFrom(params).toPath().resolve(Path.of("SPECS",
+ PACKAGE_NAME.fetchFrom(params) + ".spec"));
+ }
+
+ private Path buildRPM(Map<String, ? super Object> params,
+ Path outdir) throws IOException {
+
+ Path rpmFile = outdir.toAbsolutePath().resolve(String.format(
+ "%s-%s-%s.%s.rpm", PACKAGE_NAME.fetchFrom(params),
+ VERSION.fetchFrom(params), RELEASE.fetchFrom(params), rpmArch()));
+
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.outputting-bundle-location"),
+ rpmFile.getParent()));
+
+ PlatformPackage thePackage = createMetaPackage(params);
+
+ //run rpmbuild
+ Executor.of(
+ TOOL_RPMBUILD,
+ "-bb", specFile(params).toAbsolutePath().toString(),
+ "--define", String.format("%%_sourcedir %s",
+ thePackage.sourceRoot()),
+ // save result to output dir
+ "--define", String.format("%%_rpmdir %s", rpmFile.getParent()),
+ // do not use other system directories to build as current user
+ "--define", String.format("%%_topdir %s",
+ TEMP_ROOT.fetchFrom(params).toPath().toAbsolutePath()),
+ "--define", String.format("%%_rpmfilename %s", rpmFile.getFileName())
+ ).executeExpectSuccess();
+
+ Log.verbose(MessageFormat.format(
+ I18N.getString("message.output-bundle-location"),
+ rpmFile.getParent()));
+
+ return rpmFile;
+ }
+
+ @Override
+ public String getName() {
+ return I18N.getString("rpm.bundler.name");
+ }
+
+ @Override
+ public String getID() {
+ return "rpm";
+ }
+
+ @Override
+ public boolean supported(boolean runtimeInstaller) {
+ return Platform.isLinux() && (createRpmbuildToolValidator().validate() == null);
+ }
+
+ @Override
+ public boolean isDefault() {
+ return !LinuxDebBundler.isDebian();
+ }
+
+ private String rpmArch;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/PackageProperty.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.text.MessageFormat;
+
+final class PackageProperty {
+ /**
+ * Constructor
+ *
+ * @param name property name
+ * @param expectedValue expected property value
+ * @param substString substitution string to be placed in resource file to
+ * be replaced with the expected property value by jpackage at package build
+ * time
+ * @param customResource name of custom resource from resource directory in
+ * which this package property can be set
+ */
+ PackageProperty(String name, String expectedValue, String substString,
+ String customResource) {
+ this.name = name;
+ this.expectedValue = expectedValue;
+ this.substString = substString;
+ this.customResource = customResource;
+ }
+
+ ConfigException verifyValue(String actualValue) {
+ if (expectedValue.equals(actualValue)) {
+ return null;
+ }
+
+ final String advice;
+ if (substString != null) {
+ advice = MessageFormat.format(I18N.getString(
+ "error.unexpected-package-property.advice"), substString,
+ actualValue, name, customResource);
+ } else {
+ advice = MessageFormat.format(I18N.getString(
+ "error.unexpected-default-package-property.advice"), name,
+ customResource);
+ }
+
+ return new ConfigException(MessageFormat.format(I18N.getString(
+ "error.unexpected-package-property"), name,
+ expectedValue, actualValue, customResource, substString), advice);
+ }
+
+ final String name;
+ private final String expectedValue;
+ private final String substString;
+ private final String customResource;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/LinuxResources.properties Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,69 @@
+#
+# Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+#
+app.bundler.name=Linux Application Image
+deb.bundler.name=DEB Bundle
+rpm.bundler.name=RPM Bundle
+
+param.license-type.default=Unknown
+param.menu-group.default=Unknown
+
+resource.deb-control-file=DEB control file
+resource.deb-preinstall-script=DEB preinstall script
+resource.deb-prerm-script=DEB prerm script
+resource.deb-postinstall-script=DEB postinstall script
+resource.deb-postrm-script=DEB postrm script
+resource.copyright-file=Copyright file
+resource.menu-shortcut-descriptor=Menu shortcut descriptor
+resource.menu-icon=menu icon
+resource.rpm-spec-file=RPM spec file
+
+error.tool-not-found.advice=Please install required packages
+error.tool-old-version.advice=Please install required packages
+
+error.invalid-install-dir=Invalid installation directory "{0}"
+error.unsupported-install-dir=Installing to system directory "{0}" is currently unsupported
+
+error.no-content-types-for-file-association=No MIME types were specified for File Association number {0}
+error.too-many-content-types-for-file-association=More than one MIME types was specified for File Association number {0}.
+error.invalid-value-for-package-name=Invalid value "{0}" for the bundle name.
+error.invalid-value-for-package-name.advice=Set the "linux-bundle-name" option to a valid Debian package name. Note that the package names must consist only of lower case letters (a-z), digits (0-9), plus (+) and minus (-) signs, and periods (.). They must be at least two characters long and must start with an alphanumeric character.
+
+message.icon-not-png=The specified icon "{0}" is not a PNG file and will not be used. The default icon will be used in it's place.
+message.test-for-tool=Test for [{0}]. Result: {1}
+message.outputting-to-location=Generating DEB for installer to: {0}.
+message.output-to-location=Package (.deb) saved to: {0}.
+message.debs-like-licenses=Debian packages should specify a license. The absence of a license will cause some linux distributions to complain about the quality of the application.
+message.outputting-bundle-location=Generating RPM for installer to: {0}.
+message.output-bundle-location=Package (.rpm) saved to: {0}.
+message.creating-association-with-null-extension=Creating association with null extension.
+
+message.ldd-not-available=ldd command not found. Package dependencies will not be generated.
+message.deb-ldd-not-available.advice=Install "libc-bin" DEB package to get ldd.
+message.rpm-ldd-not-available.advice=Install "glibc-common" RPM package to get ldd.
+
+error.unexpected-package-property=Expected value of "{0}" property is [{1}]. Actual value in output package is [{2}]. Looks like custom "{3}" file from resource directory contained hard coded value of "{0}" property
+error.unexpected-package-property.advice=Use [{0}] pattern string instead of hard coded value [{1}] of {2} property in custom "{3}" file
+error.unexpected-default-package-property.advice=Don't explicitly set value of {0} property in custom "{1}" file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/LinuxResources_ja.properties Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,69 @@
+#
+# Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+#
+app.bundler.name=Linux Application Image
+deb.bundler.name=DEB Bundle
+rpm.bundler.name=RPM Bundle
+
+param.license-type.default=Unknown
+param.menu-group.default=Unknown
+
+resource.deb-control-file=DEB control file
+resource.deb-preinstall-script=DEB preinstall script
+resource.deb-prerm-script=DEB prerm script
+resource.deb-postinstall-script=DEB postinstall script
+resource.deb-postrm-script=DEB postrm script
+resource.copyright-file=Copyright file
+resource.menu-shortcut-descriptor=Menu shortcut descriptor
+resource.menu-icon=menu icon
+resource.rpm-spec-file=RPM spec file
+
+error.tool-not-found.advice=Please install required packages
+error.tool-old-version.advice=Please install required packages
+
+error.invalid-install-dir=Invalid installation directory "{0}"
+error.unsupported-install-dir=Installing to system directory "{0}" is currently unsupported
+
+error.no-content-types-for-file-association=No MIME types were specified for File Association number {0}
+error.too-many-content-types-for-file-association=More than one MIME types was specified for File Association number {0}.
+error.invalid-value-for-package-name=Invalid value "{0}" for the bundle name.
+error.invalid-value-for-package-name.advice=Set the "linux-bundle-name" option to a valid Debian package name. Note that the package names must consist only of lower case letters (a-z), digits (0-9), plus (+) and minus (-) signs, and periods (.). They must be at least two characters long and must start with an alphanumeric character.
+
+message.icon-not-png=The specified icon "{0}" is not a PNG file and will not be used. The default icon will be used in it's place.
+message.test-for-tool=Test for [{0}]. Result: {1}
+message.outputting-to-location=Generating DEB for installer to: {0}.
+message.output-to-location=Package (.deb) saved to: {0}.
+message.debs-like-licenses=Debian packages should specify a license. The absence of a license will cause some linux distributions to complain about the quality of the application.
+message.outputting-bundle-location=Generating RPM for installer to: {0}.
+message.output-bundle-location=Package (.rpm) saved to: {0}.
+message.creating-association-with-null-extension=Creating association with null extension.
+
+message.ldd-not-available=ldd command not found. Package dependencies will not be generated.
+message.deb-ldd-not-available.advice=Install "libc-bin" DEB package to get ldd.
+message.rpm-ldd-not-available.advice=Install "glibc-common" RPM package to get ldd.
+
+error.unexpected-package-property=Expected value of "{0}" property is [{1}]. Actual value in output package is [{2}]. Looks like custom "{3}" file from resource directory contained hard coded value of "{0}" property
+error.unexpected-package-property.advice=Use [{0}] pattern string instead of hard coded value [{1}] of {2} property in custom "{3}" file
+error.unexpected-default-package-property.advice=Don't explicitly set value of {0} property in custom "{1}" file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/LinuxResources_zh_CN.properties Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,69 @@
+#
+# Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+#
+app.bundler.name=Linux Application Image
+deb.bundler.name=DEB Bundle
+rpm.bundler.name=RPM Bundle
+
+param.license-type.default=Unknown
+param.menu-group.default=Unknown
+
+resource.deb-control-file=DEB control file
+resource.deb-preinstall-script=DEB preinstall script
+resource.deb-prerm-script=DEB prerm script
+resource.deb-postinstall-script=DEB postinstall script
+resource.deb-postrm-script=DEB postrm script
+resource.copyright-file=Copyright file
+resource.menu-shortcut-descriptor=Menu shortcut descriptor
+resource.menu-icon=menu icon
+resource.rpm-spec-file=RPM spec file
+
+error.tool-not-found.advice=Please install required packages
+error.tool-old-version.advice=Please install required packages
+
+error.invalid-install-dir=Invalid installation directory "{0}"
+error.unsupported-install-dir=Installing to system directory "{0}" is currently unsupported
+
+error.no-content-types-for-file-association=No MIME types were specified for File Association number {0}
+error.too-many-content-types-for-file-association=More than one MIME types was specified for File Association number {0}.
+error.invalid-value-for-package-name=Invalid value "{0}" for the bundle name.
+error.invalid-value-for-package-name.advice=Set the "linux-bundle-name" option to a valid Debian package name. Note that the package names must consist only of lower case letters (a-z), digits (0-9), plus (+) and minus (-) signs, and periods (.). They must be at least two characters long and must start with an alphanumeric character.
+
+message.icon-not-png=The specified icon "{0}" is not a PNG file and will not be used. The default icon will be used in it's place.
+message.test-for-tool=Test for [{0}]. Result: {1}
+message.outputting-to-location=Generating DEB for installer to: {0}.
+message.output-to-location=Package (.deb) saved to: {0}.
+message.debs-like-licenses=Debian packages should specify a license. The absence of a license will cause some linux distributions to complain about the quality of the application.
+message.outputting-bundle-location=Generating RPM for installer to: {0}.
+message.output-bundle-location=Package (.rpm) saved to: {0}.
+message.creating-association-with-null-extension=Creating association with null extension.
+
+message.ldd-not-available=ldd command not found. Package dependencies will not be generated.
+message.deb-ldd-not-available.advice=Install "libc-bin" DEB package to get ldd.
+message.rpm-ldd-not-available.advice=Install "glibc-common" RPM package to get ldd.
+
+error.unexpected-package-property=Expected value of "{0}" property is [{1}]. Actual value in output package is [{2}]. Looks like custom "{3}" file from resource directory contained hard coded value of "{0}" property
+error.unexpected-package-property.advice=Use [{0}] pattern string instead of hard coded value [{1}] of {2} property in custom "{3}" file
+error.unexpected-default-package-property.advice=Don't explicitly set value of {0} property in custom "{1}" file
Binary file src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/java32.png has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/template.control Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,10 @@
+Package: APPLICATION_PACKAGE
+Version: APPLICATION_VERSION-APPLICATION_RELEASE
+Section: APPLICATION_SECTION
+Maintainer: APPLICATION_MAINTAINER
+Priority: optional
+Architecture: APPLICATION_ARCH
+Provides: APPLICATION_PACKAGE
+Description: APPLICATION_DESCRIPTION
+Depends: PACKAGE_DEFAULT_DEPENDENCIES PACKAGE_CUSTOM_DEPENDENCIES
+Installed-Size: APPLICATION_INSTALLED_SIZE
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/template.copyright Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,5 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+
+Files: *
+Copyright: APPLICATION_COPYRIGHT
+License: APPLICATION_LICENSE_TEXT
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/template.desktop Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Name=APPLICATION_NAME
+Comment=APPLICATION_DESCRIPTION
+Exec=APPLICATION_LAUNCHER
+Icon=APPLICATION_ICON
+Terminal=false
+Type=Application
+Categories=DEPLOY_BUNDLE_CATEGORY
+DESKTOP_MIMES
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/template.postinst Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,34 @@
+#!/bin/sh
+# postinst script for APPLICATION_PACKAGE
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * <postinst> `configure' <most-recently-configured-version>
+# * <old-postinst> `abort-upgrade' <new version>
+# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
+# <new-version>
+# * <postinst> `abort-remove'
+# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
+# <failed-install-package> <version> `removing'
+# <conflicting-package> <version>
+# for details, see https://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+case "$1" in
+ configure)
+DESKTOP_COMMANDS_INSTALL
+ ;;
+
+ abort-upgrade|abort-remove|abort-deconfigure)
+ ;;
+
+ *)
+ echo "postinst called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+exit 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/template.postrm Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,31 @@
+#!/bin/sh
+# postrm script for APPLICATION_PACKAGE
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * <postrm> `remove'
+# * <postrm> `purge'
+# * <old-postrm> `upgrade' <new-version>
+# * <new-postrm> `failed-upgrade' <old-version>
+# * <new-postrm> `abort-install'
+# * <new-postrm> `abort-install' <old-version>
+# * <new-postrm> `abort-upgrade' <old-version>
+# * <disappearer's-postrm> `disappear' <overwriter>
+# <overwriter-version>
+# for details, see https://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+case "$1" in
+ purge|remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
+ ;;
+
+ *)
+ echo "postrm called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+exit 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/template.preinst Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,30 @@
+#!/bin/sh
+# preinst script for APPLICATION_PACKAGE
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * <new-preinst> `install'
+# * <new-preinst> `install' <old-version>
+# * <new-preinst> `upgrade' <old-version>
+# * <old-preinst> `abort-upgrade' <new-version>
+# for details, see https://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+ install|upgrade)
+ ;;
+
+ abort-upgrade)
+ ;;
+
+ *)
+ echo "preinst called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+exit 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/template.prerm Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,37 @@
+#!/bin/sh
+# prerm script for APPLICATION_PACKAGE
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * <prerm> `remove'
+# * <old-prerm> `upgrade' <new-version>
+# * <new-prerm> `failed-upgrade' <old-version>
+# * <conflictor's-prerm> `remove' `in-favour' <package> <new-version>
+# * <deconfigured's-prerm> `deconfigure' `in-favour'
+# <package-being-installed> <version> `removing'
+# <conflicting-package> <version>
+# for details, see https://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+UTILITY_SCRIPTS
+
+case "$1" in
+ remove|upgrade|deconfigure)
+DESKTOP_COMMANDS_UNINSTALL
+ ;;
+
+ failed-upgrade)
+ ;;
+
+ *)
+ echo "prerm called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+exit 0
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/template.spec Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,58 @@
+Summary: APPLICATION_SUMMARY
+Name: APPLICATION_PACKAGE
+Version: APPLICATION_VERSION
+Release: APPLICATION_RELEASE
+License: APPLICATION_LICENSE_TYPE
+Vendor: APPLICATION_VENDOR
+Prefix: %{dirname:APPLICATION_DIRECTORY}
+Provides: APPLICATION_PACKAGE
+%if "xAPPLICATION_GROUP" != x
+Group: APPLICATION_GROUP
+%endif
+
+Autoprov: 0
+Autoreq: 0
+%if "xPACKAGE_DEFAULT_DEPENDENCIES" != x || "xPACKAGE_CUSTOM_DEPENDENCIES" != x
+Requires: PACKAGE_DEFAULT_DEPENDENCIES PACKAGE_CUSTOM_DEPENDENCIES
+%endif
+
+#comment line below to enable effective jar compression
+#it could easily get your package size from 40 to 15Mb but
+#build time will substantially increase and it may require unpack200/system java to install
+%define __jar_repack %{nil}
+
+%description
+APPLICATION_DESCRIPTION
+
+%prep
+
+%build
+
+%install
+rm -rf %{buildroot}
+install -d -m 755 %{buildroot}APPLICATION_DIRECTORY
+cp -r %{_sourcedir}APPLICATION_DIRECTORY/* %{buildroot}APPLICATION_DIRECTORY
+%if "xAPPLICATION_LICENSE_FILE" != x
+ %define license_install_file %{_defaultlicensedir}/%{name}-%{version}/%{basename:APPLICATION_LICENSE_FILE}
+ install -d -m 755 %{buildroot}%{dirname:%{license_install_file}}
+ install -m 644 APPLICATION_LICENSE_FILE %{buildroot}%{license_install_file}
+%endif
+
+%files
+%if "xAPPLICATION_LICENSE_FILE" != x
+ %license %{license_install_file}
+ %{dirname:%{license_install_file}}
+%endif
+# If installation directory for the application is /a/b/c, we want only root
+# component of the path (/a) in the spec file to make sure all subdirectories
+# are owned by the package.
+%(echo APPLICATION_DIRECTORY | sed -e "s|\(^/[^/]\{1,\}\).*$|\1|")
+
+%post
+DESKTOP_COMMANDS_INSTALL
+
+%preun
+UTILITY_SCRIPTS
+DESKTOP_COMMANDS_UNINSTALL
+
+%clean
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/utils.sh Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,104 @@
+#
+# Remove $1 desktop file from the list of default handlers for $2 mime type
+# in $3 file dumping output to stdout.
+#
+_filter_out_default_mime_handler ()
+{
+ local defaults_list="$3"
+
+ local desktop_file="$1"
+ local mime_type="$2"
+
+ awk -f- "$defaults_list" <<EOF
+ BEGIN {
+ mime_type="$mime_type"
+ mime_type_regexp="~" mime_type "="
+ desktop_file="$desktop_file"
+ }
+ \$0 ~ mime_type {
+ \$0 = substr(\$0, length(mime_type) + 2);
+ split(\$0, desktop_files, ";")
+ remaining_desktop_files
+ counter=0
+ for (idx in desktop_files) {
+ if (desktop_files[idx] != desktop_file) {
+ ++counter;
+ }
+ }
+ if (counter) {
+ printf mime_type "="
+ for (idx in desktop_files) {
+ if (desktop_files[idx] != desktop_file) {
+ printf desktop_files[idx]
+ if (--counter) {
+ printf ";"
+ }
+ }
+ }
+ printf "\n"
+ }
+ next
+ }
+
+ { print }
+EOF
+}
+
+
+#
+# Remove $2 desktop file from the list of default handlers for $@ mime types
+# in $1 file.
+# Result is saved in $1 file.
+#
+_uninstall_default_mime_handler ()
+{
+ local defaults_list=$1
+ shift
+ [ -f "$defaults_list" ] || return 0
+
+ local desktop_file="$1"
+ shift
+
+ tmpfile1=$(mktemp)
+ tmpfile2=$(mktemp)
+ cat "$defaults_list" > "$tmpfile1"
+
+ local v
+ local update=
+ for mime in "$@"; do
+ _filter_out_default_mime_handler "$desktop_file" "$mime" "$tmpfile1" > "$tmpfile2"
+ v="$tmpfile2"
+ tmpfile2="$tmpfile1"
+ tmpfile1="$v"
+
+ if ! diff -q "$tmpfile1" "$tmpfile2" > /dev/null; then
+ update=yes
+ trace Remove $desktop_file default handler for $mime mime type from $defaults_list file
+ fi
+ done
+
+ if [ -n "$update" ]; then
+ cat "$tmpfile1" > "$defaults_list"
+ trace "$defaults_list" file updated
+ fi
+
+ rm -f "$tmpfile1" "$tmpfile2"
+}
+
+
+#
+# Remove $1 desktop file from the list of default handlers for $@ mime types
+# in all known system defaults lists.
+#
+uninstall_default_mime_handler ()
+{
+ for f in /usr/share/applications/defaults.list /usr/local/share/applications/defaults.list; do
+ _uninstall_default_mime_handler "$f" "$@"
+ done
+}
+
+
+trace ()
+{
+ echo "$@"
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/module-info.java.extra Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+provides jdk.incubator.jpackage.internal.Bundler with
+ jdk.incubator.jpackage.internal.LinuxAppBundler,
+ jdk.incubator.jpackage.internal.LinuxDebBundler,
+ jdk.incubator.jpackage.internal.LinuxRpmBundler;
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/native/jpackageapplauncher/launcher.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <dlfcn.h>
+#include <locale.h>
+#include <string>
+#include <libgen.h>
+#include <stdio.h>
+#include <unistd.h>
+
+
+typedef bool (*start_launcher)(int argc, char* argv[]);
+typedef void (*stop_launcher)();
+
+#define MAX_PATH 1024
+
+std::string GetProgramPath() {
+ ssize_t len = 0;
+ std::string result;
+ char buffer[MAX_PATH] = {0};
+
+ if ((len = readlink("/proc/self/exe", buffer, MAX_PATH - 1)) != -1) {
+ buffer[len] = '\0';
+ result = buffer;
+ }
+
+ return result;
+}
+
+int main(int argc, char *argv[]) {
+ int result = 1;
+ setlocale(LC_ALL, "en_US.utf8");
+ void* library = NULL;
+
+ {
+ std::string programPath = GetProgramPath();
+ std::string libraryName = dirname((char*)programPath.c_str());
+ libraryName += "/../lib/libapplauncher.so";
+ library = dlopen(libraryName.c_str(), RTLD_LAZY);
+
+ if (library == NULL) {
+ fprintf(stderr, "dlopen failed: %s\n", dlerror());
+ fprintf(stderr, "%s not found.\n", libraryName.c_str());
+ }
+ }
+
+ if (library != NULL) {
+ start_launcher start = (start_launcher)dlsym(library, "start_launcher");
+ stop_launcher stop = (stop_launcher)dlsym(library, "stop_launcher");
+
+ if (start != NULL && stop != NULL) {
+ if (start(argc, argv) == true) {
+ result = 0;
+ stop();
+ }
+ } else {
+ fprintf(stderr, "cannot find start_launcher and stop_launcher in libapplauncher.so");
+ }
+
+ dlclose(library);
+ }
+
+
+ return result;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/native/libapplauncher/LinuxPlatform.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,1080 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "Platform.h"
+
+#include "JavaVirtualMachine.h"
+#include "LinuxPlatform.h"
+#include "PlatformString.h"
+#include "IniFile.h"
+#include "Helpers.h"
+#include "FilePath.h"
+
+#include <stdlib.h>
+#include <pwd.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <limits.h>
+#include <signal.h>
+
+#define LINUX_JPACKAGE_TMP_DIR "/.java/jpackage/tmp"
+
+TString GetEnv(const TString &name) {
+ TString result;
+
+ char *value = ::getenv((TCHAR*) name.c_str());
+
+ if (value != NULL) {
+ result = value;
+ }
+
+ return result;
+}
+
+LinuxPlatform::LinuxPlatform(void) : Platform(),
+PosixPlatform() {
+ FMainThread = pthread_self();
+}
+
+LinuxPlatform::~LinuxPlatform(void) {
+}
+
+TString LinuxPlatform::GetPackageAppDirectory() {
+ return FilePath::IncludeTrailingSeparator(
+ GetPackageRootDirectory()) + _T("lib/app");
+}
+
+TString LinuxPlatform::GetAppName() {
+ TString result = GetModuleFileName();
+ result = FilePath::ExtractFileName(result);
+ return result;
+}
+
+TString LinuxPlatform::GetPackageLauncherDirectory() {
+ return FilePath::IncludeTrailingSeparator(
+ GetPackageRootDirectory()) + _T("bin");
+}
+
+TString LinuxPlatform::GetPackageRuntimeBinDirectory() {
+ return FilePath::IncludeTrailingSeparator(GetPackageRootDirectory())
+ + _T("runtime/bin");
+}
+
+void LinuxPlatform::ShowMessage(TString title, TString description) {
+ printf("%s %s\n", PlatformString(title).toPlatformString(),
+ PlatformString(description).toPlatformString());
+ fflush(stdout);
+}
+
+void LinuxPlatform::ShowMessage(TString description) {
+ TString appname = GetModuleFileName();
+ appname = FilePath::ExtractFileName(appname);
+ ShowMessage(PlatformString(appname).toPlatformString(),
+ PlatformString(description).toPlatformString());
+}
+
+TCHAR* LinuxPlatform::ConvertStringToFileSystemString(TCHAR* Source,
+ bool &release) {
+ // Not Implemented.
+ return NULL;
+}
+
+TCHAR* LinuxPlatform::ConvertFileSystemStringToString(TCHAR* Source,
+ bool &release) {
+ // Not Implemented.
+ return NULL;
+}
+
+TString LinuxPlatform::GetModuleFileName() {
+ ssize_t len = 0;
+ TString result;
+ DynamicBuffer<TCHAR> buffer(MAX_PATH);
+ if (buffer.GetData() == NULL) {
+ return result;
+ }
+
+ if ((len = readlink("/proc/self/exe", buffer.GetData(),
+ MAX_PATH - 1)) != -1) {
+ buffer[len] = '\0';
+ result = buffer.GetData();
+ }
+
+ return result;
+}
+
+TString LinuxPlatform::GetPackageRootDirectory() {
+ TString result;
+ TString filename = GetModuleFileName();
+ TString binPath = FilePath::ExtractFilePath(filename);
+
+ size_t slash = binPath.find_last_of(TRAILING_PATHSEPARATOR);
+ if (slash != TString::npos) {
+ result = binPath.substr(0, slash);
+ }
+
+ return result;
+}
+
+TString LinuxPlatform::GetAppDataDirectory() {
+ TString result;
+ TString home = GetEnv(_T("HOME"));
+
+ if (home.empty() == false) {
+ result += FilePath::IncludeTrailingSeparator(home) + _T(".local");
+ }
+
+ return result;
+}
+
+ISectionalPropertyContainer* LinuxPlatform::GetConfigFile(TString FileName) {
+ IniFile *result = new IniFile();
+ if (result == NULL) {
+ return NULL;
+ }
+
+ result->LoadFromFile(FileName);
+
+ return result;
+}
+
+TString LinuxPlatform::GetBundledJavaLibraryFileName(TString RuntimePath) {
+ TString result = FilePath::IncludeTrailingSeparator(RuntimePath) +
+ "lib/libjli.so";
+
+ if (FilePath::FileExists(result) == false) {
+ result = FilePath::IncludeTrailingSeparator(RuntimePath) +
+ "lib/jli/libjli.so";
+ if (FilePath::FileExists(result) == false) {
+ printf("Cannot find libjli.so!");
+ }
+ }
+
+ return result;
+}
+
+bool LinuxPlatform::IsMainThread() {
+ bool result = (FMainThread == pthread_self());
+ return result;
+}
+
+TString LinuxPlatform::getTmpDirString() {
+ return TString(LINUX_JPACKAGE_TMP_DIR);
+}
+
+TPlatformNumber LinuxPlatform::GetMemorySize() {
+ long pages = sysconf(_SC_PHYS_PAGES);
+ long page_size = sysconf(_SC_PAGE_SIZE);
+ TPlatformNumber result = pages * page_size;
+ result = result / 1048576; // Convert from bytes to megabytes.
+ return result;
+}
+
+void PosixProcess::Cleanup() {
+ if (FOutputHandle != 0) {
+ close(FOutputHandle);
+ FOutputHandle = 0;
+ }
+
+ if (FInputHandle != 0) {
+ close(FInputHandle);
+ FInputHandle = 0;
+ }
+}
+
+#define PIPE_READ 0
+#define PIPE_WRITE 1
+
+bool PosixProcess::Execute(const TString Application,
+ const std::vector<TString> Arguments, bool AWait) {
+ bool result = false;
+
+ if (FRunning == false) {
+ FRunning = true;
+
+ int handles[2];
+
+ if (pipe(handles) == -1) {
+ return false;
+ }
+
+ struct sigaction sa;
+ sa.sa_handler = SIG_IGN;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+
+ FChildPID = fork();
+
+ // PID returned by vfork is 0 for the child process and the
+ // PID of the child process for the parent.
+ if (FChildPID == -1) {
+ // Error
+ TString message = PlatformString::Format(
+ _T("Error: Unable to create process %s"),
+ Application.data());
+ throw Exception(message);
+ } else if (FChildPID == 0) {
+ Cleanup();
+ TString command = Application;
+
+ for (std::vector<TString>::const_iterator iterator =
+ Arguments.begin(); iterator != Arguments.end();
+ iterator++) {
+ command += TString(_T(" ")) + *iterator;
+ }
+#ifdef DEBUG
+ printf("%s\n", command.data());
+#endif // DEBUG
+
+ dup2(handles[PIPE_READ], STDIN_FILENO);
+ dup2(handles[PIPE_WRITE], STDOUT_FILENO);
+
+ close(handles[PIPE_READ]);
+ close(handles[PIPE_WRITE]);
+
+ execl("/bin/sh", "sh", "-c", command.data(), (char *) 0);
+
+ _exit(127);
+ } else {
+ FOutputHandle = handles[PIPE_READ];
+ FInputHandle = handles[PIPE_WRITE];
+
+ if (AWait == true) {
+ ReadOutput();
+ Wait();
+ Cleanup();
+ FRunning = false;
+ result = true;
+ } else {
+ result = true;
+ }
+ }
+ }
+
+ return result;
+}
+
+
+//----------------------------------------------------------------------------
+
+#ifndef __UNIX_JPACKAGE_PLATFORM__
+#define __UNIX_JPACKAGE_PLATFORM__
+
+/** Provide an abstraction for difference in the platform APIs,
+ e.g. string manipulation functions, etc. */
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/stat.h>
+
+#define TCHAR char
+
+#define _T(x) x
+
+#define JPACKAGE_MULTIBYTE_SNPRINTF snprintf
+
+#define JPACKAGE_SNPRINTF(buffer, sizeOfBuffer, count, format, ...) \
+ snprintf((buffer), (count), (format), __VA_ARGS__)
+
+#define JPACKAGE_PRINTF(format, ...) \
+ printf((format), ##__VA_ARGS__)
+
+#define JPACKAGE_FPRINTF(dest, format, ...) \
+ fprintf((dest), (format), __VA_ARGS__)
+
+#define JPACKAGE_SSCANF(buf, format, ...) \
+ sscanf((buf), (format), __VA_ARGS__)
+
+#define JPACKAGE_STRDUP(strSource) \
+ strdup((strSource))
+
+//return "error code" (like on Windows)
+
+static int JPACKAGE_STRNCPY(char *strDest, size_t numberOfElements,
+ const char *strSource, size_t count) {
+ char *s = strncpy(strDest, strSource, count);
+ // Duplicate behavior of the Windows' _tcsncpy_s() by adding a NULL
+ // terminator at the end of the string.
+ if (count < numberOfElements) {
+ s[count] = '\0';
+ } else {
+ s[numberOfElements - 1] = '\0';
+ }
+ return (s == strDest) ? 0 : 1;
+}
+
+#define JPACKAGE_STRICMP(x, y) \
+ strcasecmp((x), (y))
+
+#define JPACKAGE_STRNICMP(x, y, cnt) \
+ strncasecmp((x), (y), (cnt))
+
+#define JPACKAGE_STRNCMP(x, y, cnt) \
+ strncmp((x), (y), (cnt))
+
+#define JPACKAGE_STRLEN(x) \
+ strlen((x))
+
+#define JPACKAGE_STRSTR(x, y) \
+ strstr((x), (y))
+
+#define JPACKAGE_STRCHR(x, y) \
+ strchr((x), (y))
+
+#define JPACKAGE_STRRCHR(x, y) \
+ strrchr((x), (y))
+
+#define JPACKAGE_STRPBRK(x, y) \
+ strpbrk((x), (y))
+
+#define JPACKAGE_GETENV(x) \
+ getenv((x))
+
+#define JPACKAGE_PUTENV(x) \
+ putenv((x))
+
+#define JPACKAGE_STRCMP(x, y) \
+ strcmp((x), (y))
+
+#define JPACKAGE_STRCPY(x, y) \
+ strcpy((x), (y))
+
+#define JPACKAGE_STRCAT(x, y) \
+ strcat((x), (y))
+
+#define JPACKAGE_ATOI(x) \
+ atoi((x))
+
+#define JPACKAGE_FOPEN(x, y) \
+ fopen((x), (y))
+
+#define JPACKAGE_FGETS(x, y, z) \
+ fgets((x), (y), (z))
+
+#define JPACKAGE_REMOVE(x) \
+ remove((x))
+
+#define JPACKAGE_SPAWNV(mode, cmd, args) \
+ spawnv((mode), (cmd), (args))
+
+#define JPACKAGE_ISDIGIT(ch) isdigit(ch)
+
+// for non-unicode, just return the input string for
+// the following 2 conversions
+#define JPACKAGE_NEW_MULTIBYTE(message) message
+
+#define JPACKAGE_NEW_FROM_MULTIBYTE(message) message
+
+// for non-unicode, no-op for the relase operation
+// since there is no memory allocated for the
+// string conversions
+#define JPACKAGE_RELEASE_MULTIBYTE(tmpMBCS)
+
+#define JPACKAGE_RELEASE_FROM_MULTIBYTE(tmpMBCS)
+
+// The size will be used for converting from 1 byte to 1 byte encoding.
+// Ensure have space for zero-terminator.
+#define JPACKAGE_GET_SIZE_FOR_ENCODING(message, theLength) (theLength + 1)
+
+#endif
+#define xmlTagType 0
+#define xmlPCDataType 1
+
+typedef struct _xmlNode XMLNode;
+typedef struct _xmlAttribute XMLAttribute;
+
+struct _xmlNode {
+ int _type; // Type of node: tag, pcdata, cdate
+ TCHAR* _name; // Contents of node
+ XMLNode* _next; // Next node at same level
+ XMLNode* _sub; // First sub-node
+ XMLAttribute* _attributes; // List of attributes
+};
+
+struct _xmlAttribute {
+ TCHAR* _name; // Name of attribute
+ TCHAR* _value; // Value of attribute
+ XMLAttribute* _next; // Next attribute for this tag
+};
+
+// Public interface
+static void RemoveNonAsciiUTF8FromBuffer(char *buf);
+XMLNode* ParseXMLDocument(TCHAR* buf);
+void FreeXMLDocument(XMLNode* root);
+
+// Utility methods for parsing document
+XMLNode* FindXMLChild(XMLNode* root, const TCHAR* name);
+TCHAR* FindXMLAttribute(XMLAttribute* attr, const TCHAR* name);
+
+// Debugging
+void PrintXMLDocument(XMLNode* node, int indt);
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <wctype.h>
+
+#define JWS_assert(s, msg) \
+ if (!(s)) { Abort(msg); }
+
+
+// Internal declarations
+static XMLNode* ParseXMLElement(void);
+static XMLAttribute* ParseXMLAttribute(void);
+static TCHAR* SkipWhiteSpace(TCHAR *p);
+static TCHAR* SkipXMLName(TCHAR *p);
+static TCHAR* SkipXMLComment(TCHAR *p);
+static TCHAR* SkipXMLDocType(TCHAR *p);
+static TCHAR* SkipXMLProlog(TCHAR *p);
+static TCHAR* SkipPCData(TCHAR *p);
+static int IsPCData(TCHAR *p);
+static void ConvertBuiltInEntities(TCHAR* p);
+static void SetToken(int type, TCHAR* start, TCHAR* end);
+static void GetNextToken(void);
+static XMLNode* CreateXMLNode(int type, TCHAR* name);
+static XMLAttribute* CreateXMLAttribute(TCHAR *name, TCHAR* value);
+static XMLNode* ParseXMLElement(void);
+static XMLAttribute* ParseXMLAttribute(void);
+static void FreeXMLAttribute(XMLAttribute* attr);
+static void PrintXMLAttributes(XMLAttribute* attr);
+static void indent(int indt);
+
+static jmp_buf jmpbuf;
+static XMLNode* root_node = NULL;
+
+/** definition of error codes for setjmp/longjmp,
+ * that can be handled in ParseXMLDocument()
+ */
+#define JMP_NO_ERROR 0
+#define JMP_OUT_OF_RANGE 1
+
+#define NEXT_CHAR(p) { \
+ if (*p != 0) { \
+ p++; \
+ } else { \
+ longjmp(jmpbuf, JMP_OUT_OF_RANGE); \
+ } \
+}
+#define NEXT_CHAR_OR_BREAK(p) { \
+ if (*p != 0) { \
+ p++; \
+ } else { \
+ break; \
+ } \
+}
+#define NEXT_CHAR_OR_RETURN(p) { \
+ if (*p != 0) { \
+ p++; \
+ } else { \
+ return; \
+ } \
+}
+#define SKIP_CHARS(p,n) { \
+ int i; \
+ for (i = 0; i < (n); i++) { \
+ if (*p != 0) { \
+ p++; \
+ } else { \
+ longjmp(jmpbuf, JMP_OUT_OF_RANGE); \
+ } \
+ } \
+}
+#define SKIP_CHARS_OR_BREAK(p,n) { \
+ int i; \
+ for (i = 0; i < (n); i++) { \
+ if (*p != 0) { \
+ p++; \
+ } else { \
+ break; \
+ } \
+ } \
+ if (i < (n)) { \
+ break; \
+ } \
+}
+
+/** Iterates through the null-terminated buffer (i.e., C string) and
+ * replaces all UTF-8 encoded character >255 with 255
+ *
+ * UTF-8 encoding:
+ *
+ * Range A: 0x0000 - 0x007F
+ * 0 | bits 0 - 7
+ * Range B : 0x0080 - 0x07FF :
+ * 110 | bits 6 - 10
+ * 10 | bits 0 - 5
+ * Range C : 0x0800 - 0xFFFF :
+ * 1110 | bits 12-15
+ * 10 | bits 6-11
+ * 10 | bits 0-5
+ */
+static void RemoveNonAsciiUTF8FromBuffer(char *buf) {
+ char* p;
+ char* q;
+ char c;
+ p = q = buf;
+ // We are not using NEXT_CHAR() to check if *q is NULL, as q is output
+ // location and offset for q is smaller than for p.
+ while (*p != '\0') {
+ c = *p;
+ if ((c & 0x80) == 0) {
+ /* Range A */
+ *q++ = *p;
+ NEXT_CHAR(p);
+ } else if ((c & 0xE0) == 0xC0) {
+ /* Range B */
+ *q++ = (char) 0xFF;
+ NEXT_CHAR(p);
+ NEXT_CHAR_OR_BREAK(p);
+ } else {
+ /* Range C */
+ *q++ = (char) 0xFF;
+ NEXT_CHAR(p);
+ SKIP_CHARS_OR_BREAK(p, 2);
+ }
+ }
+ /* Null terminate string */
+ *q = '\0';
+}
+
+static TCHAR* SkipWhiteSpace(TCHAR *p) {
+ if (p != NULL) {
+ while (iswspace(*p))
+ NEXT_CHAR_OR_BREAK(p);
+ }
+ return p;
+}
+
+static TCHAR* SkipXMLName(TCHAR *p) {
+ TCHAR c = *p;
+ /* Check if start of token */
+ if (('a' <= c && c <= 'z') ||
+ ('A' <= c && c <= 'Z') ||
+ c == '_' || c == ':') {
+
+ while (('a' <= c && c <= 'z') ||
+ ('A' <= c && c <= 'Z') ||
+ ('0' <= c && c <= '9') ||
+ c == '_' || c == ':' || c == '.' || c == '-') {
+ NEXT_CHAR(p);
+ c = *p;
+ if (c == '\0') break;
+ }
+ }
+ return p;
+}
+
+static TCHAR* SkipXMLComment(TCHAR *p) {
+ if (p != NULL) {
+ if (JPACKAGE_STRNCMP(p, _T("<!--"), 4) == 0) {
+ SKIP_CHARS(p, 4);
+ do {
+ if (JPACKAGE_STRNCMP(p, _T("-->"), 3) == 0) {
+ SKIP_CHARS(p, 3);
+ return p;
+ }
+ NEXT_CHAR(p);
+ } while (*p != '\0');
+ }
+ }
+ return p;
+}
+
+static TCHAR* SkipXMLDocType(TCHAR *p) {
+ if (p != NULL) {
+ if (JPACKAGE_STRNCMP(p, _T("<!"), 2) == 0) {
+ SKIP_CHARS(p, 2);
+ while (*p != '\0') {
+ if (*p == '>') {
+ NEXT_CHAR(p);
+ return p;
+ }
+ NEXT_CHAR(p);
+ }
+ }
+ }
+ return p;
+}
+
+static TCHAR* SkipXMLProlog(TCHAR *p) {
+ if (p != NULL) {
+ if (JPACKAGE_STRNCMP(p, _T("<?"), 2) == 0) {
+ SKIP_CHARS(p, 2);
+ do {
+ if (JPACKAGE_STRNCMP(p, _T("?>"), 2) == 0) {
+ SKIP_CHARS(p, 2);
+ return p;
+ }
+ NEXT_CHAR(p);
+ } while (*p != '\0');
+ }
+ }
+ return p;
+}
+
+/* Search for the built-in XML entities:
+ * & (&), < (<), > (>), ' ('), and "e(")
+ * and convert them to a real TCHARacter
+ */
+static void ConvertBuiltInEntities(TCHAR* p) {
+ TCHAR* q;
+ q = p;
+ // We are not using NEXT_CHAR() to check if *q is NULL,
+ // as q is output location and offset for q is smaller than for p.
+ while (*p) {
+ if (IsPCData(p)) {
+ /* dont convert &xxx values within PData */
+ TCHAR *end;
+ end = SkipPCData(p);
+ while (p < end) {
+ *q++ = *p;
+ NEXT_CHAR(p);
+ }
+ } else {
+ if (JPACKAGE_STRNCMP(p, _T("&"), 5) == 0) {
+ *q++ = '&';
+ SKIP_CHARS(p, 5);
+ } else if (JPACKAGE_STRNCMP(p, _T("<"), 4) == 0) {
+ *q = '<';
+ SKIP_CHARS(p, 4);
+ } else if (JPACKAGE_STRNCMP(p, _T(">"), 4) == 0) {
+ *q = '>';
+ SKIP_CHARS(p, 4);
+ } else if (JPACKAGE_STRNCMP(p, _T("'"), 6) == 0) {
+ *q = '\'';
+ SKIP_CHARS(p, 6);
+ } else if (JPACKAGE_STRNCMP(p, _T(""e;"), 7) == 0) {
+ *q = '\"';
+ SKIP_CHARS(p, 7);
+ } else {
+ *q++ = *p;
+ NEXT_CHAR(p);
+ }
+ }
+ }
+ *q = '\0';
+}
+
+/* ------------------------------------------------------------- */
+/* XML tokenizer */
+
+#define TOKEN_UNKNOWN 0
+#define TOKEN_BEGIN_TAG 1 /* <tag */
+#define TOKEN_END_TAG 2 /* </tag */
+#define TOKEN_CLOSE_BRACKET 3 /* > */
+#define TOKEN_EMPTY_CLOSE_BRACKET 4 /* /> */
+#define TOKEN_PCDATA 5 /* pcdata */
+#define TOKEN_CDATA 6 /* cdata */
+#define TOKEN_EOF 7
+
+static TCHAR* CurPos = NULL;
+static TCHAR* CurTokenName = NULL;
+static int CurTokenType;
+static int MaxTokenSize = -1;
+
+/* Copy token from buffer to Token variable */
+static void SetToken(int type, TCHAR* start, TCHAR* end) {
+ int len = end - start;
+ if (len > MaxTokenSize) {
+ if (CurTokenName != NULL) free(CurTokenName);
+ CurTokenName = (TCHAR *) malloc((len + 1) * sizeof (TCHAR));
+ if (CurTokenName == NULL) {
+ return;
+ }
+ MaxTokenSize = len;
+ }
+
+ CurTokenType = type;
+ JPACKAGE_STRNCPY(CurTokenName, len + 1, start, len);
+ CurTokenName[len] = '\0';
+}
+
+/* Skip XML comments, doctypes, and prolog tags */
+static TCHAR* SkipFilling(void) {
+ TCHAR *q = CurPos;
+
+ /* Skip white space and comment sections */
+ do {
+ q = CurPos;
+ CurPos = SkipWhiteSpace(CurPos);
+ CurPos = SkipXMLComment(CurPos); /* Must be called befor DocTypes */
+ CurPos = SkipXMLDocType(CurPos); /* <! ... > directives */
+ CurPos = SkipXMLProlog(CurPos); /* <? ... ?> directives */
+ } while (CurPos != q);
+
+ return CurPos;
+}
+
+/* Parses next token and initializes the global token variables above
+ The tokennizer automatically skips comments (<!-- comment -->) and
+ <! ... > directives.
+ */
+static void GetNextToken(void) {
+ TCHAR *p, *q;
+
+ /* Skip white space and comment sections */
+ p = SkipFilling();
+
+ if (p == NULL || *p == '\0') {
+ CurTokenType = TOKEN_EOF;
+ return;
+ } else if (p[0] == '<' && p[1] == '/') {
+ /* TOKEN_END_TAG */
+ q = SkipXMLName(p + 2);
+ SetToken(TOKEN_END_TAG, p + 2, q);
+ p = q;
+ } else if (*p == '<') {
+ /* TOKEN_BEGIN_TAG */
+ q = SkipXMLName(p + 1);
+ SetToken(TOKEN_BEGIN_TAG, p + 1, q);
+ p = q;
+ } else if (p[0] == '>') {
+ CurTokenType = TOKEN_CLOSE_BRACKET;
+ NEXT_CHAR(p);
+ } else if (p[0] == '/' && p[1] == '>') {
+ CurTokenType = TOKEN_EMPTY_CLOSE_BRACKET;
+ SKIP_CHARS(p, 2);
+ } else {
+ /* Search for end of data */
+ q = p + 1;
+ while (*q && *q != '<') {
+ if (IsPCData(q)) {
+ q = SkipPCData(q);
+ } else {
+ NEXT_CHAR(q);
+ }
+ }
+ SetToken(TOKEN_PCDATA, p, q);
+ /* Convert all entities inside token */
+ ConvertBuiltInEntities(CurTokenName);
+ p = q;
+ }
+ /* Advance pointer to beginning of next token */
+ CurPos = p;
+}
+
+static XMLNode* CreateXMLNode(int type, TCHAR* name) {
+ XMLNode* node;
+ node = (XMLNode*) malloc(sizeof (XMLNode));
+ if (node == NULL) {
+ return NULL;
+ }
+ node->_type = type;
+ node->_name = name;
+ node->_next = NULL;
+ node->_sub = NULL;
+ node->_attributes = NULL;
+ return node;
+}
+
+static XMLAttribute* CreateXMLAttribute(TCHAR *name, TCHAR* value) {
+ XMLAttribute* attr;
+ attr = (XMLAttribute*) malloc(sizeof (XMLAttribute));
+ if (attr == NULL) {
+ return NULL;
+ }
+ attr->_name = name;
+ attr->_value = value;
+ attr->_next = NULL;
+ return attr;
+}
+
+XMLNode* ParseXMLDocument(TCHAR* buf) {
+ XMLNode* root;
+ int err_code = setjmp(jmpbuf);
+ switch (err_code) {
+ case JMP_NO_ERROR:
+#ifndef _UNICODE
+ /* Remove UTF-8 encoding from buffer */
+ RemoveNonAsciiUTF8FromBuffer(buf);
+#endif
+
+ /* Get first Token */
+ CurPos = buf;
+ GetNextToken();
+
+ /* Parse document*/
+ root = ParseXMLElement();
+ break;
+ case JMP_OUT_OF_RANGE:
+ /* cleanup: */
+ if (root_node != NULL) {
+ FreeXMLDocument(root_node);
+ root_node = NULL;
+ }
+ if (CurTokenName != NULL) free(CurTokenName);
+ fprintf(stderr, "Error during parsing jnlp file...\n");
+ exit(-1);
+ break;
+ default:
+ root = NULL;
+ break;
+ }
+
+ return root;
+}
+
+static XMLNode* ParseXMLElement(void) {
+ XMLNode* node = NULL;
+ XMLNode* subnode = NULL;
+ XMLNode* nextnode = NULL;
+ XMLAttribute* attr = NULL;
+
+ if (CurTokenType == TOKEN_BEGIN_TAG) {
+
+ /* Create node for new element tag */
+ node = CreateXMLNode(xmlTagType, JPACKAGE_STRDUP(CurTokenName));
+ /* We need to save root node pointer to be able to cleanup
+ if an error happens during parsing */
+ if (!root_node) {
+ root_node = node;
+ }
+ /* Parse attributes. This section eats a all input until
+ EOF, a > or a /> */
+ attr = ParseXMLAttribute();
+ while (attr != NULL) {
+ attr->_next = node->_attributes;
+ node->_attributes = attr;
+ attr = ParseXMLAttribute();
+ }
+
+ /* This will eihter be a TOKEN_EOF, TOKEN_CLOSE_BRACKET, or a
+ * TOKEN_EMPTY_CLOSE_BRACKET */
+ GetNextToken();
+
+ if (CurTokenType == TOKEN_EMPTY_CLOSE_BRACKET) {
+ GetNextToken();
+ /* We are done with the sublevel - fall through to continue */
+ /* parsing tags at the same level */
+ } else if (CurTokenType == TOKEN_CLOSE_BRACKET) {
+ GetNextToken();
+
+ /* Parse until end tag if found */
+ node->_sub = ParseXMLElement();
+
+ if (CurTokenType == TOKEN_END_TAG) {
+ /* Find closing bracket '>' for end tag */
+ do {
+ GetNextToken();
+ } while (CurTokenType != TOKEN_EOF &&
+ CurTokenType != TOKEN_CLOSE_BRACKET);
+ GetNextToken();
+ }
+ }
+
+ /* Continue parsing rest on same level */
+ if (CurTokenType != TOKEN_EOF) {
+ /* Parse rest of stream at same level */
+ node->_next = ParseXMLElement();
+ }
+ return node;
+
+ } else if (CurTokenType == TOKEN_PCDATA) {
+ /* Create node for pcdata */
+ node = CreateXMLNode(xmlPCDataType, JPACKAGE_STRDUP(CurTokenName));
+ /* We need to save root node pointer to be able to cleanup
+ if an error happens during parsing */
+ if (!root_node) {
+ root_node = node;
+ }
+ GetNextToken();
+ return node;
+ }
+
+ /* Something went wrong. */
+ return NULL;
+}
+
+/* Parses an XML attribute. */
+static XMLAttribute* ParseXMLAttribute(void) {
+ TCHAR* q = NULL;
+ TCHAR* name = NULL;
+ TCHAR* PrevPos = NULL;
+
+ do {
+ /* We need to check this condition to avoid endless loop
+ in case if an error happend during parsing. */
+ if (PrevPos == CurPos) {
+ if (name != NULL) {
+ free(name);
+ name = NULL;
+ }
+
+ return NULL;
+ }
+
+ PrevPos = CurPos;
+
+ /* Skip whitespace etc. */
+ SkipFilling();
+
+ /* Check if we are done witht this attribute section */
+ if (CurPos[0] == '\0' ||
+ CurPos[0] == '>' ||
+ (CurPos[0] == '/' && CurPos[1] == '>')) {
+
+ if (name != NULL) {
+ free(name);
+ name = NULL;
+ }
+
+ return NULL;
+ }
+
+ /* Find end of name */
+ q = CurPos;
+ while (*q && !iswspace(*q) && *q != '=') NEXT_CHAR(q);
+
+ SetToken(TOKEN_UNKNOWN, CurPos, q);
+ if (name) {
+ free(name);
+ name = NULL;
+ }
+ name = JPACKAGE_STRDUP(CurTokenName);
+
+ /* Skip any whitespace */
+ CurPos = q;
+ CurPos = SkipFilling();
+
+ /* Next TCHARacter must be '=' for a valid attribute.
+ If it is not, this is really an error.
+ We ignore this, and just try to parse an attribute
+ out of the rest of the string.
+ */
+ } while (*CurPos != '=');
+
+ NEXT_CHAR(CurPos);
+ CurPos = SkipWhiteSpace(CurPos);
+ /* Parse CDATA part of attribute */
+ if ((*CurPos == '\"') || (*CurPos == '\'')) {
+ TCHAR quoteChar = *CurPos;
+ q = ++CurPos;
+ while (*q != '\0' && *q != quoteChar) NEXT_CHAR(q);
+ SetToken(TOKEN_CDATA, CurPos, q);
+ CurPos = q + 1;
+ } else {
+ q = CurPos;
+ while (*q != '\0' && !iswspace(*q)) NEXT_CHAR(q);
+ SetToken(TOKEN_CDATA, CurPos, q);
+ CurPos = q;
+ }
+
+ //Note: no need to free name and CurTokenName duplicate; they're assigned
+ // to an XMLAttribute structure in CreateXMLAttribute
+
+ return CreateXMLAttribute(name, JPACKAGE_STRDUP(CurTokenName));
+}
+
+void FreeXMLDocument(XMLNode* root) {
+ if (root == NULL) return;
+ FreeXMLDocument(root->_sub);
+ FreeXMLDocument(root->_next);
+ FreeXMLAttribute(root->_attributes);
+ free(root->_name);
+ free(root);
+}
+
+static void FreeXMLAttribute(XMLAttribute* attr) {
+ if (attr == NULL) return;
+ free(attr->_name);
+ free(attr->_value);
+ FreeXMLAttribute(attr->_next);
+ free(attr);
+}
+
+/* Find element at current level with a given name */
+XMLNode* FindXMLChild(XMLNode* root, const TCHAR* name) {
+ if (root == NULL) return NULL;
+
+ if (root->_type == xmlTagType && JPACKAGE_STRCMP(root->_name, name) == 0) {
+ return root;
+ }
+
+ return FindXMLChild(root->_next, name);
+}
+
+/* Search for an attribute with the given name and returns the contents.
+ * Returns NULL if attribute is not found
+ */
+TCHAR* FindXMLAttribute(XMLAttribute* attr, const TCHAR* name) {
+ if (attr == NULL) return NULL;
+ if (JPACKAGE_STRCMP(attr->_name, name) == 0) return attr->_value;
+ return FindXMLAttribute(attr->_next, name);
+}
+
+void PrintXMLDocument(XMLNode* node, int indt) {
+ if (node == NULL) return;
+
+ if (node->_type == xmlTagType) {
+ JPACKAGE_PRINTF(_T("\n"));
+ indent(indt);
+ JPACKAGE_PRINTF(_T("<%s"), node->_name);
+ PrintXMLAttributes(node->_attributes);
+ if (node->_sub == NULL) {
+ JPACKAGE_PRINTF(_T("/>\n"));
+ } else {
+ JPACKAGE_PRINTF(_T(">"));
+ PrintXMLDocument(node->_sub, indt + 1);
+ indent(indt);
+ JPACKAGE_PRINTF(_T("</%s>"), node->_name);
+ }
+ } else {
+ JPACKAGE_PRINTF(_T("%s"), node->_name);
+ }
+ PrintXMLDocument(node->_next, indt);
+}
+
+static void PrintXMLAttributes(XMLAttribute* attr) {
+ if (attr == NULL) return;
+
+ JPACKAGE_PRINTF(_T(" %s=\"%s\""), attr->_name, attr->_value);
+ PrintXMLAttributes(attr->_next);
+}
+
+static void indent(int indt) {
+ int i;
+ for (i = 0; i < indt; i++) {
+ JPACKAGE_PRINTF(_T(" "));
+ }
+}
+
+const TCHAR *CDStart = _T("<![CDATA[");
+const TCHAR *CDEnd = _T("]]>");
+
+static TCHAR* SkipPCData(TCHAR *p) {
+ TCHAR *end = JPACKAGE_STRSTR(p, CDEnd);
+ if (end != NULL) {
+ return end + sizeof (CDEnd);
+ }
+ return (++p);
+}
+
+static int IsPCData(TCHAR *p) {
+ const int size = sizeof (CDStart);
+ return (JPACKAGE_STRNCMP(CDStart, p, size) == 0);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/native/libapplauncher/LinuxPlatform.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef LINUXPLATFORM_H
+#define LINUXPLATFORM_H
+
+#include "Platform.h"
+#include "PosixPlatform.h"
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <pthread.h>
+#include <list>
+
+class LinuxPlatform : virtual public Platform, PosixPlatform {
+private:
+ pthread_t FMainThread;
+
+protected:
+ virtual TString getTmpDirString();
+
+public:
+ LinuxPlatform(void);
+ virtual ~LinuxPlatform(void);
+
+ TString GetPackageAppDirectory();
+ TString GetPackageLauncherDirectory();
+ TString GetPackageRuntimeBinDirectory();
+
+ virtual void ShowMessage(TString title, TString description);
+ virtual void ShowMessage(TString description);
+
+ virtual TCHAR* ConvertStringToFileSystemString(
+ TCHAR* Source, bool &release);
+ virtual TCHAR* ConvertFileSystemStringToString(
+ TCHAR* Source, bool &release);
+
+ virtual TString GetPackageRootDirectory();
+ virtual TString GetAppDataDirectory();
+ virtual TString GetAppName();
+
+ virtual TString GetModuleFileName();
+
+ virtual TString GetBundledJavaLibraryFileName(TString RuntimePath);
+
+ virtual ISectionalPropertyContainer* GetConfigFile(TString FileName);
+
+ virtual bool IsMainThread();
+ virtual TPlatformNumber GetMemorySize();
+};
+
+#endif //LINUXPLATFORM_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/native/libapplauncher/PlatformDefs.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef PLATFORM_DEFS_H
+#define PLATFORM_DEFS_H
+
+#include <errno.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <dlfcn.h>
+#include <libgen.h>
+#include <string>
+
+using namespace std;
+
+#ifndef LINUX
+#define LINUX
+#endif
+
+#define _T(x) x
+
+typedef char TCHAR;
+typedef std::string TString;
+#define StringLength strlen
+
+typedef unsigned long DWORD;
+
+#define TRAILING_PATHSEPARATOR '/'
+#define BAD_TRAILING_PATHSEPARATOR '\\'
+#define PATH_SEPARATOR ':'
+#define BAD_PATH_SEPARATOR ';'
+#define MAX_PATH 1000
+
+typedef long TPlatformNumber;
+typedef pid_t TProcessID;
+
+#define HMODULE void*
+
+typedef void* Module;
+typedef void* Procedure;
+
+#define StringToFileSystemString PlatformString
+#define FileSystemStringToString PlatformString
+
+#endif // PLATFORM_DEFS_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/EnumeratedBundlerParam.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.util.*;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+/**
+ * EnumeratedBundlerParams<T>
+ *
+ * Contains key-value pairs (elements) where keys are "displayable"
+ * keys which the IDE can display/choose and values are "identifier" values
+ * which can be stored in parameters' map.
+ *
+ * For instance the Mac has a predefined set of categories which can be applied
+ * to LSApplicationCategoryType which is required for the mac app store.
+ *
+ * The following example illustrates a simple usage of
+ * the MAC_CATEGORY parameter:
+ *
+ * <pre>{@code
+ * Set<String> keys = MAC_CATEGORY.getDisplayableKeys();
+ *
+ * String key = getLastValue(keys); // get last value for example
+ *
+ * String value = MAC_CATEGORY.getValueForDisplayableKey(key);
+ * params.put(MAC_CATEGORY.getID(), value);
+ * }</pre>
+ *
+ */
+class EnumeratedBundlerParam<T> extends BundlerParamInfo<T> {
+ // Not sure if this is the correct order, my idea is that from IDE
+ // perspective the string to display to the user is the key and then the
+ // value is some type of object (although probably a String in most cases)
+ private final Map<String, T> elements;
+ private final boolean strict;
+
+ EnumeratedBundlerParam(String id, Class<T> valueType,
+ Function<Map<String, ? super Object>, T> defaultValueFunction,
+ BiFunction<String, Map<String, ? super Object>, T> stringConverter,
+ Map<String, T> elements, boolean strict) {
+ this.id = id;
+ this.valueType = valueType;
+ this.defaultValueFunction = defaultValueFunction;
+ this.stringConverter = stringConverter;
+ this.elements = elements;
+ this.strict = strict;
+ }
+
+ boolean isInPossibleValues(T value) {
+ return elements.values().contains(value);
+ }
+
+ // Having the displayable values as the keys seems a bit wacky
+ Set<String> getDisplayableKeys() {
+ return Collections.unmodifiableSet(elements.keySet());
+ }
+
+ // mapping from a "displayable" key to an "identifier" value.
+ T getValueForDisplayableKey(String displayableKey) {
+ return elements.get(displayableKey);
+ }
+
+ boolean isStrict() {
+ return strict;
+ }
+
+ boolean isLoose() {
+ return !isStrict();
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/MacAppBundler.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.File;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.ResourceBundle;
+
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+import static jdk.incubator.jpackage.internal.MacBaseInstallerBundler.*;
+
+public class MacAppBundler extends AbstractImageBundler {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.incubator.jpackage.internal.resources.MacResources");
+
+ private static final String TEMPLATE_BUNDLE_ICON = "java.icns";
+
+ public static final BundlerParamInfo<String> MAC_CF_BUNDLE_NAME =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.MAC_BUNDLE_NAME.getId(),
+ String.class,
+ params -> null,
+ (s, p) -> s);
+
+ public static final BundlerParamInfo<String> MAC_CF_BUNDLE_VERSION =
+ new StandardBundlerParam<>(
+ "mac.CFBundleVersion",
+ String.class,
+ p -> {
+ String s = VERSION.fetchFrom(p);
+ if (validCFBundleVersion(s)) {
+ return s;
+ } else {
+ return "100";
+ }
+ },
+ (s, p) -> s);
+
+ public static final BundlerParamInfo<String> DEFAULT_ICNS_ICON =
+ new StandardBundlerParam<>(
+ ".mac.default.icns",
+ String.class,
+ params -> TEMPLATE_BUNDLE_ICON,
+ (s, p) -> s);
+
+ public static final BundlerParamInfo<String> DEVELOPER_ID_APP_SIGNING_KEY =
+ new StandardBundlerParam<>(
+ "mac.signing-key-developer-id-app",
+ String.class,
+ params -> {
+ String result = MacBaseInstallerBundler.findKey(
+ "Developer ID Application: "
+ + SIGNING_KEY_USER.fetchFrom(params),
+ SIGNING_KEYCHAIN.fetchFrom(params),
+ VERBOSE.fetchFrom(params));
+ if (result != null) {
+ MacCertificate certificate = new MacCertificate(result);
+
+ if (!certificate.isValid()) {
+ Log.error(MessageFormat.format(I18N.getString(
+ "error.certificate.expired"), result));
+ }
+ }
+
+ return result;
+ },
+ (s, p) -> s);
+
+ public static final BundlerParamInfo<String> BUNDLE_ID_SIGNING_PREFIX =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.MAC_BUNDLE_SIGNING_PREFIX.getId(),
+ String.class,
+ params -> IDENTIFIER.fetchFrom(params) + ".",
+ (s, p) -> s);
+
+ public static final BundlerParamInfo<File> ICON_ICNS =
+ new StandardBundlerParam<>(
+ "icon.icns",
+ File.class,
+ params -> {
+ File f = ICON.fetchFrom(params);
+ if (f != null && !f.getName().toLowerCase().endsWith(".icns")) {
+ Log.error(MessageFormat.format(
+ I18N.getString("message.icon-not-icns"), f));
+ return null;
+ }
+ return f;
+ },
+ (s, p) -> new File(s));
+
+ public static boolean validCFBundleVersion(String v) {
+ // CFBundleVersion (String - iOS, OS X) specifies the build version
+ // number of the bundle, which identifies an iteration (released or
+ // unreleased) of the bundle. The build version number should be a
+ // string comprised of three non-negative, period-separated integers
+ // with the first integer being greater than zero. The string should
+ // only contain numeric (0-9) and period (.) characters. Leading zeros
+ // are truncated from each integer and will be ignored (that is,
+ // 1.02.3 is equivalent to 1.2.3). This key is not localizable.
+
+ if (v == null) {
+ return false;
+ }
+
+ String p[] = v.split("\\.");
+ if (p.length > 3 || p.length < 1) {
+ Log.verbose(I18N.getString(
+ "message.version-string-too-many-components"));
+ return false;
+ }
+
+ try {
+ BigInteger n = new BigInteger(p[0]);
+ if (BigInteger.ONE.compareTo(n) > 0) {
+ Log.verbose(I18N.getString(
+ "message.version-string-first-number-not-zero"));
+ return false;
+ }
+ if (p.length > 1) {
+ n = new BigInteger(p[1]);
+ if (BigInteger.ZERO.compareTo(n) > 0) {
+ Log.verbose(I18N.getString(
+ "message.version-string-no-negative-numbers"));
+ return false;
+ }
+ }
+ if (p.length > 2) {
+ n = new BigInteger(p[2]);
+ if (BigInteger.ZERO.compareTo(n) > 0) {
+ Log.verbose(I18N.getString(
+ "message.version-string-no-negative-numbers"));
+ return false;
+ }
+ }
+ } catch (NumberFormatException ne) {
+ Log.verbose(I18N.getString("message.version-string-numbers-only"));
+ Log.verbose(ne);
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean validate(Map<String, ? super Object> params)
+ throws ConfigException {
+ try {
+ return doValidate(params);
+ } catch (RuntimeException re) {
+ if (re.getCause() instanceof ConfigException) {
+ throw (ConfigException) re.getCause();
+ } else {
+ throw new ConfigException(re);
+ }
+ }
+ }
+
+ private boolean doValidate(Map<String, ? super Object> params)
+ throws ConfigException {
+
+ imageBundleValidation(params);
+
+ if (StandardBundlerParam.getPredefinedAppImage(params) != null) {
+ return true;
+ }
+
+ // validate short version
+ if (!validCFBundleVersion(MAC_CF_BUNDLE_VERSION.fetchFrom(params))) {
+ throw new ConfigException(
+ I18N.getString("error.invalid-cfbundle-version"),
+ I18N.getString("error.invalid-cfbundle-version.advice"));
+ }
+
+ // reject explicitly set sign to true and no valid signature key
+ if (Optional.ofNullable(MacAppImageBuilder.
+ SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.FALSE)) {
+ String signingIdentity =
+ DEVELOPER_ID_APP_SIGNING_KEY.fetchFrom(params);
+ if (signingIdentity == null) {
+ throw new ConfigException(
+ I18N.getString("error.explicit-sign-no-cert"),
+ I18N.getString("error.explicit-sign-no-cert.advice"));
+ }
+
+ // Signing will not work without Xcode with command line developer tools
+ try {
+ ProcessBuilder pb = new ProcessBuilder("xcrun", "--help");
+ Process p = pb.start();
+ int code = p.waitFor();
+ if (code != 0) {
+ throw new ConfigException(
+ I18N.getString("error.no.xcode.signing"),
+ I18N.getString("error.no.xcode.signing.advice"));
+ }
+ } catch (IOException | InterruptedException ex) {
+ throw new ConfigException(ex);
+ }
+ }
+
+ return true;
+ }
+
+ File doBundle(Map<String, ? super Object> params, File outputDirectory,
+ boolean dependentTask) throws PackagerException {
+ if (StandardBundlerParam.isRuntimeInstaller(params)) {
+ return PREDEFINED_RUNTIME_IMAGE.fetchFrom(params);
+ } else {
+ return doAppBundle(params, outputDirectory, dependentTask);
+ }
+ }
+
+ File doAppBundle(Map<String, ? super Object> params, File outputDirectory,
+ boolean dependentTask) throws PackagerException {
+ try {
+ File rootDirectory = createRoot(params, outputDirectory,
+ dependentTask, APP_NAME.fetchFrom(params) + ".app");
+ AbstractAppImageBuilder appBuilder =
+ new MacAppImageBuilder(params, outputDirectory.toPath());
+ if (PREDEFINED_RUNTIME_IMAGE.fetchFrom(params) == null ) {
+ JLinkBundlerHelper.execute(params, appBuilder);
+ } else {
+ StandardBundlerParam.copyPredefinedRuntimeImage(
+ params, appBuilder);
+ }
+ return rootDirectory;
+ } catch (PackagerException pe) {
+ throw pe;
+ } catch (Exception ex) {
+ Log.verbose(ex);
+ throw new PackagerException(ex);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////////
+ // Implement Bundler
+ /////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public String getName() {
+ return I18N.getString("app.bundler.name");
+ }
+
+ @Override
+ public String getID() {
+ return "mac.app";
+ }
+
+ @Override
+ public String getBundleType() {
+ return "IMAGE";
+ }
+
+ @Override
+ public File execute(Map<String, ? super Object> params,
+ File outputParentDir) throws PackagerException {
+ return doBundle(params, outputParentDir, false);
+ }
+
+ @Override
+ public boolean supported(boolean runtimeInstaller) {
+ return true;
+ }
+
+ @Override
+ public boolean isDefault() {
+ return false;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/MacAppImageBuilder.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,945 @@
+/*
+ * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Writer;
+import java.math.BigInteger;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.PosixFilePermission;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.ResourceBundle;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathFactory;
+
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+import static jdk.incubator.jpackage.internal.MacBaseInstallerBundler.*;
+import static jdk.incubator.jpackage.internal.MacAppBundler.*;
+import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
+
+public class MacAppImageBuilder extends AbstractAppImageBuilder {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.incubator.jpackage.internal.resources.MacResources");
+
+ private static final String LIBRARY_NAME = "libapplauncher.dylib";
+ private static final String TEMPLATE_BUNDLE_ICON = "java.icns";
+ private static final String OS_TYPE_CODE = "APPL";
+ private static final String TEMPLATE_INFO_PLIST_LITE =
+ "Info-lite.plist.template";
+ private static final String TEMPLATE_RUNTIME_INFO_PLIST =
+ "Runtime-Info.plist.template";
+
+ private final Path root;
+ private final Path contentsDir;
+ private final Path appDir;
+ private final Path javaModsDir;
+ private final Path resourcesDir;
+ private final Path macOSDir;
+ private final Path runtimeDir;
+ private final Path runtimeRoot;
+ private final Path mdir;
+
+ private static List<String> keyChains;
+
+ public static final BundlerParamInfo<Boolean>
+ MAC_CONFIGURE_LAUNCHER_IN_PLIST = new StandardBundlerParam<>(
+ "mac.configure-launcher-in-plist",
+ Boolean.class,
+ params -> Boolean.FALSE,
+ (s, p) -> Boolean.valueOf(s));
+
+ public static final BundlerParamInfo<String> MAC_CF_BUNDLE_NAME =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.MAC_BUNDLE_NAME.getId(),
+ String.class,
+ params -> null,
+ (s, p) -> s);
+
+ public static final BundlerParamInfo<String> MAC_CF_BUNDLE_IDENTIFIER =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.MAC_BUNDLE_IDENTIFIER.getId(),
+ String.class,
+ params -> {
+ // Get identifier from app image if user provided
+ // app image and did not provide the identifier via CLI.
+ String identifier = extractBundleIdentifier(params);
+ if (identifier != null) {
+ return identifier;
+ }
+
+ return IDENTIFIER.fetchFrom(params);
+ },
+ (s, p) -> s);
+
+ public static final BundlerParamInfo<String> MAC_CF_BUNDLE_VERSION =
+ new StandardBundlerParam<>(
+ "mac.CFBundleVersion",
+ String.class,
+ p -> {
+ String s = VERSION.fetchFrom(p);
+ if (validCFBundleVersion(s)) {
+ return s;
+ } else {
+ return "100";
+ }
+ },
+ (s, p) -> s);
+
+ public static final BundlerParamInfo<File> ICON_ICNS =
+ new StandardBundlerParam<>(
+ "icon.icns",
+ File.class,
+ params -> {
+ File f = ICON.fetchFrom(params);
+ if (f != null && !f.getName().toLowerCase().endsWith(".icns")) {
+ Log.error(MessageFormat.format(
+ I18N.getString("message.icon-not-icns"), f));
+ return null;
+ }
+ return f;
+ },
+ (s, p) -> new File(s));
+
+ public static final StandardBundlerParam<Boolean> SIGN_BUNDLE =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.MAC_SIGN.getId(),
+ Boolean.class,
+ params -> false,
+ // valueOf(null) is false, we actually do want null in some cases
+ (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ?
+ null : Boolean.valueOf(s)
+ );
+
+ public MacAppImageBuilder(Map<String, Object> params, Path imageOutDir)
+ throws IOException {
+ super(params, imageOutDir.resolve(APP_NAME.fetchFrom(params)
+ + ".app/Contents/runtime/Contents/Home"));
+
+ Objects.requireNonNull(imageOutDir);
+
+ this.root = imageOutDir.resolve(APP_NAME.fetchFrom(params) + ".app");
+ this.contentsDir = root.resolve("Contents");
+ this.appDir = contentsDir.resolve("app");
+ this.javaModsDir = appDir.resolve("mods");
+ this.resourcesDir = contentsDir.resolve("Resources");
+ this.macOSDir = contentsDir.resolve("MacOS");
+ this.runtimeDir = contentsDir.resolve("runtime");
+ this.runtimeRoot = runtimeDir.resolve("Contents/Home");
+ this.mdir = runtimeRoot.resolve("lib");
+ Files.createDirectories(appDir);
+ Files.createDirectories(resourcesDir);
+ Files.createDirectories(macOSDir);
+ Files.createDirectories(runtimeDir);
+ }
+
+ private void writeEntry(InputStream in, Path dstFile) throws IOException {
+ Files.createDirectories(dstFile.getParent());
+ Files.copy(in, dstFile);
+ }
+
+ public static boolean validCFBundleVersion(String v) {
+ // CFBundleVersion (String - iOS, OS X) specifies the build version
+ // number of the bundle, which identifies an iteration (released or
+ // unreleased) of the bundle. The build version number should be a
+ // string comprised of three non-negative, period-separated integers
+ // with the first integer being greater than zero. The string should
+ // only contain numeric (0-9) and period (.) characters. Leading zeros
+ // are truncated from each integer and will be ignored (that is,
+ // 1.02.3 is equivalent to 1.2.3). This key is not localizable.
+
+ if (v == null) {
+ return false;
+ }
+
+ String p[] = v.split("\\.");
+ if (p.length > 3 || p.length < 1) {
+ Log.verbose(I18N.getString(
+ "message.version-string-too-many-components"));
+ return false;
+ }
+
+ try {
+ BigInteger n = new BigInteger(p[0]);
+ if (BigInteger.ONE.compareTo(n) > 0) {
+ Log.verbose(I18N.getString(
+ "message.version-string-first-number-not-zero"));
+ return false;
+ }
+ if (p.length > 1) {
+ n = new BigInteger(p[1]);
+ if (BigInteger.ZERO.compareTo(n) > 0) {
+ Log.verbose(I18N.getString(
+ "message.version-string-no-negative-numbers"));
+ return false;
+ }
+ }
+ if (p.length > 2) {
+ n = new BigInteger(p[2]);
+ if (BigInteger.ZERO.compareTo(n) > 0) {
+ Log.verbose(I18N.getString(
+ "message.version-string-no-negative-numbers"));
+ return false;
+ }
+ }
+ } catch (NumberFormatException ne) {
+ Log.verbose(I18N.getString("message.version-string-numbers-only"));
+ Log.verbose(ne);
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public Path getAppDir() {
+ return appDir;
+ }
+
+ @Override
+ public Path getAppModsDir() {
+ return javaModsDir;
+ }
+
+ @Override
+ public void prepareApplicationFiles(Map<String, ? super Object> params)
+ throws IOException {
+ Map<String, ? super Object> originalParams = new HashMap<>(params);
+ // Generate PkgInfo
+ File pkgInfoFile = new File(contentsDir.toFile(), "PkgInfo");
+ pkgInfoFile.createNewFile();
+ writePkgInfo(pkgInfoFile);
+
+ Path executable = macOSDir.resolve(getLauncherName(params));
+
+ // create the main app launcher
+ try (InputStream is_launcher =
+ getResourceAsStream("jpackageapplauncher");
+ InputStream is_lib = getResourceAsStream(LIBRARY_NAME)) {
+ // Copy executable and library to MacOS folder
+ writeEntry(is_launcher, executable);
+ writeEntry(is_lib, macOSDir.resolve(LIBRARY_NAME));
+ }
+ executable.toFile().setExecutable(true, false);
+ // generate main app launcher config file
+ File cfg = new File(root.toFile(), getLauncherCfgName(params));
+ writeCfgFile(params, cfg);
+
+ // create additional app launcher(s) and config file(s)
+ List<Map<String, ? super Object>> entryPoints =
+ StandardBundlerParam.ADD_LAUNCHERS.fetchFrom(params);
+ for (Map<String, ? super Object> entryPoint : entryPoints) {
+ Map<String, ? super Object> tmp =
+ AddLauncherArguments.merge(originalParams, entryPoint);
+
+ // add executable for add launcher
+ Path addExecutable = macOSDir.resolve(getLauncherName(tmp));
+ try (InputStream is = getResourceAsStream("jpackageapplauncher");) {
+ writeEntry(is, addExecutable);
+ }
+ addExecutable.toFile().setExecutable(true, false);
+
+ // add config file for add launcher
+ cfg = new File(root.toFile(), getLauncherCfgName(tmp));
+ writeCfgFile(tmp, cfg);
+ }
+
+ // Copy class path entries to Java folder
+ copyClassPathEntries(appDir, params);
+
+ /*********** Take care of "config" files *******/
+
+ createResource(TEMPLATE_BUNDLE_ICON, params)
+ .setCategory("icon")
+ .setExternal(ICON_ICNS.fetchFrom(params))
+ .saveToFile(resourcesDir.resolve(APP_NAME.fetchFrom(params)
+ + ".icns"));
+
+ // copy file association icons
+ for (Map<String, ?
+ super Object> fa : FILE_ASSOCIATIONS.fetchFrom(params)) {
+ File f = FA_ICON.fetchFrom(fa);
+ if (f != null && f.exists()) {
+ try (InputStream in2 = new FileInputStream(f)) {
+ Files.copy(in2, resourcesDir.resolve(f.getName()));
+ }
+
+ }
+ }
+
+ copyRuntimeFiles(params);
+ sign(params);
+ }
+
+ @Override
+ public void prepareJreFiles(Map<String, ? super Object> params)
+ throws IOException {
+ copyRuntimeFiles(params);
+ sign(params);
+ }
+
+ @Override
+ File getRuntimeImageDir(File runtimeImageTop) {
+ File home = new File(runtimeImageTop, "Contents/Home");
+ return (home.exists() ? home : runtimeImageTop);
+ }
+
+ private void copyRuntimeFiles(Map<String, ? super Object> params)
+ throws IOException {
+ // Generate Info.plist
+ writeInfoPlist(contentsDir.resolve("Info.plist").toFile(), params);
+
+ // generate java runtime info.plist
+ writeRuntimeInfoPlist(
+ runtimeDir.resolve("Contents/Info.plist").toFile(), params);
+
+ // copy library
+ Path runtimeMacOSDir = Files.createDirectories(
+ runtimeDir.resolve("Contents/MacOS"));
+
+ // JDK 9, 10, and 11 have extra '/jli/' subdir
+ Path jli = runtimeRoot.resolve("lib/libjli.dylib");
+ if (!Files.exists(jli)) {
+ jli = runtimeRoot.resolve("lib/jli/libjli.dylib");
+ }
+
+ Files.copy(jli, runtimeMacOSDir.resolve("libjli.dylib"));
+ }
+
+ private void sign(Map<String, ? super Object> params) throws IOException {
+ if (Optional.ofNullable(
+ SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.TRUE)) {
+ try {
+ addNewKeychain(params);
+ } catch (InterruptedException e) {
+ Log.error(e.getMessage());
+ }
+ String signingIdentity =
+ DEVELOPER_ID_APP_SIGNING_KEY.fetchFrom(params);
+ if (signingIdentity != null) {
+ signAppBundle(params, root, signingIdentity,
+ BUNDLE_ID_SIGNING_PREFIX.fetchFrom(params), null, null);
+ }
+ restoreKeychainList(params);
+ }
+ }
+
+ private String getLauncherName(Map<String, ? super Object> params) {
+ if (APP_NAME.fetchFrom(params) != null) {
+ return APP_NAME.fetchFrom(params);
+ } else {
+ return MAIN_CLASS.fetchFrom(params);
+ }
+ }
+
+ public static String getLauncherCfgName(
+ Map<String, ? super Object> params) {
+ return "Contents/app/" + APP_NAME.fetchFrom(params) + ".cfg";
+ }
+
+ private void copyClassPathEntries(Path javaDirectory,
+ Map<String, ? super Object> params) throws IOException {
+ List<RelativeFileSet> resourcesList =
+ APP_RESOURCES_LIST.fetchFrom(params);
+ if (resourcesList == null) {
+ throw new RuntimeException(
+ I18N.getString("message.null-classpath"));
+ }
+
+ for (RelativeFileSet classPath : resourcesList) {
+ File srcdir = classPath.getBaseDirectory();
+ for (String fname : classPath.getIncludedFiles()) {
+ copyEntry(javaDirectory, srcdir, fname);
+ }
+ }
+ }
+
+ private String getBundleName(Map<String, ? super Object> params) {
+ if (MAC_CF_BUNDLE_NAME.fetchFrom(params) != null) {
+ String bn = MAC_CF_BUNDLE_NAME.fetchFrom(params);
+ if (bn.length() > 16) {
+ Log.error(MessageFormat.format(I18N.getString(
+ "message.bundle-name-too-long-warning"),
+ MAC_CF_BUNDLE_NAME.getID(), bn));
+ }
+ return MAC_CF_BUNDLE_NAME.fetchFrom(params);
+ } else if (APP_NAME.fetchFrom(params) != null) {
+ return APP_NAME.fetchFrom(params);
+ } else {
+ String nm = MAIN_CLASS.fetchFrom(params);
+ if (nm.length() > 16) {
+ nm = nm.substring(0, 16);
+ }
+ return nm;
+ }
+ }
+
+ private void writeRuntimeInfoPlist(File file,
+ Map<String, ? super Object> params) throws IOException {
+ Map<String, String> data = new HashMap<>();
+ String identifier = StandardBundlerParam.isRuntimeInstaller(params) ?
+ MAC_CF_BUNDLE_IDENTIFIER.fetchFrom(params) :
+ "com.oracle.java." + MAC_CF_BUNDLE_IDENTIFIER.fetchFrom(params);
+ data.put("CF_BUNDLE_IDENTIFIER", identifier);
+ String name = StandardBundlerParam.isRuntimeInstaller(params) ?
+ getBundleName(params): "Java Runtime Image";
+ data.put("CF_BUNDLE_NAME", name);
+ data.put("CF_BUNDLE_VERSION", VERSION.fetchFrom(params));
+ data.put("CF_BUNDLE_SHORT_VERSION_STRING", VERSION.fetchFrom(params));
+
+ createResource(TEMPLATE_RUNTIME_INFO_PLIST, params)
+ .setPublicName("Runtime-Info.plist")
+ .setCategory(I18N.getString("resource.runtime-info-plist"))
+ .setSubstitutionData(data)
+ .saveToFile(file);
+ }
+
+ private void writeInfoPlist(File file, Map<String, ? super Object> params)
+ throws IOException {
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.preparing-info-plist"), file.getAbsolutePath()));
+
+ //prepare config for exe
+ //Note: do not need CFBundleDisplayName if we don't support localization
+ Map<String, String> data = new HashMap<>();
+ data.put("DEPLOY_ICON_FILE", APP_NAME.fetchFrom(params) + ".icns");
+ data.put("DEPLOY_BUNDLE_IDENTIFIER",
+ MAC_CF_BUNDLE_IDENTIFIER.fetchFrom(params));
+ data.put("DEPLOY_BUNDLE_NAME",
+ getBundleName(params));
+ data.put("DEPLOY_BUNDLE_COPYRIGHT",
+ COPYRIGHT.fetchFrom(params) != null ?
+ COPYRIGHT.fetchFrom(params) : "Unknown");
+ data.put("DEPLOY_LAUNCHER_NAME", getLauncherName(params));
+ data.put("DEPLOY_BUNDLE_SHORT_VERSION",
+ VERSION.fetchFrom(params) != null ?
+ VERSION.fetchFrom(params) : "1.0.0");
+ data.put("DEPLOY_BUNDLE_CFBUNDLE_VERSION",
+ MAC_CF_BUNDLE_VERSION.fetchFrom(params) != null ?
+ MAC_CF_BUNDLE_VERSION.fetchFrom(params) : "100");
+
+ boolean hasMainJar = MAIN_JAR.fetchFrom(params) != null;
+ boolean hasMainModule =
+ StandardBundlerParam.MODULE.fetchFrom(params) != null;
+
+ if (hasMainJar) {
+ data.put("DEPLOY_MAIN_JAR_NAME", MAIN_JAR.fetchFrom(params).
+ getIncludedFiles().iterator().next());
+ }
+ else if (hasMainModule) {
+ data.put("DEPLOY_MODULE_NAME",
+ StandardBundlerParam.MODULE.fetchFrom(params));
+ }
+
+ StringBuilder sb = new StringBuilder();
+ List<String> jvmOptions = JAVA_OPTIONS.fetchFrom(params);
+
+ String newline = ""; //So we don't add extra line after last append
+ for (String o : jvmOptions) {
+ sb.append(newline).append(
+ " <string>").append(o).append("</string>");
+ newline = "\n";
+ }
+
+ data.put("DEPLOY_JAVA_OPTIONS", sb.toString());
+
+ sb = new StringBuilder();
+ List<String> args = ARGUMENTS.fetchFrom(params);
+ newline = "";
+ // So we don't add unneccessary extra line after last append
+
+ for (String o : args) {
+ sb.append(newline).append(" <string>").append(o).append(
+ "</string>");
+ newline = "\n";
+ }
+ data.put("DEPLOY_ARGUMENTS", sb.toString());
+
+ newline = "";
+
+ data.put("DEPLOY_LAUNCHER_CLASS", MAIN_CLASS.fetchFrom(params));
+
+ data.put("DEPLOY_APP_CLASSPATH",
+ getCfgClassPath(CLASSPATH.fetchFrom(params)));
+
+ StringBuilder bundleDocumentTypes = new StringBuilder();
+ StringBuilder exportedTypes = new StringBuilder();
+ for (Map<String, ? super Object>
+ fileAssociation : FILE_ASSOCIATIONS.fetchFrom(params)) {
+
+ List<String> extensions = FA_EXTENSIONS.fetchFrom(fileAssociation);
+
+ if (extensions == null) {
+ Log.verbose(I18N.getString(
+ "message.creating-association-with-null-extension"));
+ }
+
+ List<String> mimeTypes = FA_CONTENT_TYPE.fetchFrom(fileAssociation);
+ String itemContentType = MAC_CF_BUNDLE_IDENTIFIER.fetchFrom(params)
+ + "." + ((extensions == null || extensions.isEmpty())
+ ? "mime" : extensions.get(0));
+ String description = FA_DESCRIPTION.fetchFrom(fileAssociation);
+ File icon = FA_ICON.fetchFrom(fileAssociation);
+
+ bundleDocumentTypes.append(" <dict>\n")
+ .append(" <key>LSItemContentTypes</key>\n")
+ .append(" <array>\n")
+ .append(" <string>")
+ .append(itemContentType)
+ .append("</string>\n")
+ .append(" </array>\n")
+ .append("\n")
+ .append(" <key>CFBundleTypeName</key>\n")
+ .append(" <string>")
+ .append(description)
+ .append("</string>\n")
+ .append("\n")
+ .append(" <key>LSHandlerRank</key>\n")
+ .append(" <string>Owner</string>\n")
+ // TODO make a bundler arg
+ .append("\n")
+ .append(" <key>CFBundleTypeRole</key>\n")
+ .append(" <string>Editor</string>\n")
+ // TODO make a bundler arg
+ .append("\n")
+ .append(" <key>LSIsAppleDefaultForType</key>\n")
+ .append(" <true/>\n")
+ // TODO make a bundler arg
+ .append("\n");
+
+ if (icon != null && icon.exists()) {
+ bundleDocumentTypes
+ .append(" <key>CFBundleTypeIconFile</key>\n")
+ .append(" <string>")
+ .append(icon.getName())
+ .append("</string>\n");
+ }
+ bundleDocumentTypes.append(" </dict>\n");
+
+ exportedTypes.append(" <dict>\n")
+ .append(" <key>UTTypeIdentifier</key>\n")
+ .append(" <string>")
+ .append(itemContentType)
+ .append("</string>\n")
+ .append("\n")
+ .append(" <key>UTTypeDescription</key>\n")
+ .append(" <string>")
+ .append(description)
+ .append("</string>\n")
+ .append(" <key>UTTypeConformsTo</key>\n")
+ .append(" <array>\n")
+ .append(" <string>public.data</string>\n")
+ //TODO expose this?
+ .append(" </array>\n")
+ .append("\n");
+
+ if (icon != null && icon.exists()) {
+ exportedTypes.append(" <key>UTTypeIconFile</key>\n")
+ .append(" <string>")
+ .append(icon.getName())
+ .append("</string>\n")
+ .append("\n");
+ }
+
+ exportedTypes.append("\n")
+ .append(" <key>UTTypeTagSpecification</key>\n")
+ .append(" <dict>\n")
+ // TODO expose via param? .append(
+ // " <key>com.apple.ostype</key>\n");
+ // TODO expose via param? .append(
+ // " <string>ABCD</string>\n")
+ .append("\n");
+
+ if (extensions != null && !extensions.isEmpty()) {
+ exportedTypes.append(
+ " <key>public.filename-extension</key>\n")
+ .append(" <array>\n");
+
+ for (String ext : extensions) {
+ exportedTypes.append(" <string>")
+ .append(ext)
+ .append("</string>\n");
+ }
+ exportedTypes.append(" </array>\n");
+ }
+ if (mimeTypes != null && !mimeTypes.isEmpty()) {
+ exportedTypes.append(" <key>public.mime-type</key>\n")
+ .append(" <array>\n");
+
+ for (String mime : mimeTypes) {
+ exportedTypes.append(" <string>")
+ .append(mime)
+ .append("</string>\n");
+ }
+ exportedTypes.append(" </array>\n");
+ }
+ exportedTypes.append(" </dict>\n")
+ .append(" </dict>\n");
+ }
+ String associationData;
+ if (bundleDocumentTypes.length() > 0) {
+ associationData =
+ "\n <key>CFBundleDocumentTypes</key>\n <array>\n"
+ + bundleDocumentTypes.toString()
+ + " </array>\n\n"
+ + " <key>UTExportedTypeDeclarations</key>\n <array>\n"
+ + exportedTypes.toString()
+ + " </array>\n";
+ } else {
+ associationData = "";
+ }
+ data.put("DEPLOY_FILE_ASSOCIATIONS", associationData);
+
+ createResource(TEMPLATE_INFO_PLIST_LITE, params)
+ .setCategory(I18N.getString("resource.app-info-plist"))
+ .setSubstitutionData(data)
+ .setPublicName("Info.plist")
+ .saveToFile(file);
+ }
+
+ private void writePkgInfo(File file) throws IOException {
+ //hardcoded as it does not seem we need to change it ever
+ String signature = "????";
+
+ try (Writer out = Files.newBufferedWriter(file.toPath())) {
+ out.write(OS_TYPE_CODE + signature);
+ out.flush();
+ }
+ }
+
+ public static void addNewKeychain(Map<String, ? super Object> params)
+ throws IOException, InterruptedException {
+ if (Platform.getMajorVersion() < 10 ||
+ (Platform.getMajorVersion() == 10 &&
+ Platform.getMinorVersion() < 12)) {
+ // we need this for OS X 10.12+
+ return;
+ }
+
+ String keyChain = SIGNING_KEYCHAIN.fetchFrom(params);
+ if (keyChain == null || keyChain.isEmpty()) {
+ return;
+ }
+
+ // get current keychain list
+ String keyChainPath = new File (keyChain).getAbsolutePath().toString();
+ List<String> keychainList = new ArrayList<>();
+ int ret = IOUtils.getProcessOutput(
+ keychainList, "security", "list-keychains");
+ if (ret != 0) {
+ Log.error(I18N.getString("message.keychain.error"));
+ return;
+ }
+
+ boolean contains = keychainList.stream().anyMatch(
+ str -> str.trim().equals("\""+keyChainPath.trim()+"\""));
+ if (contains) {
+ // keychain is already added in the search list
+ return;
+ }
+
+ keyChains = new ArrayList<>();
+ // remove "
+ keychainList.forEach((String s) -> {
+ String path = s.trim();
+ if (path.startsWith("\"") && path.endsWith("\"")) {
+ path = path.substring(1, path.length()-1);
+ }
+ keyChains.add(path);
+ });
+
+ List<String> args = new ArrayList<>();
+ args.add("security");
+ args.add("list-keychains");
+ args.add("-s");
+
+ args.addAll(keyChains);
+ args.add(keyChain);
+
+ ProcessBuilder pb = new ProcessBuilder(args);
+ IOUtils.exec(pb);
+ }
+
+ public static void restoreKeychainList(Map<String, ? super Object> params)
+ throws IOException{
+ if (Platform.getMajorVersion() < 10 ||
+ (Platform.getMajorVersion() == 10 &&
+ Platform.getMinorVersion() < 12)) {
+ // we need this for OS X 10.12+
+ return;
+ }
+
+ if (keyChains == null || keyChains.isEmpty()) {
+ return;
+ }
+
+ List<String> args = new ArrayList<>();
+ args.add("security");
+ args.add("list-keychains");
+ args.add("-s");
+
+ args.addAll(keyChains);
+
+ ProcessBuilder pb = new ProcessBuilder(args);
+ IOUtils.exec(pb);
+ }
+
+ public static void signAppBundle(
+ Map<String, ? super Object> params, Path appLocation,
+ String signingIdentity, String identifierPrefix,
+ String entitlementsFile, String inheritedEntitlements)
+ throws IOException {
+ AtomicReference<IOException> toThrow = new AtomicReference<>();
+ String appExecutable = "/Contents/MacOS/" + APP_NAME.fetchFrom(params);
+ String keyChain = SIGNING_KEYCHAIN.fetchFrom(params);
+
+ // sign all dylibs and jars
+ try (Stream<Path> stream = Files.walk(appLocation)) {
+ stream.peek(path -> { // fix permissions
+ try {
+ Set<PosixFilePermission> pfp =
+ Files.getPosixFilePermissions(path);
+ if (!pfp.contains(PosixFilePermission.OWNER_WRITE)) {
+ pfp = EnumSet.copyOf(pfp);
+ pfp.add(PosixFilePermission.OWNER_WRITE);
+ Files.setPosixFilePermissions(path, pfp);
+ }
+ } catch (IOException e) {
+ Log.verbose(e);
+ }
+ }).filter(p -> Files.isRegularFile(p)
+ && !(p.toString().contains("/Contents/MacOS/libjli.dylib")
+ || p.toString().endsWith(appExecutable)
+ || p.toString().contains("/Contents/runtime")
+ || p.toString().contains("/Contents/Frameworks"))).forEach(p -> {
+ //noinspection ThrowableResultOfMethodCallIgnored
+ if (toThrow.get() != null) return;
+
+ // If p is a symlink then skip the signing process.
+ if (Files.isSymbolicLink(p)) {
+ if (VERBOSE.fetchFrom(params)) {
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.ignoring.symlink"), p.toString()));
+ }
+ } else {
+ if (p.toString().endsWith(LIBRARY_NAME)) {
+ if (isFileSigned(p)) {
+ return;
+ }
+ }
+
+ List<String> args = new ArrayList<>();
+ args.addAll(Arrays.asList("codesign",
+ "-s", signingIdentity, // sign with this key
+ "--prefix", identifierPrefix,
+ // use the identifier as a prefix
+ "-vvvv"));
+ if (entitlementsFile != null &&
+ (p.toString().endsWith(".jar")
+ || p.toString().endsWith(".dylib"))) {
+ args.add("--entitlements");
+ args.add(entitlementsFile); // entitlements
+ } else if (inheritedEntitlements != null &&
+ Files.isExecutable(p)) {
+ args.add("--entitlements");
+ args.add(inheritedEntitlements);
+ // inherited entitlements for executable processes
+ }
+ if (keyChain != null && !keyChain.isEmpty()) {
+ args.add("--keychain");
+ args.add(keyChain);
+ }
+ args.add(p.toString());
+
+ try {
+ Set<PosixFilePermission> oldPermissions =
+ Files.getPosixFilePermissions(p);
+ File f = p.toFile();
+ f.setWritable(true, true);
+
+ ProcessBuilder pb = new ProcessBuilder(args);
+ IOUtils.exec(pb);
+
+ Files.setPosixFilePermissions(p, oldPermissions);
+ } catch (IOException ioe) {
+ toThrow.set(ioe);
+ }
+ }
+ });
+ }
+ IOException ioe = toThrow.get();
+ if (ioe != null) {
+ throw ioe;
+ }
+
+ // sign all runtime and frameworks
+ Consumer<? super Path> signIdentifiedByPList = path -> {
+ //noinspection ThrowableResultOfMethodCallIgnored
+ if (toThrow.get() != null) return;
+
+ try {
+ List<String> args = new ArrayList<>();
+ args.addAll(Arrays.asList("codesign",
+ "-s", signingIdentity, // sign with this key
+ "--prefix", identifierPrefix,
+ // use the identifier as a prefix
+ "-vvvv"));
+ if (keyChain != null && !keyChain.isEmpty()) {
+ args.add("--keychain");
+ args.add(keyChain);
+ }
+ args.add(path.toString());
+ ProcessBuilder pb = new ProcessBuilder(args);
+ IOUtils.exec(pb);
+
+ args = new ArrayList<>();
+ args.addAll(Arrays.asList("codesign",
+ "-s", signingIdentity, // sign with this key
+ "--prefix", identifierPrefix,
+ // use the identifier as a prefix
+ "-vvvv"));
+ if (keyChain != null && !keyChain.isEmpty()) {
+ args.add("--keychain");
+ args.add(keyChain);
+ }
+ args.add(path.toString()
+ + "/Contents/_CodeSignature/CodeResources");
+ pb = new ProcessBuilder(args);
+ IOUtils.exec(pb);
+ } catch (IOException e) {
+ toThrow.set(e);
+ }
+ };
+
+ Path javaPath = appLocation.resolve("Contents/runtime");
+ if (Files.isDirectory(javaPath)) {
+ signIdentifiedByPList.accept(javaPath);
+
+ ioe = toThrow.get();
+ if (ioe != null) {
+ throw ioe;
+ }
+ }
+ Path frameworkPath = appLocation.resolve("Contents/Frameworks");
+ if (Files.isDirectory(frameworkPath)) {
+ Files.list(frameworkPath)
+ .forEach(signIdentifiedByPList);
+
+ ioe = toThrow.get();
+ if (ioe != null) {
+ throw ioe;
+ }
+ }
+
+ // sign the app itself
+ List<String> args = new ArrayList<>();
+ args.addAll(Arrays.asList("codesign",
+ "-s", signingIdentity, // sign with this key
+ "-vvvv")); // super verbose output
+ if (entitlementsFile != null) {
+ args.add("--entitlements");
+ args.add(entitlementsFile); // entitlements
+ }
+ if (keyChain != null && !keyChain.isEmpty()) {
+ args.add("--keychain");
+ args.add(keyChain);
+ }
+ args.add(appLocation.toString());
+
+ ProcessBuilder pb =
+ new ProcessBuilder(args.toArray(new String[args.size()]));
+ IOUtils.exec(pb);
+ }
+
+ private static boolean isFileSigned(Path file) {
+ ProcessBuilder pb =
+ new ProcessBuilder("codesign", "--verify", file.toString());
+
+ try {
+ IOUtils.exec(pb);
+ } catch (IOException ex) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private static String extractBundleIdentifier(Map<String, Object> params) {
+ if (PREDEFINED_APP_IMAGE.fetchFrom(params) == null) {
+ return null;
+ }
+
+ try {
+ File infoPList = new File(PREDEFINED_APP_IMAGE.fetchFrom(params) +
+ File.separator + "Contents" +
+ File.separator + "Info.plist");
+
+ DocumentBuilderFactory dbf
+ = DocumentBuilderFactory.newDefaultInstance();
+ dbf.setFeature("http://apache.org/xml/features/" +
+ "nonvalidating/load-external-dtd", false);
+ DocumentBuilder b = dbf.newDocumentBuilder();
+ org.w3c.dom.Document doc = b.parse(new FileInputStream(
+ infoPList.getAbsolutePath()));
+
+ XPath xPath = XPathFactory.newInstance().newXPath();
+ // Query for the value of <string> element preceding <key>
+ // element with value equal to CFBundleIdentifier
+ String v = (String) xPath.evaluate(
+ "//string[preceding-sibling::key = \"CFBundleIdentifier\"][1]",
+ doc, XPathConstants.STRING);
+
+ if (v != null && !v.isEmpty()) {
+ return v;
+ }
+ } catch (Exception ex) {
+ Log.verbose(ex);
+ }
+
+ return null;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/MacAppStoreBundler.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.*;
+
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+import static jdk.incubator.jpackage.internal.MacAppBundler.*;
+import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
+
+public class MacAppStoreBundler extends MacBaseInstallerBundler {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.incubator.jpackage.internal.resources.MacResources");
+
+ private static final String TEMPLATE_BUNDLE_ICON_HIDPI = "java.icns";
+ private final static String DEFAULT_ENTITLEMENTS =
+ "MacAppStore.entitlements";
+ private final static String DEFAULT_INHERIT_ENTITLEMENTS =
+ "MacAppStore_Inherit.entitlements";
+
+ public static final BundlerParamInfo<String> MAC_APP_STORE_APP_SIGNING_KEY =
+ new StandardBundlerParam<>(
+ "mac.signing-key-app",
+ String.class,
+ params -> {
+ String result = MacBaseInstallerBundler.findKey(
+ "3rd Party Mac Developer Application: " +
+ SIGNING_KEY_USER.fetchFrom(params),
+ SIGNING_KEYCHAIN.fetchFrom(params),
+ VERBOSE.fetchFrom(params));
+ if (result != null) {
+ MacCertificate certificate = new MacCertificate(result);
+
+ if (!certificate.isValid()) {
+ Log.error(MessageFormat.format(
+ I18N.getString("error.certificate.expired"),
+ result));
+ }
+ }
+
+ return result;
+ },
+ (s, p) -> s);
+
+ public static final BundlerParamInfo<String> MAC_APP_STORE_PKG_SIGNING_KEY =
+ new StandardBundlerParam<>(
+ "mac.signing-key-pkg",
+ String.class,
+ params -> {
+ String result = MacBaseInstallerBundler.findKey(
+ "3rd Party Mac Developer Installer: " +
+ SIGNING_KEY_USER.fetchFrom(params),
+ SIGNING_KEYCHAIN.fetchFrom(params),
+ VERBOSE.fetchFrom(params));
+
+ if (result != null) {
+ MacCertificate certificate = new MacCertificate(result);
+
+ if (!certificate.isValid()) {
+ Log.error(MessageFormat.format(
+ I18N.getString("error.certificate.expired"),
+ result));
+ }
+ }
+
+ return result;
+ },
+ (s, p) -> s);
+
+ public static final StandardBundlerParam<File> MAC_APP_STORE_ENTITLEMENTS =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.MAC_APP_STORE_ENTITLEMENTS.getId(),
+ File.class,
+ params -> null,
+ (s, p) -> new File(s));
+
+ public static final BundlerParamInfo<String> INSTALLER_SUFFIX =
+ new StandardBundlerParam<> (
+ "mac.app-store.installerName.suffix",
+ String.class,
+ params -> "-MacAppStore",
+ (s, p) -> s);
+
+ public File bundle(Map<String, ? super Object> params,
+ File outdir) throws PackagerException {
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.building-bundle"), APP_NAME.fetchFrom(params)));
+
+ IOUtils.writableOutputDir(outdir.toPath());
+
+ // first, load in some overrides
+ // icns needs @2 versions, so load in the @2 default
+ params.put(DEFAULT_ICNS_ICON.getID(), TEMPLATE_BUNDLE_ICON_HIDPI);
+
+ // now we create the app
+ File appImageDir = APP_IMAGE_TEMP_ROOT.fetchFrom(params);
+ try {
+ appImageDir.mkdirs();
+
+ try {
+ MacAppImageBuilder.addNewKeychain(params);
+ } catch (InterruptedException e) {
+ Log.error(e.getMessage());
+ }
+ // first, make sure we don't use the local signing key
+ params.put(DEVELOPER_ID_APP_SIGNING_KEY.getID(), null);
+ File appLocation = prepareAppBundle(params);
+
+ prepareEntitlements(params);
+
+ String signingIdentity =
+ MAC_APP_STORE_APP_SIGNING_KEY.fetchFrom(params);
+ String identifierPrefix =
+ BUNDLE_ID_SIGNING_PREFIX.fetchFrom(params);
+ String entitlementsFile =
+ getConfig_Entitlements(params).toString();
+ String inheritEntitlements =
+ getConfig_Inherit_Entitlements(params).toString();
+
+ MacAppImageBuilder.signAppBundle(params, appLocation.toPath(),
+ signingIdentity, identifierPrefix,
+ entitlementsFile, inheritEntitlements);
+ MacAppImageBuilder.restoreKeychainList(params);
+
+ ProcessBuilder pb;
+
+ // create the final pkg file
+ File finalPKG = new File(outdir, INSTALLER_NAME.fetchFrom(params)
+ + INSTALLER_SUFFIX.fetchFrom(params)
+ + ".pkg");
+ outdir.mkdirs();
+
+ String installIdentify =
+ MAC_APP_STORE_PKG_SIGNING_KEY.fetchFrom(params);
+
+ List<String> buildOptions = new ArrayList<>();
+ buildOptions.add("productbuild");
+ buildOptions.add("--component");
+ buildOptions.add(appLocation.toString());
+ buildOptions.add("/Applications");
+ buildOptions.add("--sign");
+ buildOptions.add(installIdentify);
+ buildOptions.add("--product");
+ buildOptions.add(appLocation + "/Contents/Info.plist");
+ String keychainName = SIGNING_KEYCHAIN.fetchFrom(params);
+ if (keychainName != null && !keychainName.isEmpty()) {
+ buildOptions.add("--keychain");
+ buildOptions.add(keychainName);
+ }
+ buildOptions.add(finalPKG.getAbsolutePath());
+
+ pb = new ProcessBuilder(buildOptions);
+
+ IOUtils.exec(pb);
+ return finalPKG;
+ } catch (PackagerException pe) {
+ throw pe;
+ } catch (Exception ex) {
+ Log.verbose(ex);
+ throw new PackagerException(ex);
+ }
+ }
+
+ private File getConfig_Entitlements(Map<String, ? super Object> params) {
+ return new File(CONFIG_ROOT.fetchFrom(params),
+ APP_NAME.fetchFrom(params) + ".entitlements");
+ }
+
+ private File getConfig_Inherit_Entitlements(
+ Map<String, ? super Object> params) {
+ return new File(CONFIG_ROOT.fetchFrom(params),
+ APP_NAME.fetchFrom(params) + "_Inherit.entitlements");
+ }
+
+ private void prepareEntitlements(Map<String, ? super Object> params)
+ throws IOException {
+ createResource(DEFAULT_ENTITLEMENTS, params)
+ .setCategory(
+ I18N.getString("resource.mac-app-store-entitlements"))
+ .setExternal(MAC_APP_STORE_ENTITLEMENTS.fetchFrom(params))
+ .saveToFile(getConfig_Entitlements(params));
+
+ createResource(DEFAULT_INHERIT_ENTITLEMENTS, params)
+ .setCategory(I18N.getString(
+ "resource.mac-app-store-inherit-entitlements"))
+ .saveToFile(getConfig_Entitlements(params));
+ }
+
+ ///////////////////////////////////////////////////////////////////////
+ // Implement Bundler
+ ///////////////////////////////////////////////////////////////////////
+
+ @Override
+ public String getName() {
+ return I18N.getString("store.bundler.name");
+ }
+
+ @Override
+ public String getID() {
+ return "mac.appStore";
+ }
+
+ @Override
+ public boolean validate(Map<String, ? super Object> params)
+ throws ConfigException {
+ try {
+ Objects.requireNonNull(params);
+
+ // hdiutil is always available so there's no need to test for
+ // availability.
+ // run basic validation to ensure requirements are met
+
+ // we are not interested in return code, only possible exception
+ validateAppImageAndBundeler(params);
+
+ // reject explicitly set to not sign
+ if (!Optional.ofNullable(MacAppImageBuilder.
+ SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.TRUE)) {
+ throw new ConfigException(
+ I18N.getString("error.must-sign-app-store"),
+ I18N.getString("error.must-sign-app-store.advice"));
+ }
+
+ // make sure we have settings for signatures
+ if (MAC_APP_STORE_APP_SIGNING_KEY.fetchFrom(params) == null) {
+ throw new ConfigException(
+ I18N.getString("error.no-app-signing-key"),
+ I18N.getString("error.no-app-signing-key.advice"));
+ }
+ if (MAC_APP_STORE_PKG_SIGNING_KEY.fetchFrom(params) == null) {
+ throw new ConfigException(
+ I18N.getString("error.no-pkg-signing-key"),
+ I18N.getString("error.no-pkg-signing-key.advice"));
+ }
+
+ // things we could check...
+ // check the icons, make sure it has hidpi icons
+ // check the category,
+ // make sure it fits in the list apple has provided
+ // validate bundle identifier is reverse dns
+ // check for \a+\.\a+\..
+
+ return true;
+ } catch (RuntimeException re) {
+ if (re.getCause() instanceof ConfigException) {
+ throw (ConfigException) re.getCause();
+ } else {
+ throw new ConfigException(re);
+ }
+ }
+ }
+
+ @Override
+ public File execute(Map<String, ? super Object> params,
+ File outputParentDir) throws PackagerException {
+ return bundle(params, outputParentDir);
+ }
+
+ @Override
+ public boolean supported(boolean runtimeInstaller) {
+ // return (!runtimeInstaller &&
+ // Platform.getPlatform() == Platform.MAC);
+ return false; // mac-app-store not yet supported
+ }
+
+ @Override
+ public boolean isDefault() {
+ return false;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/MacBaseInstallerBundler.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+
+public abstract class MacBaseInstallerBundler extends AbstractBundler {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.incubator.jpackage.internal.resources.MacResources");
+
+ // This could be generalized more to be for any type of Image Bundler
+ public static final BundlerParamInfo<MacAppBundler> APP_BUNDLER =
+ new StandardBundlerParam<>(
+ "mac.app.bundler",
+ MacAppBundler.class,
+ params -> new MacAppBundler(),
+ (s, p) -> null);
+
+ public final BundlerParamInfo<File> APP_IMAGE_TEMP_ROOT =
+ new StandardBundlerParam<>(
+ "mac.app.imageRoot",
+ File.class,
+ params -> {
+ File imageDir = IMAGES_ROOT.fetchFrom(params);
+ if (!imageDir.exists()) imageDir.mkdirs();
+ try {
+ return Files.createTempDirectory(
+ imageDir.toPath(), "image-").toFile();
+ } catch (IOException e) {
+ return new File(imageDir, getID()+ ".image");
+ }
+ },
+ (s, p) -> new File(s));
+
+ public static final BundlerParamInfo<String> SIGNING_KEY_USER =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.MAC_SIGNING_KEY_NAME.getId(),
+ String.class,
+ params -> "",
+ null);
+
+ public static final BundlerParamInfo<String> SIGNING_KEYCHAIN =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.MAC_SIGNING_KEYCHAIN.getId(),
+ String.class,
+ params -> "",
+ null);
+
+ public static final BundlerParamInfo<String> INSTALLER_NAME =
+ new StandardBundlerParam<> (
+ "mac.installerName",
+ String.class,
+ params -> {
+ String nm = APP_NAME.fetchFrom(params);
+ if (nm == null) return null;
+
+ String version = VERSION.fetchFrom(params);
+ if (version == null) {
+ return nm;
+ } else {
+ return nm + "-" + version;
+ }
+ },
+ (s, p) -> s);
+
+ protected void validateAppImageAndBundeler(
+ Map<String, ? super Object> params) throws ConfigException {
+ if (PREDEFINED_APP_IMAGE.fetchFrom(params) != null) {
+ File applicationImage = PREDEFINED_APP_IMAGE.fetchFrom(params);
+ if (!applicationImage.exists()) {
+ throw new ConfigException(
+ MessageFormat.format(I18N.getString(
+ "message.app-image-dir-does-not-exist"),
+ PREDEFINED_APP_IMAGE.getID(),
+ applicationImage.toString()),
+ MessageFormat.format(I18N.getString(
+ "message.app-image-dir-does-not-exist.advice"),
+ PREDEFINED_APP_IMAGE.getID()));
+ }
+ if (APP_NAME.fetchFrom(params) == null) {
+ throw new ConfigException(
+ I18N.getString("message.app-image-requires-app-name"),
+ I18N.getString(
+ "message.app-image-requires-app-name.advice"));
+ }
+ } else {
+ APP_BUNDLER.fetchFrom(params).validate(params);
+ }
+ }
+
+ protected File prepareAppBundle(Map<String, ? super Object> params)
+ throws PackagerException {
+ File predefinedImage =
+ StandardBundlerParam.getPredefinedAppImage(params);
+ if (predefinedImage != null) {
+ return predefinedImage;
+ }
+ File appImageRoot = APP_IMAGE_TEMP_ROOT.fetchFrom(params);
+
+ return APP_BUNDLER.fetchFrom(params).doBundle(
+ params, appImageRoot, true);
+ }
+
+ @Override
+ public String getBundleType() {
+ return "INSTALLER";
+ }
+
+ public static String findKey(String key, String keychainName,
+ boolean verbose) {
+ if (Platform.getPlatform() != Platform.MAC) {
+ return null;
+ }
+
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(baos)) {
+ List<String> searchOptions = new ArrayList<>();
+ searchOptions.add("security");
+ searchOptions.add("find-certificate");
+ searchOptions.add("-c");
+ searchOptions.add(key);
+ searchOptions.add("-a");
+ if (keychainName != null && !keychainName.isEmpty()) {
+ searchOptions.add(keychainName);
+ }
+
+ ProcessBuilder pb = new ProcessBuilder(searchOptions);
+
+ IOUtils.exec(pb, false, ps);
+ Pattern p = Pattern.compile("\"alis\"<blob>=\"([^\"]+)\"");
+ Matcher m = p.matcher(baos.toString());
+ if (!m.find()) {
+ Log.error("Did not find a key matching '" + key + "'");
+ return null;
+ }
+ String matchedKey = m.group(1);
+ if (m.find()) {
+ Log.error("Found more than one key matching '" + key + "'");
+ return null;
+ }
+ Log.verbose("Using key '" + matchedKey + "'");
+ return matchedKey;
+ } catch (IOException ioe) {
+ Log.verbose(ioe);
+ return null;
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/MacCertificate.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.Files;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+public final class MacCertificate {
+ private final String certificate;
+
+ public MacCertificate(String certificate) {
+ this.certificate = certificate;
+ }
+
+ public boolean isValid() {
+ return verifyCertificate(this.certificate);
+ }
+
+ private static File findCertificate(String certificate) {
+ File result = null;
+
+ List<String> args = new ArrayList<>();
+ args.add("security");
+ args.add("find-certificate");
+ args.add("-c");
+ args.add(certificate);
+ args.add("-a");
+ args.add("-p");
+
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(baos)) {
+ ProcessBuilder security = new ProcessBuilder(args);
+ IOUtils.exec(security, false, ps);
+
+ File output = File.createTempFile("tempfile", ".tmp");
+
+ Files.copy(new ByteArrayInputStream(baos.toByteArray()),
+ output.toPath(), StandardCopyOption.REPLACE_EXISTING);
+
+ result = output;
+ }
+ catch (IOException ignored) {}
+
+ return result;
+ }
+
+ private static Date findCertificateDate(String filename) {
+ Date result = null;
+
+ List<String> args = new ArrayList<>();
+ args.add("/usr/bin/openssl");
+ args.add("x509");
+ args.add("-noout");
+ args.add("-enddate");
+ args.add("-in");
+ args.add(filename);
+
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(baos)) {
+ ProcessBuilder security = new ProcessBuilder(args);
+ IOUtils.exec(security, false, ps);
+ String output = baos.toString();
+ output = output.substring(output.indexOf("=") + 1);
+ DateFormat df = new SimpleDateFormat(
+ "MMM dd kk:mm:ss yyyy z", Locale.ENGLISH);
+ result = df.parse(output);
+ } catch (IOException | ParseException ex) {
+ Log.verbose(ex);
+ }
+
+ return result;
+ }
+
+ private static boolean verifyCertificate(String certificate) {
+ boolean result = false;
+
+ try {
+ File file = null;
+ Date certificateDate = null;
+
+ try {
+ file = findCertificate(certificate);
+
+ if (file != null) {
+ certificateDate = findCertificateDate(
+ file.getCanonicalPath());
+ }
+ }
+ finally {
+ if (file != null) {
+ file.delete();
+ }
+ }
+
+ if (certificateDate != null) {
+ Calendar c = Calendar.getInstance();
+ Date today = c.getTime();
+
+ if (certificateDate.after(today)) {
+ result = true;
+ }
+ }
+ }
+ catch (IOException ignored) {}
+
+ return result;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/MacDmgBundler.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,480 @@
+/*
+ * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.text.MessageFormat;
+import java.util.*;
+import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
+
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+
+public class MacDmgBundler extends MacBaseInstallerBundler {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.incubator.jpackage.internal.resources.MacResources");
+
+ static final String DEFAULT_BACKGROUND_IMAGE="background_dmg.png";
+ static final String DEFAULT_DMG_SETUP_SCRIPT="DMGsetup.scpt";
+ static final String TEMPLATE_BUNDLE_ICON = "java.icns";
+
+ static final String DEFAULT_LICENSE_PLIST="lic_template.plist";
+
+ public static final BundlerParamInfo<String> INSTALLER_SUFFIX =
+ new StandardBundlerParam<> (
+ "mac.dmg.installerName.suffix",
+ String.class,
+ params -> "",
+ (s, p) -> s);
+
+ public File bundle(Map<String, ? super Object> params,
+ File outdir) throws PackagerException {
+ Log.verbose(MessageFormat.format(I18N.getString("message.building-dmg"),
+ APP_NAME.fetchFrom(params)));
+
+ IOUtils.writableOutputDir(outdir.toPath());
+
+ File appImageDir = APP_IMAGE_TEMP_ROOT.fetchFrom(params);
+ try {
+ appImageDir.mkdirs();
+
+ if (prepareAppBundle(params) != null &&
+ prepareConfigFiles(params)) {
+ File configScript = getConfig_Script(params);
+ if (configScript.exists()) {
+ Log.verbose(MessageFormat.format(
+ I18N.getString("message.running-script"),
+ configScript.getAbsolutePath()));
+ IOUtils.run("bash", configScript);
+ }
+
+ return buildDMG(params, outdir);
+ }
+ return null;
+ } catch (IOException ex) {
+ Log.verbose(ex);
+ throw new PackagerException(ex);
+ }
+ }
+
+ private static final String hdiutil = "/usr/bin/hdiutil";
+
+ private void prepareDMGSetupScript(String volumeName,
+ Map<String, ? super Object> params) throws IOException {
+ File dmgSetup = getConfig_VolumeScript(params);
+ Log.verbose(MessageFormat.format(
+ I18N.getString("message.preparing-dmg-setup"),
+ dmgSetup.getAbsolutePath()));
+
+ //prepare config for exe
+ Map<String, String> data = new HashMap<>();
+ data.put("DEPLOY_ACTUAL_VOLUME_NAME", volumeName);
+ data.put("DEPLOY_APPLICATION_NAME", APP_NAME.fetchFrom(params));
+
+ data.put("DEPLOY_INSTALL_LOCATION", "(path to applications folder)");
+ data.put("DEPLOY_INSTALL_NAME", "Applications");
+
+ createResource(DEFAULT_DMG_SETUP_SCRIPT, params)
+ .setCategory(I18N.getString("resource.dmg-setup-script"))
+ .setSubstitutionData(data)
+ .saveToFile(dmgSetup);
+ }
+
+ private File getConfig_VolumeScript(Map<String, ? super Object> params) {
+ return new File(CONFIG_ROOT.fetchFrom(params),
+ APP_NAME.fetchFrom(params) + "-dmg-setup.scpt");
+ }
+
+ private File getConfig_VolumeBackground(
+ Map<String, ? super Object> params) {
+ return new File(CONFIG_ROOT.fetchFrom(params),
+ APP_NAME.fetchFrom(params) + "-background.png");
+ }
+
+ private File getConfig_VolumeIcon(Map<String, ? super Object> params) {
+ return new File(CONFIG_ROOT.fetchFrom(params),
+ APP_NAME.fetchFrom(params) + "-volume.icns");
+ }
+
+ private File getConfig_LicenseFile(Map<String, ? super Object> params) {
+ return new File(CONFIG_ROOT.fetchFrom(params),
+ APP_NAME.fetchFrom(params) + "-license.plist");
+ }
+
+ private void prepareLicense(Map<String, ? super Object> params) {
+ try {
+ String licFileStr = LICENSE_FILE.fetchFrom(params);
+ if (licFileStr == null) {
+ return;
+ }
+
+ File licFile = new File(licFileStr);
+ byte[] licenseContentOriginal =
+ Files.readAllBytes(licFile.toPath());
+ String licenseInBase64 =
+ Base64.getEncoder().encodeToString(licenseContentOriginal);
+
+ Map<String, String> data = new HashMap<>();
+ data.put("APPLICATION_LICENSE_TEXT", licenseInBase64);
+
+ createResource(DEFAULT_LICENSE_PLIST, params)
+ .setCategory(I18N.getString("resource.license-setup"))
+ .setSubstitutionData(data)
+ .saveToFile(getConfig_LicenseFile(params));
+
+ } catch (IOException ex) {
+ Log.verbose(ex);
+ }
+ }
+
+ private boolean prepareConfigFiles(Map<String, ? super Object> params)
+ throws IOException {
+
+ createResource(DEFAULT_BACKGROUND_IMAGE, params)
+ .setCategory(I18N.getString("resource.dmg-background"))
+ .saveToFile(getConfig_VolumeBackground(params));
+
+ createResource(TEMPLATE_BUNDLE_ICON, params)
+ .setCategory(I18N.getString("resource.volume-icon"))
+ .setExternal(MacAppBundler.ICON_ICNS.fetchFrom(params))
+ .saveToFile(getConfig_VolumeIcon(params));
+
+ createResource(null, params)
+ .setCategory(I18N.getString("resource.post-install-script"))
+ .saveToFile(getConfig_Script(params));
+
+ prepareLicense(params);
+
+ // In theory we need to extract name from results of attach command
+ // However, this will be a problem for customization as name will
+ // possibly change every time and developer will not be able to fix it
+ // As we are using tmp dir chance we get "different" name are low =>
+ // Use fixed name we used for bundle
+ prepareDMGSetupScript(APP_NAME.fetchFrom(params), params);
+
+ return true;
+ }
+
+ // name of post-image script
+ private File getConfig_Script(Map<String, ? super Object> params) {
+ return new File(CONFIG_ROOT.fetchFrom(params),
+ APP_NAME.fetchFrom(params) + "-post-image.sh");
+ }
+
+ // Location of SetFile utility may be different depending on MacOS version
+ // We look for several known places and if none of them work will
+ // try ot find it
+ private String findSetFileUtility() {
+ String typicalPaths[] = {"/Developer/Tools/SetFile",
+ "/usr/bin/SetFile", "/Developer/usr/bin/SetFile"};
+
+ String setFilePath = null;
+ for (String path: typicalPaths) {
+ File f = new File(path);
+ if (f.exists() && f.canExecute()) {
+ setFilePath = path;
+ break;
+ }
+ }
+
+ // Validate SetFile, if Xcode is not installed it will run, but exit with error
+ // code
+ if (setFilePath != null) {
+ try {
+ ProcessBuilder pb = new ProcessBuilder(setFilePath, "-h");
+ Process p = pb.start();
+ int code = p.waitFor();
+ if (code == 0) {
+ return setFilePath;
+ }
+ } catch (Exception ignored) {}
+
+ // No need for generic find attempt. We found it, but it does not work.
+ // Probably due to missing xcode.
+ return null;
+ }
+
+ // generic find attempt
+ try {
+ ProcessBuilder pb = new ProcessBuilder("xcrun", "-find", "SetFile");
+ Process p = pb.start();
+ InputStreamReader isr = new InputStreamReader(p.getInputStream());
+ BufferedReader br = new BufferedReader(isr);
+ String lineRead = br.readLine();
+ if (lineRead != null) {
+ File f = new File(lineRead);
+ if (f.exists() && f.canExecute()) {
+ return f.getAbsolutePath();
+ }
+ }
+ } catch (IOException ignored) {}
+
+ return null;
+ }
+
+ private File buildDMG(
+ Map<String, ? super Object> params, File outdir)
+ throws IOException {
+ File imagesRoot = IMAGES_ROOT.fetchFrom(params);
+ if (!imagesRoot.exists()) imagesRoot.mkdirs();
+
+ File protoDMG = new File(imagesRoot,
+ APP_NAME.fetchFrom(params) +"-tmp.dmg");
+ File finalDMG = new File(outdir, INSTALLER_NAME.fetchFrom(params)
+ + INSTALLER_SUFFIX.fetchFrom(params) + ".dmg");
+
+ File srcFolder = APP_IMAGE_TEMP_ROOT.fetchFrom(params);
+ File predefinedImage =
+ StandardBundlerParam.getPredefinedAppImage(params);
+ if (predefinedImage != null) {
+ srcFolder = predefinedImage;
+ }
+
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.creating-dmg-file"), finalDMG.getAbsolutePath()));
+
+ protoDMG.delete();
+ if (finalDMG.exists() && !finalDMG.delete()) {
+ throw new IOException(MessageFormat.format(I18N.getString(
+ "message.dmg-cannot-be-overwritten"),
+ finalDMG.getAbsolutePath()));
+ }
+
+ protoDMG.getParentFile().mkdirs();
+ finalDMG.getParentFile().mkdirs();
+
+ String hdiUtilVerbosityFlag = VERBOSE.fetchFrom(params) ?
+ "-verbose" : "-quiet";
+
+ // create temp image
+ ProcessBuilder pb = new ProcessBuilder(
+ hdiutil,
+ "create",
+ hdiUtilVerbosityFlag,
+ "-srcfolder", srcFolder.getAbsolutePath(),
+ "-volname", APP_NAME.fetchFrom(params),
+ "-ov", protoDMG.getAbsolutePath(),
+ "-fs", "HFS+",
+ "-format", "UDRW");
+ IOUtils.exec(pb);
+
+ // mount temp image
+ pb = new ProcessBuilder(
+ hdiutil,
+ "attach",
+ protoDMG.getAbsolutePath(),
+ hdiUtilVerbosityFlag,
+ "-mountroot", imagesRoot.getAbsolutePath());
+ IOUtils.exec(pb);
+
+ File mountedRoot = new File(imagesRoot.getAbsolutePath(),
+ APP_NAME.fetchFrom(params));
+
+ try {
+ // volume icon
+ File volumeIconFile = new File(mountedRoot, ".VolumeIcon.icns");
+ IOUtils.copyFile(getConfig_VolumeIcon(params),
+ volumeIconFile);
+
+ // background image
+ File bgdir = new File(mountedRoot, ".background");
+ bgdir.mkdirs();
+ IOUtils.copyFile(getConfig_VolumeBackground(params),
+ new File(bgdir, "background.png"));
+
+ // Indicate that we want a custom icon
+ // NB: attributes of the root directory are ignored
+ // when creating the volume
+ // Therefore we have to do this after we mount image
+ String setFileUtility = findSetFileUtility();
+ if (setFileUtility != null) {
+ //can not find utility => keep going without icon
+ try {
+ volumeIconFile.setWritable(true);
+ // The "creator" attribute on a file is a legacy attribute
+ // but it seems Finder excepts these bytes to be
+ // "icnC" for the volume icon
+ // (might not work on Mac 10.13 with old XCode)
+ pb = new ProcessBuilder(
+ setFileUtility,
+ "-c", "icnC",
+ volumeIconFile.getAbsolutePath());
+ IOUtils.exec(pb);
+ volumeIconFile.setReadOnly();
+
+ pb = new ProcessBuilder(
+ setFileUtility,
+ "-a", "C",
+ mountedRoot.getAbsolutePath());
+ IOUtils.exec(pb);
+ } catch (IOException ex) {
+ Log.error(ex.getMessage());
+ Log.verbose("Cannot enable custom icon using SetFile utility");
+ }
+ } else {
+ Log.verbose(I18N.getString("message.setfile.dmg"));
+ }
+
+ // We will not consider setting background image and creating link to
+ // /Application folder in DMG as critical error, since it can fail in
+ // headless enviroment.
+ try {
+ pb = new ProcessBuilder("osascript",
+ getConfig_VolumeScript(params).getAbsolutePath());
+ IOUtils.exec(pb);
+ } catch (IOException ex) {
+ Log.verbose(ex);
+ }
+ } finally {
+ // Detach the temporary image
+ pb = new ProcessBuilder(
+ hdiutil,
+ "detach",
+ "-force",
+ hdiUtilVerbosityFlag,
+ mountedRoot.getAbsolutePath());
+ IOUtils.exec(pb);
+ }
+
+ // Compress it to a new image
+ pb = new ProcessBuilder(
+ hdiutil,
+ "convert",
+ protoDMG.getAbsolutePath(),
+ hdiUtilVerbosityFlag,
+ "-format", "UDZO",
+ "-o", finalDMG.getAbsolutePath());
+ IOUtils.exec(pb);
+
+ //add license if needed
+ if (getConfig_LicenseFile(params).exists()) {
+ //hdiutil unflatten your_image_file.dmg
+ pb = new ProcessBuilder(
+ hdiutil,
+ "unflatten",
+ finalDMG.getAbsolutePath()
+ );
+ IOUtils.exec(pb);
+
+ //add license
+ pb = new ProcessBuilder(
+ hdiutil,
+ "udifrez",
+ finalDMG.getAbsolutePath(),
+ "-xml",
+ getConfig_LicenseFile(params).getAbsolutePath()
+ );
+ IOUtils.exec(pb);
+
+ //hdiutil flatten your_image_file.dmg
+ pb = new ProcessBuilder(
+ hdiutil,
+ "flatten",
+ finalDMG.getAbsolutePath()
+ );
+ IOUtils.exec(pb);
+
+ }
+
+ //Delete the temporary image
+ protoDMG.delete();
+
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.output-to-location"),
+ APP_NAME.fetchFrom(params), finalDMG.getAbsolutePath()));
+
+ return finalDMG;
+ }
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // Implement Bundler
+ //////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public String getName() {
+ return I18N.getString("dmg.bundler.name");
+ }
+
+ @Override
+ public String getID() {
+ return "dmg";
+ }
+
+ @Override
+ public boolean validate(Map<String, ? super Object> params)
+ throws ConfigException {
+ try {
+ Objects.requireNonNull(params);
+
+ //run basic validation to ensure requirements are met
+ //we are not interested in return code, only possible exception
+ validateAppImageAndBundeler(params);
+
+ return true;
+ } catch (RuntimeException re) {
+ if (re.getCause() instanceof ConfigException) {
+ throw (ConfigException) re.getCause();
+ } else {
+ throw new ConfigException(re);
+ }
+ }
+ }
+
+ @Override
+ public File execute(Map<String, ? super Object> params,
+ File outputParentDir) throws PackagerException {
+ return bundle(params, outputParentDir);
+ }
+
+ @Override
+ public boolean supported(boolean runtimeInstaller) {
+ return isSupported();
+ }
+
+ public final static String[] required =
+ {"/usr/bin/hdiutil", "/usr/bin/osascript"};
+ public static boolean isSupported() {
+ try {
+ for (String s : required) {
+ File f = new File(s);
+ if (!f.exists() || !f.canExecute()) {
+ return false;
+ }
+ }
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isDefault() {
+ return true;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/MacPkgBundler.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,555 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.*;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.text.MessageFormat;
+import java.util.*;
+
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+import static jdk.incubator.jpackage.internal.MacBaseInstallerBundler.SIGNING_KEYCHAIN;
+import static jdk.incubator.jpackage.internal.MacBaseInstallerBundler.SIGNING_KEY_USER;
+import static jdk.incubator.jpackage.internal.MacAppImageBuilder.MAC_CF_BUNDLE_IDENTIFIER;
+import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
+
+public class MacPkgBundler extends MacBaseInstallerBundler {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.incubator.jpackage.internal.resources.MacResources");
+
+ private static final String DEFAULT_BACKGROUND_IMAGE = "background_pkg.png";
+
+ private static final String TEMPLATE_PREINSTALL_SCRIPT =
+ "preinstall.template";
+ private static final String TEMPLATE_POSTINSTALL_SCRIPT =
+ "postinstall.template";
+
+ private static final BundlerParamInfo<File> PACKAGES_ROOT =
+ new StandardBundlerParam<>(
+ "mac.pkg.packagesRoot",
+ File.class,
+ params -> {
+ File packagesRoot =
+ new File(TEMP_ROOT.fetchFrom(params), "packages");
+ packagesRoot.mkdirs();
+ return packagesRoot;
+ },
+ (s, p) -> new File(s));
+
+
+ protected final BundlerParamInfo<File> SCRIPTS_DIR =
+ new StandardBundlerParam<>(
+ "mac.pkg.scriptsDir",
+ File.class,
+ params -> {
+ File scriptsDir =
+ new File(CONFIG_ROOT.fetchFrom(params), "scripts");
+ scriptsDir.mkdirs();
+ return scriptsDir;
+ },
+ (s, p) -> new File(s));
+
+ public static final
+ BundlerParamInfo<String> DEVELOPER_ID_INSTALLER_SIGNING_KEY =
+ new StandardBundlerParam<>(
+ "mac.signing-key-developer-id-installer",
+ String.class,
+ params -> {
+ String result = MacBaseInstallerBundler.findKey(
+ "Developer ID Installer: "
+ + SIGNING_KEY_USER.fetchFrom(params),
+ SIGNING_KEYCHAIN.fetchFrom(params),
+ VERBOSE.fetchFrom(params));
+ if (result != null) {
+ MacCertificate certificate = new MacCertificate(result);
+
+ if (!certificate.isValid()) {
+ Log.error(MessageFormat.format(
+ I18N.getString("error.certificate.expired"),
+ result));
+ }
+ }
+
+ return result;
+ },
+ (s, p) -> s);
+
+ public static final BundlerParamInfo<String> MAC_INSTALL_DIR =
+ new StandardBundlerParam<>(
+ "mac-install-dir",
+ String.class,
+ params -> {
+ String dir = INSTALL_DIR.fetchFrom(params);
+ return (dir != null) ? dir : "/Applications";
+ },
+ (s, p) -> s
+ );
+
+ public static final BundlerParamInfo<String> INSTALLER_SUFFIX =
+ new StandardBundlerParam<> (
+ "mac.pkg.installerName.suffix",
+ String.class,
+ params -> "",
+ (s, p) -> s);
+
+ public File bundle(Map<String, ? super Object> params,
+ File outdir) throws PackagerException {
+ Log.verbose(MessageFormat.format(I18N.getString("message.building-pkg"),
+ APP_NAME.fetchFrom(params)));
+
+ IOUtils.writableOutputDir(outdir.toPath());
+
+ try {
+ File appImageDir = prepareAppBundle(params);
+
+ if (appImageDir != null && prepareConfigFiles(params)) {
+
+ File configScript = getConfig_Script(params);
+ if (configScript.exists()) {
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.running-script"),
+ configScript.getAbsolutePath()));
+ IOUtils.run("bash", configScript);
+ }
+
+ return createPKG(params, outdir, appImageDir);
+ }
+ return null;
+ } catch (IOException ex) {
+ Log.verbose(ex);
+ throw new PackagerException(ex);
+ }
+ }
+
+ private File getPackages_AppPackage(Map<String, ? super Object> params) {
+ return new File(PACKAGES_ROOT.fetchFrom(params),
+ APP_NAME.fetchFrom(params) + "-app.pkg");
+ }
+
+ private File getConfig_DistributionXMLFile(
+ Map<String, ? super Object> params) {
+ return new File(CONFIG_ROOT.fetchFrom(params), "distribution.dist");
+ }
+
+ private File getConfig_BackgroundImage(Map<String, ? super Object> params) {
+ return new File(CONFIG_ROOT.fetchFrom(params),
+ APP_NAME.fetchFrom(params) + "-background.png");
+ }
+
+ private File getConfig_BackgroundImageDarkAqua(Map<String, ? super Object> params) {
+ return new File(CONFIG_ROOT.fetchFrom(params),
+ APP_NAME.fetchFrom(params) + "-background-darkAqua.png");
+ }
+
+ private File getScripts_PreinstallFile(Map<String, ? super Object> params) {
+ return new File(SCRIPTS_DIR.fetchFrom(params), "preinstall");
+ }
+
+ private File getScripts_PostinstallFile(
+ Map<String, ? super Object> params) {
+ return new File(SCRIPTS_DIR.fetchFrom(params), "postinstall");
+ }
+
+ private String getAppIdentifier(Map<String, ? super Object> params) {
+ return MAC_CF_BUNDLE_IDENTIFIER.fetchFrom(params);
+ }
+
+ private void preparePackageScripts(Map<String, ? super Object> params)
+ throws IOException {
+ Log.verbose(I18N.getString("message.preparing-scripts"));
+
+ Map<String, String> data = new HashMap<>();
+
+ Path appLocation = Path.of(MAC_INSTALL_DIR.fetchFrom(params),
+ APP_NAME.fetchFrom(params) + ".app", "Contents", "app");
+
+ data.put("INSTALL_LOCATION", MAC_INSTALL_DIR.fetchFrom(params));
+ data.put("APP_LOCATION", appLocation.toString());
+
+ createResource(TEMPLATE_PREINSTALL_SCRIPT, params)
+ .setCategory(I18N.getString("resource.pkg-preinstall-script"))
+ .setSubstitutionData(data)
+ .saveToFile(getScripts_PreinstallFile(params));
+ getScripts_PreinstallFile(params).setExecutable(true, false);
+
+ createResource(TEMPLATE_POSTINSTALL_SCRIPT, params)
+ .setCategory(I18N.getString("resource.pkg-postinstall-script"))
+ .setSubstitutionData(data)
+ .saveToFile(getScripts_PostinstallFile(params));
+ getScripts_PostinstallFile(params).setExecutable(true, false);
+ }
+
+ private static String URLEncoding(String pkgName) throws URISyntaxException {
+ URI uri = new URI(null, null, pkgName, null);
+ return uri.toASCIIString();
+ }
+
+ private void prepareDistributionXMLFile(Map<String, ? super Object> params)
+ throws IOException {
+ File f = getConfig_DistributionXMLFile(params);
+
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.preparing-distribution-dist"), f.getAbsolutePath()));
+
+ IOUtils.createXml(f.toPath(), xml -> {
+ xml.writeStartElement("installer-gui-script");
+ xml.writeAttribute("minSpecVersion", "1");
+
+ xml.writeStartElement("title");
+ xml.writeCharacters(APP_NAME.fetchFrom(params));
+ xml.writeEndElement();
+
+ xml.writeStartElement("background");
+ xml.writeAttribute("file", getConfig_BackgroundImage(params).getName());
+ xml.writeAttribute("mime-type", "image/png");
+ xml.writeAttribute("alignment", "bottomleft");
+ xml.writeAttribute("scaling", "none");
+ xml.writeEndElement();
+
+ xml.writeStartElement("background-darkAqua");
+ xml.writeAttribute("file", getConfig_BackgroundImageDarkAqua(params).getName());
+ xml.writeAttribute("mime-type", "image/png");
+ xml.writeAttribute("alignment", "bottomleft");
+ xml.writeAttribute("scaling", "none");
+ xml.writeEndElement();
+
+ String licFileStr = LICENSE_FILE.fetchFrom(params);
+ if (licFileStr != null) {
+ File licFile = new File(licFileStr);
+ xml.writeStartElement("license");
+ xml.writeAttribute("file", licFile.getAbsolutePath());
+ xml.writeAttribute("mime-type", "text/rtf");
+ xml.writeEndElement();
+ }
+
+ /*
+ * Note that the content of the distribution file
+ * below is generated by productbuild --synthesize
+ */
+ String appId = getAppIdentifier(params);
+
+ xml.writeStartElement("pkg-ref");
+ xml.writeAttribute("id", appId);
+ xml.writeEndElement(); // </pkg-ref>
+ xml.writeStartElement("options");
+ xml.writeAttribute("customize", "never");
+ xml.writeAttribute("require-scripts", "false");
+ xml.writeEndElement(); // </options>
+ xml.writeStartElement("choices-outline");
+ xml.writeStartElement("line");
+ xml.writeAttribute("choice", "default");
+ xml.writeStartElement("line");
+ xml.writeAttribute("choice", appId);
+ xml.writeEndElement(); // </line>
+ xml.writeEndElement(); // </line>
+ xml.writeEndElement(); // </choices-outline>
+ xml.writeStartElement("choice");
+ xml.writeAttribute("id", "default");
+ xml.writeEndElement(); // </choice>
+ xml.writeStartElement("choice");
+ xml.writeAttribute("id", appId);
+ xml.writeAttribute("visible", "false");
+ xml.writeStartElement("pkg-ref");
+ xml.writeAttribute("id", appId);
+ xml.writeEndElement(); // </pkg-ref>
+ xml.writeEndElement(); // </choice>
+ xml.writeStartElement("pkg-ref");
+ xml.writeAttribute("id", appId);
+ xml.writeAttribute("version", VERSION.fetchFrom(params));
+ xml.writeAttribute("onConclusion", "none");
+ try {
+ xml.writeCharacters(URLEncoding(
+ getPackages_AppPackage(params).getName()));
+ } catch (URISyntaxException ex) {
+ throw new IOException(ex);
+ }
+ xml.writeEndElement(); // </pkg-ref>
+
+ xml.writeEndElement(); // </installer-gui-script>
+ });
+ }
+
+ private boolean prepareConfigFiles(Map<String, ? super Object> params)
+ throws IOException {
+
+ createResource(DEFAULT_BACKGROUND_IMAGE, params)
+ .setCategory(I18N.getString("resource.pkg-background-image"))
+ .saveToFile(getConfig_BackgroundImage(params));
+
+ createResource(DEFAULT_BACKGROUND_IMAGE, params)
+ .setCategory(I18N.getString("resource.pkg-background-image"))
+ .saveToFile(getConfig_BackgroundImageDarkAqua(params));
+
+ prepareDistributionXMLFile(params);
+
+ createResource(null, params)
+ .setCategory(I18N.getString("resource.post-install-script"))
+ .saveToFile(getConfig_Script(params));
+
+ return true;
+ }
+
+ // name of post-image script
+ private File getConfig_Script(Map<String, ? super Object> params) {
+ return new File(CONFIG_ROOT.fetchFrom(params),
+ APP_NAME.fetchFrom(params) + "-post-image.sh");
+ }
+
+ private void patchCPLFile(File cpl) throws IOException {
+ String cplData = Files.readString(cpl.toPath());
+ String[] lines = cplData.split("\n");
+ try (PrintWriter out = new PrintWriter(Files.newBufferedWriter(
+ cpl.toPath()))) {
+ int skip = 0;
+ // Used to skip Java.runtime bundle, since
+ // pkgbuild with --root will find two bundles app and Java runtime.
+ // We cannot generate component proprty list when using
+ // --component argument.
+ for (int i = 0; i < lines.length; i++) {
+ if (lines[i].trim().equals("<key>BundleIsRelocatable</key>")) {
+ out.println(lines[i]);
+ out.println("<false/>");
+ i++;
+ } else if (lines[i].trim().equals("<key>ChildBundles</key>")) {
+ ++skip;
+ } else if ((skip > 0) && lines[i].trim().equals("</array>")) {
+ --skip;
+ } else {
+ if (skip == 0) {
+ out.println(lines[i]);
+ }
+ }
+ }
+ }
+ }
+
+ // pkgbuild includes all components from "--root" and subfolders,
+ // so if we have app image in folder which contains other images, then they
+ // will be included as well. It does have "--filter" option which use regex
+ // to exclude files/folder, but it will overwrite default one which excludes
+ // based on doc "any .svn or CVS directories, and any .DS_Store files".
+ // So easy aproach will be to copy user provided app-image into temp folder
+ // if root path contains other files.
+ private String getRoot(Map<String, ? super Object> params,
+ File appLocation) throws IOException {
+ String root = appLocation.getParent() == null ?
+ "." : appLocation.getParent();
+ File rootDir = new File(root);
+ File[] list = rootDir.listFiles();
+ if (list != null) { // Should not happend
+ // We should only have app image and/or .DS_Store
+ if (list.length == 1) {
+ return root;
+ } else if (list.length == 2) {
+ // Check case with app image and .DS_Store
+ if (list[0].toString().toLowerCase().endsWith(".ds_store") ||
+ list[1].toString().toLowerCase().endsWith(".ds_store")) {
+ return root; // Only app image and .DS_Store
+ }
+ }
+ }
+
+ // Copy to new root
+ Path newRoot = Files.createTempDirectory(
+ TEMP_ROOT.fetchFrom(params).toPath(),
+ "root-");
+
+ IOUtils.copyRecursive(appLocation.toPath(),
+ newRoot.resolve(appLocation.getName()));
+
+ return newRoot.toString();
+ }
+
+ private File createPKG(Map<String, ? super Object> params,
+ File outdir, File appLocation) {
+ // generic find attempt
+ try {
+ File appPKG = getPackages_AppPackage(params);
+
+ String root = getRoot(params, appLocation);
+
+ // Generate default CPL file
+ File cpl = new File(CONFIG_ROOT.fetchFrom(params).getAbsolutePath()
+ + File.separator + "cpl.plist");
+ ProcessBuilder pb = new ProcessBuilder("pkgbuild",
+ "--root",
+ root,
+ "--install-location",
+ MAC_INSTALL_DIR.fetchFrom(params),
+ "--analyze",
+ cpl.getAbsolutePath());
+
+ IOUtils.exec(pb);
+
+ patchCPLFile(cpl);
+
+ preparePackageScripts(params);
+
+ // build application package
+ pb = new ProcessBuilder("pkgbuild",
+ "--root",
+ root,
+ "--install-location",
+ MAC_INSTALL_DIR.fetchFrom(params),
+ "--component-plist",
+ cpl.getAbsolutePath(),
+ "--scripts",
+ SCRIPTS_DIR.fetchFrom(params).getAbsolutePath(),
+ appPKG.getAbsolutePath());
+ IOUtils.exec(pb);
+
+ // build final package
+ File finalPKG = new File(outdir, INSTALLER_NAME.fetchFrom(params)
+ + INSTALLER_SUFFIX.fetchFrom(params)
+ + ".pkg");
+ outdir.mkdirs();
+
+ List<String> commandLine = new ArrayList<>();
+ commandLine.add("productbuild");
+
+ commandLine.add("--resources");
+ commandLine.add(CONFIG_ROOT.fetchFrom(params).getAbsolutePath());
+
+ // maybe sign
+ if (Optional.ofNullable(MacAppImageBuilder.
+ SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.TRUE)) {
+ if (Platform.getMajorVersion() > 10 ||
+ (Platform.getMajorVersion() == 10 &&
+ Platform.getMinorVersion() >= 12)) {
+ // we need this for OS X 10.12+
+ Log.verbose(I18N.getString("message.signing.pkg"));
+ }
+
+ String signingIdentity =
+ DEVELOPER_ID_INSTALLER_SIGNING_KEY.fetchFrom(params);
+ if (signingIdentity != null) {
+ commandLine.add("--sign");
+ commandLine.add(signingIdentity);
+ }
+
+ String keychainName = SIGNING_KEYCHAIN.fetchFrom(params);
+ if (keychainName != null && !keychainName.isEmpty()) {
+ commandLine.add("--keychain");
+ commandLine.add(keychainName);
+ }
+ }
+
+ commandLine.add("--distribution");
+ commandLine.add(
+ getConfig_DistributionXMLFile(params).getAbsolutePath());
+ commandLine.add("--package-path");
+ commandLine.add(PACKAGES_ROOT.fetchFrom(params).getAbsolutePath());
+
+ commandLine.add(finalPKG.getAbsolutePath());
+
+ pb = new ProcessBuilder(commandLine);
+ IOUtils.exec(pb);
+
+ return finalPKG;
+ } catch (Exception ignored) {
+ Log.verbose(ignored);
+ return null;
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // Implement Bundler
+ //////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public String getName() {
+ return I18N.getString("pkg.bundler.name");
+ }
+
+ @Override
+ public String getID() {
+ return "pkg";
+ }
+
+ @Override
+ public boolean validate(Map<String, ? super Object> params)
+ throws ConfigException {
+ try {
+ Objects.requireNonNull(params);
+
+ // run basic validation to ensure requirements are met
+ // we are not interested in return code, only possible exception
+ validateAppImageAndBundeler(params);
+
+ if (MAC_CF_BUNDLE_IDENTIFIER.fetchFrom(params) == null) {
+ throw new ConfigException(
+ I18N.getString("message.app-image-requires-identifier"),
+ I18N.getString(
+ "message.app-image-requires-identifier.advice"));
+ }
+
+ // reject explicitly set sign to true and no valid signature key
+ if (Optional.ofNullable(MacAppImageBuilder.
+ SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.FALSE)) {
+ String signingIdentity =
+ DEVELOPER_ID_INSTALLER_SIGNING_KEY.fetchFrom(params);
+ if (signingIdentity == null) {
+ throw new ConfigException(
+ I18N.getString("error.explicit-sign-no-cert"),
+ I18N.getString(
+ "error.explicit-sign-no-cert.advice"));
+ }
+ }
+
+ // hdiutil is always available so there's no need
+ // to test for availability.
+
+ return true;
+ } catch (RuntimeException re) {
+ if (re.getCause() instanceof ConfigException) {
+ throw (ConfigException) re.getCause();
+ } else {
+ throw new ConfigException(re);
+ }
+ }
+ }
+
+ @Override
+ public File execute(Map<String, ? super Object> params,
+ File outputParentDir) throws PackagerException {
+ return bundle(params, outputParentDir);
+ }
+
+ @Override
+ public boolean supported(boolean runtimeInstaller) {
+ return true;
+ }
+
+ @Override
+ public boolean isDefault() {
+ return false;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/DMGsetup.scpt Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,38 @@
+tell application "Finder"
+ tell disk "DEPLOY_ACTUAL_VOLUME_NAME"
+ open
+ set current view of container window to icon view
+ set toolbar visible of container window to false
+ set statusbar visible of container window to false
+
+ -- size of window should match size of background
+ set the bounds of container window to {400, 100, 917, 380}
+
+ set theViewOptions to the icon view options of container window
+ set arrangement of theViewOptions to not arranged
+ set icon size of theViewOptions to 128
+ set background picture of theViewOptions to file ".background:background.png"
+
+ -- Create alias for install location
+ make new alias file at container window to DEPLOY_INSTALL_LOCATION with properties {name:"DEPLOY_INSTALL_NAME"}
+
+ set allTheFiles to the name of every item of container window
+ repeat with theFile in allTheFiles
+ set theFilePath to POSIX Path of theFile
+ if theFilePath is "/DEPLOY_APPLICATION_NAME.app"
+ -- Position application location
+ set position of item theFile of container window to {120, 130}
+ else if theFilePath is "/DEPLOY_INSTALL_NAME"
+ -- Position install location
+ set position of item theFile of container window to {390, 130}
+ else
+ -- Move all other files far enough to be not visible if user has "show hidden files" option set
+ set position of item theFile of container window to {1000, 130}
+ end
+ end repeat
+
+ update without registering applications
+ delay 5
+ close
+ end tell
+end tell
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/Info-lite.plist.template Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,37 @@
+<?xml version="1.0" ?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <key>LSMinimumSystemVersion</key>
+ <string>10.9</string>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleAllowMixedLocalizations</key>
+ <true/>
+ <key>CFBundleExecutable</key>
+ <string>DEPLOY_LAUNCHER_NAME</string>
+ <key>CFBundleIconFile</key>
+ <string>DEPLOY_ICON_FILE</string>
+ <key>CFBundleIdentifier</key>
+ <string>DEPLOY_BUNDLE_IDENTIFIER</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>DEPLOY_BUNDLE_NAME</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>DEPLOY_BUNDLE_SHORT_VERSION</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <!-- See https://developer.apple.com/app-store/categories/ for list of AppStore categories -->
+ <key>LSApplicationCategoryType</key>
+ <string>Unknown</string>
+ <key>CFBundleVersion</key>
+ <string>DEPLOY_BUNDLE_CFBUNDLE_VERSION</string>
+ <key>NSHumanReadableCopyright</key>
+ <string>DEPLOY_BUNDLE_COPYRIGHT</string>DEPLOY_FILE_ASSOCIATIONS
+ <key>NSHighResolutionCapable</key>
+ <string>true</string>
+ </dict>
+</plist>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/MacAppStore.entitlements Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <key>com.apple.security.app-sandbox</key>
+ <true/>
+ </dict>
+</plist>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/MacAppStore_Inherit.entitlements Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <key>com.apple.security.app-sandbox</key>
+ <true/>
+ <key>com.apple.security.inherit</key>
+ <true/>
+ </dict>
+</plist>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/MacResources.properties Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,89 @@
+#
+# Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+#
+
+app.bundler.name=Mac Application Image
+store.bundler.name=Mac App Store Ready Bundler
+dmg.bundler.name=Mac DMG Package
+pkg.bundler.name=Mac PKG Package
+
+error.invalid-cfbundle-version=Invalid CFBundleVersion: [{0}]
+error.invalid-cfbundle-version.advice=Set a compatible 'appVersion' or set a 'mac.CFBundleVersion'. Valid versions are one to three integers separated by dots.
+error.explicit-sign-no-cert=Signature explicitly requested but no signing certificate specified
+error.explicit-sign-no-cert.advice=Either specify a valid cert in 'mac.signing-key-developer-id-app' or unset 'signBundle' or set 'signBundle' to false.
+error.must-sign-app-store=Mac App Store apps must be signed, and signing has been disabled by bundler configuration
+error.must-sign-app-store.advice=Either unset 'signBundle' or set 'signBundle' to true
+error.no-app-signing-key=No Mac App Store App Signing Key
+error.no-app-signing-key.advice=Install your app signing keys into your Mac Keychain using XCode.
+error.no-pkg-signing-key=No Mac App Store Installer Signing Key
+error.no-pkg-signing-key.advice=Install your app signing keys into your Mac Keychain using XCode.
+error.certificate.expired=Error: Certificate expired {0}
+error.no.xcode.signing=Xcode with command line developer tools is required for signing
+error.no.xcode.signing.advice=Install Xcode with command line developer tools.
+
+resource.bundle-config-file=Bundle config file
+resource.app-info-plist=Application Info.plist
+resource.runtime-info-plist=Java Runtime Info.plist
+resource.mac-app-store-entitlements=Mac App Store Entitlements
+resource.mac-app-store-inherit-entitlements=Mac App Store Inherit Entitlements
+resource.dmg-setup-script=DMG setup script
+resource.license-setup=License setup
+resource.dmg-background=dmg background
+resource.volume-icon=volume icon
+resource.post-install-script=script to run after application image is populated
+resource.pkg-preinstall-script=PKG preinstall script
+resource.pkg-postinstall-script=PKG postinstall script
+resource.pkg-background-image=pkg background image
+
+
+message.bundle-name-too-long-warning={0} is set to ''{1}'', which is longer than 16 characters. For a better Mac experience consider shortening it.
+message.null-classpath=Null app resources?
+message.preparing-info-plist=Preparing Info.plist: {0}.
+message.icon-not-icns= The specified icon "{0}" is not an ICNS file and will not be used. The default icon will be used in it's place.
+message.version-string-too-many-components=Version sting may have between 1 and 3 numbers: 1, 1.2, 1.2.3.
+message.version-string-first-number-not-zero=The first number in a CFBundleVersion cannot be zero or negative.
+message.version-string-no-negative-numbers=Negative numbers are not allowed in version strings.
+message.version-string-numbers-only=Version strings can consist of only numbers and up to two dots.
+message.creating-association-with-null-extension=Creating association with null extension.
+message.ignoring.symlink=Warning: codesign is skipping the symlink {0}.
+message.keychain.error=Error: unable to get keychain list.
+message.building-bundle=Building Mac App Store Package for {0}.
+message.app-image-dir-does-not-exist=Specified application image directory {0}: {1} does not exists.
+message.app-image-dir-does-not-exist.advice=Confirm that the value for {0} exists.
+message.app-image-requires-app-name=When using an external app image you must specify the app name.
+message.app-image-requires-app-name.advice=Set the app name via the -name CLI flag, the fx:application/@name ANT attribute, or via the 'appName' bundler argument.
+message.app-image-requires-identifier=Unable to extract identifier from app image.
+message.app-image-requires-identifier.advice=Use "--verbose" for extended error message or specify it via "--mac-package-identifier".
+message.building-dmg=Building DMG package for {0}.
+message.running-script=Running shell script on application image [{0}].
+message.preparing-dmg-setup=Preparing dmg setup: {0}.
+message.creating-dmg-file=Creating DMG file: {0}.
+message.dmg-cannot-be-overwritten=Dmg file exists ({0} and can not be removed.
+message.output-to-location=Result DMG installer for {0}: {1}.
+message.building-pkg=Building PKG package for {0}.
+message.preparing-scripts=Preparing package scripts.
+message.preparing-distribution-dist=Preparing distribution.dist: {0}.
+message.signing.pkg=Warning: For signing PKG, you might need to set "Always Trust" for your certificate using "Keychain Access" tool.
+message.setfile.dmg=Setting custom icon on DMG file skipped because 'SetFile' utility was not found. Installing Xcode with Command Line Tools should resolve this issue.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/MacResources_ja.properties Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,89 @@
+#
+# Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+#
+
+app.bundler.name=Mac Application Image
+store.bundler.name=Mac App Store Ready Bundler
+dmg.bundler.name=Mac DMG Package
+pkg.bundler.name=Mac PKG Package
+
+error.invalid-cfbundle-version=Invalid CFBundleVersion: [{0}]
+error.invalid-cfbundle-version.advice=Set a compatible 'appVersion' or set a 'mac.CFBundleVersion'. Valid versions are one to three integers separated by dots.
+error.explicit-sign-no-cert=Signature explicitly requested but no signing certificate specified
+error.explicit-sign-no-cert.advice=Either specify a valid cert in 'mac.signing-key-developer-id-app' or unset 'signBundle' or set 'signBundle' to false.
+error.must-sign-app-store=Mac App Store apps must be signed, and signing has been disabled by bundler configuration
+error.must-sign-app-store.advice=Either unset 'signBundle' or set 'signBundle' to true
+error.no-app-signing-key=No Mac App Store App Signing Key
+error.no-app-signing-key.advice=Install your app signing keys into your Mac Keychain using XCode.
+error.no-pkg-signing-key=No Mac App Store Installer Signing Key
+error.no-pkg-signing-key.advice=Install your app signing keys into your Mac Keychain using XCode.
+error.certificate.expired=Error: Certificate expired {0}
+error.no.xcode.signing=Xcode with command line developer tools is required for signing
+error.no.xcode.signing.advice=Install Xcode with command line developer tools.
+
+resource.bundle-config-file=Bundle config file
+resource.app-info-plist=Application Info.plist
+resource.runtime-info-plist=Java Runtime Info.plist
+resource.mac-app-store-entitlements=Mac App Store Entitlements
+resource.mac-app-store-inherit-entitlements=Mac App Store Inherit Entitlements
+resource.dmg-setup-script=DMG setup script
+resource.license-setup=License setup
+resource.dmg-background=dmg background
+resource.volume-icon=volume icon
+resource.post-install-script=script to run after application image is populated
+resource.pkg-preinstall-script=PKG preinstall script
+resource.pkg-postinstall-script=PKG postinstall script
+resource.pkg-background-image=pkg background image
+
+
+message.bundle-name-too-long-warning={0} is set to ''{1}'', which is longer than 16 characters. For a better Mac experience consider shortening it.
+message.null-classpath=Null app resources?
+message.preparing-info-plist=Preparing Info.plist: {0}.
+message.icon-not-icns= The specified icon "{0}" is not an ICNS file and will not be used. The default icon will be used in it's place.
+message.version-string-too-many-components=Version sting may have between 1 and 3 numbers: 1, 1.2, 1.2.3.
+message.version-string-first-number-not-zero=The first number in a CFBundleVersion cannot be zero or negative.
+message.version-string-no-negative-numbers=Negative numbers are not allowed in version strings.
+message.version-string-numbers-only=Version strings can consist of only numbers and up to two dots.
+message.creating-association-with-null-extension=Creating association with null extension.
+message.ignoring.symlink=Warning: codesign is skipping the symlink {0}.
+message.keychain.error=Error: unable to get keychain list.
+message.building-bundle=Building Mac App Store Package for {0}.
+message.app-image-dir-does-not-exist=Specified application image directory {0}: {1} does not exists.
+message.app-image-dir-does-not-exist.advice=Confirm that the value for {0} exists.
+message.app-image-requires-app-name=When using an external app image you must specify the app name.
+message.app-image-requires-app-name.advice=Set the app name via the -name CLI flag, the fx:application/@name ANT attribute, or via the 'appName' bundler argument.
+message.app-image-requires-identifier=Unable to extract identifier from app image.
+message.app-image-requires-identifier.advice=Use "--verbose" for extended error message or specify it via "--mac-package-identifier".
+message.building-dmg=Building DMG package for {0}.
+message.running-script=Running shell script on application image [{0}].
+message.preparing-dmg-setup=Preparing dmg setup: {0}.
+message.creating-dmg-file=Creating DMG file: {0}.
+message.dmg-cannot-be-overwritten=Dmg file exists ({0} and can not be removed.
+message.output-to-location=Result DMG installer for {0}: {1}.
+message.building-pkg=Building PKG package for {0}.
+message.preparing-scripts=Preparing package scripts.
+message.preparing-distribution-dist=Preparing distribution.dist: {0}.
+message.signing.pkg=Warning: For signing PKG, you might need to set "Always Trust" for your certificate using "Keychain Access" tool.
+message.setfile.dmg=Setting custom icon on DMG file skipped because 'SetFile' utility was not found. Installing Xcode with Command Line Tools should resolve this issue.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/MacResources_zh_CN.properties Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,89 @@
+#
+# Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+#
+
+app.bundler.name=Mac Application Image
+store.bundler.name=Mac App Store Ready Bundler
+dmg.bundler.name=Mac DMG Package
+pkg.bundler.name=Mac PKG Package
+
+error.invalid-cfbundle-version=Invalid CFBundleVersion: [{0}]
+error.invalid-cfbundle-version.advice=Set a compatible 'appVersion' or set a 'mac.CFBundleVersion'. Valid versions are one to three integers separated by dots.
+error.explicit-sign-no-cert=Signature explicitly requested but no signing certificate specified
+error.explicit-sign-no-cert.advice=Either specify a valid cert in 'mac.signing-key-developer-id-app' or unset 'signBundle' or set 'signBundle' to false.
+error.must-sign-app-store=Mac App Store apps must be signed, and signing has been disabled by bundler configuration
+error.must-sign-app-store.advice=Either unset 'signBundle' or set 'signBundle' to true
+error.no-app-signing-key=No Mac App Store App Signing Key
+error.no-app-signing-key.advice=Install your app signing keys into your Mac Keychain using XCode.
+error.no-pkg-signing-key=No Mac App Store Installer Signing Key
+error.no-pkg-signing-key.advice=Install your app signing keys into your Mac Keychain using XCode.
+error.certificate.expired=Error: Certificate expired {0}
+error.no.xcode.signing=Xcode with command line developer tools is required for signing
+error.no.xcode.signing.advice=Install Xcode with command line developer tools.
+
+resource.bundle-config-file=Bundle config file
+resource.app-info-plist=Application Info.plist
+resource.runtime-info-plist=Java Runtime Info.plist
+resource.mac-app-store-entitlements=Mac App Store Entitlements
+resource.mac-app-store-inherit-entitlements=Mac App Store Inherit Entitlements
+resource.dmg-setup-script=DMG setup script
+resource.license-setup=License setup
+resource.dmg-background=dmg background
+resource.volume-icon=volume icon
+resource.post-install-script=script to run after application image is populated
+resource.pkg-preinstall-script=PKG preinstall script
+resource.pkg-postinstall-script=PKG postinstall script
+resource.pkg-background-image=pkg background image
+
+
+message.bundle-name-too-long-warning={0} is set to ''{1}'', which is longer than 16 characters. For a better Mac experience consider shortening it.
+message.null-classpath=Null app resources?
+message.preparing-info-plist=Preparing Info.plist: {0}.
+message.icon-not-icns= The specified icon "{0}" is not an ICNS file and will not be used. The default icon will be used in it's place.
+message.version-string-too-many-components=Version sting may have between 1 and 3 numbers: 1, 1.2, 1.2.3.
+message.version-string-first-number-not-zero=The first number in a CFBundleVersion cannot be zero or negative.
+message.version-string-no-negative-numbers=Negative numbers are not allowed in version strings.
+message.version-string-numbers-only=Version strings can consist of only numbers and up to two dots.
+message.creating-association-with-null-extension=Creating association with null extension.
+message.ignoring.symlink=Warning: codesign is skipping the symlink {0}.
+message.keychain.error=Error: unable to get keychain list.
+message.building-bundle=Building Mac App Store Package for {0}.
+message.app-image-dir-does-not-exist=Specified application image directory {0}: {1} does not exists.
+message.app-image-dir-does-not-exist.advice=Confirm that the value for {0} exists.
+message.app-image-requires-app-name=When using an external app image you must specify the app name.
+message.app-image-requires-app-name.advice=Set the app name via the -name CLI flag, the fx:application/@name ANT attribute, or via the 'appName' bundler argument.
+message.app-image-requires-identifier=Unable to extract identifier from app image.
+message.app-image-requires-identifier.advice=Use "--verbose" for extended error message or specify it via "--mac-package-identifier".
+message.building-dmg=Building DMG package for {0}.
+message.running-script=Running shell script on application image [{0}].
+message.preparing-dmg-setup=Preparing dmg setup: {0}.
+message.creating-dmg-file=Creating DMG file: {0}.
+message.dmg-cannot-be-overwritten=Dmg file exists ({0} and can not be removed.
+message.output-to-location=Result DMG installer for {0}: {1}.
+message.building-pkg=Building PKG package for {0}.
+message.preparing-scripts=Preparing package scripts.
+message.preparing-distribution-dist=Preparing distribution.dist: {0}.
+message.signing.pkg=Warning: For signing PKG, you might need to set "Always Trust" for your certificate using "Keychain Access" tool.
+message.setfile.dmg=Setting custom icon on DMG file skipped because 'SetFile' utility was not found. Installing Xcode with Command Line Tools should resolve this issue.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/Runtime-Info.plist.template Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleExecutable</key>
+ <string>libjli.dylib</string>
+ <key>CFBundleIdentifier</key>
+ <string>CF_BUNDLE_IDENTIFIER</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>7.0</string>
+ <key>CFBundleName</key>
+ <string>CF_BUNDLE_NAME</string>
+ <key>CFBundlePackageType</key>
+ <string>BNDL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>CF_BUNDLE_SHORT_VERSION_STRING</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>CF_BUNDLE_VERSION</string>
+</dict>
+</plist>
Binary file src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/background_dmg.png has changed
Binary file src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/background_pkg.png has changed
Binary file src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/java.icns has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/lic_template.plist Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,244 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>LPic</key>
+ <array>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAAAAgAAAAAAAAAAAAQAAA==</data>
+ <key>ID</key>
+ <string>5000</string>
+ <key>Name</key>
+ <string></string>
+ </dict>
+ </array>
+ <key>STR#</key>
+ <array>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYPRW5nbGlzaCBkZWZhdWx0BUFncmVlCERpc2FncmVlBVByaW50B1NhdmUuLi56SWYgeW91IGFncmVlIHdpdGggdGhlIHRlcm1zIG9mIHRoaXMgbGljZW5zZSwgY2xpY2sgIkFncmVlIiB0byBhY2Nlc3MgdGhlIHNvZnR3YXJlLiAgSWYgeW91IGRvIG5vdCBhZ3JlZSwgcHJlc3MgIkRpc2FncmVlLiI=</data>
+ <key>ID</key>
+ <string>5000</string>
+ <key>Name</key>
+ <string>English buttons</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYHRGV1dHNjaAtBa3plcHRpZXJlbghBYmxlaG5lbgdEcnVja2VuClNpY2hlcm4uLi7nS2xpY2tlbiBTaWUgaW4g0kFremVwdGllcmVu0ywgd2VubiBTaWUgbWl0IGRlbiBCZXN0aW1tdW5nZW4gZGVzIFNvZnR3YXJlLUxpemVuenZlcnRyYWdzIGVpbnZlcnN0YW5kZW4gc2luZC4gRmFsbHMgbmljaHQsIGJpdHRlINJBYmxlaG5lbtMgYW5rbGlja2VuLiBTaWUga5pubmVuIGRpZSBTb2Z0d2FyZSBudXIgaW5zdGFsbGllcmVuLCB3ZW5uIFNpZSDSQWt6ZXB0aWVyZW7TIGFuZ2VrbGlja3QgaGFiZW4u</data>
+ <key>ID</key>
+ <string>5001</string>
+ <key>Name</key>
+ <string>German</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYHRW5nbGlzaAVBZ3JlZQhEaXNhZ3JlZQVQcmludAdTYXZlLi4ue0lmIHlvdSBhZ3JlZSB3aXRoIHRoZSB0ZXJtcyBvZiB0aGlzIGxpY2Vuc2UsIHByZXNzICJBZ3JlZSIgdG8gaW5zdGFsbCB0aGUgc29mdHdhcmUuICBJZiB5b3UgZG8gbm90IGFncmVlLCBwcmVzcyAiRGlzYWdyZWUiLg==</data>
+ <key>ID</key>
+ <string>5002</string>
+ <key>Name</key>
+ <string>English</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYHRXNwYZZvbAdBY2VwdGFyCk5vIGFjZXB0YXIISW1wcmltaXIKR3VhcmRhci4uLsBTaSBlc3SHIGRlIGFjdWVyZG8gY29uIGxvcyB0jnJtaW5vcyBkZSBlc3RhIGxpY2VuY2lhLCBwdWxzZSAiQWNlcHRhciIgcGFyYSBpbnN0YWxhciBlbCBzb2Z0d2FyZS4gRW4gZWwgc3VwdWVzdG8gZGUgcXVlIG5vIGVzdI4gZGUgYWN1ZXJkbyBjb24gbG9zIHSOcm1pbm9zIGRlIGVzdGEgbGljZW5jaWEsIHB1bHNlICJObyBhY2VwdGFyLiI=</data>
+ <key>ID</key>
+ <string>5003</string>
+ <key>Name</key>
+ <string>Spanish</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYIRnJhbo1haXMIQWNjZXB0ZXIHUmVmdXNlcghJbXByaW1lcg5FbnJlZ2lzdHJlci4uLrpTaSB2b3VzIGFjY2VwdGV6IGxlcyB0ZXJtZXMgZGUgbGEgcHKOc2VudGUgbGljZW5jZSwgY2xpcXVleiBzdXIgIkFjY2VwdGVyIiBhZmluIGQnaW5zdGFsbGVyIGxlIGxvZ2ljaWVsLiBTaSB2b3VzIG4nkHRlcyBwYXMgZCdhY2NvcmQgYXZlYyBsZXMgdGVybWVzIGRlIGxhIGxpY2VuY2UsIGNsaXF1ZXogc3VyICJSZWZ1c2VyIi4=</data>
+ <key>ID</key>
+ <string>5004</string>
+ <key>Name</key>
+ <string>French</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYISXRhbGlhbm8HQWNjZXR0bwdSaWZpdXRvBlN0YW1wYQtSZWdpc3RyYS4uLn9TZSBhY2NldHRpIGxlIGNvbmRpemlvbmkgZGkgcXVlc3RhIGxpY2VuemEsIGZhaSBjbGljIHN1ICJBY2NldHRvIiBwZXIgaW5zdGFsbGFyZSBpbCBzb2Z0d2FyZS4gQWx0cmltZW50aSBmYWkgY2xpYyBzdSAiUmlmaXV0byIu</data>
+ <key>ID</key>
+ <string>5005</string>
+ <key>Name</key>
+ <string>Italian</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYISmFwYW5lc2UKk6+I04K1gtyCtwyTr4jTgrWC3IK5gvEIiPON/IK3gukHlduRti4uLrSWe4Ncg3SDZ4NFg0eDQY5nl3CLlpH4jF+W8YLMj/CMj4LJk6+I04KzguqC6Y/qjYeCyYLNgUGDXIN0g2eDRYNHg0GC8INDg5ODWINngVuDi4K3gumCvYLfgsmBdZOviNOCtYLcgreBdoLwiZ+CtYLEgq2CvoKzgqKBQoFAk6+I04KzguqCyIKij+qNh4LJgs2BQYF1k6+I04K1gtyCuYLxgXaC8ImfgrWCxIKtgr6Cs4KigUI=</data>
+ <key>ID</key>
+ <string>5006</string>
+ <key>Name</key>
+ <string>Japanese</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYKTmVkZXJsYW5kcwJKYQNOZWUFUHJpbnQJQmV3YWFyLi4upEluZGllbiB1IGFra29vcmQgZ2FhdCBtZXQgZGUgdm9vcndhYXJkZW4gdmFuIGRlemUgbGljZW50aWUsIGt1bnQgdSBvcCAnSmEnIGtsaWtrZW4gb20gZGUgcHJvZ3JhbW1hdHV1ciB0ZSBpbnN0YWxsZXJlbi4gSW5kaWVuIHUgbmlldCBha2tvb3JkIGdhYXQsIGtsaWt0IHUgb3AgJ05lZScu</data>
+ <key>ID</key>
+ <string>5007</string>
+ <key>Name</key>
+ <string>Dutch</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYGU3ZlbnNrCEdvZGuKbm5zBkF2YppqcwhTa3JpdiB1dAhTcGFyYS4uLpNPbSBEdSBnb2Rrim5uZXIgbGljZW5zdmlsbGtvcmVuIGtsaWNrYSBwjCAiR29ka4pubnMiIGaaciBhdHQgaW5zdGFsbGVyYSBwcm9ncmFtcHJvZHVrdGVuLiBPbSBEdSBpbnRlIGdvZGuKbm5lciBsaWNlbnN2aWxsa29yZW4sIGtsaWNrYSBwjCAiQXZimmpzIi4=</data>
+ <key>ID</key>
+ <string>5008</string>
+ <key>Name</key>
+ <string>Swedish</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYRUG9ydHVndZBzLCBCcmFzaWwJQ29uY29yZGFyCURpc2NvcmRhcghJbXByaW1pcglTYWx2YXIuLi6MU2UgZXN0hyBkZSBhY29yZG8gY29tIG9zIHRlcm1vcyBkZXN0YSBsaWNlbo1hLCBwcmVzc2lvbmUgIkNvbmNvcmRhciIgcGFyYSBpbnN0YWxhciBvIHNvZnR3YXJlLiBTZSBui28gZXN0hyBkZSBhY29yZG8sIHByZXNzaW9uZSAiRGlzY29yZGFyIi4=</data>
+ <key>ID</key>
+ <string>5009</string>
+ <key>Name</key>
+ <string>Brazilian Portuguese</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYSU2ltcGxpZmllZCBDaGluZXNlBM2s0uIGsrvNrNLiBLTy06EGtOa0oqGtVMjnufvE+s2s0uKxvtDtv8nQrdLptcTM9b/uo6zH67C0obDNrNLiobHAtLCy17C0y8jtvP6ho8jnufvE+rK7zazS4qOsx+uwtKGwsrvNrNLiobGhow==</data>
+ <key>ID</key>
+ <string>5010</string>
+ <key>Name</key>
+ <string>Simplified Chinese</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYTVHJhZGl0aW9uYWwgQ2hpbmVzZQSmULdOBqSjplC3TgSmQ6ZMBsB4pnOhS1CmcKpHsXqmULdOpbuzXKVpw9K4zKq6sfi02qFBvdCr9qGnplC3TqGopUimd7jLs27F6aFDpnCqR6SjplC3TqFBvdCr9qGnpKOmULdOoaihQw==</data>
+ <key>ID</key>
+ <string>5011</string>
+ <key>Name</key>
+ <string>Traditional Chinese</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYFRGFuc2sERW5pZwVVZW5pZwdVZHNrcml2CkFya2l2ZXIuLi6YSHZpcyBkdSBhY2NlcHRlcmVyIGJldGluZ2Vsc2VybmUgaSBsaWNlbnNhZnRhbGVuLCBza2FsIGR1IGtsaWtrZSBwjCDSRW5pZ9MgZm9yIGF0IGluc3RhbGxlcmUgc29mdHdhcmVuLiBLbGlrIHCMINJVZW5pZ9MgZm9yIGF0IGFubnVsbGVyZSBpbnN0YWxsZXJpbmdlbi4=</data>
+ <key>ID</key>
+ <string>5012</string>
+ <key>Name</key>
+ <string>Danish</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYFU3VvbWkISHl2imtzeW4KRW4gaHl2imtzeQdUdWxvc3RhCVRhbGxlbm5hyW9IeXaKa3N5IGxpc2Vuc3Npc29waW11a3NlbiBlaGRvdCBvc29pdHRhbWFsbGEg1Uh5doprc3nVLiBKb3MgZXQgaHl2imtzeSBzb3BpbXVrc2VuIGVodG9qYSwgb3NvaXRhINVFbiBoeXaKa3N51S4=</data>
+ <key>ID</key>
+ <string>5013</string>
+ <key>Name</key>
+ <string>Finnish</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYRRnJhbo1haXMgY2FuYWRpZW4IQWNjZXB0ZXIHUmVmdXNlcghJbXByaW1lcg5FbnJlZ2lzdHJlci4uLrpTaSB2b3VzIGFjY2VwdGV6IGxlcyB0ZXJtZXMgZGUgbGEgcHKOc2VudGUgbGljZW5jZSwgY2xpcXVleiBzdXIgIkFjY2VwdGVyIiBhZmluIGQnaW5zdGFsbGVyIGxlIGxvZ2ljaWVsLiBTaSB2b3VzIG4nkHRlcyBwYXMgZCdhY2NvcmQgYXZlYyBsZXMgdGVybWVzIGRlIGxhIGxpY2VuY2UsIGNsaXF1ZXogc3VyICJSZWZ1c2VyIi4=</data>
+ <key>ID</key>
+ <string>5014</string>
+ <key>Name</key>
+ <string>French Canadian</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYGS29yZWFuBLW/wMcJtb/AxyC+yMfUBsfBuLDGrgfA+sDlLi4ufrvnv+sgsOi+4LytwMcgs7u/67+hILW/wMfHz7jpLCAitb/AxyIgtNzD37imILStt68gvNLHwcauv/6+7rimILyzxKHHz73KvcO/wC4gtb/Ax8fPwfYgvsq0wrTZuOksICK1v8DHIL7Ix9QiILTcw9+4piC0qbijvcq9w7/ALg==</data>
+ <key>ID</key>
+ <string>5015</string>
+ <key>Name</key>
+ <string>Korean</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYFTm9yc2sERW5pZwlJa2tlIGVuaWcIU2tyaXYgdXQKQXJraXZlci4uLqNIdmlzIERlIGVyIGVuaWcgaSBiZXN0ZW1tZWxzZW5lIGkgZGVubmUgbGlzZW5zYXZ0YWxlbiwga2xpa2tlciBEZSBwjCAiRW5pZyIta25hcHBlbiBmb3IgjCBpbnN0YWxsZXJlIHByb2dyYW12YXJlbi4gSHZpcyBEZSBpa2tlIGVyIGVuaWcsIGtsaWtrZXIgRGUgcIwgIklra2UgZW5pZyIu</data>
+ <key>ID</key>
+ <string>5016</string>
+ <key>Name</key>
+ <string>Norwegian</string>
+ </dict>
+ </array>
+ <key>TEXT</key>
+ <array>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>APPLICATION_LICENSE_TEXT</data>
+ <key>ID</key>
+ <string>5000</string>
+ <key>Name</key>
+ <string>English SLA</string>
+ </dict>
+ </array>
+ <key>TMPL</key>
+ <array>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>E0RlZmF1bHQgTGFuZ3VhZ2UgSUREV1JEBUNvdW50T0NOVAQqKioqTFNUQwtzeXMgbGFuZyBJRERXUkQebG9jYWwgcmVzIElEIChvZmZzZXQgZnJvbSA1MDAwRFdSRBAyLWJ5dGUgbGFuZ3VhZ2U/RFdSRAQqKioqTFNURQ==</data>
+ <key>ID</key>
+ <string>128</string>
+ <key>Name</key>
+ <string>LPic</string>
+ </dict>
+ </array>
+ <key>plst</key>
+ <array>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0050</string>
+ <key>Data</key>
+ <data>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</data>
+ <key>ID</key>
+ <string>0</string>
+ <key>Name</key>
+ <string></string>
+ </dict>
+ </array>
+ <key>styl</key>
+ <array>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAMAAAAAAAwACQAUAAAAAAAAAAAAAAAAACcADAAJABQBAAAAAAAAAAAAAAAAKgAMAAkAFAAAAAAAAAAAAAA=</data>
+ <key>ID</key>
+ <string>5000</string>
+ <key>Name</key>
+ <string>English SLA</string>
+ </dict>
+ </array>
+</dict>
+</plist>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/postinstall.template Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,7 @@
+#!/usr/bin/env sh
+
+chown root:wheel "INSTALL_LOCATION"
+chmod a+rX "INSTALL_LOCATION"
+chmod +r "APP_LOCATION/"*.jar
+
+exit 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/preinstall.template Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,8 @@
+#!/usr/bin/env sh
+
+if [ ! -d "INSTALL_LOCATION" ]
+then
+ mkdir -p "INSTALL_LOCATION"
+fi
+
+exit 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/macosx/classes/module-info.java.extra Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+provides jdk.incubator.jpackage.internal.Bundler with
+ jdk.incubator.jpackage.internal.MacAppBundler,
+ jdk.incubator.jpackage.internal.MacAppStoreBundler,
+ jdk.incubator.jpackage.internal.MacDmgBundler,
+ jdk.incubator.jpackage.internal.MacPkgBundler;
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/macosx/native/jpackageapplauncher/main.m Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#import <Cocoa/Cocoa.h>
+#include <dlfcn.h>
+#include <unistd.h>
+
+typedef bool (*start_launcher)(int argc, char* argv[]);
+typedef void (*stop_launcher)();
+
+int main(int argc, char *argv[]) {
+#if !__has_feature(objc_arc)
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+#endif
+
+ int result = 1;
+
+ @try {
+ setlocale(LC_ALL, "en_US.utf8");
+
+ NSBundle *mainBundle = [NSBundle mainBundle];
+ NSString *mainBundlePath = [mainBundle bundlePath];
+ NSString *libraryName = [mainBundlePath stringByAppendingPathComponent:@"Contents/MacOS/libapplauncher.dylib"];
+
+ void* library = dlopen([libraryName UTF8String], RTLD_LAZY);
+
+ if (library == NULL) {
+ NSLog(@"%@ not found.\n", libraryName);
+ }
+
+ if (library != NULL) {
+ start_launcher start =
+ (start_launcher)dlsym(library, "start_launcher");
+ stop_launcher stop =
+ (stop_launcher)dlsym(library, "stop_launcher");
+
+ if (start != NULL && stop != NULL) {
+ if (start(argc, argv) == true) {
+ result = 0;
+ stop();
+ }
+ } else if (start == NULL) {
+ NSLog(@"start_launcher not found in %@.\n", libraryName);
+ } else {
+ NSLog(@"stop_launcher not found in %@.\n", libraryName);
+ }
+ dlclose(library);
+ }
+ } @catch (NSException *exception) {
+ NSLog(@"%@: %@", exception, [exception callStackSymbols]);
+ result = 1;
+ }
+
+#if !__has_feature(objc_arc)
+ [pool drain];
+#endif
+
+ return result;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/macosx/native/libapplauncher/MacPlatform.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef MACPLATFORM_H
+#define MACPLATFORM_H
+
+#include "Platform.h"
+#include "PosixPlatform.h"
+
+class MacPlatform : virtual public Platform, PosixPlatform {
+private:
+ bool UsePListForConfigFile();
+
+protected:
+ virtual TString getTmpDirString();
+
+public:
+ MacPlatform(void);
+ virtual ~MacPlatform(void);
+
+public:
+ virtual void ShowMessage(TString title, TString description);
+ virtual void ShowMessage(TString description);
+
+ virtual TCHAR* ConvertStringToFileSystemString(
+ TCHAR* Source, bool &release);
+ virtual TCHAR* ConvertFileSystemStringToString(
+ TCHAR* Source, bool &release);
+
+ virtual TString GetPackageRootDirectory();
+ virtual TString GetAppDataDirectory();
+ virtual TString GetBundledJavaLibraryFileName(TString RuntimePath);
+ virtual TString GetAppName();
+
+ TString GetPackageAppDirectory();
+ TString GetPackageLauncherDirectory();
+ TString GetPackageRuntimeBinDirectory();
+
+ virtual ISectionalPropertyContainer* GetConfigFile(TString FileName);
+ virtual TString GetModuleFileName();
+
+ virtual bool IsMainThread();
+ virtual TPlatformNumber GetMemorySize();
+
+ virtual std::map<TString, TString> GetKeys();
+};
+
+
+#endif // MACPLATFORM_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/macosx/native/libapplauncher/MacPlatform.mm Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,505 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "Platform.h"
+
+#include "MacPlatform.h"
+#include "Helpers.h"
+#include "Package.h"
+#include "PropertyFile.h"
+#include "IniFile.h"
+
+#include <sys/sysctl.h>
+#include <pthread.h>
+#include <vector>
+#include <signal.h>
+#include <mach-o/dyld.h>
+
+#import <Foundation/Foundation.h>
+#import <AppKit/NSRunningApplication.h>
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <CoreFoundation/CFString.h>
+
+#ifdef __OBJC__
+#import <Cocoa/Cocoa.h>
+#endif //__OBJC__
+
+#define MAC_JPACKAGE_TMP_DIR \
+ "/Library/Application Support/Java/JPackage/tmp"
+
+NSString* StringToNSString(TString Value) {
+ NSString* result = [NSString stringWithCString : Value.c_str()
+ encoding : [NSString defaultCStringEncoding]];
+ return result;
+}
+
+FileSystemStringToString::FileSystemStringToString(const TCHAR* value) {
+ bool release = false;
+ PlatformString lvalue = PlatformString(value);
+ Platform& platform = Platform::GetInstance();
+ TCHAR* buffer = platform.ConvertFileSystemStringToString(lvalue, release);
+ FData = buffer;
+
+ if (buffer != NULL && release == true) {
+ delete[] buffer;
+ }
+}
+
+FileSystemStringToString::operator TString() {
+ return FData;
+}
+
+StringToFileSystemString::StringToFileSystemString(const TString &value) {
+ FRelease = false;
+ PlatformString lvalue = PlatformString(value);
+ Platform& platform = Platform::GetInstance();
+ FData = platform.ConvertStringToFileSystemString(lvalue, FRelease);
+}
+
+StringToFileSystemString::~StringToFileSystemString() {
+ if (FRelease == true) {
+ delete[] FData;
+ }
+}
+
+StringToFileSystemString::operator TCHAR* () {
+ return FData;
+}
+
+MacPlatform::MacPlatform(void) : Platform(), PosixPlatform() {
+}
+
+MacPlatform::~MacPlatform(void) {
+}
+
+TString MacPlatform::GetPackageAppDirectory() {
+ return FilePath::IncludeTrailingSeparator(
+ GetPackageRootDirectory()) + _T("app");
+}
+
+TString MacPlatform::GetPackageLauncherDirectory() {
+ return FilePath::IncludeTrailingSeparator(
+ GetPackageRootDirectory()) + _T("MacOS");
+}
+
+TString MacPlatform::GetPackageRuntimeBinDirectory() {
+ return FilePath::IncludeTrailingSeparator(GetPackageRootDirectory()) +
+ _T("runtime/Contents/Home/bin");
+}
+
+bool MacPlatform::UsePListForConfigFile() {
+ return FilePath::FileExists(GetConfigFileName()) == false;
+}
+
+void MacPlatform::ShowMessage(TString Title, TString Description) {
+ NSString *ltitle = StringToNSString(Title);
+ NSString *ldescription = StringToNSString(Description);
+
+ NSLog(@"%@:%@", ltitle, ldescription);
+}
+
+void MacPlatform::ShowMessage(TString Description) {
+ TString appname = GetModuleFileName();
+ appname = FilePath::ExtractFileName(appname);
+ ShowMessage(appname, Description);
+}
+
+TString MacPlatform::getTmpDirString() {
+ return TString(MAC_JPACKAGE_TMP_DIR);
+}
+
+TCHAR* MacPlatform::ConvertStringToFileSystemString(TCHAR* Source,
+ bool &release) {
+ TCHAR* result = NULL;
+ release = false;
+ CFStringRef StringRef = CFStringCreateWithCString(kCFAllocatorDefault,
+ Source, kCFStringEncodingUTF8);
+
+ if (StringRef != NULL) {
+ @ try {
+ CFIndex length =
+ CFStringGetMaximumSizeOfFileSystemRepresentation(StringRef);
+ result = new char[length + 1];
+ if (result != NULL) {
+ if (CFStringGetFileSystemRepresentation(StringRef,
+ result, length)) {
+ release = true;
+ } else {
+ delete[] result;
+ result = NULL;
+ }
+ }
+ }
+ @finally
+ {
+ CFRelease(StringRef);
+ }
+ }
+
+ return result;
+}
+
+TCHAR* MacPlatform::ConvertFileSystemStringToString(TCHAR* Source,
+ bool &release) {
+ TCHAR* result = NULL;
+ release = false;
+ CFStringRef StringRef = CFStringCreateWithFileSystemRepresentation(
+ kCFAllocatorDefault, Source);
+
+ if (StringRef != NULL) {
+ @ try {
+ CFIndex length = CFStringGetLength(StringRef);
+
+ if (length > 0) {
+ CFIndex maxSize = CFStringGetMaximumSizeForEncoding(
+ length, kCFStringEncodingUTF8);
+
+ result = new char[maxSize + 1];
+ if (result != NULL) {
+ if (CFStringGetCString(StringRef, result, maxSize,
+ kCFStringEncodingUTF8) == true) {
+ release = true;
+ } else {
+ delete[] result;
+ result = NULL;
+ }
+ }
+ }
+ }
+ @finally
+ {
+ CFRelease(StringRef);
+ }
+ }
+
+ return result;
+}
+
+TString MacPlatform::GetPackageRootDirectory() {
+ NSBundle *mainBundle = [NSBundle mainBundle];
+ NSString *mainBundlePath = [mainBundle bundlePath];
+ NSString *contentsPath =
+ [mainBundlePath stringByAppendingString : @"/Contents"];
+ TString result = [contentsPath UTF8String];
+ return result;
+}
+
+TString MacPlatform::GetAppDataDirectory() {
+ TString result;
+ NSArray *paths = NSSearchPathForDirectoriesInDomains(
+ NSApplicationSupportDirectory, NSUserDomainMask, YES);
+ NSString *applicationSupportDirectory = [paths firstObject];
+ result = [applicationSupportDirectory UTF8String];
+ return result;
+}
+
+TString MacPlatform::GetBundledJavaLibraryFileName(TString RuntimePath) {
+ TString result;
+
+ // first try lib/, then lib/jli
+ result = FilePath::IncludeTrailingSeparator(RuntimePath) +
+ _T("Contents/Home/lib/libjli.dylib");
+
+ if (FilePath::FileExists(result) == false) {
+ result = FilePath::IncludeTrailingSeparator(RuntimePath) +
+ _T("Contents/Home/lib/jli/libjli.dylib");
+
+ if (FilePath::FileExists(result) == false) {
+ // cannot find
+ NSLog(@"Cannot find libjli.dysym!");
+ result = _T("");
+ }
+ }
+
+ return result;
+}
+
+TString MacPlatform::GetAppName() {
+ NSString *appName = [[NSProcessInfo processInfo] processName];
+ TString result = [appName UTF8String];
+ return result;
+}
+
+void PosixProcess::Cleanup() {
+ if (FOutputHandle != 0) {
+ close(FOutputHandle);
+ FOutputHandle = 0;
+ }
+
+ if (FInputHandle != 0) {
+ close(FInputHandle);
+ FInputHandle = 0;
+ }
+
+ sigaction(SIGINT, &savintr, (struct sigaction *) 0);
+ sigaction(SIGQUIT, &savequit, (struct sigaction *) 0);
+ sigprocmask(SIG_SETMASK, &saveblock, (sigset_t *) 0);
+}
+
+#define PIPE_READ 0
+#define PIPE_WRITE 1
+
+bool PosixProcess::Execute(const TString Application,
+ const std::vector<TString> Arguments, bool AWait) {
+ bool result = false;
+
+ if (FRunning == false) {
+ FRunning = true;
+
+ int handles[2];
+
+ if (pipe(handles) == -1) {
+ return false;
+ }
+
+ struct sigaction sa;
+ sa.sa_handler = SIG_IGN;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sigemptyset(&savintr.sa_mask);
+ sigemptyset(&savequit.sa_mask);
+ sigaction(SIGINT, &sa, &savintr);
+ sigaction(SIGQUIT, &sa, &savequit);
+ sigaddset(&sa.sa_mask, SIGCHLD);
+ sigprocmask(SIG_BLOCK, &sa.sa_mask, &saveblock);
+
+ FChildPID = fork();
+
+ // PID returned by vfork is 0 for the child process and the
+ // PID of the child process for the parent.
+ if (FChildPID == -1) {
+ // Error
+ TString message = PlatformString::Format(
+ _T("Error: Unable to create process %s"),
+ Application.data());
+ throw Exception(message);
+ } else if (FChildPID == 0) {
+ Cleanup();
+ TString command = Application;
+
+ for (std::vector<TString>::const_iterator iterator =
+ Arguments.begin(); iterator != Arguments.end();
+ iterator++) {
+ command += TString(_T(" ")) + *iterator;
+ }
+#ifdef DEBUG
+ printf("%s\n", command.data());
+#endif // DEBUG
+
+ dup2(handles[PIPE_READ], STDIN_FILENO);
+ dup2(handles[PIPE_WRITE], STDOUT_FILENO);
+
+ close(handles[PIPE_READ]);
+ close(handles[PIPE_WRITE]);
+
+ execl("/bin/sh", "sh", "-c", command.data(), (char *) 0);
+
+ _exit(127);
+ } else {
+ FOutputHandle = handles[PIPE_READ];
+ FInputHandle = handles[PIPE_WRITE];
+
+ if (AWait == true) {
+ ReadOutput();
+ Wait();
+ Cleanup();
+ FRunning = false;
+ result = true;
+ } else {
+ result = true;
+ }
+ }
+ }
+
+ return result;
+}
+
+void AppendPListArrayToIniFile(NSDictionary *infoDictionary,
+ IniFile *result, TString Section) {
+ NSString *sectionKey =
+ [NSString stringWithUTF8String : PlatformString(Section).toMultibyte()];
+ NSDictionary *array = [infoDictionary objectForKey : sectionKey];
+
+ for (id option in array) {
+ if ([option isKindOfClass : [NSString class]]) {
+ TString arg = [option UTF8String];
+
+ TString name;
+ TString value;
+
+ if (Helpers::SplitOptionIntoNameValue(arg, name, value) == true) {
+ result->Append(Section, name, value);
+ }
+ }
+ }
+}
+
+void AppendPListDictionaryToIniFile(NSDictionary *infoDictionary,
+ IniFile *result, TString Section, bool FollowSection = true) {
+ NSDictionary *dictionary = NULL;
+
+ if (FollowSection == true) {
+ NSString *sectionKey = [NSString stringWithUTF8String : PlatformString(
+ Section).toMultibyte()];
+ dictionary = [infoDictionary objectForKey : sectionKey];
+ } else {
+ dictionary = infoDictionary;
+ }
+
+ for (id key in dictionary) {
+ id option = [dictionary valueForKey : key];
+
+ if ([key isKindOfClass : [NSString class]] &&
+ [option isKindOfClass : [NSString class]]) {
+ TString name = [key UTF8String];
+ TString value = [option UTF8String];
+ result->Append(Section, name, value);
+ }
+ }
+}
+
+// Convert parts of the info.plist to the INI format the rest of the jpackage
+// uses unless a jpackage config file exists.
+ISectionalPropertyContainer* MacPlatform::GetConfigFile(TString FileName) {
+ IniFile* result = new IniFile();
+ if (result == NULL) {
+ return NULL;
+ }
+
+ if (UsePListForConfigFile() == false) {
+ result->LoadFromFile(FileName);
+ } else {
+ NSBundle *mainBundle = [NSBundle mainBundle];
+ NSDictionary *infoDictionary = [mainBundle infoDictionary];
+ std::map<TString, TString> keys = GetKeys();
+
+ // JPackage options.
+ AppendPListDictionaryToIniFile(infoDictionary, result,
+ keys[CONFIG_SECTION_APPLICATION], false);
+
+ // jvmargs
+ AppendPListArrayToIniFile(infoDictionary, result,
+ keys[CONFIG_SECTION_JAVAOPTIONS]);
+
+ // Generate AppCDS Cache
+ AppendPListDictionaryToIniFile(infoDictionary, result,
+ keys[CONFIG_SECTION_APPCDSJAVAOPTIONS]);
+ AppendPListDictionaryToIniFile(infoDictionary, result,
+ keys[CONFIG_SECTION_APPCDSGENERATECACHEJAVAOPTIONS]);
+
+ // args
+ AppendPListArrayToIniFile(infoDictionary, result,
+ keys[CONFIG_SECTION_ARGOPTIONS]);
+ }
+
+ return result;
+}
+
+TString GetModuleFileNameOSX() {
+ Dl_info module_info;
+ if (dladdr(reinterpret_cast<void*> (GetModuleFileNameOSX),
+ &module_info) == 0) {
+ // Failed to find the symbol we asked for.
+ return std::string();
+ }
+ return TString(module_info.dli_fname);
+}
+
+TString MacPlatform::GetModuleFileName() {
+ TString result;
+ DynamicBuffer<TCHAR> buffer(MAX_PATH);
+ uint32_t size = buffer.GetSize();
+
+ if (_NSGetExecutablePath(buffer.GetData(), &size) == 0) {
+ result = FileSystemStringToString(buffer.GetData());
+ }
+
+ return result;
+}
+
+bool MacPlatform::IsMainThread() {
+ bool result = (pthread_main_np() == 1);
+ return result;
+}
+
+TPlatformNumber MacPlatform::GetMemorySize() {
+ unsigned long long memory = [[NSProcessInfo processInfo] physicalMemory];
+
+ // Convert from bytes to megabytes.
+ TPlatformNumber result = memory / 1048576;
+
+ return result;
+}
+
+std::map<TString, TString> MacPlatform::GetKeys() {
+ std::map<TString, TString> keys;
+
+ if (UsePListForConfigFile() == false) {
+ return Platform::GetKeys();
+ } else {
+ keys.insert(std::map<TString, TString>::value_type(CONFIG_VERSION,
+ _T("app.version")));
+ keys.insert(std::map<TString, TString>::value_type(CONFIG_MAINJAR_KEY,
+ _T("JavaMainJarName")));
+ keys.insert(std::map<TString,
+ TString>::value_type(CONFIG_MAINMODULE_KEY,
+ _T("JavaMainModuleName")));
+ keys.insert(std::map<TString, TString>::value_type(
+ CONFIG_MAINCLASSNAME_KEY, _T("JavaMainClassName")));
+ keys.insert(std::map<TString, TString>::value_type(
+ CONFIG_CLASSPATH_KEY, _T("JavaAppClasspath")));
+ keys.insert(std::map<TString, TString>::value_type(APP_NAME_KEY,
+ _T("CFBundleName")));
+ keys.insert(std::map<TString, TString>::value_type(JAVA_RUNTIME_KEY,
+ _T("JavaRuntime")));
+ keys.insert(std::map<TString,
+ TString>::value_type(JPACKAGE_APP_DATA_DIR,
+ _T("CFBundleIdentifier")));
+
+ keys.insert(std::map<TString, TString>::value_type(CONFIG_SPLASH_KEY,
+ _T("app.splash")));
+ keys.insert(std::map<TString, TString>::value_type(CONFIG_APP_MEMORY,
+ _T("app.memory")));
+ keys.insert(std::map<TString, TString>::value_type(CONFIG_APP_DEBUG,
+ _T("app.debug")));
+ keys.insert(std::map<TString, TString>::value_type(
+ CONFIG_APPLICATION_INSTANCE, _T("app.application.instance")));
+
+ keys.insert(std::map<TString, TString>::value_type(
+ CONFIG_SECTION_APPLICATION, _T("Application")));
+ keys.insert(std::map<TString, TString>::value_type(
+ CONFIG_SECTION_JAVAOPTIONS, _T("JavaOptions")));
+ keys.insert(std::map<TString, TString>::value_type(
+ CONFIG_SECTION_APPCDSJAVAOPTIONS, _T("AppCDSJavaOptions")));
+ keys.insert(std::map<TString, TString>::value_type(
+ CONFIG_SECTION_APPCDSGENERATECACHEJAVAOPTIONS,
+ _T("AppCDSGenerateCacheJavaOptions")));
+ keys.insert(std::map<TString, TString>::value_type(
+ CONFIG_SECTION_ARGOPTIONS, _T("ArgOptions")));
+ }
+
+ return keys;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/macosx/native/libapplauncher/PlatformDefs.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef PLATFORM_DEFS_H
+#define PLATFORM_DEFS_H
+
+#include <errno.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <dlfcn.h>
+#include <libgen.h>
+#include <string>
+
+using namespace std;
+
+#ifndef MAC
+#define MAC
+#endif
+
+#define _T(x) x
+
+typedef char TCHAR;
+typedef std::string TString;
+#define StringLength strlen
+
+typedef unsigned long DWORD;
+
+#define TRAILING_PATHSEPARATOR '/'
+#define BAD_TRAILING_PATHSEPARATOR '\\'
+#define PATH_SEPARATOR ':'
+#define BAD_PATH_SEPARATOR ';'
+#define MAX_PATH 1000
+
+typedef long TPlatformNumber;
+typedef pid_t TProcessID;
+
+#define HMODULE void*
+
+typedef void* Module;
+typedef void* Procedure;
+
+
+// StringToFileSystemString is a stack object. It's usage is
+// simply inline to convert a
+// TString to a file system string. Example:
+//
+// return dlopen(StringToFileSystemString(FileName), RTLD_LAZY);
+//
+class StringToFileSystemString {
+ // Prohibit Heap-Based StringToFileSystemString
+private:
+ static void *operator new(size_t size);
+ static void operator delete(void *ptr);
+
+private:
+ TCHAR* FData;
+ bool FRelease;
+
+public:
+ StringToFileSystemString(const TString &value);
+ ~StringToFileSystemString();
+
+ operator TCHAR* ();
+};
+
+
+// FileSystemStringToString is a stack object. It's usage is
+// simply inline to convert a
+// file system string to a TString. Example:
+//
+// DynamicBuffer<TCHAR> buffer(MAX_PATH);
+// if (readlink("/proc/self/exe", buffer.GetData(), MAX_PATH) != -1)
+// result = FileSystemStringToString(buffer.GetData());
+//
+class FileSystemStringToString {
+ // Prohibit Heap-Based FileSystemStringToString
+private:
+ static void *operator new(size_t size);
+ static void operator delete(void *ptr);
+
+private:
+ TString FData;
+
+public:
+ FileSystemStringToString(const TCHAR* value);
+
+ operator TString ();
+};
+
+#endif // PLATFORM_DEFS_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/ToolProviderFactory.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage;
+
+import jdk.incubator.jpackage.internal.JPackageToolProvider;
+import java.io.PrintWriter;
+import java.util.Optional;
+import java.util.spi.ToolProvider;
+
+/**
+ * A factory class to obtain a {@linkplain ToolProvider tool provider}
+ * for the incubating {@code jpackage} tool.
+ *
+ * It is planned to implement {@code jpackage} tool as a service provider
+ * to {@link ToolProvider} in a future release at which point
+ * {@link ToolProvider#findFirst} can be used to look up jpackage tool.
+ *
+ * @since 14
+ */
+
+public class ToolProviderFactory {
+
+ private static ToolProvider provider = new JPackageToolProvider();
+
+ // Prevent creating an instance of this class
+ private ToolProviderFactory() {
+ }
+
+ /**
+ * Returns an {@link Optional} containing the {@code ToolProvider}
+ * if the given toolname is "jpackage". Returns an empty
+ * {@code Optional} if the given toolname is not "jpackage".
+ *
+ * @param toolname {@code String} name of tool to look for.
+ * @return an {@link Optional} containing the {@code ToolPovider}
+ *
+ * @since 14
+ */
+ public static Optional<ToolProvider> findFirst(String toolName) {
+ if ("jpackage".equals(toolName)) {
+ return Optional.of(provider);
+ } else {
+ return Optional.empty();
+ }
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/AbstractAppImageBuilder.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.text.MessageFormat;
+import java.util.List;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.ArrayList;
+
+import jdk.incubator.jpackage.internal.resources.ResourceLocator;
+
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+
+/*
+ * AbstractAppImageBuilder
+ * This is sub-classed by each of the platform dependent AppImageBuilder
+ * classes, and contains resource processing code common to all platforms.
+ */
+
+public abstract class AbstractAppImageBuilder {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.incubator.jpackage.internal.resources.MainResources");
+
+ private final Path root;
+
+ public AbstractAppImageBuilder(Map<String, Object> unused, Path root) {
+ this.root = root;
+ }
+
+ public InputStream getResourceAsStream(String name) {
+ return ResourceLocator.class.getResourceAsStream(name);
+ }
+
+ public abstract void prepareApplicationFiles(
+ Map<String, ? super Object> params) throws IOException;
+ public abstract void prepareJreFiles(
+ Map<String, ? super Object> params) throws IOException;
+ public abstract Path getAppDir();
+ public abstract Path getAppModsDir();
+
+ public Path getRuntimeRoot() {
+ return this.root;
+ }
+
+ protected void copyEntry(Path appDir, File srcdir, String fname)
+ throws IOException {
+ Path dest = appDir.resolve(fname);
+ Files.createDirectories(dest.getParent());
+ File src = new File(srcdir, fname);
+ if (src.isDirectory()) {
+ IOUtils.copyRecursive(src.toPath(), dest);
+ } else {
+ Files.copy(src.toPath(), dest);
+ }
+ }
+
+ public void writeCfgFile(Map<String, ? super Object> params,
+ File cfgFileName) throws IOException {
+ cfgFileName.getParentFile().mkdirs();
+ cfgFileName.delete();
+ File mainJar = JLinkBundlerHelper.getMainJar(params);
+ ModFile.ModType mainJarType = ModFile.ModType.Unknown;
+
+ if (mainJar != null) {
+ mainJarType = new ModFile(mainJar).getModType();
+ }
+
+ String mainModule = StandardBundlerParam.MODULE.fetchFrom(params);
+
+ try (PrintStream out = new PrintStream(cfgFileName)) {
+
+ out.println("[Application]");
+ out.println("app.name=" + APP_NAME.fetchFrom(params));
+ out.println("app.version=" + VERSION.fetchFrom(params));
+ out.println("app.runtime=" + getCfgRuntimeDir());
+ out.println("app.identifier=" + IDENTIFIER.fetchFrom(params));
+ out.println("app.classpath="
+ + getCfgClassPath(CLASSPATH.fetchFrom(params)));
+
+ // The main app is required to be a jar, modular or unnamed.
+ if (mainModule != null &&
+ (mainJarType == ModFile.ModType.Unknown ||
+ mainJarType == ModFile.ModType.ModularJar)) {
+ out.println("app.mainmodule=" + mainModule);
+ } else {
+ String mainClass =
+ StandardBundlerParam.MAIN_CLASS.fetchFrom(params);
+ // If the app is contained in an unnamed jar then launch it the
+ // legacy way and the main class string must be
+ // of the format com/foo/Main
+ if (mainJar != null) {
+ out.println("app.mainjar=" + getCfgAppDir()
+ + mainJar.toPath().getFileName().toString());
+ }
+ if (mainClass != null) {
+ out.println("app.mainclass="
+ + mainClass.replace("\\", "/"));
+ }
+ }
+
+ out.println();
+ out.println("[JavaOptions]");
+ List<String> jvmargs = JAVA_OPTIONS.fetchFrom(params);
+ for (String arg : jvmargs) {
+ out.println(arg);
+ }
+ Path modsDir = getAppModsDir();
+
+ if (modsDir != null && modsDir.toFile().exists()) {
+ out.println("--module-path");
+ out.println(getCfgAppDir().replace("\\","/") + "mods");
+ }
+
+ out.println();
+ out.println("[ArgOptions]");
+ List<String> args = ARGUMENTS.fetchFrom(params);
+ for (String arg : args) {
+ if (arg.endsWith("=") &&
+ (arg.indexOf("=") == arg.lastIndexOf("="))) {
+ out.print(arg.substring(0, arg.length() - 1));
+ out.println("\\=");
+ } else {
+ out.println(arg);
+ }
+ }
+ }
+ }
+
+ File getRuntimeImageDir(File runtimeImageTop) {
+ return runtimeImageTop;
+ }
+
+ protected String getCfgAppDir() {
+ return "$ROOTDIR" + File.separator
+ + getAppDir().getFileName() + File.separator;
+ }
+
+ protected String getCfgRuntimeDir() {
+ return "$ROOTDIR" + File.separator + "runtime";
+ }
+
+ String getCfgClassPath(String classpath) {
+ String cfgAppDir = getCfgAppDir();
+
+ StringBuilder sb = new StringBuilder();
+ for (String path : classpath.split("[:;]")) {
+ if (path.length() > 0) {
+ sb.append(cfgAppDir);
+ sb.append(path);
+ sb.append(File.pathSeparator);
+ }
+ }
+ if (sb.length() > 0) {
+ sb.deleteCharAt(sb.length() - 1);
+ }
+ return sb.toString();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/AbstractBundler.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+
+/**
+ * AbstractBundler
+ *
+ * This is the base class all bundlers extend from.
+ * It contains methods and parameters common to all bundlers.
+ * The concrete implementations are in the platform specific bundlers.
+ */
+abstract class AbstractBundler implements Bundler {
+
+ static final BundlerParamInfo<File> IMAGES_ROOT =
+ new StandardBundlerParam<>(
+ "imagesRoot",
+ File.class,
+ params -> new File(
+ StandardBundlerParam.TEMP_ROOT.fetchFrom(params), "images"),
+ (s, p) -> null);
+
+ @Override
+ public String toString() {
+ return getName();
+ }
+
+ @Override
+ public void cleanup(Map<String, ? super Object> params) {
+ try {
+ IOUtils.deleteRecursive(
+ StandardBundlerParam.TEMP_ROOT.fetchFrom(params));
+ } catch (IOException e) {
+ Log.verbose(e.getMessage());
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/AbstractImageBundler.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.text.MessageFormat;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.io.File;
+import java.io.IOException;
+
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+
+/**
+ * AbstractImageBundler
+ *
+ * This is the base class for each of the Application Image Bundlers.
+ *
+ * It contains methods and parameters common to all Image Bundlers.
+ *
+ * Application Image Bundlers are created in "create-app-image" mode,
+ * or as an intermediate step in "create-installer" mode.
+ *
+ * The concrete implementations are in the platform specific Bundlers.
+ */
+public abstract class AbstractImageBundler extends AbstractBundler {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.incubator.jpackage.internal.resources.MainResources");
+
+ public void imageBundleValidation(Map<String, ? super Object> params)
+ throws ConfigException {
+ StandardBundlerParam.validateMainClassInfoFromAppResources(params);
+
+ }
+
+ protected File createRoot(Map<String, ? super Object> params,
+ File outputDirectory, boolean dependentTask, String name)
+ throws PackagerException {
+
+ IOUtils.writableOutputDir(outputDirectory.toPath());
+
+ if (!dependentTask) {
+ Log.verbose(MessageFormat.format(
+ I18N.getString("message.creating-app-bundle"),
+ name, outputDirectory.getAbsolutePath()));
+ }
+
+ // NAME will default to CLASS, so the real problem is no MAIN_CLASS
+ if (name == null) {
+ throw new PackagerException("ERR_NoMainClass");
+ }
+
+ // Create directory structure
+ File rootDirectory = new File(outputDirectory, name);
+
+ if (rootDirectory.exists()) {
+ throw new PackagerException("error.root-exists",
+ rootDirectory.getAbsolutePath());
+ }
+
+ rootDirectory.mkdirs();
+
+ return rootDirectory;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/AddLauncherArguments.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.io.File;
+import jdk.incubator.jpackage.internal.Arguments.CLIOptions;
+
+/*
+ * AddLauncherArguments
+ *
+ * Processes a add-launcher properties file to create the Map of
+ * bundle params applicable to the add-launcher:
+ *
+ * BundlerParams p = (new AddLauncherArguments(file)).getLauncherMap();
+ *
+ * A add-launcher is another executable program generated by either the
+ * create-app-image mode or the create-installer mode.
+ * The add-launcher may be the same program with different configuration,
+ * or a completely different program created from the same files.
+ *
+ * There may be multiple add-launchers, each created by using the
+ * command line arg "--add-launcher <file path>
+ *
+ * The add-launcher properties file may have any of:
+ *
+ * appVersion
+ * module
+ * main-jar
+ * main-class
+ * icon
+ * arguments
+ * java-options
+ * win-console
+ * linux-app-category
+ *
+ */
+class AddLauncherArguments {
+
+ private final String name;
+ private final String filename;
+ private Map<String, String> allArgs;
+ private Map<String, ? super Object> bundleParams;
+
+ AddLauncherArguments(String name, String filename) {
+ this.name = name;
+ this.filename = filename;
+ }
+
+ private void initLauncherMap() {
+ if (bundleParams != null) {
+ return;
+ }
+
+ allArgs = Arguments.getPropertiesFromFile(filename);
+ allArgs.put(CLIOptions.NAME.getId(), name);
+
+ bundleParams = new HashMap<>();
+ String mainJar = getOptionValue(CLIOptions.MAIN_JAR);
+ String mainClass = getOptionValue(CLIOptions.APPCLASS);
+ String module = getOptionValue(CLIOptions.MODULE);
+
+ if (module != null && mainClass != null) {
+ putUnlessNull(bundleParams, CLIOptions.MODULE.getId(),
+ module + "/" + mainClass);
+ } else if (module != null) {
+ putUnlessNull(bundleParams, CLIOptions.MODULE.getId(),
+ module);
+ } else {
+ putUnlessNull(bundleParams, CLIOptions.MAIN_JAR.getId(),
+ mainJar);
+ putUnlessNull(bundleParams, CLIOptions.APPCLASS.getId(),
+ mainClass);
+ }
+
+ putUnlessNull(bundleParams, CLIOptions.NAME.getId(),
+ getOptionValue(CLIOptions.NAME));
+
+ putUnlessNull(bundleParams, CLIOptions.VERSION.getId(),
+ getOptionValue(CLIOptions.VERSION));
+
+ putUnlessNull(bundleParams, CLIOptions.RELEASE.getId(),
+ getOptionValue(CLIOptions.RELEASE));
+
+ putUnlessNull(bundleParams, CLIOptions.LINUX_CATEGORY.getId(),
+ getOptionValue(CLIOptions.LINUX_CATEGORY));
+
+ putUnlessNull(bundleParams,
+ CLIOptions.WIN_CONSOLE_HINT.getId(),
+ getOptionValue(CLIOptions.WIN_CONSOLE_HINT));
+
+ String value = getOptionValue(CLIOptions.ICON);
+ putUnlessNull(bundleParams, CLIOptions.ICON.getId(),
+ (value == null) ? null : new File(value));
+
+ // "arguments" and "java-options" even if value is null:
+ if (allArgs.containsKey(CLIOptions.ARGUMENTS.getId())) {
+ String argumentStr = getOptionValue(CLIOptions.ARGUMENTS);
+ bundleParams.put(CLIOptions.ARGUMENTS.getId(),
+ Arguments.getArgumentList(argumentStr));
+ }
+
+ if (allArgs.containsKey(CLIOptions.JAVA_OPTIONS.getId())) {
+ String jvmargsStr = getOptionValue(CLIOptions.JAVA_OPTIONS);
+ bundleParams.put(CLIOptions.JAVA_OPTIONS.getId(),
+ Arguments.getArgumentList(jvmargsStr));
+ }
+ }
+
+ private String getOptionValue(CLIOptions option) {
+ if (option == null || allArgs == null) {
+ return null;
+ }
+
+ String id = option.getId();
+
+ if (allArgs.containsKey(id)) {
+ return allArgs.get(id);
+ }
+
+ return null;
+ }
+
+ Map<String, ? super Object> getLauncherMap() {
+ initLauncherMap();
+ return bundleParams;
+ }
+
+ private void putUnlessNull(Map<String, ? super Object> params,
+ String param, Object value) {
+ if (value != null) {
+ params.put(param, value);
+ }
+ }
+
+ static Map<String, ? super Object> merge(
+ Map<String, ? super Object> original,
+ Map<String, ? super Object> additional) {
+ Map<String, ? super Object> tmp = new HashMap<>(original);
+ if (additional.containsKey(CLIOptions.MODULE.getId())) {
+ tmp.remove(CLIOptions.MAIN_JAR.getId());
+ tmp.remove(CLIOptions.APPCLASS.getId());
+ } else if (additional.containsKey(CLIOptions.MAIN_JAR.getId())) {
+ tmp.remove(CLIOptions.MODULE.getId());
+ }
+ if (additional.containsKey(CLIOptions.ARGUMENTS.getId())) {
+ // if add launcher properties file contains "arguments", even with
+ // null value, disregard the "arguments" from command line
+ tmp.remove(CLIOptions.ARGUMENTS.getId());
+ }
+ if (additional.containsKey(CLIOptions.JAVA_OPTIONS.getId())) {
+ // same thing for java-options
+ tmp.remove(CLIOptions.JAVA_OPTIONS.getId());
+ }
+ tmp.putAll(additional);
+ return tmp;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/AppImageFile.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,241 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.incubator.jpackage.internal;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+
+public class AppImageFile {
+
+ // These values will be loaded from AppImage xml file.
+ private final String creatorVersion;
+ private final String creatorPlatform;
+ private final String launcherName;
+ private final List<String> addLauncherNames;
+
+ private final static String FILENAME = ".jpackage.xml";
+
+ private final static Map<Platform, String> PLATFORM_LABELS = Map.of(
+ Platform.LINUX, "linux", Platform.WINDOWS, "windows", Platform.MAC,
+ "macOS");
+
+
+ private AppImageFile() {
+ this(null, null, null, null);
+ }
+
+ private AppImageFile(String launcherName, List<String> addLauncherNames,
+ String creatorVersion, String creatorPlatform) {
+ this.launcherName = launcherName;
+ this.addLauncherNames = addLauncherNames;
+ this.creatorVersion = creatorVersion;
+ this.creatorPlatform = creatorPlatform;
+ }
+
+ /**
+ * Returns list of additional launchers configured for the application.
+ * Each item in the list is not null or empty string.
+ * Returns empty list for application without additional launchers.
+ */
+ List<String> getAddLauncherNames() {
+ return addLauncherNames;
+ }
+
+ /**
+ * Returns main application launcher name. Never returns null or empty value.
+ */
+ String getLauncherName() {
+ return launcherName;
+ }
+
+ void verifyCompatible() throws ConfigException {
+ // Just do nothing for now.
+ }
+
+ /**
+ * Returns path to application image info file.
+ * @param appImageDir - path to application image
+ */
+ public static Path getPathInAppImage(Path appImageDir) {
+ return appImageDir.resolve(FILENAME);
+ }
+
+ /**
+ * Saves file with application image info in application image.
+ * @param appImageDir - path to application image
+ * @throws IOException
+ */
+ static void save(Path appImageDir, Map<String, Object> params)
+ throws IOException {
+ IOUtils.createXml(getPathInAppImage(appImageDir), xml -> {
+ xml.writeStartElement("jpackage-state");
+ xml.writeAttribute("version", getVersion());
+ xml.writeAttribute("platform", getPlatform());
+
+ xml.writeStartElement("main-launcher");
+ xml.writeCharacters(APP_NAME.fetchFrom(params));
+ xml.writeEndElement();
+
+ List<Map<String, ? super Object>> addLaunchers =
+ ADD_LAUNCHERS.fetchFrom(params);
+
+ for (int i = 0; i < addLaunchers.size(); i++) {
+ Map<String, ? super Object> sl = addLaunchers.get(i);
+ xml.writeStartElement("add-launcher");
+ xml.writeCharacters(APP_NAME.fetchFrom(sl));
+ xml.writeEndElement();
+ }
+ });
+ }
+
+ /**
+ * Loads application image info from application image.
+ * @param appImageDir - path to application image
+ * @return valid info about application image or null
+ * @throws IOException
+ */
+ static AppImageFile load(Path appImageDir) throws IOException {
+ try {
+ Path path = getPathInAppImage(appImageDir);
+ DocumentBuilderFactory dbf =
+ DocumentBuilderFactory.newDefaultInstance();
+ dbf.setFeature(
+ "http://apache.org/xml/features/nonvalidating/load-external-dtd",
+ false);
+ DocumentBuilder b = dbf.newDocumentBuilder();
+ Document doc = b.parse(new FileInputStream(path.toFile()));
+
+ XPath xPath = XPathFactory.newInstance().newXPath();
+
+ String mainLauncher = xpathQueryNullable(xPath,
+ "/jpackage-state/main-launcher/text()", doc);
+ if (mainLauncher == null) {
+ // No main launcher, this is fatal.
+ return new AppImageFile();
+ }
+
+ List<String> addLaunchers = new ArrayList<String>();
+
+ String platform = xpathQueryNullable(xPath,
+ "/jpackage-state/@platform", doc);
+
+ String version = xpathQueryNullable(xPath,
+ "/jpackage-state/@version", doc);
+
+ NodeList launcherNameNodes = (NodeList) xPath.evaluate(
+ "/jpackage-state/add-launcher/text()", doc,
+ XPathConstants.NODESET);
+
+ for (int i = 0; i != launcherNameNodes.getLength(); i++) {
+ addLaunchers.add(launcherNameNodes.item(i).getNodeValue());
+ }
+
+ AppImageFile file = new AppImageFile(
+ mainLauncher, addLaunchers, version, platform);
+ if (!file.isValid()) {
+ file = new AppImageFile();
+ }
+ return file;
+ } catch (ParserConfigurationException | SAXException ex) {
+ // Let caller sort this out
+ throw new IOException(ex);
+ } catch (XPathExpressionException ex) {
+ // This should never happen as XPath expressions should be correct
+ throw new RuntimeException(ex);
+ }
+ }
+
+ /**
+ * Returns list of launcher names configured for the application.
+ * The first item in the returned list is main launcher name.
+ * Following items in the list are names of additional launchers.
+ */
+ static List<String> getLauncherNames(Path appImageDir,
+ Map<String, ? super Object> params) {
+ List<String> launchers = new ArrayList<>();
+ try {
+ AppImageFile appImageInfo = AppImageFile.load(appImageDir);
+ if (appImageInfo != null) {
+ launchers.add(appImageInfo.getLauncherName());
+ launchers.addAll(appImageInfo.getAddLauncherNames());
+ return launchers;
+ }
+ } catch (IOException ioe) {
+ Log.verbose(ioe);
+ }
+
+ launchers.add(APP_NAME.fetchFrom(params));
+ ADD_LAUNCHERS.fetchFrom(params).stream().map(APP_NAME::fetchFrom).forEach(
+ launchers::add);
+ return launchers;
+ }
+
+ private static String xpathQueryNullable(XPath xPath, String xpathExpr,
+ Document xml) throws XPathExpressionException {
+ NodeList nodes = (NodeList) xPath.evaluate(xpathExpr, xml,
+ XPathConstants.NODESET);
+ if (nodes != null && nodes.getLength() > 0) {
+ return nodes.item(0).getNodeValue();
+ }
+ return null;
+ }
+
+ private static String getVersion() {
+ return System.getProperty("java.version");
+ }
+
+ private static String getPlatform() {
+ return PLATFORM_LABELS.get(Platform.getPlatform());
+ }
+
+ private boolean isValid() {
+ if (launcherName == null || launcherName.length() == 0 ||
+ addLauncherNames.indexOf("") != -1) {
+ // Some launchers have empty names. This is invalid.
+ return false;
+ }
+
+ // Add more validation.
+
+ return true;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/ApplicationLayout.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.incubator.jpackage.internal;
+
+import java.nio.file.Path;
+import java.util.Map;
+
+
+/**
+ * Application directory layout.
+ */
+public final class ApplicationLayout implements PathGroup.Facade<ApplicationLayout> {
+ enum PathRole {
+ RUNTIME, APP, LAUNCHERS, DESKTOP, APP_MODS, DLLS
+ }
+
+ ApplicationLayout(Map<Object, Path> paths) {
+ data = new PathGroup(paths);
+ }
+
+ private ApplicationLayout(PathGroup data) {
+ this.data = data;
+ }
+
+ @Override
+ public PathGroup pathGroup() {
+ return data;
+ }
+
+ @Override
+ public ApplicationLayout resolveAt(Path root) {
+ return new ApplicationLayout(pathGroup().resolveAt(root));
+ }
+
+ /**
+ * Path to launchers directory.
+ */
+ public Path launchersDirectory() {
+ return pathGroup().getPath(PathRole.LAUNCHERS);
+ }
+
+ /**
+ * Path to directory with dynamic libraries.
+ */
+ public Path dllDirectory() {
+ return pathGroup().getPath(PathRole.DLLS);
+ }
+
+ /**
+ * Path to application data directory.
+ */
+ public Path appDirectory() {
+ return pathGroup().getPath(PathRole.APP);
+ }
+
+ /**
+ * Path to Java runtime directory.
+ */
+ public Path runtimeDirectory() {
+ return pathGroup().getPath(PathRole.RUNTIME);
+ }
+
+ /**
+ * Path to application mods directory.
+ */
+ public Path appModsDirectory() {
+ return pathGroup().getPath(PathRole.APP_MODS);
+ }
+
+ /**
+ * Path to directory with application's desktop integration files.
+ */
+ public Path destktopIntegrationDirectory() {
+ return pathGroup().getPath(PathRole.DESKTOP);
+ }
+
+ static ApplicationLayout linuxAppImage() {
+ return new ApplicationLayout(Map.of(
+ PathRole.LAUNCHERS, Path.of("bin"),
+ PathRole.APP, Path.of("lib/app"),
+ PathRole.RUNTIME, Path.of("lib/runtime"),
+ PathRole.DESKTOP, Path.of("lib"),
+ PathRole.DLLS, Path.of("lib"),
+ PathRole.APP_MODS, Path.of("lib/app/mods")
+ ));
+ }
+
+ static ApplicationLayout windowsAppImage() {
+ return new ApplicationLayout(Map.of(
+ PathRole.LAUNCHERS, Path.of(""),
+ PathRole.APP, Path.of("app"),
+ PathRole.RUNTIME, Path.of("runtime"),
+ PathRole.DESKTOP, Path.of(""),
+ PathRole.DLLS, Path.of(""),
+ PathRole.APP_MODS, Path.of("app/mods")
+ ));
+ }
+
+ static ApplicationLayout macAppImage() {
+ return new ApplicationLayout(Map.of(
+ PathRole.LAUNCHERS, Path.of("Contents/MacOS"),
+ PathRole.APP, Path.of("Contents/app"),
+ PathRole.RUNTIME, Path.of("Contents/runtime"),
+ PathRole.DESKTOP, Path.of("Contents/Resources"),
+ PathRole.DLLS, Path.of("Contents/MacOS"),
+ PathRole.APP_MODS, Path.of("Contents/app/mods")
+ ));
+ }
+
+ public static ApplicationLayout platformAppImage() {
+ if (Platform.isWindows()) {
+ return windowsAppImage();
+ }
+
+ if (Platform.isLinux()) {
+ return linuxAppImage();
+ }
+
+ if (Platform.isMac()) {
+ return macAppImage();
+ }
+
+ throw Platform.throwUnknownPlatformError();
+ }
+
+ public static ApplicationLayout javaRuntime() {
+ return new ApplicationLayout(Map.of(PathRole.RUNTIME, Path.of("")));
+ }
+
+ private final PathGroup data;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/ArgAction.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+@FunctionalInterface
+interface ArgAction {
+ void execute();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/Arguments.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,802 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.incubator.jpackage.internal;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Properties;
+import java.util.ResourceBundle;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.stream.Stream;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Arguments
+ *
+ * This class encapsulates and processes the command line arguments,
+ * in effect, implementing all the work of jpackage tool.
+ *
+ * The primary entry point, processArguments():
+ * Processes and validates command line arguments, constructing DeployParams.
+ * Validates the DeployParams, and generate the BundleParams.
+ * Generates List of Bundlers from BundleParams valid for this platform.
+ * Executes each Bundler in the list.
+ */
+public class Arguments {
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.incubator.jpackage.internal.resources.MainResources");
+
+ private static final String FA_EXTENSIONS = "extension";
+ private static final String FA_CONTENT_TYPE = "mime-type";
+ private static final String FA_DESCRIPTION = "description";
+ private static final String FA_ICON = "icon";
+
+ // regexp for parsing args (for example, for additional launchers)
+ private static Pattern pattern = Pattern.compile(
+ "(?:(?:([\"'])(?:\\\\\\1|.)*?(?:\\1|$))|(?:\\\\[\"'\\s]|[^\\s]))++");
+
+ private DeployParams deployParams = null;
+
+ private int pos = 0;
+ private List<String> argList = null;
+
+ private List<CLIOptions> allOptions = null;
+
+ private String input = null;
+ private String output = null;
+
+ private boolean hasMainJar = false;
+ private boolean hasMainClass = false;
+ private boolean hasMainModule = false;
+ public boolean userProvidedBuildRoot = false;
+
+ private String buildRoot = null;
+ private String mainJarPath = null;
+
+ private static boolean runtimeInstaller = false;
+
+ private List<AddLauncherArguments> addLaunchers = null;
+
+ private static Map<String, CLIOptions> argIds = new HashMap<>();
+ private static Map<String, CLIOptions> argShortIds = new HashMap<>();
+
+ static {
+ // init maps for parsing arguments
+ (EnumSet.allOf(CLIOptions.class)).forEach(option -> {
+ argIds.put(option.getIdWithPrefix(), option);
+ if (option.getShortIdWithPrefix() != null) {
+ argShortIds.put(option.getShortIdWithPrefix(), option);
+ }
+ });
+ }
+
+ public Arguments(String[] args) {
+ argList = new ArrayList<String>(args.length);
+ for (String arg : args) {
+ argList.add(arg);
+ }
+ Log.verbose ("\njpackage argument list: \n" + argList + "\n");
+ pos = 0;
+
+ deployParams = new DeployParams();
+
+ allOptions = new ArrayList<>();
+
+ addLaunchers = new ArrayList<>();
+
+ output = Paths.get("").toAbsolutePath().toString();
+ deployParams.setOutput(new File(output));
+ }
+
+ // CLIOptions is public for DeployParamsTest
+ public enum CLIOptions {
+ PACKAGE_TYPE("type", "t", OptionCategories.PROPERTY, () -> {
+ context().deployParams.setTargetFormat(popArg());
+ }),
+
+ INPUT ("input", "i", OptionCategories.PROPERTY, () -> {
+ context().input = popArg();
+ setOptionValue("input", context().input);
+ }),
+
+ OUTPUT ("dest", "d", OptionCategories.PROPERTY, () -> {
+ context().output = popArg();
+ context().deployParams.setOutput(new File(context().output));
+ }),
+
+ DESCRIPTION ("description", OptionCategories.PROPERTY),
+
+ VENDOR ("vendor", OptionCategories.PROPERTY),
+
+ APPCLASS ("main-class", OptionCategories.PROPERTY, () -> {
+ context().hasMainClass = true;
+ setOptionValue("main-class", popArg());
+ }),
+
+ NAME ("name", "n", OptionCategories.PROPERTY),
+
+ VERBOSE ("verbose", OptionCategories.PROPERTY, () -> {
+ setOptionValue("verbose", true);
+ Log.setVerbose();
+ }),
+
+ RESOURCE_DIR("resource-dir",
+ OptionCategories.PROPERTY, () -> {
+ String resourceDir = popArg();
+ setOptionValue("resource-dir", resourceDir);
+ }),
+
+ ARGUMENTS ("arguments", OptionCategories.PROPERTY, () -> {
+ List<String> arguments = getArgumentList(popArg());
+ setOptionValue("arguments", arguments);
+ }),
+
+ ICON ("icon", OptionCategories.PROPERTY),
+
+ COPYRIGHT ("copyright", OptionCategories.PROPERTY),
+
+ LICENSE_FILE ("license-file", OptionCategories.PROPERTY),
+
+ VERSION ("app-version", OptionCategories.PROPERTY),
+
+ RELEASE ("linux-app-release", OptionCategories.PROPERTY),
+
+ JAVA_OPTIONS ("java-options", OptionCategories.PROPERTY, () -> {
+ List<String> args = getArgumentList(popArg());
+ args.forEach(a -> setOptionValue("java-options", a));
+ }),
+
+ FILE_ASSOCIATIONS ("file-associations",
+ OptionCategories.PROPERTY, () -> {
+ Map<String, ? super Object> args = new HashMap<>();
+
+ // load .properties file
+ Map<String, String> initialMap = getPropertiesFromFile(popArg());
+
+ String ext = initialMap.get(FA_EXTENSIONS);
+ if (ext != null) {
+ args.put(StandardBundlerParam.FA_EXTENSIONS.getID(), ext);
+ }
+
+ String type = initialMap.get(FA_CONTENT_TYPE);
+ if (type != null) {
+ args.put(StandardBundlerParam.FA_CONTENT_TYPE.getID(), type);
+ }
+
+ String desc = initialMap.get(FA_DESCRIPTION);
+ if (desc != null) {
+ args.put(StandardBundlerParam.FA_DESCRIPTION.getID(), desc);
+ }
+
+ String icon = initialMap.get(FA_ICON);
+ if (icon != null) {
+ args.put(StandardBundlerParam.FA_ICON.getID(), icon);
+ }
+
+ ArrayList<Map<String, ? super Object>> associationList =
+ new ArrayList<Map<String, ? super Object>>();
+
+ associationList.add(args);
+
+ // check that we really add _another_ value to the list
+ setOptionValue("file-associations", associationList);
+
+ }),
+
+ ADD_LAUNCHER ("add-launcher",
+ OptionCategories.PROPERTY, () -> {
+ String spec = popArg();
+ String name = null;
+ String filename = spec;
+ if (spec.contains("=")) {
+ String[] values = spec.split("=", 2);
+ name = values[0];
+ filename = values[1];
+ }
+ context().addLaunchers.add(
+ new AddLauncherArguments(name, filename));
+ }),
+
+ TEMP_ROOT ("temp", OptionCategories.PROPERTY, () -> {
+ context().buildRoot = popArg();
+ context().userProvidedBuildRoot = true;
+ setOptionValue("temp", context().buildRoot);
+ }),
+
+ INSTALL_DIR ("install-dir", OptionCategories.PROPERTY),
+
+ PREDEFINED_APP_IMAGE ("app-image", OptionCategories.PROPERTY),
+
+ PREDEFINED_RUNTIME_IMAGE ("runtime-image", OptionCategories.PROPERTY),
+
+ MAIN_JAR ("main-jar", OptionCategories.PROPERTY, () -> {
+ context().mainJarPath = popArg();
+ context().hasMainJar = true;
+ setOptionValue("main-jar", context().mainJarPath);
+ }),
+
+ MODULE ("module", "m", OptionCategories.MODULAR, () -> {
+ context().hasMainModule = true;
+ setOptionValue("module", popArg());
+ }),
+
+ ADD_MODULES ("add-modules", OptionCategories.MODULAR),
+
+ MODULE_PATH ("module-path", "p", OptionCategories.MODULAR),
+
+ BIND_SERVICES ("bind-services", OptionCategories.PROPERTY, () -> {
+ setOptionValue("bind-services", true);
+ }),
+
+ MAC_SIGN ("mac-sign", "s", OptionCategories.PLATFORM_MAC, () -> {
+ setOptionValue("mac-sign", true);
+ }),
+
+ MAC_BUNDLE_NAME ("mac-package-name", OptionCategories.PLATFORM_MAC),
+
+ MAC_BUNDLE_IDENTIFIER("mac-package-identifier",
+ OptionCategories.PLATFORM_MAC),
+
+ MAC_BUNDLE_SIGNING_PREFIX ("mac-package-signing-prefix",
+ OptionCategories.PLATFORM_MAC),
+
+ MAC_SIGNING_KEY_NAME ("mac-signing-key-user-name",
+ OptionCategories.PLATFORM_MAC),
+
+ MAC_SIGNING_KEYCHAIN ("mac-signing-keychain",
+ OptionCategories.PLATFORM_MAC),
+
+ MAC_APP_STORE_ENTITLEMENTS ("mac-app-store-entitlements",
+ OptionCategories.PLATFORM_MAC),
+
+ WIN_MENU_HINT ("win-menu", OptionCategories.PLATFORM_WIN, () -> {
+ setOptionValue("win-menu", true);
+ }),
+
+ WIN_MENU_GROUP ("win-menu-group", OptionCategories.PLATFORM_WIN),
+
+ WIN_SHORTCUT_HINT ("win-shortcut",
+ OptionCategories.PLATFORM_WIN, () -> {
+ setOptionValue("win-shortcut", true);
+ }),
+
+ WIN_PER_USER_INSTALLATION ("win-per-user-install",
+ OptionCategories.PLATFORM_WIN, () -> {
+ setOptionValue("win-per-user-install", false);
+ }),
+
+ WIN_DIR_CHOOSER ("win-dir-chooser",
+ OptionCategories.PLATFORM_WIN, () -> {
+ setOptionValue("win-dir-chooser", true);
+ }),
+
+ WIN_UPGRADE_UUID ("win-upgrade-uuid",
+ OptionCategories.PLATFORM_WIN),
+
+ WIN_CONSOLE_HINT ("win-console", OptionCategories.PLATFORM_WIN, () -> {
+ setOptionValue("win-console", true);
+ }),
+
+ LINUX_BUNDLE_NAME ("linux-package-name",
+ OptionCategories.PLATFORM_LINUX),
+
+ LINUX_DEB_MAINTAINER ("linux-deb-maintainer",
+ OptionCategories.PLATFORM_LINUX),
+
+ LINUX_CATEGORY ("linux-app-category",
+ OptionCategories.PLATFORM_LINUX),
+
+ LINUX_RPM_LICENSE_TYPE ("linux-rpm-license-type",
+ OptionCategories.PLATFORM_LINUX),
+
+ LINUX_PACKAGE_DEPENDENCIES ("linux-package-deps",
+ OptionCategories.PLATFORM_LINUX),
+
+ LINUX_SHORTCUT_HINT ("linux-shortcut",
+ OptionCategories.PLATFORM_LINUX, () -> {
+ setOptionValue("linux-shortcut", true);
+ }),
+
+ LINUX_MENU_GROUP ("linux-menu-group", OptionCategories.PLATFORM_LINUX);
+
+ private final String id;
+ private final String shortId;
+ private final OptionCategories category;
+ private final ArgAction action;
+ private static Arguments argContext;
+
+ private CLIOptions(String id, OptionCategories category) {
+ this(id, null, category, null);
+ }
+
+ private CLIOptions(String id, String shortId,
+ OptionCategories category) {
+ this(id, shortId, category, null);
+ }
+
+ private CLIOptions(String id,
+ OptionCategories category, ArgAction action) {
+ this(id, null, category, action);
+ }
+
+ private CLIOptions(String id, String shortId,
+ OptionCategories category, ArgAction action) {
+ this.id = id;
+ this.shortId = shortId;
+ this.action = action;
+ this.category = category;
+ }
+
+ static void setContext(Arguments context) {
+ argContext = context;
+ }
+
+ public static Arguments context() {
+ if (argContext != null) {
+ return argContext;
+ } else {
+ throw new RuntimeException("Argument context is not set.");
+ }
+ }
+
+ public String getId() {
+ return this.id;
+ }
+
+ String getIdWithPrefix() {
+ return "--" + this.id;
+ }
+
+ String getShortIdWithPrefix() {
+ return this.shortId == null ? null : "-" + this.shortId;
+ }
+
+ void execute() {
+ if (action != null) {
+ action.execute();
+ } else {
+ defaultAction();
+ }
+ }
+
+ private void defaultAction() {
+ context().deployParams.addBundleArgument(id, popArg());
+ }
+
+ private static void setOptionValue(String option, Object value) {
+ context().deployParams.addBundleArgument(option, value);
+ }
+
+ private static String popArg() {
+ nextArg();
+ return (context().pos >= context().argList.size()) ?
+ "" : context().argList.get(context().pos);
+ }
+
+ private static String getArg() {
+ return (context().pos >= context().argList.size()) ?
+ "" : context().argList.get(context().pos);
+ }
+
+ private static void nextArg() {
+ context().pos++;
+ }
+
+ private static boolean hasNextArg() {
+ return context().pos < context().argList.size();
+ }
+ }
+
+ enum OptionCategories {
+ MODULAR,
+ PROPERTY,
+ PLATFORM_MAC,
+ PLATFORM_WIN,
+ PLATFORM_LINUX;
+ }
+
+ public boolean processArguments() {
+ try {
+
+ // init context of arguments
+ CLIOptions.setContext(this);
+
+ // parse cmd line
+ String arg;
+ CLIOptions option;
+ for (; CLIOptions.hasNextArg(); CLIOptions.nextArg()) {
+ arg = CLIOptions.getArg();
+ if ((option = toCLIOption(arg)) != null) {
+ // found a CLI option
+ allOptions.add(option);
+ option.execute();
+ } else {
+ throw new PackagerException("ERR_InvalidOption", arg);
+ }
+ }
+
+ if (hasMainJar && !hasMainClass) {
+ // try to get main-class from manifest
+ String mainClass = getMainClassFromManifest();
+ if (mainClass != null) {
+ CLIOptions.setOptionValue(
+ CLIOptions.APPCLASS.getId(), mainClass);
+ }
+ }
+
+ // display error for arguments that are not supported
+ // for current configuration.
+
+ validateArguments();
+
+ addResources(deployParams, input, mainJarPath);
+
+ List<Map<String, ? super Object>> launchersAsMap =
+ new ArrayList<>();
+
+ for (AddLauncherArguments sl : addLaunchers) {
+ launchersAsMap.add(sl.getLauncherMap());
+ }
+
+ deployParams.addBundleArgument(
+ StandardBundlerParam.ADD_LAUNCHERS.getID(),
+ launchersAsMap);
+
+ // at this point deployParams should be already configured
+
+ deployParams.validate();
+
+ BundleParams bp = deployParams.getBundleParams();
+
+ // validate name(s)
+ ArrayList<String> usedNames = new ArrayList<String>();
+ usedNames.add(bp.getName()); // add main app name
+
+ for (AddLauncherArguments sl : addLaunchers) {
+ Map<String, ? super Object> slMap = sl.getLauncherMap();
+ String slName =
+ (String) slMap.get(Arguments.CLIOptions.NAME.getId());
+ if (slName == null) {
+ throw new PackagerException("ERR_NoAddLauncherName");
+ }
+ // same rules apply to additional launcher names as app name
+ DeployParams.validateName(slName, false);
+ for (String usedName : usedNames) {
+ if (slName.equals(usedName)) {
+ throw new PackagerException("ERR_NoUniqueName");
+ }
+ }
+ usedNames.add(slName);
+ }
+ if (runtimeInstaller && bp.getName() == null) {
+ throw new PackagerException("ERR_NoJreInstallerName");
+ }
+
+ generateBundle(bp.getBundleParamsAsMap());
+ return true;
+ } catch (Exception e) {
+ if (Log.isVerbose()) {
+ Log.verbose(e);
+ } else {
+ String msg1 = e.getMessage();
+ Log.error(msg1);
+ if (e.getCause() != null && e.getCause() != e) {
+ String msg2 = e.getCause().getMessage();
+ if (msg2 != null && !msg1.contains(msg2)) {
+ Log.error(msg2);
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ private void validateArguments() throws PackagerException {
+ String type = deployParams.getTargetFormat();
+ String ptype = (type != null) ? type : "default";
+ boolean imageOnly = deployParams.isTargetAppImage();
+ boolean hasAppImage = allOptions.contains(
+ CLIOptions.PREDEFINED_APP_IMAGE);
+ boolean hasRuntime = allOptions.contains(
+ CLIOptions.PREDEFINED_RUNTIME_IMAGE);
+ boolean installerOnly = !imageOnly && hasAppImage;
+ runtimeInstaller = !imageOnly && hasRuntime && !hasAppImage &&
+ !hasMainModule && !hasMainJar;
+
+ for (CLIOptions option : allOptions) {
+ if (!ValidOptions.checkIfSupported(option)) {
+ // includes option valid only on different platform
+ throw new PackagerException("ERR_UnsupportedOption",
+ option.getIdWithPrefix());
+ }
+ if (imageOnly) {
+ if (!ValidOptions.checkIfImageSupported(option)) {
+ throw new PackagerException("ERR_InvalidTypeOption",
+ option.getIdWithPrefix(), type);
+ }
+ } else if (installerOnly || runtimeInstaller) {
+ if (!ValidOptions.checkIfInstallerSupported(option)) {
+ if (runtimeInstaller) {
+ throw new PackagerException("ERR_NoInstallerEntryPoint",
+ option.getIdWithPrefix());
+ } else {
+ throw new PackagerException("ERR_InvalidTypeOption",
+ option.getIdWithPrefix(), ptype);
+ }
+ }
+ }
+ }
+ if (installerOnly && hasRuntime) {
+ // note --runtime-image is only for image or runtime installer.
+ throw new PackagerException("ERR_InvalidTypeOption",
+ CLIOptions.PREDEFINED_RUNTIME_IMAGE.getIdWithPrefix(),
+ ptype);
+ }
+ if (hasMainJar && hasMainModule) {
+ throw new PackagerException("ERR_BothMainJarAndModule");
+ }
+ if (imageOnly && !hasMainJar && !hasMainModule) {
+ throw new PackagerException("ERR_NoEntryPoint");
+ }
+ }
+
+ private jdk.incubator.jpackage.internal.Bundler getPlatformBundler() {
+ boolean appImage = deployParams.isTargetAppImage();
+ String type = deployParams.getTargetFormat();
+ String bundleType = (appImage ? "IMAGE" : "INSTALLER");
+
+ for (jdk.incubator.jpackage.internal.Bundler bundler :
+ Bundlers.createBundlersInstance().getBundlers(bundleType)) {
+ if (type == null) {
+ if (bundler.isDefault()
+ && bundler.supported(runtimeInstaller)) {
+ return bundler;
+ }
+ } else {
+ if ((appImage || type.equalsIgnoreCase(bundler.getID()))
+ && bundler.supported(runtimeInstaller)) {
+ return bundler;
+ }
+ }
+ }
+ return null;
+ }
+
+ private void generateBundle(Map<String,? super Object> params)
+ throws PackagerException {
+
+ boolean bundleCreated = false;
+
+ // the temp dir needs to be fetched from the params early,
+ // to prevent each copy of the params (such as may be used for
+ // additional launchers) from generating a separate temp dir when
+ // the default is used (the default is a new temp directory)
+ // The bundler.cleanup() below would not otherwise be able to
+ // clean these extra (and unneeded) temp directories.
+ StandardBundlerParam.TEMP_ROOT.fetchFrom(params);
+
+ // determine what bundler to run
+ jdk.incubator.jpackage.internal.Bundler bundler = getPlatformBundler();
+
+ if (bundler == null) {
+ throw new PackagerException("ERR_InvalidInstallerType",
+ deployParams.getTargetFormat());
+ }
+
+ Map<String, ? super Object> localParams = new HashMap<>(params);
+ try {
+ bundler.validate(localParams);
+ File result = bundler.execute(localParams, deployParams.outdir);
+ if (result == null) {
+ throw new PackagerException("MSG_BundlerFailed",
+ bundler.getID(), bundler.getName());
+ }
+ Log.verbose(MessageFormat.format(
+ I18N.getString("message.bundle-created"),
+ bundler.getName()));
+ } catch (ConfigException e) {
+ Log.verbose(e);
+ if (e.getAdvice() != null) {
+ throw new PackagerException(e, "MSG_BundlerConfigException",
+ bundler.getName(), e.getMessage(), e.getAdvice());
+ } else {
+ throw new PackagerException(e,
+ "MSG_BundlerConfigExceptionNoAdvice",
+ bundler.getName(), e.getMessage());
+ }
+ } catch (RuntimeException re) {
+ Log.verbose(re);
+ throw new PackagerException(re, "MSG_BundlerRuntimeException",
+ bundler.getName(), re.toString());
+ } finally {
+ if (userProvidedBuildRoot) {
+ Log.verbose(MessageFormat.format(
+ I18N.getString("message.debug-working-directory"),
+ (new File(buildRoot)).getAbsolutePath()));
+ } else {
+ // always clean up the temporary directory created
+ // when --temp option not used.
+ bundler.cleanup(localParams);
+ }
+ }
+ }
+
+ private void addResources(DeployParams deployParams,
+ String inputdir, String mainJar) throws PackagerException {
+
+ if (inputdir == null || inputdir.isEmpty()) {
+ return;
+ }
+
+ File baseDir = new File(inputdir);
+
+ if (!baseDir.isDirectory()) {
+ throw new PackagerException("ERR_InputNotDirectory", inputdir);
+ }
+ if (!baseDir.canRead()) {
+ throw new PackagerException("ERR_CannotReadInputDir", inputdir);
+ }
+
+ List<String> fileNames;
+ fileNames = new ArrayList<>();
+ try (Stream<Path> files = Files.list(baseDir.toPath())) {
+ files.forEach(file -> fileNames.add(
+ file.getFileName().toString()));
+ } catch (IOException e) {
+ Log.error("Unable to add resources: " + e.getMessage());
+ }
+ fileNames.forEach(file -> deployParams.addResource(baseDir, file));
+
+ deployParams.setClasspath(mainJar);
+ }
+
+ static CLIOptions toCLIOption(String arg) {
+ CLIOptions option;
+ if ((option = argIds.get(arg)) == null) {
+ option = argShortIds.get(arg);
+ }
+ return option;
+ }
+
+ static Map<String, String> getPropertiesFromFile(String filename) {
+ Map<String, String> map = new HashMap<>();
+ // load properties file
+ File file = new File(filename);
+ Properties properties = new Properties();
+ try (FileInputStream in = new FileInputStream(file)) {
+ properties.load(in);
+ } catch (IOException e) {
+ Log.error("Exception: " + e.getMessage());
+ }
+
+ for (final String name: properties.stringPropertyNames()) {
+ map.put(name, properties.getProperty(name));
+ }
+
+ return map;
+ }
+
+ static List<String> getArgumentList(String inputString) {
+ List<String> list = new ArrayList<>();
+ if (inputString == null || inputString.isEmpty()) {
+ return list;
+ }
+
+ // The "pattern" regexp attempts to abide to the rule that
+ // strings are delimited by whitespace unless surrounded by
+ // quotes, then it is anything (including spaces) in the quotes.
+ Matcher m = pattern.matcher(inputString);
+ while (m.find()) {
+ String s = inputString.substring(m.start(), m.end()).trim();
+ // Ensure we do not have an empty string. trim() will take care of
+ // whitespace only strings. The regex preserves quotes and escaped
+ // chars so we need to clean them before adding to the List
+ if (!s.isEmpty()) {
+ list.add(unquoteIfNeeded(s));
+ }
+ }
+ return list;
+ }
+
+ private static String unquoteIfNeeded(String in) {
+ if (in == null) {
+ return null;
+ }
+
+ if (in.isEmpty()) {
+ return "";
+ }
+
+ // Use code points to preserve non-ASCII chars
+ StringBuilder sb = new StringBuilder();
+ int codeLen = in.codePointCount(0, in.length());
+ int quoteChar = -1;
+ for (int i = 0; i < codeLen; i++) {
+ int code = in.codePointAt(i);
+ if (code == '"' || code == '\'') {
+ // If quote is escaped make sure to copy it
+ if (i > 0 && in.codePointAt(i - 1) == '\\') {
+ sb.deleteCharAt(sb.length() - 1);
+ sb.appendCodePoint(code);
+ continue;
+ }
+ if (quoteChar != -1) {
+ if (code == quoteChar) {
+ // close quote, skip char
+ quoteChar = -1;
+ } else {
+ sb.appendCodePoint(code);
+ }
+ } else {
+ // opening quote, skip char
+ quoteChar = code;
+ }
+ } else {
+ sb.appendCodePoint(code);
+ }
+ }
+ return sb.toString();
+ }
+
+ private String getMainClassFromManifest() {
+ if (mainJarPath == null ||
+ input == null ) {
+ return null;
+ }
+
+ JarFile jf;
+ try {
+ File file = new File(input, mainJarPath);
+ if (!file.exists()) {
+ return null;
+ }
+ jf = new JarFile(file);
+ Manifest m = jf.getManifest();
+ Attributes attrs = (m != null) ? m.getMainAttributes() : null;
+ if (attrs != null) {
+ return attrs.getValue(Attributes.Name.MAIN_CLASS);
+ }
+ } catch (IOException ignore) {}
+ return null;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/BasicBundlers.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.ServiceLoader;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * BasicBundlers
+ *
+ * A basic bundlers collection that loads the default bundlers.
+ * Loads the common bundlers.
+ * <UL>
+ * <LI>Windows file image</LI>
+ * <LI>Mac .app</LI>
+ * <LI>Linux file image</LI>
+ * <LI>Windows MSI</LI>
+ * <LI>Windows EXE</LI>
+ * <LI>Mac DMG</LI>
+ * <LI>Mac PKG</LI>
+ * <LI>Linux DEB</LI>
+ * <LI>Linux RPM</LI>
+ *
+ * </UL>
+ */
+public class BasicBundlers implements Bundlers {
+
+ boolean defaultsLoaded = false;
+
+ private final Collection<Bundler> bundlers = new CopyOnWriteArrayList<>();
+
+ @Override
+ public Collection<Bundler> getBundlers() {
+ return Collections.unmodifiableCollection(bundlers);
+ }
+
+ @Override
+ public Collection<Bundler> getBundlers(String type) {
+ if (type == null) return Collections.emptySet();
+ switch (type) {
+ case "NONE":
+ return Collections.emptySet();
+ case "ALL":
+ return getBundlers();
+ default:
+ return Arrays.asList(getBundlers().stream()
+ .filter(b -> type.equalsIgnoreCase(b.getBundleType()))
+ .toArray(Bundler[]::new));
+ }
+ }
+
+ // Loads bundlers from the META-INF/services direct
+ @Override
+ public void loadBundlersFromServices(ClassLoader cl) {
+ ServiceLoader<Bundler> loader = ServiceLoader.load(Bundler.class, cl);
+ for (Bundler aLoader : loader) {
+ bundlers.add(aLoader);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/BundleParams.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+
+public class BundleParams {
+
+ final protected Map<String, ? super Object> params;
+
+ // RelativeFileSet
+ public static final String PARAM_APP_RESOURCES = "appResources";
+
+ // String - Icon file name
+ public static final String PARAM_ICON = "icon";
+
+ // String - Name of bundle file and native launcher
+ public static final String PARAM_NAME = "name";
+
+ // String - application vendor, used by most of the bundlers
+ public static final String PARAM_VENDOR = "vendor";
+
+ // String - email name and email, only used for debian */
+ public static final String PARAM_EMAIL = "email";
+
+ // String - vendor <email>, only used for debian */
+ public static final String PARAM_MAINTAINER = "maintainer";
+
+ /* String - Copyright. Used on Mac */
+ public static final String PARAM_COPYRIGHT = "copyright";
+
+ // String - GUID on windows for MSI, CFBundleIdentifier on Mac
+ // If not compatible with requirements then bundler either do not bundle
+ // or autogenerate
+ public static final String PARAM_IDENTIFIER = "identifier";
+
+ /* boolean - shortcut preferences */
+ public static final String PARAM_SHORTCUT = "shortcutHint";
+ // boolean - menu shortcut preference
+ public static final String PARAM_MENU = "menuHint";
+
+ // String - Application version. Format may differ for different bundlers
+ public static final String PARAM_VERSION = "appVersion";
+
+ // String - Application release. Used on Linux.
+ public static final String PARAM_RELEASE = "appRelease";
+
+ // String - Optional application description. Used by MSI and on Linux
+ public static final String PARAM_DESCRIPTION = "description";
+
+ // String - License type. Needed on Linux (rpm)
+ public static final String PARAM_LICENSE_TYPE = "licenseType";
+
+ // String - File with license. Format is OS/bundler specific
+ public static final String PARAM_LICENSE_FILE = "licenseFile";
+
+ // String Main application class.
+ // Not used directly but used to derive default values
+ public static final String PARAM_APPLICATION_CLASS = "applicationClass";
+
+ // boolean - Adds a dialog to let the user choose a directory
+ // where the product will be installed.
+ public static final String PARAM_INSTALLDIR_CHOOSER = "installdirChooser";
+
+ /**
+ * create a new bundle with all default values
+ */
+ public BundleParams() {
+ params = new HashMap<>();
+ }
+
+ /**
+ * Create a bundle params with a copy of the params
+ * @param params map of initial parameters to be copied in.
+ */
+ public BundleParams(Map<String, ?> params) {
+ this.params = new HashMap<>(params);
+ }
+
+ public void addAllBundleParams(Map<String, ? super Object> params) {
+ this.params.putAll(params);
+ }
+
+ // NOTE: we do not care about application parameters here
+ // as they will be embeded into jar file manifest and
+ // java launcher will take care of them!
+
+ public Map<String, ? super Object> getBundleParamsAsMap() {
+ return new HashMap<>(params);
+ }
+
+ public String getName() {
+ return APP_NAME.fetchFrom(params);
+ }
+
+ public void setAppResourcesList(
+ List<jdk.incubator.jpackage.internal.RelativeFileSet> rfs) {
+ putUnlessNull(APP_RESOURCES_LIST.getID(), rfs);
+ }
+
+ private void putUnlessNull(String param, Object value) {
+ if (value != null) {
+ params.put(param, value);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/Bundler.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Bundler
+ *
+ * The basic interface implemented by all Bundlers.
+ */
+public interface Bundler {
+ /**
+ * @return User Friendly name of this bundler.
+ */
+ String getName();
+
+ /**
+ * @return Command line identifier of the bundler. Should be unique.
+ */
+ String getID();
+
+ /**
+ * @return The bundle type of the bundle that is created by this bundler.
+ */
+ String getBundleType();
+
+ /**
+ * Determines if this bundler will execute with the given parameters.
+ *
+ * @param params The parameters to be validate. Validation may modify
+ * the map, so if you are going to be using the same map
+ * across multiple bundlers you should pass in a deep copy.
+ * @return true if valid
+ * @throws ConfigException If the configuration params are incorrect. The
+ * exception may contain advice on how to modify the params map
+ * to make it valid.
+ */
+ public boolean validate(Map<String, ? super Object> params)
+ throws ConfigException;
+
+ /**
+ * Creates a bundle from existing content.
+ *
+ * If a call to {@link #validate(java.util.Map)} date} returns true with
+ * the parameters map, then you can expect a valid output.
+ * However if an exception was thrown out of validate or it returned
+ * false then you should not expect sensible results from this call.
+ * It may or may not return a value, and it may or may not throw an
+ * exception. But any output should not be considered valid or sane.
+ *
+ * @param params The Bundle parameters,
+ * Keyed by the id from the ParamInfo. Execution may
+ * modify the map, so if you are going to be using the
+ * same map across multiple bundlers you should pass
+ * in a deep copy.
+ * @param outputParentDir
+ * The parent dir that the returned bundle will be placed in.
+ * @return The resulting bundled file
+ *
+ * For a bundler that produces a single artifact file this will be the
+ * location of that artifact (.exe file, .deb file, etc)
+ *
+ * For a bundler that produces a specific directory format output this will
+ * be the location of that specific directory (.app file, etc).
+ *
+ * For a bundler that produce multiple files, this will be a parent
+ * directory of those files (linux and windows images), whose name is not
+ * relevant to the result.
+ *
+ * @throws java.lang.IllegalArgumentException for any of the following
+ * reasons:
+ * <ul>
+ * <li>A required parameter is not found in the params list, for
+ * example missing the main class.</li>
+ * <li>A parameter has the wrong type of an object, for example a
+ * String where a File is required</li>
+ * <li>Bundler specific incompatibilities with the parameters, for
+ * example a bad version number format or an application id with
+ * forward slashes.</li>
+ * </ul>
+ */
+ public File execute(Map<String, ? super Object> params,
+ File outputParentDir) throws PackagerException;
+
+ /**
+ * Removes temporary files that are used for bundling.
+ */
+ public void cleanup(Map<String, ? super Object> params);
+
+ /**
+ * Returns "true" if this bundler is supported on current platform.
+ */
+ public boolean supported(boolean runtimeInstaller);
+
+ /**
+ * Returns "true" if this bundler is he default for the current platform.
+ */
+ public boolean isDefault();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/BundlerParamInfo.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+/**
+ * BundlerParamInfo<T>
+ *
+ * A BundlerParamInfo encapsulates an individual bundler parameter of type <T>.
+ */
+class BundlerParamInfo<T> {
+
+ /**
+ * The command line and hashmap name of the parameter
+ */
+ String id;
+
+ /**
+ * Type of the parameter
+ */
+ Class<T> valueType;
+
+ /**
+ * Indicates if value was set using default value function
+ */
+ boolean isDefaultValue;
+
+ /**
+ * If the value is not set, and no fallback value is found,
+ * the parameter uses the value returned by the producer.
+ */
+ Function<Map<String, ? super Object>, T> defaultValueFunction;
+
+ /**
+ * An optional string converter for command line arguments.
+ */
+ BiFunction<String, Map<String, ? super Object>, T> stringConverter;
+
+ String getID() {
+ return id;
+ }
+
+ Class<T> getValueType() {
+ return valueType;
+ }
+
+ boolean getIsDefaultValue() {
+ return isDefaultValue;
+ }
+
+ Function<Map<String, ? super Object>, T> getDefaultValueFunction() {
+ return defaultValueFunction;
+ }
+
+ BiFunction<String, Map<String, ? super Object>,T>
+ getStringConverter() {
+ return stringConverter;
+ }
+
+ @SuppressWarnings("unchecked")
+ final T fetchFrom(Map<String, ? super Object> params) {
+ return fetchFrom(params, true);
+ }
+
+ @SuppressWarnings("unchecked")
+ final T fetchFrom(Map<String, ? super Object> params,
+ boolean invokeDefault) {
+ Object o = params.get(getID());
+ if (o instanceof String && getStringConverter() != null) {
+ return getStringConverter().apply((String)o, params);
+ }
+
+ Class<T> klass = getValueType();
+ if (klass.isInstance(o)) {
+ return (T) o;
+ }
+ if (o != null) {
+ throw new IllegalArgumentException("Param " + getID()
+ + " should be of type " + getValueType()
+ + " but is a " + o.getClass());
+ }
+ if (params.containsKey(getID())) {
+ // explicit nulls are allowed
+ return null;
+ }
+
+ if (invokeDefault && (getDefaultValueFunction() != null)) {
+ T result = getDefaultValueFunction().apply(params);
+ if (result != null) {
+ params.put(getID(), result);
+ isDefaultValue = true;
+ }
+ return result;
+ }
+
+ // ultimate fallback
+ return null;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/Bundlers.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.ServiceLoader;
+
+/**
+ * Bundlers
+ *
+ * The interface implemented by BasicBundlers
+ */
+public interface Bundlers {
+
+ /**
+ * This convenience method will call
+ * {@link #createBundlersInstance(ClassLoader)}
+ * with the classloader that this Bundlers is loaded from.
+ *
+ * @return an instance of Bundlers loaded and configured from
+ * the current ClassLoader.
+ */
+ public static Bundlers createBundlersInstance() {
+ return createBundlersInstance(Bundlers.class.getClassLoader());
+ }
+
+ /**
+ * This convenience method will automatically load a Bundlers instance
+ * from either META-INF/services or the default
+ * {@link BasicBundlers} if none are found in
+ * the services meta-inf.
+ *
+ * After instantiating the bundlers instance it will load the default
+ * bundlers via {@link #loadDefaultBundlers()} as well as requesting
+ * the services loader to load any other bundelrs via
+ * {@link #loadBundlersFromServices(ClassLoader)}.
+
+ *
+ * @param servicesClassLoader the classloader to search for
+ * META-INF/service registered bundlers
+ * @return an instance of Bundlers loaded and configured from
+ * the specified ClassLoader
+ */
+ public static Bundlers createBundlersInstance(
+ ClassLoader servicesClassLoader) {
+ ServiceLoader<Bundlers> bundlersLoader =
+ ServiceLoader.load(Bundlers.class, servicesClassLoader);
+ Bundlers bundlers = null;
+ Iterator<Bundlers> iter = bundlersLoader.iterator();
+ if (iter.hasNext()) {
+ bundlers = iter.next();
+ }
+ if (bundlers == null) {
+ bundlers = new BasicBundlers();
+ }
+
+ bundlers.loadBundlersFromServices(servicesClassLoader);
+ return bundlers;
+ }
+
+ /**
+ * Returns all of the preconfigured, requested, and manually
+ * configured bundlers loaded with this instance.
+ *
+ * @return a read-only collection of the requested bundlers
+ */
+ Collection<Bundler> getBundlers();
+
+ /**
+ * Returns all of the preconfigured, requested, and manually
+ * configured bundlers loaded with this instance that are of
+ * a specific BundleType, such as disk images, installers, or
+ * remote installers.
+ *
+ * @return a read-only collection of the requested bundlers
+ */
+ Collection<Bundler> getBundlers(String type);
+
+ /**
+ * Loads bundlers from the META-INF/services directly.
+ *
+ * This method is called from the
+ * {@link #createBundlersInstance(ClassLoader)}
+ * and {@link #createBundlersInstance()} methods.
+ */
+ void loadBundlersFromServices(ClassLoader cl);
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/CLIHelp.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.util.ResourceBundle;
+import java.io.File;
+import java.text.MessageFormat;
+
+
+/**
+ * CLIHelp
+ *
+ * Generate and show the command line interface help message(s).
+ */
+public class CLIHelp {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.incubator.jpackage.internal.resources.HelpResources");
+
+ // generates --help for jpackage's CLI
+ public static void showHelp(boolean noArgs) {
+
+ if (noArgs) {
+ Log.info(I18N.getString("MSG_Help_no_args"));
+ } else {
+ Platform platform = (Log.isVerbose()) ?
+ Platform.UNKNOWN : Platform.getPlatform();
+ String types;
+ String pLaunchOptions;
+ String pInstallOptions;
+ String pInstallDir;
+ switch (platform) {
+ case MAC:
+ types = "{\"app-image\", \"dmg\", \"pkg\"}";
+ pLaunchOptions = I18N.getString("MSG_Help_mac_launcher");
+ pInstallOptions = "";
+ pInstallDir
+ = I18N.getString("MSG_Help_mac_linux_install_dir");
+ break;
+ case LINUX:
+ types = "{\"app-image\", \"rpm\", \"deb\"}";
+ pLaunchOptions = "";
+ pInstallOptions = I18N.getString("MSG_Help_linux_install");
+ pInstallDir
+ = I18N.getString("MSG_Help_mac_linux_install_dir");
+ break;
+ case WINDOWS:
+ types = "{\"app-image\", \"exe\", \"msi\"}";
+ pLaunchOptions = I18N.getString("MSG_Help_win_launcher");
+ pInstallOptions = I18N.getString("MSG_Help_win_install");
+ pInstallDir
+ = I18N.getString("MSG_Help_win_install_dir");
+ break;
+ default:
+ types = "{\"app-image\", \"exe\", \"msi\", \"rpm\", \"deb\", \"pkg\", \"dmg\"}";
+ pLaunchOptions = I18N.getString("MSG_Help_win_launcher")
+ + I18N.getString("MSG_Help_mac_launcher");
+ pInstallOptions = I18N.getString("MSG_Help_win_install")
+ + I18N.getString("MSG_Help_linux_install");
+ pInstallDir
+ = I18N.getString("MSG_Help_default_install_dir");
+ break;
+ }
+ Log.info(MessageFormat.format(I18N.getString("MSG_Help"),
+ File.pathSeparator, types, pLaunchOptions,
+ pInstallOptions, pInstallDir));
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/ConfigException.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+public class ConfigException extends Exception {
+ private static final long serialVersionUID = 1L;
+ final String advice;
+
+ public ConfigException(String msg, String advice) {
+ super(msg);
+ this.advice = advice;
+ }
+
+ public ConfigException(String msg, String advice, Exception cause) {
+ super(msg, cause);
+ this.advice = advice;
+ }
+
+ public ConfigException(Exception cause) {
+ super(cause);
+ this.advice = null;
+ }
+
+ public String getAdvice() {
+ return advice;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/DeployParams.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,354 @@
+/*
+ * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.InvalidPathException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+/**
+ * DeployParams
+ *
+ * This class is generated and used in Arguments.processArguments() as
+ * intermediate step in generating the BundleParams and ultimately the Bundles
+ */
+public class DeployParams {
+
+ final List<RelativeFileSet> resources = new ArrayList<>();
+
+ String targetFormat = null; // means default type for this platform
+
+ File outdir = null;
+
+ // raw arguments to the bundler
+ Map<String, ? super Object> bundlerArguments = new LinkedHashMap<>();
+
+ public void setOutput(File output) {
+ outdir = output;
+ }
+
+ static class Template {
+ File in;
+ File out;
+
+ Template(File in, File out) {
+ this.in = in;
+ this.out = out;
+ }
+ }
+
+ // we need to expand as in some cases
+ // (most notably jpackage)
+ // we may get "." as filename and assumption is we include
+ // everything in the given folder
+ // (IOUtils.copyfiles() have recursive behavior)
+ List<File> expandFileset(File root) {
+ List<File> files = new LinkedList<>();
+ if (!Files.isSymbolicLink(root.toPath())) {
+ if (root.isDirectory()) {
+ File[] children = root.listFiles();
+ if (children != null) {
+ for (File f : children) {
+ files.addAll(expandFileset(f));
+ }
+ }
+ } else {
+ files.add(root);
+ }
+ }
+ return files;
+ }
+
+ public void addResource(File baseDir, String path) {
+ addResource(baseDir, new File(baseDir, path));
+ }
+
+ public void addResource(File baseDir, File file) {
+ // normalize initial file
+ // to strip things like "." in the path
+ // or it can confuse symlink detection logic
+ file = file.getAbsoluteFile();
+
+ if (baseDir == null) {
+ baseDir = file.getParentFile();
+ }
+ resources.add(new RelativeFileSet(
+ baseDir, new LinkedHashSet<>(expandFileset(file))));
+ }
+
+ void setClasspath(String mainJarPath) {
+ String classpath;
+ // we want main jar first on the classpath
+ if (mainJarPath != null) {
+ classpath = mainJarPath + File.pathSeparator;
+ } else {
+ classpath = "";
+ }
+ for (RelativeFileSet resource : resources) {
+ for (String file : resource.getIncludedFiles()) {
+ if (file.endsWith(".jar")) {
+ if (!file.equals(mainJarPath)) {
+ classpath += file + File.pathSeparator;
+ }
+ }
+ }
+ }
+ addBundleArgument(
+ StandardBundlerParam.CLASSPATH.getID(), classpath);
+ }
+
+ static void validateName(String s, boolean forApp)
+ throws PackagerException {
+
+ String exceptionKey = forApp ?
+ "ERR_InvalidAppName" : "ERR_InvalidSLName";
+
+ if (s == null) {
+ if (forApp) {
+ return;
+ } else {
+ throw new PackagerException(exceptionKey, s);
+ }
+ }
+ if (s.length() == 0 || s.charAt(s.length() - 1) == '\\') {
+ throw new PackagerException(exceptionKey, s);
+ }
+ try {
+ // name must be valid path element for this file system
+ Path p = (new File(s)).toPath();
+ // and it must be a single name element in a path
+ if (p.getNameCount() != 1) {
+ throw new PackagerException(exceptionKey, s);
+ }
+ } catch (InvalidPathException ipe) {
+ throw new PackagerException(ipe, exceptionKey, s);
+ }
+
+ for (int i = 0; i < s.length(); i++) {
+ char a = s.charAt(i);
+ // We check for ASCII codes first which we accept. If check fails,
+ // check if it is acceptable extended ASCII or unicode character.
+ if (a < ' ' || a > '~') {
+ // Accept anything else including special chars like copyright
+ // symbols. Note: space will be included by ASCII check above,
+ // but other whitespace like tabs or new line will be rejected.
+ if (Character.isISOControl(a) ||
+ Character.isWhitespace(a)) {
+ throw new PackagerException(exceptionKey, s);
+ }
+ } else if (a == '"' || a == '%') {
+ throw new PackagerException(exceptionKey, s);
+ }
+ }
+ }
+
+ public void validate() throws PackagerException {
+ boolean hasModule = (bundlerArguments.get(
+ Arguments.CLIOptions.MODULE.getId()) != null);
+ boolean hasAppImage = (bundlerArguments.get(
+ Arguments.CLIOptions.PREDEFINED_APP_IMAGE.getId()) != null);
+ boolean hasClass = (bundlerArguments.get(
+ Arguments.CLIOptions.APPCLASS.getId()) != null);
+ boolean hasMain = (bundlerArguments.get(
+ Arguments.CLIOptions.MAIN_JAR.getId()) != null);
+ boolean hasRuntimeImage = (bundlerArguments.get(
+ Arguments.CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId()) != null);
+ boolean hasInput = (bundlerArguments.get(
+ Arguments.CLIOptions.INPUT.getId()) != null);
+ boolean hasModulePath = (bundlerArguments.get(
+ Arguments.CLIOptions.MODULE_PATH.getId()) != null);
+ boolean runtimeInstaller = !isTargetAppImage() &&
+ !hasAppImage && !hasModule && !hasMain && hasRuntimeImage;
+
+ if (isTargetAppImage()) {
+ // Module application requires --runtime-image or --module-path
+ if (hasModule) {
+ if (!hasModulePath && !hasRuntimeImage) {
+ throw new PackagerException("ERR_MissingArgument",
+ "--runtime-image or --module-path");
+ }
+ } else {
+ if (!hasInput) {
+ throw new PackagerException(
+ "ERR_MissingArgument", "--input");
+ }
+ }
+ } else {
+ if (!runtimeInstaller) {
+ if (hasModule) {
+ if (!hasModulePath && !hasRuntimeImage && !hasAppImage) {
+ throw new PackagerException("ERR_MissingArgument",
+ "--runtime-image, --module-path or --app-image");
+ }
+ } else {
+ if (!hasInput && !hasAppImage) {
+ throw new PackagerException("ERR_MissingArgument",
+ "--input or --app-image");
+ }
+ }
+ }
+ }
+
+ // if bundling non-modular image, or installer without app-image
+ // then we need some resources and a main class
+ if (!hasModule && !hasAppImage && !runtimeInstaller) {
+ if (resources.isEmpty()) {
+ throw new PackagerException("ERR_MissingAppResources");
+ }
+ if (!hasMain) {
+ throw new PackagerException("ERR_MissingArgument",
+ "--main-jar");
+ }
+ }
+
+ String name = (String)bundlerArguments.get(
+ Arguments.CLIOptions.NAME.getId());
+ validateName(name, true);
+
+ // Validate app image if set
+ String appImage = (String)bundlerArguments.get(
+ Arguments.CLIOptions.PREDEFINED_APP_IMAGE.getId());
+ if (appImage != null) {
+ File appImageDir = new File(appImage);
+ if (!appImageDir.exists() || appImageDir.list().length == 0) {
+ throw new PackagerException("ERR_AppImageNotExist", appImage);
+ }
+ }
+
+ // Validate temp dir
+ String root = (String)bundlerArguments.get(
+ Arguments.CLIOptions.TEMP_ROOT.getId());
+ if (root != null) {
+ String [] contents = (new File(root)).list();
+
+ if (contents != null && contents.length > 0) {
+ throw new PackagerException("ERR_BuildRootInvalid", root);
+ }
+ }
+
+ // Validate license file if set
+ String license = (String)bundlerArguments.get(
+ Arguments.CLIOptions.LICENSE_FILE.getId());
+ if (license != null) {
+ File licenseFile = new File(license);
+ if (!licenseFile.exists()) {
+ throw new PackagerException("ERR_LicenseFileNotExit");
+ }
+ }
+ }
+
+ void setTargetFormat(String t) {
+ targetFormat = t;
+ }
+
+ String getTargetFormat() {
+ return targetFormat;
+ }
+
+ boolean isTargetAppImage() {
+ return ("app-image".equals(targetFormat));
+ }
+
+ private static final Set<String> multi_args = new TreeSet<>(Arrays.asList(
+ StandardBundlerParam.JAVA_OPTIONS.getID(),
+ StandardBundlerParam.ARGUMENTS.getID(),
+ StandardBundlerParam.MODULE_PATH.getID(),
+ StandardBundlerParam.ADD_MODULES.getID(),
+ StandardBundlerParam.LIMIT_MODULES.getID(),
+ StandardBundlerParam.FILE_ASSOCIATIONS.getID()
+ ));
+
+ @SuppressWarnings("unchecked")
+ public void addBundleArgument(String key, Object value) {
+ // special hack for multi-line arguments
+ if (multi_args.contains(key)) {
+ Object existingValue = bundlerArguments.get(key);
+ if (existingValue instanceof String && value instanceof String) {
+ String delim = "\n\n";
+ if (key.equals(StandardBundlerParam.MODULE_PATH.getID())) {
+ delim = File.pathSeparator;
+ } else if (key.equals(
+ StandardBundlerParam.ADD_MODULES.getID())) {
+ delim = ",";
+ }
+ bundlerArguments.put(key, existingValue + delim + value);
+ } else if (existingValue instanceof List && value instanceof List) {
+ ((List)existingValue).addAll((List)value);
+ } else if (existingValue instanceof Map &&
+ value instanceof String && ((String)value).contains("=")) {
+ String[] mapValues = ((String)value).split("=", 2);
+ ((Map)existingValue).put(mapValues[0], mapValues[1]);
+ } else {
+ bundlerArguments.put(key, value);
+ }
+ } else {
+ bundlerArguments.put(key, value);
+ }
+ }
+
+ BundleParams getBundleParams() {
+ BundleParams bundleParams = new BundleParams();
+
+ // construct app resources relative to destination folder!
+ bundleParams.setAppResourcesList(resources);
+
+ Map<String, String> unescapedHtmlParams = new TreeMap<>();
+ Map<String, String> escapedHtmlParams = new TreeMap<>();
+
+ // check for collisions
+ TreeSet<String> keys = new TreeSet<>(bundlerArguments.keySet());
+ keys.retainAll(bundleParams.getBundleParamsAsMap().keySet());
+
+ if (!keys.isEmpty()) {
+ throw new RuntimeException("Deploy Params and Bundler Arguments "
+ + "overlap in the following values:" + keys.toString());
+ }
+
+ bundleParams.addAllBundleParams(bundlerArguments);
+
+ return bundleParams;
+ }
+
+ @Override
+ public String toString() {
+ return "DeployParams {" + "output: " + outdir
+ + " resources: {" + resources + "}}";
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/DottedVersion.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+/**
+ * Dotted numeric version string.
+ * E.g.: 1.0.37, 10, 0.5
+ */
+class DottedVersion implements Comparable<String> {
+
+ public DottedVersion(String version) {
+ greedy = true;
+ components = parseVersionString(version, greedy);
+ value = version;
+ }
+
+ private DottedVersion(String version, boolean greedy) {
+ this.greedy = greedy;
+ components = parseVersionString(version, greedy);
+ value = version;
+ }
+
+ public static DottedVersion greedy(String version) {
+ return new DottedVersion(version);
+ }
+
+ public static DottedVersion lazy(String version) {
+ return new DottedVersion(version, false);
+ }
+
+ @Override
+ public int compareTo(String o) {
+ int result = 0;
+ int[] otherComponents = parseVersionString(o, greedy);
+ for (int i = 0; i < Math.min(components.length, otherComponents.length)
+ && result == 0; ++i) {
+ result = components[i] - otherComponents[i];
+ }
+
+ if (result == 0) {
+ result = components.length - otherComponents.length;
+ }
+
+ return result;
+ }
+
+ private static int[] parseVersionString(String version, boolean greedy) {
+ Objects.requireNonNull(version);
+ if (version.isEmpty()) {
+ if (!greedy) {
+ return new int[] {0};
+ }
+ throw new IllegalArgumentException("Version may not be empty string");
+ }
+
+ int lastNotZeroIdx = -1;
+ List<Integer> components = new ArrayList<>();
+ for (var component : version.split("\\.", -1)) {
+ if (component.isEmpty()) {
+ if (!greedy) {
+ break;
+ }
+
+ throw new IllegalArgumentException(String.format(
+ "Version [%s] contains a zero lenght component", version));
+ }
+
+ if (!DIGITS.matcher(component).matches()) {
+ // Catch "+N" and "-N" cases.
+ if (!greedy) {
+ break;
+ }
+
+ throw new IllegalArgumentException(String.format(
+ "Version [%s] contains invalid component [%s]", version,
+ component));
+ }
+
+ final int num;
+ try {
+ num = Integer.parseInt(component);
+ } catch (NumberFormatException ex) {
+ if (!greedy) {
+ break;
+ }
+
+ throw ex;
+ }
+
+ if (num != 0) {
+ lastNotZeroIdx = components.size();
+ }
+ components.add(num);
+ }
+
+ if (lastNotZeroIdx + 1 != components.size()) {
+ // Strip trailing zeros.
+ components = components.subList(0, lastNotZeroIdx + 1);
+ }
+
+ if (components.isEmpty()) {
+ components.add(0);
+ }
+ return components.stream().mapToInt(Integer::intValue).toArray();
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+
+ final private int[] components;
+ final private String value;
+ final private boolean greedy;
+
+ private static final Pattern DIGITS = Pattern.compile("\\d+");
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/Executor.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.incubator.jpackage.internal;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+final public class Executor {
+
+ Executor() {
+ }
+
+ Executor setOutputConsumer(Consumer<Stream<String>> v) {
+ outputConsumer = v;
+ return this;
+ }
+
+ Executor saveOutput(boolean v) {
+ saveOutput = v;
+ return this;
+ }
+
+ Executor setProcessBuilder(ProcessBuilder v) {
+ pb = v;
+ return this;
+ }
+
+ Executor setCommandLine(String... cmdline) {
+ return setProcessBuilder(new ProcessBuilder(cmdline));
+ }
+
+ List<String> getOutput() {
+ return output;
+ }
+
+ Executor executeExpectSuccess() throws IOException {
+ int ret = execute();
+ if (0 != ret) {
+ throw new IOException(
+ String.format("Command %s exited with %d code",
+ createLogMessage(pb), ret));
+ }
+ return this;
+ }
+
+ int execute() throws IOException {
+ output = null;
+
+ boolean needProcessOutput = outputConsumer != null || Log.isVerbose() || saveOutput;
+ if (needProcessOutput) {
+ pb.redirectErrorStream(true);
+ } else {
+ // We are not going to read process output, so need to notify
+ // ProcessBuilder about this. Otherwise some processes might just
+ // hang up (`ldconfig -p`).
+ pb.redirectError(ProcessBuilder.Redirect.DISCARD);
+ pb.redirectOutput(ProcessBuilder.Redirect.DISCARD);
+ }
+
+ Log.verbose(String.format("Running %s", createLogMessage(pb)));
+ Process p = pb.start();
+
+ if (needProcessOutput) {
+ try (var br = new BufferedReader(new InputStreamReader(
+ p.getInputStream()))) {
+ final List<String> savedOutput;
+ // Need to save output if explicitely requested (saveOutput=true) or
+ // if will be used used by multiple consumers
+ if ((outputConsumer != null && Log.isVerbose()) || saveOutput) {
+ savedOutput = br.lines().collect(Collectors.toList());
+ if (saveOutput) {
+ output = savedOutput;
+ }
+ } else {
+ savedOutput = null;
+ }
+
+ Supplier<Stream<String>> outputStream = () -> {
+ if (savedOutput != null) {
+ return savedOutput.stream();
+ }
+ return br.lines();
+ };
+
+ if (Log.isVerbose()) {
+ outputStream.get().forEach(Log::verbose);
+ }
+
+ if (outputConsumer != null) {
+ outputConsumer.accept(outputStream.get());
+ }
+
+ if (savedOutput == null) {
+ // For some processes on Linux if the output stream
+ // of the process is opened but not consumed, the process
+ // would exit with code 141.
+ // It turned out that reading just a single line of process
+ // output fixes the problem, but let's process
+ // all of the output, just in case.
+ br.lines().forEach(x -> {});
+ }
+ }
+ }
+
+ try {
+ return p.waitFor();
+ } catch (InterruptedException ex) {
+ Log.verbose(ex);
+ throw new RuntimeException(ex);
+ }
+ }
+
+ static Executor of(String... cmdline) {
+ return new Executor().setCommandLine(cmdline);
+ }
+
+ static Executor of(ProcessBuilder pb) {
+ return new Executor().setProcessBuilder(pb);
+ }
+
+ private static String createLogMessage(ProcessBuilder pb) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(String.format("%s", pb.command()));
+ if (pb.directory() != null) {
+ sb.append(String.format("in %s", pb.directory().getAbsolutePath()));
+ }
+ return sb.toString();
+ }
+
+ private ProcessBuilder pb;
+ private boolean saveOutput;
+ private List<String> output;
+ private Consumer<Stream<String>> outputConsumer;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/FileAssociation.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.stream.Collectors;
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+
+final class FileAssociation {
+ void verify() {
+ if (extensions.isEmpty()) {
+ Log.error(I18N.getString(
+ "message.creating-association-with-null-extension"));
+ }
+ }
+
+ static List<FileAssociation> fetchFrom(Map<String, ? super Object> params) {
+ String launcherName = APP_NAME.fetchFrom(params);
+
+ return FILE_ASSOCIATIONS.fetchFrom(params).stream().filter(
+ Objects::nonNull).map(fa -> {
+ FileAssociation assoc = new FileAssociation();
+
+ assoc.launcherPath = Path.of(launcherName);
+ assoc.description = FA_DESCRIPTION.fetchFrom(fa);
+ assoc.extensions = Optional.ofNullable(
+ FA_EXTENSIONS.fetchFrom(fa)).orElse(Collections.emptyList());
+ assoc.mimeTypes = Optional.ofNullable(
+ FA_CONTENT_TYPE.fetchFrom(fa)).orElse(Collections.emptyList());
+
+ File icon = FA_ICON.fetchFrom(fa);
+ if (icon != null) {
+ assoc.iconPath = icon.toPath();
+ }
+
+ return assoc;
+ }).collect(Collectors.toList());
+ }
+
+ Path launcherPath;
+ Path iconPath;
+ List<String> mimeTypes;
+ List<String> extensions;
+ String description;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/I18N.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.incubator.jpackage.internal;
+
+import java.util.ResourceBundle;
+
+class I18N {
+
+ static String getString(String key) {
+ if (PLATFORM.containsKey(key)) {
+ return PLATFORM.getString(key);
+ }
+ return SHARED.getString(key);
+ }
+
+ private static final ResourceBundle SHARED = ResourceBundle.getBundle(
+ "jdk.incubator.jpackage.internal.resources.MainResources");
+
+ private static final ResourceBundle PLATFORM;
+
+ static {
+ if (Platform.isLinux()) {
+ PLATFORM = ResourceBundle.getBundle(
+ "jdk.incubator.jpackage.internal.resources.LinuxResources");
+ } else if (Platform.isWindows()) {
+ PLATFORM = ResourceBundle.getBundle(
+ "jdk.incubator.jpackage.internal.resources.WinResources");
+ } else if (Platform.isMac()) {
+ PLATFORM = ResourceBundle.getBundle(
+ "jdk.incubator.jpackage.internal.resources.MacResources");
+ } else {
+ throw new IllegalStateException("Unknwon platform");
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/IOUtils.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,335 @@
+/*
+ * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.*;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.nio.channels.FileChannel;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.*;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+
+/**
+ * IOUtils
+ *
+ * A collection of static utility methods.
+ */
+public class IOUtils {
+
+ public static void deleteRecursive(File path) throws IOException {
+ if (!path.exists()) {
+ return;
+ }
+ Path directory = path.toPath();
+ Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(Path file,
+ BasicFileAttributes attr) throws IOException {
+ if (Platform.getPlatform() == Platform.WINDOWS) {
+ Files.setAttribute(file, "dos:readonly", false);
+ }
+ Files.delete(file);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult preVisitDirectory(Path dir,
+ BasicFileAttributes attr) throws IOException {
+ if (Platform.getPlatform() == Platform.WINDOWS) {
+ Files.setAttribute(dir, "dos:readonly", false);
+ }
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir, IOException e)
+ throws IOException {
+ Files.delete(dir);
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ }
+
+ public static void copyRecursive(Path src, Path dest) throws IOException {
+ copyRecursive(src, dest, List.of());
+ }
+
+ public static void copyRecursive(Path src, Path dest,
+ final List<String> excludes) throws IOException {
+ Files.walkFileTree(src, new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult preVisitDirectory(final Path dir,
+ final BasicFileAttributes attrs) throws IOException {
+ if (excludes.contains(dir.toFile().getName())) {
+ return FileVisitResult.SKIP_SUBTREE;
+ } else {
+ Files.createDirectories(dest.resolve(src.relativize(dir)));
+ return FileVisitResult.CONTINUE;
+ }
+ }
+
+ @Override
+ public FileVisitResult visitFile(final Path file,
+ final BasicFileAttributes attrs) throws IOException {
+ if (!excludes.contains(file.toFile().getName())) {
+ Files.copy(file, dest.resolve(src.relativize(file)));
+ }
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ }
+
+ public static void copyFile(File sourceFile, File destFile)
+ throws IOException {
+ destFile.getParentFile().mkdirs();
+
+ //recreate the file as existing copy may have weird permissions
+ destFile.delete();
+ destFile.createNewFile();
+
+ try (FileChannel source = new FileInputStream(sourceFile).getChannel();
+ FileChannel destination =
+ new FileOutputStream(destFile).getChannel()) {
+
+ if (destination != null && source != null) {
+ destination.transferFrom(source, 0, source.size());
+ }
+ }
+
+ //preserve executable bit!
+ if (sourceFile.canExecute()) {
+ destFile.setExecutable(true, false);
+ }
+ if (!sourceFile.canWrite()) {
+ destFile.setReadOnly();
+ }
+ destFile.setReadable(true, false);
+ }
+
+ // run "launcher paramfile" in the directory where paramfile is kept
+ public static void run(String launcher, File paramFile)
+ throws IOException {
+ if (paramFile != null && paramFile.exists()) {
+ ProcessBuilder pb =
+ new ProcessBuilder(launcher, paramFile.getName());
+ pb = pb.directory(paramFile.getParentFile());
+ exec(pb);
+ }
+ }
+
+ public static void exec(ProcessBuilder pb)
+ throws IOException {
+ exec(pb, false, null);
+ }
+
+ static void exec(ProcessBuilder pb, boolean testForPresenseOnly,
+ PrintStream consumer) throws IOException {
+ List<String> output = new ArrayList<>();
+ Executor exec = Executor.of(pb).setOutputConsumer(lines -> {
+ lines.forEach(output::add);
+ if (consumer != null) {
+ output.forEach(consumer::println);
+ }
+ });
+
+ if (testForPresenseOnly) {
+ exec.execute();
+ } else {
+ exec.executeExpectSuccess();
+ }
+ }
+
+ public static int getProcessOutput(List<String> result, String... args)
+ throws IOException, InterruptedException {
+
+ ProcessBuilder pb = new ProcessBuilder(args);
+
+ final Process p = pb.start();
+
+ List<String> list = new ArrayList<>();
+
+ final BufferedReader in =
+ new BufferedReader(new InputStreamReader(p.getInputStream()));
+ final BufferedReader err =
+ new BufferedReader(new InputStreamReader(p.getErrorStream()));
+
+ Thread t = new Thread(() -> {
+ try {
+ String line;
+ while ((line = in.readLine()) != null) {
+ list.add(line);
+ }
+ } catch (IOException ioe) {
+ Log.verbose(ioe);
+ }
+
+ try {
+ String line;
+ while ((line = err.readLine()) != null) {
+ Log.error(line);
+ }
+ } catch (IOException ioe) {
+ Log.verbose(ioe);
+ }
+ });
+ t.setDaemon(true);
+ t.start();
+
+ int ret = p.waitFor();
+
+ result.clear();
+ result.addAll(list);
+
+ return ret;
+ }
+
+ static void writableOutputDir(Path outdir) throws PackagerException {
+ File file = outdir.toFile();
+
+ if (!file.isDirectory() && !file.mkdirs()) {
+ throw new PackagerException("error.cannot-create-output-dir",
+ file.getAbsolutePath());
+ }
+ if (!file.canWrite()) {
+ throw new PackagerException("error.cannot-write-to-output-dir",
+ file.getAbsolutePath());
+ }
+ }
+
+ public static Path replaceSuffix(Path path, String suffix) {
+ Path parent = path.getParent();
+ String filename = path.getFileName().toString().replaceAll("\\.[^.]*$", "")
+ + Optional.ofNullable(suffix).orElse("");
+ return parent != null ? parent.resolve(filename) : Path.of(filename);
+ }
+
+ public static Path addSuffix(Path path, String suffix) {
+ Path parent = path.getParent();
+ String filename = path.getFileName().toString() + suffix;
+ return parent != null ? parent.resolve(filename) : Path.of(filename);
+ }
+
+ public static String getSuffix(Path path) {
+ String filename = replaceSuffix(path.getFileName(), null).toString();
+ return path.getFileName().toString().substring(filename.length());
+ }
+
+ @FunctionalInterface
+ public static interface XmlConsumer {
+ void accept(XMLStreamWriter xml) throws IOException, XMLStreamException;
+ }
+
+ public static void createXml(Path dstFile, XmlConsumer xmlConsumer) throws
+ IOException {
+ XMLOutputFactory xmlFactory = XMLOutputFactory.newInstance();
+ try (Writer w = Files.newBufferedWriter(dstFile)) {
+ // Wrap with pretty print proxy
+ XMLStreamWriter xml = (XMLStreamWriter) Proxy.newProxyInstance(
+ XMLStreamWriter.class.getClassLoader(), new Class<?>[]{
+ XMLStreamWriter.class}, new PrettyPrintHandler(
+ xmlFactory.createXMLStreamWriter(w)));
+
+ xml.writeStartDocument();
+ xmlConsumer.accept(xml);
+ xml.writeEndDocument();
+ xml.flush();
+ xml.close();
+ } catch (XMLStreamException ex) {
+ throw new IOException(ex);
+ } catch (IOException ex) {
+ throw ex;
+ }
+ }
+
+ private static class PrettyPrintHandler implements InvocationHandler {
+
+ PrettyPrintHandler(XMLStreamWriter target) {
+ this.target = target;
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws
+ Throwable {
+ switch (method.getName()) {
+ case "writeStartElement":
+ // update state of parent node
+ if (depth > 0) {
+ hasChildElement.put(depth - 1, true);
+ }
+ // reset state of current node
+ hasChildElement.put(depth, false);
+ // indent for current depth
+ target.writeCharacters(EOL);
+ target.writeCharacters(repeat(depth, INDENT));
+ depth++;
+ break;
+ case "writeEndElement":
+ depth--;
+ if (hasChildElement.get(depth) == true) {
+ target.writeCharacters(EOL);
+ target.writeCharacters(repeat(depth, INDENT));
+ }
+ break;
+ case "writeProcessingInstruction":
+ case "writeEmptyElement":
+ // update state of parent node
+ if (depth > 0) {
+ hasChildElement.put(depth - 1, true);
+ }
+ // indent for current depth
+ target.writeCharacters(EOL);
+ target.writeCharacters(repeat(depth, INDENT));
+ break;
+ default:
+ break;
+ }
+ method.invoke(target, args);
+ return null;
+ }
+
+ private static String repeat(int d, String s) {
+ StringBuilder sb = new StringBuilder();
+ while (d-- > 0) {
+ sb.append(s);
+ }
+ return sb.toString();
+ }
+
+ private final XMLStreamWriter target;
+ private int depth = 0;
+ private final Map<Integer, Boolean> hasChildElement = new HashMap<>();
+ private static final String INDENT = " ";
+ private static final String EOL = "\n";
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/JLinkBundlerHelper.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,398 @@
+/*
+ * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.ResourceBundle;
+import java.util.Set;
+import java.util.Optional;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.regex.Matcher;
+import java.util.spi.ToolProvider;
+import java.util.jar.JarFile;
+import java.lang.module.Configuration;
+import java.lang.module.ResolvedModule;
+import java.lang.module.ModuleDescriptor;
+import java.lang.module.ModuleFinder;
+import java.lang.module.ModuleReference;
+import jdk.internal.module.ModulePath;
+
+
+final class JLinkBundlerHelper {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.incubator.jpackage.internal.resources.MainResources");
+
+ static final ToolProvider JLINK_TOOL =
+ ToolProvider.findFirst("jlink").orElseThrow();
+
+ static File getMainJar(Map<String, ? super Object> params) {
+ File result = null;
+ RelativeFileSet fileset =
+ StandardBundlerParam.MAIN_JAR.fetchFrom(params);
+
+ if (fileset != null) {
+ String filename = fileset.getIncludedFiles().iterator().next();
+ result = fileset.getBaseDirectory().toPath().
+ resolve(filename).toFile();
+
+ if (result == null || !result.exists()) {
+ String srcdir =
+ StandardBundlerParam.SOURCE_DIR.fetchFrom(params);
+
+ if (srcdir != null) {
+ result = new File(srcdir + File.separator + filename);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ static String getMainClassFromModule(Map<String, ? super Object> params) {
+ String mainModule = StandardBundlerParam.MODULE.fetchFrom(params);
+ if (mainModule != null) {
+
+ int index = mainModule.indexOf("/");
+ if (index > 0) {
+ return mainModule.substring(index + 1);
+ } else {
+ ModuleDescriptor descriptor =
+ JLinkBundlerHelper.getMainModuleDescription(params);
+ if (descriptor != null) {
+ Optional<String> mainClass = descriptor.mainClass();
+ if (mainClass.isPresent()) {
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.module-class"),
+ mainClass.get(),
+ JLinkBundlerHelper.getMainModule(params)));
+ return mainClass.get();
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ static String getMainModule(Map<String, ? super Object> params) {
+ String result = null;
+ String mainModule = StandardBundlerParam.MODULE.fetchFrom(params);
+
+ if (mainModule != null) {
+ int index = mainModule.indexOf("/");
+
+ if (index > 0) {
+ result = mainModule.substring(0, index);
+ } else {
+ result = mainModule;
+ }
+ }
+
+ return result;
+ }
+
+ static void execute(Map<String, ? super Object> params,
+ AbstractAppImageBuilder imageBuilder)
+ throws IOException, Exception {
+
+ List<Path> modulePath =
+ StandardBundlerParam.MODULE_PATH.fetchFrom(params);
+ Set<String> addModules =
+ StandardBundlerParam.ADD_MODULES.fetchFrom(params);
+ Set<String> limitModules =
+ StandardBundlerParam.LIMIT_MODULES.fetchFrom(params);
+ Path outputDir = imageBuilder.getRuntimeRoot();
+ File mainJar = getMainJar(params);
+ ModFile.ModType mainJarType = ModFile.ModType.Unknown;
+
+ if (mainJar != null) {
+ mainJarType = new ModFile(mainJar).getModType();
+ } else if (StandardBundlerParam.MODULE.fetchFrom(params) == null) {
+ // user specified only main class, all jars will be on the classpath
+ mainJarType = ModFile.ModType.UnnamedJar;
+ }
+
+ boolean bindServices =
+ StandardBundlerParam.BIND_SERVICES.fetchFrom(params);
+
+ // Modules
+ String mainModule = getMainModule(params);
+ if (mainModule == null) {
+ if (mainJarType == ModFile.ModType.UnnamedJar) {
+ if (addModules.isEmpty()) {
+ // The default for an unnamed jar is ALL_DEFAULT
+ addModules.add(ModuleHelper.ALL_DEFAULT);
+ }
+ } else if (mainJarType == ModFile.ModType.Unknown ||
+ mainJarType == ModFile.ModType.ModularJar) {
+ addModules.add(ModuleHelper.ALL_DEFAULT);
+ }
+ }
+
+ Set<String> modules = new ModuleHelper(
+ modulePath, addModules, limitModules).modules();
+
+ if (mainModule != null) {
+ modules.add(mainModule);
+ }
+
+ runJLink(outputDir, modulePath, modules, limitModules,
+ new HashMap<String,String>(), bindServices);
+
+ imageBuilder.prepareApplicationFiles(params);
+ }
+
+
+ // Returns the path to the JDK modules in the user defined module path.
+ static Path findPathOfModule( List<Path> modulePath, String moduleName) {
+
+ for (Path path : modulePath) {
+ Path moduleNamePath = path.resolve(moduleName);
+
+ if (Files.exists(moduleNamePath)) {
+ return path;
+ }
+ }
+
+ return null;
+ }
+
+ static ModuleDescriptor getMainModuleDescription(Map<String, ? super Object> params) {
+ boolean hasModule = params.containsKey(StandardBundlerParam.MODULE.getID());
+ if (hasModule) {
+ List<Path> modulePath = StandardBundlerParam.MODULE_PATH.fetchFrom(params);
+ if (!modulePath.isEmpty()) {
+ ModuleFinder finder = ModuleFinder.of(modulePath.toArray(new Path[0]));
+ String mainModule = JLinkBundlerHelper.getMainModule(params);
+ Optional<ModuleReference> omref = finder.find(mainModule);
+ if (omref.isPresent()) {
+ return omref.get().descriptor();
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /*
+ * Returns the set of modules that would be visible by default for
+ * a non-modular-aware application consisting of the given elements.
+ */
+ private static Set<String> getDefaultModules(
+ Collection<Path> paths, Collection<String> addModules) {
+
+ // the modules in the run-time image that export an API
+ Stream<String> systemRoots = ModuleFinder.ofSystem().findAll().stream()
+ .map(ModuleReference::descriptor)
+ .filter(JLinkBundlerHelper::exportsAPI)
+ .map(ModuleDescriptor::name);
+
+ Set<String> roots = Stream.concat(systemRoots,
+ addModules.stream()).collect(Collectors.toSet());
+
+ ModuleFinder finder = createModuleFinder(paths);
+
+ return Configuration.empty()
+ .resolveAndBind(finder, ModuleFinder.of(), roots)
+ .modules()
+ .stream()
+ .map(ResolvedModule::name)
+ .collect(Collectors.toSet());
+ }
+
+ /*
+ * Returns true if the given module exports an API to all module.
+ */
+ private static boolean exportsAPI(ModuleDescriptor descriptor) {
+ return descriptor.exports()
+ .stream()
+ .anyMatch(e -> !e.isQualified());
+ }
+
+ private static ModuleFinder createModuleFinder(Collection<Path> modulePath) {
+ return ModuleFinder.compose(
+ ModulePath.of(JarFile.runtimeVersion(), true,
+ modulePath.toArray(Path[]::new)),
+ ModuleFinder.ofSystem());
+ }
+
+ private static class ModuleHelper {
+ // The token for "all modules on the module path".
+ private static final String ALL_MODULE_PATH = "ALL-MODULE-PATH";
+
+ // The token for "all valid runtime modules".
+ static final String ALL_DEFAULT = "ALL-DEFAULT";
+
+ private final Set<String> modules = new HashSet<>();
+ ModuleHelper(List<Path> paths, Set<String> addModules,
+ Set<String> limitModules) {
+ boolean addAllModulePath = false;
+ boolean addDefaultMods = false;
+
+ for (Iterator<String> iterator = addModules.iterator();
+ iterator.hasNext();) {
+ String module = iterator.next();
+
+ switch (module) {
+ case ALL_MODULE_PATH:
+ iterator.remove();
+ addAllModulePath = true;
+ break;
+ case ALL_DEFAULT:
+ iterator.remove();
+ addDefaultMods = true;
+ break;
+ default:
+ this.modules.add(module);
+ }
+ }
+
+ if (addAllModulePath) {
+ this.modules.addAll(getModuleNamesFromPath(paths));
+ } else if (addDefaultMods) {
+ this.modules.addAll(getDefaultModules(
+ paths, addModules));
+ }
+ }
+
+ Set<String> modules() {
+ return modules;
+ }
+
+ private static Set<String> getModuleNamesFromPath(List<Path> paths) {
+
+ return createModuleFinder(paths)
+ .findAll()
+ .stream()
+ .map(ModuleReference::descriptor)
+ .map(ModuleDescriptor::name)
+ .collect(Collectors.toSet());
+ }
+ }
+
+ private static void runJLink(Path output, List<Path> modulePath,
+ Set<String> modules, Set<String> limitModules,
+ HashMap<String, String> user, boolean bindServices)
+ throws PackagerException {
+
+ // This is just to ensure jlink is given a non-existant directory
+ // The passed in output path should be non-existant or empty directory
+ try {
+ IOUtils.deleteRecursive(output.toFile());
+ } catch (IOException ioe) {
+ throw new PackagerException(ioe);
+ }
+
+ ArrayList<String> args = new ArrayList<String>();
+ args.add("--output");
+ args.add(output.toString());
+ if (modulePath != null && !modulePath.isEmpty()) {
+ args.add("--module-path");
+ args.add(getPathList(modulePath));
+ }
+ if (modules != null && !modules.isEmpty()) {
+ args.add("--add-modules");
+ args.add(getStringList(modules));
+ }
+ if (limitModules != null && !limitModules.isEmpty()) {
+ args.add("--limit-modules");
+ args.add(getStringList(limitModules));
+ }
+ if (user != null && !user.isEmpty()) {
+ for (Map.Entry<String, String> entry : user.entrySet()) {
+ args.add(entry.getKey());
+ args.add(entry.getValue());
+ }
+ } else {
+ args.add("--strip-native-commands");
+ args.add("--strip-debug");
+ args.add("--no-man-pages");
+ args.add("--no-header-files");
+ if (bindServices) {
+ args.add("--bind-services");
+ }
+ }
+
+ StringWriter writer = new StringWriter();
+ PrintWriter pw = new PrintWriter(writer);
+
+ Log.verbose("jlink arguments: " + args);
+ int retVal = JLINK_TOOL.run(pw, pw, args.toArray(new String[0]));
+ String jlinkOut = writer.toString();
+
+ if (retVal != 0) {
+ throw new PackagerException("error.jlink.failed" , jlinkOut);
+ } else if (jlinkOut.length() > 0) {
+ Log.verbose("jlink output: " + jlinkOut);
+ }
+ }
+
+ private static String getPathList(List<Path> pathList) {
+ String ret = null;
+ for (Path p : pathList) {
+ String s = Matcher.quoteReplacement(p.toString());
+ if (ret == null) {
+ ret = s;
+ } else {
+ ret += File.pathSeparator + s;
+ }
+ }
+ return ret;
+ }
+
+ private static String getStringList(Set<String> strings) {
+ String ret = null;
+ for (String s : strings) {
+ if (ret == null) {
+ ret = s;
+ } else {
+ ret += "," + s;
+ }
+ }
+ return (ret == null) ? null : Matcher.quoteReplacement(ret);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/JPackageToolProvider.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.PrintWriter;
+import java.util.spi.ToolProvider;
+
+/**
+ * JPackageToolProvider
+ *
+ * This is the ToolProvider implementation exported
+ * to java.util.spi.ToolProvider and ultimately javax.tools.ToolProvider
+ */
+public class JPackageToolProvider implements ToolProvider {
+
+ public String name() {
+ return "jpackage";
+ }
+
+ public synchronized int run(
+ PrintWriter out, PrintWriter err, String... args) {
+ try {
+ return new jdk.incubator.jpackage.main.Main().execute(out, err, args);
+ } catch (RuntimeException re) {
+ Log.error(re.getMessage());
+ Log.verbose(re);
+ return 1;
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/Log.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+
+/**
+ * Log
+ *
+ * General purpose logging mechanism.
+ */
+public class Log {
+ public static class Logger {
+ private boolean verbose = false;
+ private PrintWriter out = null;
+ private PrintWriter err = null;
+
+ // verbose defaults to true unless environment variable JPACKAGE_DEBUG
+ // is set to true.
+ // Then it is only set to true by using --verbose jpackage option
+
+ public Logger() {
+ verbose = ("true".equals(System.getenv("JPACKAGE_DEBUG")));
+ }
+
+ public void setVerbose() {
+ verbose = true;
+ }
+
+ public boolean isVerbose() {
+ return verbose;
+ }
+
+ public void setPrintWriter(PrintWriter out, PrintWriter err) {
+ this.out = out;
+ this.err = err;
+ }
+
+ public void flush() {
+ if (out != null) {
+ out.flush();
+ }
+
+ if (err != null) {
+ err.flush();
+ }
+ }
+
+ public void info(String msg) {
+ if (out != null) {
+ out.println(msg);
+ } else {
+ System.out.println(msg);
+ }
+ }
+
+ public void error(String msg) {
+ if (err != null) {
+ err.println(msg);
+ } else {
+ System.err.println(msg);
+ }
+ }
+
+ public void verbose(Throwable t) {
+ if (out != null && verbose) {
+ t.printStackTrace(out);
+ } else if (verbose) {
+ t.printStackTrace(System.out);
+ }
+ }
+
+ public void verbose(String msg) {
+ if (out != null && verbose) {
+ out.println(msg);
+ } else if (verbose) {
+ System.out.println(msg);
+ }
+ }
+ }
+
+ private static Logger delegate = null;
+
+ public static void setLogger(Logger logger) {
+ delegate = (logger != null) ? logger : new Logger();
+ }
+
+ public static void flush() {
+ if (delegate != null) {
+ delegate.flush();
+ }
+ }
+
+ public static void info(String msg) {
+ if (delegate != null) {
+ delegate.info(msg);
+ }
+ }
+
+ public static void error(String msg) {
+ if (delegate != null) {
+ delegate.error(msg);
+ }
+ }
+
+ public static void setVerbose() {
+ if (delegate != null) {
+ delegate.setVerbose();
+ }
+ }
+
+ public static boolean isVerbose() {
+ return (delegate != null) ? delegate.isVerbose() : false;
+ }
+
+ public static void verbose(String msg) {
+ if (delegate != null) {
+ delegate.verbose(msg);
+ }
+ }
+
+ public static void verbose(Throwable t) {
+ if (delegate != null) {
+ delegate.verbose(t);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/ModFile.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+final class ModFile {
+ private final String filename;
+ private final ModType moduleType;
+
+ enum JarType {All, UnnamedJar, ModularJar}
+ enum ModType {
+ Unknown, UnnamedJar, ModularJar, Jmod, ExplodedModule}
+
+ ModFile(File aFile) {
+ super();
+ filename = aFile.getPath();
+ moduleType = getModType(aFile);
+ }
+
+ String getModName() {
+ File file = new File(getFileName());
+ // do not try to remove extension for directories
+ return moduleType == ModType.ExplodedModule ?
+ file.getName() : getFileWithoutExtension(file.getName());
+ }
+
+ String getFileName() {
+ return filename;
+ }
+
+ ModType getModType() {
+ return moduleType;
+ }
+
+ private static ModType getModType(File aFile) {
+ ModType result = ModType.Unknown;
+ String filename = aFile.getAbsolutePath();
+
+ if (aFile.isFile()) {
+ if (filename.endsWith(".jmod")) {
+ result = ModType.Jmod;
+ }
+ else if (filename.endsWith(".jar")) {
+ JarType status = isModularJar(filename);
+
+ if (status == JarType.ModularJar) {
+ result = ModType.ModularJar;
+ }
+ else if (status == JarType.UnnamedJar) {
+ result = ModType.UnnamedJar;
+ }
+ }
+ }
+ else if (aFile.isDirectory()) {
+ File moduleInfo = new File(
+ filename + File.separator + "module-info.class");
+
+ if (moduleInfo.exists()) {
+ result = ModType.ExplodedModule;
+ }
+ }
+
+ return result;
+ }
+
+ private static JarType isModularJar(String FileName) {
+ JarType result = JarType.All;
+
+ try (ZipInputStream zip =
+ new ZipInputStream(new FileInputStream(FileName))) {
+ result = JarType.UnnamedJar;
+
+ for (ZipEntry entry = zip.getNextEntry(); entry != null;
+ entry = zip.getNextEntry()) {
+ if (entry.getName().matches("module-info.class")) {
+ result = JarType.ModularJar;
+ break;
+ }
+ }
+ } catch (IOException ex) {
+ }
+
+ return result;
+ }
+
+ private static String getFileWithoutExtension(String FileName) {
+ return FileName.replaceFirst("[.][^.]+$", "");
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/OverridableResource.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.incubator.jpackage.internal;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.RESOURCE_DIR;
+import jdk.incubator.jpackage.internal.resources.ResourceLocator;
+
+/**
+ * Resource file that may have the default value supplied by jpackage. It can be
+ * overridden by a file from resource directory set with {@code --resource-dir}
+ * jpackage parameter.
+ *
+ * Resource has default name and public name. Default name is the name of a file
+ * in {@code jdk.incubator.jpackage.internal.resources} package that provides the default
+ * value of the resource.
+ *
+ * Public name is a path relative to resource directory to a file with custom
+ * value of the resource.
+ *
+ * Use #setPublicName to set the public name.
+ *
+ * If #setPublicName was not called, name of file passed in #saveToFile function
+ * will be used as a public name.
+ *
+ * Use #setExternal to set arbitrary file as a source of resource. If non-null
+ * value was passed in #setExternal call that value will be used as a path to file
+ * to copy in the destination file passed in #saveToFile function call.
+ */
+final class OverridableResource {
+
+ OverridableResource(String defaultName) {
+ this.defaultName = defaultName;
+ }
+
+ OverridableResource setSubstitutionData(Map<String, String> v) {
+ if (v != null) {
+ // Disconnect `v`
+ substitutionData = new HashMap<>(v);
+ } else {
+ substitutionData = null;
+ }
+ return this;
+ }
+
+ OverridableResource setCategory(String v) {
+ category = v;
+ return this;
+ }
+
+ OverridableResource setResourceDir(Path v) {
+ resourceDir = v;
+ return this;
+ }
+
+ OverridableResource setResourceDir(File v) {
+ return setResourceDir(toPath(v));
+ }
+
+ /**
+ * Set name of file to look for in resource dir.
+ *
+ * @return this
+ */
+ OverridableResource setPublicName(Path v) {
+ publicName = v;
+ return this;
+ }
+
+ OverridableResource setPublicName(String v) {
+ return setPublicName(Path.of(v));
+ }
+
+ OverridableResource setExternal(Path v) {
+ externalPath = v;
+ return this;
+ }
+
+ OverridableResource setExternal(File v) {
+ return setExternal(toPath(v));
+ }
+
+ void saveToFile(Path dest) throws IOException {
+ final String printableCategory;
+ if (category != null) {
+ printableCategory = String.format("[%s]", category);
+ } else {
+ printableCategory = "";
+ }
+
+ if (externalPath != null && externalPath.toFile().exists()) {
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.using-custom-resource-from-file"),
+ printableCategory,
+ externalPath.toAbsolutePath().normalize()));
+
+ try (InputStream in = Files.newInputStream(externalPath)) {
+ processResourceStream(in, dest);
+ }
+ return;
+ }
+
+ final Path resourceName = Optional.ofNullable(publicName).orElse(
+ dest.getFileName());
+
+ if (resourceDir != null) {
+ final Path customResource = resourceDir.resolve(resourceName);
+ if (customResource.toFile().exists()) {
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.using-custom-resource"), printableCategory,
+ resourceDir.normalize().toAbsolutePath().relativize(
+ customResource.normalize().toAbsolutePath())));
+
+ try (InputStream in = Files.newInputStream(customResource)) {
+ processResourceStream(in, dest);
+ }
+ return;
+ }
+ }
+
+ if (defaultName != null) {
+ Log.verbose(MessageFormat.format(
+ I18N.getString("message.using-default-resource"),
+ defaultName, printableCategory, resourceName));
+
+ try (InputStream in = readDefault(defaultName)) {
+ processResourceStream(in, dest);
+ }
+ }
+ }
+
+ void saveToFile(File dest) throws IOException {
+ saveToFile(dest.toPath());
+ }
+
+ static InputStream readDefault(String resourceName) {
+ return ResourceLocator.class.getResourceAsStream(resourceName);
+ }
+
+ static OverridableResource createResource(String defaultName,
+ Map<String, ? super Object> params) {
+ return new OverridableResource(defaultName).setResourceDir(
+ RESOURCE_DIR.fetchFrom(params));
+ }
+
+ private static List<String> substitute(Stream<String> lines,
+ Map<String, String> substitutionData) {
+ return lines.map(line -> {
+ String result = line;
+ for (var entry : substitutionData.entrySet()) {
+ result = result.replace(entry.getKey(), Optional.ofNullable(
+ entry.getValue()).orElse(""));
+ }
+ return result;
+ }).collect(Collectors.toList());
+ }
+
+ private static Path toPath(File v) {
+ if (v != null) {
+ return v.toPath();
+ }
+ return null;
+ }
+
+ private void processResourceStream(InputStream rawResource, Path dest)
+ throws IOException {
+ if (substitutionData == null) {
+ Files.createDirectories(dest.getParent());
+ Files.copy(rawResource, dest, StandardCopyOption.REPLACE_EXISTING);
+ } else {
+ // Utf8 in and out
+ try (BufferedReader reader = new BufferedReader(
+ new InputStreamReader(rawResource, StandardCharsets.UTF_8))) {
+ Files.createDirectories(dest.getParent());
+ Files.write(dest, substitute(reader.lines(), substitutionData));
+ }
+ }
+ }
+
+ private Map<String, String> substitutionData;
+ private String category;
+ private Path resourceDir;
+ private Path publicName;
+ private Path externalPath;
+ private final String defaultName;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/PackagerException.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.text.MessageFormat;
+import java.util.ResourceBundle;
+
+public class PackagerException extends Exception {
+ private static final long serialVersionUID = 1L;
+ private static final ResourceBundle bundle = ResourceBundle.getBundle(
+ "jdk.incubator.jpackage.internal.resources.MainResources");
+
+ public PackagerException(Throwable cause) {
+ super(cause);
+ }
+
+ public PackagerException(String key, Throwable cause) {
+ super(bundle.getString(key), cause);
+ }
+
+ public PackagerException(String key) {
+ super(bundle.getString(key));
+ }
+
+ public PackagerException(String key, String ... arguments) {
+ super(MessageFormat.format(
+ bundle.getString(key), (Object[]) arguments));
+ }
+
+ public PackagerException(
+ Throwable cause, String key, String ... arguments) {
+ super(MessageFormat.format(bundle.getString(key),
+ (Object[]) arguments), cause);
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/PathGroup.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,261 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.incubator.jpackage.internal;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+
+/**
+ * Group of paths.
+ * Each path in the group is assigned a unique id.
+ */
+final class PathGroup {
+ PathGroup(Map<Object, Path> paths) {
+ entries = new HashMap<>(paths);
+ }
+
+ Path getPath(Object id) {
+ if (id == null) {
+ throw new NullPointerException();
+ }
+ return entries.get(id);
+ }
+
+ void setPath(Object id, Path path) {
+ if (path != null) {
+ entries.put(id, path);
+ } else {
+ entries.remove(id);
+ }
+ }
+
+ /**
+ * All configured entries.
+ */
+ List<Path> paths() {
+ return entries.values().stream().collect(Collectors.toList());
+ }
+
+ /**
+ * Root entries.
+ */
+ List<Path> roots() {
+ // Sort by the number of path components in ascending order.
+ List<Map.Entry<Path, Path>> sorted = normalizedPaths().stream().sorted(
+ (a, b) -> a.getKey().getNameCount() - b.getKey().getNameCount()).collect(
+ Collectors.toList());
+
+ // Returns `true` if `a` is a parent of `b`
+ BiFunction<Map.Entry<Path, Path>, Map.Entry<Path, Path>, Boolean> isParentOrSelf = (a, b) -> {
+ return a == b || b.getKey().startsWith(a.getKey());
+ };
+
+ return sorted.stream().filter(
+ v -> v == sorted.stream().sequential().filter(
+ v2 -> isParentOrSelf.apply(v2, v)).findFirst().get()).map(
+ v -> v.getValue()).collect(Collectors.toList());
+ }
+
+ long sizeInBytes() throws IOException {
+ long reply = 0;
+ for (Path dir : roots().stream().filter(f -> Files.isDirectory(f)).collect(
+ Collectors.toList())) {
+ try (Stream<Path> stream = Files.walk(dir)) {
+ reply += stream.filter(p -> Files.isRegularFile(p)).mapToLong(
+ f -> f.toFile().length()).sum();
+ }
+ }
+ return reply;
+ }
+
+ PathGroup resolveAt(Path root) {
+ return new PathGroup(entries.entrySet().stream().collect(
+ Collectors.toMap(e -> e.getKey(),
+ e -> root.resolve(e.getValue()))));
+ }
+
+ void copy(PathGroup dst) throws IOException {
+ copy(this, dst, null, false);
+ }
+
+ void move(PathGroup dst) throws IOException {
+ copy(this, dst, null, true);
+ }
+
+ void transform(PathGroup dst, TransformHandler handler) throws IOException {
+ copy(this, dst, handler, false);
+ }
+
+ static interface Facade<T> {
+ PathGroup pathGroup();
+
+ default Collection<Path> paths() {
+ return pathGroup().paths();
+ }
+
+ default List<Path> roots() {
+ return pathGroup().roots();
+ }
+
+ default long sizeInBytes() throws IOException {
+ return pathGroup().sizeInBytes();
+ }
+
+ T resolveAt(Path root);
+
+ default void copy(Facade<T> dst) throws IOException {
+ pathGroup().copy(dst.pathGroup());
+ }
+
+ default void move(Facade<T> dst) throws IOException {
+ pathGroup().move(dst.pathGroup());
+ }
+
+ default void transform(Facade<T> dst, TransformHandler handler) throws
+ IOException {
+ pathGroup().transform(dst.pathGroup(), handler);
+ }
+ }
+
+ static interface TransformHandler {
+ public void copyFile(Path src, Path dst) throws IOException;
+ public void createDirectory(Path dir) throws IOException;
+ }
+
+ private static void copy(PathGroup src, PathGroup dst,
+ TransformHandler handler, boolean move) throws IOException {
+ List<Map.Entry<Path, Path>> copyItems = new ArrayList<>();
+ List<Path> excludeItems = new ArrayList<>();
+
+ for (var id: src.entries.keySet()) {
+ Path srcPath = src.entries.get(id);
+ if (dst.entries.containsKey(id)) {
+ copyItems.add(Map.entry(srcPath, dst.entries.get(id)));
+ } else {
+ excludeItems.add(srcPath);
+ }
+ }
+
+ copy(move, copyItems, excludeItems, handler);
+ }
+
+ private static void copy(boolean move, List<Map.Entry<Path, Path>> entries,
+ List<Path> excludePaths, TransformHandler handler) throws
+ IOException {
+
+ if (handler == null) {
+ handler = new TransformHandler() {
+ @Override
+ public void copyFile(Path src, Path dst) throws IOException {
+ Files.createDirectories(dst.getParent());
+ if (move) {
+ Files.move(src, dst);
+ } else {
+ Files.copy(src, dst);
+ }
+ }
+
+ @Override
+ public void createDirectory(Path dir) throws IOException {
+ Files.createDirectories(dir);
+ }
+ };
+ }
+
+ // destination -> source file mapping
+ Map<Path, Path> actions = new HashMap<>();
+ for (var action: entries) {
+ Path src = action.getKey();
+ Path dst = action.getValue();
+ if (src.toFile().isDirectory()) {
+ try (Stream<Path> stream = Files.walk(src)) {
+ stream.sequential().forEach(path -> actions.put(dst.resolve(
+ src.relativize(path)).normalize(), path));
+ }
+ } else {
+ actions.put(dst.normalize(), src);
+ }
+ }
+
+ for (var action : actions.entrySet()) {
+ Path dst = action.getKey();
+ Path src = action.getValue();
+
+ if (excludePaths.stream().anyMatch(src::startsWith)) {
+ continue;
+ }
+
+ if (src.equals(dst) || !src.toFile().exists()) {
+ continue;
+ }
+
+ if (src.toFile().isDirectory()) {
+ handler.createDirectory(dst);
+ } else {
+ handler.copyFile(src, dst);
+ }
+ }
+
+ if (move) {
+ // Delete source dirs.
+ for (var entry: entries) {
+ File srcFile = entry.getKey().toFile();
+ if (srcFile.isDirectory()) {
+ IOUtils.deleteRecursive(srcFile);
+ }
+ }
+ }
+ }
+
+ private static Map.Entry<Path, Path> normalizedPath(Path v) {
+ final Path normalized;
+ if (!v.isAbsolute()) {
+ normalized = Path.of("./").resolve(v.normalize());
+ } else {
+ normalized = v.normalize();
+ }
+
+ return Map.entry(normalized, v);
+ }
+
+ private List<Map.Entry<Path, Path>> normalizedPaths() {
+ return entries.values().stream().map(PathGroup::normalizedPath).collect(
+ Collectors.toList());
+ }
+
+ private final Map<Object, Path> entries;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/Platform.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.util.regex.Pattern;
+
+/**
+ * Platform
+ *
+ * Use <code>Platform</code> to detect the operating system
+ * that is currently running.
+ *
+ * Example:
+ *
+ * Platform platform = Platform.getPlatform();
+ *
+ * switch(platform) {
+ * case Platform.MAC: {
+ * // Do something
+ * break;
+ * }
+ * case Platform.WINDOWS:
+ * case Platform.LINUX: {
+ * // Do something else
+ * }
+ * }
+ *
+ */
+enum Platform {UNKNOWN, WINDOWS, LINUX, MAC;
+ private static final Platform platform;
+ private static final int majorVersion;
+ private static final int minorVersion;
+
+ static {
+ String os = System.getProperty("os.name").toLowerCase();
+
+ if (os.indexOf("win") >= 0) {
+ platform = Platform.WINDOWS;
+ }
+ else if (os.indexOf("nix") >= 0 || os.indexOf("nux") >= 0) {
+ platform = Platform.LINUX;
+ }
+ else if (os.indexOf("mac") >= 0) {
+ platform = Platform.MAC;
+ }
+ else {
+ platform = Platform.UNKNOWN;
+ }
+
+ String version = System.getProperty("os.version").toString();
+ String[] parts = version.split(Pattern.quote("."));
+
+ if (parts.length > 0) {
+ majorVersion = Integer.parseInt(parts[0]);
+
+ if (parts.length > 1) {
+ minorVersion = Integer.parseInt(parts[1]);
+ }
+ else {
+ minorVersion = -1;
+ }
+ }
+ else {
+ majorVersion = -1;
+ minorVersion = -1;
+ }
+ }
+
+ private Platform() {}
+
+ static Platform getPlatform() {
+ return platform;
+ }
+
+ static int getMajorVersion() {
+ return majorVersion;
+ }
+
+ static int getMinorVersion() {
+ return minorVersion;
+ }
+
+ static boolean isWindows() {
+ return getPlatform() == WINDOWS;
+ }
+
+ static boolean isMac() {
+ return getPlatform() == MAC;
+ }
+
+ static boolean isLinux() {
+ return getPlatform() == LINUX;
+ }
+
+ static RuntimeException throwUnknownPlatformError() {
+ throw new IllegalArgumentException("Unknown platform");
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/PlatformPackage.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.incubator.jpackage.internal;
+
+import java.nio.file.Path;
+
+/**
+ *
+ * Platform package of an application.
+ */
+interface PlatformPackage {
+ /**
+ * Platform-specific package name.
+ */
+ String name();
+
+ /**
+ * Root directory where sources for packaging tool should be stored
+ */
+ Path sourceRoot();
+
+ /**
+ * Source application layout from which to build the package.
+ */
+ ApplicationLayout sourceApplicationLayout();
+
+ /**
+ * Application layout of the installed package.
+ */
+ ApplicationLayout installedApplicationLayout();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/RelativeFileSet.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * RelativeFileSet
+ *
+ * A class encapsulating a directory and a set of files within it.
+ */
+class RelativeFileSet {
+
+ private File basedir;
+ private Set<String> files = new LinkedHashSet<>();
+
+ RelativeFileSet(File base, Collection<File> files) {
+ basedir = base;
+ String baseAbsolute = basedir.getAbsolutePath();
+ for (File f: files) {
+ String absolute = f.getAbsolutePath();
+ if (!absolute.startsWith(baseAbsolute)) {
+ throw new RuntimeException("File " + f.getAbsolutePath() +
+ " does not belong to " + baseAbsolute);
+ }
+ if (!absolute.equals(baseAbsolute)) {
+ // possible in jpackage case
+ this.files.add(absolute.substring(baseAbsolute.length()+1));
+ }
+ }
+ }
+
+ RelativeFileSet(File base, Set<File> files) {
+ this(base, (Collection<File>) files);
+ }
+
+ File getBaseDirectory() {
+ return basedir;
+ }
+
+ Set<String> getIncludedFiles() {
+ return files;
+ }
+
+ @Override
+ public String toString() {
+ if (files.size() == 1) {
+ return "" + basedir + File.pathSeparator + files;
+ }
+ return "RelativeFileSet {basedir:" + basedir
+ + ", files: {" + files + "}";
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/ScriptRunner.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.APP_NAME;
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.CONFIG_ROOT;
+
+/**
+ * Runs custom script from resource directory.
+ */
+class ScriptRunner {
+ ScriptRunner() {
+ environment = new ProcessBuilder().environment();
+ }
+
+ ScriptRunner setResourceCategoryId(String v) {
+ resourceCategoryId = v;
+ return this;
+ }
+
+ ScriptRunner setDirectory(Path v) {
+ directory = v;
+ return this;
+ }
+
+ ScriptRunner setScriptNameSuffix(String v) {
+ scriptNameSuffix = v;
+ return this;
+ }
+
+ ScriptRunner addEnvironment(Map<String, String> v) {
+ environment.putAll(v);
+ return this;
+ }
+
+ ScriptRunner setEnvironmentVariable(String envVarName, String envVarValue) {
+ Objects.requireNonNull(envVarName);
+ if (envVarValue == null) {
+ environment.remove(envVarName);
+ } else {
+ environment.put(envVarName, envVarValue);
+ }
+ return this;
+ }
+
+ public void run(Map<String, ? super Object> params) throws IOException {
+ String scriptName = String.format("%s-%s%s", APP_NAME.fetchFrom(params),
+ scriptNameSuffix, scriptSuffix());
+ Path scriptPath = CONFIG_ROOT.fetchFrom(params).toPath().resolve(
+ scriptName);
+ createResource(null, params)
+ .setCategory(I18N.getString(resourceCategoryId))
+ .saveToFile(scriptPath);
+ if (!Files.exists(scriptPath)) {
+ return;
+ }
+
+ ProcessBuilder pb = new ProcessBuilder(shell(),
+ scriptPath.toAbsolutePath().toString());
+ Map<String, String> workEnvironment = pb.environment();
+ workEnvironment.clear();
+ workEnvironment.putAll(environment);
+
+ if (directory != null) {
+ pb.directory(directory.toFile());
+ }
+
+ Executor.of(pb).executeExpectSuccess();
+ }
+
+ private static String shell() {
+ if (Platform.isWindows()) {
+ return "cscript";
+ }
+ return Optional.ofNullable(System.getenv("SHELL")).orElseGet(() -> "sh");
+ }
+
+ private static String scriptSuffix() {
+ if (Platform.isWindows()) {
+ return ".wsf";
+ }
+ return ".sh";
+ }
+
+ private String scriptNameSuffix;
+ private String resourceCategoryId;
+ private Path directory;
+ private Map<String, String> environment;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/StandardBundlerParam.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,790 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.module.ModuleDescriptor;
+import java.lang.module.ModuleDescriptor.Version;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.ResourceBundle;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * StandardBundlerParam
+ *
+ * A parameter to a bundler.
+ *
+ * Also contains static definitions of all of the common bundler parameters.
+ * (additional platform specific and mode specific bundler parameters
+ * are defined in each of the specific bundlers)
+ *
+ * Also contains static methods that operate on maps of parameters.
+ */
+class StandardBundlerParam<T> extends BundlerParamInfo<T> {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.incubator.jpackage.internal.resources.MainResources");
+ private static final String JAVABASEJMOD = "java.base.jmod";
+ private final static String DEFAULT_VERSION = "1.0";
+ private final static String DEFAULT_RELEASE = "1";
+
+ StandardBundlerParam(String id, Class<T> valueType,
+ Function<Map<String, ? super Object>, T> defaultValueFunction,
+ BiFunction<String, Map<String, ? super Object>, T> stringConverter)
+ {
+ this.id = id;
+ this.valueType = valueType;
+ this.defaultValueFunction = defaultValueFunction;
+ this.stringConverter = stringConverter;
+ }
+
+ static final StandardBundlerParam<RelativeFileSet> APP_RESOURCES =
+ new StandardBundlerParam<>(
+ BundleParams.PARAM_APP_RESOURCES,
+ RelativeFileSet.class,
+ null, // no default. Required parameter
+ null // no string translation,
+ // tool must provide complex type
+ );
+
+ @SuppressWarnings("unchecked")
+ static final
+ StandardBundlerParam<List<RelativeFileSet>> APP_RESOURCES_LIST =
+ new StandardBundlerParam<>(
+ BundleParams.PARAM_APP_RESOURCES + "List",
+ (Class<List<RelativeFileSet>>) (Object) List.class,
+ // Default is appResources, as a single item list
+ p -> new ArrayList<>(Collections.singletonList(
+ APP_RESOURCES.fetchFrom(p))),
+ StandardBundlerParam::createAppResourcesListFromString
+ );
+
+ static final StandardBundlerParam<String> SOURCE_DIR =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.INPUT.getId(),
+ String.class,
+ p -> null,
+ (s, p) -> {
+ String value = String.valueOf(s);
+ if (value.charAt(value.length() - 1) ==
+ File.separatorChar) {
+ return value.substring(0, value.length() - 1);
+ }
+ else {
+ return value;
+ }
+ }
+ );
+
+ // note that each bundler is likely to replace this one with
+ // their own converter
+ static final StandardBundlerParam<RelativeFileSet> MAIN_JAR =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.MAIN_JAR.getId(),
+ RelativeFileSet.class,
+ params -> {
+ extractMainClassInfoFromAppResources(params);
+ return (RelativeFileSet) params.get("mainJar");
+ },
+ (s, p) -> getMainJar(s, p)
+ );
+
+ static final StandardBundlerParam<String> CLASSPATH =
+ new StandardBundlerParam<>(
+ "classpath",
+ String.class,
+ params -> {
+ extractMainClassInfoFromAppResources(params);
+ String cp = (String) params.get("classpath");
+ return cp == null ? "" : cp;
+ },
+ (s, p) -> s
+ );
+
+ static final StandardBundlerParam<String> MAIN_CLASS =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.APPCLASS.getId(),
+ String.class,
+ params -> {
+ if (isRuntimeInstaller(params)) {
+ return null;
+ }
+ extractMainClassInfoFromAppResources(params);
+ String s = (String) params.get(
+ BundleParams.PARAM_APPLICATION_CLASS);
+ if (s == null) {
+ s = JLinkBundlerHelper.getMainClassFromModule(
+ params);
+ }
+ return s;
+ },
+ (s, p) -> s
+ );
+
+ static final StandardBundlerParam<File> PREDEFINED_RUNTIME_IMAGE =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId(),
+ File.class,
+ params -> null,
+ (s, p) -> new File(s)
+ );
+
+ static final StandardBundlerParam<String> APP_NAME =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.NAME.getId(),
+ String.class,
+ params -> {
+ String s = MAIN_CLASS.fetchFrom(params);
+ if (s != null) {
+ int idx = s.lastIndexOf(".");
+ if (idx >= 0) {
+ return s.substring(idx+1);
+ }
+ return s;
+ } else if (isRuntimeInstaller(params)) {
+ File f = PREDEFINED_RUNTIME_IMAGE.fetchFrom(params);
+ if (f != null) {
+ return f.getName();
+ }
+ }
+ return null;
+ },
+ (s, p) -> s
+ );
+
+ static final StandardBundlerParam<File> ICON =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.ICON.getId(),
+ File.class,
+ params -> null,
+ (s, p) -> new File(s)
+ );
+
+ static final StandardBundlerParam<String> VENDOR =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.VENDOR.getId(),
+ String.class,
+ params -> I18N.getString("param.vendor.default"),
+ (s, p) -> s
+ );
+
+ static final StandardBundlerParam<String> DESCRIPTION =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.DESCRIPTION.getId(),
+ String.class,
+ params -> params.containsKey(APP_NAME.getID())
+ ? APP_NAME.fetchFrom(params)
+ : I18N.getString("param.description.default"),
+ (s, p) -> s
+ );
+
+ static final StandardBundlerParam<String> COPYRIGHT =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.COPYRIGHT.getId(),
+ String.class,
+ params -> MessageFormat.format(I18N.getString(
+ "param.copyright.default"), new Date()),
+ (s, p) -> s
+ );
+
+ @SuppressWarnings("unchecked")
+ static final StandardBundlerParam<List<String>> ARGUMENTS =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.ARGUMENTS.getId(),
+ (Class<List<String>>) (Object) List.class,
+ params -> Collections.emptyList(),
+ (s, p) -> null
+ );
+
+ @SuppressWarnings("unchecked")
+ static final StandardBundlerParam<List<String>> JAVA_OPTIONS =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.JAVA_OPTIONS.getId(),
+ (Class<List<String>>) (Object) List.class,
+ params -> Collections.emptyList(),
+ (s, p) -> Arrays.asList(s.split("\n\n"))
+ );
+
+ // note that each bundler is likely to replace this one with
+ // their own converter
+ static final StandardBundlerParam<String> VERSION =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.VERSION.getId(),
+ String.class,
+ params -> getDefaultAppVersion(params),
+ (s, p) -> s
+ );
+
+ static final StandardBundlerParam<String> RELEASE =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.RELEASE.getId(),
+ String.class,
+ params -> DEFAULT_RELEASE,
+ (s, p) -> s
+ );
+
+ @SuppressWarnings("unchecked")
+ public static final StandardBundlerParam<String> LICENSE_FILE =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.LICENSE_FILE.getId(),
+ String.class,
+ params -> null,
+ (s, p) -> s
+ );
+
+ static final StandardBundlerParam<File> TEMP_ROOT =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.TEMP_ROOT.getId(),
+ File.class,
+ params -> {
+ try {
+ return Files.createTempDirectory(
+ "jdk.incubator.jpackage").toFile();
+ } catch (IOException ioe) {
+ return null;
+ }
+ },
+ (s, p) -> new File(s)
+ );
+
+ public static final StandardBundlerParam<File> CONFIG_ROOT =
+ new StandardBundlerParam<>(
+ "configRoot",
+ File.class,
+ params -> {
+ File root =
+ new File(TEMP_ROOT.fetchFrom(params), "config");
+ root.mkdirs();
+ return root;
+ },
+ (s, p) -> null
+ );
+
+ static final StandardBundlerParam<String> IDENTIFIER =
+ new StandardBundlerParam<>(
+ "identifier.default",
+ String.class,
+ params -> {
+ String s = MAIN_CLASS.fetchFrom(params);
+ if (s == null) return null;
+
+ int idx = s.lastIndexOf(".");
+ if (idx >= 1) {
+ return s.substring(0, idx);
+ }
+ return s;
+ },
+ (s, p) -> s
+ );
+
+ static final StandardBundlerParam<Boolean> BIND_SERVICES =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.BIND_SERVICES.getId(),
+ Boolean.class,
+ params -> false,
+ (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ?
+ true : Boolean.valueOf(s)
+ );
+
+
+ static final StandardBundlerParam<Boolean> VERBOSE =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.VERBOSE.getId(),
+ Boolean.class,
+ params -> false,
+ // valueOf(null) is false, and we actually do want null
+ (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ?
+ true : Boolean.valueOf(s)
+ );
+
+ static final StandardBundlerParam<File> RESOURCE_DIR =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.RESOURCE_DIR.getId(),
+ File.class,
+ params -> null,
+ (s, p) -> new File(s)
+ );
+
+ static final BundlerParamInfo<String> INSTALL_DIR =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.INSTALL_DIR.getId(),
+ String.class,
+ params -> null,
+ (s, p) -> s
+ );
+
+ static final StandardBundlerParam<File> PREDEFINED_APP_IMAGE =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.PREDEFINED_APP_IMAGE.getId(),
+ File.class,
+ params -> null,
+ (s, p) -> new File(s));
+
+ @SuppressWarnings("unchecked")
+ static final StandardBundlerParam<List<Map<String, ? super Object>>> ADD_LAUNCHERS =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.ADD_LAUNCHER.getId(),
+ (Class<List<Map<String, ? super Object>>>) (Object)
+ List.class,
+ params -> new ArrayList<>(1),
+ // valueOf(null) is false, and we actually do want null
+ (s, p) -> null
+ );
+
+ @SuppressWarnings("unchecked")
+ static final StandardBundlerParam
+ <List<Map<String, ? super Object>>> FILE_ASSOCIATIONS =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.FILE_ASSOCIATIONS.getId(),
+ (Class<List<Map<String, ? super Object>>>) (Object)
+ List.class,
+ params -> new ArrayList<>(1),
+ // valueOf(null) is false, and we actually do want null
+ (s, p) -> null
+ );
+
+ @SuppressWarnings("unchecked")
+ static final StandardBundlerParam<List<String>> FA_EXTENSIONS =
+ new StandardBundlerParam<>(
+ "fileAssociation.extension",
+ (Class<List<String>>) (Object) List.class,
+ params -> null, // null means not matched to an extension
+ (s, p) -> Arrays.asList(s.split("(,|\\s)+"))
+ );
+
+ @SuppressWarnings("unchecked")
+ static final StandardBundlerParam<List<String>> FA_CONTENT_TYPE =
+ new StandardBundlerParam<>(
+ "fileAssociation.contentType",
+ (Class<List<String>>) (Object) List.class,
+ params -> null,
+ // null means not matched to a content/mime type
+ (s, p) -> Arrays.asList(s.split("(,|\\s)+"))
+ );
+
+ static final StandardBundlerParam<String> FA_DESCRIPTION =
+ new StandardBundlerParam<>(
+ "fileAssociation.description",
+ String.class,
+ params -> APP_NAME.fetchFrom(params) + " File",
+ null
+ );
+
+ static final StandardBundlerParam<File> FA_ICON =
+ new StandardBundlerParam<>(
+ "fileAssociation.icon",
+ File.class,
+ ICON::fetchFrom,
+ (s, p) -> new File(s)
+ );
+
+ @SuppressWarnings("unchecked")
+ static final BundlerParamInfo<List<Path>> MODULE_PATH =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.MODULE_PATH.getId(),
+ (Class<List<Path>>) (Object)List.class,
+ p -> { return getDefaultModulePath(); },
+ (s, p) -> {
+ List<Path> modulePath = Arrays.asList(s
+ .split(File.pathSeparator)).stream()
+ .map(ss -> new File(ss).toPath())
+ .collect(Collectors.toList());
+ Path javaBasePath = null;
+ if (modulePath != null) {
+ javaBasePath = JLinkBundlerHelper
+ .findPathOfModule(modulePath, JAVABASEJMOD);
+ } else {
+ modulePath = new ArrayList<Path>();
+ }
+
+ // Add the default JDK module path to the module path.
+ if (javaBasePath == null) {
+ List<Path> jdkModulePath = getDefaultModulePath();
+
+ if (jdkModulePath != null) {
+ modulePath.addAll(jdkModulePath);
+ javaBasePath =
+ JLinkBundlerHelper.findPathOfModule(
+ modulePath, JAVABASEJMOD);
+ }
+ }
+
+ if (javaBasePath == null ||
+ !Files.exists(javaBasePath)) {
+ Log.error(String.format(I18N.getString(
+ "warning.no.jdk.modules.found")));
+ }
+
+ return modulePath;
+ });
+
+ static final BundlerParamInfo<String> MODULE =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.MODULE.getId(),
+ String.class,
+ p -> null,
+ (s, p) -> {
+ return String.valueOf(s);
+ });
+
+ @SuppressWarnings("unchecked")
+ static final BundlerParamInfo<Set<String>> ADD_MODULES =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.ADD_MODULES.getId(),
+ (Class<Set<String>>) (Object) Set.class,
+ p -> new LinkedHashSet<String>(),
+ (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(",")))
+ );
+
+ @SuppressWarnings("unchecked")
+ static final BundlerParamInfo<Set<String>> LIMIT_MODULES =
+ new StandardBundlerParam<>(
+ "limit-modules",
+ (Class<Set<String>>) (Object) Set.class,
+ p -> new LinkedHashSet<String>(),
+ (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(",")))
+ );
+
+ static boolean isRuntimeInstaller(Map<String, ? super Object> params) {
+ if (params.containsKey(MODULE.getID()) ||
+ params.containsKey(MAIN_JAR.getID()) ||
+ params.containsKey(PREDEFINED_APP_IMAGE.getID())) {
+ return false; // we are building or are given an application
+ }
+ // runtime installer requires --runtime-image, if this is false
+ // here then we should have thrown error validating args.
+ return params.containsKey(PREDEFINED_RUNTIME_IMAGE.getID());
+ }
+
+ static File getPredefinedAppImage(Map<String, ? super Object> params) {
+ File applicationImage = null;
+ if (PREDEFINED_APP_IMAGE.fetchFrom(params) != null) {
+ applicationImage = PREDEFINED_APP_IMAGE.fetchFrom(params);
+ if (!applicationImage.exists()) {
+ throw new RuntimeException(
+ MessageFormat.format(I18N.getString(
+ "message.app-image-dir-does-not-exist"),
+ PREDEFINED_APP_IMAGE.getID(),
+ applicationImage.toString()));
+ }
+ }
+ return applicationImage;
+ }
+
+ static void copyPredefinedRuntimeImage(
+ Map<String, ? super Object> params,
+ AbstractAppImageBuilder appBuilder)
+ throws IOException , ConfigException {
+ File topImage = PREDEFINED_RUNTIME_IMAGE.fetchFrom(params);
+ if (!topImage.exists()) {
+ throw new ConfigException(
+ MessageFormat.format(I18N.getString(
+ "message.runtime-image-dir-does-not-exist"),
+ PREDEFINED_RUNTIME_IMAGE.getID(),
+ topImage.toString()),
+ MessageFormat.format(I18N.getString(
+ "message.runtime-image-dir-does-not-exist.advice"),
+ PREDEFINED_RUNTIME_IMAGE.getID()));
+ }
+ File image = appBuilder.getRuntimeImageDir(topImage);
+ // copy whole runtime, need to skip jmods and src.zip
+ final List<String> excludes = Arrays.asList("jmods", "src.zip");
+ IOUtils.copyRecursive(image.toPath(), appBuilder.getRuntimeRoot(), excludes);
+
+ // if module-path given - copy modules to appDir/mods
+ List<Path> modulePath =
+ StandardBundlerParam.MODULE_PATH.fetchFrom(params);
+ List<Path> defaultModulePath = getDefaultModulePath();
+ Path dest = appBuilder.getAppModsDir();
+
+ if (dest != null) {
+ for (Path mp : modulePath) {
+ if (!defaultModulePath.contains(mp)) {
+ Files.createDirectories(dest);
+ IOUtils.copyRecursive(mp, dest);
+ }
+ }
+ }
+
+ appBuilder.prepareApplicationFiles(params);
+ }
+
+ static void extractMainClassInfoFromAppResources(
+ Map<String, ? super Object> params) {
+ boolean hasMainClass = params.containsKey(MAIN_CLASS.getID());
+ boolean hasMainJar = params.containsKey(MAIN_JAR.getID());
+ boolean hasMainJarClassPath = params.containsKey(CLASSPATH.getID());
+ boolean hasModule = params.containsKey(MODULE.getID());
+
+ if (hasMainClass && hasMainJar && hasMainJarClassPath || hasModule ||
+ isRuntimeInstaller(params)) {
+ return;
+ }
+
+ // it's a pair.
+ // The [0] is the srcdir [1] is the file relative to sourcedir
+ List<String[]> filesToCheck = new ArrayList<>();
+
+ if (hasMainJar) {
+ RelativeFileSet rfs = MAIN_JAR.fetchFrom(params);
+ for (String s : rfs.getIncludedFiles()) {
+ filesToCheck.add(
+ new String[] {rfs.getBaseDirectory().toString(), s});
+ }
+ } else if (hasMainJarClassPath) {
+ for (String s : CLASSPATH.fetchFrom(params).split("\\s+")) {
+ if (APP_RESOURCES.fetchFrom(params) != null) {
+ filesToCheck.add(
+ new String[] {APP_RESOURCES.fetchFrom(params)
+ .getBaseDirectory().toString(), s});
+ }
+ }
+ } else {
+ List<RelativeFileSet> rfsl = APP_RESOURCES_LIST.fetchFrom(params);
+ if (rfsl == null || rfsl.isEmpty()) {
+ return;
+ }
+ for (RelativeFileSet rfs : rfsl) {
+ if (rfs == null) continue;
+
+ for (String s : rfs.getIncludedFiles()) {
+ filesToCheck.add(
+ new String[]{rfs.getBaseDirectory().toString(), s});
+ }
+ }
+ }
+
+ // presume the set iterates in-order
+ for (String[] fnames : filesToCheck) {
+ try {
+ // only sniff jars
+ if (!fnames[1].toLowerCase().endsWith(".jar")) continue;
+
+ File file = new File(fnames[0], fnames[1]);
+ // that actually exist
+ if (!file.exists()) continue;
+
+ try (JarFile jf = new JarFile(file)) {
+ Manifest m = jf.getManifest();
+ Attributes attrs = (m != null) ?
+ m.getMainAttributes() : null;
+
+ if (attrs != null) {
+ if (!hasMainJar) {
+ if (fnames[0] == null) {
+ fnames[0] = file.getParentFile().toString();
+ }
+ params.put(MAIN_JAR.getID(), new RelativeFileSet(
+ new File(fnames[0]),
+ new LinkedHashSet<>(Collections
+ .singletonList(file))));
+ }
+ if (!hasMainJarClassPath) {
+ String cp =
+ attrs.getValue(Attributes.Name.CLASS_PATH);
+ params.put(CLASSPATH.getID(),
+ cp == null ? "" : cp);
+ }
+ break;
+ }
+ }
+ } catch (IOException ignore) {
+ ignore.printStackTrace();
+ }
+ }
+ }
+
+ static void validateMainClassInfoFromAppResources(
+ Map<String, ? super Object> params) throws ConfigException {
+ boolean hasMainClass = params.containsKey(MAIN_CLASS.getID());
+ boolean hasMainJar = params.containsKey(MAIN_JAR.getID());
+ boolean hasMainJarClassPath = params.containsKey(CLASSPATH.getID());
+ boolean hasModule = params.containsKey(MODULE.getID());
+ boolean hasAppImage = params.containsKey(PREDEFINED_APP_IMAGE.getID());
+
+ if (hasMainClass && hasMainJar && hasMainJarClassPath ||
+ hasAppImage || isRuntimeInstaller(params)) {
+ return;
+ }
+ if (hasModule) {
+ if (JLinkBundlerHelper.getMainClassFromModule(params) == null) {
+ throw new ConfigException(
+ I18N.getString("ERR_NoMainClass"), null);
+ }
+ } else {
+ extractMainClassInfoFromAppResources(params);
+
+ if (!params.containsKey(MAIN_CLASS.getID())) {
+ if (hasMainJar) {
+ throw new ConfigException(
+ MessageFormat.format(I18N.getString(
+ "error.no-main-class-with-main-jar"),
+ MAIN_JAR.fetchFrom(params)),
+ MessageFormat.format(I18N.getString(
+ "error.no-main-class-with-main-jar.advice"),
+ MAIN_JAR.fetchFrom(params)));
+ } else {
+ throw new ConfigException(
+ I18N.getString("error.no-main-class"),
+ I18N.getString("error.no-main-class.advice"));
+ }
+ }
+ }
+ }
+
+ private static List<RelativeFileSet>
+ createAppResourcesListFromString(String s,
+ Map<String, ? super Object> objectObjectMap) {
+ List<RelativeFileSet> result = new ArrayList<>();
+ for (String path : s.split("[:;]")) {
+ File f = new File(path);
+ if (f.getName().equals("*") || path.endsWith("/") ||
+ path.endsWith("\\")) {
+ if (f.getName().equals("*")) {
+ f = f.getParentFile();
+ }
+ Set<File> theFiles = new HashSet<>();
+ try {
+ try (Stream<Path> stream = Files.walk(f.toPath())) {
+ stream.filter(Files::isRegularFile)
+ .forEach(p -> theFiles.add(p.toFile()));
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ result.add(new RelativeFileSet(f, theFiles));
+ } else {
+ result.add(new RelativeFileSet(f.getParentFile(),
+ Collections.singleton(f)));
+ }
+ }
+ return result;
+ }
+
+ private static RelativeFileSet getMainJar(
+ String mainJarValue, Map<String, ? super Object> params) {
+ for (RelativeFileSet rfs : APP_RESOURCES_LIST.fetchFrom(params)) {
+ File appResourcesRoot = rfs.getBaseDirectory();
+ File mainJarFile = new File(appResourcesRoot, mainJarValue);
+
+ if (mainJarFile.exists()) {
+ return new RelativeFileSet(appResourcesRoot,
+ new LinkedHashSet<>(Collections.singletonList(
+ mainJarFile)));
+ }
+ mainJarFile = new File(mainJarValue);
+ if (mainJarFile.exists()) {
+ // absolute path for main-jar may fail is not legal
+ // below contains explicit error message.
+ } else {
+ List<Path> modulePath = MODULE_PATH.fetchFrom(params);
+ modulePath.removeAll(getDefaultModulePath());
+ if (!modulePath.isEmpty()) {
+ Path modularJarPath = JLinkBundlerHelper.findPathOfModule(
+ modulePath, mainJarValue);
+ if (modularJarPath != null &&
+ Files.exists(modularJarPath)) {
+ return new RelativeFileSet(appResourcesRoot,
+ new LinkedHashSet<>(Collections.singletonList(
+ modularJarPath.toFile())));
+ }
+ }
+ }
+ }
+
+ throw new IllegalArgumentException(
+ new ConfigException(MessageFormat.format(I18N.getString(
+ "error.main-jar-does-not-exist"),
+ mainJarValue), I18N.getString(
+ "error.main-jar-does-not-exist.advice")));
+ }
+
+ static List<Path> getDefaultModulePath() {
+ List<Path> result = new ArrayList<Path>();
+ Path jdkModulePath = Paths.get(
+ System.getProperty("java.home"), "jmods").toAbsolutePath();
+
+ if (jdkModulePath != null && Files.exists(jdkModulePath)) {
+ result.add(jdkModulePath);
+ }
+ else {
+ // On a developer build the JDK Home isn't where we expect it
+ // relative to the jmods directory. Do some extra
+ // processing to find it.
+ Map<String, String> env = System.getenv();
+
+ if (env.containsKey("JDK_HOME")) {
+ jdkModulePath = Paths.get(env.get("JDK_HOME"),
+ ".." + File.separator + "images"
+ + File.separator + "jmods").toAbsolutePath();
+
+ if (jdkModulePath != null && Files.exists(jdkModulePath)) {
+ result.add(jdkModulePath);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ static String getDefaultAppVersion(Map<String, ? super Object> params) {
+ String appVersion = DEFAULT_VERSION;
+
+ ModuleDescriptor descriptor = JLinkBundlerHelper.getMainModuleDescription(params);
+ if (descriptor != null) {
+ Optional<Version> oversion = descriptor.version();
+ if (oversion.isPresent()) {
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.module-version"),
+ oversion.get().toString(),
+ JLinkBundlerHelper.getMainModule(params)));
+ appVersion = oversion.get().toString();
+ }
+ }
+
+ return appVersion;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/ToolValidator.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.incubator.jpackage.internal;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+
+public final class ToolValidator {
+
+ ToolValidator(String tool) {
+ this(Path.of(tool));
+ }
+
+ ToolValidator(Path toolPath) {
+ this.toolPath = toolPath;
+ args = new ArrayList<>();
+
+ if (Platform.getPlatform() == Platform.LINUX) {
+ setCommandLine("--version");
+ }
+
+ setToolNotFoundErrorHandler(null);
+ setToolOldVersionErrorHandler(null);
+ }
+
+ ToolValidator setCommandLine(String... args) {
+ this.args = List.of(args);
+ return this;
+ }
+
+ ToolValidator setMinimalVersion(Comparable<String> v) {
+ minimalVersion = v;
+ return this;
+ }
+
+ ToolValidator setVersionParser(Function<Stream<String>, String> v) {
+ versionParser = v;
+ return this;
+ }
+
+ ToolValidator setToolNotFoundErrorHandler(
+ BiFunction<String, IOException, ConfigException> v) {
+ toolNotFoundErrorHandler = v;
+ return this;
+ }
+
+ ToolValidator setToolOldVersionErrorHandler(BiFunction<String, String, ConfigException> v) {
+ toolOldVersionErrorHandler = v;
+ return this;
+ }
+
+ ConfigException validate() {
+ List<String> cmdline = new ArrayList<>();
+ cmdline.add(toolPath.toString());
+ cmdline.addAll(args);
+
+ String name = toolPath.getFileName().toString();
+ try {
+ ProcessBuilder pb = new ProcessBuilder(cmdline);
+ AtomicBoolean canUseTool = new AtomicBoolean();
+ if (minimalVersion == null) {
+ // No version check.
+ canUseTool.setPlain(true);
+ }
+
+ String[] version = new String[1];
+ Executor.of(pb).setOutputConsumer(lines -> {
+ if (versionParser != null && minimalVersion != null) {
+ version[0] = versionParser.apply(lines);
+ if (minimalVersion.compareTo(version[0]) < 0) {
+ canUseTool.setPlain(true);
+ }
+ }
+ }).execute();
+
+ if (!canUseTool.getPlain()) {
+ if (toolOldVersionErrorHandler != null) {
+ return toolOldVersionErrorHandler.apply(name, version[0]);
+ }
+ return new ConfigException(MessageFormat.format(I18N.getString(
+ "error.tool-old-version"), name, minimalVersion),
+ MessageFormat.format(I18N.getString(
+ "error.tool-old-version.advice"), name,
+ minimalVersion));
+ }
+ } catch (IOException e) {
+ if (toolNotFoundErrorHandler != null) {
+ return toolNotFoundErrorHandler.apply(name, e);
+ }
+ return new ConfigException(MessageFormat.format(I18N.getString(
+ "error.tool-not-found"), name, e.getMessage()),
+ MessageFormat.format(I18N.getString(
+ "error.tool-not-found.advice"), name), e);
+ }
+
+ // All good. Tool can be used.
+ return null;
+ }
+
+ private final Path toolPath;
+ private List<String> args;
+ private Comparable<String> minimalVersion;
+ private Function<Stream<String>, String> versionParser;
+ private BiFunction<String, IOException, ConfigException> toolNotFoundErrorHandler;
+ private BiFunction<String, String, ConfigException> toolOldVersionErrorHandler;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/ValidOptions.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import jdk.incubator.jpackage.internal.Arguments.CLIOptions;
+
+/**
+ * ValidOptions
+ *
+ * Two basic methods for validating command line options.
+ *
+ * initArgs()
+ * Computes the Map of valid options for each mode on this Platform.
+ *
+ * checkIfSupported(CLIOptions arg)
+ * Determine if the given arg is valid on this platform.
+ *
+ * checkIfImageSupported(CLIOptions arg)
+ * Determine if the given arg is valid for creating app image.
+ *
+ * checkIfInstallerSupported(CLIOptions arg)
+ * Determine if the given arg is valid for creating installer.
+ *
+ */
+class ValidOptions {
+
+ enum USE {
+ ALL, // valid in all cases
+ LAUNCHER, // valid when creating a launcher
+ INSTALL // valid when creating an installer
+ }
+
+ private static final HashMap<String, USE> options = new HashMap<>();
+
+
+ // initializing list of mandatory arguments
+ static {
+ options.put(CLIOptions.NAME.getId(), USE.ALL);
+ options.put(CLIOptions.VERSION.getId(), USE.ALL);
+ options.put(CLIOptions.OUTPUT.getId(), USE.ALL);
+ options.put(CLIOptions.TEMP_ROOT.getId(), USE.ALL);
+ options.put(CLIOptions.VERBOSE.getId(), USE.ALL);
+ options.put(CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId(), USE.ALL);
+ options.put(CLIOptions.RESOURCE_DIR.getId(), USE.ALL);
+ options.put(CLIOptions.DESCRIPTION.getId(), USE.ALL);
+ options.put(CLIOptions.VENDOR.getId(), USE.ALL);
+ options.put(CLIOptions.COPYRIGHT.getId(), USE.ALL);
+ options.put(CLIOptions.PACKAGE_TYPE.getId(), USE.ALL);
+
+ options.put(CLIOptions.INPUT.getId(), USE.LAUNCHER);
+ options.put(CLIOptions.MODULE.getId(), USE.LAUNCHER);
+ options.put(CLIOptions.MODULE_PATH.getId(), USE.LAUNCHER);
+ options.put(CLIOptions.ADD_MODULES.getId(), USE.LAUNCHER);
+ options.put(CLIOptions.MAIN_JAR.getId(), USE.LAUNCHER);
+ options.put(CLIOptions.APPCLASS.getId(), USE.LAUNCHER);
+ options.put(CLIOptions.ICON.getId(), USE.LAUNCHER);
+ options.put(CLIOptions.ARGUMENTS.getId(), USE.LAUNCHER);
+ options.put(CLIOptions.JAVA_OPTIONS.getId(), USE.LAUNCHER);
+ options.put(CLIOptions.ADD_LAUNCHER.getId(), USE.LAUNCHER);
+ options.put(CLIOptions.BIND_SERVICES.getId(), USE.LAUNCHER);
+
+ options.put(CLIOptions.LICENSE_FILE.getId(), USE.INSTALL);
+ options.put(CLIOptions.INSTALL_DIR.getId(), USE.INSTALL);
+ options.put(CLIOptions.PREDEFINED_APP_IMAGE.getId(), USE.INSTALL);
+
+ options.put(CLIOptions.FILE_ASSOCIATIONS.getId(),
+ (Platform.getPlatform() == Platform.MAC) ? USE.ALL : USE.INSTALL);
+
+ if (Platform.getPlatform() == Platform.WINDOWS) {
+ options.put(CLIOptions.WIN_CONSOLE_HINT.getId(), USE.LAUNCHER);
+
+ options.put(CLIOptions.WIN_MENU_HINT.getId(), USE.INSTALL);
+ options.put(CLIOptions.WIN_MENU_GROUP.getId(), USE.INSTALL);
+ options.put(CLIOptions.WIN_SHORTCUT_HINT.getId(), USE.INSTALL);
+ options.put(CLIOptions.WIN_DIR_CHOOSER.getId(), USE.INSTALL);
+ options.put(CLIOptions.WIN_UPGRADE_UUID.getId(), USE.INSTALL);
+ options.put(CLIOptions.WIN_PER_USER_INSTALLATION.getId(),
+ USE.INSTALL);
+ }
+
+ if (Platform.getPlatform() == Platform.MAC) {
+ options.put(CLIOptions.MAC_SIGN.getId(), USE.ALL);
+ options.put(CLIOptions.MAC_BUNDLE_NAME.getId(), USE.ALL);
+ options.put(CLIOptions.MAC_BUNDLE_IDENTIFIER.getId(), USE.ALL);
+ options.put(CLIOptions.MAC_BUNDLE_SIGNING_PREFIX.getId(),
+ USE.ALL);
+ options.put(CLIOptions.MAC_SIGNING_KEY_NAME.getId(), USE.ALL);
+ options.put(CLIOptions.MAC_SIGNING_KEYCHAIN.getId(), USE.ALL);
+ options.put(CLIOptions.MAC_APP_STORE_ENTITLEMENTS.getId(),
+ USE.ALL);
+ }
+
+ if (Platform.getPlatform() == Platform.LINUX) {
+ options.put(CLIOptions.LINUX_BUNDLE_NAME.getId(), USE.INSTALL);
+ options.put(CLIOptions.LINUX_DEB_MAINTAINER.getId(), USE.INSTALL);
+ options.put(CLIOptions.LINUX_CATEGORY.getId(), USE.INSTALL);
+ options.put(CLIOptions.LINUX_RPM_LICENSE_TYPE.getId(), USE.INSTALL);
+ options.put(CLIOptions.LINUX_PACKAGE_DEPENDENCIES.getId(),
+ USE.INSTALL);
+ options.put(CLIOptions.LINUX_MENU_GROUP.getId(), USE.INSTALL);
+ options.put(CLIOptions.RELEASE.getId(), USE.INSTALL);
+ options.put(CLIOptions.LINUX_SHORTCUT_HINT.getId(), USE.INSTALL);
+ }
+ }
+
+ static boolean checkIfSupported(CLIOptions arg) {
+ return options.containsKey(arg.getId());
+ }
+
+ static boolean checkIfImageSupported(CLIOptions arg) {
+ USE use = options.get(arg.getId());
+ return USE.ALL == use || USE.LAUNCHER == use;
+ }
+
+ static boolean checkIfInstallerSupported(CLIOptions arg) {
+ USE use = options.get(arg.getId());
+ return USE.ALL == use || USE.INSTALL == use;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/resources/HelpResources.properties Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,275 @@
+#
+# Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+#
+
+MSG_Help=Usage: jpackage <options>\n\
+\n\
+Sample usages:\n\
+--------------\n\
+\ Generate an application package suitable for the host system:\n\
+\ For a modular application:\n\
+\ jpackage -n name -p modulePath -m moduleName/className\n\
+\ For a non-modular application:\n\
+\ jpackage -i inputDir -n name \\\n\
+\ --main-class className --main-jar myJar.jar\n\
+\ From a pre-built application image:\n\
+\ jpackage -n name --app-image appImageDir\n\
+\ Generate an application image:\n\
+\ For a modular application:\n\
+\ jpackage --type app-image -n name -p modulePath \\\n\
+\ -m moduleName/className\n\
+\ For a non-modular application:\n\
+\ jpackage --type app-image -i inputDir -n name \\\n\
+\ --main-class className --main-jar myJar.jar\n\
+\ To provide your own options to jlink, run jlink separately:\n\
+\ jlink --output appRuntimeImage -p modulePath -m moduleName \\\n\
+\ --no-header-files [<additional jlink options>...]\n\
+\ jpackage --type app-image -n name \\\n\
+\ -m moduleName/className --runtime-image appRuntimeImage\n\
+\ Generate a Java runtime package:\n\
+\ jpackage -n name --runtime-image <runtime-image>\n\
+\n\
+Generic Options:\n\
+\ @<filename> \n\
+\ Read options and/or mode from a file \n\
+\ This option can be used multiple times.\n\
+\ --type -t <type> \n\
+\ The type of package to create\n\
+\ Valid values are: {1} \n\
+\ If this option is not specified a platform dependent\n\
+\ default type will be created.\n\
+\ --app-version <version>\n\
+\ Version of the application and/or package\n\
+\ --copyright <copyright string>\n\
+\ Copyright for the application\n\
+\ --description <description string>\n\
+\ Description of the application\n\
+\ --help -h \n\
+\ Print the usage text with a list and description of each valid\n\
+\ option for the current platform to the output stream, and exit\n\
+\ --name -n <name>\n\
+\ Name of the application and/or package\n\
+\ --dest -d <destination path>\n\
+\ Path where generated output file is placed\n\
+\ Defaults to the current working directory.\n\
+\ (absolute path or relative to the current directory)\n\
+\ --temp <file path>\n\
+\ Path of a new or empty directory used to create temporary files\n\
+\ (absolute path or relative to the current directory)\n\
+\ If specified, the temp dir will not be removed upon the task\n\
+\ completion and must be removed manually\n\
+\ If not specified, a temporary directory will be created and\n\
+\ removed upon the task completion.\n\
+\ --vendor <vendor string>\n\
+\ Vendor of the application\n\
+\ --verbose\n\
+\ Enables verbose output\n\
+\ --version\n\
+\ Print the product version to the output stream and exit\n\
+\n\
+\Options for creating the runtime image:\n\
+\ --add-modules <module name>[,<module name>...]\n\
+\ A comma (",") separated list of modules to add.\n\
+\ This module list, along with the main module (if specified)\n\
+\ will be passed to jlink as the --add-module argument.\n\
+\ if not specified, either just the main module (if --module is\n\
+\ specified), or the default set of modules (if --main-jar is \n\
+\ specified) are used.\n\
+\ This option can be used multiple times.\n\
+\ --module-path -p <module path>...\n\
+\ A {0} separated list of paths\n\
+\ Each path is either a directory of modules or the path to a\n\
+\ modular jar.\n\
+\ (each path is absolute or relative to the current directory)\n\
+\ This option can be used multiple times.\n\
+\ --bind-services \n\
+\ Pass on --bind-services option to jlink (which will link in \n\
+\ service provider modules and their dependences) \n\
+\ --runtime-image <file path>\n\
+\ Path of the predefined runtime image that will be copied into\n\
+\ the application image\n\
+\ (absolute path or relative to the current directory)\n\
+\ If --runtime-image is not specified, jpackage will run jlink to\n\
+\ create the runtime image using options:\n\
+\ --strip-debug, --no-header-files, --no-man-pages, and\n\
+\ --strip-native-commands.\n\
+\n\
+\Options for creating the application image:\n\
+\ --icon <icon file path>\n\
+\ Path of the icon of the application package\n\
+\ (absolute path or relative to the current directory)\n\
+\ --input -i <input path>\n\
+\ Path of the input directory that contains the files to be packaged\n\
+\ (absolute path or relative to the current directory)\n\
+\ All files in the input directory will be packaged into the\n\
+\ application image.\n\
+\n\
+\Options for creating the application launcher(s):\n\
+\ --add-launcher <launcher name>=<file path>\n\
+\ Name of launcher, and a path to a Properties file that contains\n\
+\ a list of key, value pairs\n\
+\ (absolute path or relative to the current directory)\n\
+\ The keys "module", "main-jar", "main-class",\n\
+\ "arguments", "java-options", "app-version", "icon", and\n\
+\ "win-console" can be used.\n\
+\ These options are added to, or used to overwrite, the original\n\
+\ command line options to build an additional alternative launcher.\n\
+\ The main application launcher will be built from the command line\n\
+\ options. Additional alternative launchers can be built using\n\
+\ this option, and this option can be used multiple times to\n\
+\ build multiple additional launchers. \n\
+\ --arguments <main class arguments>\n\
+\ Command line arguments to pass to the main class if no command\n\
+\ line arguments are given to the launcher\n\
+\ This option can be used multiple times.\n\
+\ --java-options <java options>\n\
+\ Options to pass to the Java runtime\n\
+\ This option can be used multiple times.\n\
+\ --main-class <class name>\n\
+\ Qualified name of the application main class to execute\n\
+\ This option can only be used if --main-jar is specified.\n\
+\ --main-jar <main jar file>\n\
+\ The main JAR of the application; containing the main class\n\
+\ (specified as a path relative to the input path)\n\
+\ Either --module or --main-jar option can be specified but not\n\
+\ both.\n\
+\ --module -m <module name>[/<main class>]\n\
+\ The main module (and optionally main class) of the application\n\
+\ This module must be located on the module path.\n\
+\ When this option is specified, the main module will be linked\n\
+\ in the Java runtime image. Either --module or --main-jar\n\
+\ option can be specified but not both.\n\
+{2}\n\
+\Options for creating the application package:\n\
+\ --app-image <file path>\n\
+\ Location of the predefined application image that is used\n\
+\ to build an installable package\n\
+\ (absolute path or relative to the current directory)\n\
+\ --file-associations <file path>\n\
+\ Path to a Properties file that contains list of key, value pairs\n\
+\ (absolute path or relative to the current directory)\n\
+\ The keys "extension", "mime-type", "icon", and "description"\n\
+\ can be used to describe the association.\n\
+\ This option can be used multiple times.\n\
+\ --install-dir <file path>\n\
+\ {4}\
+\ --license-file <file path>\n\
+\ Path to the license file\n\
+\ (absolute path or relative to the current directory)\n\
+\ --resource-dir <path>\n\
+\ Path to override jpackage resources\n\
+\ Icons, template files, and other resources of jpackage can be\n\
+\ over-ridden by adding replacement resources to this directory.\n\
+\ (absolute path or relative to the current directory)\n\
+\ --runtime-image <file-path>\n\
+\ Path of the predefined runtime image to install\n\
+\ (absolute path or relative to the current directory)\n\
+\ Option is required when creating a runtime package.\n\
+\n\
+\Platform dependent options for creating the application package:\n\
+{3}
+
+MSG_Help_win_launcher=\
+\n\
+\Platform dependent option for creating the application launcher:\n\
+\ --win-console\n\
+\ Creates a console launcher for the application, should be\n\
+\ specified for application which requires console interactions\n\
+
+MSG_Help_win_install=\
+\ --win-dir-chooser\n\
+\ Adds a dialog to enable the user to choose a directory in which\n\
+\ the product is installed\n\
+\ --win-menu\n\
+\ Adds the application to the system menu\n\
+\ --win-menu-group <menu group name>\n\
+\ Start Menu group this application is placed in\n\
+\ --win-per-user-install\n\
+\ Request to perform an install on a per-user basis\n\
+\ --win-shortcut\n\
+\ Creates a desktop shortcut for the application\n\
+\ --win-upgrade-uuid <id string>\n\
+\ UUID associated with upgrades for this package\n\
+
+MSG_Help_win_install_dir=\
+\Relative sub-path under the default installation location\n\
+
+MSG_Help_mac_launcher=\
+\ --mac-package-identifier <ID string>\n\
+\ An identifier that uniquely identifies the application for macOS\n\
+\ Defaults to the main class name.\n\
+\ May only use alphanumeric (A-Z,a-z,0-9), hyphen (-),\n\
+\ and period (.) characters.\n\
+\ --mac-package-name <name string>\n\
+\ Name of the application as it appears in the Menu Bar\n\
+\ This can be different from the application name.\n\
+\ This name must be less than 16 characters long and be suitable for\n\
+\ displaying in the menu bar and the application Info window.\n\
+\ Defaults to the application name.\n\
+\ --mac-package-signing-prefix <prefix string>\n\
+\ When signing the application package, this value is prefixed\n\
+\ to all components that need to be signed that don't have\n\
+\ an existing package identifier.\n\
+\ --mac-sign\n\
+\ Request that the package be signed\n\
+\ --mac-signing-keychain <file path>\n\
+\ Path of the keychain to search for the signing identity\n\
+\ (absolute path or relative to the current directory).\n\
+\ If not specified, the standard keychains are used.\n\
+\ --mac-signing-key-user-name <team name>\n\
+\ Team name portion in Apple signing identities' names.\n\
+\ For example "Developer ID Application: "\n\
+
+MSG_Help_linux_install=\
+\ --linux-package-name <package name>\n\
+\ Name for Linux package, defaults to the application name\n\
+\ --linux-deb-maintainer <email address>\n\
+\ Maintainer for .deb package\n\
+\ --linux-menu-group <menu-group-name>\n\
+\ Menu group this application is placed in\n\
+\ --linux-package-deps\n\
+\ Required packages or capabilities for the application\n\
+\ --linux-rpm-license-type <type string>\n\
+\ Type of the license ("License: <value>" of the RPM .spec)\n\
+\ --linux-app-release <release value>\n\
+\ Release value of the RPM <name>.spec file or \n\
+\ Debian revision value of the DEB control file.\n\
+\ --linux-app-category <category value>\n\
+\ Group value of the RPM <name>.spec file or \n\
+\ Section value of DEB control file.\n\
+\ --linux-shortcut\n\
+\ Creates a shortcut for the application\n\
+
+MSG_Help_mac_linux_install_dir=\
+\Absolute path of the installation directory of the application\n\
+
+MSG_Help_default_install_dir=\
+\Absolute path of the installation directory of the application on OS X\n\
+\ or Linux. Relative sub-path of the installation location of\n\
+\ the application such as "Program Files" or "AppData" on Windows.\n\
+
+MSG_Help_no_args=Usage: jpackage <options>\n\
+\Use jpackage --help (or -h) for a list of possible options\
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/resources/HelpResources_ja.properties Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,275 @@
+#
+# Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+#
+
+MSG_Help=Usage: jpackage <options>\n\
+\n\
+Sample usages:\n\
+--------------\n\
+\ Generate an application package suitable for the host system:\n\
+\ For a modular application:\n\
+\ jpackage -n name -p modulePath -m moduleName/className\n\
+\ For a non-modular application:\n\
+\ jpackage -i inputDir -n name \\\n\
+\ --main-class className --main-jar myJar.jar\n\
+\ From a pre-built application image:\n\
+\ jpackage -n name --app-image appImageDir\n\
+\ Generate an application image:\n\
+\ For a modular application:\n\
+\ jpackage --type app-image -n name -p modulePath \\\n\
+\ -m moduleName/className\n\
+\ For a non-modular application:\n\
+\ jpackage --type app-image -i inputDir -n name \\\n\
+\ --main-class className --main-jar myJar.jar\n\
+\ To provide your own options to jlink, run jlink separately:\n\
+\ jlink --output appRuntimeImage -p modulePath -m moduleName \\\n\
+\ --no-header-files [<additional jlink options>...]\n\
+\ jpackage --type app-image -n name \\\n\
+\ -m moduleName/className --runtime-image appRuntimeImage\n\
+\ Generate a Java runtime package:\n\
+\ jpackage -n name --runtime-image <runtime-image>\n\
+\n\
+Generic Options:\n\
+\ @<filename> \n\
+\ Read options and/or mode from a file \n\
+\ This option can be used multiple times.\n\
+\ --type -t <type> \n\
+\ The type of package to create\n\
+\ Valid values are: {1} \n\
+\ If this option is not specified a platform dependent\n\
+\ default type will be created.\n\
+\ --app-version <version>\n\
+\ Version of the application and/or package\n\
+\ --copyright <copyright string>\n\
+\ Copyright for the application\n\
+\ --description <description string>\n\
+\ Description of the application\n\
+\ --help -h \n\
+\ Print the usage text with a list and description of each valid\n\
+\ option for the current platform to the output stream, and exit\n\
+\ --name -n <name>\n\
+\ Name of the application and/or package\n\
+\ --dest -d <destination path>\n\
+\ Path where generated output file is placed\n\
+\ Defaults to the current working directory.\n\
+\ (absolute path or relative to the current directory)\n\
+\ --temp <file path>\n\
+\ Path of a new or empty directory used to create temporary files\n\
+\ (absolute path or relative to the current directory)\n\
+\ If specified, the temp dir will not be removed upon the task\n\
+\ completion and must be removed manually\n\
+\ If not specified, a temporary directory will be created and\n\
+\ removed upon the task completion.\n\
+\ --vendor <vendor string>\n\
+\ Vendor of the application\n\
+\ --verbose\n\
+\ Enables verbose output\n\
+\ --version\n\
+\ Print the product version to the output stream and exit\n\
+\n\
+\Options for creating the runtime image:\n\
+\ --add-modules <module name>[,<module name>...]\n\
+\ A comma (",") separated list of modules to add.\n\
+\ This module list, along with the main module (if specified)\n\
+\ will be passed to jlink as the --add-module argument.\n\
+\ if not specified, either just the main module (if --module is\n\
+\ specified), or the default set of modules (if --main-jar is \n\
+\ specified) are used.\n\
+\ This option can be used multiple times.\n\
+\ --module-path -p <module path>...\n\
+\ A {0} separated list of paths\n\
+\ Each path is either a directory of modules or the path to a\n\
+\ modular jar.\n\
+\ (each path is absolute or relative to the current directory)\n\
+\ This option can be used multiple times.\n\
+\ --bind-services \n\
+\ Pass on --bind-services option to jlink (which will link in \n\
+\ service provider modules and their dependences) \n\
+\ --runtime-image <file path>\n\
+\ Path of the predefined runtime image that will be copied into\n\
+\ the application image\n\
+\ (absolute path or relative to the current directory)\n\
+\ If --runtime-image is not specified, jpackage will run jlink to\n\
+\ create the runtime image using options:\n\
+\ --strip-debug, --no-header-files, --no-man-pages, and\n\
+\ --strip-native-commands.\n\
+\n\
+\Options for creating the application image:\n\
+\ --icon <icon file path>\n\
+\ Path of the icon of the application package\n\
+\ (absolute path or relative to the current directory)\n\
+\ --input -i <input path>\n\
+\ Path of the input directory that contains the files to be packaged\n\
+\ (absolute path or relative to the current directory)\n\
+\ All files in the input directory will be packaged into the\n\
+\ application image.\n\
+\n\
+\Options for creating the application launcher(s):\n\
+\ --add-launcher <launcher name>=<file path>\n\
+\ Name of launcher, and a path to a Properties file that contains\n\
+\ a list of key, value pairs\n\
+\ (absolute path or relative to the current directory)\n\
+\ The keys "module", "main-jar", "main-class",\n\
+\ "arguments", "java-options", "app-version", "icon", and\n\
+\ "win-console" can be used.\n\
+\ These options are added to, or used to overwrite, the original\n\
+\ command line options to build an additional alternative launcher.\n\
+\ The main application launcher will be built from the command line\n\
+\ options. Additional alternative launchers can be built using\n\
+\ this option, and this option can be used multiple times to\n\
+\ build multiple additional launchers. \n\
+\ --arguments <main class arguments>\n\
+\ Command line arguments to pass to the main class if no command\n\
+\ line arguments are given to the launcher\n\
+\ This option can be used multiple times.\n\
+\ --java-options <java options>\n\
+\ Options to pass to the Java runtime\n\
+\ This option can be used multiple times.\n\
+\ --main-class <class name>\n\
+\ Qualified name of the application main class to execute\n\
+\ This option can only be used if --main-jar is specified.\n\
+\ --main-jar <main jar file>\n\
+\ The main JAR of the application; containing the main class\n\
+\ (specified as a path relative to the input path)\n\
+\ Either --module or --main-jar option can be specified but not\n\
+\ both.\n\
+\ --module -m <module name>[/<main class>]\n\
+\ The main module (and optionally main class) of the application\n\
+\ This module must be located on the module path.\n\
+\ When this option is specified, the main module will be linked\n\
+\ in the Java runtime image. Either --module or --main-jar\n\
+\ option can be specified but not both.\n\
+{2}\n\
+\Options for creating the application package:\n\
+\ --app-image <file path>\n\
+\ Location of the predefined application image that is used\n\
+\ to build an installable package\n\
+\ (absolute path or relative to the current directory)\n\
+\ --file-associations <file path>\n\
+\ Path to a Properties file that contains list of key, value pairs\n\
+\ (absolute path or relative to the current directory)\n\
+\ The keys "extension", "mime-type", "icon", and "description"\n\
+\ can be used to describe the association.\n\
+\ This option can be used multiple times.\n\
+\ --install-dir <file path>\n\
+\ {4}\
+\ --license-file <file path>\n\
+\ Path to the license file\n\
+\ (absolute path or relative to the current directory)\n\
+\ --resource-dir <path>\n\
+\ Path to override jpackage resources\n\
+\ Icons, template files, and other resources of jpackage can be\n\
+\ over-ridden by adding replacement resources to this directory.\n\
+\ (absolute path or relative to the current directory)\n\
+\ --runtime-image <file-path>\n\
+\ Path of the predefined runtime image to install\n\
+\ (absolute path or relative to the current directory)\n\
+\ Option is required when creating a runtime package.\n\
+\n\
+\Platform dependent options for creating the application package:\n\
+{3}
+
+MSG_Help_win_launcher=\
+\n\
+\Platform dependent option for creating the application launcher:\n\
+\ --win-console\n\
+\ Creates a console launcher for the application, should be\n\
+\ specified for application which requires console interactions\n\
+
+MSG_Help_win_install=\
+\ --win-dir-chooser\n\
+\ Adds a dialog to enable the user to choose a directory in which\n\
+\ the product is installed\n\
+\ --win-menu\n\
+\ Adds the application to the system menu\n\
+\ --win-menu-group <menu group name>\n\
+\ Start Menu group this application is placed in\n\
+\ --win-per-user-install\n\
+\ Request to perform an install on a per-user basis\n\
+\ --win-shortcut\n\
+\ Creates a desktop shortcut for the application\n\
+\ --win-upgrade-uuid <id string>\n\
+\ UUID associated with upgrades for this package\n\
+
+MSG_Help_win_install_dir=\
+\Relative sub-path under the default installation location\n\
+
+MSG_Help_mac_launcher=\
+\ --mac-package-identifier <ID string>\n\
+\ An identifier that uniquely identifies the application for macOS\n\
+\ Defaults to the main class name.\n\
+\ May only use alphanumeric (A-Z,a-z,0-9), hyphen (-),\n\
+\ and period (.) characters.\n\
+\ --mac-package-name <name string>\n\
+\ Name of the application as it appears in the Menu Bar\n\
+\ This can be different from the application name.\n\
+\ This name must be less than 16 characters long and be suitable for\n\
+\ displaying in the menu bar and the application Info window.\n\
+\ Defaults to the application name.\n\
+\ --mac-package-signing-prefix <prefix string>\n\
+\ When signing the application package, this value is prefixed\n\
+\ to all components that need to be signed that don't have\n\
+\ an existing package identifier.\n\
+\ --mac-sign\n\
+\ Request that the package be signed\n\
+\ --mac-signing-keychain <file path>\n\
+\ Path of the keychain to search for the signing identity\n\
+\ (absolute path or relative to the current directory).\n\
+\ If not specified, the standard keychains are used.\n\
+\ --mac-signing-key-user-name <team name>\n\
+\ Team name portion in Apple signing identities' names.\n\
+\ For example "Developer ID Application: "\n\
+
+MSG_Help_linux_install=\
+\ --linux-package-name <package name>\n\
+\ Name for Linux package, defaults to the application name\n\
+\ --linux-deb-maintainer <email address>\n\
+\ Maintainer for .deb package\n\
+\ --linux-menu-group <menu-group-name>\n\
+\ Menu group this application is placed in\n\
+\ --linux-package-deps\n\
+\ Required packages or capabilities for the application\n\
+\ --linux-rpm-license-type <type string>\n\
+\ Type of the license ("License: <value>" of the RPM .spec)\n\
+\ --linux-app-release <release value>\n\
+\ Release value of the RPM <name>.spec file or \n\
+\ Debian revision value of the DEB control file.\n\
+\ --linux-app-category <category value>\n\
+\ Group value of the RPM <name>.spec file or \n\
+\ Section value of DEB control file.\n\
+\ --linux-shortcut\n\
+\ Creates a shortcut for the application\n\
+
+MSG_Help_mac_linux_install_dir=\
+\Absolute path of the installation directory of the application\n\
+
+MSG_Help_default_install_dir=\
+\Absolute path of the installation directory of the application on OS X\n\
+\ or Linux. Relative sub-path of the installation location of\n\
+\ the application such as "Program Files" or "AppData" on Windows.\n\
+
+MSG_Help_no_args=Usage: jpackage <options>\n\
+\Use jpackage --help (or -h) for a list of possible options\
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/resources/HelpResources_zh_CN.properties Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,275 @@
+#
+# Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+#
+
+MSG_Help=Usage: jpackage <options>\n\
+\n\
+Sample usages:\n\
+--------------\n\
+\ Generate an application package suitable for the host system:\n\
+\ For a modular application:\n\
+\ jpackage -n name -p modulePath -m moduleName/className\n\
+\ For a non-modular application:\n\
+\ jpackage -i inputDir -n name \\\n\
+\ --main-class className --main-jar myJar.jar\n\
+\ From a pre-built application image:\n\
+\ jpackage -n name --app-image appImageDir\n\
+\ Generate an application image:\n\
+\ For a modular application:\n\
+\ jpackage --type app-image -n name -p modulePath \\\n\
+\ -m moduleName/className\n\
+\ For a non-modular application:\n\
+\ jpackage --type app-image -i inputDir -n name \\\n\
+\ --main-class className --main-jar myJar.jar\n\
+\ To provide your own options to jlink, run jlink separately:\n\
+\ jlink --output appRuntimeImage -p modulePath -m moduleName \\\n\
+\ --no-header-files [<additional jlink options>...]\n\
+\ jpackage --type app-image -n name \\\n\
+\ -m moduleName/className --runtime-image appRuntimeImage\n\
+\ Generate a Java runtime package:\n\
+\ jpackage -n name --runtime-image <runtime-image>\n\
+\n\
+Generic Options:\n\
+\ @<filename> \n\
+\ Read options and/or mode from a file \n\
+\ This option can be used multiple times.\n\
+\ --type -t <type> \n\
+\ The type of package to create\n\
+\ Valid values are: {1} \n\
+\ If this option is not specified a platform dependent\n\
+\ default type will be created.\n\
+\ --app-version <version>\n\
+\ Version of the application and/or package\n\
+\ --copyright <copyright string>\n\
+\ Copyright for the application\n\
+\ --description <description string>\n\
+\ Description of the application\n\
+\ --help -h \n\
+\ Print the usage text with a list and description of each valid\n\
+\ option for the current platform to the output stream, and exit\n\
+\ --name -n <name>\n\
+\ Name of the application and/or package\n\
+\ --dest -d <destination path>\n\
+\ Path where generated output file is placed\n\
+\ Defaults to the current working directory.\n\
+\ (absolute path or relative to the current directory)\n\
+\ --temp <file path>\n\
+\ Path of a new or empty directory used to create temporary files\n\
+\ (absolute path or relative to the current directory)\n\
+\ If specified, the temp dir will not be removed upon the task\n\
+\ completion and must be removed manually\n\
+\ If not specified, a temporary directory will be created and\n\
+\ removed upon the task completion.\n\
+\ --vendor <vendor string>\n\
+\ Vendor of the application\n\
+\ --verbose\n\
+\ Enables verbose output\n\
+\ --version\n\
+\ Print the product version to the output stream and exit\n\
+\n\
+\Options for creating the runtime image:\n\
+\ --add-modules <module name>[,<module name>...]\n\
+\ A comma (",") separated list of modules to add.\n\
+\ This module list, along with the main module (if specified)\n\
+\ will be passed to jlink as the --add-module argument.\n\
+\ if not specified, either just the main module (if --module is\n\
+\ specified), or the default set of modules (if --main-jar is \n\
+\ specified) are used.\n\
+\ This option can be used multiple times.\n\
+\ --module-path -p <module path>...\n\
+\ A {0} separated list of paths\n\
+\ Each path is either a directory of modules or the path to a\n\
+\ modular jar.\n\
+\ (each path is absolute or relative to the current directory)\n\
+\ This option can be used multiple times.\n\
+\ --bind-services \n\
+\ Pass on --bind-services option to jlink (which will link in \n\
+\ service provider modules and their dependences) \n\
+\ --runtime-image <file path>\n\
+\ Path of the predefined runtime image that will be copied into\n\
+\ the application image\n\
+\ (absolute path or relative to the current directory)\n\
+\ If --runtime-image is not specified, jpackage will run jlink to\n\
+\ create the runtime image using options:\n\
+\ --strip-debug, --no-header-files, --no-man-pages, and\n\
+\ --strip-native-commands.\n\
+\n\
+\Options for creating the application image:\n\
+\ --icon <icon file path>\n\
+\ Path of the icon of the application package\n\
+\ (absolute path or relative to the current directory)\n\
+\ --input -i <input path>\n\
+\ Path of the input directory that contains the files to be packaged\n\
+\ (absolute path or relative to the current directory)\n\
+\ All files in the input directory will be packaged into the\n\
+\ application image.\n\
+\n\
+\Options for creating the application launcher(s):\n\
+\ --add-launcher <launcher name>=<file path>\n\
+\ Name of launcher, and a path to a Properties file that contains\n\
+\ a list of key, value pairs\n\
+\ (absolute path or relative to the current directory)\n\
+\ The keys "module", "main-jar", "main-class",\n\
+\ "arguments", "java-options", "app-version", "icon", and\n\
+\ "win-console" can be used.\n\
+\ These options are added to, or used to overwrite, the original\n\
+\ command line options to build an additional alternative launcher.\n\
+\ The main application launcher will be built from the command line\n\
+\ options. Additional alternative launchers can be built using\n\
+\ this option, and this option can be used multiple times to\n\
+\ build multiple additional launchers. \n\
+\ --arguments <main class arguments>\n\
+\ Command line arguments to pass to the main class if no command\n\
+\ line arguments are given to the launcher\n\
+\ This option can be used multiple times.\n\
+\ --java-options <java options>\n\
+\ Options to pass to the Java runtime\n\
+\ This option can be used multiple times.\n\
+\ --main-class <class name>\n\
+\ Qualified name of the application main class to execute\n\
+\ This option can only be used if --main-jar is specified.\n\
+\ --main-jar <main jar file>\n\
+\ The main JAR of the application; containing the main class\n\
+\ (specified as a path relative to the input path)\n\
+\ Either --module or --main-jar option can be specified but not\n\
+\ both.\n\
+\ --module -m <module name>[/<main class>]\n\
+\ The main module (and optionally main class) of the application\n\
+\ This module must be located on the module path.\n\
+\ When this option is specified, the main module will be linked\n\
+\ in the Java runtime image. Either --module or --main-jar\n\
+\ option can be specified but not both.\n\
+{2}\n\
+\Options for creating the application package:\n\
+\ --app-image <file path>\n\
+\ Location of the predefined application image that is used\n\
+\ to build an installable package\n\
+\ (absolute path or relative to the current directory)\n\
+\ --file-associations <file path>\n\
+\ Path to a Properties file that contains list of key, value pairs\n\
+\ (absolute path or relative to the current directory)\n\
+\ The keys "extension", "mime-type", "icon", and "description"\n\
+\ can be used to describe the association.\n\
+\ This option can be used multiple times.\n\
+\ --install-dir <file path>\n\
+\ {4}\
+\ --license-file <file path>\n\
+\ Path to the license file\n\
+\ (absolute path or relative to the current directory)\n\
+\ --resource-dir <path>\n\
+\ Path to override jpackage resources\n\
+\ Icons, template files, and other resources of jpackage can be\n\
+\ over-ridden by adding replacement resources to this directory.\n\
+\ (absolute path or relative to the current directory)\n\
+\ --runtime-image <file-path>\n\
+\ Path of the predefined runtime image to install\n\
+\ (absolute path or relative to the current directory)\n\
+\ Option is required when creating a runtime package.\n\
+\n\
+\Platform dependent options for creating the application package:\n\
+{3}
+
+MSG_Help_win_launcher=\
+\n\
+\Platform dependent option for creating the application launcher:\n\
+\ --win-console\n\
+\ Creates a console launcher for the application, should be\n\
+\ specified for application which requires console interactions\n\
+
+MSG_Help_win_install=\
+\ --win-dir-chooser\n\
+\ Adds a dialog to enable the user to choose a directory in which\n\
+\ the product is installed\n\
+\ --win-menu\n\
+\ Adds the application to the system menu\n\
+\ --win-menu-group <menu group name>\n\
+\ Start Menu group this application is placed in\n\
+\ --win-per-user-install\n\
+\ Request to perform an install on a per-user basis\n\
+\ --win-shortcut\n\
+\ Creates a desktop shortcut for the application\n\
+\ --win-upgrade-uuid <id string>\n\
+\ UUID associated with upgrades for this package\n\
+
+MSG_Help_win_install_dir=\
+\Relative sub-path under the default installation location\n\
+
+MSG_Help_mac_launcher=\
+\ --mac-package-identifier <ID string>\n\
+\ An identifier that uniquely identifies the application for macOS\n\
+\ Defaults to the main class name.\n\
+\ May only use alphanumeric (A-Z,a-z,0-9), hyphen (-),\n\
+\ and period (.) characters.\n\
+\ --mac-package-name <name string>\n\
+\ Name of the application as it appears in the Menu Bar\n\
+\ This can be different from the application name.\n\
+\ This name must be less than 16 characters long and be suitable for\n\
+\ displaying in the menu bar and the application Info window.\n\
+\ Defaults to the application name.\n\
+\ --mac-package-signing-prefix <prefix string>\n\
+\ When signing the application package, this value is prefixed\n\
+\ to all components that need to be signed that don't have\n\
+\ an existing package identifier.\n\
+\ --mac-sign\n\
+\ Request that the package be signed\n\
+\ --mac-signing-keychain <file path>\n\
+\ Path of the keychain to search for the signing identity\n\
+\ (absolute path or relative to the current directory).\n\
+\ If not specified, the standard keychains are used.\n\
+\ --mac-signing-key-user-name <team name>\n\
+\ Team name portion in Apple signing identities' names.\n\
+\ For example "Developer ID Application: "\n\
+
+MSG_Help_linux_install=\
+\ --linux-package-name <package name>\n\
+\ Name for Linux package, defaults to the application name\n\
+\ --linux-deb-maintainer <email address>\n\
+\ Maintainer for .deb package\n\
+\ --linux-menu-group <menu-group-name>\n\
+\ Menu group this application is placed in\n\
+\ --linux-package-deps\n\
+\ Required packages or capabilities for the application\n\
+\ --linux-rpm-license-type <type string>\n\
+\ Type of the license ("License: <value>" of the RPM .spec)\n\
+\ --linux-app-release <release value>\n\
+\ Release value of the RPM <name>.spec file or \n\
+\ Debian revision value of the DEB control file.\n\
+\ --linux-app-category <category value>\n\
+\ Group value of the RPM <name>.spec file or \n\
+\ Section value of DEB control file.\n\
+\ --linux-shortcut\n\
+\ Creates a shortcut for the application\n\
+
+MSG_Help_mac_linux_install_dir=\
+\Absolute path of the installation directory of the application\n\
+
+MSG_Help_default_install_dir=\
+\Absolute path of the installation directory of the application on OS X\n\
+\ or Linux. Relative sub-path of the installation location of\n\
+\ the application such as "Program Files" or "AppData" on Windows.\n\
+
+MSG_Help_no_args=Usage: jpackage <options>\n\
+\Use jpackage --help (or -h) for a list of possible options\
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/resources/MainResources.properties Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,92 @@
+#
+# Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+#
+
+param.copyright.default=Copyright (C) {0,date,YYYY}
+param.description.default=None
+param.vendor.default=Unknown
+
+message.using-default-resource=Using default package resource {0} {1} (add {2} to the resource-dir to customize).
+message.no-default-resource=no default package resource {0} {1} (add {2} to the resource-dir to customize).
+message.using-custom-resource-from-file=Using custom package resource {0} (loaded from file {1}).
+message.using-custom-resource=Using custom package resource {0} (loaded from {1}).
+message.creating-app-bundle=Creating app package: {0} in {1}
+message.app-image-dir-does-not-exist=Specified application image directory {0}: {1} does not exists
+message.app-image-dir-does-not-exist.advice=Confirm that the value for {0} exists
+message.runtime-image-dir-does-not-exist=Specified runtime image directory {0}: {1} does not exists
+message.runtime-image-dir-does-not-exist.advice=Confirm that the value for {0} exists
+message.debug-working-directory=Kept working directory for debug: {0}
+message.bundle-created=Succeeded in building {0} package
+message.module-version=Using version "{0}" from module "{1}" as application version
+message.module-class=Using class "{0}" from module "{1}" as application main class
+
+error.cannot-create-output-dir=Destination directory {0} cannot be created
+error.cannot-write-to-output-dir=Destination directory {0} is not writable
+error.root-exists=Error: Application destination directory {0} already exists
+error.no-main-class-with-main-jar=A main class was not specified nor was one found in the jar {0}
+error.no-main-class-with-main-jar.advice=Specify a main class or ensure that the jar {0} specifies one in the manifest
+error.no-main-class=A main class was not specified nor was one found in the supplied application resources
+error.no-main-class.advice=Please specify a application class or ensure that the appResources has a jar containing one in the manifest
+error.main-jar-does-not-exist=The configured main jar does not exist {0} in the input directory
+error.main-jar-does-not-exist.advice=The main jar must be specified relative to the input directory (not an absolute path), and must exist within that directory
+
+error.tool-not-found=Can not find {0}. Reason: {1}
+error.tool-not-found.advice=Please install {0}
+error.tool-old-version=Can not find {0} {1} or newer
+error.tool-old-version.advice=Please install {0} {1} or newer
+error.jlink.failed=jlink failed with: {0}
+
+warning.module.does.not.exist=Module [{0}] does not exist
+warning.no.jdk.modules.found=Warning: No JDK Modules found
+
+MSG_BundlerFailed=Error: Bundler "{1}" ({0}) failed to produce a package
+MSG_BundlerConfigException=Bundler {0} skipped because of a configuration problem: {1} \n\
+Advice to fix: {2}
+MSG_BundlerConfigExceptionNoAdvice=Bundler {0} skipped because of a configuration problem: {1}
+MSG_BundlerRuntimeException=Bundler {0} failed because of {1}
+MSG_BundlerFailed=Error: Bundler "{1}" ({0}) failed to produce a package
+
+ERR_NoMainClass=Error: Main application class is missing
+ERR_UnsupportedOption=Error: Option [{0}] is not valid on this platform
+ERR_InvalidTypeOption=Error: Option [{0}] is not valid with type [{1}]
+ERR_NoInstallerEntryPoint=Error: Option [{0}] is not valid without --module or --main-jar entry point option
+
+ERR_MissingArgument=Error: Missing argument: {0}
+ERR_MissingAppResources=Error: No application jars found
+ERR_AppImageNotExist=Error: App image directory "{0}" does not exist
+ERR_NoAddLauncherName=Error: --add-launcher option requires a name and a file path (--add-launcher <name>=<file path>)
+ERR_NoUniqueName=Error: --add-launcher <name>=<file path> requires a unique name
+ERR_NoJreInstallerName=Error: Jre Installers require a name parameter
+ERR_InvalidAppName=Error: Invalid Application name: {0}
+ERR_InvalidSLName=Error: Invalid Add Launcher name: {0}
+ERR_LicenseFileNotExit=Error: Specified license file does not exist
+ERR_BuildRootInvalid=Error: temp ({0}) must be non-existant or empty directory
+ERR_InvalidOption=Error: Invalid Option: [{0}]
+ERR_InvalidInstallerType=Error: Invalid or unsupported type: [{0}]
+ERR_BothMainJarAndModule=Error: Cannot have both --main-jar and --module Options
+ERR_NoEntryPoint=Error: creating application image requires --main-jar or --module Option
+ERR_InputNotDirectory=Error: Input directory specified is not a directory: {0}
+ERR_CannotReadInputDir=Error: No permission to read from input directory: {0}
+ERR_CannotParseOptions=Error: Processing @filename option: {0}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/resources/MainResources_ja.properties Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,92 @@
+#
+# Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+#
+
+param.copyright.default=Copyright (C) {0,date,YYYY}
+param.description.default=None
+param.vendor.default=Unknown
+
+message.using-default-resource=Using default package resource {0} {1} (add {2} to the resource-dir to customize).
+message.no-default-resource=no default package resource {0} {1} (add {2} to the resource-dir to customize).
+message.using-custom-resource-from-file=Using custom package resource {0} (loaded from file {1}).
+message.using-custom-resource=Using custom package resource {0} (loaded from {1}).
+message.creating-app-bundle=Creating app package: {0} in {1}
+message.app-image-dir-does-not-exist=Specified application image directory {0}: {1} does not exists
+message.app-image-dir-does-not-exist.advice=Confirm that the value for {0} exists
+message.runtime-image-dir-does-not-exist=Specified runtime image directory {0}: {1} does not exists
+message.runtime-image-dir-does-not-exist.advice=Confirm that the value for {0} exists
+message.debug-working-directory=Kept working directory for debug: {0}
+message.bundle-created=Succeeded in building {0} package
+message.module-version=Using version "{0}" from module "{1}" as application version
+message.module-class=Using class "{0}" from module "{1}" as application main class
+
+error.cannot-create-output-dir=Destination directory {0} cannot be created
+error.cannot-write-to-output-dir=Destination directory {0} is not writable
+error.root-exists=Error: Application destination directory {0} already exists
+error.no-main-class-with-main-jar=A main class was not specified nor was one found in the jar {0}
+error.no-main-class-with-main-jar.advice=Specify a main class or ensure that the jar {0} specifies one in the manifest
+error.no-main-class=A main class was not specified nor was one found in the supplied application resources
+error.no-main-class.advice=Please specify a application class or ensure that the appResources has a jar containing one in the manifest
+error.main-jar-does-not-exist=The configured main jar does not exist {0} in the input directory
+error.main-jar-does-not-exist.advice=The main jar must be specified relative to the input directory (not an absolute path), and must exist within that directory
+
+error.tool-not-found=Can not find {0}. Reason: {1}
+error.tool-not-found.advice=Please install {0}
+error.tool-old-version=Can not find {0} {1} or newer
+error.tool-old-version.advice=Please install {0} {1} or newer
+error.jlink.failed=jlink failed with: {0}
+
+warning.module.does.not.exist=Module [{0}] does not exist
+warning.no.jdk.modules.found=Warning: No JDK Modules found
+
+MSG_BundlerFailed=Error: Bundler "{1}" ({0}) failed to produce a package
+MSG_BundlerConfigException=Bundler {0} skipped because of a configuration problem: {1} \n\
+Advice to fix: {2}
+MSG_BundlerConfigExceptionNoAdvice=Bundler {0} skipped because of a configuration problem: {1}
+MSG_BundlerRuntimeException=Bundler {0} failed because of {1}
+MSG_BundlerFailed=Error: Bundler "{1}" ({0}) failed to produce a package
+
+ERR_NoMainClass=Error: Main application class is missing
+ERR_UnsupportedOption=Error: Option [{0}] is not valid on this platform
+ERR_InvalidTypeOption=Error: Option [{0}] is not valid with type [{1}]
+ERR_NoInstallerEntryPoint=Error: Option [{0}] is not valid without --module or --main-jar entry point option
+
+ERR_MissingArgument=Error: Missing argument: {0}
+ERR_MissingAppResources=Error: No application jars found
+ERR_AppImageNotExist=Error: App image directory "{0}" does not exist
+ERR_NoAddLauncherName=Error: --add-launcher option requires a name and a file path (--add-launcher <name>=<file path>)
+ERR_NoUniqueName=Error: --add-launcher <name>=<file path> requires a unique name
+ERR_NoJreInstallerName=Error: Jre Installers require a name parameter
+ERR_InvalidAppName=Error: Invalid Application name: {0}
+ERR_InvalidSLName=Error: Invalid Add Launcher name: {0}
+ERR_LicenseFileNotExit=Error: Specified license file does not exist
+ERR_BuildRootInvalid=Error: temp ({0}) must be non-existant or empty directory
+ERR_InvalidOption=Error: Invalid Option: [{0}]
+ERR_InvalidInstallerType=Error: Invalid or unsupported type: [{0}]
+ERR_BothMainJarAndModule=Error: Cannot have both --main-jar and --module Options
+ERR_NoEntryPoint=Error: creating application image requires --main-jar or --module Option
+ERR_InputNotDirectory=Error: Input directory specified is not a directory: {0}
+ERR_CannotReadInputDir=Error: No permission to read from input directory: {0}
+ERR_CannotParseOptions=Error: Processing @filename option: {0}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/resources/MainResources_zh_CN.properties Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,92 @@
+#
+# Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+#
+
+param.copyright.default=Copyright (C) {0,date,YYYY}
+param.description.default=None
+param.vendor.default=Unknown
+
+message.using-default-resource=Using default package resource {0} {1} (add {2} to the resource-dir to customize).
+message.no-default-resource=no default package resource {0} {1} (add {2} to the resource-dir to customize).
+message.using-custom-resource-from-file=Using custom package resource {0} (loaded from file {1}).
+message.using-custom-resource=Using custom package resource {0} (loaded from {1}).
+message.creating-app-bundle=Creating app package: {0} in {1}
+message.app-image-dir-does-not-exist=Specified application image directory {0}: {1} does not exists
+message.app-image-dir-does-not-exist.advice=Confirm that the value for {0} exists
+message.runtime-image-dir-does-not-exist=Specified runtime image directory {0}: {1} does not exists
+message.runtime-image-dir-does-not-exist.advice=Confirm that the value for {0} exists
+message.debug-working-directory=Kept working directory for debug: {0}
+message.bundle-created=Succeeded in building {0} package
+message.module-version=Using version "{0}" from module "{1}" as application version
+message.module-class=Using class "{0}" from module "{1}" as application main class
+
+error.cannot-create-output-dir=Destination directory {0} cannot be created
+error.cannot-write-to-output-dir=Destination directory {0} is not writable
+error.root-exists=Error: Application destination directory {0} already exists
+error.no-main-class-with-main-jar=A main class was not specified nor was one found in the jar {0}
+error.no-main-class-with-main-jar.advice=Specify a main class or ensure that the jar {0} specifies one in the manifest
+error.no-main-class=A main class was not specified nor was one found in the supplied application resources
+error.no-main-class.advice=Please specify a application class or ensure that the appResources has a jar containing one in the manifest
+error.main-jar-does-not-exist=The configured main jar does not exist {0} in the input directory
+error.main-jar-does-not-exist.advice=The main jar must be specified relative to the input directory (not an absolute path), and must exist within that directory
+
+error.tool-not-found=Can not find {0}. Reason: {1}
+error.tool-not-found.advice=Please install {0}
+error.tool-old-version=Can not find {0} {1} or newer
+error.tool-old-version.advice=Please install {0} {1} or newer
+error.jlink.failed=jlink failed with: {0}
+
+warning.module.does.not.exist=Module [{0}] does not exist
+warning.no.jdk.modules.found=Warning: No JDK Modules found
+
+MSG_BundlerFailed=Error: Bundler "{1}" ({0}) failed to produce a package
+MSG_BundlerConfigException=Bundler {0} skipped because of a configuration problem: {1} \n\
+Advice to fix: {2}
+MSG_BundlerConfigExceptionNoAdvice=Bundler {0} skipped because of a configuration problem: {1}
+MSG_BundlerRuntimeException=Bundler {0} failed because of {1}
+MSG_BundlerFailed=Error: Bundler "{1}" ({0}) failed to produce a package
+
+ERR_NoMainClass=Error: Main application class is missing
+ERR_UnsupportedOption=Error: Option [{0}] is not valid on this platform
+ERR_InvalidTypeOption=Error: Option [{0}] is not valid with type [{1}]
+ERR_NoInstallerEntryPoint=Error: Option [{0}] is not valid without --module or --main-jar entry point option
+
+ERR_MissingArgument=Error: Missing argument: {0}
+ERR_MissingAppResources=Error: No application jars found
+ERR_AppImageNotExist=Error: App image directory "{0}" does not exist
+ERR_NoAddLauncherName=Error: --add-launcher option requires a name and a file path (--add-launcher <name>=<file path>)
+ERR_NoUniqueName=Error: --add-launcher <name>=<file path> requires a unique name
+ERR_NoJreInstallerName=Error: Jre Installers require a name parameter
+ERR_InvalidAppName=Error: Invalid Application name: {0}
+ERR_InvalidSLName=Error: Invalid Add Launcher name: {0}
+ERR_LicenseFileNotExit=Error: Specified license file does not exist
+ERR_BuildRootInvalid=Error: temp ({0}) must be non-existant or empty directory
+ERR_InvalidOption=Error: Invalid Option: [{0}]
+ERR_InvalidInstallerType=Error: Invalid or unsupported type: [{0}]
+ERR_BothMainJarAndModule=Error: Cannot have both --main-jar and --module Options
+ERR_NoEntryPoint=Error: creating application image requires --main-jar or --module Option
+ERR_InputNotDirectory=Error: Input directory specified is not a directory: {0}
+ERR_CannotReadInputDir=Error: No permission to read from input directory: {0}
+ERR_CannotParseOptions=Error: Processing @filename option: {0}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/resources/ResourceLocator.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal.resources;
+
+/*
+ * ResourceLocator
+ * This empty class is the only class in this package. Otherwise the
+ * package consists only of resources. ResourceLocator is needed in order
+ * to call getResourceAsStream() to get those resources.
+ */
+
+public class ResourceLocator {
+ public ResourceLocator() {
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/main/CommandLine.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,214 @@
+/*
+ * Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.main;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.File;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This file was originally a copy of CommandLine.java in
+ * com.sun.tools.javac.main.
+ * It should track changes made to that file.
+ */
+
+/**
+ * Various utility methods for processing Java tool command line arguments.
+ *
+ * <p><b>This is NOT part of any supported API.
+ * If you write code that depends on this, you do so at your own risk.
+ * This code and its internal interfaces are subject to change or
+ * deletion without notice.</b>
+ */
+class CommandLine {
+ /**
+ * Process Win32-style command files for the specified command line
+ * arguments and return the resulting arguments. A command file argument
+ * is of the form '@file' where 'file' is the name of the file whose
+ * contents are to be parsed for additional arguments. The contents of
+ * the command file are parsed using StreamTokenizer and the original
+ * '@file' argument replaced with the resulting tokens. Recursive command
+ * files are not supported. The '@' character itself can be quoted with
+ * the sequence '@@'.
+ * @param args the arguments that may contain @files
+ * @return the arguments, with @files expanded
+ * @throws IOException if there is a problem reading any of the @files
+ */
+ public static String[] parse(String[] args) throws IOException {
+ List<String> newArgs = new ArrayList<>();
+ appendParsedCommandArgs(newArgs, Arrays.asList(args));
+ return newArgs.toArray(new String[newArgs.size()]);
+ }
+
+ private static void appendParsedCommandArgs(List<String> newArgs,
+ List<String> args) throws IOException {
+ for (String arg : args) {
+ if (arg.length() > 1 && arg.charAt(0) == '@') {
+ arg = arg.substring(1);
+ if (arg.charAt(0) == '@') {
+ newArgs.add(arg);
+ } else {
+ loadCmdFile(arg, newArgs);
+ }
+ } else {
+ newArgs.add(arg);
+ }
+ }
+ }
+
+ private static void loadCmdFile(String name, List<String> args)
+ throws IOException {
+ if (!Files.isReadable(Path.of(name))) {
+ throw new FileNotFoundException(name);
+ }
+ try (Reader r = Files.newBufferedReader(Paths.get(name),
+ Charset.defaultCharset())) {
+ Tokenizer t = new Tokenizer(r);
+ String s;
+ while ((s = t.nextToken()) != null) {
+ args.add(s);
+ }
+ }
+ }
+
+ public static class Tokenizer {
+ private final Reader in;
+ private int ch;
+
+ public Tokenizer(Reader in) throws IOException {
+ this.in = in;
+ ch = in.read();
+ }
+
+ public String nextToken() throws IOException {
+ skipWhite();
+ if (ch == -1) {
+ return null;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ char quoteChar = 0;
+
+ while (ch != -1) {
+ switch (ch) {
+ case ' ':
+ case '\t':
+ case '\f':
+ if (quoteChar == 0) {
+ return sb.toString();
+ }
+ sb.append((char) ch);
+ break;
+
+ case '\n':
+ case '\r':
+ return sb.toString();
+
+ case '\'':
+ case '"':
+ if (quoteChar == 0) {
+ quoteChar = (char) ch;
+ } else if (quoteChar == ch) {
+ quoteChar = 0;
+ } else {
+ sb.append((char) ch);
+ }
+ break;
+
+ case '\\':
+ if (quoteChar != 0) {
+ ch = in.read();
+ switch (ch) {
+ case '\n':
+ case '\r':
+ while (ch == ' ' || ch == '\n'
+ || ch == '\r' || ch == '\t'
+ || ch == '\f') {
+ ch = in.read();
+ }
+ continue;
+
+ case 'n':
+ ch = '\n';
+ break;
+ case 'r':
+ ch = '\r';
+ break;
+ case 't':
+ ch = '\t';
+ break;
+ case 'f':
+ ch = '\f';
+ break;
+ }
+ }
+ sb.append((char) ch);
+ break;
+
+ default:
+ sb.append((char) ch);
+ }
+
+ ch = in.read();
+ }
+
+ return sb.toString();
+ }
+
+ void skipWhite() throws IOException {
+ while (ch != -1) {
+ switch (ch) {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\r':
+ case '\f':
+ break;
+
+ case '#':
+ ch = in.read();
+ while (ch != '\n' && ch != '\r' && ch != -1) {
+ ch = in.read();
+ }
+ break;
+
+ default:
+ return;
+ }
+
+ ch = in.read();
+ }
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/main/Main.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.main;
+
+import jdk.incubator.jpackage.internal.Arguments;
+import jdk.incubator.jpackage.internal.Log;
+import jdk.incubator.jpackage.internal.CLIHelp;
+import java.io.PrintWriter;
+import java.util.ResourceBundle;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.text.MessageFormat;
+
+public class Main {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.incubator.jpackage.internal.resources.MainResources");
+
+ /**
+ * main(String... args)
+ * This is the entry point for the jpackage tool.
+ *
+ * @param args command line arguments
+ */
+ public static void main(String... args) throws Exception {
+ // Create logger with default system.out and system.err
+ Log.setLogger(null);
+
+ int status = new jdk.incubator.jpackage.main.Main().execute(args);
+ System.exit(status);
+ }
+
+ /**
+ * execute() - this is the entry point for the ToolProvider API.
+ *
+ * @param out output stream
+ * @param err error output stream
+ * @param args command line arguments
+ * @return an exit code. 0 means success, non-zero means an error occurred.
+ */
+ public int execute(PrintWriter out, PrintWriter err, String... args) {
+ // Create logger with provided streams
+ Log.Logger logger = new Log.Logger();
+ logger.setPrintWriter(out, err);
+ Log.setLogger(logger);
+
+ return execute(args);
+ }
+
+ private int execute(String... args) {
+ try {
+ String[] newArgs;
+ try {
+ newArgs = CommandLine.parse(args);
+ } catch (FileNotFoundException fnfe) {
+ Log.error(MessageFormat.format(I18N.getString(
+ "ERR_CannotParseOptions"), fnfe.getMessage()));
+ return 1;
+ } catch (IOException ioe) {
+ Log.error(ioe.getMessage());
+ return 1;
+ }
+
+ if (newArgs.length == 0) {
+ CLIHelp.showHelp(true);
+ } else if (hasHelp(newArgs)){
+ if (hasVersion(newArgs)) {
+ Log.info(System.getProperty("java.version") + "\n");
+ }
+ CLIHelp.showHelp(false);
+ } else if (hasVersion(newArgs)) {
+ Log.info(System.getProperty("java.version"));
+ } else {
+ Arguments arguments = new Arguments(newArgs);
+ if (!arguments.processArguments()) {
+ // processArguments() will log error message if failed.
+ return 1;
+ }
+ }
+ return 0;
+ } finally {
+ Log.flush();
+ }
+ }
+
+ private boolean hasHelp(String[] args) {
+ for (String a : args) {
+ if ("--help".equals(a) || "-h".equals(a)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean hasVersion(String[] args) {
+ for (String a : args) {
+ if ("--version".equals(a)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/module-info.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * Defines the Java Packaging tool, jpackage.
+ *
+ * <p>jpackage is a tool for generating self-contained application bundles.
+ *
+ * @moduleGraph
+ * @since 14
+ */
+
+module jdk.incubator.jpackage {
+ requires jdk.jlink;
+
+ requires java.desktop;
+
+ uses jdk.incubator.jpackage.internal.Bundler;
+ uses jdk.incubator.jpackage.internal.Bundlers;
+
+ provides jdk.incubator.jpackage.internal.Bundlers with
+ jdk.incubator.jpackage.internal.BasicBundlers;
+
+/*
+ * while jpackage is incubating, do not provide ToolProvider directly, but
+ * instead export jdk.incubator.jpackage.ToolProviderFactory
+ *
+ * provides java.util.spi.ToolProvider
+ * with jdk.incubator.jpackage.internal.JPackageToolProvider;
+ */
+ exports jdk.incubator.jpackage;
+
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/native/libapplauncher/FileAttributes.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef FILEATTRIBUTES_H
+#define FILEATTRIBUTES_H
+
+#include "Platform.h"
+#include "PlatformString.h"
+#include "FileAttribute.h"
+
+#include <vector>
+
+class FileAttributes {
+private:
+ TString FFileName;
+ bool FFollowLink;
+ std::vector<FileAttribute> FAttributes;
+
+ bool WriteAttributes();
+ bool ReadAttributes();
+ bool Valid(const FileAttribute Value);
+
+public:
+ FileAttributes(const TString FileName, bool FollowLink = true);
+
+ void Append(const FileAttribute Value);
+ bool Contains(const FileAttribute Value);
+ void Remove(const FileAttribute Value);
+};
+
+#endif // FILEATTRIBUTES_H
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/native/libapplauncher/FilePath.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef FILEPATH_H
+#define FILEPATH_H
+
+#include "Platform.h"
+#include "PlatformString.h"
+#include "FileAttribute.h"
+
+#include <vector>
+
+class FileAttributes {
+private:
+ TString FFileName;
+ bool FFollowLink;
+ std::vector<FileAttribute> FAttributes;
+
+ bool WriteAttributes();
+ bool ReadAttributes();
+ bool Valid(const FileAttribute Value);
+
+public:
+ FileAttributes(const TString FileName, bool FollowLink = true);
+
+ void Append(const FileAttribute Value);
+ bool Contains(const FileAttribute Value);
+ void Remove(const FileAttribute Value);
+};
+
+class FilePath {
+private:
+ FilePath(void) {}
+ ~FilePath(void) {}
+
+public:
+ static bool FileExists(const TString FileName);
+ static bool DirectoryExists(const TString DirectoryName);
+
+ static bool DeleteFile(const TString FileName);
+ static bool DeleteDirectory(const TString DirectoryName);
+
+ static TString ExtractFilePath(TString Path);
+ static TString ExtractFileExt(TString Path);
+ static TString ExtractFileName(TString Path);
+ static TString ChangeFileExt(TString Path, TString Extension);
+
+ static TString IncludeTrailingSeparator(const TString value);
+ static TString IncludeTrailingSeparator(const char* value);
+ static TString IncludeTrailingSeparator(const wchar_t* value);
+ static TString FixPathForPlatform(TString Path);
+ static TString FixPathSeparatorForPlatform(TString Path);
+ static TString PathSeparator();
+
+ static bool CreateDirectory(TString Path, bool ownerOnly);
+ static void ChangePermissions(TString FileName, bool ownerOnly);
+};
+
+#endif //FILEPATH_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/native/libapplauncher/Helpers.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,240 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "Helpers.h"
+#include "PlatformString.h"
+#include "PropertyFile.h"
+
+
+bool Helpers::SplitOptionIntoNameValue(
+ TString option, TString& Name, TString& Value) {
+ bool hasValue = false;
+ Name = _T("");
+ Value = _T("");
+ unsigned int index = 0;
+
+ for (; index < option.length(); index++) {
+ TCHAR c = option[index];
+
+ switch (c) {
+ case '=': {
+ index++;
+ hasValue = true;
+ break;
+ }
+
+ case '\\': {
+ if (index + 1 < option.length()) {
+ c = option[index + 1];
+
+ switch (c) {
+ case '\\': {
+ index++;
+ Name += '\\';
+ break;
+ }
+
+ case '=': {
+ index++;
+ Name += '=';
+ break;
+ }
+ }
+
+ }
+
+ continue;
+ }
+
+ default: {
+ Name += c;
+ continue;
+ }
+ }
+
+ break;
+ }
+
+ if (hasValue) {
+ Value = option.substr(index, index - option.length());
+ }
+
+ return (option.length() > 0);
+}
+
+
+TString Helpers::ReplaceString(TString subject, const TString& search,
+ const TString& replace) {
+ size_t pos = 0;
+ while((pos = subject.find(search, pos)) != TString::npos) {
+ subject.replace(pos, search.length(), replace);
+ pos += replace.length();
+ }
+ return subject;
+}
+
+TString Helpers::ConvertIdToFilePath(TString Value) {
+ TString search;
+ search = '.';
+ TString replace;
+ replace = '/';
+ TString result = ReplaceString(Value, search, replace);
+ return result;
+}
+
+TString Helpers::ConvertIdToJavaPath(TString Value) {
+ TString search;
+ search = '.';
+ TString replace;
+ replace = '/';
+ TString result = ReplaceString(Value, search, replace);
+ search = '\\';
+ result = ReplaceString(result, search, replace);
+ return result;
+}
+
+TString Helpers::ConvertJavaPathToId(TString Value) {
+ TString search;
+ search = '/';
+ TString replace;
+ replace = '.';
+ TString result = ReplaceString(Value, search, replace);
+ return result;
+}
+
+OrderedMap<TString, TString>
+ Helpers::GetJavaOptionsFromConfig(IPropertyContainer* config) {
+ OrderedMap<TString, TString> result;
+
+ for (unsigned int index = 0; index < config->GetCount(); index++) {
+ TString argname =
+ TString(_T("jvmarg.")) + PlatformString(index + 1).toString();
+ TString argvalue;
+
+ if (config->GetValue(argname, argvalue) == false) {
+ break;
+ }
+ else if (argvalue.empty() == false) {
+ TString name;
+ TString value;
+ if (Helpers::SplitOptionIntoNameValue(argvalue, name, value)) {
+ result.Append(name, value);
+ }
+ }
+ }
+
+ return result;
+}
+
+std::list<TString> Helpers::GetArgsFromConfig(IPropertyContainer* config) {
+ std::list<TString> result;
+
+ for (unsigned int index = 0; index < config->GetCount(); index++) {
+ TString argname = TString(_T("arg."))
+ + PlatformString(index + 1).toString();
+ TString argvalue;
+
+ if (config->GetValue(argname, argvalue) == false) {
+ break;
+ }
+ else if (argvalue.empty() == false) {
+ result.push_back((argvalue));
+ }
+ }
+
+ return result;
+}
+
+std::list<TString>
+ Helpers::MapToNameValueList(OrderedMap<TString, TString> Map) {
+ std::list<TString> result;
+ std::vector<TString> keys = Map.GetKeys();
+
+ for (OrderedMap<TString, TString>::const_iterator iterator = Map.begin();
+ iterator != Map.end(); iterator++) {
+ JPPair<TString, TString> *item = *iterator;
+ TString key = item->first;
+ TString value = item->second;
+
+ if (value.length() == 0) {
+ result.push_back(key);
+ } else {
+ result.push_back(key + _T('=') + value);
+ }
+ }
+
+ return result;
+}
+
+TString Helpers::NameValueToString(TString name, TString value) {
+ TString result;
+
+ if (value.empty() == true) {
+ result = name;
+ }
+ else {
+ result = name + TString(_T("=")) + value;
+ }
+
+ return result;
+}
+
+std::list<TString> Helpers::StringToArray(TString Value) {
+ std::list<TString> result;
+ TString line;
+
+ for (unsigned int index = 0; index < Value.length(); index++) {
+ TCHAR c = Value[index];
+
+ switch (c) {
+ case '\n': {
+ result.push_back(line);
+ line = _T("");
+ break;
+ }
+
+ case '\r': {
+ result.push_back(line);
+ line = _T("");
+
+ if (Value[index + 1] == '\n')
+ index++;
+
+ break;
+ }
+
+ default: {
+ line += c;
+ }
+ }
+ }
+
+ // The buffer may not have ended with a Carriage Return/Line Feed.
+ if (line.length() > 0) {
+ result.push_back(line);
+ }
+
+ return result;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/native/libapplauncher/Helpers.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef HELPERS_H
+#define HELPERS_H
+
+#include "Platform.h"
+#include "OrderedMap.h"
+#include "IniFile.h"
+
+
+class Helpers {
+private:
+ Helpers(void) {}
+ ~Helpers(void) {}
+
+public:
+ // Supports two formats for option:
+ // Example 1:
+ // foo=bar
+ //
+ // Example 2:
+ // <name=foo=, value=goo>
+ static bool SplitOptionIntoNameValue(TString option,
+ TString& Name, TString& Value);
+ static TString ReplaceString(TString subject, const TString& search,
+ const TString& replace);
+ static TString ConvertIdToFilePath(TString Value);
+ static TString ConvertIdToJavaPath(TString Value);
+ static TString ConvertJavaPathToId(TString Value);
+
+ static OrderedMap<TString, TString>
+ GetJavaOptionsFromConfig(IPropertyContainer* config);
+ static std::list<TString> GetArgsFromConfig(IPropertyContainer* config);
+
+ static std::list<TString>
+ MapToNameValueList(OrderedMap<TString, TString> Map);
+
+ static TString NameValueToString(TString name, TString value);
+
+ static std::list<TString> StringToArray(TString Value);
+};
+
+#endif // HELPERS_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/native/libapplauncher/IniFile.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,261 @@
+/*
+ * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "IniFile.h"
+#include "Helpers.h"
+
+#include <string>
+
+
+IniFile::IniFile() : ISectionalPropertyContainer() {
+}
+
+IniFile::~IniFile() {
+ for (OrderedMap<TString, IniSectionData*>::iterator iterator =
+ FMap.begin(); iterator != FMap.end(); iterator++) {
+ JPPair<TString, IniSectionData*> *item = *iterator;
+ delete item->second;
+ }
+}
+
+bool IniFile::LoadFromFile(const TString FileName) {
+ bool result = false;
+ Platform& platform = Platform::GetInstance();
+
+ std::list<TString> contents = platform.LoadFromFile(FileName);
+
+ if (contents.empty() == false) {
+ bool found = false;
+
+ // Determine the if file is an INI file or property file.
+ // Assign FDefaultSection if it is
+ // an INI file. Otherwise FDefaultSection is NULL.
+ for (std::list<TString>::const_iterator iterator = contents.begin();
+ iterator != contents.end(); iterator++) {
+ TString line = *iterator;
+
+ if (line[0] == ';') {
+ // Semicolon is a comment so ignore the line.
+ continue;
+ }
+ else {
+ if (line[0] == '[') {
+ found = true;
+ }
+
+ break;
+ }
+ }
+
+ if (found == true) {
+ TString sectionName;
+
+ for (std::list<TString>::const_iterator iterator = contents.begin();
+ iterator != contents.end(); iterator++) {
+ TString line = *iterator;
+
+ if (line[0] == ';') {
+ // Semicolon is a comment so ignore the line.
+ continue;
+ }
+ else if (line[0] == '[' && line[line.length() - 1] == ']') {
+ sectionName = line.substr(1, line.size() - 2);
+ }
+ else if (sectionName.empty() == false) {
+ TString name;
+ TString value;
+
+ if (Helpers::SplitOptionIntoNameValue(
+ line, name, value) == true) {
+ Append(sectionName, name, value);
+ }
+ }
+ }
+
+ result = true;
+ }
+ }
+
+ return result;
+}
+
+bool IniFile::SaveToFile(const TString FileName, bool ownerOnly) {
+ bool result = false;
+
+ std::list<TString> contents;
+ std::vector<TString> keys = FMap.GetKeys();
+
+ for (unsigned int index = 0; index < keys.size(); index++) {
+ TString name = keys[index];
+ IniSectionData *section;
+
+ if (FMap.GetValue(name, section) == true) {
+ contents.push_back(_T("[") + name + _T("]"));
+ std::list<TString> lines = section->GetLines();
+ contents.insert(contents.end(), lines.begin(), lines.end());
+ contents.push_back(_T(""));
+ }
+ }
+
+ Platform& platform = Platform::GetInstance();
+ platform.SaveToFile(FileName, contents, ownerOnly);
+ result = true;
+ return result;
+}
+
+void IniFile::Append(const TString SectionName,
+ const TString Key, TString Value) {
+ if (FMap.ContainsKey(SectionName) == true) {
+ IniSectionData* section;
+
+ if (FMap.GetValue(SectionName, section) == true && section != NULL) {
+ section->SetValue(Key, Value);
+ }
+ }
+ else {
+ IniSectionData *section = new IniSectionData();
+ section->SetValue(Key, Value);
+ FMap.Append(SectionName, section);
+ }
+}
+
+void IniFile::AppendSection(const TString SectionName,
+ OrderedMap<TString, TString> Values) {
+ if (FMap.ContainsKey(SectionName) == true) {
+ IniSectionData* section;
+
+ if (FMap.GetValue(SectionName, section) == true && section != NULL) {
+ section->Append(Values);
+ }
+ }
+ else {
+ IniSectionData *section = new IniSectionData(Values);
+ FMap.Append(SectionName, section);
+ }
+}
+
+bool IniFile::GetValue(const TString SectionName,
+ const TString Key, TString& Value) {
+ bool result = false;
+ IniSectionData* section;
+
+ if (FMap.GetValue(SectionName, section) == true && section != NULL) {
+ result = section->GetValue(Key, Value);
+ }
+
+ return result;
+}
+
+bool IniFile::SetValue(const TString SectionName,
+ const TString Key, TString Value) {
+ bool result = false;
+ IniSectionData* section;
+
+ if (FMap.GetValue(SectionName, section) && section != NULL) {
+ result = section->SetValue(Key, Value);
+ }
+ else {
+ Append(SectionName, Key, Value);
+ }
+
+
+ return result;
+}
+
+bool IniFile::GetSection(const TString SectionName,
+ OrderedMap<TString, TString> &Data) {
+ bool result = false;
+
+ if (FMap.ContainsKey(SectionName) == true) {
+ IniSectionData* section = NULL;
+
+ if (FMap.GetValue(SectionName, section) == true && section != NULL) {
+ OrderedMap<TString, TString> data = section->GetData();
+ Data.Append(data);
+ result = true;
+ }
+ }
+
+ return result;
+}
+
+bool IniFile::ContainsSection(const TString SectionName) {
+ return FMap.ContainsKey(SectionName);
+}
+
+//----------------------------------------------------------------------------
+
+IniSectionData::IniSectionData() {
+ FMap.SetAllowDuplicates(true);
+}
+
+IniSectionData::IniSectionData(OrderedMap<TString, TString> Values) {
+ FMap = Values;
+}
+
+std::vector<TString> IniSectionData::GetKeys() {
+ return FMap.GetKeys();
+}
+
+std::list<TString> IniSectionData::GetLines() {
+ std::list<TString> result;
+ std::vector<TString> keys = FMap.GetKeys();
+
+ for (unsigned int index = 0; index < keys.size(); index++) {
+ TString name = keys[index];
+ TString value;
+
+ if (FMap.GetValue(name, value) == true) {
+ name = Helpers::ReplaceString(name, _T("="), _T("\\="));
+ value = Helpers::ReplaceString(value, _T("="), _T("\\="));
+
+ TString line = name + _T('=') + value;
+ result.push_back(line);
+ }
+ }
+
+ return result;
+}
+
+OrderedMap<TString, TString> IniSectionData::GetData() {
+ OrderedMap<TString, TString> result = FMap;
+ return result;
+}
+
+bool IniSectionData::GetValue(const TString Key, TString& Value) {
+ return FMap.GetValue(Key, Value);
+}
+
+bool IniSectionData::SetValue(const TString Key, TString Value) {
+ return FMap.SetValue(Key, Value);
+}
+
+void IniSectionData::Append(OrderedMap<TString, TString> Values) {
+ FMap.Append(Values);
+}
+
+size_t IniSectionData::GetCount() {
+ return FMap.Count();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/native/libapplauncher/IniFile.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef INIFILE_H
+#define INIFILE_H
+
+#include "Platform.h"
+#include "OrderedMap.h"
+
+#include <map>
+
+
+class IniSectionData : public IPropertyContainer {
+private:
+ OrderedMap<TString, TString> FMap;
+
+public:
+ IniSectionData();
+ IniSectionData(OrderedMap<TString, TString> Values);
+
+ std::vector<TString> GetKeys();
+ std::list<TString> GetLines();
+ OrderedMap<TString, TString> GetData();
+
+ bool SetValue(const TString Key, TString Value);
+ void Append(OrderedMap<TString, TString> Values);
+
+ virtual bool GetValue(const TString Key, TString& Value);
+ virtual size_t GetCount();
+};
+
+
+class IniFile : public ISectionalPropertyContainer {
+private:
+ OrderedMap<TString, IniSectionData*> FMap;
+
+public:
+ IniFile();
+ virtual ~IniFile();
+
+ void internalTest();
+
+ bool LoadFromFile(const TString FileName);
+ bool SaveToFile(const TString FileName, bool ownerOnly = true);
+
+ void Append(const TString SectionName, const TString Key, TString Value);
+ void AppendSection(const TString SectionName,
+ OrderedMap<TString, TString> Values);
+ bool SetValue(const TString SectionName,
+ const TString Key, TString Value);
+
+ // ISectionalPropertyContainer
+ virtual bool GetSection(const TString SectionName,
+ OrderedMap<TString, TString> &Data);
+ virtual bool ContainsSection(const TString SectionName);
+ virtual bool GetValue(const TString SectionName,
+ const TString Key, TString& Value);
+};
+
+#endif // INIFILE_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/native/libapplauncher/JavaVirtualMachine.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,318 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "JavaVirtualMachine.h"
+#include "Platform.h"
+#include "PlatformString.h"
+#include "FilePath.h"
+#include "Package.h"
+#include "Helpers.h"
+#include "Messages.h"
+#include "Macros.h"
+
+#include "jni.h"
+
+#include <map>
+#include <list>
+#include <sstream>
+
+
+bool RunVM() {
+ JavaVirtualMachine javavm;
+
+ bool result = javavm.StartJVM();
+
+ if (!result) {
+ Platform& platform = Platform::GetInstance();
+ platform.ShowMessage(_T("Failed to launch JVM\n"));
+ }
+
+ return result;
+}
+
+//----------------------------------------------------------------------------
+
+JavaOptions::JavaOptions(): FOptions(NULL) {
+}
+
+JavaOptions::~JavaOptions() {
+ if (FOptions != NULL) {
+ for (unsigned int index = 0; index < GetCount(); index++) {
+ delete[] FOptions[index].optionString;
+ }
+
+ delete[] FOptions;
+ }
+}
+
+void JavaOptions::AppendValue(const TString Key, TString Value, void* Extra) {
+ JavaOptionItem item;
+ item.name = Key;
+ item.value = Value;
+ item.extraInfo = Extra;
+ FItems.push_back(item);
+}
+
+void JavaOptions::AppendValue(const TString Key, TString Value) {
+ AppendValue(Key, Value, NULL);
+}
+
+void JavaOptions::AppendValue(const TString Key) {
+ AppendValue(Key, _T(""), NULL);
+}
+
+void JavaOptions::AppendValues(OrderedMap<TString, TString> Values) {
+ if (Values.GetAllowDuplicates()) {
+ for (int i = 0; i < (int)Values.Count(); i++) {
+ TString name, value;
+
+ bool bResult = Values.GetKey(i, name);
+ bResult &= Values.GetValue(i, value);
+
+ if (bResult) {
+ AppendValue(name, value);
+ }
+ }
+ } else { // In case we asked to add values from OrderedMap with allow
+ // duplicates set to false. Not used now, but should avoid possible
+ // bugs.
+ std::vector<TString> orderedKeys = Values.GetKeys();
+
+ for (std::vector<TString>::const_iterator iterator = orderedKeys.begin();
+ iterator != orderedKeys.end(); iterator++) {
+ TString name = *iterator;
+ TString value;
+
+ if (Values.GetValue(name, value) == true) {
+ AppendValue(name, value);
+ }
+ }
+ }
+}
+
+void JavaOptions::ReplaceValue(const TString Key, TString Value) {
+ for (std::list<JavaOptionItem>::iterator iterator = FItems.begin();
+ iterator != FItems.end(); iterator++) {
+
+ TString lkey = iterator->name;
+
+ if (lkey == Key) {
+ JavaOptionItem item = *iterator;
+ item.value = Value;
+ iterator = FItems.erase(iterator);
+ FItems.insert(iterator, item);
+ break;
+ }
+ }
+}
+
+std::list<TString> JavaOptions::ToList() {
+ std::list<TString> result;
+ Macros& macros = Macros::GetInstance();
+
+ for (std::list<JavaOptionItem>::const_iterator iterator = FItems.begin();
+ iterator != FItems.end(); iterator++) {
+ TString key = iterator->name;
+ TString value = iterator->value;
+ TString option = Helpers::NameValueToString(key, value);
+ option = macros.ExpandMacros(option);
+ result.push_back(option);
+ }
+
+ return result;
+}
+
+size_t JavaOptions::GetCount() {
+ return FItems.size();
+}
+
+//----------------------------------------------------------------------------
+
+JavaVirtualMachine::JavaVirtualMachine() {
+}
+
+JavaVirtualMachine::~JavaVirtualMachine(void) {
+}
+
+bool JavaVirtualMachine::StartJVM() {
+ Platform& platform = Platform::GetInstance();
+ Package& package = Package::GetInstance();
+
+ TString classpath = package.GetClassPath();
+ TString modulepath = package.GetModulePath();
+ JavaOptions options;
+
+ if (modulepath.empty() == false) {
+ options.AppendValue(_T("-Djava.module.path"), modulepath);
+ }
+
+ options.AppendValue(_T("-Djava.library.path"),
+ package.GetPackageAppDirectory() + FilePath::PathSeparator()
+ + package.GetPackageLauncherDirectory());
+ options.AppendValue(
+ _T("-Djava.launcher.path"), package.GetPackageLauncherDirectory());
+ options.AppendValues(package.GetJavaOptions());
+
+#ifdef DEBUG
+ if (package.Debugging() == dsJava) {
+ options.AppendValue(_T("-Xdebug"), _T(""));
+ options.AppendValue(
+ _T("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=localhost:5005"),
+ _T(""));
+ platform.ShowMessage(_T("localhost:5005"));
+ }
+#endif // DEBUG
+
+ TString maxHeapSizeOption;
+ TString minHeapSizeOption;
+
+
+ if (package.GetMemoryState() == PackageBootFields::msAuto) {
+ TPlatformNumber memorySize = package.GetMemorySize();
+ TString memory =
+ PlatformString((size_t)memorySize).toString() + _T("m");
+ maxHeapSizeOption = TString(_T("-Xmx")) + memory;
+ options.AppendValue(maxHeapSizeOption, _T(""));
+
+ if (memorySize > 256)
+ minHeapSizeOption = _T("-Xms256m");
+ else
+ minHeapSizeOption = _T("-Xms") + memory;
+
+ options.AppendValue(minHeapSizeOption, _T(""));
+ }
+
+ TString mainClassName = package.GetMainClassName();
+ TString mainModule = package.GetMainModule();
+
+ if (mainClassName.empty() == true && mainModule.empty() == true) {
+ Messages& messages = Messages::GetInstance();
+ platform.ShowMessage(messages.GetMessage(NO_MAIN_CLASS_SPECIFIED));
+ return false;
+ }
+
+ configureLibrary();
+
+ // Initialize the arguments to JLI_Launch()
+ //
+ // On Mac OS X JLI_Launch spawns a new thread that actually starts the JVM.
+ // This new thread simply re-runs main(argc, argv). Therefore we do not
+ // want to add new args if we are still in the original main thread so we
+ // will treat them as command line args provided by the user ...
+ // Only propagate original set of args first time.
+
+ options.AppendValue(_T("-classpath"));
+ options.AppendValue(classpath);
+
+ std::list<TString> vmargs;
+ vmargs.push_back(package.GetCommandName());
+
+ if (package.HasSplashScreen() == true) {
+ options.AppendValue(TString(_T("-splash:"))
+ + package.GetSplashScreenFileName(), _T(""));
+ }
+
+ if (mainModule.empty() == true) {
+ options.AppendValue(Helpers::ConvertJavaPathToId(mainClassName),
+ _T(""));
+ } else {
+ options.AppendValue(_T("-m"));
+ options.AppendValue(mainModule);
+ }
+
+ return launchVM(options, vmargs);
+}
+
+void JavaVirtualMachine::configureLibrary() {
+ Platform& platform = Platform::GetInstance();
+ Package& package = Package::GetInstance();
+ TString libName = package.GetJavaLibraryFileName();
+ platform.addPlatformDependencies(&javaLibrary);
+ javaLibrary.Load(libName);
+}
+
+bool JavaVirtualMachine::launchVM(JavaOptions& options,
+ std::list<TString>& vmargs) {
+ Platform& platform = Platform::GetInstance();
+ Package& package = Package::GetInstance();
+
+#ifdef MAC
+ // Mac adds a ProcessSerialNumber to args when launched from .app
+ // filter out the psn since they it's not expected in the app
+ if (platform.IsMainThread() == false) {
+ std::list<TString> loptions = options.ToList();
+ vmargs.splice(vmargs.end(), loptions,
+ loptions.begin(), loptions.end());
+ }
+#else
+ std::list<TString> loptions = options.ToList();
+ vmargs.splice(vmargs.end(), loptions, loptions.begin(), loptions.end());
+#endif
+
+ std::list<TString> largs = package.GetArgs();
+ vmargs.splice(vmargs.end(), largs, largs.begin(), largs.end());
+
+ size_t argc = vmargs.size();
+ DynamicBuffer<char*> argv(argc + 1);
+ if (argv.GetData() == NULL) {
+ return false;
+ }
+
+ unsigned int index = 0;
+ for (std::list<TString>::const_iterator iterator = vmargs.begin();
+ iterator != vmargs.end(); iterator++) {
+ TString item = *iterator;
+ std::string arg = PlatformString(item).toStdString();
+#ifdef DEBUG
+ printf("%i %s\n", index, arg.c_str());
+#endif // DEBUG
+ argv[index] = PlatformString::duplicate(arg.c_str());
+ index++;
+ }
+
+ argv[argc] = NULL;
+
+// On Mac we can only free the boot fields if the calling thread is
+// not the main thread.
+#ifdef MAC
+ if (platform.IsMainThread() == false) {
+ package.FreeBootFields();
+ }
+#else
+ package.FreeBootFields();
+#endif // MAC
+
+ if (javaLibrary.JavaVMCreate(argc, argv.GetData()) == true) {
+ return true;
+ }
+
+ for (index = 0; index < argc; index++) {
+ if (argv[index] != NULL) {
+ delete[] argv[index];
+ }
+ }
+
+ return false;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/native/libapplauncher/JavaVirtualMachine.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef JAVAVIRTUALMACHINE_H
+#define JAVAVIRTUALMACHINE_H
+
+
+#include "jni.h"
+#include "Platform.h"
+#include "Library.h"
+
+struct JavaOptionItem {
+ TString name;
+ TString value;
+ void* extraInfo;
+};
+
+class JavaOptions {
+private:
+ std::list<JavaOptionItem> FItems;
+ JavaVMOption* FOptions;
+
+public:
+ JavaOptions();
+ ~JavaOptions();
+
+ void AppendValue(const TString Key, TString Value, void* Extra);
+ void AppendValue(const TString Key, TString Value);
+ void AppendValue(const TString Key);
+ void AppendValues(OrderedMap<TString, TString> Values);
+ void ReplaceValue(const TString Key, TString Value);
+ std::list<TString> ToList();
+ size_t GetCount();
+};
+
+class JavaVirtualMachine {
+private:
+ JavaLibrary javaLibrary;
+
+ void configureLibrary();
+ bool launchVM(JavaOptions& options, std::list<TString>& vmargs);
+public:
+ JavaVirtualMachine();
+ ~JavaVirtualMachine(void);
+
+ bool StartJVM();
+};
+
+bool RunVM();
+
+#endif // JAVAVIRTUALMACHINE_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/native/libapplauncher/Library.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "Library.h"
+#include "Platform.h"
+#include "Messages.h"
+#include "PlatformString.h"
+
+#include <fstream>
+#include <locale>
+
+Library::Library() {
+ Initialize();
+}
+
+Library::Library(const TString &FileName) {
+ Initialize();
+ Load(FileName);
+}
+
+Library::~Library() {
+ Unload();
+}
+
+void Library::Initialize() {
+ FModule = NULL;
+ FDependentLibraryNames = NULL;
+ FDependenciesLibraries = NULL;
+}
+
+void Library::InitializeDependencies() {
+ if (FDependentLibraryNames == NULL) {
+ FDependentLibraryNames = new std::vector<TString>();
+ }
+
+ if (FDependenciesLibraries == NULL) {
+ FDependenciesLibraries = new std::vector<Library*>();
+ }
+}
+
+void Library::LoadDependencies() {
+ if (FDependentLibraryNames != NULL && FDependenciesLibraries != NULL) {
+ for (std::vector<TString>::const_iterator iterator =
+ FDependentLibraryNames->begin();
+ iterator != FDependentLibraryNames->end(); iterator++) {
+ Library* library = new Library();
+
+ if (library->Load(*iterator) == true) {
+ FDependenciesLibraries->push_back(library);
+ }
+ }
+
+ delete FDependentLibraryNames;
+ FDependentLibraryNames = NULL;
+ }
+}
+
+void Library::UnloadDependencies() {
+ if (FDependenciesLibraries != NULL) {
+ for (std::vector<Library*>::const_iterator iterator =
+ FDependenciesLibraries->begin();
+ iterator != FDependenciesLibraries->end(); iterator++) {
+ Library* library = *iterator;
+
+ if (library != NULL) {
+ library->Unload();
+ delete library;
+ }
+ }
+
+ delete FDependenciesLibraries;
+ FDependenciesLibraries = NULL;
+ }
+}
+
+Procedure Library::GetProcAddress(const std::string& MethodName) const {
+ Platform& platform = Platform::GetInstance();
+ return platform.GetProcAddress(FModule, MethodName);
+}
+
+bool Library::Load(const TString &FileName) {
+ bool result = true;
+
+ if (FModule == NULL) {
+ LoadDependencies();
+ Platform& platform = Platform::GetInstance();
+ FModule = platform.LoadLibrary(FileName);
+
+ if (FModule == NULL) {
+ Messages& messages = Messages::GetInstance();
+ platform.ShowMessage(messages.GetMessage(LIBRARY_NOT_FOUND),
+ FileName);
+ result = false;
+ } else {
+ fname = PlatformString(FileName).toStdString();
+ }
+ }
+
+ return result;
+}
+
+bool Library::Unload() {
+ bool result = false;
+
+ if (FModule != NULL) {
+ Platform& platform = Platform::GetInstance();
+ platform.FreeLibrary(FModule);
+ FModule = NULL;
+ UnloadDependencies();
+ result = true;
+ }
+
+ return result;
+}
+
+void Library::AddDependency(const TString &FileName) {
+ InitializeDependencies();
+
+ if (FDependentLibraryNames != NULL) {
+ FDependentLibraryNames->push_back(FileName);
+ }
+}
+
+void Library::AddDependencies(const std::vector<TString> &Dependencies) {
+ if (Dependencies.size() > 0) {
+ InitializeDependencies();
+
+ if (FDependentLibraryNames != NULL) {
+ for (std::vector<TString>::const_iterator iterator =
+ FDependentLibraryNames->begin();
+ iterator != FDependentLibraryNames->end(); iterator++) {
+ TString fileName = *iterator;
+ AddDependency(fileName);
+ }
+ }
+ }
+}
+
+JavaLibrary::JavaLibrary() : Library(), FCreateProc(NULL) {
+}
+
+bool JavaLibrary::JavaVMCreate(size_t argc, char *argv[]) {
+ if (FCreateProc == NULL) {
+ FCreateProc = (JAVA_CREATE) GetProcAddress(LAUNCH_FUNC);
+ }
+
+ if (FCreateProc == NULL) {
+ Platform& platform = Platform::GetInstance();
+ Messages& messages = Messages::GetInstance();
+ platform.ShowMessage(
+ messages.GetMessage(FAILED_LOCATING_JVM_ENTRY_POINT));
+ return false;
+ }
+
+ return FCreateProc((int) argc, argv,
+ 0, NULL,
+ 0, NULL,
+ "",
+ "",
+ "java",
+ "java",
+ false,
+ false,
+ false,
+ 0) == 0;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/native/libapplauncher/Library.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef LIBRARY_H
+#define LIBRARY_H
+
+#include "PlatformDefs.h"
+//#include "Platform.h"
+#include "OrderedMap.h"
+
+#include "jni.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <memory.h>
+#include <string>
+#include <map>
+#include <list>
+#include <vector>
+#include <fstream>
+
+using namespace std;
+
+// Private typedef for function pointer casting
+
+#if defined(_WIN32) && !defined(_WIN64)
+#define LAUNCH_FUNC "_JLI_Launch@56"
+#else
+#define LAUNCH_FUNC "JLI_Launch"
+#endif
+
+
+typedef int (JNICALL *JAVA_CREATE)(int argc, char ** argv,
+ int jargc, const char** jargv,
+ int appclassc, const char** appclassv,
+ const char* fullversion,
+ const char* dotversion,
+ const char* pname,
+ const char* lname,
+ jboolean javaargs,
+ jboolean cpwildcard,
+ jboolean javaw,
+ jint ergo);
+
+class Library {
+private:
+ std::vector<TString> *FDependentLibraryNames;
+ std::vector<Library*> *FDependenciesLibraries;
+ Module FModule;
+ std::string fname;
+
+ void Initialize();
+ void InitializeDependencies();
+ void LoadDependencies();
+ void UnloadDependencies();
+
+public:
+ void* GetProcAddress(const std::string& MethodName) const;
+
+public:
+ Library();
+ Library(const TString &FileName);
+ ~Library();
+
+ bool Load(const TString &FileName);
+ bool Unload();
+
+ const std::string& GetName() const {
+ return fname;
+ }
+
+ void AddDependency(const TString &FileName);
+ void AddDependencies(const std::vector<TString> &Dependencies);
+};
+
+class JavaLibrary : public Library {
+ JAVA_CREATE FCreateProc;
+ JavaLibrary(const TString &FileName);
+public:
+ JavaLibrary();
+ bool JavaVMCreate(size_t argc, char *argv[]);
+};
+
+#endif // LIBRARY_H
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/native/libapplauncher/Macros.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "Macros.h"
+#include "Package.h"
+#include "Helpers.h"
+
+
+Macros::Macros(void) {
+}
+
+Macros::~Macros(void) {
+}
+
+void Macros::Initialize() {
+ Package& package = Package::GetInstance();
+ Macros& macros = Macros::GetInstance();
+
+ // Public macros.
+ macros.AddMacro(_T("$ROOTDIR"), package.GetPackageRootDirectory());
+ macros.AddMacro(_T("$APPDIR"), package.GetPackageAppDirectory());
+ macros.AddMacro(_T("$BINDIR"), package.GetPackageLauncherDirectory());
+}
+
+Macros& Macros::GetInstance() {
+ static Macros instance;
+ return instance;
+}
+
+TString Macros::ExpandMacros(TString Value) {
+ TString result = Value;
+
+ for (std::map<TString, TString>::iterator iterator = FData.begin();
+ iterator != FData.end();
+ iterator++) {
+
+ TString name = iterator->first;
+
+ if (Value.find(name) != TString::npos) {
+ TString lvalue = iterator->second;
+ result = Helpers::ReplaceString(Value, name, lvalue);
+ result = ExpandMacros(result);
+ break;
+ }
+ }
+
+ return result;
+}
+
+void Macros::AddMacro(TString Key, TString Value) {
+ FData.insert(std::map<TString, TString>::value_type(Key, Value));
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/native/libapplauncher/Macros.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef MACROS_H
+#define MACROS_H
+
+#include "Platform.h"
+
+#include <map>
+
+
+class Macros {
+private:
+ std::map<TString, TString> FData;
+
+ Macros(void);
+
+public:
+ static Macros& GetInstance();
+ static void Initialize();
+ ~Macros(void);
+
+ TString ExpandMacros(TString Value);
+ void AddMacro(TString Key, TString Value);
+};
+
+#endif // MACROS_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/native/libapplauncher/Messages.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "Messages.h"
+#include "Platform.h"
+#include "FilePath.h"
+#include "Helpers.h"
+#include "Macros.h"
+#include "JavaVirtualMachine.h"
+
+Messages::Messages(void) {
+ FMessages.SetReadOnly(false);
+ FMessages.SetValue(LIBRARY_NOT_FOUND, _T("Failed to find library."));
+ FMessages.SetValue(FAILED_CREATING_JVM, _T("Failed to create JVM"));
+ FMessages.SetValue(FAILED_LOCATING_JVM_ENTRY_POINT,
+ _T("Failed to locate JLI_Launch"));
+ FMessages.SetValue(NO_MAIN_CLASS_SPECIFIED, _T("No main class specified"));
+ FMessages.SetValue(METHOD_NOT_FOUND, _T("No method %s in class %s."));
+ FMessages.SetValue(CLASS_NOT_FOUND, _T("Class %s not found."));
+ FMessages.SetValue(ERROR_INVOKING_METHOD, _T("Error invoking method."));
+ FMessages.SetValue(APPCDS_CACHE_FILE_NOT_FOUND,
+ _T("Error: AppCDS cache does not exists:\n%s\n"));
+}
+
+Messages& Messages::GetInstance() {
+ static Messages instance;
+ // Guaranteed to be destroyed. Instantiated on first use.
+ return instance;
+}
+
+Messages::~Messages(void) {
+}
+
+TString Messages::GetMessage(const TString Key) {
+ TString result;
+ FMessages.GetValue(Key, result);
+ Macros& macros = Macros::GetInstance();
+ result = macros.ExpandMacros(result);
+ return result;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/native/libapplauncher/Messages.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef MESSAGES_H
+#define MESSAGES_H
+
+#include "PropertyFile.h"
+
+#define LIBRARY_NOT_FOUND _T("library.not.found")
+#define FAILED_CREATING_JVM _T("failed.creating.jvm")
+#define FAILED_LOCATING_JVM_ENTRY_POINT _T("failed.locating.jvm.entry.point")
+#define NO_MAIN_CLASS_SPECIFIED _T("no.main.class.specified")
+
+#define METHOD_NOT_FOUND _T("method.not.found")
+#define CLASS_NOT_FOUND _T("class.not.found")
+#define ERROR_INVOKING_METHOD _T("error.invoking.method")
+
+#define CONFIG_FILE_NOT_FOUND _T("config.file.not.found")
+
+#define BUNDLED_JVM_NOT_FOUND _T("bundled.jvm.not.found")
+
+#define APPCDS_CACHE_FILE_NOT_FOUND _T("appcds.cache.file.not.found")
+
+class Messages {
+private:
+ PropertyFile FMessages;
+
+ Messages(void);
+public:
+ static Messages& GetInstance();
+ ~Messages(void);
+
+ TString GetMessage(const TString Key);
+};
+
+#endif // MESSAGES_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/native/libapplauncher/OrderedMap.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ORDEREDMAP_H
+#define ORDEREDMAP_H
+
+#include <map>
+#include <vector>
+#include <assert.h>
+#include <stdexcept>
+
+#include <iostream>
+
+template <typename _T1, typename _T2>
+struct JPPair
+{
+ typedef _T1 first_type;
+ typedef _T2 second_type;
+
+ first_type first;
+ second_type second;
+
+ JPPair(first_type Value1, second_type Value2) {
+ first = Value1;
+ second = Value2;
+ }
+};
+
+
+template <typename TKey, typename TValue>
+class OrderedMap {
+public:
+ typedef TKey key_type;
+ typedef TValue mapped_type;
+ typedef JPPair<key_type, mapped_type> container_type;
+ typedef typename std::vector<container_type*>::iterator iterator;
+ typedef typename std::vector<container_type*>::const_iterator const_iterator;
+
+private:
+ typedef std::map<key_type, container_type*> map_type;
+ typedef std::vector<container_type*> list_type;
+
+ map_type FMap;
+ list_type FList;
+ bool FAllowDuplicates;
+
+ typename list_type::iterator FindListItem(const key_type Key) {
+ typename list_type::iterator result = FList.end();
+
+ for (typename list_type::iterator iterator =
+ FList.begin(); iterator != FList.end(); iterator++) {
+ container_type *item = *iterator;
+
+ if (item->first == Key) {
+ result = iterator;
+ break;
+ }
+ }
+
+ return result;
+ }
+
+public:
+ OrderedMap() {
+ FAllowDuplicates = false;
+ }
+
+ OrderedMap(const OrderedMap<key_type, mapped_type> &Value) {
+ Append(Value);
+ FAllowDuplicates = Value.GetAllowDuplicates();
+ }
+
+ ~OrderedMap() {
+ Clear();
+ }
+
+ void SetAllowDuplicates(bool Value) {
+ FAllowDuplicates = Value;
+ }
+
+ bool GetAllowDuplicates() const {
+ return FAllowDuplicates;
+ }
+
+ iterator begin() {
+ return FList.begin();
+ }
+
+ const_iterator begin() const {
+ return FList.begin();
+ }
+
+ iterator end() {
+ return FList.end();
+ }
+
+ const_iterator end() const {
+ return FList.end();
+ }
+
+ void Clear() {
+ for (typename list_type::iterator iterator =
+ FList.begin(); iterator != FList.end(); iterator++) {
+ container_type *item = *iterator;
+
+ if (item != NULL) {
+ delete item;
+ item = NULL;
+ }
+ }
+
+ FMap.clear();
+ FList.clear();
+ }
+
+ bool ContainsKey(key_type Key) {
+ bool result = false;
+
+ if (FMap.find(Key) != FMap.end()) {
+ result = true;
+ }
+
+ return result;
+ }
+
+ std::vector<key_type> GetKeys() {
+ std::vector<key_type> result;
+
+ for (typename list_type::const_iterator iterator = FList.begin();
+ iterator != FList.end(); iterator++) {
+ container_type *item = *iterator;
+ result.push_back(item->first);
+ }
+
+ return result;
+ }
+
+ void Assign(const OrderedMap<key_type, mapped_type> &Value) {
+ Clear();
+ Append(Value);
+ }
+
+ void Append(const OrderedMap<key_type, mapped_type> &Value) {
+ for (size_t index = 0; index < Value.FList.size(); index++) {
+ container_type *item = Value.FList[index];
+ Append(item->first, item->second);
+ }
+ }
+
+ void Append(key_type Key, mapped_type Value) {
+ container_type *item = new container_type(Key, Value);
+ FMap.insert(std::pair<key_type, container_type*>(Key, item));
+ FList.push_back(item);
+ }
+
+ bool RemoveByKey(key_type Key) {
+ bool result = false;
+ typename list_type::iterator iterator = FindListItem(Key);
+
+ if (iterator != FList.end()) {
+ FMap.erase(Key);
+ FList.erase(iterator);
+ result = true;
+ }
+
+ return result;
+ }
+
+ bool GetValue(key_type Key, mapped_type &Value) {
+ bool result = false;
+ container_type* item = FMap[Key];
+
+ if (item != NULL) {
+ Value = item->second;
+ result = true;
+ }
+
+ return result;
+ }
+
+ bool SetValue(key_type Key, mapped_type &Value) {
+ bool result = false;
+
+ if ((FAllowDuplicates == false) && (ContainsKey(Key) == true)) {
+ container_type *item = FMap[Key];
+
+ if (item != NULL) {
+ item->second = Value;
+ result = true;
+ }
+ }
+ else {
+ Append(Key, Value);
+ result = true;
+ }
+
+ return result;
+ }
+
+ bool GetKey(int index, key_type &Value) {
+ if (index < 0 || index >= (int)FList.size()) {
+ return false;
+ }
+ container_type *item = FList.at(index);
+ if (item != NULL) {
+ Value = item->first;
+ return true;
+ }
+
+ return false;
+ }
+
+ bool GetValue(int index, mapped_type &Value) {
+ if (index < 0 || index >= (int)FList.size()) {
+ return false;
+ }
+ container_type *item = FList.at(index);
+ if (item != NULL) {
+ Value = item->second;
+ return true;
+ }
+
+ return false;
+ }
+
+ mapped_type &operator[](key_type Key) {
+ container_type* item = FMap[Key];
+ assert(item != NULL);
+
+ if (item != NULL) {
+ return item->second;
+ }
+
+ throw std::invalid_argument("Key not found");
+ }
+
+ OrderedMap& operator= (OrderedMap &Value) {
+ Clear();
+ FAllowDuplicates = Value.GetAllowDuplicates();
+ Append(Value);
+ return *this;
+ }
+
+ size_t Count() {
+ return FList.size();
+ }
+};
+
+#endif // ORDEREDMAP_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/native/libapplauncher/Package.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,557 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "Package.h"
+#include "Helpers.h"
+#include "Macros.h"
+#include "IniFile.h"
+
+#include <assert.h>
+
+
+Package::Package(void) {
+ FInitialized = false;
+ Initialize();
+}
+
+TPlatformNumber StringToPercentageOfNumber(TString Value,
+ TPlatformNumber Number) {
+ TPlatformNumber result = 0;
+ size_t percentage = atoi(PlatformString(Value.c_str()));
+
+ if (percentage > 0 && Number > 0) {
+ result = Number * percentage / 100;
+ }
+
+ return result;
+}
+
+void Package::Initialize() {
+ if (FInitialized == true) {
+ return;
+ }
+
+ Platform& platform = Platform::GetInstance();
+
+ FBootFields = new PackageBootFields();
+ FDebugging = dsNone;
+
+ // Allow duplicates for Java options, so we can have multiple --add-exports
+ // or similar args.
+ FBootFields->FJavaOptions.SetAllowDuplicates(true);
+ FBootFields->FPackageRootDirectory = platform.GetPackageRootDirectory();
+ FBootFields->FPackageAppDirectory = platform.GetPackageAppDirectory();
+ FBootFields->FPackageLauncherDirectory =
+ platform.GetPackageLauncherDirectory();
+ FBootFields->FAppDataDirectory = platform.GetAppDataDirectory();
+
+ std::map<TString, TString> keys = platform.GetKeys();
+
+ // Read from configure.cfg/Info.plist
+ AutoFreePtr<ISectionalPropertyContainer> config =
+ platform.GetConfigFile(platform.GetConfigFileName());
+
+ config->GetValue(keys[CONFIG_SECTION_APPLICATION],
+ keys[JPACKAGE_APP_DATA_DIR], FBootFields->FPackageAppDataDirectory);
+ FBootFields->FPackageAppDataDirectory =
+ FilePath::FixPathForPlatform(FBootFields->FPackageAppDataDirectory);
+
+ // Main JAR.
+ config->GetValue(keys[CONFIG_SECTION_APPLICATION],
+ keys[CONFIG_MAINJAR_KEY], FBootFields->FMainJar);
+ FBootFields->FMainJar = FilePath::FixPathForPlatform(FBootFields->FMainJar);
+
+ // Main Module.
+ config->GetValue(keys[CONFIG_SECTION_APPLICATION],
+ keys[CONFIG_MAINMODULE_KEY], FBootFields->FMainModule);
+
+ // Classpath.
+ // 1. If the provided class path contains main jar then only use
+ // provided class path.
+ // 2. If class path provided by config file is empty then add main jar.
+ // 3. If main jar is not in provided class path then add it.
+ config->GetValue(keys[CONFIG_SECTION_APPLICATION],
+ keys[CONFIG_CLASSPATH_KEY], FBootFields->FClassPath);
+ FBootFields->FClassPath =
+ FilePath::FixPathSeparatorForPlatform(FBootFields->FClassPath);
+
+ if (FBootFields->FClassPath.empty() == true) {
+ FBootFields->FClassPath = GetMainJar();
+ } else if (FBootFields->FClassPath.find(GetMainJar()) == TString::npos) {
+ FBootFields->FClassPath = GetMainJar()
+ + FilePath::PathSeparator() + FBootFields->FClassPath;
+ }
+
+ // Modulepath.
+ config->GetValue(keys[CONFIG_SECTION_APPLICATION],
+ keys[CONFIG_MODULEPATH_KEY], FBootFields->FModulePath);
+ FBootFields->FModulePath =
+ FilePath::FixPathSeparatorForPlatform(FBootFields->FModulePath);
+
+ // Main Class.
+ config->GetValue(keys[CONFIG_SECTION_APPLICATION],
+ keys[CONFIG_MAINCLASSNAME_KEY], FBootFields->FMainClassName);
+
+ // Splash Screen.
+ if (config->GetValue(keys[CONFIG_SECTION_APPLICATION],
+ keys[CONFIG_SPLASH_KEY],
+ FBootFields->FSplashScreenFileName) == true) {
+ FBootFields->FSplashScreenFileName =
+ FilePath::IncludeTrailingSeparator(GetPackageAppDirectory())
+ + FilePath::FixPathForPlatform(FBootFields->FSplashScreenFileName);
+
+ if (FilePath::FileExists(FBootFields->FSplashScreenFileName) == false) {
+ FBootFields->FSplashScreenFileName = _T("");
+ }
+ }
+
+ // Runtime.
+ config->GetValue(keys[CONFIG_SECTION_APPLICATION],
+ keys[JAVA_RUNTIME_KEY], FBootFields->FJavaRuntimeDirectory);
+
+ // Read jvmargs.
+ PromoteAppCDSState(config);
+ ReadJavaOptions(config);
+
+ // Read args if none were passed in.
+ if (FBootFields->FArgs.size() == 0) {
+ OrderedMap<TString, TString> args;
+
+ if (config->GetSection(keys[CONFIG_SECTION_ARGOPTIONS], args) == true) {
+ FBootFields->FArgs = Helpers::MapToNameValueList(args);
+ }
+ }
+
+ // Auto Memory.
+ TString autoMemory;
+
+ if (config->GetValue(keys[CONFIG_SECTION_APPLICATION],
+ keys[CONFIG_APP_MEMORY], autoMemory) == true) {
+ if (autoMemory == _T("auto") || autoMemory == _T("100%")) {
+ FBootFields->FMemoryState = PackageBootFields::msAuto;
+ FBootFields->FMemorySize = platform.GetMemorySize();
+ } else if (autoMemory.length() == 2 && isdigit(autoMemory[0]) &&
+ autoMemory[1] == '%') {
+ FBootFields->FMemoryState = PackageBootFields::msAuto;
+ FBootFields->FMemorySize =
+ StringToPercentageOfNumber(autoMemory.substr(0, 1),
+ platform.GetMemorySize());
+ } else if (autoMemory.length() == 3 && isdigit(autoMemory[0]) &&
+ isdigit(autoMemory[1]) && autoMemory[2] == '%') {
+ FBootFields->FMemoryState = PackageBootFields::msAuto;
+ FBootFields->FMemorySize =
+ StringToPercentageOfNumber(autoMemory.substr(0, 2),
+ platform.GetMemorySize());
+ } else {
+ FBootFields->FMemoryState = PackageBootFields::msManual;
+ FBootFields->FMemorySize = 0;
+ }
+ }
+
+ // Debug
+ TString debug;
+ if (config->GetValue(keys[CONFIG_SECTION_APPLICATION],
+ keys[CONFIG_APP_DEBUG], debug) == true) {
+ FBootFields->FArgs.push_back(debug);
+ }
+}
+
+void Package::Clear() {
+ FreeBootFields();
+ FInitialized = false;
+}
+
+// This is the only location that the AppCDS state should be modified except
+// by command line arguments provided by the user.
+//
+// The state of AppCDS is as follows:
+//
+// -> cdsUninitialized
+// -> cdsGenCache If -Xappcds:generatecache
+// -> cdsDisabled If -Xappcds:off
+// -> cdsEnabled If "AppCDSJavaOptions" section is present
+// -> cdsAuto If "AppCDSJavaOptions" section is present and
+// app.appcds.cache=auto
+// -> cdsDisabled Default
+//
+void Package::PromoteAppCDSState(ISectionalPropertyContainer* Config) {
+ Platform& platform = Platform::GetInstance();
+ std::map<TString, TString> keys = platform.GetKeys();
+
+ // The AppCDS state can change at this point.
+ switch (platform.GetAppCDSState()) {
+ case cdsEnabled:
+ case cdsAuto:
+ case cdsDisabled:
+ case cdsGenCache: {
+ // Do nothing.
+ break;
+ }
+
+ case cdsUninitialized: {
+ if (Config->ContainsSection(
+ keys[CONFIG_SECTION_APPCDSJAVAOPTIONS]) == true) {
+ // If the AppCDS section is present then enable AppCDS.
+ TString appCDSCacheValue;
+
+ // If running with AppCDS enabled, and the configuration has
+ // been setup so "auto" is enabled, then
+ // the launcher will attempt to generate the cache file
+ // automatically and run the application.
+ if (Config->GetValue(keys[CONFIG_SECTION_APPLICATION],
+ _T("app.appcds.cache"), appCDSCacheValue) == true &&
+ appCDSCacheValue == _T("auto")) {
+ platform.SetAppCDSState(cdsAuto);
+ }
+ else {
+ platform.SetAppCDSState(cdsEnabled);
+ }
+ } else {
+
+ platform.SetAppCDSState(cdsDisabled);
+ }
+ }
+ }
+}
+
+void Package::ReadJavaOptions(ISectionalPropertyContainer* Config) {
+ Platform& platform = Platform::GetInstance();
+ std::map<TString, TString> keys = platform.GetKeys();
+
+ // Evaluate based on the current AppCDS state.
+ switch (platform.GetAppCDSState()) {
+ case cdsUninitialized: {
+ throw Exception(_T("Internal Error"));
+ }
+
+ case cdsDisabled: {
+ Config->GetSection(keys[CONFIG_SECTION_JAVAOPTIONS],
+ FBootFields->FJavaOptions);
+ break;
+ }
+
+ case cdsGenCache: {
+ Config->GetSection(keys[
+ CONFIG_SECTION_APPCDSGENERATECACHEJAVAOPTIONS],
+ FBootFields->FJavaOptions);
+ break;
+ }
+
+ case cdsAuto:
+ case cdsEnabled: {
+ if (Config->GetValue(keys[CONFIG_SECTION_APPCDSJAVAOPTIONS],
+ _T( "-XX:SharedArchiveFile"),
+ FBootFields->FAppCDSCacheFileName) == true) {
+ // File names may contain the incorrect path separators.
+ // The cache file name must be corrected at this point.
+ if (FBootFields->FAppCDSCacheFileName.empty() == false) {
+ IniFile* iniConfig = dynamic_cast<IniFile*>(Config);
+
+ if (iniConfig != NULL) {
+ FBootFields->FAppCDSCacheFileName =
+ FilePath::FixPathForPlatform(
+ FBootFields->FAppCDSCacheFileName);
+ iniConfig->SetValue(keys[
+ CONFIG_SECTION_APPCDSJAVAOPTIONS],
+ _T( "-XX:SharedArchiveFile"),
+ FBootFields->FAppCDSCacheFileName);
+ }
+ }
+
+ Config->GetSection(keys[CONFIG_SECTION_APPCDSJAVAOPTIONS],
+ FBootFields->FJavaOptions);
+ }
+
+ break;
+ }
+ }
+}
+
+void Package::SetCommandLineArguments(int argc, TCHAR* argv[]) {
+ if (argc > 0) {
+ std::list<TString> args;
+
+ // Prepare app arguments. Skip value at index 0 -
+ // this is path to executable.
+ FBootFields->FCommandName = argv[0];
+
+ // Path to executable is at 0 index so start at index 1.
+ for (int index = 1; index < argc; index++) {
+ TString arg = argv[index];
+
+#ifdef DEBUG
+ if (arg == _T("-debug")) {
+ FDebugging = dsNative;
+ }
+
+ if (arg == _T("-javadebug")) {
+ FDebugging = dsJava;
+ }
+#endif //DEBUG
+#ifdef MAC
+ if (arg.find(_T("-psn_"), 0) != TString::npos) {
+ Platform& platform = Platform::GetInstance();
+
+ if (platform.IsMainThread() == true) {
+#ifdef DEBUG
+ printf("%s\n", arg.c_str());
+#endif //DEBUG
+ continue;
+ }
+ }
+
+ if (arg == _T("-NSDocumentRevisionsDebugMode")) {
+ // Ignore -NSDocumentRevisionsDebugMode and
+ // the following YES/NO
+ index++;
+ continue;
+ }
+#endif //MAC
+
+ args.push_back(arg);
+ }
+
+ if (args.size() > 0) {
+ FBootFields->FArgs = args;
+ }
+ }
+}
+
+Package& Package::GetInstance() {
+ static Package instance;
+ // Guaranteed to be destroyed. Instantiated on first use.
+ return instance;
+}
+
+Package::~Package(void) {
+ FreeBootFields();
+}
+
+void Package::FreeBootFields() {
+ if (FBootFields != NULL) {
+ delete FBootFields;
+ FBootFields = NULL;
+ }
+}
+
+OrderedMap<TString, TString> Package::GetJavaOptions() {
+ return FBootFields->FJavaOptions;
+}
+
+std::vector<TString> GetKeysThatAreNotDuplicates(OrderedMap<TString,
+ TString> &Defaults, OrderedMap<TString, TString> &Overrides) {
+ std::vector<TString> result;
+ std::vector<TString> overrideKeys = Overrides.GetKeys();
+
+ for (size_t index = 0; index < overrideKeys.size(); index++) {
+ TString overridesKey = overrideKeys[index];
+ TString overridesValue;
+ TString defaultValue;
+
+ if ((Defaults.ContainsKey(overridesKey) == false) ||
+ (Defaults.GetValue(overridesKey, defaultValue) == true &&
+ Overrides.GetValue(overridesKey, overridesValue) == true &&
+ defaultValue != overridesValue)) {
+ result.push_back(overridesKey);
+ }
+ }
+
+ return result;
+}
+
+OrderedMap<TString, TString> CreateOrderedMapFromKeyList(OrderedMap<TString,
+ TString> &Map, std::vector<TString> &Keys) {
+ OrderedMap<TString, TString> result;
+
+ for (size_t index = 0; index < Keys.size(); index++) {
+ TString key = Keys[index];
+ TString value;
+
+ if (Map.GetValue(key, value) == true) {
+ result.Append(key, value);
+ }
+ }
+
+ return result;
+}
+
+std::vector<TString> GetKeysThatAreNotOverridesOfDefaultValues(
+ OrderedMap<TString, TString> &Defaults, OrderedMap<TString,
+ TString> &Overrides) {
+ std::vector<TString> result;
+ std::vector<TString> keys = Overrides.GetKeys();
+
+ for (unsigned int index = 0; index< keys.size(); index++) {
+ TString key = keys[index];
+
+ if (Defaults.ContainsKey(key) == true) {
+ try {
+ TString value = Overrides[key];
+ Defaults[key] = value;
+ }
+ catch (std::out_of_range &) {
+ }
+ }
+ else {
+ result.push_back(key);
+ }
+ }
+
+ return result;
+}
+
+std::list<TString> Package::GetArgs() {
+ assert(FBootFields != NULL);
+ return FBootFields->FArgs;
+}
+
+TString Package::GetPackageRootDirectory() {
+ assert(FBootFields != NULL);
+ return FBootFields->FPackageRootDirectory;
+}
+
+TString Package::GetPackageAppDirectory() {
+ assert(FBootFields != NULL);
+ return FBootFields->FPackageAppDirectory;
+}
+
+TString Package::GetPackageLauncherDirectory() {
+ assert(FBootFields != NULL);
+ return FBootFields->FPackageLauncherDirectory;
+}
+
+TString Package::GetAppDataDirectory() {
+ assert(FBootFields != NULL);
+ return FBootFields->FAppDataDirectory;
+}
+
+TString Package::GetAppCDSCacheDirectory() {
+ if (FAppCDSCacheDirectory.empty()) {
+ Platform& platform = Platform::GetInstance();
+ FAppCDSCacheDirectory = FilePath::IncludeTrailingSeparator(
+ platform.GetAppDataDirectory())
+ + FilePath::IncludeTrailingSeparator(
+ GetPackageAppDataDirectory()) + _T("cache");
+
+ Macros& macros = Macros::GetInstance();
+ FAppCDSCacheDirectory = macros.ExpandMacros(FAppCDSCacheDirectory);
+ FAppCDSCacheDirectory =
+ FilePath::FixPathForPlatform(FAppCDSCacheDirectory);
+ }
+
+ return FAppCDSCacheDirectory;
+}
+
+TString Package::GetAppCDSCacheFileName() {
+ assert(FBootFields != NULL);
+
+ if (FBootFields->FAppCDSCacheFileName.empty() == false) {
+ Macros& macros = Macros::GetInstance();
+ FBootFields->FAppCDSCacheFileName =
+ macros.ExpandMacros(FBootFields->FAppCDSCacheFileName);
+ FBootFields->FAppCDSCacheFileName =
+ FilePath::FixPathForPlatform(FBootFields->FAppCDSCacheFileName);
+ }
+
+ return FBootFields->FAppCDSCacheFileName;
+}
+
+TString Package::GetPackageAppDataDirectory() {
+ assert(FBootFields != NULL);
+ return FBootFields->FPackageAppDataDirectory;
+}
+
+TString Package::GetClassPath() {
+ assert(FBootFields != NULL);
+ return FBootFields->FClassPath;
+}
+
+TString Package::GetModulePath() {
+ assert(FBootFields != NULL);
+ return FBootFields->FModulePath;
+}
+
+TString Package::GetMainJar() {
+ assert(FBootFields != NULL);
+ return FBootFields->FMainJar;
+}
+
+TString Package::GetMainModule() {
+ assert(FBootFields != NULL);
+ return FBootFields->FMainModule;
+}
+
+TString Package::GetMainClassName() {
+ assert(FBootFields != NULL);
+ return FBootFields->FMainClassName;
+}
+
+TString Package::GetJavaLibraryFileName() {
+ assert(FBootFields != NULL);
+
+ if (FBootFields->FJavaLibraryFileName.empty() == true) {
+ Platform& platform = Platform::GetInstance();
+ Macros& macros = Macros::GetInstance();
+ TString jvmRuntimePath = macros.ExpandMacros(GetJavaRuntimeDirectory());
+ FBootFields->FJavaLibraryFileName =
+ platform.GetBundledJavaLibraryFileName(jvmRuntimePath);
+ }
+
+ return FBootFields->FJavaLibraryFileName;
+}
+
+TString Package::GetJavaRuntimeDirectory() {
+ assert(FBootFields != NULL);
+ return FBootFields->FJavaRuntimeDirectory;
+}
+
+TString Package::GetSplashScreenFileName() {
+ assert(FBootFields != NULL);
+ return FBootFields->FSplashScreenFileName;
+}
+
+bool Package::HasSplashScreen() {
+ assert(FBootFields != NULL);
+ return FilePath::FileExists(FBootFields->FSplashScreenFileName);
+}
+
+TString Package::GetCommandName() {
+ assert(FBootFields != NULL);
+ return FBootFields->FCommandName;
+}
+
+TPlatformNumber Package::GetMemorySize() {
+ assert(FBootFields != NULL);
+ return FBootFields->FMemorySize;
+}
+
+PackageBootFields::MemoryState Package::GetMemoryState() {
+ assert(FBootFields != NULL);
+ return FBootFields->FMemoryState;
+}
+
+DebugState Package::Debugging() {
+ return FDebugging;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/native/libapplauncher/Package.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef PACKAGE_H
+#define PACKAGE_H
+
+
+#include "Platform.h"
+#include "PlatformString.h"
+#include "FilePath.h"
+#include "PropertyFile.h"
+
+#include <map>
+#include <list>
+
+class PackageBootFields {
+public:
+ enum MemoryState {msManual, msAuto};
+
+public:
+ OrderedMap<TString, TString> FJavaOptions;
+ std::list<TString> FArgs;
+
+ TString FPackageRootDirectory;
+ TString FPackageAppDirectory;
+ TString FPackageLauncherDirectory;
+ TString FAppDataDirectory;
+ TString FPackageAppDataDirectory;
+ TString FClassPath;
+ TString FModulePath;
+ TString FMainJar;
+ TString FMainModule;
+ TString FMainClassName;
+ TString FJavaRuntimeDirectory;
+ TString FJavaLibraryFileName;
+ TString FSplashScreenFileName;
+ bool FUseJavaPreferences;
+ TString FCommandName;
+
+ TString FAppCDSCacheFileName;
+
+ TPlatformNumber FMemorySize;
+ MemoryState FMemoryState;
+};
+
+
+class Package {
+private:
+ Package(Package const&); // Don't Implement.
+ void operator=(Package const&); // Don't implement
+
+private:
+ bool FInitialized;
+ PackageBootFields* FBootFields;
+ TString FAppCDSCacheDirectory;
+
+ DebugState FDebugging;
+
+ Package(void);
+
+ TString GetMainJar();
+ void ReadJavaOptions(ISectionalPropertyContainer* Config);
+ void PromoteAppCDSState(ISectionalPropertyContainer* Config);
+
+public:
+ static Package& GetInstance();
+ ~Package(void);
+
+ void Initialize();
+ void Clear();
+ void FreeBootFields();
+
+ void SetCommandLineArguments(int argc, TCHAR* argv[]);
+
+ OrderedMap<TString, TString> GetJavaOptions();
+ TString GetMainModule();
+
+ std::list<TString> GetArgs();
+
+ TString GetPackageRootDirectory();
+ TString GetPackageAppDirectory();
+ TString GetPackageLauncherDirectory();
+ TString GetAppDataDirectory();
+
+ TString GetAppCDSCacheDirectory();
+ TString GetAppCDSCacheFileName();
+
+ TString GetPackageAppDataDirectory();
+ TString GetClassPath();
+ TString GetModulePath();
+ TString GetMainClassName();
+ TString GetJavaLibraryFileName();
+ TString GetJavaRuntimeDirectory();
+ TString GetSplashScreenFileName();
+ bool HasSplashScreen();
+ TString GetCommandName();
+
+ TPlatformNumber GetMemorySize();
+ PackageBootFields::MemoryState GetMemoryState();
+
+ DebugState Debugging();
+};
+
+#endif // PACKAGE_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/native/libapplauncher/Platform.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "Platform.h"
+#include "Messages.h"
+#include "PlatformString.h"
+#include "FilePath.h"
+
+#include <fstream>
+#include <locale>
+
+#ifdef WINDOWS
+#include "WindowsPlatform.h"
+#endif // WINDOWS
+#ifdef LINUX
+#include "LinuxPlatform.h"
+#endif // LINUX
+#ifdef MAC
+#include "MacPlatform.h"
+#endif // MAC
+
+Platform& Platform::GetInstance() {
+#ifdef WINDOWS
+ static WindowsPlatform instance;
+#endif // WINDOWS
+
+#ifdef LINUX
+ static LinuxPlatform instance;
+#endif // LINUX
+
+#ifdef MAC
+ static MacPlatform instance;
+#endif // MAC
+
+ return instance;
+}
+
+TString Platform::GetConfigFileName() {
+ TString result;
+ TString basedir = GetPackageAppDirectory();
+
+ if (basedir.empty() == false) {
+ basedir = FilePath::IncludeTrailingSeparator(basedir);
+ TString appConfig = basedir + GetAppName() + _T(".cfg");
+
+ if (FilePath::FileExists(appConfig) == true) {
+ result = appConfig;
+ }
+ else {
+ result = basedir + _T("package.cfg");
+
+ if (FilePath::FileExists(result) == false) {
+ result = _T("");
+ }
+ }
+ }
+
+ return result;
+}
+
+std::list<TString> Platform::LoadFromFile(TString FileName) {
+ std::list<TString> result;
+
+ if (FilePath::FileExists(FileName) == true) {
+ std::wifstream stream(FileName.data());
+ InitStreamLocale(&stream);
+
+ if (stream.is_open() == true) {
+ while (stream.eof() == false) {
+ std::wstring line;
+ std::getline(stream, line);
+
+ // # at the first character will comment out the line.
+ if (line.empty() == false && line[0] != '#') {
+ result.push_back(PlatformString(line).toString());
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+void Platform::SaveToFile(TString FileName, std::list<TString> Contents, bool ownerOnly) {
+ TString path = FilePath::ExtractFilePath(FileName);
+
+ if (FilePath::DirectoryExists(path) == false) {
+ FilePath::CreateDirectory(path, ownerOnly);
+ }
+
+ std::wofstream stream(FileName.data());
+ InitStreamLocale(&stream);
+
+ FilePath::ChangePermissions(FileName.data(), ownerOnly);
+
+ if (stream.is_open() == true) {
+ for (std::list<TString>::const_iterator iterator =
+ Contents.begin(); iterator != Contents.end(); iterator++) {
+ TString line = *iterator;
+ stream << PlatformString(line).toUnicodeString() << std::endl;
+ }
+ }
+}
+
+std::map<TString, TString> Platform::GetKeys() {
+ std::map<TString, TString> keys;
+ keys.insert(std::map<TString, TString>::value_type(CONFIG_VERSION,
+ _T("app.version")));
+ keys.insert(std::map<TString, TString>::value_type(CONFIG_MAINJAR_KEY,
+ _T("app.mainjar")));
+ keys.insert(std::map<TString, TString>::value_type(CONFIG_MAINMODULE_KEY,
+ _T("app.mainmodule")));
+ keys.insert(std::map<TString, TString>::value_type(CONFIG_MAINCLASSNAME_KEY,
+ _T("app.mainclass")));
+ keys.insert(std::map<TString, TString>::value_type(CONFIG_CLASSPATH_KEY,
+ _T("app.classpath")));
+ keys.insert(std::map<TString, TString>::value_type(CONFIG_MODULEPATH_KEY,
+ _T("app.modulepath")));
+ keys.insert(std::map<TString, TString>::value_type(APP_NAME_KEY,
+ _T("app.name")));
+ keys.insert(std::map<TString, TString>::value_type(JAVA_RUNTIME_KEY,
+ _T("app.runtime")));
+ keys.insert(std::map<TString, TString>::value_type(JPACKAGE_APP_DATA_DIR,
+ _T("app.identifier")));
+ keys.insert(std::map<TString, TString>::value_type(CONFIG_SPLASH_KEY,
+ _T("app.splash")));
+ keys.insert(std::map<TString, TString>::value_type(CONFIG_APP_MEMORY,
+ _T("app.memory")));
+ keys.insert(std::map<TString, TString>::value_type(CONFIG_APP_DEBUG,
+ _T("app.debug")));
+ keys.insert(std::map<TString,
+ TString>::value_type(CONFIG_APPLICATION_INSTANCE,
+ _T("app.application.instance")));
+ keys.insert(std::map<TString,
+ TString>::value_type(CONFIG_SECTION_APPLICATION,
+ _T("Application")));
+ keys.insert(std::map<TString,
+ TString>::value_type(CONFIG_SECTION_JAVAOPTIONS,
+ _T("JavaOptions")));
+ keys.insert(std::map<TString,
+ TString>::value_type(CONFIG_SECTION_APPCDSJAVAOPTIONS,
+ _T("AppCDSJavaOptions")));
+ keys.insert(std::map<TString,
+ TString>::value_type(CONFIG_SECTION_APPCDSGENERATECACHEJAVAOPTIONS,
+ _T("AppCDSGenerateCacheJavaOptions")));
+ keys.insert(std::map<TString,
+ TString>::value_type(CONFIG_SECTION_ARGOPTIONS,
+ _T("ArgOptions")));
+
+ return keys;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/native/libapplauncher/Platform.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,264 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef PLATFORM_H
+#define PLATFORM_H
+
+#include "PlatformDefs.h"
+#include "Properties.h"
+#include "OrderedMap.h"
+#include "Library.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <memory.h>
+#include <string>
+#include <map>
+#include <list>
+#include <vector>
+#include <fstream>
+
+using namespace std;
+
+// Config file sections
+#define CONFIG_SECTION_APPLICATION _T("CONFIG_SECTION_APPLICATION")
+#define CONFIG_SECTION_JAVAOPTIONS _T("CONFIG_SECTION_JAVAOPTIONS")
+#define CONFIG_SECTION_APPCDSJAVAOPTIONS _T("CONFIG_SECTION_APPCDSJAVAOPTIONS")
+#define CONFIG_SECTION_ARGOPTIONS _T("CONFIG_SECTION_ARGOPTIONS")
+#define CONFIG_SECTION_APPCDSGENERATECACHEJAVAOPTIONS \
+ _T("CONFIG_SECTION_APPCDSGENERATECACHEJAVAOPTIONS")
+
+// Config file keys.
+#define CONFIG_VERSION _T("CONFIG_VERSION")
+#define CONFIG_MAINJAR_KEY _T("CONFIG_MAINJAR_KEY")
+#define CONFIG_MAINMODULE_KEY _T("CONFIG_MAINMODULE_KEY")
+#define CONFIG_MAINCLASSNAME_KEY _T("CONFIG_MAINCLASSNAME_KEY")
+#define CONFIG_CLASSPATH_KEY _T("CONFIG_CLASSPATH_KEY")
+#define CONFIG_MODULEPATH_KEY _T("CONFIG_MODULEPATH_KEY")
+#define APP_NAME_KEY _T("APP_NAME_KEY")
+#define CONFIG_SPLASH_KEY _T("CONFIG_SPLASH_KEY")
+#define CONFIG_APP_MEMORY _T("CONFIG_APP_MEMORY")
+#define CONFIG_APP_DEBUG _T("CONFIG_APP_DEBUG")
+#define CONFIG_APPLICATION_INSTANCE _T("CONFIG_APPLICATION_INSTANCE")
+
+#define JAVA_RUNTIME_KEY _T("JAVA_RUNTIME_KEY")
+#define JPACKAGE_APP_DATA_DIR _T("CONFIG_APP_IDENTIFIER")
+
+struct WideString {
+ size_t length;
+ wchar_t* data;
+
+ WideString() { length = 0; data = NULL; }
+};
+
+struct MultibyteString {
+ size_t length;
+ char* data;
+
+ MultibyteString() { length = 0; data = NULL; }
+};
+
+class Process {
+protected:
+ std::list<TString> FOutput;
+
+public:
+ Process() {
+ Output.SetInstance(this);
+ Input.SetInstance(this);
+ }
+
+ virtual ~Process() {}
+
+ virtual bool IsRunning() = 0;
+ virtual bool Terminate() = 0;
+ virtual bool Execute(const TString Application,
+ const std::vector<TString> Arguments, bool AWait = false) = 0;
+ virtual bool Wait() = 0;
+ virtual TProcessID GetProcessID() = 0;
+
+ virtual std::list<TString> GetOutput() { return FOutput; }
+ virtual void SetInput(TString Value) = 0;
+
+ ReadProperty<Process, std::list<TString>, &Process::GetOutput> Output;
+ WriteProperty<Process, TString, &Process::SetInput> Input;
+};
+
+
+template <typename T>
+class AutoFreePtr {
+private:
+ T* FObject;
+
+public:
+ AutoFreePtr() {
+ FObject = NULL;
+ }
+
+ AutoFreePtr(T* Value) {
+ FObject = Value;
+ }
+
+ ~AutoFreePtr() {
+ if (FObject != NULL) {
+ delete FObject;
+ }
+ }
+
+ operator T* () const {
+ return FObject;
+ }
+
+ T& operator* () const {
+ return *FObject;
+ }
+
+ T* operator->() const {
+ return FObject;
+ }
+
+ T** operator&() {
+ return &FObject;
+ }
+
+ T* operator=(const T * rhs) {
+ FObject = rhs;
+ return FObject;
+ }
+};
+
+enum DebugState {dsNone, dsNative, dsJava};
+enum MessageResponse {mrOK, mrCancel};
+enum AppCDSState {cdsUninitialized, cdsDisabled,
+ cdsEnabled, cdsAuto, cdsGenCache};
+
+class Platform {
+private:
+ AppCDSState FAppCDSState;
+
+protected:
+ Platform(void): FAppCDSState(cdsUninitialized) {
+ }
+
+public:
+ AppCDSState GetAppCDSState() { return FAppCDSState; }
+ void SetAppCDSState(AppCDSState Value) { FAppCDSState = Value; }
+
+ static Platform& GetInstance();
+
+ virtual ~Platform(void) {}
+
+public:
+ virtual void ShowMessage(TString title, TString description) = 0;
+ virtual void ShowMessage(TString description) = 0;
+ virtual MessageResponse ShowResponseMessage(TString title,
+ TString description) = 0;
+
+ // Caller must free result using delete[].
+ virtual TCHAR* ConvertStringToFileSystemString(TCHAR* Source,
+ bool &release) = 0;
+
+ // Caller must free result using delete[].
+ virtual TCHAR* ConvertFileSystemStringToString(TCHAR* Source,
+ bool &release) = 0;
+
+ // Returns:
+ // Windows=C:\Users\<username>\AppData\Local
+ // Linux=~/.local
+ // Mac=~/Library/Application Support
+ virtual TString GetAppDataDirectory() = 0;
+
+ virtual TString GetPackageAppDirectory() = 0;
+ virtual TString GetPackageLauncherDirectory() = 0;
+ virtual TString GetPackageRuntimeBinDirectory() = 0;
+ virtual TString GetAppName() = 0;
+
+ virtual TString GetConfigFileName();
+
+ virtual TString GetBundledJavaLibraryFileName(TString RuntimePath) = 0;
+
+ // Caller must free result.
+ virtual ISectionalPropertyContainer* GetConfigFile(TString FileName) = 0;
+
+ virtual TString GetModuleFileName() = 0;
+ virtual TString GetPackageRootDirectory() = 0;
+
+ virtual Module LoadLibrary(TString FileName) = 0;
+ virtual void FreeLibrary(Module Module) = 0;
+ virtual Procedure GetProcAddress(Module Module, std::string MethodName) = 0;
+
+ // Caller must free result.
+ virtual Process* CreateProcess() = 0;
+
+ virtual bool IsMainThread() = 0;
+
+ // Returns megabytes.
+ virtual TPlatformNumber GetMemorySize() = 0;
+
+ virtual std::map<TString, TString> GetKeys();
+
+ virtual void InitStreamLocale(wios *stream) = 0;
+ virtual std::list<TString> LoadFromFile(TString FileName);
+ virtual void SaveToFile(TString FileName,
+ std::list<TString> Contents, bool ownerOnly);
+
+ virtual TString GetTempDirectory() = 0;
+
+ virtual void addPlatformDependencies(JavaLibrary *pJavaLibrary) = 0;
+
+public:
+ // String helpers
+ // Caller must free result using delete[].
+ static void CopyString(char *Destination,
+ size_t NumberOfElements, const char *Source);
+
+ // Caller must free result using delete[].
+ static void CopyString(wchar_t *Destination,
+ size_t NumberOfElements, const wchar_t *Source);
+
+ static WideString MultibyteStringToWideString(const char* value);
+ static MultibyteString WideStringToMultibyteString(const wchar_t* value);
+};
+
+class Exception: public std::exception {
+private:
+ TString FMessage;
+
+protected:
+ void SetMessage(const TString Message) {
+ FMessage = Message;
+ }
+
+public:
+ explicit Exception() : exception() {}
+ explicit Exception(const TString Message) : exception() {
+ SetMessage(Message);
+ }
+ virtual ~Exception() throw() {}
+
+ TString GetMessage() { return FMessage; }
+};
+
+#endif // PLATFORM_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/native/libapplauncher/PlatformString.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "PlatformString.h"
+
+#include "Helpers.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdlib.h>
+#include <memory.h>
+#include <sstream>
+#include <string.h>
+
+#include "jni.h"
+
+void PlatformString::initialize() {
+ FWideTStringToFree = NULL;
+ FLength = 0;
+ FData = NULL;
+}
+
+PlatformString::PlatformString(void) {
+ initialize();
+}
+
+PlatformString::~PlatformString(void) {
+ if (FData != NULL) {
+ delete[] FData;
+ }
+
+ if (FWideTStringToFree != NULL) {
+ delete[] FWideTStringToFree;
+ }
+}
+
+PlatformString::PlatformString(const PlatformString &value) {
+ initialize();
+ FLength = value.FLength;
+ FData = new char[FLength + 1];
+ Platform::CopyString(FData, FLength + 1, value.FData);
+}
+
+PlatformString::PlatformString(const char* value) {
+ initialize();
+ FLength = strlen(value);
+ FData = new char[FLength + 1];
+ Platform::CopyString(FData, FLength + 1, value);
+}
+
+PlatformString::PlatformString(size_t Value) {
+ initialize();
+
+ std::stringstream ss;
+ std::string s;
+ ss << Value;
+ s = ss.str();
+
+ FLength = strlen(s.c_str());
+ FData = new char[FLength + 1];
+ Platform::CopyString(FData, FLength + 1, s.c_str());
+}
+
+PlatformString::PlatformString(const wchar_t* value) {
+ initialize();
+ MultibyteString temp = Platform::WideStringToMultibyteString(value);
+ FLength = temp.length;
+ FData = temp.data;
+}
+
+PlatformString::PlatformString(const std::string &value) {
+ initialize();
+ const char* lvalue = value.data();
+ FLength = value.size();
+ FData = new char[FLength + 1];
+ Platform::CopyString(FData, FLength + 1, lvalue);
+}
+
+PlatformString::PlatformString(const std::wstring &value) {
+ initialize();
+ const wchar_t* lvalue = value.data();
+ MultibyteString temp = Platform::WideStringToMultibyteString(lvalue);
+ FLength = temp.length;
+ FData = temp.data;
+}
+
+TString PlatformString::Format(const TString value, ...) {
+ TString result = value;
+
+ va_list arglist;
+ va_start(arglist, value);
+
+ while (1) {
+ size_t pos = result.find(_T("%s"), 0);
+
+ if (pos == TString::npos) {
+ break;
+ }
+ else {
+ TCHAR* arg = va_arg(arglist, TCHAR*);
+
+ if (arg == NULL) {
+ break;
+ }
+ else {
+ result.replace(pos, StringLength(_T("%s")), arg);
+ }
+ }
+ }
+
+ va_end(arglist);
+
+ return result;
+}
+
+size_t PlatformString::length() {
+ return FLength;
+}
+
+char* PlatformString::c_str() {
+ return FData;
+}
+
+char* PlatformString::toMultibyte() {
+ return FData;
+}
+
+wchar_t* PlatformString::toWideString() {
+ WideString result = Platform::MultibyteStringToWideString(FData);
+
+ if (result.data != NULL) {
+ if (FWideTStringToFree != NULL) {
+ delete [] FWideTStringToFree;
+ }
+
+ FWideTStringToFree = result.data;
+ }
+
+ return result.data;
+}
+
+std::wstring PlatformString::toUnicodeString() {
+ std::wstring result;
+ wchar_t* data = toWideString();
+
+ if (FLength != 0 && data != NULL) {
+ // NOTE: Cleanup of result is handled by PlatformString destructor.
+ result = data;
+ }
+
+ return result;
+}
+
+std::string PlatformString::toStdString() {
+ std::string result;
+ char* data = toMultibyte();
+
+ if (FLength > 0 && data != NULL) {
+ result = data;
+ }
+
+ return result;
+}
+
+TCHAR* PlatformString::toPlatformString() {
+#ifdef _UNICODE
+ return toWideString();
+#else
+ return c_str();
+#endif //_UNICODE
+}
+
+TString PlatformString::toString() {
+#ifdef _UNICODE
+ return toUnicodeString();
+#else
+ return toStdString();
+#endif //_UNICODE
+}
+
+PlatformString::operator char* () {
+ return c_str();
+}
+
+PlatformString::operator wchar_t* () {
+ return toWideString();
+}
+
+PlatformString::operator std::wstring () {
+ return toUnicodeString();
+}
+
+char* PlatformString::duplicate(const char* Value) {
+ size_t length = strlen(Value);
+ char* result = new char[length + 1];
+ Platform::CopyString(result, length + 1, Value);
+ return result;
+}
+
+wchar_t* PlatformString::duplicate(const wchar_t* Value) {
+ size_t length = wcslen(Value);
+ wchar_t* result = new wchar_t[length + 1];
+ Platform::CopyString(result, length + 1, Value);
+ return result;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/native/libapplauncher/PlatformString.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef PLATFORMSTRING_H
+#define PLATFORMSTRING_H
+
+
+#include <string>
+#include <list>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "jni.h"
+#include "Platform.h"
+
+
+template <typename T>
+class DynamicBuffer {
+private:
+ T* FData;
+ size_t FSize;
+
+public:
+ DynamicBuffer(size_t Size) {
+ FSize = 0;
+ FData = NULL;
+ Resize(Size);
+ }
+
+ ~DynamicBuffer() {
+ delete[] FData;
+ }
+
+ T* GetData() { return FData; }
+ size_t GetSize() { return FSize; }
+
+ bool Resize(size_t Size) {
+ FSize = Size;
+
+ if (FData != NULL) {
+ delete[] FData;
+ FData = NULL;
+ }
+
+ if (FSize != 0) {
+ FData = new T[FSize];
+ if (FData != NULL) {
+ Zero();
+ } else {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ void Zero() {
+ memset(FData, 0, FSize * sizeof(T));
+ }
+
+ T& operator[](size_t index) {
+ return FData[index];
+ }
+};
+
+class PlatformString {
+private:
+ char* FData; // Stored as UTF-8
+ size_t FLength;
+ wchar_t* FWideTStringToFree;
+
+ void initialize();
+
+// Prohibit Heap-Based PlatformStrings
+private:
+ static void *operator new(size_t size);
+ static void operator delete(void *ptr);
+
+public:
+ PlatformString(void);
+ PlatformString(const PlatformString &value);
+ PlatformString(const char* value);
+ PlatformString(const wchar_t* value);
+ PlatformString(const std::string &value);
+ PlatformString(const std::wstring &value);
+ PlatformString(size_t Value);
+
+ static TString Format(const TString value, ...);
+
+ ~PlatformString(void);
+
+ size_t length();
+
+ char* c_str();
+ char* toMultibyte();
+ wchar_t* toWideString();
+ std::wstring toUnicodeString();
+ std::string toStdString();
+ TCHAR* toPlatformString();
+ TString toString();
+
+ operator char* ();
+ operator wchar_t* ();
+ operator std::wstring ();
+
+ // Caller must free result using delete[].
+ static char* duplicate(const char* Value);
+
+ // Caller must free result using delete[].
+ static wchar_t* duplicate(const wchar_t* Value);
+};
+
+
+#endif // PLATFORMSTRING_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/native/libapplauncher/Properties.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef PROPERTIES_H
+#define PROPERTIES_H
+
+#include "PlatformDefs.h"
+#include "OrderedMap.h"
+
+//#include <stdio.h>
+//#include <stdlib.h>
+//#include <memory.h>
+//#include <string>
+//#include <map>
+//#include <list>
+//#include <vector>
+//#include <fstream>
+
+//using namespace std;
+
+template <typename ObjectType, typename ValueType,
+ ValueType (ObjectType::*getter)(void),
+ void (ObjectType::*setter)(ValueType)>
+class Property {
+private:
+ ObjectType* FObject;
+
+public:
+ Property() {
+ FObject = NULL;
+ }
+
+ void SetInstance(ObjectType* Value) {
+ FObject = Value;
+ }
+
+ // To set the value using the set method.
+ ValueType operator =(const ValueType& Value) {
+ assert(FObject != NULL);
+ (FObject->*setter)(Value);
+ return Value;
+ }
+
+ // The Property class is treated as the internal type.
+ operator ValueType() {
+ assert(FObject != NULL);
+ return (FObject->*getter)();
+ }
+};
+
+template <typename ObjectType, typename ValueType,
+ ValueType (ObjectType::*getter)(void)>
+class ReadProperty {
+private:
+ ObjectType* FObject;
+
+public:
+ ReadProperty() {
+ FObject = NULL;
+ }
+
+ void SetInstance(ObjectType* Value) {
+ FObject = Value;
+ }
+
+ // The Property class is treated as the internal type.
+ operator ValueType() {
+ assert(FObject != NULL);
+ return (FObject->*getter)();
+ }
+};
+
+template <typename ObjectType, typename ValueType,
+ void (ObjectType::*setter)(ValueType)>
+class WriteProperty {
+private:
+ ObjectType* FObject;
+
+public:
+ WriteProperty() {
+ FObject = NULL;
+ }
+
+ void SetInstance(ObjectType* Value) {
+ FObject = Value;
+ }
+
+ // To set the value using the set method.
+ ValueType operator =(const ValueType& Value) {
+ assert(FObject != NULL);
+ (FObject->*setter)(Value);
+ return Value;
+ }
+};
+
+template <typename ValueType,
+ ValueType (*getter)(void), void (*setter)(ValueType)>
+class StaticProperty {
+public:
+ StaticProperty() {
+ }
+
+ // To set the value using the set method.
+ ValueType operator =(const ValueType& Value) {
+ (*getter)(Value);
+ return Value;
+ }
+
+ // The Property class is treated as the internal type which is the getter.
+ operator ValueType() {
+ return (*setter)();
+ }
+};
+
+template <typename ValueType, ValueType (*getter)(void)>
+class StaticReadProperty {
+public:
+ StaticReadProperty() {
+ }
+
+ // The Property class is treated as the internal type which is the getter.
+ operator ValueType() {
+ return (*getter)();
+ }
+};
+
+template <typename ValueType, void (*setter)(ValueType)>
+class StaticWriteProperty {
+public:
+ StaticWriteProperty() {
+ }
+
+ // To set the value using the set method.
+ ValueType operator =(const ValueType& Value) {
+ (*setter)(Value);
+ return Value;
+ }
+};
+
+class IPropertyContainer {
+public:
+ IPropertyContainer(void) {}
+ virtual ~IPropertyContainer(void) {}
+
+ virtual bool GetValue(const TString Key, TString& Value) = 0;
+ virtual size_t GetCount() = 0;
+};
+
+class ISectionalPropertyContainer {
+public:
+ ISectionalPropertyContainer(void) {}
+ virtual ~ISectionalPropertyContainer(void) {}
+
+ virtual bool GetValue(const TString SectionName,
+ const TString Key, TString& Value) = 0;
+ virtual bool ContainsSection(const TString SectionName) = 0;
+ virtual bool GetSection(const TString SectionName,
+ OrderedMap<TString, TString> &Data) = 0;
+};
+
+#endif // PROPERTIES_H
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/native/libapplauncher/PropertyFile.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "PropertyFile.h"
+
+#include "Helpers.h"
+#include "FilePath.h"
+
+#include <string>
+
+
+PropertyFile::PropertyFile(void) : IPropertyContainer() {
+ FReadOnly = false;
+ FModified = false;
+}
+
+PropertyFile::PropertyFile(const TString FileName) : IPropertyContainer() {
+ FReadOnly = true;
+ FModified = false;
+ LoadFromFile(FileName);
+}
+
+PropertyFile::PropertyFile(OrderedMap<TString, TString> Value) {
+ FData.Append(Value);
+}
+
+PropertyFile::PropertyFile(PropertyFile &Value) {
+ FData = Value.FData;
+ FReadOnly = Value.FReadOnly;
+ FModified = Value.FModified;
+}
+
+PropertyFile::~PropertyFile(void) {
+ FData.Clear();
+}
+
+void PropertyFile::SetModified(bool Value) {
+ FModified = Value;
+}
+
+bool PropertyFile::IsModified() {
+ return FModified;
+}
+
+bool PropertyFile::GetReadOnly() {
+ return FReadOnly;
+}
+
+void PropertyFile::SetReadOnly(bool Value) {
+ FReadOnly = Value;
+}
+
+bool PropertyFile::LoadFromFile(const TString FileName) {
+ bool result = false;
+ Platform& platform = Platform::GetInstance();
+
+ std::list<TString> contents = platform.LoadFromFile(FileName);
+
+ if (contents.empty() == false) {
+ for (std::list<TString>::const_iterator iterator = contents.begin();
+ iterator != contents.end(); iterator++) {
+ TString line = *iterator;
+ TString name;
+ TString value;
+
+ if (Helpers::SplitOptionIntoNameValue(line, name, value) == true) {
+ FData.Append(name, value);
+ }
+ }
+
+ SetModified(false);
+ result = true;
+ }
+
+ return result;
+}
+
+bool PropertyFile::SaveToFile(const TString FileName, bool ownerOnly) {
+ bool result = false;
+
+ if (GetReadOnly() == false && IsModified()) {
+ std::list<TString> contents;
+ std::vector<TString> keys = FData.GetKeys();
+
+ for (size_t index = 0; index < keys.size(); index++) {
+ TString name = keys[index];
+
+ try {
+ TString value;// = FData[index];
+
+ if (FData.GetValue(name, value) == true) {
+ TString line = name + _T('=') + value;
+ contents.push_back(line);
+ }
+ }
+ catch (std::out_of_range &) {
+ }
+ }
+
+ Platform& platform = Platform::GetInstance();
+ platform.SaveToFile(FileName, contents, ownerOnly);
+
+ SetModified(false);
+ result = true;
+ }
+
+ return result;
+}
+
+bool PropertyFile::GetValue(const TString Key, TString& Value) {
+ return FData.GetValue(Key, Value);
+}
+
+bool PropertyFile::SetValue(const TString Key, TString Value) {
+ bool result = false;
+
+ if (GetReadOnly() == false) {
+ FData.SetValue(Key, Value);
+ SetModified(true);
+ result = true;
+ }
+
+ return result;
+}
+
+bool PropertyFile::RemoveKey(const TString Key) {
+ bool result = false;
+
+ if (GetReadOnly() == false) {
+ result = FData.RemoveByKey(Key);
+
+ if (result == true) {
+ SetModified(true);
+ }
+ }
+
+ return result;
+}
+
+size_t PropertyFile::GetCount() {
+ return FData.Count();
+}
+
+OrderedMap<TString, TString> PropertyFile::GetData() {
+ return FData;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/native/libapplauncher/PropertyFile.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef PROPERTYFILE_H
+#define PROPERTYFILE_H
+
+#include "Platform.h"
+#include "Helpers.h"
+
+
+class PropertyFile : public IPropertyContainer {
+private:
+ bool FReadOnly;
+ bool FModified;
+ OrderedMap<TString, TString> FData;
+
+ void SetModified(bool Value);
+
+public:
+ PropertyFile(void);
+ PropertyFile(const TString FileName);
+ PropertyFile(OrderedMap<TString, TString> Value);
+ PropertyFile(PropertyFile &Value);
+ virtual ~PropertyFile(void);
+
+ bool IsModified();
+ bool GetReadOnly();
+ void SetReadOnly(bool Value);
+
+ bool LoadFromFile(const TString FileName);
+ bool SaveToFile(const TString FileName, bool ownerOnly = true);
+
+ bool SetValue(const TString Key, TString Value);
+ bool RemoveKey(const TString Key);
+
+ OrderedMap<TString, TString> GetData();
+
+ // IPropertyContainer
+ virtual bool GetValue(const TString Key, TString& Value);
+ virtual size_t GetCount();
+};
+
+#endif // PROPERTYFILE_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/native/libapplauncher/main.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "Platform.h"
+#include "PlatformString.h"
+#include "FilePath.h"
+#include "PropertyFile.h"
+#include "JavaVirtualMachine.h"
+#include "Package.h"
+#include "Macros.h"
+#include "Messages.h"
+
+#include <stdio.h>
+#include <signal.h>
+#include <stdlib.h>
+
+/*
+This is the app launcher program for application packaging on Windows, Mac,
+ and Linux.
+
+Basic approach:
+ - Launcher (jpackageapplauncher) is executable that loads
+ applauncher.dll/libapplauncher.dylib/libapplauncher.so
+ and calls start_launcher below.
+ - Reads app/package.cfg or Info.plist or app/<appname>.cfg for application
+ launch configuration (package.cfg is property file).
+ - Load Java with requested Java settings (bundled client Java if availble,
+ server or installed Java otherwise).
+ - Wait for Java to exit and then exit from Main
+ - To debug application by passing command line argument.
+ - Application folder is added to the library path (so LoadLibrary()) works.
+
+Limitations and future work:
+ - Running Java code in primordial thread may cause problems
+ (example: can not use custom stack size).
+ Solution used by java launcher is to create a new thread to invoke Java.
+ See CR 6316197 for more information.
+*/
+
+extern "C" {
+
+ JNIEXPORT bool start_launcher(int argc, TCHAR* argv[]) {
+ bool result = false;
+ bool parentProcess = true;
+
+ // Platform must be initialize first.
+ Platform& platform = Platform::GetInstance();
+
+ try {
+ for (int index = 0; index < argc; index++) {
+ TString argument = argv[index];
+
+ if (argument == _T("-Xappcds:generatecache")) {
+ platform.SetAppCDSState(cdsGenCache);
+ }
+ else if (argument == _T("-Xappcds:off")) {
+ platform.SetAppCDSState(cdsDisabled);
+ }
+ else if (argument == _T("-Xapp:child")) {
+ parentProcess = false;
+ }
+ }
+
+ // Package must be initialized after Platform is fully initialized.
+ Package& package = Package::GetInstance();
+ Macros::Initialize();
+ package.SetCommandLineArguments(argc, argv);
+
+ switch (platform.GetAppCDSState()) {
+ case cdsDisabled:
+ case cdsUninitialized:
+ case cdsEnabled: {
+ break;
+ }
+
+ case cdsGenCache: {
+ TString cacheDirectory = package.GetAppCDSCacheDirectory();
+
+ if (FilePath::DirectoryExists(cacheDirectory) == false) {
+ FilePath::CreateDirectory(cacheDirectory, true);
+ } else {
+ TString cacheFileName =
+ package.GetAppCDSCacheFileName();
+ if (FilePath::FileExists(cacheFileName) == true) {
+ FilePath::DeleteFile(cacheFileName);
+ }
+ }
+
+ break;
+ }
+
+ case cdsAuto: {
+ TString cacheFileName = package.GetAppCDSCacheFileName();
+
+ if (parentProcess == true &&
+ FilePath::FileExists(cacheFileName) == false) {
+ AutoFreePtr<Process> process = platform.CreateProcess();
+ std::vector<TString> args;
+ args.push_back(_T("-Xappcds:generatecache"));
+ args.push_back(_T("-Xapp:child"));
+ process->Execute(
+ platform.GetModuleFileName(), args, true);
+
+ if (FilePath::FileExists(cacheFileName) == false) {
+ // Cache does not exist after trying to generate it,
+ // so run without cache.
+ platform.SetAppCDSState(cdsDisabled);
+ package.Clear();
+ package.Initialize();
+ }
+ }
+
+ break;
+ }
+ }
+
+ // Validation
+ switch (platform.GetAppCDSState()) {
+ case cdsDisabled:
+ case cdsGenCache: {
+ // Do nothing.
+ break;
+ }
+
+ case cdsEnabled:
+ case cdsAuto: {
+ TString cacheFileName =
+ package.GetAppCDSCacheFileName();
+
+ if (FilePath::FileExists(cacheFileName) == false) {
+ Messages& messages = Messages::GetInstance();
+ TString message = PlatformString::Format(
+ messages.GetMessage(
+ APPCDS_CACHE_FILE_NOT_FOUND),
+ cacheFileName.data());
+ throw Exception(message);
+ }
+ break;
+ }
+
+ case cdsUninitialized: {
+ platform.ShowMessage(_T("Internal Error"));
+ break;
+ }
+ }
+
+ // Run App
+ result = RunVM();
+ } catch (Exception &e) {
+ platform.ShowMessage(e.GetMessage());
+ }
+
+ return result;
+ }
+
+ JNIEXPORT void stop_launcher() {
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/unix/native/libapplauncher/FileAttribute.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef FILEATTRIBUTE_H
+#define FILEATTRIBUTE_H
+
+enum FileAttribute {
+ faBlockSpecial,
+ faCharacterSpecial,
+ faFIFOSpecial,
+ faNormal,
+ faDirectory,
+ faSymbolicLink,
+ faSocket,
+
+ // Owner
+ faReadOnly,
+ faWriteOnly,
+ faReadWrite,
+ faExecute,
+
+ // Group
+ faGroupReadOnly,
+ faGroupWriteOnly,
+ faGroupReadWrite,
+ faGroupExecute,
+
+ // Others
+ faOthersReadOnly,
+ faOthersWriteOnly,
+ faOthersReadWrite,
+ faOthersExecute,
+
+ faHidden
+};
+
+#endif // FILEATTRIBUTE_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/unix/native/libapplauncher/FileAttributes.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,331 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "FileAttributes.h"
+
+#include <algorithm>
+#include <list>
+#include <sys/stat.h>
+
+FileAttributes::FileAttributes(const TString FileName, bool FollowLink) {
+ FFileName = FileName;
+ FFollowLink = FollowLink;
+ ReadAttributes();
+}
+
+bool FileAttributes::WriteAttributes() {
+ bool result = false;
+
+ mode_t attributes = 0;
+
+ for (std::vector<FileAttribute>::const_iterator iterator =
+ FAttributes.begin();
+ iterator != FAttributes.end(); iterator++) {
+ switch (*iterator) {
+ case faBlockSpecial:
+ {
+ attributes |= S_IFBLK;
+ break;
+ }
+ case faCharacterSpecial:
+ {
+ attributes |= S_IFCHR;
+ break;
+ }
+ case faFIFOSpecial:
+ {
+ attributes |= S_IFIFO;
+ break;
+ }
+ case faNormal:
+ {
+ attributes |= S_IFREG;
+ break;
+ }
+ case faDirectory:
+ {
+ attributes |= S_IFDIR;
+ break;
+ }
+ case faSymbolicLink:
+ {
+ attributes |= S_IFLNK;
+ break;
+ }
+ case faSocket:
+ {
+ attributes |= S_IFSOCK;
+ break;
+ }
+
+ // Owner
+ case faReadOnly:
+ {
+ attributes |= S_IRUSR;
+ break;
+ }
+ case faWriteOnly:
+ {
+ attributes |= S_IWUSR;
+ break;
+ }
+ case faReadWrite:
+ {
+ attributes |= S_IRUSR;
+ attributes |= S_IWUSR;
+ break;
+ }
+ case faExecute:
+ {
+ attributes |= S_IXUSR;
+ break;
+ }
+
+ // Group
+ case faGroupReadOnly:
+ {
+ attributes |= S_IRGRP;
+ break;
+ }
+ case faGroupWriteOnly:
+ {
+ attributes |= S_IWGRP;
+ break;
+ }
+ case faGroupReadWrite:
+ {
+ attributes |= S_IRGRP;
+ attributes |= S_IWGRP;
+ break;
+ }
+ case faGroupExecute:
+ {
+ attributes |= S_IXGRP;
+ break;
+ }
+
+ // Others
+ case faOthersReadOnly:
+ {
+ attributes |= S_IROTH;
+ break;
+ }
+ case faOthersWriteOnly:
+ {
+ attributes |= S_IWOTH;
+ break;
+ }
+ case faOthersReadWrite:
+ {
+ attributes |= S_IROTH;
+ attributes |= S_IWOTH;
+ break;
+ }
+ case faOthersExecute:
+ {
+ attributes |= S_IXOTH;
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ if (chmod(FFileName.data(), attributes) == 0) {
+ result = true;
+ }
+
+ return result;
+}
+
+#define S_ISRUSR(m) (((m) & S_IRWXU) == S_IRUSR)
+#define S_ISWUSR(m) (((m) & S_IRWXU) == S_IWUSR)
+#define S_ISXUSR(m) (((m) & S_IRWXU) == S_IXUSR)
+
+#define S_ISRGRP(m) (((m) & S_IRWXG) == S_IRGRP)
+#define S_ISWGRP(m) (((m) & S_IRWXG) == S_IWGRP)
+#define S_ISXGRP(m) (((m) & S_IRWXG) == S_IXGRP)
+
+#define S_ISROTH(m) (((m) & S_IRWXO) == S_IROTH)
+#define S_ISWOTH(m) (((m) & S_IRWXO) == S_IWOTH)
+#define S_ISXOTH(m) (((m) & S_IRWXO) == S_IXOTH)
+
+bool FileAttributes::ReadAttributes() {
+ bool result = false;
+
+ struct stat status;
+
+ if (stat(StringToFileSystemString(FFileName), &status) == 0) {
+ result = true;
+
+ if (S_ISBLK(status.st_mode) != 0) {
+ FAttributes.push_back(faBlockSpecial);
+ }
+ if (S_ISCHR(status.st_mode) != 0) {
+ FAttributes.push_back(faCharacterSpecial);
+ }
+ if (S_ISFIFO(status.st_mode) != 0) {
+ FAttributes.push_back(faFIFOSpecial);
+ }
+ if (S_ISREG(status.st_mode) != 0) {
+ FAttributes.push_back(faNormal);
+ }
+ if (S_ISDIR(status.st_mode) != 0) {
+ FAttributes.push_back(faDirectory);
+ }
+ if (S_ISLNK(status.st_mode) != 0) {
+ FAttributes.push_back(faSymbolicLink);
+ }
+ if (S_ISSOCK(status.st_mode) != 0) {
+ FAttributes.push_back(faSocket);
+ }
+
+ // Owner
+ if (S_ISRUSR(status.st_mode) != 0) {
+ if (S_ISWUSR(status.st_mode) != 0) {
+ FAttributes.push_back(faReadWrite);
+ } else {
+ FAttributes.push_back(faReadOnly);
+ }
+ } else if (S_ISWUSR(status.st_mode) != 0) {
+ FAttributes.push_back(faWriteOnly);
+ }
+
+ if (S_ISXUSR(status.st_mode) != 0) {
+ FAttributes.push_back(faExecute);
+ }
+
+ // Group
+ if (S_ISRGRP(status.st_mode) != 0) {
+ if (S_ISWGRP(status.st_mode) != 0) {
+ FAttributes.push_back(faGroupReadWrite);
+ } else {
+ FAttributes.push_back(faGroupReadOnly);
+ }
+ } else if (S_ISWGRP(status.st_mode) != 0) {
+ FAttributes.push_back(faGroupWriteOnly);
+ }
+
+ if (S_ISXGRP(status.st_mode) != 0) {
+ FAttributes.push_back(faGroupExecute);
+ }
+
+
+ // Others
+ if (S_ISROTH(status.st_mode) != 0) {
+ if (S_ISWOTH(status.st_mode) != 0) {
+ FAttributes.push_back(faOthersReadWrite);
+ } else {
+ FAttributes.push_back(faOthersReadOnly);
+ }
+ } else if (S_ISWOTH(status.st_mode) != 0) {
+ FAttributes.push_back(faOthersWriteOnly);
+ }
+
+ if (S_ISXOTH(status.st_mode) != 0) {
+ FAttributes.push_back(faOthersExecute);
+ }
+
+ if (FFileName.size() > 0 && FFileName[0] == '.') {
+ FAttributes.push_back(faHidden);
+ }
+ }
+
+ return result;
+}
+
+bool FileAttributes::Valid(const FileAttribute Value) {
+ bool result = false;
+
+ switch (Value) {
+ case faReadWrite:
+ case faWriteOnly:
+ case faExecute:
+
+ case faGroupReadWrite:
+ case faGroupWriteOnly:
+ case faGroupReadOnly:
+ case faGroupExecute:
+
+ case faOthersReadWrite:
+ case faOthersWriteOnly:
+ case faOthersReadOnly:
+ case faOthersExecute:
+
+ case faReadOnly:
+ result = true;
+ break;
+
+ default:
+ break;
+ }
+
+ return result;
+}
+
+void FileAttributes::Append(FileAttribute Value) {
+ if (Valid(Value) == true) {
+ if ((Value == faReadOnly && Contains(faWriteOnly) == true) ||
+ (Value == faWriteOnly && Contains(faReadOnly) == true)) {
+ Value = faReadWrite;
+ }
+
+ FAttributes.push_back(Value);
+ WriteAttributes();
+ }
+}
+
+bool FileAttributes::Contains(FileAttribute Value) {
+ bool result = false;
+
+ std::vector<FileAttribute>::const_iterator iterator =
+ std::find(FAttributes.begin(), FAttributes.end(), Value);
+
+ if (iterator != FAttributes.end()) {
+ result = true;
+ }
+
+ return result;
+}
+
+void FileAttributes::Remove(FileAttribute Value) {
+ if (Valid(Value) == true) {
+ if (Value == faReadOnly && Contains(faReadWrite) == true) {
+ Append(faWriteOnly);
+ Remove(faReadWrite);
+ } else if (Value == faWriteOnly && Contains(faReadWrite) == true) {
+ Append(faReadOnly);
+ Remove(faReadWrite);
+ }
+
+ std::vector<FileAttribute>::iterator iterator =
+ std::find(FAttributes.begin(), FAttributes.end(), Value);
+
+ if (iterator != FAttributes.end()) {
+ FAttributes.erase(iterator);
+ WriteAttributes();
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/unix/native/libapplauncher/FilePath.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "PlatformDefs.h"
+#include "FilePath.h"
+
+#include <algorithm>
+#include <list>
+#include <sys/stat.h>
+
+bool FilePath::FileExists(const TString FileName) {
+ bool result = false;
+ struct stat buf;
+
+ if ((stat(StringToFileSystemString(FileName), &buf) == 0) &&
+ (S_ISREG(buf.st_mode) != 0)) {
+ result = true;
+ }
+
+ return result;
+}
+
+bool FilePath::DirectoryExists(const TString DirectoryName) {
+ bool result = false;
+
+ struct stat buf;
+
+ if ((stat(StringToFileSystemString(DirectoryName), &buf) == 0) &&
+ (S_ISDIR(buf.st_mode) != 0)) {
+ result = true;
+ }
+
+ return result;
+}
+
+bool FilePath::DeleteFile(const TString FileName) {
+ bool result = false;
+
+ if (FileExists(FileName) == true) {
+ if (unlink(StringToFileSystemString(FileName)) == 0) {
+ result = true;
+ }
+ }
+
+ return result;
+}
+
+bool FilePath::DeleteDirectory(const TString DirectoryName) {
+ bool result = false;
+
+ if (DirectoryExists(DirectoryName) == true) {
+ if (unlink(StringToFileSystemString(DirectoryName)) == 0) {
+ result = true;
+ }
+ }
+
+ return result;
+}
+
+TString FilePath::IncludeTrailingSeparator(const TString value) {
+ TString result = value;
+
+ if (value.size() > 0) {
+ TString::iterator i = result.end();
+ i--;
+
+ if (*i != TRAILING_PATHSEPARATOR) {
+ result += TRAILING_PATHSEPARATOR;
+ }
+ }
+
+ return result;
+}
+
+TString FilePath::IncludeTrailingSeparator(const char* value) {
+ TString lvalue = PlatformString(value).toString();
+ return IncludeTrailingSeparator(lvalue);
+}
+
+TString FilePath::IncludeTrailingSeparator(const wchar_t* value) {
+ TString lvalue = PlatformString(value).toString();
+ return IncludeTrailingSeparator(lvalue);
+}
+
+TString FilePath::ExtractFilePath(TString Path) {
+ return dirname(StringToFileSystemString(Path));
+}
+
+TString FilePath::ExtractFileExt(TString Path) {
+ TString result;
+ size_t dot = Path.find_last_of('.');
+
+ if (dot != TString::npos) {
+ result = Path.substr(dot, Path.size() - dot);
+ }
+
+ return result;
+}
+
+TString FilePath::ExtractFileName(TString Path) {
+ return basename(StringToFileSystemString(Path));
+}
+
+TString FilePath::ChangeFileExt(TString Path, TString Extension) {
+ TString result;
+ size_t dot = Path.find_last_of('.');
+
+ if (dot != TString::npos) {
+ result = Path.substr(0, dot) + Extension;
+ }
+
+ if (result.empty() == true) {
+ result = Path;
+ }
+
+ return result;
+}
+
+TString FilePath::FixPathForPlatform(TString Path) {
+ TString result = Path;
+ std::replace(result.begin(), result.end(),
+ BAD_TRAILING_PATHSEPARATOR, TRAILING_PATHSEPARATOR);
+ return result;
+}
+
+TString FilePath::FixPathSeparatorForPlatform(TString Path) {
+ TString result = Path;
+ std::replace(result.begin(), result.end(),
+ BAD_PATH_SEPARATOR, PATH_SEPARATOR);
+ return result;
+}
+
+TString FilePath::PathSeparator() {
+ TString result;
+ result = PATH_SEPARATOR;
+ return result;
+}
+
+bool FilePath::CreateDirectory(TString Path, bool ownerOnly) {
+ bool result = false;
+
+ std::list<TString> paths;
+ TString lpath = Path;
+
+ while (lpath.empty() == false && DirectoryExists(lpath) == false) {
+ paths.push_front(lpath);
+ lpath = ExtractFilePath(lpath);
+ }
+
+ for (std::list<TString>::iterator iterator = paths.begin();
+ iterator != paths.end(); iterator++) {
+ lpath = *iterator;
+
+ mode_t mode = S_IRWXU;
+ if (!ownerOnly) {
+ mode |= S_IRWXG | S_IROTH | S_IXOTH;
+ }
+ if (mkdir(StringToFileSystemString(lpath), mode) == 0) {
+ result = true;
+ } else {
+ result = false;
+ break;
+ }
+ }
+
+ return result;
+}
+
+void FilePath::ChangePermissions(TString FileName, bool ownerOnly) {
+ mode_t mode = S_IRWXU;
+ if (!ownerOnly) {
+ mode |= S_IRWXG | S_IROTH | S_IXOTH;
+ }
+ chmod(FileName.data(), mode);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/unix/native/libapplauncher/PosixPlatform.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "PosixPlatform.h"
+
+#include "PlatformString.h"
+#include "FilePath.h"
+#include "Helpers.h"
+
+#include <assert.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/sysctl.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <limits.h>
+#include <pwd.h>
+#include <iostream>
+#include <algorithm>
+#include <dlfcn.h>
+#include <signal.h>
+
+using namespace std;
+
+PosixPlatform::PosixPlatform(void) {
+}
+
+PosixPlatform::~PosixPlatform(void) {
+}
+
+TString PosixPlatform::GetTempDirectory() {
+ struct passwd* pw = getpwuid(getuid());
+ TString homedir(pw->pw_dir);
+ homedir += getTmpDirString();
+ if (!FilePath::DirectoryExists(homedir)) {
+ if (!FilePath::CreateDirectory(homedir, false)) {
+ homedir.clear();
+ }
+ }
+
+ return homedir;
+}
+
+TString PosixPlatform::fixName(const TString& name) {
+ TString fixedName(name);
+ const TString chars("?:*<>/\\");
+ for (TString::const_iterator it = chars.begin(); it != chars.end(); it++) {
+ fixedName.erase(std::remove(fixedName.begin(),
+ fixedName.end(), *it), fixedName.end());
+ }
+ return fixedName;
+}
+
+MessageResponse PosixPlatform::ShowResponseMessage(TString title,
+ TString description) {
+ MessageResponse result = mrCancel;
+
+ printf("%s %s (Y/N)\n", PlatformString(title).toPlatformString(),
+ PlatformString(description).toPlatformString());
+ fflush(stdout);
+
+ std::string input;
+ std::cin >> input;
+
+ if (input == "Y") {
+ result = mrOK;
+ }
+
+ return result;
+}
+
+Module PosixPlatform::LoadLibrary(TString FileName) {
+ return dlopen(StringToFileSystemString(FileName), RTLD_LAZY);
+}
+
+void PosixPlatform::FreeLibrary(Module AModule) {
+ dlclose(AModule);
+}
+
+Procedure PosixPlatform::GetProcAddress(Module AModule,
+ std::string MethodName) {
+ return dlsym(AModule, PlatformString(MethodName));
+}
+
+Process* PosixPlatform::CreateProcess() {
+ return new PosixProcess();
+}
+
+void PosixPlatform::addPlatformDependencies(JavaLibrary *pJavaLibrary) {
+}
+
+void Platform::CopyString(char *Destination,
+ size_t NumberOfElements, const char *Source) {
+ strncpy(Destination, Source, NumberOfElements);
+
+ if (NumberOfElements > 0) {
+ Destination[NumberOfElements - 1] = '\0';
+ }
+}
+
+void Platform::CopyString(wchar_t *Destination,
+ size_t NumberOfElements, const wchar_t *Source) {
+ wcsncpy(Destination, Source, NumberOfElements);
+
+ if (NumberOfElements > 0) {
+ Destination[NumberOfElements - 1] = '\0';
+ }
+}
+
+// Owner must free the return value.
+
+MultibyteString Platform::WideStringToMultibyteString(
+ const wchar_t* value) {
+ MultibyteString result;
+ size_t count = 0;
+
+ if (value == NULL) {
+ return result;
+ }
+
+ count = wcstombs(NULL, value, 0);
+ if (count > 0) {
+ result.data = new char[count + 1];
+ result.data[count] = '\0';
+ result.length = count;
+ wcstombs(result.data, value, count);
+ }
+
+ return result;
+}
+
+// Owner must free the return value.
+
+WideString Platform::MultibyteStringToWideString(const char* value) {
+ WideString result;
+ size_t count = 0;
+
+ if (value == NULL) {
+ return result;
+ }
+
+ count = mbstowcs(NULL, value, 0);
+ if (count > 0) {
+ result.data = new wchar_t[count + 1];
+ result.data[count] = '\0';
+ result.length = count;
+ mbstowcs(result.data, value, count);
+ }
+
+ return result;
+}
+
+void PosixPlatform::InitStreamLocale(wios *stream) {
+ // Nothing to do for POSIX platforms.
+}
+
+PosixProcess::PosixProcess() : Process() {
+ FChildPID = 0;
+ FRunning = false;
+ FOutputHandle = 0;
+ FInputHandle = 0;
+}
+
+PosixProcess::~PosixProcess() {
+ Terminate();
+}
+
+bool PosixProcess::ReadOutput() {
+ bool result = false;
+
+ if (FOutputHandle != 0 && IsRunning() == true) {
+ char buffer[4096] = {0};
+
+ ssize_t count = read(FOutputHandle, buffer, sizeof (buffer));
+
+ if (count == -1) {
+ if (errno == EINTR) {
+ // continue;
+ } else {
+ perror("read");
+ exit(1);
+ }
+ } else if (count == 0) {
+ // break;
+ } else {
+ std::list<TString> output = Helpers::StringToArray(buffer);
+ FOutput.splice(FOutput.end(), output, output.begin(), output.end());
+ result = true;
+ }
+ }
+
+ return false;
+}
+
+bool PosixProcess::IsRunning() {
+ bool result = false;
+
+ if (kill(FChildPID, 0) == 0) {
+ result = true;
+ }
+
+ return result;
+}
+
+bool PosixProcess::Terminate() {
+ bool result = false;
+
+ if (IsRunning() == true && FRunning == true) {
+ FRunning = false;
+ Cleanup();
+ int status = kill(FChildPID, SIGTERM);
+
+ if (status == 0) {
+ result = true;
+ } else {
+#ifdef DEBUG
+ if (errno == EINVAL) {
+ printf("Kill error: The value of the sig argument is an invalid or unsupported signal number.");
+ } else if (errno == EPERM) {
+ printf("Kill error: The process does not have permission to send the signal to any receiving process.");
+ } else if (errno == ESRCH) {
+ printf("Kill error: No process or process group can be found corresponding to that specified by pid.");
+ }
+#endif // DEBUG
+ if (IsRunning() == true) {
+ status = kill(FChildPID, SIGKILL);
+
+ if (status == 0) {
+ result = true;
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+bool PosixProcess::Wait() {
+ bool result = false;
+
+ int status = 0;
+ pid_t wpid = 0;
+
+ wpid = wait(&status);
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+ if (errno != EINTR) {
+ status = -1;
+ }
+ }
+
+#ifdef DEBUG
+ if (WIFEXITED(status)) {
+ printf("child exited, status=%d\n", WEXITSTATUS(status));
+ } else if (WIFSIGNALED(status)) {
+ printf("child killed (signal %d)\n", WTERMSIG(status));
+ } else if (WIFSTOPPED(status)) {
+ printf("child stopped (signal %d)\n", WSTOPSIG(status));
+#ifdef WIFCONTINUED // Not all implementations support this
+ } else if (WIFCONTINUED(status)) {
+ printf("child continued\n");
+#endif // WIFCONTINUED
+ } else { // Non-standard case -- may never happen
+ printf("Unexpected status (0x%x)\n", status);
+ }
+#endif // DEBUG
+
+ if (wpid != -1) {
+ result = true;
+ }
+
+ return result;
+}
+
+TProcessID PosixProcess::GetProcessID() {
+ return FChildPID;
+}
+
+void PosixProcess::SetInput(TString Value) {
+ if (FInputHandle != 0) {
+ if (write(FInputHandle, Value.data(), Value.size()) < 0) {
+ throw Exception(_T("Internal Error - write failed"));
+ }
+ }
+}
+
+std::list<TString> PosixProcess::GetOutput() {
+ ReadOutput();
+ return Process::GetOutput();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/unix/native/libapplauncher/PosixPlatform.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef POSIXPLATFORM_H
+#define POSIXPLATFORM_H
+
+#include "Platform.h"
+#include <signal.h>
+
+class PosixPlatform : virtual public Platform {
+protected:
+
+ TString fixName(const TString& name);
+
+ virtual TString getTmpDirString() = 0;
+
+public:
+ PosixPlatform(void);
+ virtual ~PosixPlatform(void);
+
+public:
+ virtual MessageResponse ShowResponseMessage(TString title,
+ TString description);
+
+ virtual Module LoadLibrary(TString FileName);
+ virtual void FreeLibrary(Module AModule);
+ virtual Procedure GetProcAddress(Module AModule, std::string MethodName);
+
+ virtual Process* CreateProcess();
+ virtual TString GetTempDirectory();
+ void InitStreamLocale(wios *stream);
+ void addPlatformDependencies(JavaLibrary *pJavaLibrary);
+};
+
+class PosixProcess : public Process {
+private:
+ pid_t FChildPID;
+ sigset_t saveblock;
+ int FOutputHandle;
+ int FInputHandle;
+ struct sigaction savintr, savequit;
+ bool FRunning;
+
+ void Cleanup();
+ bool ReadOutput();
+
+public:
+ PosixProcess();
+ virtual ~PosixProcess();
+
+ virtual bool IsRunning();
+ virtual bool Terminate();
+ virtual bool Execute(const TString Application,
+ const std::vector<TString> Arguments, bool AWait = false);
+ virtual bool Wait();
+ virtual TProcessID GetProcessID();
+ virtual void SetInput(TString Value);
+ virtual std::list<TString> GetOutput();
+};
+
+#endif // POSIXPLATFORM_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WinAppBundler.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.text.MessageFormat;
+import java.util.*;
+
+import static jdk.incubator.jpackage.internal.WindowsBundlerParam.*;
+
+public class WinAppBundler extends AbstractImageBundler {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.incubator.jpackage.internal.resources.WinResources");
+
+ static final BundlerParamInfo<File> ICON_ICO =
+ new StandardBundlerParam<>(
+ "icon.ico",
+ File.class,
+ params -> {
+ File f = ICON.fetchFrom(params);
+ if (f != null && !f.getName().toLowerCase().endsWith(".ico")) {
+ Log.error(MessageFormat.format(
+ I18N.getString("message.icon-not-ico"), f));
+ return null;
+ }
+ return f;
+ },
+ (s, p) -> new File(s));
+
+ @Override
+ public boolean validate(Map<String, ? super Object> params)
+ throws ConfigException {
+ try {
+ Objects.requireNonNull(params);
+ return doValidate(params);
+ } catch (RuntimeException re) {
+ if (re.getCause() instanceof ConfigException) {
+ throw (ConfigException) re.getCause();
+ } else {
+ throw new ConfigException(re);
+ }
+ }
+ }
+
+ // to be used by chained bundlers, e.g. by EXE bundler to avoid
+ // skipping validation if p.type does not include "image"
+ private boolean doValidate(Map<String, ? super Object> p)
+ throws ConfigException {
+
+ imageBundleValidation(p);
+ return true;
+ }
+
+ public boolean bundle(Map<String, ? super Object> p, File outputDirectory)
+ throws PackagerException {
+ return doBundle(p, outputDirectory, false) != null;
+ }
+
+ File doBundle(Map<String, ? super Object> p, File outputDirectory,
+ boolean dependentTask) throws PackagerException {
+ if (StandardBundlerParam.isRuntimeInstaller(p)) {
+ return PREDEFINED_RUNTIME_IMAGE.fetchFrom(p);
+ } else {
+ return doAppBundle(p, outputDirectory, dependentTask);
+ }
+ }
+
+ File doAppBundle(Map<String, ? super Object> p, File outputDirectory,
+ boolean dependentTask) throws PackagerException {
+ try {
+ File rootDirectory = createRoot(p, outputDirectory, dependentTask,
+ APP_NAME.fetchFrom(p));
+ AbstractAppImageBuilder appBuilder =
+ new WindowsAppImageBuilder(p, outputDirectory.toPath());
+ if (PREDEFINED_RUNTIME_IMAGE.fetchFrom(p) == null ) {
+ JLinkBundlerHelper.execute(p, appBuilder);
+ } else {
+ StandardBundlerParam.copyPredefinedRuntimeImage(p, appBuilder);
+ }
+ if (!dependentTask) {
+ Log.verbose(MessageFormat.format(
+ I18N.getString("message.result-dir"),
+ outputDirectory.getAbsolutePath()));
+ }
+ return rootDirectory;
+ } catch (PackagerException pe) {
+ throw pe;
+ } catch (Exception e) {
+ Log.verbose(e);
+ throw new PackagerException(e);
+ }
+ }
+
+ @Override
+ public String getName() {
+ return I18N.getString("app.bundler.name");
+ }
+
+ @Override
+ public String getID() {
+ return "windows.app";
+ }
+
+ @Override
+ public String getBundleType() {
+ return "IMAGE";
+ }
+
+ @Override
+ public File execute(Map<String, ? super Object> params,
+ File outputParentDir) throws PackagerException {
+ return doBundle(params, outputParentDir, false);
+ }
+
+ @Override
+ public boolean supported(boolean platformInstaller) {
+ return true;
+ }
+
+ @Override
+ public boolean isDefault() {
+ return false;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WinExeBundler.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.incubator.jpackage.internal;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.text.MessageFormat;
+import java.util.*;
+
+public class WinExeBundler extends AbstractBundler {
+
+ static {
+ System.loadLibrary("jpackage");
+ }
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.incubator.jpackage.internal.resources.WinResources");
+
+ public static final BundlerParamInfo<WinAppBundler> APP_BUNDLER
+ = new WindowsBundlerParam<>(
+ "win.app.bundler",
+ WinAppBundler.class,
+ params -> new WinAppBundler(),
+ null);
+
+ public static final BundlerParamInfo<File> EXE_IMAGE_DIR
+ = new WindowsBundlerParam<>(
+ "win.exe.imageDir",
+ File.class,
+ params -> {
+ File imagesRoot = IMAGES_ROOT.fetchFrom(params);
+ if (!imagesRoot.exists()) {
+ imagesRoot.mkdirs();
+ }
+ return new File(imagesRoot, "win-exe.image");
+ },
+ (s, p) -> null);
+
+ private final static String EXE_WRAPPER_NAME = "msiwrapper.exe";
+
+ @Override
+ public String getName() {
+ return getString("exe.bundler.name");
+ }
+
+ @Override
+ public String getID() {
+ return "exe";
+ }
+
+ @Override
+ public String getBundleType() {
+ return "INSTALLER";
+ }
+
+ @Override
+ public File execute(Map<String, ? super Object> params,
+ File outputParentDir) throws PackagerException {
+ return bundle(params, outputParentDir);
+ }
+
+ @Override
+ public boolean supported(boolean platformInstaller) {
+ return msiBundler.supported(platformInstaller);
+ }
+
+ @Override
+ public boolean isDefault() {
+ return true;
+ }
+
+ @Override
+ public boolean validate(Map<String, ? super Object> params)
+ throws ConfigException {
+ return msiBundler.validate(params);
+ }
+
+ public File bundle(Map<String, ? super Object> params, File outdir)
+ throws PackagerException {
+
+ IOUtils.writableOutputDir(outdir.toPath());
+
+ File exeImageDir = EXE_IMAGE_DIR.fetchFrom(params);
+
+ // Write msi to temporary directory.
+ File msi = msiBundler.bundle(params, exeImageDir);
+
+ try {
+ new ScriptRunner()
+ .setDirectory(msi.toPath().getParent())
+ .setResourceCategoryId("resource.post-msi-script")
+ .setScriptNameSuffix("post-msi")
+ .setEnvironmentVariable("JpMsiFile", msi.getAbsolutePath().toString())
+ .run(params);
+
+ return buildEXE(msi, outdir);
+ } catch (IOException ex) {
+ Log.verbose(ex);
+ throw new PackagerException(ex);
+ }
+ }
+
+ private File buildEXE(File msi, File outdir)
+ throws IOException {
+
+ Log.verbose(MessageFormat.format(
+ getString("message.outputting-to-location"),
+ outdir.getAbsolutePath()));
+
+ // Copy template msi wrapper next to msi file
+ String exePath = msi.getAbsolutePath();
+ exePath = exePath.substring(0, exePath.lastIndexOf('.')) + ".exe";
+ try (InputStream is = OverridableResource.readDefault(EXE_WRAPPER_NAME)) {
+ Files.copy(is, Path.of(exePath));
+ }
+ // Embed msi in msi wrapper exe.
+ embedMSI(exePath, msi.getAbsolutePath());
+
+ Path dstExePath = Paths.get(outdir.getAbsolutePath(),
+ Path.of(exePath).getFileName().toString());
+ Files.deleteIfExists(dstExePath);
+
+ Files.copy(Path.of(exePath), dstExePath);
+
+ Log.verbose(MessageFormat.format(
+ getString("message.output-location"),
+ outdir.getAbsolutePath()));
+
+ return dstExePath.toFile();
+ }
+
+ private static String getString(String key)
+ throws MissingResourceException {
+ return I18N.getString(key);
+ }
+
+ private final WinMsiBundler msiBundler = new WinMsiBundler();
+
+ private static native int embedMSI(String exePath, String msiPath);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WinMsiBundler.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,580 @@
+/*
+ * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.*;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.text.MessageFormat;
+import java.util.*;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+
+import static jdk.incubator.jpackage.internal.WindowsBundlerParam.*;
+
+/**
+ * WinMsiBundler
+ *
+ * Produces .msi installer from application image. Uses WiX Toolkit to build
+ * .msi installer.
+ * <p>
+ * {@link #execute} method creates a number of source files with the description
+ * of installer to be processed by WiX tools. Generated source files are stored
+ * in "config" subdirectory next to "app" subdirectory in the root work
+ * directory. The following WiX source files are generated:
+ * <ul>
+ * <li>main.wxs. Main source file with the installer description
+ * <li>bundle.wxf. Source file with application and Java run-time directory tree
+ * description.
+ * </ul>
+ * <p>
+ * main.wxs file is a copy of main.wxs resource from
+ * jdk.incubator.jpackage.internal.resources package. It is parametrized with the
+ * following WiX variables:
+ * <ul>
+ * <li>JpAppName. Name of the application. Set to the value of --name command
+ * line option
+ * <li>JpAppVersion. Version of the application. Set to the value of
+ * --app-version command line option
+ * <li>JpAppVendor. Vendor of the application. Set to the value of --vendor
+ * command line option
+ * <li>JpAppDescription. Description of the application. Set to the value of
+ * --description command line option
+ * <li>JpProductCode. Set to product code UUID of the application. Random value
+ * generated by jpackage every time {@link #execute} method is called
+ * <li>JpProductUpgradeCode. Set to upgrade code UUID of the application. Random
+ * value generated by jpackage every time {@link #execute} method is called if
+ * --win-upgrade-uuid command line option is not specified. Otherwise this
+ * variable is set to the value of --win-upgrade-uuid command line option
+ * <li>JpAllowDowngrades. Set to "yes" if --win-upgrade-uuid command line option
+ * was specified. Undefined otherwise
+ * <li>JpLicenseRtf. Set to the value of --license-file command line option.
+ * Undefined is --license-file command line option was not specified
+ * <li>JpInstallDirChooser. Set to "yes" if --win-dir-chooser command line
+ * option was specified. Undefined otherwise
+ * <li>JpConfigDir. Absolute path to the directory with generated WiX source
+ * files.
+ * <li>JpIsSystemWide. Set to "yes" if --win-per-user-install command line
+ * option was not specified. Undefined otherwise
+ * </ul>
+ */
+public class WinMsiBundler extends AbstractBundler {
+
+ public static final BundlerParamInfo<WinAppBundler> APP_BUNDLER =
+ new WindowsBundlerParam<>(
+ "win.app.bundler",
+ WinAppBundler.class,
+ params -> new WinAppBundler(),
+ null);
+
+ public static final BundlerParamInfo<File> MSI_IMAGE_DIR =
+ new WindowsBundlerParam<>(
+ "win.msi.imageDir",
+ File.class,
+ params -> {
+ File imagesRoot = IMAGES_ROOT.fetchFrom(params);
+ if (!imagesRoot.exists()) imagesRoot.mkdirs();
+ return new File(imagesRoot, "win-msi.image");
+ },
+ (s, p) -> null);
+
+ public static final BundlerParamInfo<File> WIN_APP_IMAGE =
+ new WindowsBundlerParam<>(
+ "win.app.image",
+ File.class,
+ null,
+ (s, p) -> null);
+
+ public static final StandardBundlerParam<Boolean> MSI_SYSTEM_WIDE =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.WIN_PER_USER_INSTALLATION.getId(),
+ Boolean.class,
+ params -> true, // MSIs default to system wide
+ // valueOf(null) is false,
+ // and we actually do want null
+ (s, p) -> (s == null || "null".equalsIgnoreCase(s))? null
+ : Boolean.valueOf(s)
+ );
+
+
+ public static final StandardBundlerParam<String> PRODUCT_VERSION =
+ new StandardBundlerParam<>(
+ "win.msi.productVersion",
+ String.class,
+ VERSION::fetchFrom,
+ (s, p) -> s
+ );
+
+ private static final BundlerParamInfo<String> UPGRADE_UUID =
+ new WindowsBundlerParam<>(
+ Arguments.CLIOptions.WIN_UPGRADE_UUID.getId(),
+ String.class,
+ null,
+ (s, p) -> s);
+
+ @Override
+ public String getName() {
+ return I18N.getString("msi.bundler.name");
+ }
+
+ @Override
+ public String getID() {
+ return "msi";
+ }
+
+ @Override
+ public String getBundleType() {
+ return "INSTALLER";
+ }
+
+ @Override
+ public File execute(Map<String, ? super Object> params,
+ File outputParentDir) throws PackagerException {
+ return bundle(params, outputParentDir);
+ }
+
+ @Override
+ public boolean supported(boolean platformInstaller) {
+ try {
+ if (wixToolset == null) {
+ wixToolset = WixTool.toolset();
+ }
+ return true;
+ } catch (ConfigException ce) {
+ Log.error(ce.getMessage());
+ if (ce.getAdvice() != null) {
+ Log.error(ce.getAdvice());
+ }
+ } catch (Exception e) {
+ Log.error(e.getMessage());
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isDefault() {
+ return false;
+ }
+
+ private static UUID getUpgradeCode(Map<String, ? super Object> params) {
+ String upgradeCode = UPGRADE_UUID.fetchFrom(params);
+ if (upgradeCode != null) {
+ return UUID.fromString(upgradeCode);
+ }
+ return createNameUUID("UpgradeCode", params, List.of(VENDOR, APP_NAME));
+ }
+
+ private static UUID getProductCode(Map<String, ? super Object> params) {
+ return createNameUUID("ProductCode", params, List.of(VENDOR, APP_NAME,
+ VERSION));
+ }
+
+ private static UUID createNameUUID(String prefix,
+ Map<String, ? super Object> params,
+ List<StandardBundlerParam<String>> components) {
+ String key = Stream.concat(Stream.of(prefix), components.stream().map(
+ c -> c.fetchFrom(params))).collect(Collectors.joining("/"));
+ return UUID.nameUUIDFromBytes(key.getBytes(StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public boolean validate(Map<String, ? super Object> params)
+ throws ConfigException {
+ try {
+ if (wixToolset == null) {
+ wixToolset = WixTool.toolset();
+ }
+
+ try {
+ getUpgradeCode(params);
+ } catch (IllegalArgumentException ex) {
+ throw new ConfigException(ex);
+ }
+
+ for (var toolInfo: wixToolset.values()) {
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.tool-version"), toolInfo.path.getFileName(),
+ toolInfo.version));
+ }
+
+ wixSourcesBuilder.setWixVersion(wixToolset.get(WixTool.Light).version);
+
+ wixSourcesBuilder.logWixFeatures();
+
+ /********* validate bundle parameters *************/
+
+ String version = PRODUCT_VERSION.fetchFrom(params);
+ if (!isVersionStringValid(version)) {
+ throw new ConfigException(
+ MessageFormat.format(I18N.getString(
+ "error.version-string-wrong-format"), version),
+ MessageFormat.format(I18N.getString(
+ "error.version-string-wrong-format.advice"),
+ PRODUCT_VERSION.getID()));
+ }
+
+ // only one mime type per association, at least one file extension
+ List<Map<String, ? super Object>> associations =
+ FILE_ASSOCIATIONS.fetchFrom(params);
+ if (associations != null) {
+ for (int i = 0; i < associations.size(); i++) {
+ Map<String, ? super Object> assoc = associations.get(i);
+ List<String> mimes = FA_CONTENT_TYPE.fetchFrom(assoc);
+ if (mimes.size() > 1) {
+ throw new ConfigException(MessageFormat.format(
+ I18N.getString("error.too-many-content-types-for-file-association"), i),
+ I18N.getString("error.too-many-content-types-for-file-association.advice"));
+ }
+ }
+ }
+
+ return true;
+ } catch (RuntimeException re) {
+ if (re.getCause() instanceof ConfigException) {
+ throw (ConfigException) re.getCause();
+ } else {
+ throw new ConfigException(re);
+ }
+ }
+ }
+
+ // https://msdn.microsoft.com/en-us/library/aa370859%28v=VS.85%29.aspx
+ // The format of the string is as follows:
+ // major.minor.build
+ // The first field is the major version and has a maximum value of 255.
+ // The second field is the minor version and has a maximum value of 255.
+ // The third field is called the build version or the update version and
+ // has a maximum value of 65,535.
+ static boolean isVersionStringValid(String v) {
+ if (v == null) {
+ return true;
+ }
+
+ String p[] = v.split("\\.");
+ if (p.length > 3) {
+ Log.verbose(I18N.getString(
+ "message.version-string-too-many-components"));
+ return false;
+ }
+
+ try {
+ int val = Integer.parseInt(p[0]);
+ if (val < 0 || val > 255) {
+ Log.verbose(I18N.getString(
+ "error.version-string-major-out-of-range"));
+ return false;
+ }
+ if (p.length > 1) {
+ val = Integer.parseInt(p[1]);
+ if (val < 0 || val > 255) {
+ Log.verbose(I18N.getString(
+ "error.version-string-minor-out-of-range"));
+ return false;
+ }
+ }
+ if (p.length > 2) {
+ val = Integer.parseInt(p[2]);
+ if (val < 0 || val > 65535) {
+ Log.verbose(I18N.getString(
+ "error.version-string-build-out-of-range"));
+ return false;
+ }
+ }
+ } catch (NumberFormatException ne) {
+ Log.verbose(I18N.getString("error.version-string-part-not-number"));
+ Log.verbose(ne);
+ return false;
+ }
+
+ return true;
+ }
+
+ private void prepareProto(Map<String, ? super Object> params)
+ throws PackagerException, IOException {
+ File appImage = StandardBundlerParam.getPredefinedAppImage(params);
+ File appDir = null;
+
+ // we either have an application image or need to build one
+ if (appImage != null) {
+ appDir = new File(MSI_IMAGE_DIR.fetchFrom(params),
+ APP_NAME.fetchFrom(params));
+ // copy everything from appImage dir into appDir/name
+ IOUtils.copyRecursive(appImage.toPath(), appDir.toPath());
+ } else {
+ appDir = APP_BUNDLER.fetchFrom(params).doBundle(params,
+ MSI_IMAGE_DIR.fetchFrom(params), true);
+ }
+
+ params.put(WIN_APP_IMAGE.getID(), appDir);
+
+ String licenseFile = LICENSE_FILE.fetchFrom(params);
+ if (licenseFile != null) {
+ // need to copy license file to the working directory
+ // and convert to rtf if needed
+ File lfile = new File(licenseFile);
+ File destFile = new File(CONFIG_ROOT.fetchFrom(params),
+ lfile.getName());
+
+ IOUtils.copyFile(lfile, destFile);
+ destFile.setWritable(true);
+ ensureByMutationFileIsRTF(destFile);
+ }
+ }
+
+ public File bundle(Map<String, ? super Object> params, File outdir)
+ throws PackagerException {
+
+ IOUtils.writableOutputDir(outdir.toPath());
+
+ Path imageDir = MSI_IMAGE_DIR.fetchFrom(params).toPath();
+ try {
+ Files.createDirectories(imageDir);
+
+ prepareProto(params);
+
+ wixSourcesBuilder
+ .initFromParams(WIN_APP_IMAGE.fetchFrom(params).toPath(), params)
+ .createMainFragment(CONFIG_ROOT.fetchFrom(params).toPath().resolve(
+ "bundle.wxf"));
+
+ Map<String, String> wixVars = prepareMainProjectFile(params);
+
+ new ScriptRunner()
+ .setDirectory(imageDir)
+ .setResourceCategoryId("resource.post-app-image-script")
+ .setScriptNameSuffix("post-image")
+ .setEnvironmentVariable("JpAppImageDir", imageDir.toAbsolutePath().toString())
+ .run(params);
+
+ return buildMSI(params, wixVars, outdir);
+ } catch (IOException ex) {
+ Log.verbose(ex);
+ throw new PackagerException(ex);
+ }
+ }
+
+ Map<String, String> prepareMainProjectFile(
+ Map<String, ? super Object> params) throws IOException {
+ Map<String, String> data = new HashMap<>();
+
+ final UUID productCode = getProductCode(params);
+ final UUID upgradeCode = getUpgradeCode(params);
+
+ data.put("JpProductCode", productCode.toString());
+ data.put("JpProductUpgradeCode", upgradeCode.toString());
+
+ Log.verbose(MessageFormat.format(I18N.getString("message.product-code"),
+ productCode));
+ Log.verbose(MessageFormat.format(I18N.getString("message.upgrade-code"),
+ upgradeCode));
+
+ data.put("JpAllowUpgrades", "yes");
+
+ data.put("JpAppName", APP_NAME.fetchFrom(params));
+ data.put("JpAppDescription", DESCRIPTION.fetchFrom(params));
+ data.put("JpAppVendor", VENDOR.fetchFrom(params));
+ data.put("JpAppVersion", PRODUCT_VERSION.fetchFrom(params));
+
+ final Path configDir = CONFIG_ROOT.fetchFrom(params).toPath();
+
+ data.put("JpConfigDir", configDir.toAbsolutePath().toString());
+
+ if (MSI_SYSTEM_WIDE.fetchFrom(params)) {
+ data.put("JpIsSystemWide", "yes");
+ }
+
+ String licenseFile = LICENSE_FILE.fetchFrom(params);
+ if (licenseFile != null) {
+ String lname = new File(licenseFile).getName();
+ File destFile = new File(CONFIG_ROOT.fetchFrom(params), lname);
+ data.put("JpLicenseRtf", destFile.getAbsolutePath());
+ }
+
+ // Copy CA dll to include with installer
+ if (INSTALLDIR_CHOOSER.fetchFrom(params)) {
+ data.put("JpInstallDirChooser", "yes");
+ String fname = "wixhelper.dll";
+ try (InputStream is = OverridableResource.readDefault(fname)) {
+ Files.copy(is, Paths.get(
+ CONFIG_ROOT.fetchFrom(params).getAbsolutePath(),
+ fname));
+ }
+ }
+
+ // Copy l10n files.
+ for (String loc : Arrays.asList("en", "ja", "zh_CN")) {
+ String fname = "MsiInstallerStrings_" + loc + ".wxl";
+ try (InputStream is = OverridableResource.readDefault(fname)) {
+ Files.copy(is, Paths.get(
+ CONFIG_ROOT.fetchFrom(params).getAbsolutePath(),
+ fname));
+ }
+ }
+
+ createResource("main.wxs", params)
+ .setCategory(I18N.getString("resource.main-wix-file"))
+ .saveToFile(configDir.resolve("main.wxs"));
+
+ createResource("overrides.wxi", params)
+ .setCategory(I18N.getString("resource.overrides-wix-file"))
+ .saveToFile(configDir.resolve("overrides.wxi"));
+
+ return data;
+ }
+
+ private File buildMSI(Map<String, ? super Object> params,
+ Map<String, String> wixVars, File outdir)
+ throws IOException {
+
+ File msiOut = new File(
+ outdir, INSTALLER_FILE_NAME.fetchFrom(params) + ".msi");
+
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.preparing-msi-config"), msiOut.getAbsolutePath()));
+
+ WixPipeline wixPipeline = new WixPipeline()
+ .setToolset(wixToolset.entrySet().stream().collect(
+ Collectors.toMap(
+ entry -> entry.getKey(),
+ entry -> entry.getValue().path)))
+ .setWixObjDir(TEMP_ROOT.fetchFrom(params).toPath().resolve("wixobj"))
+ .setWorkDir(WIN_APP_IMAGE.fetchFrom(params).toPath())
+ .addSource(CONFIG_ROOT.fetchFrom(params).toPath().resolve("main.wxs"), wixVars)
+ .addSource(CONFIG_ROOT.fetchFrom(params).toPath().resolve("bundle.wxf"), null);
+
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.generating-msi"), msiOut.getAbsolutePath()));
+
+ boolean enableLicenseUI = (LICENSE_FILE.fetchFrom(params) != null);
+ boolean enableInstalldirUI = INSTALLDIR_CHOOSER.fetchFrom(params);
+
+ List<String> lightArgs = new ArrayList<>();
+
+ if (!MSI_SYSTEM_WIDE.fetchFrom(params)) {
+ wixPipeline.addLightOptions("-sice:ICE91");
+ }
+ if (enableLicenseUI || enableInstalldirUI) {
+ wixPipeline.addLightOptions("-ext", "WixUIExtension");
+ }
+
+ wixPipeline.addLightOptions("-loc",
+ CONFIG_ROOT.fetchFrom(params).toPath().resolve(I18N.getString(
+ "resource.wxl-file-name")).toAbsolutePath().toString());
+
+ // Only needed if we using CA dll, so Wix can find it
+ if (enableInstalldirUI) {
+ wixPipeline.addLightOptions("-b", CONFIG_ROOT.fetchFrom(params).getAbsolutePath());
+ }
+
+ wixPipeline.buildMsi(msiOut.toPath().toAbsolutePath());
+
+ return msiOut;
+ }
+
+ public static void ensureByMutationFileIsRTF(File f) {
+ if (f == null || !f.isFile()) return;
+
+ try {
+ boolean existingLicenseIsRTF = false;
+
+ try (FileInputStream fin = new FileInputStream(f)) {
+ byte[] firstBits = new byte[7];
+
+ if (fin.read(firstBits) == firstBits.length) {
+ String header = new String(firstBits);
+ existingLicenseIsRTF = "{\\rtf1\\".equals(header);
+ }
+ }
+
+ if (!existingLicenseIsRTF) {
+ List<String> oldLicense = Files.readAllLines(f.toPath());
+ try (Writer w = Files.newBufferedWriter(
+ f.toPath(), Charset.forName("Windows-1252"))) {
+ w.write("{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033"
+ + "{\\fonttbl{\\f0\\fnil\\fcharset0 Arial;}}\n"
+ + "\\viewkind4\\uc1\\pard\\sa200\\sl276"
+ + "\\slmult1\\lang9\\fs20 ");
+ oldLicense.forEach(l -> {
+ try {
+ for (char c : l.toCharArray()) {
+ // 0x00 <= ch < 0x20 Escaped (\'hh)
+ // 0x20 <= ch < 0x80 Raw(non - escaped) char
+ // 0x80 <= ch <= 0xFF Escaped(\ 'hh)
+ // 0x5C, 0x7B, 0x7D (special RTF characters
+ // \,{,})Escaped(\'hh)
+ // ch > 0xff Escaped (\\ud###?)
+ if (c < 0x10) {
+ w.write("\\'0");
+ w.write(Integer.toHexString(c));
+ } else if (c > 0xff) {
+ w.write("\\ud");
+ w.write(Integer.toString(c));
+ // \\uc1 is in the header and in effect
+ // so we trail with a replacement char if
+ // the font lacks that character - '?'
+ w.write("?");
+ } else if ((c < 0x20) || (c >= 0x80) ||
+ (c == 0x5C) || (c == 0x7B) ||
+ (c == 0x7D)) {
+ w.write("\\'");
+ w.write(Integer.toHexString(c));
+ } else {
+ w.write(c);
+ }
+ }
+ // blank lines are interpreted as paragraph breaks
+ if (l.length() < 1) {
+ w.write("\\par");
+ } else {
+ w.write(" ");
+ }
+ w.write("\r\n");
+ } catch (IOException e) {
+ Log.verbose(e);
+ }
+ });
+ w.write("}\r\n");
+ }
+ }
+ } catch (IOException e) {
+ Log.verbose(e);
+ }
+
+ }
+
+ private Map<WixTool, WixTool.ToolInfo> wixToolset;
+ private WixSourcesBuilder wixSourcesBuilder = new WixSourcesBuilder();
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WindowsAppImageBuilder.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,367 @@
+/*
+ * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.UncheckedIOException;
+import java.io.Writer;
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.PosixFilePermission;
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.ResourceBundle;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
+
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+
+public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
+
+ static {
+ System.loadLibrary("jpackage");
+ }
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.incubator.jpackage.internal.resources.WinResources");
+
+ private final static String LIBRARY_NAME = "applauncher.dll";
+ private final static String REDIST_MSVCR = "vcruntimeVS_VER.dll";
+ private final static String REDIST_MSVCP = "msvcpVS_VER.dll";
+
+ private final static String TEMPLATE_APP_ICON ="java48.ico";
+
+ private static final String EXECUTABLE_PROPERTIES_TEMPLATE =
+ "WinLauncher.template";
+
+ private final Path root;
+ private final Path appDir;
+ private final Path appModsDir;
+ private final Path runtimeDir;
+ private final Path mdir;
+ private final Path binDir;
+
+ public static final BundlerParamInfo<Boolean> REBRAND_EXECUTABLE =
+ new WindowsBundlerParam<>(
+ "win.launcher.rebrand",
+ Boolean.class,
+ params -> Boolean.TRUE,
+ (s, p) -> Boolean.valueOf(s));
+
+ public static final BundlerParamInfo<File> ICON_ICO =
+ new StandardBundlerParam<>(
+ "icon.ico",
+ File.class,
+ params -> {
+ File f = ICON.fetchFrom(params);
+ if (f != null && !f.getName().toLowerCase().endsWith(".ico")) {
+ Log.error(MessageFormat.format(
+ I18N.getString("message.icon-not-ico"), f));
+ return null;
+ }
+ return f;
+ },
+ (s, p) -> new File(s));
+
+ public static final StandardBundlerParam<Boolean> CONSOLE_HINT =
+ new WindowsBundlerParam<>(
+ Arguments.CLIOptions.WIN_CONSOLE_HINT.getId(),
+ Boolean.class,
+ params -> false,
+ // valueOf(null) is false,
+ // and we actually do want null in some cases
+ (s, p) -> (s == null
+ || "null".equalsIgnoreCase(s)) ? true : Boolean.valueOf(s));
+
+ public WindowsAppImageBuilder(Map<String, Object> params, Path imageOutDir)
+ throws IOException {
+ super(params,
+ imageOutDir.resolve(APP_NAME.fetchFrom(params) + "/runtime"));
+
+ Objects.requireNonNull(imageOutDir);
+
+ this.root = imageOutDir.resolve(APP_NAME.fetchFrom(params));
+ this.appDir = root.resolve("app");
+ this.appModsDir = appDir.resolve("mods");
+ this.runtimeDir = root.resolve("runtime");
+ this.mdir = runtimeDir.resolve("lib");
+ this.binDir = root;
+ Files.createDirectories(appDir);
+ Files.createDirectories(runtimeDir);
+ }
+
+ private void writeEntry(InputStream in, Path dstFile) throws IOException {
+ Files.createDirectories(dstFile.getParent());
+ Files.copy(in, dstFile);
+ }
+
+ private static String getLauncherName(Map<String, ? super Object> params) {
+ return APP_NAME.fetchFrom(params) + ".exe";
+ }
+
+ // Returns launcher resource name for launcher we need to use.
+ public static String getLauncherResourceName(
+ Map<String, ? super Object> params) {
+ if (CONSOLE_HINT.fetchFrom(params)) {
+ return "jpackageapplauncher.exe";
+ } else {
+ return "jpackageapplauncherw.exe";
+ }
+ }
+
+ public static String getLauncherCfgName(
+ Map<String, ? super Object> params) {
+ return "app/" + APP_NAME.fetchFrom(params) +".cfg";
+ }
+
+ private File getConfig_AppIcon(Map<String, ? super Object> params) {
+ return new File(getConfigRoot(params),
+ APP_NAME.fetchFrom(params) + ".ico");
+ }
+
+ private File getConfig_ExecutableProperties(
+ Map<String, ? super Object> params) {
+ return new File(getConfigRoot(params),
+ APP_NAME.fetchFrom(params) + ".properties");
+ }
+
+ File getConfigRoot(Map<String, ? super Object> params) {
+ return CONFIG_ROOT.fetchFrom(params);
+ }
+
+ @Override
+ public Path getAppDir() {
+ return appDir;
+ }
+
+ @Override
+ public Path getAppModsDir() {
+ return appModsDir;
+ }
+
+ @Override
+ public void prepareApplicationFiles(Map<String, ? super Object> params)
+ throws IOException {
+ Map<String, ? super Object> originalParams = new HashMap<>(params);
+
+ try {
+ IOUtils.writableOutputDir(root);
+ IOUtils.writableOutputDir(binDir);
+ } catch (PackagerException pe) {
+ throw new RuntimeException(pe);
+ }
+ AppImageFile.save(root, params);
+
+ // create the .exe launchers
+ createLauncherForEntryPoint(params);
+
+ // copy the jars
+ copyApplication(params);
+
+ // copy in the needed libraries
+ try (InputStream is_lib = getResourceAsStream(LIBRARY_NAME)) {
+ Files.copy(is_lib, binDir.resolve(LIBRARY_NAME));
+ }
+
+ copyMSVCDLLs();
+
+ // create the additional launcher(s), if any
+ List<Map<String, ? super Object>> entryPoints =
+ StandardBundlerParam.ADD_LAUNCHERS.fetchFrom(params);
+ for (Map<String, ? super Object> entryPoint : entryPoints) {
+ createLauncherForEntryPoint(
+ AddLauncherArguments.merge(originalParams, entryPoint));
+ }
+ }
+
+ @Override
+ public void prepareJreFiles(Map<String, ? super Object> params)
+ throws IOException {}
+
+ private void copyMSVCDLLs() throws IOException {
+ AtomicReference<IOException> ioe = new AtomicReference<>();
+ try (Stream<Path> files = Files.list(runtimeDir.resolve("bin"))) {
+ files.filter(p -> Pattern.matches(
+ "^(vcruntime|msvcp|msvcr|ucrtbase|api-ms-win-).*\\.dll$",
+ p.toFile().getName().toLowerCase()))
+ .forEach(p -> {
+ try {
+ Files.copy(p, binDir.resolve((p.toFile().getName())));
+ } catch (IOException e) {
+ ioe.set(e);
+ }
+ });
+ }
+
+ IOException e = ioe.get();
+ if (e != null) {
+ throw e;
+ }
+ }
+
+ private void validateValueAndPut(
+ Map<String, String> data, String key,
+ BundlerParamInfo<String> param,
+ Map<String, ? super Object> params) {
+ String value = param.fetchFrom(params);
+ if (value.contains("\r") || value.contains("\n")) {
+ Log.error("Configuration Parameter " + param.getID()
+ + " contains multiple lines of text, ignore it");
+ data.put(key, "");
+ return;
+ }
+ data.put(key, value);
+ }
+
+ protected void prepareExecutableProperties(
+ Map<String, ? super Object> params) throws IOException {
+
+ Map<String, String> data = new HashMap<>();
+
+ // mapping Java parameters in strings for version resource
+ validateValueAndPut(data, "COMPANY_NAME", VENDOR, params);
+ validateValueAndPut(data, "FILE_DESCRIPTION", DESCRIPTION, params);
+ validateValueAndPut(data, "FILE_VERSION", VERSION, params);
+ data.put("INTERNAL_NAME", getLauncherName(params));
+ validateValueAndPut(data, "LEGAL_COPYRIGHT", COPYRIGHT, params);
+ data.put("ORIGINAL_FILENAME", getLauncherName(params));
+ validateValueAndPut(data, "PRODUCT_NAME", APP_NAME, params);
+ validateValueAndPut(data, "PRODUCT_VERSION", VERSION, params);
+
+ createResource(EXECUTABLE_PROPERTIES_TEMPLATE, params)
+ .setCategory(I18N.getString("resource.executable-properties-template"))
+ .setSubstitutionData(data)
+ .saveToFile(getConfig_ExecutableProperties(params));
+ }
+
+ private void createLauncherForEntryPoint(
+ Map<String, ? super Object> params) throws IOException {
+
+ File iconTarget = getConfig_AppIcon(params);
+
+ createResource(TEMPLATE_APP_ICON, params)
+ .setCategory("icon")
+ .setExternal(ICON_ICO.fetchFrom(params))
+ .saveToFile(iconTarget);
+
+ writeCfgFile(params, root.resolve(
+ getLauncherCfgName(params)).toFile());
+
+ prepareExecutableProperties(params);
+
+ // Copy executable to bin folder
+ Path executableFile = binDir.resolve(getLauncherName(params));
+
+ try (InputStream is_launcher =
+ getResourceAsStream(getLauncherResourceName(params))) {
+ writeEntry(is_launcher, executableFile);
+ }
+
+ File launcher = executableFile.toFile();
+ launcher.setWritable(true, true);
+
+ // Update branding of EXE file
+ if (REBRAND_EXECUTABLE.fetchFrom(params)) {
+ try {
+ String tempDirectory = WindowsDefender.getUserTempDirectory();
+ if (Arguments.CLIOptions.context().userProvidedBuildRoot) {
+ tempDirectory =
+ TEMP_ROOT.fetchFrom(params).getAbsolutePath();
+ }
+ if (WindowsDefender.isThereAPotentialWindowsDefenderIssue(
+ tempDirectory)) {
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.potential.windows.defender.issue"),
+ tempDirectory));
+ }
+
+ launcher.setWritable(true);
+
+ if (iconTarget.exists()) {
+ iconSwap(iconTarget.getAbsolutePath(),
+ launcher.getAbsolutePath());
+ }
+
+ File executableProperties =
+ getConfig_ExecutableProperties(params);
+
+ if (executableProperties.exists()) {
+ if (versionSwap(executableProperties.getAbsolutePath(),
+ launcher.getAbsolutePath()) != 0) {
+ throw new RuntimeException(MessageFormat.format(
+ I18N.getString("error.version-swap"),
+ executableProperties.getAbsolutePath()));
+ }
+ }
+ } finally {
+ executableFile.toFile().setExecutable(true);
+ executableFile.toFile().setReadOnly();
+ }
+ }
+
+ Files.copy(iconTarget.toPath(),
+ binDir.resolve(APP_NAME.fetchFrom(params) + ".ico"));
+ }
+
+ private void copyApplication(Map<String, ? super Object> params)
+ throws IOException {
+ List<RelativeFileSet> appResourcesList =
+ APP_RESOURCES_LIST.fetchFrom(params);
+ if (appResourcesList == null) {
+ throw new RuntimeException("Null app resources?");
+ }
+ for (RelativeFileSet appResources : appResourcesList) {
+ if (appResources == null) {
+ throw new RuntimeException("Null app resources?");
+ }
+ File srcdir = appResources.getBaseDirectory();
+ for (String fname : appResources.getIncludedFiles()) {
+ copyEntry(appDir, srcdir, fname);
+ }
+ }
+ }
+
+ private static native int iconSwap(String iconTarget, String launcher);
+
+ private static native int versionSwap(String executableProperties,
+ String launcher);
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WindowsBundlerParam.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.text.MessageFormat;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+class WindowsBundlerParam<T> extends StandardBundlerParam<T> {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.incubator.jpackage.internal.resources.WinResources");
+
+ WindowsBundlerParam(String id, Class<T> valueType,
+ Function<Map<String, ? super Object>, T> defaultValueFunction,
+ BiFunction<String,
+ Map<String, ? super Object>, T> stringConverter) {
+ super(id, valueType, defaultValueFunction, stringConverter);
+ }
+
+ static final BundlerParamInfo<String> INSTALLER_FILE_NAME =
+ new StandardBundlerParam<> (
+ "win.installerName",
+ String.class,
+ params -> {
+ String nm = APP_NAME.fetchFrom(params);
+ if (nm == null) return null;
+
+ String version = VERSION.fetchFrom(params);
+ if (version == null) {
+ return nm;
+ } else {
+ return nm + "-" + version;
+ }
+ },
+ (s, p) -> s);
+
+ static final StandardBundlerParam<String> MENU_GROUP =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.WIN_MENU_GROUP.getId(),
+ String.class,
+ params -> I18N.getString("param.menu-group.default"),
+ (s, p) -> s
+ );
+
+ static final BundlerParamInfo<Boolean> INSTALLDIR_CHOOSER =
+ new StandardBundlerParam<> (
+ Arguments.CLIOptions.WIN_DIR_CHOOSER.getId(),
+ Boolean.class,
+ params -> Boolean.FALSE,
+ (s, p) -> Boolean.valueOf(s)
+ );
+
+ static final BundlerParamInfo<String> WINDOWS_INSTALL_DIR =
+ new StandardBundlerParam<>(
+ "windows-install-dir",
+ String.class,
+ params -> {
+ String dir = INSTALL_DIR.fetchFrom(params);
+ if (dir != null) {
+ if (dir.contains(":") || dir.contains("..")) {
+ Log.error(MessageFormat.format(I18N.getString(
+ "message.invalid.install.dir"), dir,
+ APP_NAME.fetchFrom(params)));
+ } else {
+ if (dir.startsWith("\\")) {
+ dir = dir.substring(1);
+ }
+ if (dir.endsWith("\\")) {
+ dir = dir.substring(0, dir.length() - 1);
+ }
+ return dir;
+ }
+ }
+ return APP_NAME.fetchFrom(params); // Default to app name
+ },
+ (s, p) -> s
+ );
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WindowsDefender.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.util.List;
+
+final class WindowsDefender {
+
+ private WindowsDefender() {}
+
+ static final boolean isThereAPotentialWindowsDefenderIssue(String dir) {
+ boolean result = false;
+
+ if (Platform.getPlatform() == Platform.WINDOWS &&
+ Platform.getMajorVersion() == 10) {
+
+ // If DisableRealtimeMonitoring is not enabled then there
+ // may be a problem.
+ if (!WindowsRegistry.readDisableRealtimeMonitoring() &&
+ !isDirectoryInExclusionPath(dir)) {
+ result = true;
+ }
+ }
+
+ return result;
+ }
+
+ private static boolean isDirectoryInExclusionPath(String dir) {
+ boolean result = false;
+ // If the user temp directory is not found in the exclusion
+ // list then there may be a problem.
+ List<String> paths = WindowsRegistry.readExclusionsPaths();
+ for (String s : paths) {
+ if (WindowsRegistry.comparePaths(s, dir)) {
+ result = true;
+ break;
+ }
+ }
+
+ return result;
+ }
+
+ static final String getUserTempDirectory() {
+ String tempDirectory = System.getProperty("java.io.tmpdir");
+ return tempDirectory;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WindowsRegistry.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+
+final class WindowsRegistry {
+
+ // Currently we only support HKEY_LOCAL_MACHINE. Native implementation will
+ // require support for additinal HKEY if needed.
+ private static final int HKEY_LOCAL_MACHINE = 1;
+
+ static {
+ System.loadLibrary("jpackage");
+ }
+
+ private WindowsRegistry() {}
+
+ /**
+ * Reads the registry value for DisableRealtimeMonitoring.
+ * @return true if DisableRealtimeMonitoring is set to 0x1,
+ * false otherwise.
+ */
+ static final boolean readDisableRealtimeMonitoring() {
+ final String subKey = "Software\\Microsoft\\"
+ + "Windows Defender\\Real-Time Protection";
+ final String value = "DisableRealtimeMonitoring";
+ int result = readDwordValue(HKEY_LOCAL_MACHINE, subKey, value, 0);
+ return (result == 1);
+ }
+
+ static final List<String> readExclusionsPaths() {
+ List<String> result = new ArrayList<>();
+ final String subKey = "Software\\Microsoft\\"
+ + "Windows Defender\\Exclusions\\Paths";
+ long lKey = openRegistryKey(HKEY_LOCAL_MACHINE, subKey);
+ if (lKey == 0) {
+ return result;
+ }
+
+ String valueName;
+ int index = 0;
+ do {
+ valueName = enumRegistryValue(lKey, index);
+ if (valueName != null) {
+ result.add(valueName);
+ index++;
+ }
+ } while (valueName != null);
+
+ closeRegistryKey(lKey);
+
+ return result;
+ }
+
+ /**
+ * Reads DWORD registry value.
+ *
+ * @param key one of HKEY predefine value
+ * @param subKey registry sub key
+ * @param value value to read
+ * @param defaultValue default value in case if subKey or value not found
+ * or any other errors occurred
+ * @return value's data only if it was read successfully, otherwise
+ * defaultValue
+ */
+ private static native int readDwordValue(int key, String subKey,
+ String value, int defaultValue);
+
+ /**
+ * Open registry key.
+ *
+ * @param key one of HKEY predefine value
+ * @param subKey registry sub key
+ * @return native handle to open key
+ */
+ private static native long openRegistryKey(int key, String subKey);
+
+ /**
+ * Enumerates the values for registry key.
+ *
+ * @param lKey native handle to open key returned by openRegistryKey
+ * @param index index of value starting from 0. Increment until this
+ * function returns NULL which means no more values.
+ * @return returns value or NULL if error or no more data
+ */
+ private static native String enumRegistryValue(long lKey, int index);
+
+ /**
+ * Close registry key.
+ *
+ * @param lKey native handle to open key returned by openRegistryKey
+ */
+ private static native void closeRegistryKey(long lKey);
+
+ /**
+ * Compares two Windows paths regardless case and if paths
+ * are short or long.
+ *
+ * @param path1 path to compare
+ * @param path2 path to compare
+ * @return true if paths point to same location
+ */
+ public static native boolean comparePaths(String path1, String path2);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WixPipeline.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.function.UnaryOperator;
+import java.util.stream.Stream;
+
+/**
+ * WiX pipeline. Compiles and links WiX sources.
+ */
+public class WixPipeline {
+ WixPipeline() {
+ sources = new ArrayList<>();
+ lightOptions = new ArrayList<>();
+ }
+
+ WixPipeline setToolset(Map<WixTool, Path> v) {
+ toolset = v;
+ return this;
+ }
+
+ WixPipeline setWixVariables(Map<String, String> v) {
+ wixVariables = v;
+ return this;
+ }
+
+ WixPipeline setWixObjDir(Path v) {
+ wixObjDir = v;
+ return this;
+ }
+
+ WixPipeline setWorkDir(Path v) {
+ workDir = v;
+ return this;
+ }
+
+ WixPipeline addSource(Path source, Map<String, String> wixVariables) {
+ WixSource entry = new WixSource();
+ entry.source = source;
+ entry.variables = wixVariables;
+ sources.add(entry);
+ return this;
+ }
+
+ WixPipeline addLightOptions(String ... v) {
+ lightOptions.addAll(List.of(v));
+ return this;
+ }
+
+ void buildMsi(Path msi) throws IOException {
+ List<Path> wixObjs = new ArrayList<>();
+ for (var source : sources) {
+ wixObjs.add(compile(source));
+ }
+
+ List<String> lightCmdline = new ArrayList<>(List.of(
+ toolset.get(WixTool.Light).toString(),
+ "-nologo",
+ "-spdb",
+ "-ext", "WixUtilExtension",
+ "-out", msi.toString()
+ ));
+
+ lightCmdline.addAll(lightOptions);
+ wixObjs.stream().map(Path::toString).forEach(lightCmdline::add);
+
+ Files.createDirectories(msi.getParent());
+ execute(lightCmdline);
+ }
+
+ private Path compile(WixSource wixSource) throws IOException {
+ UnaryOperator<Path> adjustPath = path -> {
+ return workDir != null ? path.toAbsolutePath() : path;
+ };
+
+ Path wixObj = adjustPath.apply(wixObjDir).resolve(IOUtils.replaceSuffix(
+ wixSource.source.getFileName(), ".wixobj"));
+
+ List<String> cmdline = new ArrayList<>(List.of(
+ toolset.get(WixTool.Candle).toString(),
+ "-nologo",
+ adjustPath.apply(wixSource.source).toString(),
+ "-ext", "WixUtilExtension",
+ "-arch", "x64",
+ "-out", wixObj.toAbsolutePath().toString()
+ ));
+
+ Map<String, String> appliedVaribales = new HashMap<>();
+ Stream.of(wixVariables, wixSource.variables)
+ .filter(Objects::nonNull)
+ .forEachOrdered(appliedVaribales::putAll);
+
+ appliedVaribales.entrySet().stream().map(wixVar -> String.format("-d%s=%s",
+ wixVar.getKey(), wixVar.getValue())).forEachOrdered(
+ cmdline::add);
+
+ execute(cmdline);
+
+ return wixObj;
+ }
+
+ private void execute(List<String> cmdline) throws IOException {
+ Executor.of(new ProcessBuilder(cmdline).directory(
+ workDir != null ? workDir.toFile() : null)).executeExpectSuccess();
+ }
+
+ private final static class WixSource {
+ Path source;
+ Map<String, String> variables;
+ }
+
+ private Map<WixTool, Path> toolset;
+ private Map<String, String> wixVariables;
+ private List<String> lightOptions;
+ private Path wixObjDir;
+ private Path workDir;
+ private List<WixSource> sources;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WixSourcesBuilder.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,847 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.text.MessageFormat;
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import jdk.incubator.jpackage.internal.IOUtils.XmlConsumer;
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+import static jdk.incubator.jpackage.internal.WinMsiBundler.*;
+import static jdk.incubator.jpackage.internal.WindowsBundlerParam.MENU_GROUP;
+import static jdk.incubator.jpackage.internal.WindowsBundlerParam.WINDOWS_INSTALL_DIR;
+
+/**
+ * Creates application WiX source files.
+ */
+class WixSourcesBuilder {
+
+ WixSourcesBuilder setWixVersion(DottedVersion v) {
+ wixVersion = v;
+ return this;
+ }
+
+ WixSourcesBuilder initFromParams(Path appImageRoot,
+ Map<String, ? super Object> params) {
+ Supplier<ApplicationLayout> appImageSupplier = () -> {
+ if (StandardBundlerParam.isRuntimeInstaller(params)) {
+ return ApplicationLayout.javaRuntime();
+ } else {
+ return ApplicationLayout.platformAppImage();
+ }
+ };
+
+ systemWide = MSI_SYSTEM_WIDE.fetchFrom(params);
+
+ registryKeyPath = Path.of("Software",
+ VENDOR.fetchFrom(params),
+ APP_NAME.fetchFrom(params),
+ VERSION.fetchFrom(params)).toString();
+
+ installDir = (systemWide ? PROGRAM_FILES : LOCAL_PROGRAM_FILES).resolve(
+ WINDOWS_INSTALL_DIR.fetchFrom(params));
+
+ do {
+ ApplicationLayout layout = appImageSupplier.get();
+ // Don't want AppImageFile.FILENAME in installed application.
+ // Register it with app image at a role without a match in installed
+ // app layout to exclude it from layout transformation.
+ layout.pathGroup().setPath(new Object(),
+ AppImageFile.getPathInAppImage(Path.of("")));
+
+ // Want absolute paths to source files in generated WiX sources.
+ // This is to handle scenario if sources would be processed from
+ // differnt current directory.
+ appImage = layout.resolveAt(appImageRoot.toAbsolutePath().normalize());
+ } while (false);
+
+ installedAppImage = appImageSupplier.get().resolveAt(INSTALLDIR);
+
+ shortcutFolders = new HashSet<>();
+ if (SHORTCUT_HINT.fetchFrom(params)) {
+ shortcutFolders.add(ShortcutsFolder.Desktop);
+ }
+ if (MENU_HINT.fetchFrom(params)) {
+ shortcutFolders.add(ShortcutsFolder.ProgramMenu);
+ }
+
+ if (StandardBundlerParam.isRuntimeInstaller(params)) {
+ launcherPaths = Collections.emptyList();
+ } else {
+ launcherPaths = AppImageFile.getLauncherNames(appImageRoot, params).stream()
+ .map(name -> installedAppImage.launchersDirectory().resolve(name))
+ .map(WixSourcesBuilder::addExeSuffixToPath)
+ .collect(Collectors.toList());
+ }
+
+ programMenuFolderName = MENU_GROUP.fetchFrom(params);
+
+ initFileAssociations(params);
+
+ return this;
+ }
+
+ void createMainFragment(Path file) throws IOException {
+ removeFolderItems = new HashMap<>();
+ defaultedMimes = new HashSet<>();
+ IOUtils.createXml(file, xml -> {
+ xml.writeStartElement("Wix");
+ xml.writeDefaultNamespace("http://schemas.microsoft.com/wix/2006/wi");
+ xml.writeNamespace("util",
+ "http://schemas.microsoft.com/wix/UtilExtension");
+
+ xml.writeStartElement("Fragment");
+
+ addFaComponentGroup(xml);
+
+ addShortcutComponentGroup(xml);
+
+ addFilesComponentGroup(xml);
+
+ xml.writeEndElement(); // <Fragment>
+
+ addIconsFragment(xml);
+
+ xml.writeEndElement(); // <Wix>
+ });
+ }
+
+ void logWixFeatures() {
+ if (wixVersion.compareTo("3.6") >= 0) {
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.use-wix36-features"), wixVersion));
+ }
+ }
+
+ private void normalizeFileAssociation(FileAssociation fa) {
+ fa.launcherPath = addExeSuffixToPath(
+ installedAppImage.launchersDirectory().resolve(fa.launcherPath));
+
+ if (fa.iconPath != null && !fa.iconPath.toFile().exists()) {
+ fa.iconPath = null;
+ }
+
+ if (fa.iconPath != null) {
+ fa.iconPath = fa.iconPath.toAbsolutePath();
+ }
+
+ // Filter out empty extensions.
+ fa.extensions = fa.extensions.stream().filter(Predicate.not(
+ String::isEmpty)).collect(Collectors.toList());
+ }
+
+ private static Path addExeSuffixToPath(Path path) {
+ return IOUtils.addSuffix(path, ".exe");
+ }
+
+ private Path getInstalledFaIcoPath(FileAssociation fa) {
+ String fname = String.format("fa_%s.ico", String.join("_", fa.extensions));
+ return installedAppImage.destktopIntegrationDirectory().resolve(fname);
+ }
+
+ private void initFileAssociations(Map<String, ? super Object> params) {
+ associations = FileAssociation.fetchFrom(params).stream()
+ .peek(this::normalizeFileAssociation)
+ // Filter out file associations without extensions.
+ .filter(fa -> !fa.extensions.isEmpty())
+ .collect(Collectors.toList());
+
+ associations.stream().filter(fa -> fa.iconPath != null).forEach(fa -> {
+ // Need to add fa icon in the image.
+ Object key = new Object();
+ appImage.pathGroup().setPath(key, fa.iconPath);
+ installedAppImage.pathGroup().setPath(key, getInstalledFaIcoPath(fa));
+ });
+ }
+
+ private static UUID createNameUUID(String str) {
+ return UUID.nameUUIDFromBytes(str.getBytes(StandardCharsets.UTF_8));
+ }
+
+ private static UUID createNameUUID(Path path, String role) {
+ if (path.isAbsolute() || !ROOT_DIRS.contains(path.getName(0))) {
+ throw throwInvalidPathException(path);
+ }
+ // Paths are case insensitive on Windows
+ String keyPath = path.toString().toLowerCase();
+ if (role != null) {
+ keyPath = role + "@" + keyPath;
+ }
+ return createNameUUID(keyPath);
+ }
+
+ /**
+ * Value for Id attribute of various WiX elements.
+ */
+ enum Id {
+ File,
+ Folder("dir"),
+ Shortcut,
+ ProgId,
+ Icon,
+ CreateFolder("mkdir"),
+ RemoveFolder("rm");
+
+ Id() {
+ this.prefix = name().toLowerCase();
+ }
+
+ Id(String prefix) {
+ this.prefix = prefix;
+ }
+
+ String of(Path path) {
+ if (this == Folder && KNOWN_DIRS.contains(path)) {
+ return path.getFileName().toString();
+ }
+
+ String result = of(path, prefix, name());
+
+ if (this == Icon) {
+ // Icon id constructed from UUID value is too long and triggers
+ // CNDL1000 warning, so use Java hash code instead.
+ result = String.format("%s%d", prefix, result.hashCode()).replace(
+ "-", "_");
+ }
+
+ return result;
+ }
+
+ private static String of(Path path, String prefix, String role) {
+ Objects.requireNonNull(role);
+ Objects.requireNonNull(prefix);
+ return String.format("%s%s", prefix,
+ createNameUUID(path, role).toString().replace("-", ""));
+ }
+
+ static String of(Path path, String prefix) {
+ return of(path, prefix, prefix);
+ }
+
+ private final String prefix;
+ }
+
+ enum Component {
+ File(cfg().file()),
+ Shortcut(cfg().file().withRegistryKeyPath()),
+ ProgId(cfg().file().withRegistryKeyPath()),
+ CreateFolder(cfg().withRegistryKeyPath()),
+ RemoveFolder(cfg().withRegistryKeyPath());
+
+ Component() {
+ this.cfg = cfg();
+ this.id = Id.valueOf(name());
+ }
+
+ Component(Config cfg) {
+ this.cfg = cfg;
+ this.id = Id.valueOf(name());
+ }
+
+ UUID guidOf(Path path) {
+ return createNameUUID(path, name());
+ }
+
+ String idOf(Path path) {
+ return id.of(path);
+ }
+
+ boolean isRegistryKeyPath() {
+ return cfg.withRegistryKeyPath;
+ }
+
+ boolean isFile() {
+ return cfg.isFile;
+ }
+
+ static void startElement(XMLStreamWriter xml, String componentId,
+ String componentGuid) throws XMLStreamException, IOException {
+ xml.writeStartElement("Component");
+ xml.writeAttribute("Win64", "yes");
+ xml.writeAttribute("Id", componentId);
+ xml.writeAttribute("Guid", componentGuid);
+ }
+
+ private static final class Config {
+ Config withRegistryKeyPath() {
+ withRegistryKeyPath = true;
+ return this;
+ }
+
+ Config file() {
+ isFile = true;
+ return this;
+ }
+
+ private boolean isFile;
+ private boolean withRegistryKeyPath;
+ }
+
+ private static Config cfg() {
+ return new Config();
+ }
+
+ private final Config cfg;
+ private final Id id;
+ };
+
+ private static void addComponentGroup(XMLStreamWriter xml, String id,
+ List<String> componentIds) throws XMLStreamException, IOException {
+ xml.writeStartElement("ComponentGroup");
+ xml.writeAttribute("Id", id);
+ componentIds = componentIds.stream().filter(Objects::nonNull).collect(
+ Collectors.toList());
+ for (var componentId : componentIds) {
+ xml.writeStartElement("ComponentRef");
+ xml.writeAttribute("Id", componentId);
+ xml.writeEndElement();
+ }
+ xml.writeEndElement();
+ }
+
+ private String addComponent(XMLStreamWriter xml, Path path,
+ Component role, XmlConsumer xmlConsumer) throws XMLStreamException,
+ IOException {
+
+ final Path directoryRefPath;
+ if (role.isFile()) {
+ directoryRefPath = path.getParent();
+ } else {
+ directoryRefPath = path;
+ }
+
+ xml.writeStartElement("DirectoryRef");
+ xml.writeAttribute("Id", Id.Folder.of(directoryRefPath));
+
+ final String componentId = "c" + role.idOf(path);
+ Component.startElement(xml, componentId, String.format("{%s}",
+ role.guidOf(path)));
+
+ boolean isRegistryKeyPath = !systemWide || role.isRegistryKeyPath();
+ if (isRegistryKeyPath) {
+ addRegistryKeyPath(xml, directoryRefPath);
+ if ((role.isFile() || (role == Component.CreateFolder
+ && !systemWide)) && !SYSTEM_DIRS.contains(directoryRefPath)) {
+ xml.writeStartElement("RemoveFolder");
+ int counter = Optional.ofNullable(removeFolderItems.get(
+ directoryRefPath)).orElse(Integer.valueOf(0)).intValue() + 1;
+ removeFolderItems.put(directoryRefPath, counter);
+ xml.writeAttribute("Id", String.format("%s_%d", Id.RemoveFolder.of(
+ directoryRefPath), counter));
+ xml.writeAttribute("On", "uninstall");
+ xml.writeEndElement();
+ }
+ }
+
+ xml.writeStartElement(role.name());
+ if (role != Component.CreateFolder) {
+ xml.writeAttribute("Id", role.idOf(path));
+ }
+
+ if (!isRegistryKeyPath) {
+ xml.writeAttribute("KeyPath", "yes");
+ }
+
+ xmlConsumer.accept(xml);
+ xml.writeEndElement();
+
+ xml.writeEndElement(); // <Component>
+ xml.writeEndElement(); // <DirectoryRef>
+
+ return componentId;
+ }
+
+ private void addFaComponentGroup(XMLStreamWriter xml)
+ throws XMLStreamException, IOException {
+
+ List<String> componentIds = new ArrayList<>();
+ for (var fa : associations) {
+ componentIds.addAll(addFaComponents(xml, fa));
+ }
+ addComponentGroup(xml, "FileAssociations", componentIds);
+ }
+
+ private void addShortcutComponentGroup(XMLStreamWriter xml) throws
+ XMLStreamException, IOException {
+ List<String> componentIds = new ArrayList<>();
+ Set<ShortcutsFolder> defineShortcutFolders = new HashSet<>();
+ for (var launcherPath : launcherPaths) {
+ for (var folder : shortcutFolders) {
+ String componentId = addShortcutComponent(xml, launcherPath,
+ folder);
+ if (componentId != null) {
+ defineShortcutFolders.add(folder);
+ componentIds.add(componentId);
+ }
+ }
+ }
+
+ for (var folder : defineShortcutFolders) {
+ Path path = folder.getPath(this);
+ componentIds.addAll(addRootBranch(xml, path));
+ }
+
+ addComponentGroup(xml, "Shortcuts", componentIds);
+ }
+
+ private String addShortcutComponent(XMLStreamWriter xml, Path launcherPath,
+ ShortcutsFolder folder) throws XMLStreamException, IOException {
+ Objects.requireNonNull(folder);
+
+ if (!INSTALLDIR.equals(launcherPath.getName(0))) {
+ throw throwInvalidPathException(launcherPath);
+ }
+
+ String launcherBasename = IOUtils.replaceSuffix(
+ launcherPath.getFileName(), "").toString();
+
+ Path shortcutPath = folder.getPath(this).resolve(launcherBasename);
+ return addComponent(xml, shortcutPath, Component.Shortcut, unused -> {
+ final Path icoFile = IOUtils.addSuffix(
+ installedAppImage.destktopIntegrationDirectory().resolve(
+ launcherBasename), ".ico");
+
+ xml.writeAttribute("Name", launcherBasename);
+ xml.writeAttribute("WorkingDirectory", INSTALLDIR.toString());
+ xml.writeAttribute("Advertise", "no");
+ xml.writeAttribute("IconIndex", "0");
+ xml.writeAttribute("Target", String.format("[#%s]",
+ Component.File.idOf(launcherPath)));
+ xml.writeAttribute("Icon", Id.Icon.of(icoFile));
+ });
+ }
+
+ private List<String> addFaComponents(XMLStreamWriter xml,
+ FileAssociation fa) throws XMLStreamException, IOException {
+ List<String> components = new ArrayList<>();
+ for (var extension: fa.extensions) {
+ Path path = INSTALLDIR.resolve(String.format("%s_%s", extension,
+ fa.launcherPath.getFileName()));
+ components.add(addComponent(xml, path, Component.ProgId, unused -> {
+ xml.writeAttribute("Description", fa.description);
+
+ if (fa.iconPath != null) {
+ xml.writeAttribute("Icon", Id.File.of(getInstalledFaIcoPath(
+ fa)));
+ xml.writeAttribute("IconIndex", "0");
+ }
+
+ xml.writeStartElement("Extension");
+ xml.writeAttribute("Id", extension);
+ xml.writeAttribute("Advertise", "no");
+
+ var mimeIt = fa.mimeTypes.iterator();
+ if (mimeIt.hasNext()) {
+ String mime = mimeIt.next();
+ xml.writeAttribute("ContentType", mime);
+
+ if (!defaultedMimes.contains(mime)) {
+ xml.writeStartElement("MIME");
+ xml.writeAttribute("ContentType", mime);
+ xml.writeAttribute("Default", "yes");
+ xml.writeEndElement();
+ defaultedMimes.add(mime);
+ }
+ }
+
+ xml.writeStartElement("Verb");
+ xml.writeAttribute("Id", "open");
+ xml.writeAttribute("Command", "Open");
+ xml.writeAttribute("Argument", "%1");
+ xml.writeAttribute("TargetFile", Id.File.of(fa.launcherPath));
+ xml.writeEndElement(); // <Verb>
+
+ xml.writeEndElement(); // <Extension>
+ }));
+ }
+
+ return components;
+ }
+
+ private List<String> addRootBranch(XMLStreamWriter xml, Path path)
+ throws XMLStreamException, IOException {
+ if (!ROOT_DIRS.contains(path.getName(0))) {
+ throw throwInvalidPathException(path);
+ }
+
+ Function<Path, String> createDirectoryName = dir -> null;
+
+ boolean sysDir = true;
+ int levels = 1;
+ var dirIt = path.iterator();
+ xml.writeStartElement("DirectoryRef");
+ xml.writeAttribute("Id", dirIt.next().toString());
+
+ path = path.getName(0);
+ while (dirIt.hasNext()) {
+ levels++;
+ Path name = dirIt.next();
+ path = path.resolve(name);
+
+ if (sysDir && !SYSTEM_DIRS.contains(path)) {
+ sysDir = false;
+ createDirectoryName = dir -> dir.getFileName().toString();
+ }
+
+ final String directoryId;
+ if (!sysDir && path.equals(installDir)) {
+ directoryId = INSTALLDIR.toString();
+ } else {
+ directoryId = Id.Folder.of(path);
+ }
+ xml.writeStartElement("Directory");
+ xml.writeAttribute("Id", directoryId);
+
+ String directoryName = createDirectoryName.apply(path);
+ if (directoryName != null) {
+ xml.writeAttribute("Name", directoryName);
+ }
+ }
+
+ while (0 != levels--) {
+ xml.writeEndElement();
+ }
+
+ List<String> componentIds = new ArrayList<>();
+ while (!SYSTEM_DIRS.contains(path = path.getParent())) {
+ componentIds.add(addRemoveDirectoryComponent(xml, path));
+ }
+
+ return componentIds;
+ }
+
+ private String addRemoveDirectoryComponent(XMLStreamWriter xml, Path path)
+ throws XMLStreamException, IOException {
+ return addComponent(xml, path, Component.RemoveFolder,
+ unused -> xml.writeAttribute("On", "uninstall"));
+ }
+
+ private List<String> addDirectoryHierarchy(XMLStreamWriter xml)
+ throws XMLStreamException, IOException {
+
+ Set<Path> allDirs = new HashSet<>();
+ Set<Path> emptyDirs = new HashSet<>();
+ appImage.transform(installedAppImage, new PathGroup.TransformHandler() {
+ @Override
+ public void copyFile(Path src, Path dst) throws IOException {
+ Path dir = dst.getParent();
+ createDirectory(dir);
+ emptyDirs.remove(dir);
+ }
+
+ @Override
+ public void createDirectory(final Path dir) throws IOException {
+ if (!allDirs.contains(dir)) {
+ emptyDirs.add(dir);
+ }
+
+ Path it = dir;
+ while (it != null && allDirs.add(it)) {
+ it = it.getParent();
+ }
+
+ it = dir;
+ while ((it = it.getParent()) != null && emptyDirs.remove(it));
+ }
+ });
+
+ List<String> componentIds = new ArrayList<>();
+ for (var dir : emptyDirs) {
+ componentIds.add(addComponent(xml, dir, Component.CreateFolder,
+ unused -> {}));
+ }
+
+ if (!systemWide) {
+ // Per-user install requires <RemoveFolder> component in every
+ // directory.
+ for (var dir : allDirs.stream()
+ .filter(Predicate.not(emptyDirs::contains))
+ .filter(Predicate.not(removeFolderItems::containsKey))
+ .collect(Collectors.toList())) {
+ componentIds.add(addRemoveDirectoryComponent(xml, dir));
+ }
+ }
+
+ allDirs.remove(INSTALLDIR);
+ for (var dir : allDirs) {
+ xml.writeStartElement("DirectoryRef");
+ xml.writeAttribute("Id", Id.Folder.of(dir.getParent()));
+ xml.writeStartElement("Directory");
+ xml.writeAttribute("Id", Id.Folder.of(dir));
+ xml.writeAttribute("Name", dir.getFileName().toString());
+ xml.writeEndElement();
+ xml.writeEndElement();
+ }
+
+ componentIds.addAll(addRootBranch(xml, installDir));
+
+ return componentIds;
+ }
+
+ private void addFilesComponentGroup(XMLStreamWriter xml)
+ throws XMLStreamException, IOException {
+
+ List<Map.Entry<Path, Path>> files = new ArrayList<>();
+ appImage.transform(installedAppImage, new PathGroup.TransformHandler() {
+ @Override
+ public void copyFile(Path src, Path dst) throws IOException {
+ files.add(Map.entry(src, dst));
+ }
+
+ @Override
+ public void createDirectory(final Path dir) throws IOException {
+ }
+ });
+
+ List<String> componentIds = new ArrayList<>();
+ for (var file : files) {
+ Path src = file.getKey();
+ Path dst = file.getValue();
+
+ componentIds.add(addComponent(xml, dst, Component.File, unused -> {
+ xml.writeAttribute("Source", src.normalize().toString());
+ Path name = dst.getFileName();
+ if (!name.equals(src.getFileName())) {
+ xml.writeAttribute("Name", name.toString());
+ }
+ }));
+ }
+
+ componentIds.addAll(addDirectoryHierarchy(xml));
+
+ componentIds.add(addDirectoryCleaner(xml, INSTALLDIR));
+
+ addComponentGroup(xml, "Files", componentIds);
+ }
+
+ private void addIconsFragment(XMLStreamWriter xml) throws
+ XMLStreamException, IOException {
+
+ PathGroup srcPathGroup = appImage.pathGroup();
+ PathGroup dstPathGroup = installedAppImage.pathGroup();
+
+ // Build list of copy operations for all .ico files in application image
+ List<Map.Entry<Path, Path>> icoFiles = new ArrayList<>();
+ srcPathGroup.transform(dstPathGroup, new PathGroup.TransformHandler() {
+ @Override
+ public void copyFile(Path src, Path dst) throws IOException {
+ if (src.getFileName().toString().endsWith(".ico")) {
+ icoFiles.add(Map.entry(src, dst));
+ }
+ }
+
+ @Override
+ public void createDirectory(Path dst) throws IOException {
+ }
+ });
+
+ xml.writeStartElement("Fragment");
+ for (var icoFile : icoFiles) {
+ xml.writeStartElement("Icon");
+ xml.writeAttribute("Id", Id.Icon.of(icoFile.getValue()));
+ xml.writeAttribute("SourceFile", icoFile.getKey().toString());
+ xml.writeEndElement();
+ }
+ xml.writeEndElement();
+ }
+
+ private void addRegistryKeyPath(XMLStreamWriter xml, Path path) throws
+ XMLStreamException, IOException {
+ addRegistryKeyPath(xml, path, () -> "ProductCode", () -> "[ProductCode]");
+ }
+
+ private void addRegistryKeyPath(XMLStreamWriter xml, Path path,
+ Supplier<String> nameAttr, Supplier<String> valueAttr) throws
+ XMLStreamException, IOException {
+
+ String regRoot = USER_PROFILE_DIRS.stream().anyMatch(path::startsWith)
+ || !systemWide ? "HKCU" : "HKLM";
+
+ xml.writeStartElement("RegistryKey");
+ xml.writeAttribute("Root", regRoot);
+ xml.writeAttribute("Key", registryKeyPath);
+ if (wixVersion.compareTo("3.6") < 0) {
+ xml.writeAttribute("Action", "createAndRemoveOnUninstall");
+ }
+ xml.writeStartElement("RegistryValue");
+ xml.writeAttribute("Type", "string");
+ xml.writeAttribute("KeyPath", "yes");
+ xml.writeAttribute("Name", nameAttr.get());
+ xml.writeAttribute("Value", valueAttr.get());
+ xml.writeEndElement(); // <RegistryValue>
+ xml.writeEndElement(); // <RegistryKey>
+ }
+
+ private String addDirectoryCleaner(XMLStreamWriter xml, Path path) throws
+ XMLStreamException, IOException {
+ if (wixVersion.compareTo("3.6") < 0) {
+ return null;
+ }
+
+ // rm -rf
+ final String baseId = Id.of(path, "rm_rf");
+ final String propertyId = baseId.toUpperCase();
+ final String componentId = ("c" + baseId);
+
+ xml.writeStartElement("Property");
+ xml.writeAttribute("Id", propertyId);
+ xml.writeStartElement("RegistrySearch");
+ xml.writeAttribute("Id", Id.of(path, "regsearch"));
+ xml.writeAttribute("Root", systemWide ? "HKLM" : "HKCU");
+ xml.writeAttribute("Key", registryKeyPath);
+ xml.writeAttribute("Type", "raw");
+ xml.writeAttribute("Name", propertyId);
+ xml.writeEndElement(); // <RegistrySearch>
+ xml.writeEndElement(); // <Property>
+
+ xml.writeStartElement("DirectoryRef");
+ xml.writeAttribute("Id", INSTALLDIR.toString());
+ Component.startElement(xml, componentId, "*");
+
+ addRegistryKeyPath(xml, INSTALLDIR, () -> propertyId, () -> {
+ // The following code converts a path to value to be saved in registry.
+ // E.g.:
+ // INSTALLDIR -> [INSTALLDIR]
+ // TERGETDIR/ProgramFiles64Folder/foo/bar -> [ProgramFiles64Folder]foo/bar
+ final Path rootDir = KNOWN_DIRS.stream()
+ .sorted(Comparator.comparing(Path::getNameCount).reversed())
+ .filter(path::startsWith)
+ .findFirst().get();
+ StringBuilder sb = new StringBuilder();
+ sb.append(String.format("[%s]", rootDir.getFileName().toString()));
+ sb.append(rootDir.relativize(path).toString());
+ return sb.toString();
+ });
+
+ xml.writeStartElement(
+ "http://schemas.microsoft.com/wix/UtilExtension",
+ "RemoveFolderEx");
+ xml.writeAttribute("On", "uninstall");
+ xml.writeAttribute("Property", propertyId);
+ xml.writeEndElement(); // <RemoveFolderEx>
+ xml.writeEndElement(); // <Component>
+ xml.writeEndElement(); // <DirectoryRef>
+
+ return componentId;
+ }
+
+ private static IllegalArgumentException throwInvalidPathException(Path v) {
+ throw new IllegalArgumentException(String.format("Invalid path [%s]", v));
+ }
+
+ enum ShortcutsFolder {
+ ProgramMenu(PROGRAM_MENU_PATH),
+ Desktop(DESKTOP_PATH);
+
+ private ShortcutsFolder(Path root) {
+ this.root = root;
+ }
+
+ Path getPath(WixSourcesBuilder outer) {
+ if (this == ProgramMenu) {
+ return root.resolve(outer.programMenuFolderName);
+ }
+ return root;
+ }
+
+ private final Path root;
+ }
+
+ private DottedVersion wixVersion;
+
+ private boolean systemWide;
+
+ private String registryKeyPath;
+
+ private Path installDir;
+
+ private String programMenuFolderName;
+
+ private List<FileAssociation> associations;
+
+ private Set<ShortcutsFolder> shortcutFolders;
+
+ private List<Path> launcherPaths;
+
+ private ApplicationLayout appImage;
+ private ApplicationLayout installedAppImage;
+
+ private Map<Path, Integer> removeFolderItems;
+ private Set<String> defaultedMimes;
+
+ private final static Path TARGETDIR = Path.of("TARGETDIR");
+
+ private final static Path INSTALLDIR = Path.of("INSTALLDIR");
+
+ private final static Set<Path> ROOT_DIRS = Set.of(INSTALLDIR, TARGETDIR);
+
+ private final static Path PROGRAM_MENU_PATH = TARGETDIR.resolve("ProgramMenuFolder");
+
+ private final static Path DESKTOP_PATH = TARGETDIR.resolve("DesktopFolder");
+
+ private final static Path PROGRAM_FILES = TARGETDIR.resolve("ProgramFiles64Folder");
+
+ private final static Path LOCAL_PROGRAM_FILES = TARGETDIR.resolve("LocalAppDataFolder");
+
+ private final static Set<Path> SYSTEM_DIRS = Set.of(TARGETDIR,
+ PROGRAM_MENU_PATH, DESKTOP_PATH, PROGRAM_FILES, LOCAL_PROGRAM_FILES);
+
+ private final static Set<Path> KNOWN_DIRS = Stream.of(Set.of(INSTALLDIR),
+ SYSTEM_DIRS).flatMap(Set::stream).collect(
+ Collectors.toUnmodifiableSet());
+
+ private final static Set<Path> USER_PROFILE_DIRS = Set.of(LOCAL_PROGRAM_FILES,
+ PROGRAM_MENU_PATH, DESKTOP_PATH);
+
+ private static final StandardBundlerParam<Boolean> MENU_HINT =
+ new WindowsBundlerParam<>(
+ Arguments.CLIOptions.WIN_MENU_HINT.getId(),
+ Boolean.class,
+ params -> false,
+ // valueOf(null) is false,
+ // and we actually do want null in some cases
+ (s, p) -> (s == null ||
+ "null".equalsIgnoreCase(s))? true : Boolean.valueOf(s)
+ );
+
+ private static final StandardBundlerParam<Boolean> SHORTCUT_HINT =
+ new WindowsBundlerParam<>(
+ Arguments.CLIOptions.WIN_SHORTCUT_HINT.getId(),
+ Boolean.class,
+ params -> false,
+ // valueOf(null) is false,
+ // and we actually do want null in some cases
+ (s, p) -> (s == null ||
+ "null".equalsIgnoreCase(s))? false : Boolean.valueOf(s)
+ );
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WixTool.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.IOException;
+import java.nio.file.*;
+import java.text.MessageFormat;
+import java.util.*;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * WiX tool.
+ */
+public enum WixTool {
+ Candle, Light;
+
+ static final class ToolInfo {
+ ToolInfo(Path path, String version) {
+ this.path = path;
+ this.version = new DottedVersion(version);
+ }
+
+ final Path path;
+ final DottedVersion version;
+ }
+
+ static Map<WixTool, ToolInfo> toolset() throws ConfigException {
+ Map<WixTool, ToolInfo> toolset = new HashMap<>();
+ for (var tool : values()) {
+ toolset.put(tool, tool.find());
+ }
+ return toolset;
+ }
+
+ ToolInfo find() throws ConfigException {
+ final Path toolFileName = IOUtils.addSuffix(
+ Path.of(name().toLowerCase()), ".exe");
+
+ String[] version = new String[1];
+ ConfigException reason = createToolValidator(toolFileName, version).get();
+ if (version[0] != null) {
+ if (reason == null) {
+ // Found in PATH.
+ return new ToolInfo(toolFileName, version[0]);
+ }
+
+ // Found in PATH, but something went wrong.
+ throw reason;
+ }
+
+ for (var dir : findWixInstallDirs()) {
+ Path path = dir.resolve(toolFileName);
+ if (path.toFile().exists()) {
+ reason = createToolValidator(path, version).get();
+ if (reason != null) {
+ throw reason;
+ }
+ return new ToolInfo(path, version[0]);
+ }
+ }
+
+ throw reason;
+ }
+
+ private static Supplier<ConfigException> createToolValidator(Path toolPath,
+ String[] versionCtnr) {
+ return new ToolValidator(toolPath)
+ .setCommandLine("/?")
+ .setMinimalVersion(MINIMAL_VERSION)
+ .setToolNotFoundErrorHandler(
+ (name, ex) -> new ConfigException(
+ I18N.getString("error.no-wix-tools"),
+ I18N.getString("error.no-wix-tools.advice")))
+ .setToolOldVersionErrorHandler(
+ (name, version) -> new ConfigException(
+ MessageFormat.format(I18N.getString(
+ "message.wrong-tool-version"), name,
+ version, MINIMAL_VERSION),
+ I18N.getString("error.no-wix-tools.advice")))
+ .setVersionParser(output -> {
+ versionCtnr[0] = "";
+ String firstLineOfOutput = output.findFirst().orElse("");
+ int separatorIdx = firstLineOfOutput.lastIndexOf(' ');
+ if (separatorIdx == -1) {
+ return null;
+ }
+ versionCtnr[0] = firstLineOfOutput.substring(separatorIdx + 1);
+ return versionCtnr[0];
+ })::validate;
+ }
+
+ private final static DottedVersion MINIMAL_VERSION = DottedVersion.lazy("3.0");
+
+ static Path getSystemDir(String envVar, String knownDir) {
+ return Optional
+ .ofNullable(getEnvVariableAsPath(envVar))
+ .orElseGet(() -> Optional
+ .ofNullable(getEnvVariableAsPath("SystemDrive"))
+ .orElseGet(() -> Path.of("C:")).resolve(knownDir));
+ }
+
+ private static Path getEnvVariableAsPath(String envVar) {
+ String path = System.getenv(envVar);
+ if (path != null) {
+ try {
+ return Path.of(path);
+ } catch (InvalidPathException ex) {
+ Log.error(MessageFormat.format(I18N.getString(
+ "error.invalid-envvar"), envVar));
+ }
+ }
+ return null;
+ }
+
+ private static List<Path> findWixInstallDirs() {
+ PathMatcher wixInstallDirMatcher = FileSystems.getDefault().getPathMatcher(
+ "glob:WiX Toolset v*");
+
+ Path programFiles = getSystemDir("ProgramFiles", "\\Program Files");
+ Path programFilesX86 = getSystemDir("ProgramFiles(x86)",
+ "\\Program Files (x86)");
+
+ // Returns list of WiX install directories ordered by WiX version number.
+ // Newer versions go first.
+ return Stream.of(programFiles, programFilesX86).map(path -> {
+ List<Path> result;
+ try (var paths = Files.walk(path, 1)) {
+ result = paths.collect(Collectors.toList());
+ } catch (IOException ex) {
+ Log.verbose(ex);
+ result = Collections.emptyList();
+ }
+ return result;
+ }).flatMap(List::stream)
+ .filter(path -> wixInstallDirMatcher.matches(path.getFileName()))
+ .sorted(Comparator.comparing(Path::getFileName).reversed())
+ .map(path -> path.resolve("bin"))
+ .collect(Collectors.toList());
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/MsiInstallerStrings_en.wxl Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<WixLocalization Culture="en-us" xmlns="http://schemas.microsoft.com/wix/2006/localization" Codepage="1252">
+ <String Id="message.install.dir.exist">The folder [INSTALLDIR] already exist. Would you like to install to that folder anyway?</String>
+ <String Id="MainFeatureTitle">Main Feature</String>
+ <String Id="DowngradeErrorMessage">A higher version of [ProductName] is already installed. Downgrades disabled. Setup will now exit.</String>
+ <String Id="DisallowUpgradeErrorMessage">A lower version of [ProductName] is already installed. Upgrades disabled. Setup will now exit.</String>
+</WixLocalization>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/MsiInstallerStrings_ja.wxl Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<WixLocalization Culture="en-us" xmlns="http://schemas.microsoft.com/wix/2006/localization" Codepage="932">
+ <String Id="message.install.dir.exist">The folder [INSTALLDIR] already exist. Would you like to install to that folder anyway?</String>
+ <String Id="MainFeatureTitle">Main Feature</String>
+ <String Id="DowngradeErrorMessage">A higher version of [ProductName] is already installed. Downgrades disabled. Setup will now exit.</String>
+ <String Id="DisallowUpgradeErrorMessage">A lower version of [ProductName] is already installed. Upgrades disabled. Setup will now exit.</String>
+</WixLocalization>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/MsiInstallerStrings_zh_CN.wxl Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<WixLocalization Culture="en-us" xmlns="http://schemas.microsoft.com/wix/2006/localization" Codepage="936">
+ <String Id="message.install.dir.exist">The folder [INSTALLDIR] already exist. Would you like to install to that folder anyway?</String>
+ <String Id="MainFeatureTitle">Main Feature</String>
+ <String Id="DowngradeErrorMessage">A higher version of [ProductName] is already installed. Downgrades disabled. Setup will now exit.</String>
+ <String Id="DisallowUpgradeErrorMessage">A lower version of [ProductName] is already installed. Upgrades disabled. Setup will now exit.</String>
+</WixLocalization>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/WinLauncher.template Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,34 @@
+#
+# Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+#
+
+CompanyName=COMPANY_NAME
+FileDescription=FILE_DESCRIPTION
+FileVersion=FILE_VERSION
+InternalName=INTERNAL_NAME
+LegalCopyright=LEGAL_COPYRIGHT
+OriginalFilename=ORIGINAL_FILENAME
+ProductName=PRODUCT_NAME
+ProductVersion=PRODUCT_VERSION
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/WinResources.properties Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,67 @@
+#
+# Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+#
+
+app.bundler.name=Windows Application Image
+exe.bundler.name=EXE Installer Package
+msi.bundler.name=MSI Installer Package
+
+param.menu-group.default=Unknown
+
+resource.executable-properties-template=Template for creating executable properties file
+resource.setup-icon=setup dialog icon
+resource.post-app-image-script=script to run after application image is populated
+resource.post-msi-script=script to run after msi file for exe installer is created
+resource.wxl-file-name=MsiInstallerStrings_en.wxl
+resource.main-wix-file=Main WiX project file
+resource.overrides-wix-file=Overrides WiX project file
+
+error.no-wix-tools=Can not find WiX tools (light.exe, candle.exe)
+error.no-wix-tools.advice=Download WiX 3.0 or later from https://wixtoolset.org and add it to the PATH.
+error.version-string-wrong-format=Version string is not compatible with MSI rules [{0}]
+error.version-string-wrong-format.advice=Set the bundler argument "{0}" according to these rules: https://msdn.microsoft.com/en-us/library/aa370859%28v\=VS.85%29.aspx .
+error.version-string-major-out-of-range=Major version must be in the range [0, 255]
+error.version-string-build-out-of-range=Build part of version must be in the range [0, 65535]
+error.version-string-minor-out-of-range=Minor version must be in the range [0, 255]
+error.version-string-part-not-number=Failed to convert version component to int
+error.version-swap=Failed to update version information for {0}
+error.invalid-envvar=Invalid value of {0} environment variable
+
+message.result-dir=Result application bundle: {0}.
+message.icon-not-ico=The specified icon "{0}" is not an ICO file and will not be used. The default icon will be used in it's place.
+message.potential.windows.defender.issue=Warning: Windows Defender may prevent jpackage from functioning. If there is an issue, it can be addressed by either disabling realtime monitoring, or adding an exclusion for the directory "{0}".
+message.outputting-to-location=Generating EXE for installer to: {0}.
+message.output-location=Installer (.exe) saved to: {0}
+message.tool-version=Detected [{0}] version [{1}].
+message.creating-association-with-null-extension=Creating association with null extension.
+message.wrong-tool-version=Detected [{0}] version {1} but version {2} is required.
+message.version-string-too-many-components=Version sting may have up to 3 components - major.minor.build .
+message.use-wix36-features=WiX {0} detected. Enabling advanced cleanup action.
+message.product-code=MSI ProductCode: {0}.
+message.upgrade-code=MSI UpgradeCode: {0}.
+message.preparing-msi-config=Preparing MSI config: {0}.
+message.generating-msi=Generating MSI: {0}.
+message.invalid.install.dir=Warning: Invalid install directory {0}. Install directory should be a relative sub-path under the default installation location such as "Program Files". Defaulting to application name "{1}".
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/WinResources_ja.properties Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,67 @@
+#
+# Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+#
+
+app.bundler.name=Windows Application Image
+exe.bundler.name=EXE Installer Package
+msi.bundler.name=MSI Installer Package
+
+param.menu-group.default=Unknown
+
+resource.executable-properties-template=Template for creating executable properties file
+resource.setup-icon=setup dialog icon
+resource.post-app-image-script=script to run after application image is populated
+resource.post-msi-script=script to run after msi file for exe installer is created
+resource.wxl-file-name=MsiInstallerStrings_en.wxl
+resource.main-wix-file=Main WiX project file
+resource.overrides-wix-file=Overrides WiX project file
+
+error.no-wix-tools=Can not find WiX tools (light.exe, candle.exe)
+error.no-wix-tools.advice=Download WiX 3.0 or later from https://wixtoolset.org and add it to the PATH.
+error.version-string-wrong-format=Version string is not compatible with MSI rules [{0}]
+error.version-string-wrong-format.advice=Set the bundler argument "{0}" according to these rules: https://msdn.microsoft.com/en-us/library/aa370859%28v\=VS.85%29.aspx .
+error.version-string-major-out-of-range=Major version must be in the range [0, 255]
+error.version-string-build-out-of-range=Build part of version must be in the range [0, 65535]
+error.version-string-minor-out-of-range=Minor version must be in the range [0, 255]
+error.version-string-part-not-number=Failed to convert version component to int
+error.version-swap=Failed to update version information for {0}
+error.invalid-envvar=Invalid value of {0} environment variable
+
+message.result-dir=Result application bundle: {0}.
+message.icon-not-ico=The specified icon "{0}" is not an ICO file and will not be used. The default icon will be used in it's place.
+message.potential.windows.defender.issue=Warning: Windows Defender may prevent jpackage from functioning. If there is an issue, it can be addressed by either disabling realtime monitoring, or adding an exclusion for the directory "{0}".
+message.outputting-to-location=Generating EXE for installer to: {0}.
+message.output-location=Installer (.exe) saved to: {0}
+message.tool-version=Detected [{0}] version [{1}].
+message.creating-association-with-null-extension=Creating association with null extension.
+message.wrong-tool-version=Detected [{0}] version {1} but version {2} is required.
+message.version-string-too-many-components=Version sting may have up to 3 components - major.minor.build .
+message.use-wix36-features=WiX {0} detected. Enabling advanced cleanup action.
+message.product-code=MSI ProductCode: {0}.
+message.upgrade-code=MSI UpgradeCode: {0}.
+message.preparing-msi-config=Preparing MSI config: {0}.
+message.generating-msi=Generating MSI: {0}.
+message.invalid.install.dir=Warning: Invalid install directory {0}. Install directory should be a relative sub-path under the default installation location such as "Program Files". Defaulting to application name "{1}".
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/WinResources_zh_CN.properties Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,67 @@
+#
+# Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation. Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+#
+#
+
+app.bundler.name=Windows Application Image
+exe.bundler.name=EXE Installer Package
+msi.bundler.name=MSI Installer Package
+
+param.menu-group.default=Unknown
+
+resource.executable-properties-template=Template for creating executable properties file
+resource.setup-icon=setup dialog icon
+resource.post-app-image-script=script to run after application image is populated
+resource.post-msi-script=script to run after msi file for exe installer is created
+resource.wxl-file-name=MsiInstallerStrings_en.wxl
+resource.main-wix-file=Main WiX project file
+resource.overrides-wix-file=Overrides WiX project file
+
+error.no-wix-tools=Can not find WiX tools (light.exe, candle.exe)
+error.no-wix-tools.advice=Download WiX 3.0 or later from https://wixtoolset.org and add it to the PATH.
+error.version-string-wrong-format=Version string is not compatible with MSI rules [{0}]
+error.version-string-wrong-format.advice=Set the bundler argument "{0}" according to these rules: https://msdn.microsoft.com/en-us/library/aa370859%28v\=VS.85%29.aspx .
+error.version-string-major-out-of-range=Major version must be in the range [0, 255]
+error.version-string-build-out-of-range=Build part of version must be in the range [0, 65535]
+error.version-string-minor-out-of-range=Minor version must be in the range [0, 255]
+error.version-string-part-not-number=Failed to convert version component to int
+error.version-swap=Failed to update version information for {0}
+error.invalid-envvar=Invalid value of {0} environment variable
+
+message.result-dir=Result application bundle: {0}.
+message.icon-not-ico=The specified icon "{0}" is not an ICO file and will not be used. The default icon will be used in it's place.
+message.potential.windows.defender.issue=Warning: Windows Defender may prevent jpackage from functioning. If there is an issue, it can be addressed by either disabling realtime monitoring, or adding an exclusion for the directory "{0}".
+message.outputting-to-location=Generating EXE for installer to: {0}.
+message.output-location=Installer (.exe) saved to: {0}
+message.tool-version=Detected [{0}] version [{1}].
+message.creating-association-with-null-extension=Creating association with null extension.
+message.wrong-tool-version=Detected [{0}] version {1} but version {2} is required.
+message.version-string-too-many-components=Version sting may have up to 3 components - major.minor.build .
+message.use-wix36-features=WiX {0} detected. Enabling advanced cleanup action.
+message.product-code=MSI ProductCode: {0}.
+message.upgrade-code=MSI UpgradeCode: {0}.
+message.preparing-msi-config=Preparing MSI config: {0}.
+message.generating-msi=Generating MSI: {0}.
+message.invalid.install.dir=Warning: Invalid install directory {0}. Install directory should be a relative sub-path under the default installation location such as "Program Files". Defaulting to application name "{1}".
+
Binary file src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/java48.ico has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/main.wxs Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
+ xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
+
+ <?ifdef JpIsSystemWide ?>
+ <?define JpInstallScope="perMachine"?>
+ <?else?>
+ <?define JpInstallScope="perUser"?>
+ <?endif?>
+
+ <?define JpProductLanguage=1033 ?>
+ <?define JpInstallerVersion=200 ?>
+ <?define JpCompressedMsi=yes ?>
+
+ <?include $(var.JpConfigDir)/overrides.wxi ?>
+
+ <Product
+ Id="$(var.JpProductCode)"
+ Name="$(var.JpAppName)"
+ Language="$(var.JpProductLanguage)"
+ Version="$(var.JpAppVersion)"
+ Manufacturer="$(var.JpAppVendor)"
+ UpgradeCode="$(var.JpProductUpgradeCode)">
+
+ <Package
+ Description="$(var.JpAppDescription)"
+ Manufacturer="$(var.JpAppVendor)"
+ InstallerVersion="$(var.JpInstallerVersion)"
+ Compressed="$(var.JpCompressedMsi)"
+ InstallScope="$(var.JpInstallScope)" Platform="x64"
+ />
+
+ <Media Id="1" Cabinet="Data.cab" EmbedCab="yes" />
+
+ <?ifdef JpAllowDowngrades ?>
+ <?ifdef JpAllowUpgrades ?>
+ <MajorUpgrade
+ AllowDowngrades="yes"
+ Disallow="no"
+ />
+ <?endif?>
+ <?endif?>
+
+ <?ifdef JpAllowDowngrades ?>
+ <?ifndef JpAllowUpgrades ?>
+ <MajorUpgrade
+ AllowDowngrades="yes"
+ Disallow="yes"
+ DisallowUpgradeErrorMessage="!(loc.DisallowUpgradeErrorMessage)"
+ />
+ <?endif?>
+ <?endif?>
+
+ <?ifndef JpAllowDowngrades ?>
+ <?ifdef JpAllowUpgrades ?>
+ <MajorUpgrade
+ AllowDowngrades="no"
+ Disallow="no"
+ DowngradeErrorMessage="!(loc.DowngradeErrorMessage)"
+ />
+ <?endif?>
+ <?endif?>
+
+ <?ifndef JpAllowDowngrades ?>
+ <?ifndef JpAllowUpgrades ?>
+ <MajorUpgrade
+ AllowDowngrades="no"
+ Disallow="yes"
+ DowngradeErrorMessage="!(loc.DowngradeErrorMessage)"
+ DisallowUpgradeErrorMessage="!(loc.DisallowUpgradeErrorMessage)"
+ />
+ <?endif?>
+ <?endif?>
+
+ <!-- Standard required root -->
+ <Directory Id="TARGETDIR" Name="SourceDir"/>
+
+ <Feature Id="DefaultFeature" Title="!(loc.MainFeatureTitle)" Level="1">
+ <ComponentGroupRef Id="Shortcuts"/>
+ <ComponentGroupRef Id="Files"/>
+ <ComponentGroupRef Id="FileAssociations"/>
+ </Feature>
+
+ <?ifdef JpInstallDirChooser ?>
+ <Binary Id="JpCaDll" SourceFile="wixhelper.dll"/>
+ <CustomAction Id="JpCheckInstallDir" BinaryKey="JpCaDll" DllEntry="CheckInstallDir" />
+ <?endif?>
+
+ <UI>
+ <?ifdef JpInstallDirChooser ?>
+ <Dialog Id="JpInvalidInstallDir" Width="300" Height="85" Title="[ProductName] Setup" NoMinimize="yes">
+ <Control Id="JpInvalidInstallDirYes" Type="PushButton" X="100" Y="55" Width="50" Height="15" Default="no" Cancel="no" Text="Yes">
+ <Publish Event="NewDialog" Value="VerifyReadyDlg">1</Publish>
+ </Control>
+ <Control Id="JpInvalidInstallDirNo" Type="PushButton" X="150" Y="55" Width="50" Height="15" Default="yes" Cancel="yes" Text="No">
+ <Publish Event="NewDialog" Value="InstallDirDlg">1</Publish>
+ </Control>
+ <Control Id="Text" Type="Text" X="25" Y="15" Width="250" Height="30" TabSkip="no">
+ <Text>!(loc.message.install.dir.exist)</Text>
+ </Control>
+ </Dialog>
+
+ <!--
+ Run WixUI_InstallDir dialog in the default install directory.
+ -->
+ <Property Id="WIXUI_INSTALLDIR" Value="INSTALLDIR"/>
+ <UIRef Id="WixUI_InstallDir" />
+
+ <Publish Dialog="InstallDirDlg" Control="Next" Event="DoAction" Value="JpCheckInstallDir" Order="3">1</Publish>
+ <Publish Dialog="InstallDirDlg" Control="Next" Event="NewDialog" Value="JpInvalidInstallDir" Order="5">INSTALLDIR_VALID="0"</Publish>
+ <Publish Dialog="InstallDirDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg" Order="5">INSTALLDIR_VALID="1"</Publish>
+
+ <?ifndef JpLicenseRtf ?>
+ <!--
+ No license file provided.
+ Override the dialog sequence in built-in dialog set "WixUI_InstallDir"
+ to exclude license dialog.
+ -->
+ <Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="InstallDirDlg" Order="2">1</Publish>
+ <Publish Dialog="InstallDirDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg" Order="2">1</Publish>
+ <?endif?>
+
+ <?else?>
+
+ <?ifdef JpLicenseRtf ?>
+ <UIRef Id="WixUI_Minimal" />
+ <?endif?>
+
+ <?endif?>
+ </UI>
+
+ <?ifdef JpLicenseRtf ?>
+ <WixVariable Id="WixUILicenseRtf" Value="$(var.JpLicenseRtf)"/>
+ <?endif?>
+
+ </Product>
+</Wix>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/overrides.wxi Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Stub by design -->
+<Include/>
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/module-info.java.extra Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+provides jdk.incubator.jpackage.internal.Bundler with
+ jdk.incubator.jpackage.internal.WinAppBundler,
+ jdk.incubator.jpackage.internal.WinExeBundler,
+ jdk.incubator.jpackage.internal.WinMsiBundler;
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/jpackageapplauncher/WinLauncher.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <Windows.h>
+#include <Shellapi.h>
+#include <locale.h>
+#include <tchar.h>
+#include <string>
+
+#define JPACKAGE_LIBRARY TEXT("applauncher.dll")
+
+typedef bool (*start_launcher)(int argc, TCHAR* argv[]);
+typedef void (*stop_launcher)();
+
+std::wstring GetTitle() {
+ std::wstring result;
+ wchar_t buffer[MAX_PATH];
+ GetModuleFileName(NULL, buffer, MAX_PATH - 1);
+ buffer[MAX_PATH - 1] = '\0';
+ result = buffer;
+ size_t slash = result.find_last_of('\\');
+
+ if (slash != std::wstring::npos)
+ result = result.substr(slash + 1, result.size() - slash - 1);
+
+ return result;
+}
+
+#ifdef LAUNCHERC
+int main(int argc0, char *argv0[]) {
+#else // LAUNCHERC
+int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
+ LPTSTR lpCmdLine, int nCmdShow) {
+#endif // LAUNCHERC
+ int result = 1;
+ TCHAR **argv;
+ int argc;
+
+ // [RT-31061] otherwise UI can be left in back of other windows.
+ ::AllowSetForegroundWindow(ASFW_ANY);
+
+ ::setlocale(LC_ALL, "en_US.utf8");
+ argv = CommandLineToArgvW(GetCommandLine(), &argc);
+
+ HMODULE library = ::LoadLibrary(JPACKAGE_LIBRARY);
+
+ if (library == NULL) {
+ std::wstring title = GetTitle();
+ std::wstring description = std::wstring(JPACKAGE_LIBRARY)
+ + std::wstring(TEXT(" not found."));
+ MessageBox(NULL, description.data(),
+ title.data(), MB_ICONERROR | MB_OK);
+ }
+ else {
+ start_launcher start =
+ (start_launcher)GetProcAddress(library, "start_launcher");
+ stop_launcher stop =
+ (stop_launcher)GetProcAddress(library, "stop_launcher");
+
+ if (start != NULL && stop != NULL) {
+ if (start(argc, argv) == true) {
+ result = 0;
+ stop();
+ }
+ }
+
+ ::FreeLibrary(library);
+ }
+
+ if (argv != NULL) {
+ LocalFree(argv);
+ }
+
+ return result;
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libapplauncher/DllMain.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <windows.h>
+
+extern "C" {
+
+ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason,
+ LPVOID lpvReserved) {
+ return true;
+ }
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libapplauncher/FileAttribute.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef FILEATTRIBUTE_H
+#define FILEATTRIBUTE_H
+
+enum FileAttribute {
+ faArchive = FILE_ATTRIBUTE_ARCHIVE,
+ faCompressed = FILE_ATTRIBUTE_COMPRESSED,
+ faDevice = FILE_ATTRIBUTE_DEVICE,
+ faDirectory = FILE_ATTRIBUTE_DIRECTORY,
+ faEncrypted = FILE_ATTRIBUTE_ENCRYPTED,
+ faHidden = FILE_ATTRIBUTE_HIDDEN,
+ faNormal = FILE_ATTRIBUTE_NORMAL,
+ faNotContentIndexed = FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
+ faOffline = FILE_ATTRIBUTE_OFFLINE,
+ faSystem = FILE_ATTRIBUTE_SYSTEM,
+ faSymbolicLink = FILE_ATTRIBUTE_REPARSE_POINT,
+ faSparceFile = FILE_ATTRIBUTE_SPARSE_FILE,
+ faReadOnly = FILE_ATTRIBUTE_READONLY,
+ faTemporary = FILE_ATTRIBUTE_TEMPORARY,
+ faVirtual = FILE_ATTRIBUTE_VIRTUAL
+};
+
+#endif // FILEATTRIBUTE_H
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libapplauncher/FilePath.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,468 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "FilePath.h"
+
+#include <algorithm>
+#include <list>
+#include <ShellAPI.h>
+
+bool FilePath::FileExists(const TString FileName) {
+ bool result = false;
+ WIN32_FIND_DATA FindFileData;
+ TString fileName = FixPathForPlatform(FileName);
+ HANDLE handle = FindFirstFile(fileName.data(), &FindFileData);
+
+ if (handle != INVALID_HANDLE_VALUE) {
+ if (FILE_ATTRIBUTE_DIRECTORY & FindFileData.dwFileAttributes) {
+ result = true;
+ }
+ else {
+ result = true;
+ }
+
+ FindClose(handle);
+ }
+ return result;
+}
+
+bool FilePath::DirectoryExists(const TString DirectoryName) {
+ bool result = false;
+ WIN32_FIND_DATA FindFileData;
+ TString directoryName = FixPathForPlatform(DirectoryName);
+ HANDLE handle = FindFirstFile(directoryName.data(), &FindFileData);
+
+ if (handle != INVALID_HANDLE_VALUE) {
+ if (FILE_ATTRIBUTE_DIRECTORY & FindFileData.dwFileAttributes) {
+ result = true;
+ }
+
+ FindClose(handle);
+ }
+ return result;
+}
+
+std::string GetLastErrorAsString() {
+ // Get the error message, if any.
+ DWORD errorMessageID = ::GetLastError();
+
+ if (errorMessageID == 0) {
+ return "No error message has been recorded";
+ }
+
+ LPSTR messageBuffer = NULL;
+ size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER
+ | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL,
+ SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL);
+
+ std::string message(messageBuffer, size);
+
+ // Free the buffer.
+ LocalFree(messageBuffer);
+
+ return message;
+}
+
+bool FilePath::DeleteFile(const TString FileName) {
+ bool result = false;
+
+ if (FileExists(FileName) == true) {
+ TString lFileName = FixPathForPlatform(FileName);
+ FileAttributes attributes(lFileName);
+
+ if (attributes.Contains(faReadOnly) == true) {
+ attributes.Remove(faReadOnly);
+ }
+
+ result = ::DeleteFile(lFileName.data()) == TRUE;
+ }
+
+ return result;
+}
+
+bool FilePath::DeleteDirectory(const TString DirectoryName) {
+ bool result = false;
+
+ if (DirectoryExists(DirectoryName) == true) {
+ SHFILEOPSTRUCTW fos = {0};
+ TString directoryName = FixPathForPlatform(DirectoryName);
+ DynamicBuffer<TCHAR> lDirectoryName(directoryName.size() + 2);
+ if (lDirectoryName.GetData() == NULL) {
+ return false;
+ }
+ memcpy(lDirectoryName.GetData(), directoryName.data(),
+ (directoryName.size() + 2) * sizeof(TCHAR));
+ lDirectoryName[directoryName.size() + 1] = NULL;
+ // Double null terminate for SHFileOperation.
+
+ // Delete the folder and everything inside.
+ fos.wFunc = FO_DELETE;
+ fos.pFrom = lDirectoryName.GetData();
+ fos.fFlags = FOF_NO_UI;
+ result = SHFileOperation(&fos) == 0;
+ }
+
+ return result;
+}
+
+TString FilePath::IncludeTrailingSeparator(const TString value) {
+ TString result = value;
+
+ if (value.size() > 0) {
+ TString::iterator i = result.end();
+ i--;
+
+ if (*i != TRAILING_PATHSEPARATOR) {
+ result += TRAILING_PATHSEPARATOR;
+ }
+ }
+
+ return result;
+}
+
+TString FilePath::IncludeTrailingSeparator(const char* value) {
+ TString lvalue = PlatformString(value).toString();
+ return IncludeTrailingSeparator(lvalue);
+}
+
+TString FilePath::IncludeTrailingSeparator(const wchar_t* value) {
+ TString lvalue = PlatformString(value).toString();
+ return IncludeTrailingSeparator(lvalue);
+}
+
+TString FilePath::ExtractFilePath(TString Path) {
+ TString result;
+ size_t slash = Path.find_last_of(TRAILING_PATHSEPARATOR);
+ if (slash != TString::npos)
+ result = Path.substr(0, slash);
+ return result;
+}
+
+TString FilePath::ExtractFileExt(TString Path) {
+ TString result;
+ size_t dot = Path.find_last_of('.');
+
+ if (dot != TString::npos) {
+ result = Path.substr(dot, Path.size() - dot);
+ }
+
+ return result;
+}
+
+TString FilePath::ExtractFileName(TString Path) {
+ TString result;
+
+ size_t slash = Path.find_last_of(TRAILING_PATHSEPARATOR);
+ if (slash != TString::npos)
+ result = Path.substr(slash + 1, Path.size() - slash - 1);
+
+ return result;
+}
+
+TString FilePath::ChangeFileExt(TString Path, TString Extension) {
+ TString result;
+ size_t dot = Path.find_last_of('.');
+
+ if (dot != TString::npos) {
+ result = Path.substr(0, dot) + Extension;
+ }
+
+ if (result.empty() == true) {
+ result = Path;
+ }
+
+ return result;
+}
+
+TString FilePath::FixPathForPlatform(TString Path) {
+ TString result = Path;
+ std::replace(result.begin(), result.end(),
+ BAD_TRAILING_PATHSEPARATOR, TRAILING_PATHSEPARATOR);
+ // The maximum path that does not require long path prefix. On Windows the
+ // maximum path is 260 minus 1 (NUL) but for directories it is 260 minus
+ // 12 minus 1 (to allow for the creation of a 8.3 file in the directory).
+ const int maxPath = 247;
+ if (result.length() > maxPath &&
+ result.find(_T("\\\\?\\")) == TString::npos &&
+ result.find(_T("\\\\?\\UNC")) == TString::npos) {
+ const TString prefix(_T("\\\\"));
+ if (!result.compare(0, prefix.size(), prefix)) {
+ // UNC path, converting to UNC path in long notation
+ result = _T("\\\\?\\UNC") + result.substr(1, result.length());
+ } else {
+ // converting to non-UNC path in long notation
+ result = _T("\\\\?\\") + result;
+ }
+ }
+ return result;
+}
+
+TString FilePath::FixPathSeparatorForPlatform(TString Path) {
+ TString result = Path;
+ std::replace(result.begin(), result.end(),
+ BAD_PATH_SEPARATOR, PATH_SEPARATOR);
+ return result;
+}
+
+TString FilePath::PathSeparator() {
+ TString result;
+ result = PATH_SEPARATOR;
+ return result;
+}
+
+bool FilePath::CreateDirectory(TString Path, bool ownerOnly) {
+ bool result = false;
+
+ std::list<TString> paths;
+ TString lpath = Path;
+
+ while (lpath.empty() == false && DirectoryExists(lpath) == false) {
+ paths.push_front(lpath);
+ lpath = ExtractFilePath(lpath);
+ }
+
+ for (std::list<TString>::iterator iterator = paths.begin();
+ iterator != paths.end(); iterator++) {
+ lpath = *iterator;
+
+ if (_wmkdir(lpath.data()) == 0) {
+ result = true;
+ } else {
+ result = false;
+ break;
+ }
+ }
+
+ return result;
+}
+
+void FilePath::ChangePermissions(TString FileName, bool ownerOnly) {
+}
+
+#include <algorithm>
+
+FileAttributes::FileAttributes(const TString FileName, bool FollowLink) {
+ FFileName = FileName;
+ FFollowLink = FollowLink;
+ ReadAttributes();
+}
+
+bool FileAttributes::WriteAttributes() {
+ bool result = false;
+
+ DWORD attributes = 0;
+
+ for (std::vector<FileAttribute>::const_iterator iterator =
+ FAttributes.begin();
+ iterator != FAttributes.end(); iterator++) {
+ switch (*iterator) {
+ case faArchive: {
+ attributes = attributes & FILE_ATTRIBUTE_ARCHIVE;
+ break;
+ }
+ case faCompressed: {
+ attributes = attributes & FILE_ATTRIBUTE_COMPRESSED;
+ break;
+ }
+ case faDevice: {
+ attributes = attributes & FILE_ATTRIBUTE_DEVICE;
+ break;
+ }
+ case faDirectory: {
+ attributes = attributes & FILE_ATTRIBUTE_DIRECTORY;
+ break;
+ }
+ case faEncrypted: {
+ attributes = attributes & FILE_ATTRIBUTE_ENCRYPTED;
+ break;
+ }
+ case faHidden: {
+ attributes = attributes & FILE_ATTRIBUTE_HIDDEN;
+ break;
+ }
+ case faNormal: {
+ attributes = attributes & FILE_ATTRIBUTE_NORMAL;
+ break;
+ }
+ case faNotContentIndexed: {
+ attributes = attributes & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
+ break;
+ }
+ case faOffline: {
+ attributes = attributes & FILE_ATTRIBUTE_OFFLINE;
+ break;
+ }
+ case faSystem: {
+ attributes = attributes & FILE_ATTRIBUTE_SYSTEM;
+ break;
+ }
+ case faSymbolicLink: {
+ attributes = attributes & FILE_ATTRIBUTE_REPARSE_POINT;
+ break;
+ }
+ case faSparceFile: {
+ attributes = attributes & FILE_ATTRIBUTE_SPARSE_FILE;
+ break;
+ }
+ case faReadOnly: {
+ attributes = attributes & FILE_ATTRIBUTE_READONLY;
+ break;
+ }
+ case faTemporary: {
+ attributes = attributes & FILE_ATTRIBUTE_TEMPORARY;
+ break;
+ }
+ case faVirtual: {
+ attributes = attributes & FILE_ATTRIBUTE_VIRTUAL;
+ break;
+ }
+ }
+ }
+
+ if (::SetFileAttributes(FFileName.data(), attributes) != 0) {
+ result = true;
+ }
+
+ return result;
+}
+
+#define S_ISRUSR(m) (((m) & S_IRWXU) == S_IRUSR)
+#define S_ISWUSR(m) (((m) & S_IRWXU) == S_IWUSR)
+#define S_ISXUSR(m) (((m) & S_IRWXU) == S_IXUSR)
+
+#define S_ISRGRP(m) (((m) & S_IRWXG) == S_IRGRP)
+#define S_ISWGRP(m) (((m) & S_IRWXG) == S_IWGRP)
+#define S_ISXGRP(m) (((m) & S_IRWXG) == S_IXGRP)
+
+#define S_ISROTH(m) (((m) & S_IRWXO) == S_IROTH)
+#define S_ISWOTH(m) (((m) & S_IRWXO) == S_IWOTH)
+#define S_ISXOTH(m) (((m) & S_IRWXO) == S_IXOTH)
+
+bool FileAttributes::ReadAttributes() {
+ bool result = false;
+
+ DWORD attributes = ::GetFileAttributes(FFileName.data());
+
+ if (attributes != INVALID_FILE_ATTRIBUTES) {
+ result = true;
+
+ if (attributes | FILE_ATTRIBUTE_ARCHIVE) {
+ FAttributes.push_back(faArchive);
+ }
+ if (attributes | FILE_ATTRIBUTE_COMPRESSED) {
+ FAttributes.push_back(faCompressed);
+ }
+ if (attributes | FILE_ATTRIBUTE_DEVICE) {
+ FAttributes.push_back(faDevice);
+ }
+ if (attributes | FILE_ATTRIBUTE_DIRECTORY) {
+ FAttributes.push_back(faDirectory);
+ }
+ if (attributes | FILE_ATTRIBUTE_ENCRYPTED) {
+ FAttributes.push_back(faEncrypted);
+ }
+ if (attributes | FILE_ATTRIBUTE_HIDDEN) {
+ FAttributes.push_back(faHidden);
+ }
+ if (attributes | FILE_ATTRIBUTE_NORMAL) {
+ FAttributes.push_back(faNormal);
+ }
+ if (attributes | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED) {
+ FAttributes.push_back(faNotContentIndexed);
+ }
+ if (attributes | FILE_ATTRIBUTE_SYSTEM) {
+ FAttributes.push_back(faSystem);
+ }
+ if (attributes | FILE_ATTRIBUTE_OFFLINE) {
+ FAttributes.push_back(faOffline);
+ }
+ if (attributes | FILE_ATTRIBUTE_REPARSE_POINT) {
+ FAttributes.push_back(faSymbolicLink);
+ }
+ if (attributes | FILE_ATTRIBUTE_SPARSE_FILE) {
+ FAttributes.push_back(faSparceFile);
+ }
+ if (attributes | FILE_ATTRIBUTE_READONLY ) {
+ FAttributes.push_back(faReadOnly);
+ }
+ if (attributes | FILE_ATTRIBUTE_TEMPORARY) {
+ FAttributes.push_back(faTemporary);
+ }
+ if (attributes | FILE_ATTRIBUTE_VIRTUAL) {
+ FAttributes.push_back(faVirtual);
+ }
+ }
+
+ return result;
+}
+
+bool FileAttributes::Valid(const FileAttribute Value) {
+ bool result = false;
+
+ switch (Value) {
+ case faHidden:
+ case faReadOnly: {
+ result = true;
+ break;
+ }
+ default:
+ break;
+ }
+
+ return result;
+}
+
+void FileAttributes::Append(FileAttribute Value) {
+ if (Valid(Value) == true) {
+ FAttributes.push_back(Value);
+ WriteAttributes();
+ }
+}
+
+bool FileAttributes::Contains(FileAttribute Value) {
+ bool result = false;
+
+ std::vector<FileAttribute>::const_iterator iterator =
+ std::find(FAttributes.begin(), FAttributes.end(), Value);
+
+ if (iterator != FAttributes.end()) {
+ result = true;
+ }
+
+ return result;
+}
+
+void FileAttributes::Remove(FileAttribute Value) {
+ if (Valid(Value) == true) {
+ std::vector<FileAttribute>::iterator iterator =
+ std::find(FAttributes.begin(), FAttributes.end(), Value);
+
+ if (iterator != FAttributes.end()) {
+ FAttributes.erase(iterator);
+ WriteAttributes();
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libapplauncher/PlatformDefs.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef PLATFORM_DEFS_H
+#define PLATFORM_DEFS_H
+
+// Define Windows compatibility requirements XP or later
+#define WINVER 0x0600
+#define _WIN32_WINNT 0x0600
+
+#include <Windows.h>
+#include <tchar.h>
+#include <shlobj.h>
+#include <direct.h>
+#include <process.h>
+#include <malloc.h>
+#include <string>
+
+using namespace std;
+
+#ifndef WINDOWS
+#define WINDOWS
+#endif
+
+typedef std::wstring TString;
+#define StringLength wcslen
+
+#define TRAILING_PATHSEPARATOR '\\'
+#define BAD_TRAILING_PATHSEPARATOR '/'
+#define PATH_SEPARATOR ';'
+#define BAD_PATH_SEPARATOR ':'
+
+typedef ULONGLONG TPlatformNumber;
+typedef DWORD TProcessID;
+
+typedef void* Module;
+typedef void* Procedure;
+
+#endif // PLATFORM_DEFS_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libapplauncher/WindowsPlatform.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,759 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "Platform.h"
+
+#include "JavaVirtualMachine.h"
+#include "WindowsPlatform.h"
+#include "Package.h"
+#include "Helpers.h"
+#include "PlatformString.h"
+#include "Macros.h"
+
+#include <map>
+#include <vector>
+#include <regex>
+#include <fstream>
+#include <locale>
+#include <codecvt>
+
+using namespace std;
+
+#define WINDOWS_JPACKAGE_TMP_DIR \
+ L"\\AppData\\Local\\Java\\JPackage\\tmp"
+
+class Registry {
+private:
+ HKEY FKey;
+ HKEY FOpenKey;
+ bool FOpen;
+
+public:
+
+ Registry(HKEY Key) {
+ FOpen = false;
+ FKey = Key;
+ }
+
+ ~Registry() {
+ Close();
+ }
+
+ void Close() {
+ if (FOpen == true) {
+ RegCloseKey(FOpenKey);
+ }
+ }
+
+ bool Open(TString SubKey) {
+ bool result = false;
+ Close();
+
+ if (RegOpenKeyEx(FKey, SubKey.data(), 0, KEY_READ, &FOpenKey) ==
+ ERROR_SUCCESS) {
+ result = true;
+ }
+
+ return result;
+ }
+
+ std::list<TString> GetKeys() {
+ std::list<TString> result;
+ DWORD count;
+
+ if (RegQueryInfoKey(FOpenKey, NULL, NULL, NULL, NULL, NULL, NULL,
+ &count, NULL, NULL, NULL, NULL) == ERROR_SUCCESS) {
+
+ DWORD length = 255;
+ DynamicBuffer<TCHAR> buffer(length);
+ if (buffer.GetData() == NULL) {
+ return result;
+ }
+
+ for (unsigned int index = 0; index < count; index++) {
+ buffer.Zero();
+ DWORD status = RegEnumValue(FOpenKey, index, buffer.GetData(),
+ &length, NULL, NULL, NULL, NULL);
+
+ while (status == ERROR_MORE_DATA) {
+ length = length * 2;
+ if (!buffer.Resize(length)) {
+ return result;
+ }
+ status = RegEnumValue(FOpenKey, index, buffer.GetData(),
+ &length, NULL, NULL, NULL, NULL);
+ }
+
+ if (status == ERROR_SUCCESS) {
+ TString value = buffer.GetData();
+ result.push_back(value);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ TString ReadString(TString Name) {
+ TString result;
+ DWORD length;
+ DWORD dwRet;
+ DynamicBuffer<wchar_t> buffer(0);
+ length = 0;
+
+ dwRet = RegQueryValueEx(FOpenKey, Name.data(), NULL, NULL, NULL,
+ &length);
+ if (dwRet == ERROR_MORE_DATA || dwRet == 0) {
+ if (!buffer.Resize(length + 1)) {
+ return result;
+ }
+ dwRet = RegQueryValueEx(FOpenKey, Name.data(), NULL, NULL,
+ (LPBYTE) buffer.GetData(), &length);
+ result = buffer.GetData();
+ }
+
+ return result;
+ }
+};
+
+WindowsPlatform::WindowsPlatform(void) : Platform() {
+ FMainThread = ::GetCurrentThreadId();
+}
+
+WindowsPlatform::~WindowsPlatform(void) {
+}
+
+TString WindowsPlatform::GetPackageAppDirectory() {
+ return FilePath::IncludeTrailingSeparator(
+ GetPackageRootDirectory()) + _T("app");
+}
+
+TString WindowsPlatform::GetPackageLauncherDirectory() {
+ return GetPackageRootDirectory();
+}
+
+TString WindowsPlatform::GetPackageRuntimeBinDirectory() {
+ return FilePath::IncludeTrailingSeparator(GetPackageRootDirectory()) + _T("runtime\\bin");
+}
+
+TCHAR* WindowsPlatform::ConvertStringToFileSystemString(TCHAR* Source,
+ bool &release) {
+ // Not Implemented.
+ return NULL;
+}
+
+TCHAR* WindowsPlatform::ConvertFileSystemStringToString(TCHAR* Source,
+ bool &release) {
+ // Not Implemented.
+ return NULL;
+}
+
+TString WindowsPlatform::GetPackageRootDirectory() {
+ TString result;
+ TString filename = GetModuleFileName();
+ return FilePath::ExtractFilePath(filename);
+}
+
+TString WindowsPlatform::GetAppDataDirectory() {
+ TString result;
+ TCHAR path[MAX_PATH];
+
+ if (SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, path) == S_OK) {
+ result = path;
+ }
+
+ return result;
+}
+
+TString WindowsPlatform::GetAppName() {
+ TString result = GetModuleFileName();
+ result = FilePath::ExtractFileName(result);
+ result = FilePath::ChangeFileExt(result, _T(""));
+ return result;
+}
+
+void WindowsPlatform::ShowMessage(TString title, TString description) {
+ MessageBox(NULL, description.data(),
+ !title.empty() ? title.data() : description.data(),
+ MB_ICONERROR | MB_OK);
+}
+
+void WindowsPlatform::ShowMessage(TString description) {
+ TString appname = GetModuleFileName();
+ appname = FilePath::ExtractFileName(appname);
+ MessageBox(NULL, description.data(), appname.data(), MB_ICONERROR | MB_OK);
+}
+
+MessageResponse WindowsPlatform::ShowResponseMessage(TString title,
+ TString description) {
+ MessageResponse result = mrCancel;
+
+ if (::MessageBox(NULL, description.data(), title.data(), MB_OKCANCEL) ==
+ IDOK) {
+ result = mrOK;
+ }
+
+ return result;
+}
+
+TString WindowsPlatform::GetBundledJavaLibraryFileName(TString RuntimePath) {
+ TString result = FilePath::IncludeTrailingSeparator(RuntimePath) +
+ _T("jre\\bin\\jli.dll");
+
+ if (FilePath::FileExists(result) == false) {
+ result = FilePath::IncludeTrailingSeparator(RuntimePath) +
+ _T("bin\\jli.dll");
+ }
+
+ return result;
+}
+
+ISectionalPropertyContainer* WindowsPlatform::GetConfigFile(TString FileName) {
+ IniFile *result = new IniFile();
+ if (result == NULL) {
+ return NULL;
+ }
+
+ result->LoadFromFile(FileName);
+
+ return result;
+}
+
+TString WindowsPlatform::GetModuleFileName() {
+ TString result;
+ DynamicBuffer<wchar_t> buffer(MAX_PATH);
+ if (buffer.GetData() == NULL) {
+ return result;
+ }
+
+ ::GetModuleFileName(NULL, buffer.GetData(),
+ static_cast<DWORD> (buffer.GetSize()));
+
+ while (ERROR_INSUFFICIENT_BUFFER == GetLastError()) {
+ if (!buffer.Resize(buffer.GetSize() * 2)) {
+ return result;
+ }
+ ::GetModuleFileName(NULL, buffer.GetData(),
+ static_cast<DWORD> (buffer.GetSize()));
+ }
+
+ result = buffer.GetData();
+ return result;
+}
+
+Module WindowsPlatform::LoadLibrary(TString FileName) {
+ return ::LoadLibrary(FileName.data());
+}
+
+void WindowsPlatform::FreeLibrary(Module AModule) {
+ ::FreeLibrary((HMODULE) AModule);
+}
+
+Procedure WindowsPlatform::GetProcAddress(Module AModule,
+ std::string MethodName) {
+ return ::GetProcAddress((HMODULE) AModule, MethodName.c_str());
+}
+
+bool WindowsPlatform::IsMainThread() {
+ bool result = (FMainThread == ::GetCurrentThreadId());
+ return result;
+}
+
+TString WindowsPlatform::GetTempDirectory() {
+ TString result;
+ PWSTR userDir = 0;
+
+ if (SUCCEEDED(SHGetKnownFolderPath(
+ FOLDERID_Profile,
+ 0,
+ NULL,
+ &userDir))) {
+ result = userDir;
+ result += WINDOWS_JPACKAGE_TMP_DIR;
+ CoTaskMemFree(userDir);
+ }
+
+ return result;
+}
+
+static BOOL CALLBACK enumWindows(HWND winHandle, LPARAM lParam) {
+ DWORD pid = (DWORD) lParam, wPid = 0;
+ GetWindowThreadProcessId(winHandle, &wPid);
+ if (pid == wPid) {
+ SetForegroundWindow(winHandle);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+TPlatformNumber WindowsPlatform::GetMemorySize() {
+ SYSTEM_INFO si;
+ GetSystemInfo(&si);
+ size_t result = (size_t) si.lpMaximumApplicationAddress;
+ result = result / 1048576; // Convert from bytes to megabytes.
+ return result;
+}
+
+std::vector<TString> FilterList(std::vector<TString> &Items,
+ std::wregex Pattern) {
+ std::vector<TString> result;
+
+ for (std::vector<TString>::iterator it = Items.begin();
+ it != Items.end(); ++it) {
+ TString item = *it;
+ std::wsmatch match;
+
+ if (std::regex_search(item, match, Pattern)) {
+ result.push_back(item);
+ }
+ }
+ return result;
+}
+
+Process* WindowsPlatform::CreateProcess() {
+ return new WindowsProcess();
+}
+
+void WindowsPlatform::InitStreamLocale(wios *stream) {
+ const std::locale empty_locale = std::locale::empty();
+ const std::locale utf8_locale =
+ std::locale(empty_locale, new std::codecvt_utf8<wchar_t>());
+ stream->imbue(utf8_locale);
+}
+
+void WindowsPlatform::addPlatformDependencies(JavaLibrary *pJavaLibrary) {
+ if (pJavaLibrary == NULL) {
+ return;
+ }
+
+ if (FilePath::FileExists(_T("msvcr100.dll")) == true) {
+ pJavaLibrary->AddDependency(_T("msvcr100.dll"));
+ }
+
+ TString runtimeBin = GetPackageRuntimeBinDirectory();
+ SetDllDirectory(runtimeBin.c_str());
+}
+
+void Platform::CopyString(char *Destination,
+ size_t NumberOfElements, const char *Source) {
+ strcpy_s(Destination, NumberOfElements, Source);
+
+ if (NumberOfElements > 0) {
+ Destination[NumberOfElements - 1] = '\0';
+ }
+}
+
+void Platform::CopyString(wchar_t *Destination,
+ size_t NumberOfElements, const wchar_t *Source) {
+ wcscpy_s(Destination, NumberOfElements, Source);
+
+ if (NumberOfElements > 0) {
+ Destination[NumberOfElements - 1] = '\0';
+ }
+}
+
+// Owner must free the return value.
+MultibyteString Platform::WideStringToMultibyteString(
+ const wchar_t* value) {
+ MultibyteString result;
+ size_t count = 0;
+
+ if (value == NULL) {
+ return result;
+ }
+
+ count = WideCharToMultiByte(CP_UTF8, 0, value, -1, NULL, 0, NULL, NULL);
+
+ if (count > 0) {
+ result.data = new char[count + 1];
+ result.length = WideCharToMultiByte(CP_UTF8, 0, value, -1,
+ result.data, (int)count, NULL, NULL);
+ }
+
+ return result;
+}
+
+// Owner must free the return value.
+WideString Platform::MultibyteStringToWideString(const char* value) {
+ WideString result;
+ size_t count = 0;
+
+ if (value == NULL) {
+ return result;
+ }
+
+ mbstowcs_s(&count, NULL, 0, value, _TRUNCATE);
+
+ if (count > 0) {
+ result.data = new wchar_t[count + 1];
+ mbstowcs_s(&result.length, result.data, count, value, count);
+ }
+
+ return result;
+}
+
+FileHandle::FileHandle(std::wstring FileName) {
+ FHandle = ::CreateFile(FileName.data(), GENERIC_READ, FILE_SHARE_READ,
+ NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
+}
+
+FileHandle::~FileHandle() {
+ if (IsValid() == true) {
+ ::CloseHandle(FHandle);
+ }
+}
+
+bool FileHandle::IsValid() {
+ return FHandle != INVALID_HANDLE_VALUE;
+}
+
+HANDLE FileHandle::GetHandle() {
+ return FHandle;
+}
+
+FileMappingHandle::FileMappingHandle(HANDLE FileHandle) {
+ FHandle = ::CreateFileMapping(FileHandle, NULL, PAGE_READONLY, 0, 0, NULL);
+}
+
+bool FileMappingHandle::IsValid() {
+ return FHandle != NULL;
+}
+
+FileMappingHandle::~FileMappingHandle() {
+ if (IsValid() == true) {
+ ::CloseHandle(FHandle);
+ }
+}
+
+HANDLE FileMappingHandle::GetHandle() {
+ return FHandle;
+}
+
+FileData::FileData(HANDLE Handle) {
+ FBaseAddress = ::MapViewOfFile(Handle, FILE_MAP_READ, 0, 0, 0);
+}
+
+FileData::~FileData() {
+ if (IsValid() == true) {
+ ::UnmapViewOfFile(FBaseAddress);
+ }
+}
+
+bool FileData::IsValid() {
+ return FBaseAddress != NULL;
+}
+
+LPVOID FileData::GetBaseAddress() {
+ return FBaseAddress;
+}
+
+WindowsLibrary::WindowsLibrary(std::wstring FileName) {
+ FFileName = FileName;
+}
+
+std::vector<TString> WindowsLibrary::GetImports() {
+ std::vector<TString> result;
+ FileHandle library(FFileName);
+
+ if (library.IsValid() == true) {
+ FileMappingHandle mapping(library.GetHandle());
+
+ if (mapping.IsValid() == true) {
+ FileData fileData(mapping.GetHandle());
+
+ if (fileData.IsValid() == true) {
+ PIMAGE_DOS_HEADER dosHeader =
+ (PIMAGE_DOS_HEADER) fileData.GetBaseAddress();
+ PIMAGE_FILE_HEADER pImgFileHdr =
+ (PIMAGE_FILE_HEADER) fileData.GetBaseAddress();
+ if (dosHeader->e_magic == IMAGE_DOS_SIGNATURE) {
+ result = DumpPEFile(dosHeader);
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+// Given an RVA, look up the section header that encloses it and return a
+// pointer to its IMAGE_SECTION_HEADER
+
+PIMAGE_SECTION_HEADER WindowsLibrary::GetEnclosingSectionHeader(DWORD rva,
+ PIMAGE_NT_HEADERS pNTHeader) {
+ PIMAGE_SECTION_HEADER result = 0;
+ PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(pNTHeader);
+
+ for (unsigned index = 0; index < pNTHeader->FileHeader.NumberOfSections;
+ index++, section++) {
+ // Is the RVA is within this section?
+ if ((rva >= section->VirtualAddress) &&
+ (rva < (section->VirtualAddress + section->Misc.VirtualSize))) {
+ result = section;
+ }
+ }
+
+ return result;
+}
+
+LPVOID WindowsLibrary::GetPtrFromRVA(DWORD rva, PIMAGE_NT_HEADERS pNTHeader,
+ DWORD imageBase) {
+ LPVOID result = 0;
+ PIMAGE_SECTION_HEADER pSectionHdr = GetEnclosingSectionHeader(rva,
+ pNTHeader);
+
+ if (pSectionHdr != NULL) {
+ INT delta = (INT) (
+ pSectionHdr->VirtualAddress - pSectionHdr->PointerToRawData);
+ DWORD_PTR dwp = (DWORD_PTR) (imageBase + rva - delta);
+ result = reinterpret_cast<LPVOID> (dwp); // VS2017 - FIXME
+ }
+
+ return result;
+}
+
+std::vector<TString> WindowsLibrary::GetImportsSection(DWORD base,
+ PIMAGE_NT_HEADERS pNTHeader) {
+ std::vector<TString> result;
+
+ // Look up where the imports section is located. Normally in
+ // the .idata section,
+ // but not necessarily so. Therefore, grab the RVA from the data dir.
+ DWORD importsStartRVA = pNTHeader->OptionalHeader.DataDirectory[
+ IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
+
+ if (importsStartRVA != NULL) {
+ // Get the IMAGE_SECTION_HEADER that contains the imports. This is
+ // usually the .idata section, but doesn't have to be.
+ PIMAGE_SECTION_HEADER pSection =
+ GetEnclosingSectionHeader(importsStartRVA, pNTHeader);
+
+ if (pSection != NULL) {
+ PIMAGE_IMPORT_DESCRIPTOR importDesc =
+ (PIMAGE_IMPORT_DESCRIPTOR) GetPtrFromRVA(
+ importsStartRVA, pNTHeader, base);
+
+ if (importDesc != NULL) {
+ while (true) {
+ // See if we've reached an empty IMAGE_IMPORT_DESCRIPTOR
+ if ((importDesc->TimeDateStamp == 0) &&
+ (importDesc->Name == 0)) {
+ break;
+ }
+
+ std::string filename = (char*) GetPtrFromRVA(
+ importDesc->Name, pNTHeader, base);
+ result.push_back(PlatformString(filename));
+ importDesc++; // advance to next IMAGE_IMPORT_DESCRIPTOR
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+std::vector<TString> WindowsLibrary::DumpPEFile(PIMAGE_DOS_HEADER dosHeader) {
+ std::vector<TString> result;
+ // all of this is VS2017 - FIXME
+ DWORD_PTR dwDosHeaders = reinterpret_cast<DWORD_PTR> (dosHeader);
+ DWORD_PTR dwPIHeaders = dwDosHeaders + (DWORD) (dosHeader->e_lfanew);
+
+ PIMAGE_NT_HEADERS pNTHeader =
+ reinterpret_cast<PIMAGE_NT_HEADERS> (dwPIHeaders);
+
+ // Verify that the e_lfanew field gave us a reasonable
+ // pointer and the PE signature.
+ // TODO: To really fix JDK-8131321 this condition needs to be changed.
+ // There is a matching change
+ // in JavaVirtualMachine.cpp that also needs to be changed.
+ if (pNTHeader->Signature == IMAGE_NT_SIGNATURE) {
+ DWORD base = (DWORD) (dwDosHeaders);
+ result = GetImportsSection(base, pNTHeader);
+ }
+
+ return result;
+}
+
+#include <TlHelp32.h>
+
+WindowsJob::WindowsJob() {
+ FHandle = NULL;
+}
+
+WindowsJob::~WindowsJob() {
+ if (FHandle != NULL) {
+ CloseHandle(FHandle);
+ }
+}
+
+HANDLE WindowsJob::GetHandle() {
+ if (FHandle == NULL) {
+ FHandle = CreateJobObject(NULL, NULL); // GLOBAL
+
+ if (FHandle == NULL) {
+ ::MessageBox(0, _T("Could not create job object"),
+ _T("TEST"), MB_OK);
+ } else {
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {0};
+
+ // Configure all child processes associated with
+ // the job to terminate when the
+ jeli.BasicLimitInformation.LimitFlags =
+ JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
+ if (0 == SetInformationJobObject(FHandle,
+ JobObjectExtendedLimitInformation, &jeli, sizeof (jeli))) {
+ ::MessageBox(0, _T("Could not SetInformationJobObject"),
+ _T("TEST"), MB_OK);
+ }
+ }
+ }
+
+ return FHandle;
+}
+
+// Initialize static member of WindowsProcess
+WindowsJob WindowsProcess::FJob;
+
+WindowsProcess::WindowsProcess() : Process() {
+ FRunning = false;
+}
+
+WindowsProcess::~WindowsProcess() {
+ Terminate();
+}
+
+void WindowsProcess::Cleanup() {
+ CloseHandle(FProcessInfo.hProcess);
+ CloseHandle(FProcessInfo.hThread);
+}
+
+bool WindowsProcess::IsRunning() {
+ bool result = false;
+
+ HANDLE handle = ::CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0);
+ if (handle == INVALID_HANDLE_VALUE) {
+ return false;
+ }
+
+ PROCESSENTRY32 process = {0};
+ process.dwSize = sizeof (process);
+
+ if (::Process32First(handle, &process)) {
+ do {
+ if (process.th32ProcessID == FProcessInfo.dwProcessId) {
+ result = true;
+ break;
+ }
+ } while (::Process32Next(handle, &process));
+ }
+
+ CloseHandle(handle);
+
+ return result;
+}
+
+bool WindowsProcess::Terminate() {
+ bool result = false;
+
+ if (IsRunning() == true && FRunning == true) {
+ FRunning = false;
+ }
+
+ return result;
+}
+
+bool WindowsProcess::Execute(const TString Application,
+ const std::vector<TString> Arguments, bool AWait) {
+ bool result = false;
+
+ if (FRunning == false) {
+ FRunning = true;
+
+ STARTUPINFO startupInfo;
+ ZeroMemory(&startupInfo, sizeof (startupInfo));
+ startupInfo.cb = sizeof (startupInfo);
+ ZeroMemory(&FProcessInfo, sizeof (FProcessInfo));
+
+ TString command = Application;
+
+ for (std::vector<TString>::const_iterator iterator = Arguments.begin();
+ iterator != Arguments.end(); iterator++) {
+ command += TString(_T(" ")) + *iterator;
+ }
+
+ if (::CreateProcess(Application.data(), (wchar_t*)command.data(), NULL,
+ NULL, FALSE, 0, NULL, NULL, &startupInfo, &FProcessInfo)
+ == FALSE) {
+ TString message = PlatformString::Format(
+ _T("Error: Unable to create process %s"),
+ Application.data());
+ throw Exception(message);
+ } else {
+ if (FJob.GetHandle() != NULL) {
+ if (::AssignProcessToJobObject(FJob.GetHandle(),
+ FProcessInfo.hProcess) == 0) {
+ // Failed to assign process to job. It doesn't prevent
+ // anything from continuing so continue.
+ }
+ }
+
+ // Wait until child process exits.
+ if (AWait == true) {
+ Wait();
+ // Close process and thread handles.
+ Cleanup();
+ }
+ }
+ }
+
+ return result;
+}
+
+bool WindowsProcess::Wait() {
+ bool result = false;
+
+ WaitForSingleObject(FProcessInfo.hProcess, INFINITE);
+ return result;
+}
+
+TProcessID WindowsProcess::GetProcessID() {
+ return FProcessInfo.dwProcessId;
+}
+
+bool WindowsProcess::ReadOutput() {
+ bool result = false;
+ // TODO implement
+ return result;
+}
+
+void WindowsProcess::SetInput(TString Value) {
+ // TODO implement
+}
+
+std::list<TString> WindowsProcess::GetOutput() {
+ ReadOutput();
+ return Process::GetOutput();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libapplauncher/WindowsPlatform.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef WINDOWSPLATFORM_H
+#define WINDOWSPLATFORM_H
+
+#include <Windows.h>
+#include "Platform.h"
+
+class WindowsPlatform : virtual public Platform {
+private:
+ DWORD FMainThread;
+
+public:
+ WindowsPlatform(void);
+ virtual ~WindowsPlatform(void);
+
+ virtual TCHAR* ConvertStringToFileSystemString(TCHAR* Source,
+ bool &release);
+ virtual TCHAR* ConvertFileSystemStringToString(TCHAR* Source,
+ bool &release);
+
+ virtual void ShowMessage(TString title, TString description);
+ virtual void ShowMessage(TString description);
+ virtual MessageResponse ShowResponseMessage(TString title,
+ TString description);
+
+ virtual TString GetPackageRootDirectory();
+ virtual TString GetAppDataDirectory();
+ virtual TString GetAppName();
+ virtual TString GetBundledJavaLibraryFileName(TString RuntimePath);
+ TString GetPackageAppDirectory();
+ TString GetPackageLauncherDirectory();
+ TString GetPackageRuntimeBinDirectory();
+
+ virtual ISectionalPropertyContainer* GetConfigFile(TString FileName);
+
+ virtual TString GetModuleFileName();
+ virtual Module LoadLibrary(TString FileName);
+ virtual void FreeLibrary(Module AModule);
+ virtual Procedure GetProcAddress(Module AModule, std::string MethodName);
+
+ virtual Process* CreateProcess();
+
+ virtual bool IsMainThread();
+ virtual TPlatformNumber GetMemorySize();
+
+ virtual TString GetTempDirectory();
+ void InitStreamLocale(wios *stream);
+ void addPlatformDependencies(JavaLibrary *pJavaLibrary);
+};
+
+class FileHandle {
+private:
+ HANDLE FHandle;
+
+public:
+ FileHandle(std::wstring FileName);
+ ~FileHandle();
+
+ bool IsValid();
+ HANDLE GetHandle();
+};
+
+
+class FileMappingHandle {
+private:
+ HANDLE FHandle;
+
+public:
+ FileMappingHandle(HANDLE FileHandle);
+ ~FileMappingHandle();
+
+ bool IsValid();
+ HANDLE GetHandle();
+};
+
+
+class FileData {
+private:
+ LPVOID FBaseAddress;
+
+public:
+ FileData(HANDLE Handle);
+ ~FileData();
+
+ bool IsValid();
+ LPVOID GetBaseAddress();
+};
+
+
+class WindowsLibrary {
+private:
+ TString FFileName;
+
+ // Given an RVA, look up the section header that encloses it and return a
+ // pointer to its IMAGE_SECTION_HEADER
+ static PIMAGE_SECTION_HEADER GetEnclosingSectionHeader(DWORD rva,
+ PIMAGE_NT_HEADERS pNTHeader);
+ static LPVOID GetPtrFromRVA(DWORD rva, PIMAGE_NT_HEADERS pNTHeader,
+ DWORD imageBase);
+ static std::vector<TString> GetImportsSection(DWORD base,
+ PIMAGE_NT_HEADERS pNTHeader);
+ static std::vector<TString> DumpPEFile(PIMAGE_DOS_HEADER dosHeader);
+
+public:
+ WindowsLibrary(const TString FileName);
+
+ std::vector<TString> GetImports();
+};
+
+
+class WindowsJob {
+private:
+ HANDLE FHandle;
+
+public:
+ WindowsJob();
+ ~WindowsJob();
+
+ HANDLE GetHandle();
+};
+
+
+class WindowsProcess : public Process {
+private:
+ bool FRunning;
+
+ PROCESS_INFORMATION FProcessInfo;
+ static WindowsJob FJob;
+
+ void Cleanup();
+ bool ReadOutput();
+
+public:
+ WindowsProcess();
+ virtual ~WindowsProcess();
+
+ virtual bool IsRunning();
+ virtual bool Terminate();
+ virtual bool Execute(const TString Application,
+ const std::vector<TString> Arguments, bool AWait = false);
+ virtual bool Wait();
+ virtual TProcessID GetProcessID();
+ virtual void SetInput(TString Value);
+ virtual std::list<TString> GetOutput();
+};
+
+#endif // WINDOWSPLATFORM_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/ByteBuffer.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "ByteBuffer.h"
+
+#include <stdio.h>
+
+ByteBuffer::ByteBuffer() {
+ buffer.reserve(1024);
+}
+
+ByteBuffer::~ByteBuffer() {
+}
+
+LPBYTE ByteBuffer::getPtr() {
+ return &buffer[0];
+}
+
+size_t ByteBuffer::getPos() {
+ return buffer.size();
+}
+
+void ByteBuffer::AppendString(wstring str) {
+ size_t len = (str.size() + 1) * sizeof (WCHAR);
+ AppendBytes((BYTE*) str.c_str(), len);
+}
+
+void ByteBuffer::AppendWORD(WORD word) {
+ AppendBytes((BYTE*) & word, sizeof (WORD));
+}
+
+void ByteBuffer::Align(size_t bytesNumber) {
+ size_t pos = getPos();
+ if (pos % bytesNumber) {
+ DWORD dwNull = 0;
+ size_t len = bytesNumber - pos % bytesNumber;
+ AppendBytes((BYTE*) & dwNull, len);
+ }
+}
+
+void ByteBuffer::AppendBytes(BYTE* ptr, size_t len) {
+ buffer.insert(buffer.end(), ptr, ptr + len);
+}
+
+void ByteBuffer::ReplaceWORD(size_t offset, WORD word) {
+ ReplaceBytes(offset, (BYTE*) & word, sizeof (WORD));
+}
+
+void ByteBuffer::ReplaceBytes(size_t offset, BYTE* ptr, size_t len) {
+ for (size_t i = 0; i < len; i++) {
+ buffer[offset + i] = *(ptr + i);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/ByteBuffer.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef BYTEBUFFER_H
+#define BYTEBUFFER_H
+
+#include <windows.h>
+#include <vector>
+#include <string>
+
+using namespace std;
+
+class ByteBuffer {
+public:
+ ByteBuffer();
+ ~ByteBuffer();
+
+ LPBYTE getPtr();
+ size_t getPos();
+
+ void AppendString(wstring str);
+ void AppendWORD(WORD word);
+ void AppendBytes(BYTE* ptr, size_t len);
+
+ void ReplaceWORD(size_t offset, WORD word);
+ void ReplaceBytes(size_t offset, BYTE* ptr, size_t len);
+
+ void Align(size_t bytesNumber);
+
+private:
+ vector<BYTE> buffer;
+};
+
+#endif // BYTEBUFFER_H
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/ErrorHandling.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <algorithm>
+
+#include "ErrorHandling.h"
+#include "Log.h"
+
+
+namespace {
+
+tstring getFilename(const SourceCodePos& pos) {
+ const std::string buf(pos.file);
+ const std::string::size_type idx = buf.find_last_of("\\/");
+ if (idx == std::string::npos) {
+ return tstrings::fromUtf8(buf);
+ }
+ return tstrings::fromUtf8(buf.substr(idx + 1));
+}
+
+void reportError(const SourceCodePos& pos, const tstring& msg) {
+ Logger::defaultLogger().log(Logger::LOG_ERROR, getFilename(pos).c_str(),
+ pos.lno, tstrings::fromUtf8(pos.func).c_str(), msg);
+}
+
+} // namespace
+
+void reportError(const SourceCodePos& pos, const std::exception& e) {
+ reportError(pos, (tstrings::any() << "Exception with message \'"
+ << e.what() << "\' caught").tstr());
+}
+
+
+void reportUnknownError(const SourceCodePos& pos) {
+ reportError(pos, _T("Unknown exception caught"));
+}
+
+
+std::string makeMessage(const std::exception& e, const SourceCodePos& pos) {
+ std::ostringstream printer;
+ printer << getFilename(pos) << "(" << pos.lno << ") at "
+ << pos.func << "(): "
+ << e.what();
+ return printer.str();
+}
+
+
+namespace {
+
+bool isNotSpace(int chr) {
+ return isspace(chr) == 0;
+}
+
+
+enum TrimMode {
+ TrimLeading = 0x10,
+ TrimTrailing = 0x20,
+ TrimBoth = TrimLeading | TrimTrailing
+};
+
+// Returns position of the last printed character in the given string.
+// Returns std::string::npos if nothing was printed.
+size_t printWithoutWhitespaces(std::ostream& out, const std::string& str,
+ TrimMode mode) {
+ std::string::const_reverse_iterator it = str.rbegin();
+ std::string::const_reverse_iterator end = str.rend();
+
+ if (mode & TrimLeading) {
+ // skip leading whitespace
+ std::string::const_iterator entry = std::find_if(str.begin(),
+ str.end(), isNotSpace);
+ end = std::string::const_reverse_iterator(entry);
+ }
+
+ if (mode & TrimTrailing) {
+ // skip trailing whitespace
+ it = std::find_if(it, end, isNotSpace);
+ }
+
+ if (it == end) {
+ return std::string::npos;
+ }
+
+ const size_t pos = str.rend() - end;
+ const size_t len = end - it;
+ out.write(str.c_str() + pos, len);
+ return pos + len - 1;
+}
+
+} // namespace
+
+std::string joinErrorMessages(const std::string& a, const std::string& b) {
+ const std::string endPhraseChars(";.,:!?");
+ const std::string space(" ");
+ const std::string dotAndSpace(". ");
+
+ std::ostringstream printer;
+ printer.exceptions(std::ios::failbit | std::ios::badbit);
+
+ size_t idx = printWithoutWhitespaces(printer, a, TrimTrailing);
+ size_t extra = 0;
+ if (idx < a.size() && endPhraseChars.find(a[idx]) == std::string::npos) {
+ printer << dotAndSpace;
+ extra = dotAndSpace.size();
+ } else if (idx != std::string::npos) {
+ printer << space;
+ extra = space.size();
+ }
+
+ idx = printWithoutWhitespaces(printer, b, TrimBoth);
+
+ const std::string str = printer.str();
+
+ if (std::string::npos == idx && extra) {
+ // Nothing printed from the 'b' message. Backout delimiter string.
+ return str.substr(0, str.size() - extra);
+ }
+ return str;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/ErrorHandling.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ErrorHandling_h
+#define ErrorHandling_h
+
+
+#include <stdexcept>
+
+#include "SourceCodePos.h"
+#include "tstrings.h"
+
+
+//
+// Exception handling helpers. Allow transparent exception logging.
+// Use as follows:
+//
+// void foo () {
+// JP_TRY;
+//
+// if (!do_something()) {
+// JP_THROW("do_something() failed");
+// }
+//
+// JP_CATCH_ALL;
+// }
+//
+
+
+// Logs std::exception caught at 'pos'.
+void reportError(const SourceCodePos& pos, const std::exception& e);
+// Logs unknown exception caught at 'pos'.
+// Assumed to be called from catch (...) {}
+void reportUnknownError(const SourceCodePos& pos);
+
+std::string makeMessage(const std::exception& e, const SourceCodePos& pos);
+
+std::string joinErrorMessages(const std::string& a, const std::string& b);
+
+
+template <class Base>
+class JpError: public Base {
+public:
+ JpError(const Base& e, const SourceCodePos& pos):
+ Base(e), msg(::makeMessage(e, pos)) {
+ }
+
+ ~JpError() throw() {
+ }
+
+ // override Base::what()
+ const char* what() const throw() {
+ return msg.c_str();
+ }
+private:
+ // Assert Base is derived from std::exception
+ enum { isDerivedFromStdException =
+ sizeof(static_cast<std::exception*>((Base*)0)) };
+
+ std::string msg;
+};
+
+template <class T>
+inline JpError<T> makeException(const T& obj, const SourceCodePos& p) {
+ return JpError<T>(obj, p);
+}
+
+inline JpError<std::runtime_error> makeException(
+ const std::string& msg, const SourceCodePos& p) {
+ return JpError<std::runtime_error>(std::runtime_error(msg), p);
+}
+
+inline JpError<std::runtime_error> makeException(
+ const tstrings::any& msg, const SourceCodePos& p) {
+ return makeException(msg.str(), p);
+}
+
+inline JpError<std::runtime_error> makeException(
+ std::string::const_pointer msg, const SourceCodePos& p) {
+ return makeException(std::string(msg), p);
+}
+
+
+#define JP_REPORT_ERROR(e) reportError(JP_SOURCE_CODE_POS, e)
+#define JP_REPORT_UNKNOWN_ERROR reportUnknownError(JP_SOURCE_CODE_POS)
+
+// Redefine locally in cpp file(s) if need more handling than just reporting
+#define JP_HANDLE_ERROR(e) JP_REPORT_ERROR(e)
+#define JP_HANDLE_UNKNOWN_ERROR JP_REPORT_UNKNOWN_ERROR
+
+
+#define JP_TRY \
+ try \
+ { \
+ do {} while(0)
+
+#define JP_DEFAULT_CATCH_EXCEPTIONS \
+ JP_CATCH_STD_EXCEPTION \
+ JP_CATCH_UNKNOWN_EXCEPTION
+
+#define JP_CATCH_EXCEPTIONS \
+ JP_DEFAULT_CATCH_EXCEPTIONS
+
+#define JP_CATCH_ALL \
+ } \
+ JP_CATCH_EXCEPTIONS \
+ do {} while(0)
+
+#define JP_CATCH_STD_EXCEPTION \
+ catch (const std::exception& e) \
+ { \
+ JP_HANDLE_ERROR(e); \
+ }
+
+#define JP_CATCH_UNKNOWN_EXCEPTION \
+ catch (...) \
+ { \
+ JP_HANDLE_UNKNOWN_ERROR; \
+ }
+
+
+#define JP_THROW(e) throw makeException((e), JP_SOURCE_CODE_POS)
+
+#define JP_NO_THROW(expr) \
+ JP_TRY; \
+ expr; \
+ JP_CATCH_ALL
+
+#endif // #ifndef ErrorHandling_h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/FileUtils.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,709 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <memory>
+#include <algorithm>
+#include <shlwapi.h>
+
+#include "FileUtils.h"
+#include "WinErrorHandling.h"
+#include "Log.h"
+
+
+// Needed by FileUtils::isDirectoryNotEmpty
+#pragma comment(lib, "shlwapi")
+
+
+namespace FileUtils {
+
+namespace {
+
+
+tstring reservedFilenameChars() {
+ tstring buf;
+ for (char charCode = 0; charCode < 32; ++charCode) {
+ buf.append(1, charCode);
+ }
+ buf += _T("<>:\"|?*/\\");
+ return buf;
+}
+
+} // namespace
+
+bool isDirSeparator(const tstring::value_type c) {
+ return (c == '/' || c == '\\');
+}
+
+bool isFileExists(const tstring &filePath) {
+ return GetFileAttributes(filePath.c_str()) != INVALID_FILE_ATTRIBUTES;
+}
+
+namespace {
+bool isDirectoryAttrs(const DWORD attrs) {
+ return attrs != INVALID_FILE_ATTRIBUTES
+ && (attrs & FILE_ATTRIBUTE_DIRECTORY) != 0;
+}
+} // namespace
+
+bool isDirectory(const tstring &filePath) {
+ return isDirectoryAttrs(GetFileAttributes(filePath.c_str()));
+}
+
+bool isDirectoryNotEmpty(const tstring &dirPath) {
+ if (!isDirectory(dirPath)) {
+ return false;
+ }
+ return FALSE == PathIsDirectoryEmpty(dirPath.c_str());
+}
+
+tstring dirname(const tstring &path) {
+ tstring::size_type pos = path.find_last_of(_T("\\/"));
+ if (pos != tstring::npos) {
+ pos = path.find_last_not_of(_T("\\/"), pos); // skip trailing slashes
+ }
+ return pos == tstring::npos ? tstring() : path.substr(0, pos + 1);
+}
+
+tstring basename(const tstring &path) {
+ const tstring::size_type pos = path.find_last_of(_T("\\/"));
+ if (pos == tstring::npos) {
+ return path;
+ }
+ return path.substr(pos + 1);
+}
+
+tstring suffix(const tstring &path) {
+ const tstring::size_type pos = path.rfind('.');
+ if (pos == tstring::npos) {
+ return tstring();
+ }
+ const tstring::size_type dirSepPos = path.find_first_of(_T("\\/"),
+ pos + 1);
+ if (dirSepPos != tstring::npos) {
+ return tstring();
+ }
+ // test for '/..' and '..' cases
+ if (pos != 0 && path[pos - 1] == '.'
+ && (pos == 1 || isDirSeparator(path[pos - 2]))) {
+ return tstring();
+ }
+ return path.substr(pos);
+}
+
+tstring combinePath(const tstring& parent, const tstring& child) {
+ if (parent.empty()) {
+ return child;
+ }
+ if (child.empty()) {
+ return parent;
+ }
+
+ tstring parentWOSlash = removeTrailingSlash(parent);
+ // also handle the case when child contains starting slash
+ bool childHasSlash = isDirSeparator(child.front());
+ tstring childWOSlash = childHasSlash ? child.substr(1) : child;
+
+ return parentWOSlash + _T("\\") + childWOSlash;
+}
+
+tstring removeTrailingSlash(const tstring& path) {
+ if (path.empty()) {
+ return path;
+ }
+ tstring::const_reverse_iterator it = path.rbegin();
+ tstring::const_reverse_iterator end = path.rend();
+
+ while (it != end && isDirSeparator(*it)) {
+ ++it;
+ }
+ return path.substr(0, end - it);
+}
+
+tstring normalizePath(tstring v) {
+ std::replace(v.begin(), v.end(), '/', '\\');
+ return tstrings::toLower(v);
+}
+
+namespace {
+
+bool createNewFile(const tstring& path) {
+ HANDLE h = CreateFile(path.c_str(), GENERIC_WRITE, 0, NULL, CREATE_NEW,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ // if the file exists => h == INVALID_HANDLE_VALUE & GetLastError
+ // returns ERROR_FILE_EXISTS
+ if (h != INVALID_HANDLE_VALUE) {
+ CloseHandle(h);
+ LOG_TRACE(tstrings::any() << "Created [" << path << "] file");
+ return true;
+ }
+ return false;
+}
+
+} // namespace
+
+tstring createTempFile(const tstring &prefix, const tstring &suffix,
+ const tstring &path) {
+ const tstring invalidChars = reservedFilenameChars();
+
+ if (prefix.find_first_of(invalidChars) != tstring::npos) {
+ JP_THROW(tstrings::any() << "Illegal characters in prefix=" << prefix);
+ }
+
+ if (suffix.find_first_of(invalidChars) != tstring::npos) {
+ JP_THROW(tstrings::any() << "Illegal characters in suffix=" << suffix);
+ }
+
+ int rnd = (int)GetTickCount();
+
+ // do no more than 100 attempts
+ for (int i=0; i<100; i++) {
+ const tstring filePath = mkpath() << path << (prefix
+ + (tstrings::any() << (rnd + i)).tstr() + suffix);
+ if (createNewFile(filePath)) {
+ return filePath;
+ }
+ }
+
+ // 100 attempts failed
+ JP_THROW(tstrings::any() << "createTempFile(" << prefix << ", "
+ << suffix << ", "
+ << path << ") failed");
+}
+
+tstring createTempDirectory(const tstring &prefix, const tstring &suffix,
+ const tstring &basedir) {
+ const tstring filePath = createTempFile(prefix, suffix, basedir);
+ // delete the file and create directory with the same name
+ deleteFile(filePath);
+ createDirectory(filePath);
+ return filePath;
+}
+
+tstring createUniqueFile(const tstring &prototype) {
+ if (createNewFile(prototype)) {
+ return prototype;
+ }
+
+ return createTempFile(replaceSuffix(basename(prototype)),
+ suffix(prototype), dirname(prototype));
+}
+
+namespace {
+
+void createDir(const tstring path, LPSECURITY_ATTRIBUTES saAttr,
+ tstring_array* createdDirs=0) {
+ if (CreateDirectory(path.c_str(), saAttr)) {
+ LOG_TRACE(tstrings::any() << "Created [" << path << "] directory");
+ if (createdDirs) {
+ createdDirs->push_back(removeTrailingSlash(path));
+ }
+ } else {
+ const DWORD createDirectoryErr = GetLastError();
+ // if saAttr is specified, fail even if the directory exists
+ if (saAttr != NULL || !isDirectory(path)) {
+ JP_THROW(SysError(tstrings::any() << "CreateDirectory("
+ << path << ") failed", CreateDirectory, createDirectoryErr));
+ }
+ }
+}
+
+}
+
+void createDirectory(const tstring &path, tstring_array* createdDirs) {
+ const tstring dirPath = removeTrailingSlash(path) + _T("\\");
+
+ tstring::size_type pos = dirPath.find_first_of(_T("\\/"));
+ while (pos != tstring::npos) {
+ const tstring subdirPath = dirPath.substr(0, pos + 1);
+ createDir(subdirPath, NULL, createdDirs);
+ pos = dirPath.find_first_of(_T("\\/"), pos + 1);
+ }
+}
+
+
+void copyFile(const tstring& fromPath, const tstring& toPath,
+ bool failIfExists) {
+ createDirectory(dirname(toPath));
+ if (!CopyFile(fromPath.c_str(), toPath.c_str(),
+ (failIfExists ? TRUE : FALSE))) {
+ JP_THROW(SysError(tstrings::any()
+ << "CopyFile(" << fromPath << ", " << toPath << ", "
+ << failIfExists << ") failed", CopyFile));
+ }
+ LOG_TRACE(tstrings::any() << "Copied [" << fromPath << "] file to ["
+ << toPath << "]");
+}
+
+
+namespace {
+
+void moveFileImpl(const tstring& fromPath, const tstring& toPath,
+ DWORD flags) {
+ const bool isDir = isDirectory(fromPath);
+ if (!MoveFileEx(fromPath.c_str(), toPath.empty() ? NULL : toPath.c_str(),
+ flags)) {
+ JP_THROW(SysError(tstrings::any() << "MoveFileEx(" << fromPath
+ << ", " << toPath << ", " << flags << ") failed", MoveFileEx));
+ }
+
+ const bool onReboot = 0 != (flags & MOVEFILE_DELAY_UNTIL_REBOOT);
+
+ const LPCTSTR label = isDir ? _T("folder") : _T("file");
+
+ tstrings::any msg;
+ if (!toPath.empty()) {
+ if (onReboot) {
+ msg << "Move";
+ } else {
+ msg << "Moved";
+ }
+ msg << " '" << fromPath << "' " << label << " to '" << toPath << "'";
+ } else {
+ if (onReboot) {
+ msg << "Delete";
+ } else {
+ msg << "Deleted";
+ }
+ msg << " '" << fromPath << "' " << label;
+ }
+ if (onReboot) {
+ msg << " on reboot";
+ }
+ LOG_TRACE(msg);
+}
+
+} // namespace
+
+
+void moveFile(const tstring& fromPath, const tstring& toPath,
+ bool failIfExists) {
+ createDirectory(dirname(toPath));
+
+ DWORD flags = MOVEFILE_COPY_ALLOWED;
+ if (!failIfExists) {
+ flags |= MOVEFILE_REPLACE_EXISTING;
+ }
+
+ moveFileImpl(fromPath, toPath, flags);
+}
+
+void deleteFile(const tstring &path)
+{
+ if (!deleteFile(path, std::nothrow)) {
+ JP_THROW(SysError(tstrings::any()
+ << "DeleteFile(" << path << ") failed", DeleteFile));
+ }
+}
+
+namespace {
+
+bool notFound(const DWORD status=GetLastError()) {
+ return status == ERROR_FILE_NOT_FOUND || status == ERROR_PATH_NOT_FOUND;
+}
+
+bool deleteFileImpl(const std::nothrow_t &, const tstring &path) {
+ const bool deleted = (DeleteFile(path.c_str()) != 0);
+ if (deleted) {
+ LOG_TRACE(tstrings::any() << "Deleted [" << path << "] file");
+ return true;
+ }
+ return notFound();
+}
+
+} // namespace
+
+bool deleteFile(const tstring &path, const std::nothrow_t &) throw()
+{
+ bool deleted = deleteFileImpl(std::nothrow, path);
+ const DWORD status = GetLastError();
+ if (!deleted && status == ERROR_ACCESS_DENIED) {
+ DWORD attrs = GetFileAttributes(path.c_str());
+ SetLastError(status);
+ if (attrs == INVALID_FILE_ATTRIBUTES) {
+ return false;
+ }
+ if (attrs & FILE_ATTRIBUTE_READONLY) {
+ // DeleteFile() failed because file is R/O.
+ // Remove R/O attribute and retry DeleteFile().
+ attrs &= ~FILE_ATTRIBUTE_READONLY;
+ if (SetFileAttributes(path.c_str(), attrs)) {
+ LOG_TRACE(tstrings::any() << "Discarded R/O attribute from ["
+ << path << "] file");
+ deleted = deleteFileImpl(std::nothrow, path);
+ } else {
+ LOG_WARNING(SysError(tstrings::any()
+ << "Failed to discard R/O attribute from ["
+ << path << "] file. File will not be deleted",
+ SetFileAttributes).what());
+ SetLastError(status);
+ }
+ }
+ }
+
+ return deleted || notFound();
+}
+
+void deleteDirectory(const tstring &path)
+{
+ if (!deleteDirectory(path, std::nothrow)) {
+ JP_THROW(SysError(tstrings::any()
+ << "RemoveDirectory(" << path << ") failed", RemoveDirectory));
+ }
+}
+
+bool deleteDirectory(const tstring &path, const std::nothrow_t &) throw()
+{
+ const bool deleted = (RemoveDirectory(path.c_str()) != 0);
+ if (deleted) {
+ LOG_TRACE(tstrings::any() << "Deleted [" << path << "] directory");
+ }
+ return deleted || notFound();
+}
+
+namespace {
+
+class DeleteFilesCallback: public DirectoryCallback {
+public:
+ explicit DeleteFilesCallback(bool ff): failfast(ff), failed(false) {
+ }
+
+ virtual bool onFile(const tstring& path) {
+ if (failfast) {
+ deleteFile(path);
+ } else {
+ updateStatus(deleteFile(path, std::nothrow));
+ }
+ return true;
+ }
+
+ bool good() const {
+ return !failed;
+ }
+
+protected:
+ void updateStatus(bool success) {
+ if (!success) {
+ failed = true;
+ }
+ }
+
+ const bool failfast;
+private:
+ bool failed;
+};
+
+class DeleteAllCallback: public DeleteFilesCallback {
+public:
+ explicit DeleteAllCallback(bool failfast): DeleteFilesCallback(failfast) {
+ }
+
+ virtual bool onDirectory(const tstring& path) {
+ if (failfast) {
+ deleteDirectoryRecursive(path);
+ } else {
+ updateStatus(deleteDirectoryRecursive(path, std::nothrow));
+ }
+ return true;
+ }
+};
+
+
+class BatchDeleter {
+ const tstring dirPath;
+ bool recursive;
+public:
+ explicit BatchDeleter(const tstring& path): dirPath(path) {
+ deleteSubdirs(false);
+ }
+
+ BatchDeleter& deleteSubdirs(bool v) {
+ recursive = v;
+ return *this;
+ }
+
+ void execute() const {
+ if (!isFileExists(dirPath)) {
+ return;
+ }
+ iterateDirectory(true /* fail fast */);
+ if (recursive) {
+ deleteDirectory(dirPath);
+ }
+ }
+
+ bool execute(const std::nothrow_t&) const {
+ if (!isFileExists(dirPath)) {
+ return true;
+ }
+
+ if (!isDirectory(dirPath)) {
+ return false;
+ }
+
+ JP_TRY;
+ if (!iterateDirectory(false /* ignore errors */)) {
+ return false;
+ }
+ if (recursive) {
+ return deleteDirectory(dirPath, std::nothrow);
+ }
+ return true;
+ JP_CATCH_ALL;
+
+ return false;
+ }
+
+private:
+ bool iterateDirectory(bool failfast) const {
+ std::unique_ptr<DeleteFilesCallback> callback;
+ if (recursive) {
+ callback = std::unique_ptr<DeleteFilesCallback>(
+ new DeleteAllCallback(failfast));
+ } else {
+ callback = std::unique_ptr<DeleteFilesCallback>(
+ new DeleteFilesCallback(failfast));
+ }
+
+ FileUtils::iterateDirectory(dirPath, *callback);
+ return callback->good();
+ }
+};
+
+} // namespace
+
+void deleteFilesInDirectory(const tstring &dirPath) {
+ BatchDeleter(dirPath).execute();
+}
+
+bool deleteFilesInDirectory(const tstring &dirPath,
+ const std::nothrow_t &) throw() {
+ return BatchDeleter(dirPath).execute(std::nothrow);
+}
+
+void deleteDirectoryRecursive(const tstring &dirPath) {
+ BatchDeleter(dirPath).deleteSubdirs(true).execute();
+}
+
+bool deleteDirectoryRecursive(const tstring &dirPath,
+ const std::nothrow_t &) throw() {
+ return BatchDeleter(dirPath).deleteSubdirs(true).execute(std::nothrow);
+}
+
+namespace {
+
+struct FindFileDeleter {
+ typedef HANDLE pointer;
+
+ void operator()(HANDLE h) {
+ if (h && h != INVALID_HANDLE_VALUE) {
+ FindClose(h);
+ }
+ }
+};
+
+typedef std::unique_ptr<HANDLE, FindFileDeleter> UniqueFindFileHandle;
+
+}; // namesace
+void iterateDirectory(const tstring &dirPath, DirectoryCallback& callback)
+{
+ const tstring searchString = combinePath(dirPath, _T("*"));
+ WIN32_FIND_DATA findData;
+ UniqueFindFileHandle h(FindFirstFile(searchString.c_str(), &findData));
+ if (h.get() == INVALID_HANDLE_VALUE) {
+ // GetLastError() == ERROR_FILE_NOT_FOUND is OK
+ // - no files in the directory
+ // ERROR_PATH_NOT_FOUND is returned
+ // if the parent directory does not exist
+ if (GetLastError() != ERROR_FILE_NOT_FOUND) {
+ JP_THROW(SysError(tstrings::any() << "FindFirstFile("
+ << dirPath << ") failed", FindFirstFile));
+ }
+ return;
+ }
+
+ do {
+ const tstring fname(findData.cFileName);
+ const tstring filePath = combinePath(dirPath, fname);
+ if (!isDirectoryAttrs(findData.dwFileAttributes)) {
+ if (!callback.onFile(filePath)) {
+ return;
+ }
+ } else if (fname != _T(".") && fname != _T("..")) {
+ if (!callback.onDirectory(filePath)) {
+ return;
+ }
+ }
+ } while (FindNextFile(h.get(), &findData));
+
+ // expect GetLastError() == ERROR_NO_MORE_FILES
+ if (GetLastError() != ERROR_NO_MORE_FILES) {
+ JP_THROW(SysError(tstrings::any() << "FindNextFile("
+ << dirPath << ") failed", FindNextFile));
+ }
+}
+
+
+tstring replaceSuffix(const tstring& path, const tstring& newSuffix) {
+ return (path.substr(0, path.size() - suffix(path).size()) + newSuffix);
+}
+
+
+DirectoryIterator& DirectoryIterator::findItems(tstring_array& v) {
+ if (!isDirectory(root)) {
+ return *this;
+ }
+
+ iterateDirectory(root, *this);
+ v.insert(v.end(), items.begin(), items.end());
+ items = tstring_array();
+ return *this;
+}
+
+bool DirectoryIterator::onFile(const tstring& path) {
+ if (theWithFiles) {
+ items.push_back(path);
+ }
+ return true;
+}
+
+bool DirectoryIterator::onDirectory(const tstring& path) {
+ if (theWithFolders) {
+ items.push_back(path);
+ }
+ if (theRecurse) {
+ DirectoryIterator(path).recurse(theRecurse)
+ .withFiles(theWithFiles)
+ .withFolders(theWithFolders)
+ .findItems(items);
+ }
+ return true;
+}
+
+
+namespace {
+
+struct DeleterFunctor {
+ // Order of items in the following enum is important!
+ // It controls order in which items of particular type will be deleted.
+ // See Deleter::execute().
+ enum {
+ File,
+ FilesInDirectory,
+ RecursiveDirectory,
+ EmptyDirectory
+ };
+
+ void operator () (const Deleter::Path& path) const {
+ switch (path.second) {
+#define DELETE_SOME(o, f)\
+ case o:\
+ f(path.first, std::nothrow);\
+ break
+
+ DELETE_SOME(File, deleteFile);
+ DELETE_SOME(EmptyDirectory, deleteDirectory);
+ DELETE_SOME(FilesInDirectory, deleteFilesInDirectory);
+ DELETE_SOME(RecursiveDirectory, deleteDirectoryRecursive);
+
+#undef DELETE_SOME
+ default:
+ break;
+ }
+ }
+};
+
+} // namespace
+
+void Deleter::execute() {
+ Paths tmp;
+ tmp.swap(paths);
+
+ // Reorder items to delete.
+ std::stable_sort(tmp.begin(), tmp.end(), [] (const Paths::value_type& a,
+ const Paths::value_type& b) {
+ return a.second < b.second;
+ });
+
+ std::for_each(tmp.begin(), tmp.end(), DeleterFunctor());
+}
+
+Deleter& Deleter::appendFile(const tstring& path) {
+ paths.push_back(std::make_pair(path, DeleterFunctor::File));
+ return *this;
+}
+
+Deleter& Deleter::appendEmptyDirectory(const Directory& dir) {
+ tstring path = normalizePath(removeTrailingSlash(dir));
+ const tstring parent = normalizePath(removeTrailingSlash(dir.parent));
+ while(parent != path) {
+ appendEmptyDirectory(path);
+ path = dirname(path);
+ }
+
+ return *this;
+}
+
+Deleter& Deleter::appendEmptyDirectory(const tstring& path) {
+ paths.push_back(std::make_pair(path, DeleterFunctor::EmptyDirectory));
+ return *this;
+}
+
+Deleter& Deleter::appendAllFilesInDirectory(const tstring& path) {
+ paths.push_back(std::make_pair(path, DeleterFunctor::FilesInDirectory));
+ return *this;
+}
+
+Deleter& Deleter::appendRecursiveDirectory(const tstring& path) {
+ paths.push_back(std::make_pair(path, DeleterFunctor::RecursiveDirectory));
+ return *this;
+}
+
+
+FileWriter::FileWriter(const tstring& path): dstPath(path) {
+ tmpFile = FileUtils::createTempFile(_T("jds"), _T(".tmp"),
+ FileUtils::dirname(path));
+
+ cleaner.appendFile(tmpFile);
+
+ // we want to get exception on error
+ tmp.exceptions(std::ifstream::failbit | std::ifstream::badbit);
+ tmp.open(tmpFile, std::ios::binary | std::ios::trunc);
+}
+
+FileWriter& FileWriter::write(const void* buf, size_t bytes) {
+ tmp.write(static_cast<const char*>(buf), bytes);
+ return *this;
+}
+
+void FileWriter::finalize() {
+ tmp.close();
+
+ FileUtils::moveFile(tmpFile, dstPath, false);
+
+ // cancel file deletion
+ cleaner.cancel();
+}
+
+} // namespace FileUtils
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/FileUtils.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,398 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef FILEUTILS_H
+#define FILEUTILS_H
+
+
+#include <fstream>
+#include "SysInfo.h"
+
+
+namespace FileUtils {
+
+ // Returns 'true' if the given character is a path separator.
+ bool isDirSeparator(const tstring::value_type c);
+
+ // checks if the file or directory exists
+ bool isFileExists(const tstring &filePath);
+
+ // checks is the specified file is a directory
+ // returns false if the path does not exist
+ bool isDirectory(const tstring &filePath);
+
+ // checks if the specified directory is not empty
+ // returns true if the path is an existing directory and
+ // it contains at least one file other than "." or "..".
+ bool isDirectoryNotEmpty(const tstring &dirPath);
+
+ // returns directory part of the path.
+ // returns empty string if the path contains only filename.
+ // if the path ends with slash/backslash,
+ // returns removeTrailingSlashes(path).
+ tstring dirname(const tstring &path);
+
+ // returns basename part of the path
+ // if the path ends with slash/backslash, returns empty string.
+ tstring basename(const tstring &path);
+
+ /**
+ * Translates forward slashes to back slashes and returns lower case version
+ * of the given string.
+ */
+ tstring normalizePath(tstring v);
+
+ // Returns suffix of the path. If the given path has a suffix the first
+ // character of the return value is '.'.
+ // Otherwise return value if empty string.
+ tstring suffix(const tstring &path);
+
+ // combines two strings into a path
+ tstring combinePath(const tstring& parent, const tstring& child);
+
+ // removes trailing slashes and backslashes in the path if any
+ tstring removeTrailingSlash(const tstring& path);
+
+ // Creates a file with unique name in the specified base directory,
+ // throws an exception if operation fails
+ // path is constructed as <prefix><random number><suffix>.
+ // The function fails and throws exception if 'path' doesn't exist.
+ tstring createTempFile(const tstring &prefix = _T(""),
+ const tstring &suffix = _T(".tmp"),
+ const tstring &path=SysInfo::getTempDir());
+
+ // Creates a directory with unique name in the specified base directory,
+ // throws an exception if operation fails
+ // path is constructed as <prefix><random number><suffix>
+ // The function fails and throws exception if 'path' doesn't exist.
+ tstring createTempDirectory(const tstring &prefix = _T(""),
+ const tstring &suffix = _T(".tmp"),
+ const tstring &basedir=SysInfo::getTempDir());
+
+ // If the file referenced with "prototype" parameter DOES NOT exist,
+ // the return value is the given path. No new files created.
+ // Otherwise the function creates another file in the same directory as
+ // the given file with the same suffix and with the basename from the
+ // basename of the given file with some random chars appended to ensure
+ // created file is unique.
+ tstring createUniqueFile(const tstring &prototype);
+
+ // Creates directory and subdirectories if don't exist.
+ // Currently supports only "standard" path like "c:\bla-bla"
+ // If 'createdDirs' parameter is not NULL, the given array is appended with
+ // all subdirectories created by this function call.
+ void createDirectory(const tstring &path, tstring_array* createdDirs=0);
+
+ // copies file from fromPath to toPath.
+ // Creates output directory if doesn't exist.
+ void copyFile(const tstring& fromPath, const tstring& toPath,
+ bool failIfExists);
+
+ // moves file from fromPath to toPath.
+ // Creates output directory if doesn't exist.
+ void moveFile(const tstring& fromPath, const tstring& toPath,
+ bool failIfExists);
+
+ // Throws exception if fails to delete specified 'path'.
+ // Exits normally if 'path' doesn't exist or it has been deleted.
+ // Attempts to strip R/O attribute if delete fails and retry delete.
+ void deleteFile(const tstring &path);
+ // Returns 'false' if fails to delete specified 'path'.
+ // Returns 'true' if 'path' doesn't exist or it has been deleted.
+ // Attempts to strip R/O attribute if delete fails and retry delete.
+ bool deleteFile(const tstring &path, const std::nothrow_t &) throw();
+
+ // Like deleteFile(), but applies to directories.
+ void deleteDirectory(const tstring &path);
+ bool deleteDirectory(const tstring &path, const std::nothrow_t &) throw();
+
+ // Deletes all files (not subdirectories) from the specified directory.
+ // Exits normally if all files in 'dirPath' have been deleted or if
+ // 'dirPath' doesn't exist.
+ // Throws exception if 'dirPath' references existing file system object
+ // which is not a directory or when the first failure of file delete
+ // occurs.
+ void deleteFilesInDirectory(const tstring &dirPath);
+ // Deletes all files (not subdirectories) from the specified directory.
+ // Returns 'true' normally if all files in 'dirPath' have been deleted or
+ // if 'dirPath' doesn't exist.
+ // Returns 'false' if 'dirPath' references existing file system object
+ // which is not a directory or if failed to delete one ore more files in
+ // 'dirPath' directory.
+ // Doesn't abort iteration over files if the given directory after the
+ // first failure to delete a file.
+ bool deleteFilesInDirectory(const tstring &dirPath,
+ const std::nothrow_t &) throw();
+ // Like deleteFilesInDirectory, but deletes subdirectories as well
+ void deleteDirectoryRecursive(const tstring &dirPath);
+ bool deleteDirectoryRecursive(const tstring &dirPath,
+ const std::nothrow_t &) throw();
+
+ class DirectoryCallback {
+ public:
+ virtual ~DirectoryCallback() {};
+
+ virtual bool onFile(const tstring& path) {
+ return true;
+ }
+ virtual bool onDirectory(const tstring& path) {
+ return true;
+ }
+ };
+
+ // Calls the given callback for every file and subdirectory of
+ // the given directory.
+ void iterateDirectory(const tstring &dirPath, DirectoryCallback& callback);
+
+ /**
+ * Replace file suffix, example replaceSuffix("file/path.txt", ".csv")
+ * @param path file path to replace suffix
+ * @param suffix new suffix for path
+ * @return return file path with new suffix
+ */
+ tstring replaceSuffix(const tstring& path, const tstring& suffix=tstring());
+
+ class DirectoryIterator: DirectoryCallback {
+ public:
+ DirectoryIterator(const tstring& root=tstring()): root(root) {
+ recurse().withFiles().withFolders();
+ }
+
+ DirectoryIterator& recurse(bool v=true) {
+ theRecurse = v;
+ return *this;
+ }
+
+ DirectoryIterator& withFiles(bool v=true) {
+ theWithFiles = v;
+ return *this;
+ }
+
+ DirectoryIterator& withFolders(bool v=true) {
+ theWithFolders = v;
+ return *this;
+ }
+
+ tstring_array findItems() {
+ tstring_array reply;
+ findItems(reply);
+ return reply;
+ }
+
+ DirectoryIterator& findItems(tstring_array& v);
+
+ private:
+ virtual bool onFile(const tstring& path);
+ virtual bool onDirectory(const tstring& path);
+
+ private:
+ bool theRecurse;
+ bool theWithFiles;
+ bool theWithFolders;
+ tstring root;
+ tstring_array items;
+ };
+
+ // Returns array of all the files/sub-folders from the given directory,
+ // empty array if basedir is not a directory. The returned
+ // array is ordered from top down (i.e. dirs are listed first followed
+ // by subfolders and files).
+ // Order of subfolders and files is undefined
+ // but usually they are sorted by names.
+ inline tstring_array listAllContents(const tstring& basedir) {
+ return DirectoryIterator(basedir).findItems();
+ }
+
+ // Helper to construct path from multiple components.
+ //
+ // Sample usage:
+ // Construct "c:\Program Files\Java" string from three components
+ //
+ // tstring path = FileUtils::mkpath() << _T("c:")
+ // << _T("Program Files")
+ // << _T("Java");
+ //
+ class mkpath {
+ public:
+ operator const tstring& () const {
+ return path;
+ }
+
+ mkpath& operator << (const tstring& p) {
+ path = combinePath(path, p);
+ return *this;
+ }
+
+ // mimic std::string
+ const tstring::value_type* c_str() const {
+ return path.c_str();
+ }
+ private:
+ tstring path;
+ };
+
+ struct Directory {
+ Directory() {
+ }
+
+ Directory(const tstring &parent,
+ const tstring &subdir) : parent(parent), subdir(subdir) {
+ }
+
+ operator tstring () const {
+ return getPath();
+ }
+
+ tstring getPath() const {
+ return combinePath(parent, subdir);
+ }
+
+ bool empty() const {
+ return (parent.empty() && subdir.empty());
+ }
+
+ tstring parent;
+ tstring subdir;
+ };
+
+ // Deletes list of files and directories in batch mode.
+ // Registered files and directories are deleted when destructor is called.
+ // Order or delete operations is following:
+ // - delete items registered with appendFile() calls;
+ // - delete items registered with appendAllFilesInDirectory() calls;
+ // - delete items registered with appendRecursiveDirectory() calls;
+ // - delete items registered with appendEmptyDirectory() calls.
+ class Deleter {
+ public:
+ Deleter() {
+ }
+
+ ~Deleter() {
+ execute();
+ }
+
+ typedef std::pair<tstring, int> Path;
+ typedef std::vector<Path> Paths;
+
+ /**
+ * Appends all records from the given deleter Deleter into this Deleter
+ * instance. On success array with records in the passed in Deleter
+ * instance is emptied.
+ */
+ Deleter& appendFrom(Deleter& other) {
+ Paths tmp(paths);
+ tmp.insert(tmp.end(), other.paths.begin(), other.paths.end());
+ Paths empty;
+ other.paths.swap(empty);
+ paths.swap(tmp);
+ return *this;
+ }
+
+ // Schedule file for deletion.
+ Deleter& appendFile(const tstring& path);
+
+ // Schedule files for deletion.
+ template <class It>
+ Deleter& appendFiles(It b, It e) {
+ for (It it = b; it != e; ++it) {
+ appendFile(*it);
+ }
+ return *this;
+ }
+
+ // Schedule files for deletion in the given directory.
+ template <class It>
+ Deleter& appendFiles(const tstring& dirname, It b, It e) {
+ for (It it = b; it != e; ++it) {
+ appendFile(FileUtils::mkpath() << dirname << *it);
+ }
+ return *this;
+ }
+
+ // Schedule empty directory for deletion with empty roots
+ // (up to Directory.parent).
+ Deleter& appendEmptyDirectory(const Directory& dir);
+
+ // Schedule empty directory for deletion without roots.
+ // This is a particular case of
+ // appendEmptyDirectory(const Directory& dir)
+ // with Directory(dirname(path), basename(path)).
+ Deleter& appendEmptyDirectory(const tstring& path);
+
+ // Schedule all file from the given directory for deletion.
+ Deleter& appendAllFilesInDirectory(const tstring& path);
+
+ // Schedule directory for recursive deletion.
+ Deleter& appendRecursiveDirectory(const tstring& path);
+
+ void cancel() {
+ paths.clear();
+ }
+
+ // Deletes scheduled files and directories. After this function
+ // is called internal list of scheduled items is emptied.
+ void execute();
+
+ private:
+ Paths paths;
+ };
+
+
+ /**
+ * Helper to write chunks of data into binary file.
+ * Creates temporary file in the same folder with destination file.
+ * All subsequent requests to save data chunks are redirected to temporary
+ * file. finalize() method closes temporary file stream and renames
+ * temporary file.
+ * If finalize() method is not called, temporary file is deleted in
+ * ~FileWriter(), destination file is not touched.
+ */
+ class FileWriter {
+ public:
+ explicit FileWriter(const tstring& path);
+
+ FileWriter& write(const void* buf, size_t bytes);
+
+ template <class Ctnr>
+ FileWriter& write(const Ctnr& buf) {
+ return write(buf.data(),
+ buf.size() * sizeof(typename Ctnr::value_type));
+ }
+
+ void finalize();
+
+ private:
+ // Not accessible by design!
+ FileWriter& write(const std::wstring& str);
+
+ private:
+ tstring tmpFile;
+ Deleter cleaner;
+ std::ofstream tmp;
+ tstring dstPath;
+ };
+} // FileUtils
+
+#endif // FILEUTILS_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/IconSwap.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <stdio.h>
+#include <windows.h>
+#include <stdlib.h>
+#include <string>
+#include <malloc.h>
+
+using namespace std;
+
+// http://msdn.microsoft.com/en-us/library/ms997538.aspx
+
+typedef struct _ICONDIRENTRY {
+ BYTE bWidth;
+ BYTE bHeight;
+ BYTE bColorCount;
+ BYTE bReserved;
+ WORD wPlanes;
+ WORD wBitCount;
+ DWORD dwBytesInRes;
+ DWORD dwImageOffset;
+} ICONDIRENTRY, * LPICONDIRENTRY;
+
+typedef struct _ICONDIR {
+ WORD idReserved;
+ WORD idType;
+ WORD idCount;
+ ICONDIRENTRY idEntries[1];
+} ICONDIR, * LPICONDIR;
+
+// #pragmas are used here to insure that the structure's
+// packing in memory matches the packing of the EXE or DLL.
+#pragma pack(push)
+#pragma pack(2)
+
+typedef struct _GRPICONDIRENTRY {
+ BYTE bWidth;
+ BYTE bHeight;
+ BYTE bColorCount;
+ BYTE bReserved;
+ WORD wPlanes;
+ WORD wBitCount;
+ DWORD dwBytesInRes;
+ WORD nID;
+} GRPICONDIRENTRY, * LPGRPICONDIRENTRY;
+#pragma pack(pop)
+
+#pragma pack(push)
+#pragma pack(2)
+
+typedef struct _GRPICONDIR {
+ WORD idReserved;
+ WORD idType;
+ WORD idCount;
+ GRPICONDIRENTRY idEntries[1];
+} GRPICONDIR, * LPGRPICONDIR;
+#pragma pack(pop)
+
+void PrintError() {
+ LPVOID message = NULL;
+ DWORD error = GetLastError();
+
+ if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER
+ | FORMAT_MESSAGE_FROM_SYSTEM
+ | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, error,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPTSTR) & message, 0, NULL) != 0) {
+ printf("%S", (LPTSTR) message);
+ LocalFree(message);
+ }
+}
+
+// Note: We do not check here that iconTarget is valid icon.
+// Java code will already do this for us.
+
+bool ChangeIcon(wstring iconTarget, wstring launcher) {
+ WORD language = MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT);
+
+ HANDLE icon = CreateFile(iconTarget.c_str(), GENERIC_READ, 0, NULL,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (icon == INVALID_HANDLE_VALUE) {
+ PrintError();
+ return false;
+ }
+
+ // Reading .ICO file
+ WORD idReserved, idType, idCount;
+
+ DWORD dwBytesRead;
+ ReadFile(icon, &idReserved, sizeof (WORD), &dwBytesRead, NULL);
+ ReadFile(icon, &idType, sizeof (WORD), &dwBytesRead, NULL);
+ ReadFile(icon, &idCount, sizeof (WORD), &dwBytesRead, NULL);
+
+ LPICONDIR lpid = (LPICONDIR) malloc(
+ sizeof (ICONDIR) + (sizeof (ICONDIRENTRY) * (idCount - 1)));
+ if (lpid == NULL) {
+ CloseHandle(icon);
+ printf("Error: Failed to allocate memory\n");
+ return false;
+ }
+
+ lpid->idReserved = idReserved;
+ lpid->idType = idType;
+ lpid->idCount = idCount;
+
+ ReadFile(icon, &lpid->idEntries[0], sizeof (ICONDIRENTRY) * lpid->idCount,
+ &dwBytesRead, NULL);
+
+ LPGRPICONDIR lpgid = (LPGRPICONDIR) malloc(
+ sizeof (GRPICONDIR) + (sizeof (GRPICONDIRENTRY) * (idCount - 1)));
+ if (lpid == NULL) {
+ CloseHandle(icon);
+ free(lpid);
+ printf("Error: Failed to allocate memory\n");
+ return false;
+ }
+
+ lpgid->idReserved = idReserved;
+ lpgid->idType = idType;
+ lpgid->idCount = idCount;
+
+ for (int i = 0; i < lpgid->idCount; i++) {
+ lpgid->idEntries[i].bWidth = lpid->idEntries[i].bWidth;
+ lpgid->idEntries[i].bHeight = lpid->idEntries[i].bHeight;
+ lpgid->idEntries[i].bColorCount = lpid->idEntries[i].bColorCount;
+ lpgid->idEntries[i].bReserved = lpid->idEntries[i].bReserved;
+ lpgid->idEntries[i].wPlanes = lpid->idEntries[i].wPlanes;
+ lpgid->idEntries[i].wBitCount = lpid->idEntries[i].wBitCount;
+ lpgid->idEntries[i].dwBytesInRes = lpid->idEntries[i].dwBytesInRes;
+ lpgid->idEntries[i].nID = i + 1;
+ }
+
+ // Store images in .EXE
+ HANDLE update = BeginUpdateResource(launcher.c_str(), FALSE);
+ if (update == NULL) {
+ free(lpid);
+ free(lpgid);
+ CloseHandle(icon);
+ PrintError();
+ return false;
+ }
+
+ for (int i = 0; i < lpid->idCount; i++) {
+ LPBYTE lpBuffer = (LPBYTE) malloc(lpid->idEntries[i].dwBytesInRes);
+ SetFilePointer(icon, lpid->idEntries[i].dwImageOffset,
+ NULL, FILE_BEGIN);
+ ReadFile(icon, lpBuffer, lpid->idEntries[i].dwBytesInRes,
+ &dwBytesRead, NULL);
+ if (!UpdateResource(update, RT_ICON,
+ MAKEINTRESOURCE(lpgid->idEntries[i].nID),
+ language, &lpBuffer[0], lpid->idEntries[i].dwBytesInRes)) {
+ free(lpBuffer);
+ free(lpid);
+ free(lpgid);
+ CloseHandle(icon);
+ PrintError();
+ return false;
+ }
+ free(lpBuffer);
+ }
+
+ free(lpid);
+ CloseHandle(icon);
+
+ if (!UpdateResource(update, RT_GROUP_ICON, MAKEINTRESOURCE(1),
+ language, &lpgid[0], (sizeof (WORD) * 3)
+ + (sizeof (GRPICONDIRENTRY) * lpgid->idCount))) {
+ free(lpgid);
+ PrintError();
+ return false;
+ }
+
+ free(lpgid);
+
+ if (EndUpdateResource(update, FALSE) == FALSE) {
+ PrintError();
+ return false;
+ }
+
+ return true;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/IconSwap.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ICONSWAP_H
+#define ICONSWAP_H
+
+#include <string>
+
+using namespace std;
+
+bool ChangeIcon(wstring iconTarget, wstring launcher);
+
+#endif // ICONSWAP_H
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/Log.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "Log.h"
+#include "SysInfo.h"
+#include "FileUtils.h"
+
+
+namespace {
+ //
+ // IMPORTANT: Static objects with non-trivial constructors are NOT allowed
+ // in logger module. Allocate buffers only and do lazy initialization of
+ // globals in Logger::getDefault().
+ //
+ // Logging subsystem is used almost in every module, and logging API can be
+ // called from constructors of static objects in various modules. As
+ // ordering of static objects initialization between modules is undefined,
+ // this means some module may call logging api before logging static
+ // variables are initialized if any. This will result in AV. To avoid such
+ // use cases keep logging module free from static variables that require
+ // initialization with functions called by CRT.
+ //
+
+ // by default log everything
+ const Logger::LogLevel defaultLogLevel = Logger::LOG_TRACE;
+
+ char defaultLogAppenderMemory[sizeof(StderrLogAppender)] = {};
+
+ char defaultLoggerMemory[sizeof(Logger)] = {};
+
+ NopLogAppender nopLogApender;
+
+ LPCTSTR getLogLevelStr(Logger::LogLevel level) {
+ switch (level) {
+ case Logger::LOG_TRACE:
+ return _T("TRACE");
+ case Logger::LOG_INFO:
+ return _T("INFO");
+ case Logger::LOG_WARNING:
+ return _T("WARNING");
+ case Logger::LOG_ERROR:
+ return _T("ERROR");
+ }
+ return _T("UNKNOWN");
+ }
+
+ tstring retrieveModuleName() {
+ try {
+ return FileUtils::basename(SysInfo::getCurrentModulePath());
+ } catch (const std::exception&) {
+ return _T("Unknown");
+ }
+ }
+
+ TCHAR moduleName[MAX_PATH] = { 'U', 'n', 'k', 'o', 'w', 'n', TCHAR(0) };
+
+ const LPCTSTR format = _T("[%04u/%02u/%02u %02u:%02u:%02u.%03u, %s (PID: %u, TID: %u), %s:%u (%s)]\n\t%s: %s\n");
+
+ enum State { NotInitialized, Initializing, Initialized };
+ State state = NotInitialized;
+}
+
+
+LogEvent::LogEvent() {
+ memset(this, 0, sizeof(*this));
+ moduleName = tstring();
+ logLevel = tstring();
+ fileName = tstring();
+ funcName = tstring();
+ message = tstring();
+}
+
+
+StderrLogAppender::StderrLogAppender() {
+}
+
+
+/*static*/
+Logger& Logger::defaultLogger() {
+ Logger* reply = reinterpret_cast<Logger*>(defaultLoggerMemory);
+
+ if (!reply->appender) {
+ // Memory leak by design. Not an issue at all as this is global
+ // object. OS will do resources clean up anyways when application
+ // terminates and the default log appender should live as long as
+ // application lives.
+ reply->appender = new (defaultLogAppenderMemory) StderrLogAppender();
+ }
+
+ if (Initializing == state) {
+ // Recursive call to Logger::defaultLogger.
+ moduleName[0] = TCHAR(0);
+ } else if (NotInitialized == state) {
+ state = Initializing;
+
+ tstring mname = retrieveModuleName();
+ mname.resize(_countof(moduleName) - 1);
+ std::memcpy(moduleName, mname.c_str(), mname.size());
+ moduleName[mname.size()] = TCHAR(0);
+
+ // if JPACKAGE_DEBUG environment variable is NOT set to "true" disable
+ // logging.
+ if (SysInfo::getEnvVariable(std::nothrow,
+ L"JPACKAGE_DEBUG") != L"true") {
+ reply->appender = &nopLogApender;
+ }
+
+ state = Initialized;
+ }
+
+ return *reply;
+}
+
+Logger::Logger(LogAppender& appender, LogLevel logLevel)
+ : level(logLevel), appender(&appender) {
+}
+
+void Logger::setLogLevel(LogLevel logLevel) {
+ level = logLevel;
+}
+
+Logger::~Logger() {
+}
+
+
+bool Logger::isLoggable(LogLevel logLevel) const {
+ return logLevel >= level;
+}
+
+void Logger::log(LogLevel logLevel, LPCTSTR fileName, int lineNum,
+ LPCTSTR funcName, const tstring& message) const {
+ LogEvent logEvent;
+
+ // [YYYY/MM/DD HH:MM:SS.ms, <module> (PID: processID, TID: threadID),
+ // fileName:lineNum (funcName)] <tab>LEVEL: message
+ GetLocalTime(&logEvent.ts);
+
+ logEvent.pid = GetCurrentProcessId();
+ logEvent.tid = GetCurrentThreadId();
+ logEvent.moduleName = moduleName;
+ logEvent.fileName = FileUtils::basename(fileName);
+ logEvent.funcName = funcName;
+ logEvent.logLevel = getLogLevelStr(logLevel);
+ logEvent.lineNum = lineNum;
+ logEvent.message = message;
+
+ appender->append(logEvent);
+}
+
+
+void StderrLogAppender::append(const LogEvent& v)
+{
+ const tstring out = tstrings::unsafe_format(format,
+ unsigned(v.ts.wYear), unsigned(v.ts.wMonth), unsigned(v.ts.wDay),
+ unsigned(v.ts.wHour), unsigned(v.ts.wMinute), unsigned(v.ts.wSecond),
+ unsigned(v.ts.wMilliseconds),
+ v.moduleName.c_str(), v.pid, v.tid,
+ v.fileName.c_str(), v.lineNum, v.funcName.c_str(),
+ v.logLevel.c_str(),
+ v.message.c_str());
+
+ std::cerr << tstrings::toUtf8(out);
+}
+
+
+// Logger::ScopeTracer
+Logger::ScopeTracer::ScopeTracer(Logger &logger, LogLevel logLevel,
+ LPCTSTR fileName, int lineNum, LPCTSTR funcName,
+ const tstring& scopeName) : log(logger), level(logLevel),
+ file(fileName), line(lineNum),
+ func(funcName), scope(scopeName), needLog(logger.isLoggable(logLevel)) {
+ if (needLog) {
+ log.log(level, file.c_str(), line, func.c_str(),
+ tstrings::any() << "Entering " << scope);
+ }
+}
+
+Logger::ScopeTracer::~ScopeTracer() {
+ if (needLog) {
+ // we don't know what line is end of scope at, so specify line 0
+ // and add note about line when the scope begins
+ log.log(level, file.c_str(), 0, func.c_str(),
+ tstrings::any() << "Exiting " << scope << " (entered at "
+ << FileUtils::basename(file) << ":" << line << ")");
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/Log.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef __LOG_H_INCLUDED_
+#define __LOG_H_INCLUDED_
+
+#include <windows.h>
+#include "tstrings.h"
+
+
+/* Default logger (Logger::defaultLogger()) writes log messages to
+ * the default log file.
+ * Common scenario:
+ * - main() function configures default logger:
+ * FileLogAppender appender(_T("my_log_filename.log"));
+ * Logger::defaultLogger().setAppender(appender);
+ * Logger::defaultLogger().setLogLevel(LOG_INFO);
+ * If the default file name and log level are not set,
+ * _T("jusched.log")/LOG_TRACE are used.
+ *
+ * Logger fileName specifies only file name,
+ * full path for the log file depends on the platform
+ * (usually value of the TMP env. var)
+ */
+
+struct LogEvent {
+ SYSTEMTIME ts;
+ long tid;
+ long pid;
+ tstring moduleName;
+ tstring logLevel;
+ tstring fileName;
+ int lineNum;
+ tstring funcName;
+ tstring message;
+
+ LogEvent();
+};
+
+
+class LogAppender {
+public:
+ virtual ~LogAppender() {
+ }
+ virtual void append(const LogEvent& v) = 0;
+};
+
+
+class NopLogAppender: public LogAppender {
+public:
+ virtual void append(const LogEvent& v) {};
+};
+
+
+class TeeLogAppender: public LogAppender {
+public:
+ TeeLogAppender(LogAppender* first, LogAppender* second):
+ first(first), second(second) {
+ }
+ virtual ~TeeLogAppender() {
+ }
+ virtual void append(const LogEvent& v) {
+ if (first) {
+ first->append(v);
+ }
+ if (second) {
+ second->append(v);
+ }
+ }
+private:
+ LogAppender* first;
+ LogAppender* second;
+};
+
+
+/**
+ * Writes log events to stderr.
+ */
+class StderrLogAppender: public LogAppender {
+public:
+ explicit StderrLogAppender();
+
+ virtual void append(const LogEvent& v);
+};
+
+
+class Logger {
+public:
+ enum LogLevel {
+ LOG_TRACE,
+ LOG_INFO,
+ LOG_WARNING,
+ LOG_ERROR
+ };
+
+ static Logger& defaultLogger();
+
+ explicit Logger(LogAppender& appender, LogLevel logLevel = LOG_TRACE);
+ ~Logger();
+
+ LogAppender& setAppender(LogAppender& v) {
+ LogAppender& oldAppender = *appender;
+ appender = &v;
+ return oldAppender;
+ }
+
+ LogAppender& getAppender() const {
+ return *appender;
+ }
+
+ void setLogLevel(LogLevel logLevel);
+
+ bool isLoggable(LogLevel logLevel) const ;
+ void log(LogLevel logLevel, LPCTSTR fileName, int lineNum,
+ LPCTSTR funcName, const tstring& message) const;
+ void log(LogLevel logLevel, LPCTSTR fileName, int lineNum,
+ LPCTSTR funcName, const tstrings::any& message) const {
+ return log(logLevel, fileName, lineNum, funcName, message.tstr());
+ }
+ void log(LogLevel logLevel, LPCTSTR fileName, int lineNum,
+ LPCTSTR funcName, tstring::const_pointer message) const {
+ return log(logLevel, fileName, lineNum, funcName, tstring(message));
+ }
+
+ // internal class for scope tracing
+ class ScopeTracer {
+ public:
+ ScopeTracer(Logger &logger, LogLevel logLevel, LPCTSTR fileName,
+ int lineNum, LPCTSTR funcName, const tstring& scopeName);
+ ~ScopeTracer();
+
+ private:
+ const Logger &log;
+ const LogLevel level;
+ const bool needLog;
+ const tstring file;
+ const int line;
+ const tstring func;
+ const tstring scope;
+ };
+
+private:
+ LogLevel level;
+ LogAppender* appender;
+};
+
+
+// base logging macro
+#define LOGGER_LOG(logger, logLevel, message) \
+ do { \
+ if (logger.isLoggable(logLevel)) { \
+ logger.log(logLevel, _T(__FILE__), __LINE__, _T(__FUNCTION__), message); \
+ } \
+ } while(false)
+
+
+// custom logger macros
+#define LOGGER_TRACE(logger, message) LOGGER_LOG(logger, Logger::LOG_TRACE, message)
+#define LOGGER_INFO(logger, message) LOGGER_LOG(logger, Logger::LOG_INFO, message)
+#define LOGGER_WARNING(logger, message) LOGGER_LOG(logger, Logger::LOG_WARNING, message)
+#define LOGGER_ERROR(logger, message) LOGGER_LOG(logger, Logger::LOG_ERROR, message)
+// scope tracing macros
+#define LOGGER_TRACE_SCOPE(logger, scopeName) \
+ Logger::ScopeTracer tracer__COUNTER__(logger, Logger::LOG_TRACE, _T(__FILE__), __LINE__, _T(__FUNCTION__), scopeName)
+#define LOGGER_TRACE_FUNCTION(logger) LOGGER_TRACE_SCOPE(logger, _T(__FUNCTION__))
+
+
+// default logger macros
+#define LOG_TRACE(message) LOGGER_LOG(Logger::defaultLogger(), Logger::LOG_TRACE, message)
+#define LOG_INFO(message) LOGGER_LOG(Logger::defaultLogger(), Logger::LOG_INFO, message)
+#define LOG_WARNING(message) LOGGER_LOG(Logger::defaultLogger(), Logger::LOG_WARNING, message)
+#define LOG_ERROR(message) LOGGER_LOG(Logger::defaultLogger(), Logger::LOG_ERROR, message)
+// scope tracing macros
+// logs (_T("Entering ") + scopeName) at the beging, (_T("Exiting ") + scopeName) at the end of scope
+#define LOG_TRACE_SCOPE(scopeName) LOGGER_TRACE_SCOPE(Logger::defaultLogger(), scopeName)
+// logs (_T("Entering ") + functionName) at the beging, (_T("Exiting ") + __FUNCTION__) at the end of scope
+#define LOG_TRACE_FUNCTION() LOGGER_TRACE_FUNCTION(Logger::defaultLogger())
+
+
+#endif // __LOG_H_INCLUDED_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/ResourceEditor.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <algorithm>
+#include <fstream>
+#include "ResourceEditor.h"
+#include "WinErrorHandling.h"
+#include "Log.h"
+
+
+ResourceEditor::FileLock::FileLock(const std::wstring& binaryPath) {
+ h = BeginUpdateResource(binaryPath.c_str(), FALSE);
+ if (NULL == h) {
+ JP_THROW(SysError(tstrings::any() << "BeginUpdateResource("
+ << binaryPath << ") failed", BeginUpdateResource));
+ }
+
+ discard(false);
+}
+
+
+ResourceEditor::FileLock::~FileLock() {
+ if (!EndUpdateResource(h, theDiscard)) {
+ JP_NO_THROW(JP_THROW(SysError(tstrings::any()
+ << "EndUpdateResource(" << h << ") failed.", EndUpdateResource)));
+ }
+}
+
+
+ResourceEditor::ResourceEditor() {
+ language(MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL)).type(unsigned(0)).id(unsigned(0));
+}
+
+
+ResourceEditor& ResourceEditor::type(unsigned v) {
+ return type(MAKEINTRESOURCE(v));
+}
+
+
+ResourceEditor& ResourceEditor::type(LPCWSTR v) {
+ if (IS_INTRESOURCE(v)) {
+ std::wostringstream printer;
+ printer << L"#" << reinterpret_cast<size_t>(v);
+ theType = printer.str();
+ theTypePtr = MAKEINTRESOURCE(static_cast<DWORD>(reinterpret_cast<DWORD_PTR>(v)));
+ } else {
+ theType = v;
+ theTypePtr = theType.c_str();
+ }
+ return *this;
+}
+
+
+ResourceEditor& ResourceEditor::id(unsigned v) {
+ return id(MAKEINTRESOURCE(v));
+}
+
+
+ResourceEditor& ResourceEditor::id(LPCWSTR v) {
+ if (IS_INTRESOURCE(v)) {
+ std::wostringstream printer;
+ printer << L"#" << reinterpret_cast<size_t>(v);
+ theId = printer.str();
+ } else {
+ theId = v;
+ theIdPtr = theId.c_str();
+ }
+ return *this;
+}
+
+
+ResourceEditor& ResourceEditor::apply(const FileLock& dstBinary,
+ std::istream& srcStream, std::streamsize size) {
+
+ typedef std::vector<BYTE> ByteArray;
+ ByteArray buf;
+ if (size <= 0) {
+ // Read the entire stream.
+ buf = ByteArray((std::istreambuf_iterator<char>(srcStream)),
+ std::istreambuf_iterator<char>());
+ } else {
+ buf.resize(size_t(size));
+ srcStream.read(reinterpret_cast<char*>(buf.data()), size);
+ }
+
+ auto reply = UpdateResource(dstBinary.get(), theTypePtr, theIdPtr, lang,
+ buf.data(), static_cast<DWORD>(buf.size()));
+ if (reply == FALSE) {
+ JP_THROW(SysError("UpdateResource() failed", UpdateResource));
+ }
+
+ return *this;
+}
+
+
+ResourceEditor& ResourceEditor::apply(const FileLock& dstBinary,
+ const std::wstring& srcFile) {
+ std::ifstream input(srcFile, std::ios_base::binary);
+ input.exceptions(std::ios::failbit | std::ios::badbit);
+ return apply(dstBinary, input);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/ResourceEditor.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef RESOURCEEDITOR_H
+#define RESOURCEEDITOR_H
+
+#include <windows.h>
+#include <vector>
+#include <string>
+
+
+class ResourceEditor {
+public:
+ class FileLock {
+ public:
+ FileLock(const std::wstring& binaryPath);
+ ~FileLock();
+
+ HANDLE get() const {
+ return h;
+ }
+
+ void discard(bool v = true) {
+ theDiscard = v;
+ }
+
+ private:
+ FileLock(const FileLock&);
+ FileLock& operator=(const FileLock&);
+ private:
+ HANDLE h;
+ bool theDiscard;
+ };
+
+public:
+ ResourceEditor();
+
+ /**
+ * Set the language identifier of the resource to be updated.
+ */
+ ResourceEditor& language(unsigned v) {
+ lang = v;
+ return *this;
+ }
+
+ /**
+ * Set the resource type to be updated.
+ */
+ ResourceEditor& type(unsigned v);
+
+ /**
+ * Set the resource type to be updated.
+ */
+ ResourceEditor& type(LPCWSTR v);
+
+ /**
+ * Set resource ID.
+ */
+ ResourceEditor& id(unsigned v);
+
+ /**
+ * Set resource ID.
+ */
+ ResourceEditor& id(LPCWSTR v);
+
+ /**
+ * Relaces resource configured in the given binary with the given data stream.
+ */
+ ResourceEditor& apply(const FileLock& dstBinary, std::istream& srcStream, std::streamsize size=0);
+
+ /**
+ * Relaces resource configured in the given binary with contents of
+ * the given binary file.
+ */
+ ResourceEditor& apply(const FileLock& dstBinary, const std::wstring& srcFile);
+
+private:
+ unsigned lang;
+ std::wstring theId;
+ LPCWSTR theIdPtr;
+ std::wstring theType;
+ LPCWSTR theTypePtr;
+};
+
+#endif // #ifndef RESOURCEEDITOR_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/SourceCodePos.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+
+#ifndef SourceCodePos_h
+#define SourceCodePos_h
+
+
+//
+// Position in source code.
+//
+
+struct SourceCodePos
+{
+ SourceCodePos(const char* fl, const char* fnc, int l):
+ file(fl), func(fnc), lno(l)
+ {
+ }
+
+ const char* file;
+ const char* func;
+ int lno;
+};
+
+
+// Initializes SourceCodePos instance with the
+// information from the point of calling.
+#define JP_SOURCE_CODE_POS SourceCodePos(__FILE__, __FUNCTION__, __LINE__)
+
+
+#endif // #ifndef SourceCodePos_h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/SysInfo.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+
+#ifndef SYSINFO_H
+#define SYSINFO_H
+
+#include "tstrings.h"
+
+
+//
+// This namespace provides information about environment in which
+// the current application runs.
+// It is for general purpose use.
+// Functions in this namespaces are just queries about the environment.
+// Functions that change the existing environment like file or directory
+// creation should not be added to this namespace.
+//
+namespace SysInfo {
+ /**
+ * Returns temp dir (for the current user).
+ */
+ tstring getTempDir();
+
+ /**
+ * Returns absolute path to the process executable.
+ */
+ tstring getProcessModulePath();
+
+ /**
+ * Returns absolute path to the current executable module.
+ */
+ tstring getCurrentModulePath();
+
+ enum CommandArgProgramNameMode {
+ IncludeProgramName,
+ ExcludeProgramName
+ };
+ /**
+ * Retrieves the command-line arguments for the current process.
+ * With IncludeProgramName option returns result similar to argv/argc.
+ * With ExcludeProgramName option program name
+ * (the 1st element of command line)
+ * is excluded.
+ */
+ tstring_array getCommandArgs(
+ CommandArgProgramNameMode progNameMode = ExcludeProgramName);
+
+ /**
+ * Returns value of environment variable with the given name.
+ * Throws exception if variable is not set or any other error occurred
+ * reading the value.
+ */
+ tstring getEnvVariable(const tstring& name);
+
+ /**
+ * Returns value of environment variable with the given name.
+ * Returns value of 'defValue' parameter if variable is not set or any
+ * other error occurred reading the value.
+ */
+ tstring getEnvVariable(const std::nothrow_t&, const tstring& name,
+ const tstring& defValue=tstring());
+
+ /**
+ * Returns 'true' if environment variable with the given name is set.
+ */
+ bool isEnvVariableSet(const tstring& name);
+}
+
+#endif // SYSINFO_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/UniqueHandle.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef UNIQUEHANDLE_H
+#define UNIQUEHANDLE_H
+
+#include <windows.h>
+#include <memory>
+
+
+struct WndHandleDeleter {
+ typedef HANDLE pointer;
+
+ void operator()(HANDLE h) {
+ ::CloseHandle(h);
+ }
+};
+
+typedef std::unique_ptr<HANDLE, WndHandleDeleter> UniqueHandle;
+
+#endif // #ifndef UNIQUEHANDLE_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/Utils.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "Windows.h"
+#include "Utils.h"
+
+#define BUFFER_SIZE 4096
+
+wstring GetStringFromJString(JNIEnv *pEnv, jstring jstr) {
+ const jchar *pJChars = pEnv->GetStringChars(jstr, NULL);
+ if (pJChars == NULL) {
+ return wstring(L"");
+ }
+
+ wstring wstr(pJChars);
+
+ pEnv->ReleaseStringChars(jstr, pJChars);
+
+ return wstr;
+}
+
+jstring GetJStringFromString(JNIEnv *pEnv,
+ const jchar *unicodeChars, jsize len) {
+ return pEnv->NewString(unicodeChars, len);
+}
+
+wstring GetLongPath(wstring path) {
+ wstring result(L"");
+
+ size_t len = path.length();
+ if (len > 1) {
+ if (path.at(len - 1) == '\\') {
+ path.erase(len - 1);
+ }
+ }
+
+ TCHAR *pBuffer = new TCHAR[BUFFER_SIZE];
+ if (pBuffer != NULL) {
+ DWORD dwResult = GetLongPathName(path.c_str(), pBuffer, BUFFER_SIZE);
+ if (dwResult > 0 && dwResult < BUFFER_SIZE) {
+ result = wstring(pBuffer);
+ } else {
+ delete [] pBuffer;
+ pBuffer = new TCHAR[dwResult];
+ if (pBuffer != NULL) {
+ DWORD dwResult2 =
+ GetLongPathName(path.c_str(), pBuffer, dwResult);
+ if (dwResult2 == (dwResult - 1)) {
+ result = wstring(pBuffer);
+ }
+ }
+ }
+
+ if (pBuffer != NULL) {
+ delete [] pBuffer;
+ }
+ }
+
+ return result;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/Utils.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef UTILS_H
+#define UTILS_H
+
+#include <string>
+#include "jni.h"
+
+using namespace std;
+
+wstring GetStringFromJString(JNIEnv *pEnv, jstring jstr);
+jstring GetJStringFromString(JNIEnv *pEnv, const jchar *unicodeChars,
+ jsize len);
+
+wstring GetLongPath(wstring path);
+
+#endif // UTILS_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/VersionInfoSwap.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,285 @@
+/*
+ * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "VersionInfoSwap.h"
+
+#include <stdio.h>
+#include <tchar.h>
+
+#include <windows.h>
+#include <stdio.h>
+#include <Strsafe.h>
+#include <fstream>
+#include <locale>
+#include <codecvt>
+
+using namespace std;
+
+/*
+ * [Property file] contains key/value pairs
+ * The swap tool uses these pairs to create new version resource
+ *
+ * See MSDN docs for VS_VERSIONINFO structure that
+ * depicts organization of data in this version resource
+ * https://msdn.microsoft.com/en-us/library/ms647001(v=vs.85).aspx
+ *
+ * The swap tool makes changes in [Executable file]
+ * The tool assumes that the executable file has no version resource
+ * and it adds new resource in the executable file.
+ * If the executable file has an existing version resource, then
+ * the existing version resource will be replaced with new one.
+ */
+
+VersionInfoSwap::VersionInfoSwap(wstring executableProperties,
+ wstring launcher) {
+ m_executableProperties = executableProperties;
+ m_launcher = launcher;
+}
+
+bool VersionInfoSwap::PatchExecutable() {
+ bool b = LoadFromPropertyFile();
+ if (!b) {
+ return false;
+ }
+
+ ByteBuffer buf;
+ b = CreateNewResource(&buf);
+ if (!b) {
+ return false;
+ }
+
+ b = this->UpdateResource(buf.getPtr(), static_cast<DWORD> (buf.getPos()));
+ if (!b) {
+ return false;
+ }
+
+ return true;
+}
+
+bool VersionInfoSwap::LoadFromPropertyFile() {
+ wifstream stream(m_executableProperties.c_str());
+
+ const locale empty_locale = locale::empty();
+ const locale utf8_locale =
+ locale(empty_locale, new codecvt_utf8<wchar_t>());
+ stream.imbue(utf8_locale);
+
+ if (stream.is_open() == true) {
+ int lineNumber = 1;
+ while (stream.eof() == false) {
+ wstring line;
+ getline(stream, line);
+
+ // # at the first character will comment out the line.
+ if (line.empty() == false && line[0] != '#') {
+ wstring::size_type pos = line.find('=');
+ if (pos != wstring::npos) {
+ wstring name = line.substr(0, pos);
+ wstring value = line.substr(pos + 1);
+ m_props[name] = value;
+ }
+ }
+ lineNumber++;
+ }
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * Creates new version resource
+ *
+ * MSND docs for VS_VERSION_INFO structure
+ * https://msdn.microsoft.com/en-us/library/ms647001(v=vs.85).aspx
+ */
+bool VersionInfoSwap::CreateNewResource(ByteBuffer *buf) {
+ size_t versionInfoStart = buf->getPos();
+ buf->AppendWORD(0);
+ buf->AppendWORD(sizeof VS_FIXEDFILEINFO);
+ buf->AppendWORD(0);
+ buf->AppendString(TEXT("VS_VERSION_INFO"));
+ buf->Align(4);
+
+ VS_FIXEDFILEINFO fxi;
+ if (!FillFixedFileInfo(&fxi)) {
+ return false;
+ }
+ buf->AppendBytes((BYTE*) & fxi, sizeof (VS_FIXEDFILEINFO));
+ buf->Align(4);
+
+ // String File Info
+ size_t stringFileInfoStart = buf->getPos();
+ buf->AppendWORD(0);
+ buf->AppendWORD(0);
+ buf->AppendWORD(1);
+ buf->AppendString(TEXT("StringFileInfo"));
+ buf->Align(4);
+
+ // String Table
+ size_t stringTableStart = buf->getPos();
+ buf->AppendWORD(0);
+ buf->AppendWORD(0);
+ buf->AppendWORD(1);
+
+ // "040904B0" = LANG_ENGLISH/SUBLANG_ENGLISH_US, Unicode CP
+ buf->AppendString(TEXT("040904B0"));
+ buf->Align(4);
+
+ // Strings
+ vector<wstring> keys;
+ for (map<wstring, wstring>::const_iterator it =
+ m_props.begin(); it != m_props.end(); ++it) {
+ keys.push_back(it->first);
+ }
+
+ for (size_t index = 0; index < keys.size(); index++) {
+ wstring name = keys[index];
+ wstring value = m_props[name];
+
+ size_t stringStart = buf->getPos();
+ buf->AppendWORD(0);
+ buf->AppendWORD(static_cast<WORD> (value.length()));
+ buf->AppendWORD(1);
+ buf->AppendString(name);
+ buf->Align(4);
+ buf->AppendString(value);
+ buf->ReplaceWORD(stringStart,
+ static_cast<WORD> (buf->getPos() - stringStart));
+ buf->Align(4);
+ }
+
+ buf->ReplaceWORD(stringTableStart,
+ static_cast<WORD> (buf->getPos() - stringTableStart));
+ buf->ReplaceWORD(stringFileInfoStart,
+ static_cast<WORD> (buf->getPos() - stringFileInfoStart));
+
+ // VarFileInfo
+ size_t varFileInfoStart = buf->getPos();
+ buf->AppendWORD(1);
+ buf->AppendWORD(0);
+ buf->AppendWORD(1);
+ buf->AppendString(TEXT("VarFileInfo"));
+ buf->Align(4);
+
+ buf->AppendWORD(0x24);
+ buf->AppendWORD(0x04);
+ buf->AppendWORD(0x00);
+ buf->AppendString(TEXT("Translation"));
+ buf->Align(4);
+ // "000004B0" = LANG_NEUTRAL/SUBLANG_ENGLISH_US, Unicode CP
+ buf->AppendWORD(0x0000);
+ buf->AppendWORD(0x04B0);
+
+ buf->ReplaceWORD(varFileInfoStart,
+ static_cast<WORD> (buf->getPos() - varFileInfoStart));
+ buf->ReplaceWORD(versionInfoStart,
+ static_cast<WORD> (buf->getPos() - versionInfoStart));
+
+ return true;
+}
+
+bool VersionInfoSwap::FillFixedFileInfo(VS_FIXEDFILEINFO *fxi) {
+ wstring fileVersion;
+ wstring productVersion;
+ int ret;
+
+ fileVersion = m_props[TEXT("FileVersion")];
+ productVersion = m_props[TEXT("ProductVersion")];
+
+ unsigned fv_1 = 0, fv_2 = 0, fv_3 = 0, fv_4 = 0;
+ unsigned pv_1 = 0, pv_2 = 0, pv_3 = 0, pv_4 = 0;
+
+ ret = _stscanf_s(fileVersion.c_str(),
+ TEXT("%d.%d.%d.%d"), &fv_1, &fv_2, &fv_3, &fv_4);
+ if (ret <= 0 || ret > 4) {
+ return false;
+ }
+
+ ret = _stscanf_s(productVersion.c_str(),
+ TEXT("%d.%d.%d.%d"), &pv_1, &pv_2, &pv_3, &pv_4);
+ if (ret <= 0 || ret > 4) {
+ return false;
+ }
+
+ fxi->dwSignature = 0xFEEF04BD;
+ fxi->dwStrucVersion = 0x00010000;
+
+ fxi->dwFileVersionMS = MAKELONG(fv_2, fv_1);
+ fxi->dwFileVersionLS = MAKELONG(fv_4, fv_3);
+ fxi->dwProductVersionMS = MAKELONG(pv_2, pv_1);
+ fxi->dwProductVersionLS = MAKELONG(pv_4, pv_3);
+
+ fxi->dwFileFlagsMask = 0;
+ fxi->dwFileFlags = 0;
+ fxi->dwFileOS = VOS_NT_WINDOWS32;
+
+ wstring exeExt =
+ m_launcher.substr(m_launcher.find_last_of(TEXT(".")));
+ if (exeExt == TEXT(".exe")) {
+ fxi->dwFileType = VFT_APP;
+ } else if (exeExt == TEXT(".dll")) {
+ fxi->dwFileType = VFT_DLL;
+ } else {
+ fxi->dwFileType = VFT_UNKNOWN;
+ }
+ fxi->dwFileSubtype = 0;
+
+ fxi->dwFileDateLS = 0;
+ fxi->dwFileDateMS = 0;
+
+ return true;
+}
+
+/*
+ * Adds new resource in the executable
+ */
+bool VersionInfoSwap::UpdateResource(LPVOID lpResLock, DWORD size) {
+
+ HANDLE hUpdateRes;
+ BOOL r;
+
+ hUpdateRes = ::BeginUpdateResource(m_launcher.c_str(), FALSE);
+ if (hUpdateRes == NULL) {
+ return false;
+ }
+
+ r = ::UpdateResource(hUpdateRes,
+ RT_VERSION,
+ MAKEINTRESOURCE(VS_VERSION_INFO),
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
+ lpResLock,
+ size);
+
+ if (!r) {
+ return false;
+ }
+
+ if (!::EndUpdateResource(hUpdateRes, FALSE)) {
+ return false;
+ }
+
+ return true;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/VersionInfoSwap.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef VERSIONINFOSWAP_H
+#define VERSIONINFOSWAP_H
+
+#include "ByteBuffer.h"
+#include <map>
+
+using namespace std;
+
+class VersionInfoSwap {
+public:
+ VersionInfoSwap(wstring executableProperties, wstring launcher);
+
+ bool PatchExecutable();
+
+private:
+ wstring m_executableProperties;
+ wstring m_launcher;
+
+ map<wstring, wstring> m_props;
+
+ bool LoadFromPropertyFile();
+ bool CreateNewResource(ByteBuffer *buf);
+ bool UpdateResource(LPVOID lpResLock, DWORD size);
+ bool FillFixedFileInfo(VS_FIXEDFILEINFO *fxi);
+};
+
+#endif // VERSIONINFOSWAP_H
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/WinErrorHandling.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "WinErrorHandling.h"
+#include "Log.h"
+#include "SysInfo.h"
+#include "FileUtils.h"
+
+
+namespace {
+
+std::string makeMessage(const std::string& msg, const char* label,
+ const void* c, DWORD errorCode) {
+ std::ostringstream err;
+ err << (label ? label : "Some error") << " [" << errorCode << "]";
+
+ HMODULE hmodule = NULL;
+ if (c) {
+ GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS
+ | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
+ reinterpret_cast<LPCTSTR>(c), &hmodule);
+
+ if (!hmodule) {
+ LOG_WARNING(tstrings::any() << "GetModuleHandleEx() failed for "
+ << c << " address.");
+ }
+ }
+ if (hmodule || !c) {
+ err << "(" << SysError::getSysErrorMessage(errorCode, hmodule) << ")";
+ }
+
+ return joinErrorMessages(msg, err.str());
+}
+
+
+std::wstring getSystemMessageDescription(DWORD messageId, HMODULE moduleHandle) {
+ LPWSTR pMsg = NULL;
+ std::wstring descr;
+
+ // we always retrieve UNICODE description from system,
+ // convert it to utf8 if UNICODE is not defined
+
+ while (true) {
+ DWORD res = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER
+ | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS
+ | (moduleHandle != NULL ? FORMAT_MESSAGE_FROM_HMODULE : 0),
+ moduleHandle, messageId, 0, (LPWSTR)&pMsg, 0, NULL);
+ if (res > 0) {
+ // replace all non-printed chars with space
+ for (DWORD i=0; i<res; i++) {
+ if (pMsg[i] < L' ') {
+ pMsg[i] = L' ';
+ }
+ }
+ // trim right (spaces and dots)
+ for (DWORD i=res; i>0; i--) {
+ if (pMsg[i] > L' ' && pMsg[i] != L'.') {
+ break;
+ }
+ pMsg[i] = 0;
+ }
+
+ descr = pMsg;
+
+ LocalFree(pMsg);
+ } else {
+ // if we fail to get description for specific moduleHandle,
+ // try to get "common" description.
+ if (moduleHandle != NULL) {
+ moduleHandle = NULL;
+ continue;
+ }
+ descr = L"No description available";
+ }
+ break;
+ }
+
+ return descr;
+}
+
+} // namespace
+
+
+SysError::SysError(const tstrings::any& msg, const void* caller, DWORD ec,
+ const char* label):
+
+std::runtime_error(makeMessage(msg.str(), label, caller, ec)) {
+}
+
+std::wstring SysError::getSysErrorMessage(DWORD errCode, HMODULE moduleHandle) {
+ tstrings::any msg;
+ msg << "system error " << errCode
+ << " (" << getSystemMessageDescription(errCode, moduleHandle) << ")";
+ return msg.tstr();
+}
+
+std::wstring SysError::getComErrorMessage(HRESULT hr) {
+ HRESULT hrOrig = hr;
+ // for FACILITY_WIN32 facility we need to reset hiword
+ if(HRESULT_FACILITY(hr) == FACILITY_WIN32) {
+ hr = HRESULT_CODE(hr);
+ }
+ return tstrings::format(_T("COM error 0x%08X (%s)"), hrOrig,
+ getSystemMessageDescription(hr, NULL));
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/WinErrorHandling.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+
+#ifndef WinErrorHandling_h
+#define WinErrorHandling_h
+
+
+#include "ErrorHandling.h"
+
+
+class SysError : public std::runtime_error {
+public:
+ SysError(const tstrings::any& msg, const void* caller,
+ DWORD errorCode=GetLastError(), const char* label="System error");
+
+ // returns string "system error <errCode> (error_description)"
+ // in UNICODE is not defined, the string returned is utf8-encoded
+ static std::wstring getSysErrorMessage(DWORD errCode = GetLastError(),
+ HMODULE moduleHandle = NULL);
+
+ // returns string "COM error 0x<hr> (error_description)"
+ // in UNICODE is not defined, the string returned is utf8-encoded
+ static std::wstring getComErrorMessage(HRESULT hr);
+};
+
+#endif // #ifndef WinErrorHandling_h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/WinSysInfo.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <windows.h>
+#include <shellapi.h>
+
+#include "WinSysInfo.h"
+#include "FileUtils.h"
+#include "WinErrorHandling.h"
+
+#pragma comment(lib, "Shell32")
+
+namespace SysInfo {
+
+tstring getTempDir() {
+ std::vector<TCHAR> buffer(MAX_PATH);
+ DWORD res = GetTempPath(static_cast<DWORD>(buffer.size()), buffer.data());
+ if (res > buffer.size()) {
+ buffer.resize(res);
+ GetTempPath(static_cast<DWORD>(buffer.size()), buffer.data());
+ }
+ return FileUtils::removeTrailingSlash(buffer.data());
+}
+
+namespace {
+
+template <class Func>
+tstring getSystemDirImpl(Func func, const std::string& label) {
+ std::vector<TCHAR> buffer(MAX_PATH);
+ for (int i=0; i<2; i++) {
+ DWORD res = func(buffer.data(), static_cast<DWORD>(buffer.size()));
+ if (!res) {
+ JP_THROW(SysError(label + " failed", func));
+ }
+ if (res < buffer.size()) {
+ return FileUtils::removeTrailingSlash(buffer.data());
+ }
+ buffer.resize(res + 1);
+ }
+ JP_THROW("Unexpected reply from" + label);
+}
+
+} // namespace
+
+tstring getSystem32Dir() {
+ return getSystemDirImpl(GetSystemDirectory, "GetSystemDirectory");
+}
+
+tstring getWIPath() {
+ return FileUtils::mkpath() << getSystem32Dir() << _T("msiexec.exe");
+}
+
+namespace {
+
+tstring getModulePath(HMODULE h)
+{
+ std::vector<TCHAR> buf(MAX_PATH);
+ DWORD len = 0;
+ while (true) {
+ len = GetModuleFileName(h, buf.data(), (DWORD)buf.size());
+ if (len < buf.size()) {
+ break;
+ }
+ // buffer is too small, increase it
+ buf.resize(buf.size() * 2);
+ }
+
+ if (len == 0) {
+ // error occured
+ JP_THROW(SysError("GetModuleFileName failed", GetModuleFileName));
+ }
+ return tstring(buf.begin(), buf.begin() + len);
+}
+
+} // namespace
+
+tstring getProcessModulePath() {
+ return getModulePath(NULL);
+}
+
+HMODULE getCurrentModuleHandle()
+{
+ // get module handle for the address of this function
+ LPCWSTR address = reinterpret_cast<LPCWSTR>(getCurrentModuleHandle);
+ HMODULE hmodule = NULL;
+ if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS
+ | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, address, &hmodule))
+ {
+ JP_THROW(SysError(tstrings::any() << "GetModuleHandleExW failed",
+ GetModuleHandleExW));
+ }
+ return hmodule;
+}
+
+tstring getCurrentModulePath()
+{
+ return getModulePath(getCurrentModuleHandle());
+}
+
+tstring_array getCommandArgs(CommandArgProgramNameMode progNameMode)
+{
+ int argc = 0;
+ tstring_array result;
+
+ LPWSTR *parsedArgs = CommandLineToArgvW(GetCommandLineW(), &argc);
+ if (parsedArgs == NULL) {
+ JP_THROW(SysError("CommandLineToArgvW failed", CommandLineToArgvW));
+ }
+ // the 1st element contains program name
+ for (int i = progNameMode == ExcludeProgramName ? 1 : 0; i < argc; i++) {
+ result.push_back(parsedArgs[i]);
+ }
+ LocalFree(parsedArgs);
+
+ return result;
+}
+
+namespace {
+
+tstring getEnvVariableImpl(const tstring& name, bool* errorOccured=0) {
+ std::vector<TCHAR> buf(10);
+ SetLastError(ERROR_SUCCESS);
+ const DWORD size = GetEnvironmentVariable(name.c_str(), buf.data(),
+ DWORD(buf.size()));
+ if (GetLastError() == ERROR_ENVVAR_NOT_FOUND) {
+ if (errorOccured) {
+ *errorOccured = true;
+ return tstring();
+ }
+ JP_THROW(SysError(tstrings::any() << "GetEnvironmentVariable("
+ << name << ") failed. Variable not set", GetEnvironmentVariable));
+ }
+
+ if (size > buf.size()) {
+ buf.resize(size);
+ GetEnvironmentVariable(name.c_str(), buf.data(), DWORD(buf.size()));
+ if (GetLastError() != ERROR_SUCCESS) {
+ if (errorOccured) {
+ *errorOccured = true;
+ return tstring();
+ }
+ JP_THROW(SysError(tstrings::any() << "GetEnvironmentVariable("
+ << name << ") failed", GetEnvironmentVariable));
+ }
+ }
+
+ if (errorOccured) {
+ *errorOccured = false;
+ }
+ return tstring(buf.data());
+}
+
+} // namespace
+
+tstring getEnvVariable(const tstring& name) {
+ return getEnvVariableImpl(name);
+}
+
+tstring getEnvVariable(const std::nothrow_t&, const tstring& name,
+ const tstring& defValue) {
+ bool errorOccured = false;
+ const tstring reply = getEnvVariableImpl(name, &errorOccured);
+ if (errorOccured) {
+ return defValue;
+ }
+ return reply;
+}
+
+bool isEnvVariableSet(const tstring& name) {
+ TCHAR unused[1];
+ SetLastError(ERROR_SUCCESS);
+ GetEnvironmentVariable(name.c_str(), unused, _countof(unused));
+ return GetLastError() != ERROR_ENVVAR_NOT_FOUND;
+}
+
+} // end of namespace SysInfo
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/WinSysInfo.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+
+#ifndef WINSYSINFO_H
+#define WINSYSINFO_H
+
+#include "SysInfo.h"
+
+
+//
+// Windows specific SysInfo.
+//
+namespace SysInfo {
+ // gets Windows System folder. A typical path is C:\Windows\System32.
+ tstring getSystem32Dir();
+
+ // returns full path to msiexec.exe executable
+ tstring getWIPath();
+
+ // Returns handle of the current module (exe or dll).
+ // The function assumes this code is statically linked to the module.
+ HMODULE getCurrentModuleHandle();
+}
+
+
+#endif // WINSYSINFO_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/WindowsRegistry.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <Windows.h>
+#include <strsafe.h>
+#include <tchar.h>
+#include <jni.h>
+
+#include "Utils.h"
+
+// Max value name size per MSDN plus NULL
+#define VALUE_NAME_SIZE 16384
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#undef jdk_incubator_jpackage_internal_WindowsRegistry_HKEY_LOCAL_MACHINE
+#define jdk_incubator_jpackage_internal_WindowsRegistry_HKEY_LOCAL_MACHINE 1L
+
+ /*
+ * Class: jdk_incubator_jpackage_internal_WindowsRegistry
+ * Method: readDwordValue
+ * Signature: (ILjava/lang/String;Ljava/lang/String;I)I
+ */
+ JNIEXPORT jint JNICALL
+ Java_jdk_incubator_jpackage_internal_WindowsRegistry_readDwordValue(
+ JNIEnv *pEnv, jclass c, jint key, jstring jSubKey,
+ jstring jValue, jint defaultValue) {
+ jint jResult = defaultValue;
+
+ if (key != jdk_incubator_jpackage_internal_WindowsRegistry_HKEY_LOCAL_MACHINE) {
+ return jResult;
+ }
+
+ wstring subKey = GetStringFromJString(pEnv, jSubKey);
+ wstring value = GetStringFromJString(pEnv, jValue);
+
+ HKEY hSubKey = NULL;
+ LSTATUS status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, subKey.c_str(), 0,
+ KEY_QUERY_VALUE, &hSubKey);
+ if (status == ERROR_SUCCESS) {
+ DWORD dwValue = 0;
+ DWORD cbData = sizeof (DWORD);
+ status = RegQueryValueEx(hSubKey, value.c_str(), NULL, NULL,
+ (LPBYTE) & dwValue, &cbData);
+ if (status == ERROR_SUCCESS) {
+ jResult = (jint) dwValue;
+ }
+
+ RegCloseKey(hSubKey);
+ }
+
+ return jResult;
+ }
+
+ /*
+ * Class: jdk_incubator_jpackage_internal_WindowsRegistry
+ * Method: openRegistryKey
+ * Signature: (ILjava/lang/String;)J
+ */
+ JNIEXPORT jlong JNICALL
+ Java_jdk_incubator_jpackage_internal_WindowsRegistry_openRegistryKey(
+ JNIEnv *pEnv, jclass c, jint key, jstring jSubKey) {
+ if (key != jdk_incubator_jpackage_internal_WindowsRegistry_HKEY_LOCAL_MACHINE) {
+ return 0;
+ }
+
+ wstring subKey = GetStringFromJString(pEnv, jSubKey);
+ HKEY hSubKey = NULL;
+ LSTATUS status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, subKey.c_str(), 0,
+ KEY_QUERY_VALUE, &hSubKey);
+ if (status == ERROR_SUCCESS) {
+ return (jlong)hSubKey;
+ }
+
+ return 0;
+ }
+
+ /*
+ * Class: jdk_incubator_jpackage_internal_WindowsRegistry
+ * Method: enumRegistryValue
+ * Signature: (JI)Ljava/lang/String;
+ */
+ JNIEXPORT jstring JNICALL
+ Java_jdk_incubator_jpackage_internal_WindowsRegistry_enumRegistryValue(
+ JNIEnv *pEnv, jclass c, jlong lKey, jint jIndex) {
+ HKEY hKey = (HKEY)lKey;
+ TCHAR valueName[VALUE_NAME_SIZE] = {0}; // Max size per MSDN plus NULL
+ DWORD cchValueName = VALUE_NAME_SIZE;
+ LSTATUS status = RegEnumValue(hKey, (DWORD)jIndex, valueName,
+ &cchValueName, NULL, NULL, NULL, NULL);
+ if (status == ERROR_SUCCESS) {
+ size_t chLength = 0;
+ if (StringCchLength(valueName, VALUE_NAME_SIZE, &chLength)
+ == S_OK) {
+ return GetJStringFromString(pEnv, valueName, (jsize)chLength);
+ }
+ }
+
+ return NULL;
+ }
+
+ /*
+ * Class: jdk_incubator_jpackage_internal_WindowsRegistry
+ * Method: closeRegistryKey
+ * Signature: (J)V
+ */
+ JNIEXPORT void JNICALL
+ Java_jdk_incubator_jpackage_internal_WindowsRegistry_closeRegistryKey(
+ JNIEnv *pEnc, jclass c, jlong lKey) {
+ HKEY hKey = (HKEY)lKey;
+ RegCloseKey(hKey);
+ }
+
+ /*
+ * Class: jdk_incubator_jpackage_internal_WindowsRegistry
+ * Method: comparePaths
+ * Signature: (Ljava/lang/String;Ljava/lang/String;)Z
+ */
+ JNIEXPORT jboolean JNICALL
+ Java_jdk_incubator_jpackage_internal_WindowsRegistry_comparePaths(
+ JNIEnv *pEnv, jclass c, jstring jPath1, jstring jPath2) {
+ wstring path1 = GetStringFromJString(pEnv, jPath1);
+ wstring path2 = GetStringFromJString(pEnv, jPath2);
+
+ path1 = GetLongPath(path1);
+ path2 = GetLongPath(path2);
+
+ if (path1.length() == 0 || path2.length() == 0) {
+ return JNI_FALSE;
+ }
+
+ if (path1.length() != path2.length()) {
+ return JNI_FALSE;
+ }
+
+ if (_tcsnicmp(path1.c_str(), path2.c_str(), path1.length()) == 0) {
+ return JNI_TRUE;
+ }
+
+ return JNI_FALSE;
+ }
+
+#ifdef __cplusplus
+}
+#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/jpackage.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string>
+#include <windows.h>
+
+#include "ResourceEditor.h"
+#include "WinErrorHandling.h"
+#include "IconSwap.h"
+#include "VersionInfoSwap.h"
+#include "Utils.h"
+
+using namespace std;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ /*
+ * Class: jdk_incubator_jpackage_internal_WindowsAppImageBuilder
+ * Method: iconSwap
+ * Signature: (Ljava/lang/String;Ljava/lang/String;)I
+ */
+ JNIEXPORT jint JNICALL
+ Java_jdk_incubator_jpackage_internal_WindowsAppImageBuilder_iconSwap(
+ JNIEnv *pEnv, jclass c, jstring jIconTarget, jstring jLauncher) {
+ wstring iconTarget = GetStringFromJString(pEnv, jIconTarget);
+ wstring launcher = GetStringFromJString(pEnv, jLauncher);
+
+ if (ChangeIcon(iconTarget, launcher)) {
+ return 0;
+ }
+
+ return 1;
+ }
+
+ /*
+ * Class: jdk_incubator_jpackage_internal_WindowsAppImageBuilder
+ * Method: versionSwap
+ * Signature: (Ljava/lang/String;Ljava/lang/String;)I
+ */
+ JNIEXPORT jint JNICALL
+ Java_jdk_incubator_jpackage_internal_WindowsAppImageBuilder_versionSwap(
+ JNIEnv *pEnv, jclass c, jstring jExecutableProperties,
+ jstring jLauncher) {
+
+ wstring executableProperties = GetStringFromJString(pEnv,
+ jExecutableProperties);
+ wstring launcher = GetStringFromJString(pEnv, jLauncher);
+
+ VersionInfoSwap vs(executableProperties, launcher);
+ if (vs.PatchExecutable()) {
+ return 0;
+ }
+
+ return 1;
+ }
+
+ /*
+ * Class: jdk_incubator_jpackage_internal_WinExeBundler
+ * Method: embedMSI
+ * Signature: (Ljava/lang/String;Ljava/lang/String;)I
+ */
+ JNIEXPORT jint JNICALL Java_jdk_incubator_jpackage_internal_WinExeBundler_embedMSI(
+ JNIEnv *pEnv, jclass c, jstring jexePath, jstring jmsiPath) {
+
+ const wstring exePath = GetStringFromJString(pEnv, jexePath);
+ const wstring msiPath = GetStringFromJString(pEnv, jmsiPath);
+
+ JP_TRY;
+
+ ResourceEditor()
+ .id(L"msi")
+ .type(RT_RCDATA)
+ .apply(ResourceEditor::FileLock(exePath), msiPath);
+
+ return 0;
+
+ JP_CATCH_ALL;
+
+ return 1;
+ }
+
+ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason,
+ LPVOID lpvReserved) {
+ return TRUE;
+ }
+
+#ifdef __cplusplus
+}
+#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/tstrings.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,280 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdexcept>
+#include <algorithm>
+
+#include "tstrings.h"
+#include "ErrorHandling.h"
+
+
+namespace tstrings {
+
+/* Create formatted string
+ */
+tstring unsafe_format(tstring::const_pointer format, ...) {
+ if (!format) {
+ throw std::invalid_argument("Destination buffer can't be NULL");
+ }
+
+ tstring fmtout;
+ int ret;
+ const int inc = 256;
+
+ va_list args;
+ va_start(args, format);
+ do {
+ fmtout.resize(fmtout.size() + inc);
+#ifdef _MSC_VER
+ ret = _vsntprintf_s(&*fmtout.begin(), fmtout.size(), _TRUNCATE, format, args);
+#else
+ // With g++ this compiles only with '-std=gnu++0x' option
+ ret = vsnprintf(&*fmtout.begin(), fmtout.size(), format, args);
+#endif
+ } while(-1 == ret);
+ va_end(args);
+
+ //update string size by actual value
+ fmtout.resize(ret);
+
+ return fmtout;
+}
+
+/*
+ * Tests if two strings are equal according to CompareType.
+ *
+ * a - string to compare
+ * b - string to compare
+ * ct - CASE_SENSITIVE: case sensitive comparing type
+ * IGNORE_CASE: case insensitive comparing type
+ */
+bool equals(const tstring& a, const tstring& b, const CompareType ct) {
+ if (IGNORE_CASE==ct) {
+ return toLower(a) == toLower(b);
+ }
+ return a == b;
+}
+
+bool startsWith(const tstring &str, const tstring &substr, const CompareType ct)
+{
+ if (str.size() < substr.size()) {
+ return false;
+ }
+ const tstring startOfStr = str.substr(0, substr.size());
+ return tstrings::equals(startOfStr, substr, ct);
+}
+
+bool endsWith(const tstring &str, const tstring &substr, const CompareType ct)
+{
+ if (str.size() < substr.size()) {
+ return false;
+ }
+ const tstring endOfStr = str.substr(str.size() - substr.size());
+ return tstrings::equals(endOfStr, substr, ct);
+}
+
+/*
+ * Split string into a vector with given delimiter string
+ *
+ * strVector - string vector to store split tstring
+ * str - string to split
+ * delimiter - delimiter to split the string around
+ * st - ST_ALL: return value includes an empty string
+ * ST_EXCEPT_EMPTY_STRING: return value does not include an empty string
+ *
+ * Note: It does not support multiple delimiters
+ */
+void split(tstring_array &strVector, const tstring &str,
+ const tstring &delimiter, const SplitType st) {
+ tstring::size_type start = 0, end = 0, length = str.length();
+
+ if (length == 0 || delimiter.length() == 0) {
+ return;
+ }
+
+ end = str.find(delimiter, start);
+ while(end != tstring::npos) {
+ if(st == ST_ALL || end - start > 1 ) {
+ strVector.push_back(str.substr(start, end == tstring::npos ?
+ tstring::npos : end - start));
+ }
+ start = end > (tstring::npos - delimiter.size()) ?
+ tstring::npos : end + delimiter.size();
+ end = str.find(delimiter, start);
+ }
+
+ if(st == ST_ALL || start < length) {
+ strVector.push_back(str.substr(start, length - start));
+ }
+}
+
+/*
+ * Convert uppercase letters to lowercase
+ */
+tstring toLower(const tstring& str) {
+ tstring lower(str);
+ tstring::iterator ok = std::transform(lower.begin(), lower.end(),
+ lower.begin(), tolower);
+ if (ok!=lower.end()) {
+ lower.resize(0);
+ }
+ return lower;
+}
+
+
+/*
+ * Replace all substring occurrences in a tstring.
+ * If 'str' or 'search' is empty the function returns 'str'.
+ * The given 'str' remains unchanged in any case.
+ * The function returns changed copy of 'str'.
+ */
+tstring replace(const tstring &str, const tstring &search, const tstring &replace)
+{
+ if (search.empty()) {
+ return str;
+ }
+
+ tstring s(str);
+
+ for (size_t pos = 0; ; pos += replace.length()) {
+ pos = s.find(search, pos);
+ if (pos == tstring::npos) {
+ break;
+ }
+ s.erase(pos, search.length());
+ s.insert(pos, replace);
+ }
+ return s;
+}
+
+
+/*
+ * Remove trailing spaces
+ */
+
+tstring trim(const tstring& str, const tstring& whitespace) {
+ const size_t strBegin = str.find_first_not_of(whitespace);
+ if (strBegin == std::string::npos) {
+ return tstring(); // no content
+ }
+
+ const size_t strEnd = str.find_last_not_of(whitespace);
+ const size_t strRange = strEnd - strBegin + 1;
+
+ return str.substr(strBegin, strRange);
+}
+
+} // namespace tstrings
+
+
+#ifdef TSTRINGS_WITH_WCHAR
+namespace tstrings {
+
+namespace {
+/*
+ * Converts UTF16-encoded string into multi-byte string of the given encoding.
+ */
+std::string toMultiByte(const std::wstring& utf16str, int encoding) {
+ std::string reply;
+ do {
+ int cm = WideCharToMultiByte(encoding,
+ 0,
+ utf16str.c_str(),
+ int(utf16str.size()),
+ NULL,
+ 0,
+ NULL,
+ NULL);
+ if (cm < 0) {
+ JP_THROW("Unexpected reply from WideCharToMultiByte()");
+ }
+ if (0 == cm) {
+ break;
+ }
+
+ reply.resize(cm);
+ int cm2 = WideCharToMultiByte(encoding,
+ 0,
+ utf16str.c_str(),
+ int(utf16str.size()),
+ &*reply.begin(),
+ cm,
+ NULL,
+ NULL);
+ if (cm != cm2) {
+ JP_THROW("Unexpected reply from WideCharToMultiByte()");
+ }
+ } while(0);
+
+ return reply;
+}
+
+/*
+ * Converts multi-byte string of the given encoding into UTF16-encoded string.
+ */
+std::wstring fromMultiByte(const std::string& str, int encoding) {
+ std::wstring utf16;
+ do {
+ int cw = MultiByteToWideChar(encoding,
+ MB_ERR_INVALID_CHARS,
+ str.c_str(),
+ int(str.size()),
+ NULL,
+ 0);
+ if (cw < 0) {
+ JP_THROW("Unexpected reply from MultiByteToWideChar()");
+ }
+ if (0 == cw) {
+ break;
+ }
+
+ utf16.resize(cw);
+ int cw2 = MultiByteToWideChar(encoding,
+ MB_ERR_INVALID_CHARS,
+ str.c_str(),
+ int(str.size()),
+ &*utf16.begin(),
+ cw);
+ if (cw != cw2) {
+ JP_THROW("Unexpected reply from MultiByteToWideChar()");
+ }
+ } while(0);
+
+ return utf16;
+}
+} // namespace
+
+std::string toUtf8(const std::wstring& utf16str) {
+ return toMultiByte(utf16str, CP_UTF8);
+}
+
+std::wstring toUtf16(const std::string& utf8str) {
+ return fromMultiByte(utf8str, CP_UTF8);
+}
+
+} // namespace tstrings
+#endif // ifdef TSTRINGS_WITH_WCHAR
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/tstrings.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,426 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef TSTRINGS_H
+#define TSTRINGS_H
+
+#ifdef _MSC_VER
+# define TSTRINGS_WITH_WCHAR
+#endif
+
+#ifdef TSTRINGS_WITH_WCHAR
+#include <windows.h>
+#include <tchar.h>
+// Want compiler issue C4995 warnings for encounters of deprecated functions.
+#include <strsafe.h>
+#endif
+
+// STL's string header depends on deprecated functions.
+// We don't care about warnings from STL header, so disable them locally.
+#ifdef _MSC_VER
+# pragma warning(push)
+# pragma warning(disable:4995)
+#endif
+
+#include <string>
+#include <sstream>
+#include <iostream>
+#include <vector>
+
+#ifdef _MSC_VER
+# pragma warning(pop)
+#endif
+
+
+#ifndef _T
+# define _T(x) x
+#endif
+
+
+#ifdef TSTRINGS_WITH_WCHAR
+typedef std::wstring tstring;
+typedef std::wostringstream tostringstream;
+typedef std::wistringstream tistringstream;
+typedef std::wstringstream tstringstream;
+typedef std::wistream tistream;
+typedef std::wostream tostream;
+typedef std::wiostream tiostream;
+typedef std::wios tios;
+#else
+typedef std::string tstring;
+typedef std::ostringstream tostringstream;
+typedef std::istringstream tistringstream;
+typedef std::stringstream tstringstream;
+typedef std::istream tistream;
+typedef std::ostream tostream;
+typedef std::iostream tiostream;
+typedef std::ios tios;
+
+typedef const char* LPCTSTR;
+typedef char TCHAR;
+#endif
+
+// frequently used "array of tstrings" type
+typedef std::vector<tstring> tstring_array;
+
+namespace tstrings {
+ tstring unsafe_format(tstring::const_pointer format, ...);
+
+ enum CompareType {CASE_SENSITIVE, IGNORE_CASE};
+ bool equals(const tstring& a, const tstring& b,
+ const CompareType ct=CASE_SENSITIVE);
+ bool startsWith(const tstring &str, const tstring &substr,
+ const CompareType ct=CASE_SENSITIVE);
+ bool endsWith(const tstring &str, const tstring &substr,
+ const CompareType ct=CASE_SENSITIVE);
+
+ enum SplitType {ST_ALL, ST_EXCEPT_EMPTY_STRING};
+ void split(tstring_array &strVector, const tstring &str,
+ const tstring &delimiter, const SplitType st = ST_ALL);
+ inline tstring_array split(const tstring &str, const tstring &delimiter,
+ const SplitType st = ST_ALL) {
+ tstring_array result;
+ split(result, str, delimiter, st);
+ return result;
+ }
+ tstring trim(const tstring& str, const tstring& whitespace = _T(" \t"));
+
+ /**
+ * Writes sequence of values from [b, e) range into string buffer inserting
+ * 'delimiter' after each value except of the last one.
+ * Returns contents of string buffer.
+ */
+ template <class It>
+ tstring join(It b, It e, const tstring& delimiter=tstring()) {
+ tostringstream buf;
+ if (b != e) {
+ for (;;) {
+ buf << *b;
+ if (++b == e) {
+ break;
+ }
+ buf << delimiter;
+ }
+ }
+ return buf.str();
+ }
+
+ tstring toLower(const tstring& str);
+
+ tstring replace(const tstring &str, const tstring &search,
+ const tstring &replace);
+}
+
+
+namespace tstrings {
+ inline std::string toUtf8(const std::string& utf8str) {
+ return utf8str;
+ }
+
+#ifdef TSTRINGS_WITH_WCHAR
+ // conversion to Utf8
+ std::string toUtf8(const std::wstring& utf16str);
+
+ // conversion to Utf16
+ std::wstring toUtf16(const std::string& utf8str);
+
+ inline std::wstring fromUtf8(const std::string& utf8str) {
+ return toUtf16(utf8str);
+ }
+
+#else
+ inline std::string fromUtf8(const std::string& utf8str) {
+ return utf8str;
+ }
+#endif
+} // namespace tstrings
+
+
+namespace tstrings {
+namespace format_detail {
+
+ template <class T>
+ struct str_arg_value {
+ const tstring value;
+
+ str_arg_value(const std::string& v): value(fromUtf8(v)) {
+ }
+
+#ifdef TSTRINGS_WITH_WCHAR
+ str_arg_value(const std::wstring& v): value(v) {
+ }
+#endif
+
+ tstring::const_pointer operator () () const {
+ return value.c_str();
+ }
+ };
+
+ template <>
+ struct str_arg_value<tstring> {
+ const tstring::const_pointer value;
+
+ str_arg_value(const tstring& v): value(v.c_str()) {
+ }
+
+ str_arg_value(tstring::const_pointer v): value(v) {
+ }
+
+ tstring::const_pointer operator () () const {
+ return value;
+ }
+ };
+
+ inline str_arg_value<std::string> arg(const std::string& v) {
+ return v;
+ }
+
+ inline str_arg_value<std::string> arg(std::string::const_pointer v) {
+ return (v ? v : "(null)");
+ }
+
+#ifdef TSTRINGS_WITH_WCHAR
+ inline str_arg_value<std::wstring> arg(const std::wstring& v) {
+ return v;
+ }
+
+ inline str_arg_value<std::wstring> arg(std::wstring::const_pointer v) {
+ return (v ? v : L"(null)");
+ }
+#else
+ void arg(const std::wstring&); // Compilation error by design.
+ void arg(std::wstring::const_pointer); // Compilation error by design.
+#endif
+
+ template <class T>
+ struct arg_value {
+ arg_value(const T v): v(v) {
+ }
+ T operator () () const {
+ return v;
+ }
+ private:
+ const T v;
+ };
+
+ inline arg_value<int> arg(int v) {
+ return v;
+ }
+ inline arg_value<unsigned> arg(unsigned v) {
+ return v;
+ }
+ inline arg_value<long> arg(long v) {
+ return v;
+ }
+ inline arg_value<unsigned long> arg(unsigned long v) {
+ return v;
+ }
+ inline arg_value<long long> arg(long long v) {
+ return v;
+ }
+ inline arg_value<unsigned long long> arg(unsigned long long v) {
+ return v;
+ }
+ inline arg_value<float> arg(float v) {
+ return v;
+ }
+ inline arg_value<double> arg(double v) {
+ return v;
+ }
+ inline arg_value<bool> arg(bool v) {
+ return v;
+ }
+ inline arg_value<const void*> arg(const void* v) {
+ return v;
+ }
+
+} // namespace format_detail
+} // namespace tstrings
+
+
+namespace tstrings {
+ template <class T, class T2, class T3, class T4, class T5, class T6, class T7>
+ inline tstring format(const tstring& fmt, const T& v, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7) {
+ return unsafe_format(fmt.c_str(), format_detail::arg(v)(),
+ format_detail::arg(v2)(),
+ format_detail::arg(v3)(),
+ format_detail::arg(v4)(),
+ format_detail::arg(v5)(),
+ format_detail::arg(v6)(),
+ format_detail::arg(v7)());
+ }
+
+ template <class T, class T2, class T3, class T4, class T5, class T6>
+ inline tstring format(const tstring& fmt, const T& v, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6) {
+ return unsafe_format(fmt.c_str(), format_detail::arg(v)(),
+ format_detail::arg(v2)(),
+ format_detail::arg(v3)(),
+ format_detail::arg(v4)(),
+ format_detail::arg(v5)(),
+ format_detail::arg(v6)());
+ }
+
+ template <class T, class T2, class T3, class T4, class T5>
+ inline tstring format(const tstring& fmt, const T& v, const T2& v2, const T3& v3, const T4& v4, const T5& v5) {
+ return unsafe_format(fmt.c_str(), format_detail::arg(v)(),
+ format_detail::arg(v2)(),
+ format_detail::arg(v3)(),
+ format_detail::arg(v4)(),
+ format_detail::arg(v5)());
+ }
+
+ template <class T, class T2, class T3, class T4>
+ inline tstring format(const tstring& fmt, const T& v, const T2& v2, const T3& v3, const T4& v4) {
+ return unsafe_format(fmt.c_str(), format_detail::arg(v)(),
+ format_detail::arg(v2)(),
+ format_detail::arg(v3)(),
+ format_detail::arg(v4)());
+ }
+
+ template <class T, class T2, class T3>
+ inline tstring format(const tstring& fmt, const T& v, const T2& v2, const T3& v3) {
+ return unsafe_format(fmt.c_str(), format_detail::arg(v)(),
+ format_detail::arg(v2)(),
+ format_detail::arg(v3)());
+ }
+
+ template <class T, class T2>
+ inline tstring format(const tstring& fmt, const T& v, const T2& v2) {
+ return unsafe_format(fmt.c_str(), format_detail::arg(v)(),
+ format_detail::arg(v2)());
+
+ }
+
+ template <class T>
+ inline tstring format(const tstring& fmt, const T& v) {
+ return unsafe_format(fmt.c_str(), format_detail::arg(v)());
+ }
+} // namespace tstrings
+
+
+namespace tstrings {
+ /**
+ * Buffer that accepts both std::wstring and std::string instances doing
+ * encoding conversions behind the scenes. All std::string-s assumed to be
+ * UTF8-encoded, all std::wstring-s assumed to be UTF16-encoded.
+ */
+ class any {
+ public:
+ any() {
+ }
+
+ any(std::string::const_pointer msg) {
+ data << fromUtf8(msg);
+ }
+
+ any(const std::string& msg) {
+ data << fromUtf8(msg);
+ }
+
+#ifdef TSTRINGS_WITH_WCHAR
+ any(std::wstring::const_pointer msg) {
+ data << msg;
+ }
+
+ any(const std::wstring& msg) {
+ data << msg;
+ }
+
+ any& operator << (const std::wstring& v) {
+ data << v;
+ return *this;
+ }
+
+ // need this specialization instead std::wstring::pointer,
+ // otherwise LPWSTR is handled as abstract pointer (void*)
+ any& operator << (LPWSTR v) {
+ data << (v ? v : L"NULL");
+ return *this;
+ }
+
+ // need this specialization instead std::wstring::const_pointer,
+ // otherwise LPCWSTR is handled as abstract pointer (const void*)
+ any& operator << (LPCWSTR v) {
+ data << (v ? v : L"NULL");
+ return *this;
+ }
+
+ std::wstring wstr() const {
+ return data.str();
+ }
+#endif
+
+ template <class T>
+ any& operator << (T v) {
+ data << v;
+ return *this;
+ }
+
+ any& operator << (tostream& (*pf)(tostream&)) {
+ data << pf;
+ return *this;
+ }
+
+ any& operator << (tios& (*pf)(tios&)) {
+ data << pf;
+ return *this;
+ }
+
+ any& operator << (std::ios_base& (*pf)(std::ios_base&)) {
+ data << pf;
+ return *this;
+ }
+
+ std::string str() const {
+ return toUtf8(data.str());
+ }
+
+ tstring tstr() const {
+ return data.str();
+ }
+
+ private:
+ tostringstream data;
+ };
+
+ inline tstring to_tstring(const any& val) {
+ return val.tstr();
+ }
+} // namespace tstrings
+
+
+inline std::ostream& operator << (std::ostream& os, const tstrings::any& buf) {
+ os << buf.str();
+ return os;
+}
+
+#ifdef TSTRINGS_WITH_WCHAR
+inline std::wostream& operator << (std::wostream& os, const tstrings::any& buf) {
+ os << buf.wstr();
+ return os;
+}
+#endif
+
+#endif //TSTRINGS_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libwixhelper/libwixhelper.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <Windows.h>
+#include <msiquery.h>
+#include <shlwapi.h>
+
+extern "C" {
+
+#ifdef JP_EXPORT_FUNCTION
+#error Unexpected JP_EXPORT_FUNCTION define
+#endif
+#define JP_EXPORT_FUNCTION comment(linker, "/EXPORT:" __FUNCTION__ "=" __FUNCDNAME__)
+
+ BOOL WINAPI DllMain(HINSTANCE hInst, ULONG ulReason,
+ LPVOID lpvReserved) {
+ return TRUE;
+ }
+
+ BOOL DirectoryExist(TCHAR *szValue) {
+ DWORD attr = GetFileAttributes(szValue);
+ if (attr == INVALID_FILE_ATTRIBUTES) {
+ return FALSE;
+ }
+
+ if (attr & FILE_ATTRIBUTE_DIRECTORY) {
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+
+ UINT __stdcall CheckInstallDir(MSIHANDLE hInstall) {
+ #pragma JP_EXPORT_FUNCTION
+
+ TCHAR *szValue = NULL;
+ DWORD cchSize = 0;
+
+ UINT result = MsiGetProperty(hInstall, TEXT("INSTALLDIR"),
+ TEXT(""), &cchSize);
+ if (result == ERROR_MORE_DATA) {
+ cchSize = cchSize + 1; // NULL termination
+ szValue = new TCHAR[cchSize];
+ if (szValue) {
+ result = MsiGetProperty(hInstall, TEXT("INSTALLDIR"),
+ szValue, &cchSize);
+ } else {
+ return ERROR_INSTALL_FAILURE;
+ }
+ }
+
+ if (result != ERROR_SUCCESS) {
+ delete [] szValue;
+ return ERROR_INSTALL_FAILURE;
+ }
+
+ if (DirectoryExist(szValue)) {
+ if (PathIsDirectoryEmpty(szValue)) {
+ MsiSetProperty(hInstall, TEXT("INSTALLDIR_VALID"), TEXT("1"));
+ } else {
+ MsiSetProperty(hInstall, TEXT("INSTALLDIR_VALID"), TEXT("0"));
+ }
+ } else {
+ MsiSetProperty(hInstall, TEXT("INSTALLDIR_VALID"), TEXT("1"));
+ }
+
+ delete [] szValue;
+
+ return ERROR_SUCCESS;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/msiwrapper/Executor.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <algorithm>
+#include "Executor.h"
+#include "Log.h"
+#include "WinErrorHandling.h"
+
+
+namespace {
+
+void escapeArg(std::wstring& str) {
+ if (str.empty()) {
+ return;
+ }
+
+ if (str.front() == L'\"' && str.back() == L'\"' && str.size() > 1) {
+ return;
+ }
+
+ if (str.find_first_of(L" \t") != std::wstring::npos) {
+ str = L'"' + str + L'"';
+ }
+}
+
+} // namespace
+
+
+std::wstring Executor::args() const {
+ tstring_array tmpArgs;
+ // argv[0] is the module name.
+ tmpArgs.push_back(appPath);
+ tmpArgs.insert(tmpArgs.end(), argsArray.begin(), argsArray.end());
+
+ std::for_each(tmpArgs.begin(), tmpArgs.end(), escapeArg);
+ return tstrings::join(tmpArgs.begin(), tmpArgs.end(), _T(" "));
+}
+
+
+int Executor::execAndWaitForExit() const {
+ UniqueHandle h = startProcess();
+
+ const DWORD res = ::WaitForSingleObject(h.get(), INFINITE);
+ if (WAIT_FAILED == res) {
+ JP_THROW(SysError("WaitForSingleObject() failed", WaitForSingleObject));
+ }
+
+ DWORD exitCode = 0;
+ if (!GetExitCodeProcess(h.get(), &exitCode)) {
+ // Error reading process's exit code.
+ JP_THROW(SysError("GetExitCodeProcess() failed", GetExitCodeProcess));
+ }
+
+ const DWORD processId = GetProcessId(h.get());
+ if (!processId) {
+ JP_THROW(SysError("GetProcessId() failed.", GetProcessId));
+ }
+
+ LOG_TRACE(tstrings::any() << "Process with PID=" << processId
+ << " terminated. Exit code=" << exitCode);
+
+ return static_cast<int>(exitCode);
+}
+
+
+UniqueHandle Executor::startProcess() const {
+ const std::wstring argsStr = args();
+
+ std::vector<TCHAR> argsBuffer(argsStr.begin(), argsStr.end());
+ argsBuffer.push_back(0); // terminating '\0'
+
+ STARTUPINFO startupInfo;
+ ZeroMemory(&startupInfo, sizeof(startupInfo));
+ startupInfo.cb = sizeof(startupInfo);
+
+ PROCESS_INFORMATION processInfo;
+ ZeroMemory(&processInfo, sizeof(processInfo));
+
+ DWORD creationFlags = 0;
+
+ if (!theVisible) {
+ // For GUI applications.
+ startupInfo.dwFlags |= STARTF_USESHOWWINDOW;
+ startupInfo.wShowWindow = SW_HIDE;
+
+ // For console applications.
+ creationFlags |= CREATE_NO_WINDOW;
+ }
+
+ tstrings::any msg;
+ msg << "CreateProcess(" << appPath << ", " << argsStr << ")";
+
+ if (!CreateProcess(appPath.c_str(), argsBuffer.data(), NULL, NULL, FALSE,
+ creationFlags, NULL, NULL, &startupInfo, &processInfo)) {
+ msg << " failed";
+ JP_THROW(SysError(msg, CreateProcess));
+ }
+
+ msg << " succeeded; PID=" << processInfo.dwProcessId;
+ LOG_TRACE(msg);
+
+ // Close unneeded handles immediately.
+ UniqueHandle(processInfo.hThread);
+
+ // Return process handle.
+ return UniqueHandle(processInfo.hProcess);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/msiwrapper/Executor.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef EXECUTOR_H
+#define EXECUTOR_H
+
+#include "tstrings.h"
+#include "UniqueHandle.h"
+
+
+class Executor {
+public:
+ explicit Executor(const std::wstring& appPath=std::wstring()) {
+ app(appPath).visible(false);
+ }
+
+ /**
+ * Returns command line configured with arg() calls so far.
+ */
+ std::wstring args() const;
+
+ /**
+ * Set path to application to execute.
+ */
+ Executor& app(const std::wstring& v) {
+ appPath = v;
+ return *this;
+ }
+
+ /**
+ * Adds another command line argument.
+ */
+ Executor& arg(const std::wstring& v) {
+ argsArray.push_back(v);
+ return *this;
+ }
+
+ /**
+ * Controls if application window should be visible.
+ */
+ Executor& visible(bool v) {
+ theVisible = v;
+ return *this;
+ }
+
+ /**
+ * Starts application process and blocks waiting when the started
+ * process terminates.
+ * Returns process exit code.
+ * Throws exception if process start failed.
+ */
+ int execAndWaitForExit() const;
+
+private:
+ UniqueHandle startProcess() const;
+
+ bool theVisible;
+ tstring_array argsArray;
+ std::wstring appPath;
+};
+
+#endif // #ifndef EXECUTOR_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/msiwrapper/MsiWrapper.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,42 @@
+#include <algorithm>
+#include <windows.h>
+
+#include "SysInfo.h"
+#include "FileUtils.h"
+#include "Executor.h"
+#include "Resources.h"
+#include "WinErrorHandling.h"
+
+
+int __stdcall WinMain(HINSTANCE, HINSTANCE, LPSTR lpCmdLine, int nShowCmd)
+{
+ JP_TRY;
+
+ // Create temporary directory where to extract msi file.
+ const auto tempMsiDir = FileUtils::createTempDirectory();
+
+ // Schedule temporary directory for deletion.
+ FileUtils::Deleter cleaner;
+ cleaner.appendRecursiveDirectory(tempMsiDir);
+
+ const auto msiPath = FileUtils::mkpath() << tempMsiDir << L"main.msi";
+
+ // Extract msi file.
+ Resource(L"msi", RT_RCDATA).saveToFile(msiPath);
+
+ // Setup executor to run msiexec
+ Executor msiExecutor(SysInfo::getWIPath());
+ msiExecutor.arg(L"/i").arg(msiPath);
+ const auto args = SysInfo::getCommandArgs();
+ std::for_each(args.begin(), args.end(),
+ [&msiExecutor] (const tstring& arg) {
+ msiExecutor.arg(arg);
+ });
+
+ // Install msi file.
+ return msiExecutor.execAndWaitForExit();
+
+ JP_CATCH_ALL;
+
+ return -1;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/msiwrapper/Resources.cpp Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "Resources.h"
+#include "FileUtils.h"
+#include "WinErrorHandling.h"
+
+#include <fstream>
+
+
+Resource::Resource(LPCTSTR name, LPCTSTR type, HINSTANCE module) {
+ init(name, type, module);
+}
+
+Resource::Resource(UINT id, LPCTSTR type, HINSTANCE module) {
+ init(MAKEINTRESOURCE(id), type, module);
+}
+
+void Resource::init(LPCTSTR name, LPCTSTR type, HINSTANCE module) {
+ if (IS_INTRESOURCE(name)) {
+ std::wostringstream printer;
+ printer << L"#" << reinterpret_cast<size_t>(name);
+ nameStr = printer.str();
+ namePtr = name;
+ } else {
+ nameStr = name;
+ namePtr = nameStr.c_str();
+ }
+ if (IS_INTRESOURCE(type)) {
+ std::wostringstream printer;
+ printer << L"#" << reinterpret_cast<size_t>(name);
+ typeStr = printer.str();
+ typePtr = type;
+ } else {
+ typeStr = type;
+ typePtr = typeStr.c_str();
+ }
+ instance = module;
+}
+
+std::string Resource::getErrMsg(const std::string &descr) const {
+ return (tstrings::any() << descr << " (name='" << nameStr <<
+ "', type='" << typeStr << "')").str();
+}
+
+HRSRC Resource::findResource() const {
+ LPCTSTR id = namePtr;
+ // string resources are stored in blocks (stringtables)
+ // id of the resource is (stringId / 16 + 1)
+ if (typePtr == RT_STRING) {
+ id = MAKEINTRESOURCE(UINT(size_t(id) / 16 + 1));
+ }
+ return FindResource(instance, id, typePtr);
+}
+
+LPVOID Resource::getPtr(DWORD &size) const
+{
+ // LoadString returns the same result if value is zero-length or
+ // if if the value does not exists,
+ // so wee need to ensure the stringtable exists
+ HRSRC resInfo = findResource();
+ if (resInfo == NULL) {
+ JP_THROW(SysError(getErrMsg("cannot find resource"), FindResource));
+ }
+
+ HGLOBAL res = LoadResource(instance, resInfo);
+ if (res == NULL) {
+ JP_THROW(SysError(getErrMsg("cannot load resource"), LoadResource));
+ }
+
+ LPVOID ptr = LockResource(res);
+ if (res == NULL) {
+ JP_THROW(SysError(getErrMsg("cannot lock resource"), LockResource));
+ }
+
+ if (typePtr == RT_STRING) {
+ // string resources are stored in stringtables and
+ // need special handling
+ // The simplest way (while we don't need handle resource locale)
+ // is LoadString
+ // But this adds dependency on user32.dll,
+ // so implement custom string extraction
+
+ // number in the block (namePtr is an integer)
+ size_t num = size_t(namePtr) & 0xf;
+ LPWSTR strPtr = (LPWSTR)ptr;
+ for (size_t i = 0; i < num; i++) {
+ // 1st symbol contains string length
+ strPtr += DWORD(*strPtr) + 1;
+ }
+ // *strPtr contains string length, string value starts at strPtr+1
+ size = DWORD(*strPtr) * sizeof(wchar_t);
+ ptr = strPtr+1;
+ } else {
+ size = SizeofResource(instance, resInfo);
+ }
+
+ return ptr;
+}
+
+bool Resource::available() const {
+ return NULL != findResource();
+}
+
+unsigned Resource::size() const {
+ DWORD size = 0;
+ getPtr(size);
+ return size;
+}
+
+LPCVOID Resource::rawData() const {
+ DWORD size = 0;
+ return getPtr(size);
+}
+
+void Resource::saveToFile(const std::wstring &filePath) const {
+ DWORD size = 0;
+ const char *resPtr = (const char *)getPtr(size);
+
+ FileUtils::FileWriter(filePath).write(resPtr, size).finalize();
+}
+
+Resource::ByteArray Resource::binary() const {
+ DWORD size = 0;
+ LPBYTE resPtr = (LPBYTE)getPtr(size);
+ return ByteArray(resPtr, resPtr+size);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/msiwrapper/Resources.h Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef RESOURCES_H
+#define RESOURCES_H
+
+#include "WinSysInfo.h"
+
+
+/**
+ * Classes for resource loading.
+ * Common use cases:
+ * - check if resource is available and save it to file:
+ * Resource res(_T("MyResource"), _T("CustomResourceType"));
+ * if (res.available()) {
+ * res.saveToFile(_T("c:\\temp\\my_resource.bin"));
+ * }
+ */
+
+class Resource {
+public:
+ // name and type can be specified by string id,
+ // by integer id (RT_* constants or MAKEINTRESOURCE)
+ Resource(LPCWSTR name, LPCWSTR type,
+ HINSTANCE module = SysInfo::getCurrentModuleHandle());
+ Resource(UINT id, LPCWSTR type,
+ HINSTANCE module = SysInfo::getCurrentModuleHandle());
+
+ bool available() const;
+
+ // all this methods throw exception if the resource is not available
+ unsigned size() const;
+ // gets raw pointer to the resource data
+ LPCVOID rawData() const;
+
+ // save the resource to a file
+ void saveToFile(const std::wstring &filePath) const;
+
+ typedef std::vector<BYTE> ByteArray;
+ // returns the resource as byte array
+ ByteArray binary() const;
+
+private:
+ std::wstring nameStr;
+ LPCWSTR namePtr; // can be integer value or point to nameStr.c_str()
+ std::wstring typeStr;
+ LPCWSTR typePtr; // can be integer value or point to nameStr.c_str()
+ HINSTANCE instance;
+
+ void init(LPCWSTR name, LPCWSTR type, HINSTANCE module);
+
+ // generates error message
+ std::string getErrMsg(const std::string &descr) const;
+ HRSRC findResource() const;
+ LPVOID getPtr(DWORD &size) const;
+
+private:
+ // disable copying
+ Resource(const Resource&);
+ Resource& operator = (const Resource&);
+};
+
+#endif // RESOURCES_H
--- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/packager/AppRuntimeImageBuilder.java Tue Nov 12 12:19:24 2019 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,146 +0,0 @@
-/*
- * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.tools.jlink.internal.packager;
-
-
-import jdk.tools.jlink.builder.DefaultImageBuilder;
-import jdk.tools.jlink.internal.Jlink;
-import jdk.tools.jlink.internal.JlinkTask;
-import jdk.tools.jlink.plugin.Plugin;
-
-import java.io.File;
-import java.io.IOException;
-import java.lang.module.ModuleFinder;
-import java.nio.ByteOrder;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * AppRuntimeImageBuilder is a private API used only by the Java Packager to generate
- * a Java runtime image using jlink. AppRuntimeImageBuilder encapsulates the
- * arguments that jlink requires to generate this image. To create the image call the
- * build() method.
- */
-public final class AppRuntimeImageBuilder {
- private Path outputDir = null;
- private Map<String, String> launchers = Collections.emptyMap();
- private List<Path> modulePath = null;
- private Set<String> addModules = null;
- private Set<String> limitModules = null;
- private String excludeFileList = null;
- private Map<String, String> userArguments = null;
- private Boolean stripNativeCommands = null;
-
- public AppRuntimeImageBuilder() {}
-
- public void setOutputDir(Path value) {
- outputDir = value;
- }
-
- public void setLaunchers(Map<String, String> value) {
- launchers = value;
- }
-
- public void setModulePath(List<Path> value) {
- modulePath = value;
- }
-
- public void setAddModules(Set<String> value) {
- addModules = value;
- }
-
- public void setLimitModules(Set<String> value) {
- limitModules = value;
- }
-
- public void setExcludeFileList(String value) {
- excludeFileList = value;
- }
-
- public void setStripNativeCommands(boolean value) {
- stripNativeCommands = value;
- }
-
- public void setUserArguments(Map<String, String> value) {
- userArguments = value;
- }
-
- public void build() throws IOException {
- // jlink main arguments
- Jlink.JlinkConfiguration jlinkConfig =
- new Jlink.JlinkConfiguration(new File("").toPath(), // Unused
- addModules,
- ByteOrder.nativeOrder(),
- moduleFinder(modulePath,
- limitModules, addModules));
-
- // plugin configuration
- List<Plugin> plugins = new ArrayList<Plugin>();
-
- if (stripNativeCommands) {
- plugins.add(Jlink.newPlugin(
- "strip-native-commands",
- Collections.singletonMap("strip-native-commands", "on"),
- null));
- }
-
- if (excludeFileList != null && !excludeFileList.isEmpty()) {
- plugins.add(Jlink.newPlugin(
- "exclude-files",
- Collections.singletonMap("exclude-files", excludeFileList),
- null));
- }
-
- // add user supplied jlink arguments
- for (Map.Entry<String, String> entry : userArguments.entrySet()) {
- String key = entry.getKey();
- String value = entry.getValue();
- plugins.add(Jlink.newPlugin(key,
- Collections.singletonMap(key, value),
- null));
- }
-
- // build the image
- Jlink.PluginsConfiguration pluginConfig = new Jlink.PluginsConfiguration(
- plugins, new DefaultImageBuilder(outputDir, launchers), null);
- Jlink jlink = new Jlink();
- jlink.build(jlinkConfig, pluginConfig);
- }
-
- /*
- * Returns a ModuleFinder that limits observability to the given root
- * modules, their transitive dependences, plus a set of other modules.
- */
- public static ModuleFinder moduleFinder(List<Path> modulepaths,
- Set<String> roots,
- Set<String> otherModules) {
- return JlinkTask.newModuleFinder(modulepaths, roots, otherModules);
- }
-}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/TEST.properties Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,3 @@
+keys=jpackagePlatformPackage
+requires.properties=jpackage.test.SQETest
+maxOutputSize=2000000
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/apps/com.hello/com/hello/Hello.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.hello;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.PrintWriter;
+
+public class Hello {
+
+ private static final String MSG = "jpackage test application";
+ private static final int EXPECTED_NUM_OF_PARAMS = 3; // Starts at 1
+
+ public static void main(String[] args) {
+ String outputFile = "appOutput.txt";
+ File file = new File(outputFile);
+
+ try (PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)))) {
+ System.out.println(MSG);
+ out.println(MSG);
+
+ System.out.println("args.length: " + args.length);
+ out.println("args.length: " + args.length);
+
+ for (String arg : args) {
+ System.out.println(arg);
+ out.println(arg);
+ }
+
+ for (int index = 1; index <= EXPECTED_NUM_OF_PARAMS; index++) {
+ String value = System.getProperty("param" + index);
+ if (value != null) {
+ System.out.println("-Dparam" + index + "=" + value);
+ out.println("-Dparam" + index + "=" + value);
+ }
+ }
+ } catch (Exception ex) {
+ System.err.println(ex.toString());
+ }
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/apps/com.hello/module-info.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+module com.hello {
+ exports com.hello;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/apps/com.other/com/other/Other.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.other;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.PrintWriter;
+
+public class Other {
+
+ private static final String MSG = "other jpackage test application";
+ private static final int EXPECTED_NUM_OF_PARAMS = 3; // Starts at 1
+
+ public static void main(String[] args) {
+ String outputFile = "appOutput.txt";
+ File file = new File(outputFile);
+
+ try (PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)))) {
+ System.out.println(MSG);
+ out.println(MSG);
+
+ System.out.println("args.length: " + args.length);
+ out.println("args.length: " + args.length);
+
+ for (String arg : args) {
+ System.out.println(arg);
+ out.println(arg);
+ }
+
+ for (int index = 1; index <= EXPECTED_NUM_OF_PARAMS; index++) {
+ String value = System.getProperty("param" + index);
+ if (value != null) {
+ System.out.println("-Dparam" + index + "=" + value);
+ out.println("-Dparam" + index + "=" + value);
+ }
+ }
+ } catch (Exception ex) {
+ System.err.println(ex.toString());
+ }
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/apps/com.other/module-info.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+module com.other {
+ exports com.other;
+}
Binary file test/jdk/tools/jpackage/apps/dukeplug.png has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/apps/image/Hello.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.awt.AWTError;
+import java.awt.Desktop;
+import java.awt.GraphicsEnvironment;
+import java.awt.desktop.OpenFilesEvent;
+import java.awt.desktop.OpenFilesHandler;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.file.Path;
+import java.nio.file.Files;
+import java.util.stream.Collectors;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.stream.Stream;
+import java.util.Collections;
+
+public class Hello implements OpenFilesHandler {
+
+ public static void main(String[] args) throws IOException, InterruptedException {
+ var faFiles = getFaFiles();
+ if (faFiles != null) {
+ // Some files got opened through fa mechanizm.
+ // They are the arguments then.
+ args = faFiles.toArray(String[]::new);
+ }
+
+ var lines = printArgs(args);
+
+ lines.forEach(System.out::println);
+
+ var outputFile = getOutputFile(args);
+ trace(String.format("Output file: [%s]", outputFile));
+ Files.write(outputFile, lines);
+ }
+
+ private static List<String> printArgs(String[] args) {
+ List<String> lines = new ArrayList<>();
+ lines.add(MSG);
+
+ lines.add("args.length: " + args.length);
+
+ lines.addAll(List.of(args));
+
+ for (int index = 1; index <= EXPECTED_NUM_OF_PARAMS; index++) {
+ String value = System.getProperty("param" + index);
+ if (value != null) {
+ lines.add("-Dparam" + index + "=" + value);
+ }
+ }
+
+ return lines;
+ }
+
+ private static Path getOutputFile(String[] args) {
+ Path outputFilePath = Path.of("appOutput.txt");
+
+ // If first arg is a file (most likely from fa), then put output in the same folder as
+ // the file from fa.
+ if (args.length >= 1) {
+ Path faPath = Path.of(args[0]);
+ if (Files.exists(faPath)) {
+ return faPath.toAbsolutePath().getParent().resolve(outputFilePath);
+ }
+ }
+
+ try {
+ // Try writing in the default output file.
+ Files.write(outputFilePath, Collections.emptyList());
+ return outputFilePath;
+ } catch (IOException ex) {
+ // Log reason of a failure.
+ StringWriter errors = new StringWriter();
+ ex.printStackTrace(new PrintWriter(errors));
+ Stream.of(errors.toString().split("\\R")).forEachOrdered(Hello::trace);
+ }
+
+ return Path.of(System.getProperty("user.home")).resolve(outputFilePath);
+ }
+
+ @Override
+ public void openFiles(OpenFilesEvent e) {
+ synchronized(lock) {
+ trace("openFiles");
+ files = e.getFiles().stream()
+ .map(File::toString)
+ .collect(Collectors.toList());
+
+ lock.notifyAll();
+ }
+ }
+
+ private static List<String> getFaFiles() throws InterruptedException {
+ if (openFilesHandler == null) {
+ return null;
+ }
+
+ synchronized(openFilesHandler.lock) {
+ trace("getFaFiles: wait");
+ openFilesHandler.lock.wait(1000);
+ if (openFilesHandler.files == null) {
+ trace(String.format("getFaFiles: no files"));
+ return null;
+ }
+ // Return copy of `files` to keep access to `files` field synchronized.
+ trace(String.format("getFaFiles: file count %d",
+ openFilesHandler.files.size()));
+ return new ArrayList<>(openFilesHandler.files);
+ }
+ }
+
+ private List<String> files;
+ private final Object lock = new Object();
+ private final static Hello openFilesHandler = createInstance();
+
+ private static Hello createInstance() {
+ if (GraphicsEnvironment.isHeadless()) {
+ return null;
+ }
+
+ trace("Environment supports a display");
+
+ try {
+ // Disable JAB.
+ // Needed to suppress error:
+ // Exception in thread "main" java.awt.AWTError: Assistive Technology not found: com.sun.java.accessibility.AccessBridge
+ System.setProperty("javax.accessibility.assistive_technologies", "");
+ } catch (SecurityException ex) {
+ ex.printStackTrace();
+ }
+
+ try {
+ var desktop = Desktop.getDesktop();
+ if (desktop.isSupported(Desktop.Action.APP_OPEN_FILE)) {
+ trace("Set file handler");
+ Hello instance = new Hello();
+ desktop.setOpenFileHandler(instance);
+ return instance;
+ }
+ } catch (AWTError ex) {
+ trace("Set file handler failed");
+ ex.printStackTrace();
+ }
+
+ return null;
+ }
+
+ private static final String MSG = "jpackage test application";
+ private static final int EXPECTED_NUM_OF_PARAMS = 3; // Starts at 1
+
+ private static void trace(String msg) {
+ System.out.println("hello: " + msg);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/apps/installer/Hello.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.PrintWriter;
+import java.awt.Desktop;
+import java.awt.desktop.OpenFilesEvent;
+import java.awt.desktop.OpenFilesHandler;
+import java.util.List;
+
+public class Hello implements OpenFilesHandler {
+
+ private static final String MSG = "jpackage test application";
+ private static final int EXPECTED_NUM_OF_PARAMS = 3; // Starts at 1
+ private static List<File> files;
+
+ public static void main(String[] args) {
+ if(Desktop.getDesktop().isSupported(Desktop.Action.APP_OPEN_FILE)) {
+ Desktop.getDesktop().setOpenFileHandler(new Hello());
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ printToStdout(args);
+ if (args.length == 1 || (files != null && files.size() == 1)) { // Called via file association
+ printToFile(args);
+ }
+ }
+
+ private static void printToStdout(String[] args) {
+ System.out.println(MSG);
+
+ System.out.println("args.length: " + (files == null ? args.length : args.length + files.size()));
+
+ for (String arg : args) {
+ System.out.println(arg);
+ }
+
+ if (files != null) {
+ for (File file : files) {
+ System.out.println(file.getAbsolutePath());
+ }
+ }
+
+ for (int index = 1; index <= EXPECTED_NUM_OF_PARAMS; index++) {
+ String value = System.getProperty("param" + index);
+ if (value != null) {
+ System.out.println("-Dparam" + index + "=" + value);
+ }
+ }
+ }
+
+ private static void printToFile(String[] args) {
+ File inputFile = files == null ? new File(args[0]) : files.get(0);
+ String outputFile = inputFile.getParent() + File.separator + "appOutput.txt";
+ File file = new File(outputFile);
+
+ try (PrintWriter out
+ = new PrintWriter(new BufferedWriter(new FileWriter(file)))) {
+ out.println(MSG);
+
+ out.println("args.length: " + (files == null ? args.length : args.length + files.size()));
+
+ for (String arg : args) {
+ out.println(arg);
+ }
+
+ if (files != null) {
+ for (File f : files) {
+ out.println(f.getAbsolutePath());
+ }
+ }
+
+ for (int index = 1; index <= EXPECTED_NUM_OF_PARAMS; index++) {
+ String value = System.getProperty("param" + index);
+ if (value != null) {
+ out.println("-Dparam" + index + "=" + value);
+ }
+ }
+ } catch (Exception ex) {
+ System.err.println(ex.getMessage());
+ }
+ }
+
+ @Override
+ public void openFiles(OpenFilesEvent e) {
+ files = e.getFiles();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/JPackageHelper.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,684 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.BufferedWriter;
+import java.nio.file.FileVisitResult;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import java.util.spi.ToolProvider;
+import jdk.incubator.jpackage.ToolProviderFactory;
+
+public class JPackageHelper {
+
+ private static final boolean VERBOSE = false;
+ private static final String OS = System.getProperty("os.name").toLowerCase();
+ private static final String JAVA_HOME = System.getProperty("java.home");
+ public static final String TEST_SRC_ROOT;
+ public static final String TEST_SRC;
+ private static final Path BIN_DIR = Path.of(JAVA_HOME, "bin");
+ private static final Path JPACKAGE;
+ private static final Path JAVAC;
+ private static final Path JAR;
+ private static final Path JLINK;
+
+ public static class ModuleArgs {
+ private final String version;
+ private final String mainClass;
+
+ ModuleArgs(String version, String mainClass) {
+ this.version = version;
+ this.mainClass = mainClass;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public String getMainClass() {
+ return mainClass;
+ }
+ }
+
+ static {
+ if (OS.startsWith("win")) {
+ JPACKAGE = BIN_DIR.resolve("jpackage.exe");
+ JAVAC = BIN_DIR.resolve("javac.exe");
+ JAR = BIN_DIR.resolve("jar.exe");
+ JLINK = BIN_DIR.resolve("jlink.exe");
+ } else {
+ JPACKAGE = BIN_DIR.resolve("jpackage");
+ JAVAC = BIN_DIR.resolve("javac");
+ JAR = BIN_DIR.resolve("jar");
+ JLINK = BIN_DIR.resolve("jlink");
+ }
+
+ // Figure out test src based on where we called
+ TEST_SRC = System.getProperty("test.src");
+ Path root = Path.of(TEST_SRC);
+ Path apps = Path.of(TEST_SRC, "apps");
+ if (apps.toFile().exists()) {
+ // fine - test is at root
+ } else {
+ apps = Path.of(TEST_SRC, "..", "apps");
+ if (apps.toFile().exists()) {
+ root = apps.getParent().normalize(); // test is 1 level down
+ } else {
+ apps = Path.of(TEST_SRC, "..", "..", "apps");
+ if (apps.toFile().exists()) {
+ root = apps.getParent().normalize(); // 2 levels down
+ } else {
+ apps = Path.of(TEST_SRC, "..", "..", "..", "apps");
+ if (apps.toFile().exists()) {
+ root = apps.getParent().normalize(); // 3 levels down
+ } else {
+ // if we ever have tests more than three levels
+ // down we need to add code here
+ throw new RuntimeException("we should never get here");
+ }
+ }
+ }
+ }
+ TEST_SRC_ROOT = root.toString();
+ }
+
+ static final ToolProvider JPACKAGE_TOOL =
+ ToolProviderFactory.findFirst("jpackage").orElseThrow(
+ () -> new RuntimeException("jpackage tool not found"));
+
+ public static int execute(File out, String... command) throws Exception {
+ if (VERBOSE) {
+ System.out.print("Execute command: ");
+ for (String c : command) {
+ System.out.print(c);
+ System.out.print(" ");
+ }
+ System.out.println();
+ }
+
+ ProcessBuilder builder = new ProcessBuilder(command);
+ if (out != null) {
+ builder.redirectErrorStream(true);
+ builder.redirectOutput(out);
+ }
+
+ Process process = builder.start();
+ return process.waitFor();
+ }
+
+ public static Process executeNoWait(File out, String... command) throws Exception {
+ if (VERBOSE) {
+ System.out.print("Execute command: ");
+ for (String c : command) {
+ System.out.print(c);
+ System.out.print(" ");
+ }
+ System.out.println();
+ }
+
+ ProcessBuilder builder = new ProcessBuilder(command);
+ if (out != null) {
+ builder.redirectErrorStream(true);
+ builder.redirectOutput(out);
+ }
+
+ return builder.start();
+ }
+
+ private static String[] getCommand(String... args) {
+ String[] command;
+ if (args == null) {
+ command = new String[1];
+ } else {
+ command = new String[args.length + 1];
+ }
+
+ int index = 0;
+ command[index] = JPACKAGE.toString();
+
+ if (args != null) {
+ for (String arg : args) {
+ index++;
+ command[index] = arg;
+ }
+ }
+
+ return command;
+ }
+
+ public static void deleteRecursive(File path) throws IOException {
+ if (!path.exists()) {
+ return;
+ }
+
+ Path directory = path.toPath();
+ Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(Path file,
+ BasicFileAttributes attr) throws IOException {
+ file.toFile().setWritable(true);
+ if (OS.startsWith("win")) {
+ try {
+ Files.setAttribute(file, "dos:readonly", false);
+ } catch (Exception ioe) {
+ // just report and try to contune
+ System.err.println("IOException: " + ioe);
+ ioe.printStackTrace(System.err);
+ }
+ }
+ Files.delete(file);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult preVisitDirectory(Path dir,
+ BasicFileAttributes attr) throws IOException {
+ if (OS.startsWith("win")) {
+ Files.setAttribute(dir, "dos:readonly", false);
+ }
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir, IOException e)
+ throws IOException {
+ Files.delete(dir);
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ }
+
+ public static void deleteOutputFolder(String output) throws IOException {
+ File outputFolder = new File(output);
+ System.out.println("deleteOutputFolder: " + outputFolder.getAbsolutePath());
+ try {
+ deleteRecursive(outputFolder);
+ } catch (IOException ioe) {
+ System.err.println("IOException: " + ioe);
+ ioe.printStackTrace(System.err);
+ deleteRecursive(outputFolder);
+ }
+ }
+
+ public static String executeCLI(boolean retValZero, String... args) throws Exception {
+ int retVal;
+ File outfile = new File("output.log");
+ String[] command = getCommand(args);
+ try {
+ retVal = execute(outfile, command);
+ } catch (Exception ex) {
+ if (outfile.exists()) {
+ System.err.println(Files.readString(outfile.toPath()));
+ }
+ throw ex;
+ }
+
+ String output = Files.readString(outfile.toPath());
+ if (retValZero) {
+ if (retVal != 0) {
+ System.err.println("command run:");
+ for (String s : command) { System.err.println(s); }
+ System.err.println("command output:");
+ System.err.println(output);
+ throw new AssertionError("jpackage exited with error: " + retVal);
+ }
+ } else {
+ if (retVal == 0) {
+ System.err.println(output);
+ throw new AssertionError("jpackage exited without error: " + retVal);
+ }
+ }
+
+ if (VERBOSE) {
+ System.out.println("output =");
+ System.out.println(output);
+ }
+
+ return output;
+ }
+
+ public static String executeToolProvider(boolean retValZero, String... args) throws Exception {
+ StringWriter writer = new StringWriter();
+ PrintWriter pw = new PrintWriter(writer);
+ int retVal = JPACKAGE_TOOL.run(pw, pw, args);
+ String output = writer.toString();
+
+ if (retValZero) {
+ if (retVal != 0) {
+ System.err.println(output);
+ throw new AssertionError("jpackage exited with error: " + retVal);
+ }
+ } else {
+ if (retVal == 0) {
+ System.err.println(output);
+ throw new AssertionError("jpackage exited without error");
+ }
+ }
+
+ if (VERBOSE) {
+ System.out.println("output =");
+ System.out.println(output);
+ }
+
+ return output;
+ }
+
+ public static boolean isWindows() {
+ return (OS.contains("win"));
+ }
+
+ public static boolean isOSX() {
+ return (OS.contains("mac"));
+ }
+
+ public static boolean isLinux() {
+ return ((OS.contains("nix") || OS.contains("nux")));
+ }
+
+ public static void createHelloImageJar(String inputDir) throws Exception {
+ createJar(false, "Hello", "image", inputDir);
+ }
+
+ public static void createHelloImageJar() throws Exception {
+ createJar(false, "Hello", "image", "input");
+ }
+
+ public static void createHelloImageJarWithMainClass() throws Exception {
+ createJar(true, "Hello", "image", "input");
+ }
+
+ public static void createHelloInstallerJar() throws Exception {
+ createJar(false, "Hello", "installer", "input");
+ }
+
+ public static void createHelloInstallerJarWithMainClass() throws Exception {
+ createJar(true, "Hello", "installer", "input");
+ }
+
+ private static void createJar(boolean mainClassAttribute, String name,
+ String testType, String inputDir) throws Exception {
+ int retVal;
+
+ File input = new File(inputDir);
+ if (!input.exists()) {
+ input.mkdirs();
+ }
+
+ Path src = Path.of(TEST_SRC_ROOT + File.separator + "apps"
+ + File.separator + testType + File.separator + name + ".java");
+ Path dst = Path.of(name + ".java");
+
+ if (dst.toFile().exists()) {
+ Files.delete(dst);
+ }
+ Files.copy(src, dst);
+
+
+ File javacLog = new File("javac.log");
+ try {
+ retVal = execute(javacLog, JAVAC.toString(), name + ".java");
+ } catch (Exception ex) {
+ if (javacLog.exists()) {
+ System.err.println(Files.readString(javacLog.toPath()));
+ }
+ throw ex;
+ }
+
+ if (retVal != 0) {
+ if (javacLog.exists()) {
+ System.err.println(Files.readString(javacLog.toPath()));
+ }
+ throw new AssertionError("javac exited with error: " + retVal);
+ }
+
+ File jarLog = new File("jar.log");
+ try {
+ List<String> args = new ArrayList<>();
+ args.add(JAR.toString());
+ args.add("-c");
+ args.add("-v");
+ args.add("-f");
+ args.add(inputDir + File.separator + name.toLowerCase() + ".jar");
+ if (mainClassAttribute) {
+ args.add("-e");
+ args.add(name);
+ }
+ args.add(name + ".class");
+ retVal = execute(jarLog, args.stream().toArray(String[]::new));
+ } catch (Exception ex) {
+ if (jarLog.exists()) {
+ System.err.println(Files.readString(jarLog.toPath()));
+ }
+ throw ex;
+ }
+
+ if (retVal != 0) {
+ if (jarLog.exists()) {
+ System.err.println(Files.readString(jarLog.toPath()));
+ }
+ throw new AssertionError("jar exited with error: " + retVal);
+ }
+ }
+
+ public static void createHelloModule() throws Exception {
+ createModule("Hello.java", "input", "hello", null, true);
+ }
+
+ public static void createHelloModule(ModuleArgs moduleArgs) throws Exception {
+ createModule("Hello.java", "input", "hello", moduleArgs, true);
+ }
+
+ public static void createOtherModule() throws Exception {
+ createModule("Other.java", "input-other", "other", null, false);
+ }
+
+ private static void createModule(String javaFile, String inputDir, String aName,
+ ModuleArgs moduleArgs, boolean createModularJar) throws Exception {
+ int retVal;
+
+ File input = new File(inputDir);
+ if (!input.exists()) {
+ input.mkdir();
+ }
+
+ File module = new File("module" + File.separator + "com." + aName);
+ if (!module.exists()) {
+ module.mkdirs();
+ }
+
+ File javacLog = new File("javac.log");
+ try {
+ List<String> args = new ArrayList<>();
+ args.add(JAVAC.toString());
+ args.add("-d");
+ args.add("module" + File.separator + "com." + aName);
+ args.add(TEST_SRC_ROOT + File.separator + "apps" + File.separator
+ + "com." + aName + File.separator + "module-info.java");
+ args.add(TEST_SRC_ROOT + File.separator + "apps"
+ + File.separator + "com." + aName + File.separator + "com"
+ + File.separator + aName + File.separator + javaFile);
+ retVal = execute(javacLog, args.stream().toArray(String[]::new));
+ } catch (Exception ex) {
+ if (javacLog.exists()) {
+ System.err.println(Files.readString(javacLog.toPath()));
+ }
+ throw ex;
+ }
+
+ if (retVal != 0) {
+ if (javacLog.exists()) {
+ System.err.println(Files.readString(javacLog.toPath()));
+ }
+ throw new AssertionError("javac exited with error: " + retVal);
+ }
+
+ if (createModularJar) {
+ File jarLog = new File("jar.log");
+ try {
+ List<String> args = new ArrayList<>();
+ args.add(JAR.toString());
+ args.add("--create");
+ args.add("--file");
+ args.add(inputDir + File.separator + "com." + aName + ".jar");
+ if (moduleArgs != null) {
+ if (moduleArgs.getVersion() != null) {
+ args.add("--module-version");
+ args.add(moduleArgs.getVersion());
+ }
+
+ if (moduleArgs.getMainClass()!= null) {
+ args.add("--main-class");
+ args.add(moduleArgs.getMainClass());
+ }
+ }
+ args.add("-C");
+ args.add("module" + File.separator + "com." + aName);
+ args.add(".");
+
+ retVal = execute(jarLog, args.stream().toArray(String[]::new));
+ } catch (Exception ex) {
+ if (jarLog.exists()) {
+ System.err.println(Files.readString(jarLog.toPath()));
+ }
+ throw ex;
+ }
+
+ if (retVal != 0) {
+ if (jarLog.exists()) {
+ System.err.println(Files.readString(jarLog.toPath()));
+ }
+ throw new AssertionError("jar exited with error: " + retVal);
+ }
+ }
+ }
+
+ public static void createRuntime() throws Exception {
+ List<String> moreArgs = new ArrayList<>();
+ createRuntime(moreArgs);
+ }
+
+ public static void createRuntime(List<String> moreArgs) throws Exception {
+ int retVal;
+
+ File jlinkLog = new File("jlink.log");
+ try {
+ List<String> args = new ArrayList<>();
+ args.add(JLINK.toString());
+ args.add("--output");
+ args.add("runtime");
+ args.add("--add-modules");
+ args.add("java.base");
+ args.addAll(moreArgs);
+
+ retVal = execute(jlinkLog, args.stream().toArray(String[]::new));
+ } catch (Exception ex) {
+ if (jlinkLog.exists()) {
+ System.err.println(Files.readString(jlinkLog.toPath()));
+ }
+ throw ex;
+ }
+
+ if (retVal != 0) {
+ if (jlinkLog.exists()) {
+ System.err.println(Files.readString(jlinkLog.toPath()));
+ }
+ throw new AssertionError("jlink exited with error: " + retVal);
+ }
+ }
+
+ public static String listToArgumentsMap(List<String> arguments, boolean toolProvider) {
+ if (arguments.isEmpty()) {
+ return "";
+ }
+
+ String argsStr = "";
+ for (int i = 0; i < arguments.size(); i++) {
+ String arg = arguments.get(i);
+ argsStr += quote(arg, toolProvider);
+ if ((i + 1) != arguments.size()) {
+ argsStr += " ";
+ }
+ }
+
+ if (!toolProvider && isWindows()) {
+ if (argsStr.contains(" ")) {
+ if (argsStr.contains("\"")) {
+ argsStr = escapeQuote(argsStr, toolProvider);
+ }
+ argsStr = "\"" + argsStr + "\"";
+ }
+ }
+ return argsStr;
+ }
+
+ public static String[] cmdWithAtFilename(String [] cmd, int ndx, int len)
+ throws IOException {
+ ArrayList<String> newAList = new ArrayList<>();
+ String fileString = null;
+ for (int i=0; i<cmd.length; i++) {
+ if (i == ndx) {
+ newAList.add("@argfile.cmds");
+ fileString = cmd[i];
+ } else if (i > ndx && i < ndx + len) {
+ fileString += " " + cmd[i];
+ } else {
+ newAList.add(cmd[i]);
+ }
+ }
+ if (fileString != null) {
+ Path path = new File("argfile.cmds").toPath();
+ try (BufferedWriter bw = Files.newBufferedWriter(path);
+ PrintWriter out = new PrintWriter(bw)) {
+ out.println(fileString);
+ }
+ }
+ return newAList.toArray(new String[0]);
+ }
+
+ public static String [] splitAndFilter(String output) {
+ if (output == null) {
+ return null;
+ }
+
+ return Stream.of(output.split("\\R"))
+ .filter(str -> !str.startsWith("Picked up"))
+ .filter(str -> !str.startsWith("WARNING: Using incubator"))
+ .filter(str -> !str.startsWith("hello: "))
+ .collect(Collectors.toList()).toArray(String[]::new);
+ }
+
+ private static String quote(String in, boolean toolProvider) {
+ if (in == null) {
+ return null;
+ }
+
+ if (in.isEmpty()) {
+ return "";
+ }
+
+ if (!in.contains("=")) {
+ // Not a property
+ if (in.contains(" ")) {
+ in = escapeQuote(in, toolProvider);
+ return "\"" + in + "\"";
+ }
+ return in;
+ }
+
+ if (!in.contains(" ")) {
+ return in; // No need to quote
+ }
+
+ int paramIndex = in.indexOf("=");
+ if (paramIndex <= 0) {
+ return in; // Something wrong, just skip quoting
+ }
+
+ String param = in.substring(0, paramIndex);
+ String value = in.substring(paramIndex + 1);
+
+ if (value.length() == 0) {
+ return in; // No need to quote
+ }
+
+ value = escapeQuote(value, toolProvider);
+
+ return param + "=" + "\"" + value + "\"";
+ }
+
+ private static String escapeQuote(String in, boolean toolProvider) {
+ if (in == null) {
+ return null;
+ }
+
+ if (in.isEmpty()) {
+ return "";
+ }
+
+ if (in.contains("\"")) {
+ // Use code points to preserve non-ASCII chars
+ StringBuilder sb = new StringBuilder();
+ int codeLen = in.codePointCount(0, in.length());
+ for (int i = 0; i < codeLen; i++) {
+ int code = in.codePointAt(i);
+ // Note: No need to escape '\' on Linux or OS X
+ // jpackage expects us to pass arguments and properties with
+ // quotes and spaces as a map
+ // with quotes being escaped with additional \ for
+ // internal quotes.
+ // So if we want two properties below:
+ // -Djnlp.Prop1=Some "Value" 1
+ // -Djnlp.Prop2=Some Value 2
+ // jpackage will need:
+ // "-Djnlp.Prop1=\"Some \\"Value\\" 1\" -Djnlp.Prop2=\"Some Value 2\""
+ // but since we using ProcessBuilder to run jpackage we will need to escape
+ // our escape symbols as well, so we will need to pass string below to ProcessBuilder:
+ // "-Djnlp.Prop1=\\\"Some \\\\\\\"Value\\\\\\\" 1\\\" -Djnlp.Prop2=\\\"Some Value 2\\\""
+ switch (code) {
+ case '"':
+ // " -> \" -> \\\"
+ if (i == 0 || in.codePointAt(i - 1) != '\\') {
+ sb.appendCodePoint('\\');
+ sb.appendCodePoint(code);
+ }
+ break;
+ case '\\':
+ // We need to escape already escaped symbols as well
+ if ((i + 1) < codeLen) {
+ int nextCode = in.codePointAt(i + 1);
+ if (nextCode == '"') {
+ // \" -> \\\"
+ sb.appendCodePoint('\\');
+ sb.appendCodePoint('\\');
+ sb.appendCodePoint('\\');
+ sb.appendCodePoint(nextCode);
+ } else {
+ sb.appendCodePoint('\\');
+ sb.appendCodePoint(code);
+ }
+ } else {
+ sb.appendCodePoint(code);
+ }
+ break;
+ default:
+ sb.appendCodePoint(code);
+ break;
+ }
+ }
+ return sb.toString();
+ }
+
+ return in;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/JPackageInstallerHelper.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.List;
+
+public class JPackageInstallerHelper {
+ private static final String JPACKAGE_TEST_OUTPUT = "jpackage.test.output";
+ private static final String JPACKAGE_VERIFY_INSTALL = "jpackage.verify.install";
+ private static final String JPACKAGE_VERIFY_UNINSTALL = "jpackage.verify.uninstall";
+ private static String testOutput;
+ private static final boolean isTestOutputSet;
+ private static final boolean isVerifyInstall;
+ private static final boolean isVerifyUnInstall;
+
+ static {
+ String out = System.getProperty(JPACKAGE_TEST_OUTPUT);
+ isTestOutputSet = (out != null);
+ if (isTestOutputSet) {
+ File file = new File(out);
+ if (!file.exists()) {
+ throw new AssertionError(file.getAbsolutePath() + " does not exist");
+ }
+
+ if (!file.isDirectory()) {
+ throw new AssertionError(file.getAbsolutePath() + " is not a directory");
+ }
+
+ if (!file.canWrite()) {
+ throw new AssertionError(file.getAbsolutePath() + " is not writable");
+ }
+
+ if (out.endsWith(File.separator)) {
+ out = out.substring(0, out.length() - 2);
+ }
+
+ testOutput = out;
+ }
+
+ isVerifyInstall = (System.getProperty(JPACKAGE_VERIFY_INSTALL) != null);
+ isVerifyUnInstall = (System.getProperty(JPACKAGE_VERIFY_UNINSTALL) != null);
+ }
+
+ public static boolean isTestOutputSet() {
+ return isTestOutputSet;
+ }
+
+ public static boolean isVerifyInstall() {
+ return isVerifyInstall;
+ }
+
+ public static boolean isVerifyUnInstall() {
+ return isVerifyUnInstall;
+ }
+
+ public static void copyTestResults(List<String> files) throws Exception {
+ if (!isTestOutputSet()) {
+ return;
+ }
+
+ File dest = new File(testOutput);
+ if (!dest.exists()) {
+ dest.mkdirs();
+ }
+
+ if (JPackageHelper.isWindows()) {
+ files.add(JPackagePath.getTestSrc() + File.separator + "install.bat");
+ files.add(JPackagePath.getTestSrc() + File.separator + "uninstall.bat");
+ } else {
+ files.add(JPackagePath.getTestSrc() + File.separator + "install.sh");
+ files.add(JPackagePath.getTestSrc() + File.separator + "uninstall.sh");
+ }
+
+ for (String file : files) {
+ Path source = Path.of(file);
+ Path target = Path.of(dest.toPath() + File.separator + source.getFileName());
+ Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+
+ public static void validateApp(String app) throws Exception {
+ File outFile = new File("appOutput.txt");
+ if (outFile.exists()) {
+ outFile.delete();
+ }
+
+ int retVal = JPackageHelper.execute(outFile, app);
+ if (retVal != 0) {
+ throw new AssertionError(
+ "Test application exited with error: " + retVal);
+ }
+
+ if (!outFile.exists()) {
+ throw new AssertionError(outFile.getAbsolutePath() + " was not created");
+ }
+
+ String output = Files.readString(outFile.toPath());
+ String[] result = output.split("\n");
+ if (result.length != 2) {
+ System.err.println(output);
+ throw new AssertionError(
+ "Unexpected number of lines: " + result.length);
+ }
+
+ if (!result[0].trim().equals("jpackage test application")) {
+ throw new AssertionError("Unexpected result[0]: " + result[0]);
+ }
+
+ if (!result[1].trim().equals("args.length: 0")) {
+ throw new AssertionError("Unexpected result[1]: " + result[1]);
+ }
+ }
+
+ public static void validateOutput(String output) throws Exception {
+ File file = new File(output);
+ if (!file.exists()) {
+ // Try lower case in case of OS is case sensitive
+ file = new File(output.toLowerCase());
+ if (!file.exists()) {
+ throw new AssertionError("Cannot find " + file.getAbsolutePath());
+ }
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/JPackagePath.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.File;
+import java.nio.file.Path;
+
+/**
+ * Helper class which contains functions to get different system
+ * dependent paths used by tests
+ */
+public class JPackagePath {
+
+ // Return path to test src adjusted to location of caller
+ public static String getTestSrcRoot() {
+ return JPackageHelper.TEST_SRC_ROOT;
+ }
+
+ // Return path to calling test
+ public static String getTestSrc() {
+ return JPackageHelper.TEST_SRC;
+ }
+
+ // Returns path to generate test application
+ public static String getApp() {
+ return getApp("test");
+ }
+
+ public static String getApp(String name) {
+ return getAppSL(name, name);
+ }
+
+ // Returns path to generate test application icon
+ public static String getAppIcon() {
+ return getAppIcon("test");
+ }
+
+ public static String getAppIcon(String name) {
+ if (JPackageHelper.isWindows()) {
+ return Path.of("output", name, name + ".ico").toString();
+ } else if (JPackageHelper.isOSX()) {
+ return Path.of("output", name + ".app",
+ "Contents", "Resources", name + ".icns").toString();
+ } else if (JPackageHelper.isLinux()) {
+ return Path.of("output", name, "lib", name + ".png").toString();
+ } else {
+ throw new AssertionError("Cannot detect platform");
+ }
+ }
+
+ // Returns path to generate secondary launcher of given application
+ public static String getAppSL(String sl) {
+ return getAppSL("test", sl);
+ }
+
+ public static String getAppSL(String app, String sl) {
+ if (JPackageHelper.isWindows()) {
+ return Path.of("output", app, sl + ".exe").toString();
+ } else if (JPackageHelper.isOSX()) {
+ return Path.of("output", app + ".app",
+ "Contents", "MacOS", sl).toString();
+ } else if (JPackageHelper.isLinux()) {
+ return Path.of("output", app, "bin", sl).toString();
+ } else {
+ throw new AssertionError("Cannot detect platform");
+ }
+ }
+
+ // Returns path to test application cfg file
+ public static String getAppCfg() {
+ return getAppCfg("test");
+ }
+
+ public static String getAppCfg(String name) {
+ if (JPackageHelper.isWindows()) {
+ return Path.of("output", name, "app", name + ".cfg").toString();
+ } else if (JPackageHelper.isOSX()) {
+ return Path.of("output", name + ".app",
+ "Contents", "app", name + ".cfg").toString();
+ } else if (JPackageHelper.isLinux()) {
+ return Path.of("output", name, "lib", "app", name + ".cfg").toString();
+ } else {
+ throw new AssertionError("Cannot detect platform");
+ }
+ }
+
+ // Returns path including executable to java in image runtime folder
+ public static String getRuntimeJava() {
+ return getRuntimeJava("test");
+ }
+
+ public static String getRuntimeJava(String name) {
+ if (JPackageHelper.isWindows()) {
+ return Path.of(getRuntimeBin(name), "java.exe").toString();
+ }
+ return Path.of(getRuntimeBin(name), "java").toString();
+ }
+
+ // Returns output file name generate by test application
+ public static String getAppOutputFile() {
+ return "appOutput.txt";
+ }
+
+ // Returns path to bin folder in image runtime
+ public static String getRuntimeBin() {
+ return getRuntimeBin("test");
+ }
+
+ public static String getRuntimeBin(String name) {
+ if (JPackageHelper.isWindows()) {
+ return Path.of("output", name, "runtime", "bin").toString();
+ } else if (JPackageHelper.isOSX()) {
+ return Path.of("output", name + ".app",
+ "Contents", "runtime",
+ "Contents", "Home", "bin").toString();
+ } else if (JPackageHelper.isLinux()) {
+ return Path.of("output", name, "lib", "runtime", "bin").toString();
+ } else {
+ throw new AssertionError("Cannot detect platform");
+ }
+ }
+
+ public static String getOSXInstalledApp(String testName) {
+ return File.separator + "Applications"
+ + File.separator + testName + ".app"
+ + File.separator + "Contents"
+ + File.separator + "MacOS"
+ + File.separator + testName;
+ }
+
+ public static String getOSXInstalledApp(String subDir, String testName) {
+ return File.separator + "Applications"
+ + File.separator + subDir
+ + File.separator + testName + ".app"
+ + File.separator + "Contents"
+ + File.separator + "MacOS"
+ + File.separator + testName;
+ }
+
+ // Returs path to test license file
+ public static String getLicenseFilePath() {
+ String path = JPackagePath.getTestSrcRoot()
+ + File.separator + "resources"
+ + File.separator + "license.txt";
+
+ return path;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Annotations.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jpackage.test;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+public class Annotations {
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ public @interface BeforeEach {
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ public @interface AfterEach {
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ public @interface Test {
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ @Repeatable(ParameterGroup.class)
+ public @interface Parameter {
+
+ String[] value();
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ public @interface ParameterGroup {
+
+ Parameter[] value();
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ public @interface Parameters {
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/CfgFile.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jpackage.test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+public final class CfgFile {
+ public String getValue(String section, String key) {
+ Objects.requireNonNull(section);
+ Objects.requireNonNull(key);
+
+ Map<String, String> entries = data.get(section);
+ TKit.assertTrue(entries != null, String.format(
+ "Check section [%s] is found in [%s] cfg file", section, id));
+
+ String value = entries.get(key);
+ TKit.assertNotNull(value, String.format(
+ "Check key [%s] is found in [%s] section of [%s] cfg file", key,
+ section, id));
+
+ return value;
+ }
+
+ private CfgFile(Map<String, Map<String, String>> data, String id) {
+ this.data = data;
+ this.id = id;
+ }
+
+ public static CfgFile readFromFile(Path path) throws IOException {
+ TKit.trace(String.format("Read [%s] jpackage cfg file", path));
+
+ final Pattern sectionBeginRegex = Pattern.compile( "\\s*\\[([^]]*)\\]\\s*");
+ final Pattern keyRegex = Pattern.compile( "\\s*([^=]*)=(.*)" );
+
+ Map<String, Map<String, String>> result = new HashMap<>();
+
+ String currentSectionName = null;
+ Map<String, String> currentSection = new HashMap<>();
+ for (String line : Files.readAllLines(path)) {
+ Matcher matcher = sectionBeginRegex.matcher(line);
+ if (matcher.find()) {
+ if (currentSectionName != null) {
+ result.put(currentSectionName, Collections.unmodifiableMap(
+ new HashMap<>(currentSection)));
+ }
+ currentSectionName = matcher.group(1);
+ currentSection.clear();
+ continue;
+ }
+
+ matcher = keyRegex.matcher(line);
+ if (matcher.find()) {
+ currentSection.put(matcher.group(1), matcher.group(2));
+ continue;
+ }
+ }
+
+ if (!currentSection.isEmpty()) {
+ result.put("", Collections.unmodifiableMap(currentSection));
+ }
+
+ return new CfgFile(Collections.unmodifiableMap(result), path.toString());
+ }
+
+ private final Map<String, Map<String, String>> data;
+ private final String id;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/CommandArguments.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jpackage.test;
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class CommandArguments<T> {
+
+ CommandArguments() {
+ args = new ArrayList<>();
+ }
+
+ final public T addArgument(String v) {
+ args.add(v);
+ return (T) this;
+ }
+
+ final public T addArguments(List<String> v) {
+ args.addAll(v);
+ return (T) this;
+ }
+
+ final public T addArgument(Path v) {
+ return addArgument(v.toString());
+ }
+
+ final public T addArguments(String... v) {
+ return addArguments(Arrays.asList(v));
+ }
+
+ final public T addPathArguments(List<Path> v) {
+ return addArguments(v.stream().map((p) -> p.toString()).collect(
+ Collectors.toList()));
+ }
+
+ final public List<String> getAllArguments() {
+ return List.copyOf(args);
+ }
+
+ protected void verifyMutable() {
+ if (!isMutable()) {
+ throw new UnsupportedOperationException(
+ "Attempt to modify immutable object");
+ }
+ }
+
+ protected boolean isMutable() {
+ return true;
+ }
+
+ protected List<String> args;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Executor.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,364 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jpackage.test;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.StringReader;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.regex.Pattern;
+import java.util.spi.ToolProvider;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import jdk.jpackage.test.Functional.ThrowingSupplier;
+
+public final class Executor extends CommandArguments<Executor> {
+
+ public Executor() {
+ saveOutputType = new HashSet<>(Set.of(SaveOutputType.NONE));
+ }
+
+ public Executor setExecutable(String v) {
+ return setExecutable(Path.of(v));
+ }
+
+ public Executor setExecutable(Path v) {
+ executable = Objects.requireNonNull(v);
+ toolProvider = null;
+ return this;
+ }
+
+ public Executor setToolProvider(ToolProvider v) {
+ toolProvider = Objects.requireNonNull(v);
+ executable = null;
+ return this;
+ }
+
+ public Executor setToolProvider(JavaTool v) {
+ return setToolProvider(v.asToolProvider());
+ }
+
+ public Executor setDirectory(Path v) {
+ directory = v;
+ return this;
+ }
+
+ public Executor setExecutable(JavaTool v) {
+ return setExecutable(v.getPath());
+ }
+
+ /**
+ * Configures this instance to save full output that command will produce.
+ * This function is mutual exclusive with
+ * saveFirstLineOfOutput() function.
+ *
+ * @return this
+ */
+ public Executor saveOutput() {
+ saveOutputType.remove(SaveOutputType.FIRST_LINE);
+ saveOutputType.add(SaveOutputType.FULL);
+ return this;
+ }
+
+ /**
+ * Configures how to save output that command will produce. If
+ * <code>v</code> is <code>true</code>, the function call is equivalent to
+ * <code>saveOutput()</code> call. If <code>v</code> is <code>false</code>,
+ * the function will result in not preserving command output.
+ *
+ * @return this
+ */
+ public Executor saveOutput(boolean v) {
+ if (v) {
+ saveOutput();
+ } else {
+ saveOutputType.remove(SaveOutputType.FIRST_LINE);
+ saveOutputType.remove(SaveOutputType.FULL);
+ }
+ return this;
+ }
+
+ /**
+ * Configures this instance to save only the first line out output that
+ * command will produce. This function is mutual exclusive with
+ * saveOutput() function.
+ *
+ * @return this
+ */
+ public Executor saveFirstLineOfOutput() {
+ saveOutputType.add(SaveOutputType.FIRST_LINE);
+ saveOutputType.remove(SaveOutputType.FULL);
+ return this;
+ }
+
+ /**
+ * Configures this instance to dump all output that command will produce to
+ * System.out and System.err. Can be used together with saveOutput() and
+ * saveFirstLineOfOutput() to save command output and also copy it in the
+ * default output streams.
+ *
+ * @return this
+ */
+ public Executor dumpOutput() {
+ return dumpOutput(true);
+ }
+
+ public Executor dumpOutput(boolean v) {
+ if (v) {
+ saveOutputType.add(SaveOutputType.DUMP);
+ } else {
+ saveOutputType.remove(SaveOutputType.DUMP);
+ }
+ return this;
+ }
+
+ public class Result {
+
+ Result(int exitCode) {
+ this.exitCode = exitCode;
+ }
+
+ public String getFirstLineOfOutput() {
+ return output.get(0);
+ }
+
+ public List<String> getOutput() {
+ return output;
+ }
+
+ public String getPrintableCommandLine() {
+ return Executor.this.getPrintableCommandLine();
+ }
+
+ public Result assertExitCodeIs(int expectedExitCode) {
+ TKit.assertEquals(expectedExitCode, exitCode, String.format(
+ "Check command %s exited with %d code",
+ getPrintableCommandLine(), expectedExitCode));
+ return this;
+ }
+
+ public Result assertExitCodeIsZero() {
+ return assertExitCodeIs(0);
+ }
+
+ final int exitCode;
+ private List<String> output;
+ }
+
+ public Result execute() {
+ if (toolProvider != null && directory != null) {
+ throw new IllegalArgumentException(
+ "Can't change directory when using tool provider");
+ }
+
+ return ThrowingSupplier.toSupplier(() -> {
+ if (toolProvider != null) {
+ return runToolProvider();
+ }
+
+ if (executable != null) {
+ return runExecutable();
+ }
+
+ throw new IllegalStateException("No command to execute");
+ }).get();
+ }
+
+ public String executeAndGetFirstLineOfOutput() {
+ return saveFirstLineOfOutput().execute().assertExitCodeIsZero().getFirstLineOfOutput();
+ }
+
+ public List<String> executeAndGetOutput() {
+ return saveOutput().execute().assertExitCodeIsZero().getOutput();
+ }
+
+ private boolean withSavedOutput() {
+ return saveOutputType.contains(SaveOutputType.FULL) || saveOutputType.contains(
+ SaveOutputType.FIRST_LINE);
+ }
+
+ private Path executablePath() {
+ if (directory == null || executable.isAbsolute()) {
+ return executable;
+ }
+
+ // If relative path to executable is used it seems to be broken when
+ // ProcessBuilder changes the directory. On Windows it changes the
+ // directory first and on Linux it looks up for executable before
+ // changing the directory. So to stay of safe side, use absolute path
+ // to executable.
+ return executable.toAbsolutePath();
+ }
+
+ private Result runExecutable() throws IOException, InterruptedException {
+ List<String> command = new ArrayList<>();
+ command.add(executablePath().toString());
+ command.addAll(args);
+ ProcessBuilder builder = new ProcessBuilder(command);
+ StringBuilder sb = new StringBuilder(getPrintableCommandLine());
+ if (withSavedOutput()) {
+ builder.redirectErrorStream(true);
+ sb.append("; save output");
+ } else if (saveOutputType.contains(SaveOutputType.DUMP)) {
+ builder.inheritIO();
+ sb.append("; inherit I/O");
+ } else {
+ builder.redirectError(ProcessBuilder.Redirect.DISCARD);
+ builder.redirectOutput(ProcessBuilder.Redirect.DISCARD);
+ sb.append("; discard I/O");
+ }
+ if (directory != null) {
+ builder.directory(directory.toFile());
+ sb.append(String.format("; in directory [%s]", directory));
+ }
+
+ TKit.trace("Execute " + sb.toString() + "...");
+ Process process = builder.start();
+
+ List<String> outputLines = null;
+ if (withSavedOutput()) {
+ try (BufferedReader outReader = new BufferedReader(
+ new InputStreamReader(process.getInputStream()))) {
+ if (saveOutputType.contains(SaveOutputType.DUMP)
+ || saveOutputType.contains(SaveOutputType.FULL)) {
+ outputLines = outReader.lines().collect(Collectors.toList());
+ } else {
+ outputLines = Arrays.asList(
+ outReader.lines().findFirst().orElse(null));
+ }
+ } finally {
+ if (saveOutputType.contains(SaveOutputType.DUMP) && outputLines != null) {
+ outputLines.stream().forEach(System.out::println);
+ if (saveOutputType.contains(SaveOutputType.FIRST_LINE)) {
+ // Pick the first line of saved output if there is one
+ for (String line: outputLines) {
+ outputLines = List.of(line);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ Result reply = new Result(process.waitFor());
+ TKit.trace("Done. Exit code: " + reply.exitCode);
+
+ if (outputLines != null) {
+ reply.output = Collections.unmodifiableList(outputLines);
+ }
+ return reply;
+ }
+
+ private Result runToolProvider(PrintStream out, PrintStream err) {
+ TKit.trace("Execute " + getPrintableCommandLine() + "...");
+ Result reply = new Result(toolProvider.run(out, err, args.toArray(
+ String[]::new)));
+ TKit.trace("Done. Exit code: " + reply.exitCode);
+ return reply;
+ }
+
+
+ private Result runToolProvider() throws IOException {
+ if (!withSavedOutput()) {
+ if (saveOutputType.contains(SaveOutputType.DUMP)) {
+ return runToolProvider(System.out, System.err);
+ }
+
+ PrintStream nullPrintStream = new PrintStream(new OutputStream() {
+ @Override
+ public void write(int b) {
+ // Nop
+ }
+ });
+ return runToolProvider(nullPrintStream, nullPrintStream);
+ }
+
+ try (ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(buf)) {
+ Result reply = runToolProvider(ps, ps);
+ ps.flush();
+ try (BufferedReader bufReader = new BufferedReader(new StringReader(
+ buf.toString()))) {
+ if (saveOutputType.contains(SaveOutputType.FIRST_LINE)) {
+ String firstLine = bufReader.lines().findFirst().orElse(null);
+ if (firstLine != null) {
+ reply.output = List.of(firstLine);
+ }
+ } else if (saveOutputType.contains(SaveOutputType.FULL)) {
+ reply.output = bufReader.lines().collect(
+ Collectors.toUnmodifiableList());
+ }
+
+ if (saveOutputType.contains(SaveOutputType.DUMP)) {
+ Stream<String> lines;
+ if (saveOutputType.contains(SaveOutputType.FULL)) {
+ lines = reply.output.stream();
+ } else {
+ lines = bufReader.lines();
+ }
+ lines.forEach(System.out::println);
+ }
+ }
+ return reply;
+ }
+ }
+
+ public String getPrintableCommandLine() {
+ final String exec;
+ String format = "[%s](%d)";
+ if (toolProvider == null && executable == null) {
+ exec = "<null>";
+ } else if (toolProvider != null) {
+ format = "tool provider " + format;
+ exec = toolProvider.name();
+ } else {
+ exec = executablePath().toString();
+ }
+
+ return String.format(format, printCommandLine(exec, args),
+ args.size() + 1);
+ }
+
+ private static String printCommandLine(String executable, List<String> args) {
+ // Want command line printed in a way it can be easily copy/pasted
+ // to be executed manally
+ Pattern regex = Pattern.compile("\\s");
+ return Stream.concat(Stream.of(executable), args.stream()).map(
+ v -> (v.isEmpty() || regex.matcher(v).find()) ? "\"" + v + "\"" : v).collect(
+ Collectors.joining(" "));
+ }
+
+ private ToolProvider toolProvider;
+ private Path executable;
+ private Set<SaveOutputType> saveOutputType;
+ private Path directory;
+
+ private static enum SaveOutputType {
+ NONE, FULL, FIRST_LINE, DUMP
+ };
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/FileAssociations.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jpackage.test;
+
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+
+
+final public class FileAssociations {
+ public FileAssociations(String faSuffixName) {
+ suffixName = faSuffixName;
+ setFilename("fa");
+ setDescription("jpackage test extention");
+ }
+
+ private void createFile() {
+ Map<String, String> entries = new HashMap<>(Map.of(
+ "extension", suffixName,
+ "mime-type", getMime(),
+ "description", description
+ ));
+ if (icon != null) {
+ if (TKit.isWindows()) {
+ entries.put("icon", icon.toString().replace("\\", "/"));
+ } else {
+ entries.put("icon", icon.toString());
+ }
+ }
+ TKit.createPropertiesFile(file, entries);
+ }
+
+ public FileAssociations setFilename(String v) {
+ file = TKit.workDir().resolve(v + ".properties");
+ return this;
+ }
+
+ public FileAssociations setDescription(String v) {
+ description = v;
+ return this;
+ }
+
+ public FileAssociations setIcon(Path v) {
+ icon = v;
+ return this;
+ }
+
+ Path getPropertiesFile() {
+ return file;
+ }
+
+ String getSuffix() {
+ return "." + suffixName;
+ }
+
+ String getMime() {
+ return "application/x-jpackage-" + suffixName;
+ }
+
+ public void applyTo(PackageTest test) {
+ test.notForTypes(PackageType.MAC_DMG, () -> {
+ test.addInitializer(cmd -> {
+ createFile();
+ cmd.addArguments("--file-associations", getPropertiesFile());
+ });
+ test.addHelloAppFileAssociationsVerifier(this);
+ });
+ }
+
+ private Path file;
+ final private String suffixName;
+ private String description;
+ private Path icon;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Functional.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jpackage.test;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+
+public class Functional {
+ @FunctionalInterface
+ public interface ThrowingConsumer<T> {
+ void accept(T t) throws Throwable;
+
+ public static <T> Consumer<T> toConsumer(ThrowingConsumer<T> v) {
+ return o -> {
+ try {
+ v.accept(o);
+ } catch (Throwable ex) {
+ rethrowUnchecked(ex);
+ }
+ };
+ }
+ }
+
+ @FunctionalInterface
+ public interface ThrowingSupplier<T> {
+ T get() throws Throwable;
+
+ public static <T> Supplier<T> toSupplier(ThrowingSupplier<T> v) {
+ return () -> {
+ try {
+ return v.get();
+ } catch (Throwable ex) {
+ rethrowUnchecked(ex);
+ }
+ // Unreachable
+ return null;
+ };
+ }
+ }
+
+ @FunctionalInterface
+ public interface ThrowingFunction<T, R> {
+ R apply(T t) throws Throwable;
+
+ public static <T, R> Function<T, R> toFunction(ThrowingFunction<T, R> v) {
+ return (t) -> {
+ try {
+ return v.apply(t);
+ } catch (Throwable ex) {
+ rethrowUnchecked(ex);
+ }
+ // Unreachable
+ return null;
+ };
+ }
+ }
+
+ @FunctionalInterface
+ public interface ThrowingRunnable {
+ void run() throws Throwable;
+
+ public static Runnable toRunnable(ThrowingRunnable v) {
+ return () -> {
+ try {
+ v.run();
+ } catch (Throwable ex) {
+ rethrowUnchecked(ex);
+ }
+ };
+ }
+ }
+
+ public static <T> Supplier<T> identity(Supplier<T> v) {
+ return v;
+ }
+
+ public static <T> Consumer<T> identity(Consumer<T> v) {
+ return v;
+ }
+
+ public static Runnable identity(Runnable v) {
+ return v;
+ }
+
+ public static <T, R> Function<T, R> identity(Function<T, R> v) {
+ return v;
+ }
+
+ public static <T, R> Function<T, R> identityFunction(Function<T, R> v) {
+ return v;
+ }
+
+ public static <T> Predicate<T> identity(Predicate<T> v) {
+ return v;
+ }
+
+ public static <T> Predicate<T> identityPredicate(Predicate<T> v) {
+ return v;
+ }
+
+ public static class ExceptionBox extends RuntimeException {
+ public ExceptionBox(Throwable throwable) {
+ super(throwable);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static void rethrowUnchecked(Throwable throwable) throws ExceptionBox {
+ if (throwable instanceof ExceptionBox) {
+ throw (ExceptionBox)throwable;
+ }
+
+ if (throwable instanceof InvocationTargetException) {
+ new ExceptionBox(throwable.getCause());
+ }
+
+ throw new ExceptionBox(throwable);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/HelloApp.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jpackage.test;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import jdk.jpackage.test.Functional.ThrowingFunction;
+import jdk.jpackage.test.Functional.ThrowingSupplier;
+
+public class HelloApp {
+
+ HelloApp(JavaAppDesc appDesc) {
+ if (appDesc == null) {
+ this.appDesc = createDefaltAppDesc();
+ } else {
+ this.appDesc = appDesc;
+ }
+ }
+
+ private JarBuilder prepareSources(Path srcDir) throws IOException {
+ final String qualifiedClassName = appDesc.className();
+
+ final String className = qualifiedClassName.substring(
+ qualifiedClassName.lastIndexOf('.') + 1);
+ final String packageName = appDesc.packageName();
+
+ final Path srcFile = srcDir.resolve(Path.of(String.join(
+ File.separator, qualifiedClassName.split("\\.")) + ".java"));
+ Files.createDirectories(srcFile.getParent());
+
+ JarBuilder jarBuilder = createJarBuilder().addSourceFile(srcFile);
+ final String moduleName = appDesc.moduleName();
+ if (moduleName != null) {
+ Path moduleInfoFile = srcDir.resolve("module-info.java");
+ TKit.createTextFile(moduleInfoFile, List.of(
+ String.format("module %s {", moduleName),
+ String.format(" exports %s;", packageName),
+ " requires java.desktop;",
+ "}"
+ ));
+ jarBuilder.addSourceFile(moduleInfoFile);
+ jarBuilder.setModuleVersion(appDesc.moduleVersion());
+ }
+
+ // Add package directive and replace class name in java source file.
+ // Works with simple test Hello.java.
+ // Don't expect too much from these regexps!
+ Pattern classNameRegex = Pattern.compile("\\bHello\\b");
+ Pattern classDeclaration = Pattern.compile(
+ "(^.*\\bclass\\s+)\\bHello\\b(.*$)");
+ Pattern importDirective = Pattern.compile(
+ "(?<=import (?:static )?+)[^;]+");
+ AtomicBoolean classDeclared = new AtomicBoolean();
+ AtomicBoolean packageInserted = new AtomicBoolean(packageName == null);
+
+ var packageInserter = Functional.identityFunction((line) -> {
+ packageInserted.setPlain(true);
+ return String.format("package %s;%s%s", packageName,
+ System.lineSeparator(), line);
+ });
+
+ Files.write(srcFile, Files.readAllLines(HELLO_JAVA).stream().map(line -> {
+ Matcher m;
+ if (classDeclared.getPlain()) {
+ if ((m = classNameRegex.matcher(line)).find()) {
+ line = m.replaceAll(className);
+ }
+ return line;
+ }
+
+ if (!packageInserted.getPlain() && importDirective.matcher(line).find()) {
+ line = packageInserter.apply(line);
+ } else if ((m = classDeclaration.matcher(line)).find()) {
+ classDeclared.setPlain(true);
+ line = m.group(1) + className + m.group(2);
+ if (!packageInserted.getPlain()) {
+ line = packageInserter.apply(line);
+ }
+ }
+ return line;
+ }).collect(Collectors.toList()));
+
+ return jarBuilder;
+ }
+
+ private JarBuilder createJarBuilder() {
+ JarBuilder builder = new JarBuilder();
+ if (appDesc.jarWithMainClass()) {
+ builder.setMainClass(appDesc.className());
+ }
+ return builder;
+ }
+
+ void addTo(JPackageCommand cmd) {
+ final String moduleName = appDesc.moduleName();
+ final String jarFileName = appDesc.jarFileName();
+ final String qualifiedClassName = appDesc.className();
+
+ if (moduleName != null && appDesc.packageName() == null) {
+ throw new IllegalArgumentException(String.format(
+ "Module [%s] with default package", moduleName));
+ }
+
+ if (moduleName == null && CLASS_NAME.equals(qualifiedClassName)) {
+ // Use Hello.java as is.
+ cmd.addAction((self) -> {
+ Path jarFile = self.inputDir().resolve(jarFileName);
+ createJarBuilder().setOutputJar(jarFile).addSourceFile(
+ HELLO_JAVA).create();
+ });
+ } else {
+ cmd.addAction((self) -> {
+ final Path jarFile;
+ if (moduleName == null) {
+ jarFile = self.inputDir().resolve(jarFileName);
+ } else {
+ // `--module-path` option should be set by the moment
+ // when this action is being executed.
+ jarFile = Path.of(self.getArgumentValue("--module-path",
+ () -> self.inputDir().toString()), jarFileName);
+ Files.createDirectories(jarFile.getParent());
+ }
+
+ TKit.withTempDirectory("src",
+ workDir -> prepareSources(workDir).setOutputJar(jarFile).create());
+ });
+ }
+
+ if (moduleName == null) {
+ cmd.addArguments("--main-jar", jarFileName);
+ cmd.addArguments("--main-class", qualifiedClassName);
+ } else {
+ cmd.addArguments("--module-path", TKit.workDir().resolve(
+ "input-modules"));
+ cmd.addArguments("--module", String.join("/", moduleName,
+ qualifiedClassName));
+ // For modular app assume nothing will go in input directory and thus
+ // nobody will create input directory, so remove corresponding option
+ // from jpackage command line.
+ cmd.removeArgumentWithValue("--input");
+ }
+ if (TKit.isWindows()) {
+ cmd.addArguments("--win-console");
+ }
+ }
+
+ static JavaAppDesc createDefaltAppDesc() {
+ return new JavaAppDesc().setClassName(CLASS_NAME).setJarFileName(
+ "hello.jar");
+ }
+
+ static void verifyOutputFile(Path outputFile, List<String> args) {
+ if (!outputFile.isAbsolute()) {
+ verifyOutputFile(outputFile.toAbsolutePath().normalize(), args);
+ return;
+ }
+
+ TKit.assertFileExists(outputFile);
+
+ List<String> contents = ThrowingSupplier.toSupplier(
+ () -> Files.readAllLines(outputFile)).get();
+
+ List<String> expected = new ArrayList<>(List.of(
+ "jpackage test application",
+ String.format("args.length: %d", args.size())
+ ));
+ expected.addAll(args);
+
+ TKit.assertStringListEquals(expected, contents, String.format(
+ "Check contents of [%s] file", outputFile));
+ }
+
+ public static void executeLauncherAndVerifyOutput(JPackageCommand cmd) {
+ final Path launcherPath = cmd.appLauncherPath();
+ if (!cmd.isFakeRuntime(String.format("Not running [%s] launcher",
+ launcherPath))) {
+ executeAndVerifyOutput(launcherPath, cmd.getAllArgumentValues(
+ "--arguments"));
+ }
+ }
+
+ public static void executeAndVerifyOutput(Path helloAppLauncher,
+ String... defaultLauncherArgs) {
+ executeAndVerifyOutput(helloAppLauncher, List.of(defaultLauncherArgs));
+ }
+
+ public static void executeAndVerifyOutput(Path helloAppLauncher,
+ List<String> defaultLauncherArgs) {
+ // Output file will be created in the current directory.
+ Path outputFile = TKit.workDir().resolve(OUTPUT_FILENAME);
+ ThrowingFunction.toFunction(Files::deleteIfExists).apply(outputFile);
+ new Executor()
+ .setDirectory(outputFile.getParent())
+ .setExecutable(helloAppLauncher)
+ .dumpOutput()
+ .execute()
+ .assertExitCodeIsZero();
+
+ verifyOutputFile(outputFile, defaultLauncherArgs);
+ }
+
+ final static String OUTPUT_FILENAME = "appOutput.txt";
+
+ private final JavaAppDesc appDesc;
+
+ private static final Path HELLO_JAVA = TKit.TEST_SRC_ROOT.resolve(
+ "apps/image/Hello.java");
+
+ private final static String CLASS_NAME = HELLO_JAVA.getFileName().toString().split(
+ "\\.", 2)[0];
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,732 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jpackage.test;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.SecureRandom;
+import java.util.*;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import jdk.incubator.jpackage.internal.ApplicationLayout;
+import jdk.jpackage.test.Functional.ThrowingConsumer;
+import jdk.jpackage.test.Functional.ThrowingFunction;
+
+/**
+ * jpackage command line with prerequisite actions. Prerequisite actions can be
+ * anything. The simplest is to compile test application and pack in a jar for
+ * use on jpackage command line.
+ */
+public final class JPackageCommand extends CommandArguments<JPackageCommand> {
+
+ public JPackageCommand() {
+ actions = new ArrayList<>();
+ }
+
+ public JPackageCommand(JPackageCommand cmd) {
+ this();
+ args.addAll(cmd.args);
+ withToolProvider = cmd.withToolProvider;
+ saveConsoleOutput = cmd.saveConsoleOutput;
+ suppressOutput = cmd.suppressOutput;
+ ignoreDefaultRuntime = cmd.ignoreDefaultRuntime;
+ immutable = cmd.immutable;
+ actionsExecuted = cmd.actionsExecuted;
+ }
+
+ JPackageCommand createImmutableCopy() {
+ JPackageCommand reply = new JPackageCommand(this);
+ reply.immutable = true;
+ return reply;
+ }
+
+ public JPackageCommand setArgumentValue(String argName, String newValue) {
+ verifyMutable();
+
+ String prevArg = null;
+ ListIterator<String> it = args.listIterator();
+ while (it.hasNext()) {
+ String value = it.next();
+ if (prevArg != null && prevArg.equals(argName)) {
+ if (newValue != null) {
+ it.set(newValue);
+ } else {
+ it.remove();
+ it.previous();
+ it.remove();
+ }
+ return this;
+ }
+ prevArg = value;
+ }
+
+ if (newValue != null) {
+ addArguments(argName, newValue);
+ }
+
+ return this;
+ }
+
+ public JPackageCommand setArgumentValue(String argName, Path newValue) {
+ return setArgumentValue(argName, newValue.toString());
+ }
+
+ public JPackageCommand removeArgumentWithValue(String argName) {
+ return setArgumentValue(argName, (String)null);
+ }
+
+ public JPackageCommand removeArgument(String argName) {
+ args = args.stream().filter(arg -> !arg.equals(argName)).collect(
+ Collectors.toList());
+ return this;
+ }
+
+ public boolean hasArgument(String argName) {
+ return args.contains(argName);
+ }
+
+ public <T> T getArgumentValue(String argName,
+ Function<JPackageCommand, T> defaultValueSupplier,
+ Function<String, T> stringConverter) {
+ String prevArg = null;
+ for (String arg : args) {
+ if (prevArg != null && prevArg.equals(argName)) {
+ return stringConverter.apply(arg);
+ }
+ prevArg = arg;
+ }
+ if (defaultValueSupplier != null) {
+ return defaultValueSupplier.apply(this);
+ }
+ return null;
+ }
+
+ public String getArgumentValue(String argName,
+ Function<JPackageCommand, String> defaultValueSupplier) {
+ return getArgumentValue(argName, defaultValueSupplier, v -> v);
+ }
+
+ public <T> T getArgumentValue(String argName,
+ Supplier<T> defaultValueSupplier,
+ Function<String, T> stringConverter) {
+ return getArgumentValue(argName, (unused) -> defaultValueSupplier.get(),
+ stringConverter);
+ }
+
+ public String getArgumentValue(String argName,
+ Supplier<String> defaultValueSupplier) {
+ return getArgumentValue(argName, defaultValueSupplier, v -> v);
+ }
+
+ public String getArgumentValue(String argName) {
+ return getArgumentValue(argName, (Supplier<String>)null);
+ }
+
+ public String[] getAllArgumentValues(String argName) {
+ List<String> values = new ArrayList<>();
+ String prevArg = null;
+ for (String arg : args) {
+ if (prevArg != null && prevArg.equals(argName)) {
+ values.add(arg);
+ }
+ prevArg = arg;
+ }
+ return values.toArray(String[]::new);
+ }
+
+ public JPackageCommand addArguments(String name, Path value) {
+ return addArguments(name, value.toString());
+ }
+
+ public boolean isImagePackageType() {
+ return PackageType.IMAGE == getArgumentValue("--type",
+ () -> null, PACKAGE_TYPES::get);
+ }
+
+ public PackageType packageType() {
+ // Don't try to be in sync with jpackage defaults. Keep it simple:
+ // if no `--type` explicitely set on the command line, consider
+ // this is operator's fault.
+ return getArgumentValue("--type",
+ () -> {
+ throw new IllegalStateException("Package type not set");
+ }, PACKAGE_TYPES::get);
+ }
+
+ public Path outputDir() {
+ return getArgumentValue("--dest", () -> Path.of("."), Path::of);
+ }
+
+ public Path inputDir() {
+ return getArgumentValue("--input", () -> null, Path::of);
+ }
+
+ public String version() {
+ return getArgumentValue("--app-version", () -> "1.0");
+ }
+
+ public String name() {
+ return getArgumentValue("--name", () -> getArgumentValue("--main-class"));
+ }
+
+ public boolean isRuntime() {
+ return hasArgument("--runtime-image")
+ && !hasArgument("--main-jar")
+ && !hasArgument("--module")
+ && !hasArgument("--app-image");
+ }
+
+ public JPackageCommand setDefaultInputOutput() {
+ addArguments("--input", TKit.defaultInputDir());
+ addArguments("--dest", TKit.defaultOutputDir());
+ return this;
+ }
+
+ public JPackageCommand setFakeRuntime() {
+ verifyMutable();
+
+ ThrowingConsumer<Path> createBulkFile = path -> {
+ Files.createDirectories(path.getParent());
+ try (FileOutputStream out = new FileOutputStream(path.toFile())) {
+ byte[] bytes = new byte[4 * 1024];
+ new SecureRandom().nextBytes(bytes);
+ out.write(bytes);
+ }
+ };
+
+ addAction(cmd -> {
+ Path fakeRuntimeDir = TKit.workDir().resolve("fake_runtime");
+
+ TKit.trace(String.format("Init fake runtime in [%s] directory",
+ fakeRuntimeDir));
+
+ Files.createDirectories(fakeRuntimeDir);
+
+ if (TKit.isWindows() || TKit.isLinux()) {
+ // Needed to make WindowsAppBundler happy as it copies MSVC dlls
+ // from `bin` directory.
+ // Need to make the code in rpm spec happy as it assumes there is
+ // always something in application image.
+ fakeRuntimeDir.resolve("bin").toFile().mkdir();
+ }
+
+ if (TKit.isOSX()) {
+ // Make MacAppImageBuilder happy
+ createBulkFile.accept(fakeRuntimeDir.resolve(Path.of(
+ "Contents/Home/lib/jli/libjli.dylib")));
+ }
+
+ // Mak sure fake runtime takes some disk space.
+ // Package bundles with 0KB size are unexpected and considered
+ // an error by PackageTest.
+ createBulkFile.accept(fakeRuntimeDir.resolve(Path.of("bin", "bulk")));
+
+ cmd.addArguments("--runtime-image", fakeRuntimeDir);
+ });
+
+ return this;
+ }
+
+ JPackageCommand addAction(ThrowingConsumer<JPackageCommand> action) {
+ verifyMutable();
+ actions.add(ThrowingConsumer.toConsumer(action));
+ return this;
+ }
+
+ /**
+ * Shorthand for {@code helloAppImage(null)}.
+ */
+ public static JPackageCommand helloAppImage() {
+ JavaAppDesc javaAppDesc = null;
+ return helloAppImage(javaAppDesc);
+ }
+
+ /**
+ * Creates new JPackageCommand instance configured with the test Java app.
+ * For the explanation of `javaAppDesc` parameter, see documentation for
+ * #JavaAppDesc.parse() method.
+ *
+ * @param javaAppDesc Java application description
+ * @return this
+ */
+ public static JPackageCommand helloAppImage(String javaAppDesc) {
+ final JavaAppDesc appDesc;
+ if (javaAppDesc == null) {
+ appDesc = null;
+ } else {
+ appDesc = JavaAppDesc.parse(javaAppDesc);
+ }
+ return helloAppImage(appDesc);
+ }
+
+ public static JPackageCommand helloAppImage(JavaAppDesc javaAppDesc) {
+ JPackageCommand cmd = new JPackageCommand();
+ cmd.setDefaultInputOutput().setDefaultAppName();
+ PackageType.IMAGE.applyTo(cmd);
+ new HelloApp(javaAppDesc).addTo(cmd);
+ return cmd;
+ }
+
+ public JPackageCommand setPackageType(PackageType type) {
+ verifyMutable();
+ type.applyTo(this);
+ return this;
+ }
+
+ JPackageCommand setDefaultAppName() {
+ return addArguments("--name", TKit.getCurrentDefaultAppName());
+ }
+
+ /**
+ * Returns path to output bundle of configured jpackage command.
+ *
+ * If this is build image command, returns path to application image directory.
+ */
+ public Path outputBundle() {
+ final String bundleName;
+ if (isImagePackageType()) {
+ String dirName = name();
+ if (TKit.isOSX()) {
+ dirName = dirName + ".app";
+ }
+ bundleName = dirName;
+ } else if (TKit.isLinux()) {
+ bundleName = LinuxHelper.getBundleName(this);
+ } else if (TKit.isWindows()) {
+ bundleName = WindowsHelper.getBundleName(this);
+ } else if (TKit.isOSX()) {
+ bundleName = MacHelper.getBundleName(this);
+ } else {
+ throw TKit.throwUnknownPlatformError();
+ }
+
+ return outputDir().resolve(bundleName);
+ }
+
+ /**
+ * Returns application layout.
+ *
+ * If this is build image command, returns application image layout of the
+ * output bundle relative to output directory. Otherwise returns layout of
+ * installed application relative to the root directory.
+ *
+ * If this command builds Java runtime, not an application, returns
+ * corresponding layout.
+ */
+ public ApplicationLayout appLayout() {
+ final ApplicationLayout layout;
+ if (isRuntime()) {
+ layout = ApplicationLayout.javaRuntime();
+ } else {
+ layout = ApplicationLayout.platformAppImage();
+ }
+
+ if (isImagePackageType()) {
+ return layout.resolveAt(outputBundle());
+ }
+
+ return layout.resolveAt(appInstallationDirectory());
+ }
+
+ /**
+ * Returns path to directory where application will be installed or null if
+ * this is build image command.
+ *
+ * E.g. on Linux for app named Foo default the function will return
+ * `/opt/foo`
+ */
+ public Path appInstallationDirectory() {
+ if (isImagePackageType()) {
+ return null;
+ }
+
+ if (TKit.isLinux()) {
+ if (isRuntime()) {
+ // Not fancy, but OK.
+ return Path.of(getArgumentValue("--install-dir", () -> "/opt"),
+ LinuxHelper.getPackageName(this));
+ }
+
+ // Launcher is in "bin" subfolder of the installation directory.
+ return appLauncherPath().getParent().getParent();
+ }
+
+ if (TKit.isWindows()) {
+ return WindowsHelper.getInstallationDirectory(this);
+ }
+
+ if (TKit.isOSX()) {
+ return MacHelper.getInstallationDirectory(this);
+ }
+
+ throw TKit.throwUnknownPlatformError();
+ }
+
+ /**
+ * Returns path to application's Java runtime.
+ * If the command will package Java runtime only, returns correct path to
+ * runtime directory.
+ *
+ * E.g.:
+ * [jpackage --name Foo --type rpm] -> `/opt/foo/lib/runtime`
+ * [jpackage --name Foo --type app-image --dest bar] -> `bar/Foo/lib/runtime`
+ * [jpackage --name Foo --type rpm --runtime-image java] -> `/opt/foo`
+ */
+ public Path appRuntimeDirectory() {
+ return appLayout().runtimeDirectory();
+ }
+
+ /**
+ * Returns path for application launcher with the given name.
+ *
+ * E.g.: [jpackage --name Foo --type rpm] -> `/opt/foo/bin/Foo`
+ * [jpackage --name Foo --type app-image --dest bar] ->
+ * `bar/Foo/bin/Foo`
+ *
+ * @param launcherName name of launcher or {@code null} for the main
+ * launcher
+ *
+ * @throws IllegalArgumentException if the command is configured for
+ * packaging Java runtime
+ */
+ public Path appLauncherPath(String launcherName) {
+ verifyNotRuntime();
+ if (launcherName == null) {
+ launcherName = name();
+ }
+
+ if (TKit.isWindows()) {
+ launcherName = launcherName + ".exe";
+ }
+
+ if (isImagePackageType()) {
+ return appLayout().launchersDirectory().resolve(launcherName);
+ }
+
+ if (TKit.isLinux()) {
+ return LinuxHelper.getLauncherPath(this).getParent().resolve(launcherName);
+ }
+
+ return appLayout().launchersDirectory().resolve(launcherName);
+ }
+
+ /**
+ * Shorthand for {@code appLauncherPath(null)}.
+ */
+ public Path appLauncherPath() {
+ return appLauncherPath(null);
+ }
+
+ private void verifyNotRuntime() {
+ if (isRuntime()) {
+ throw new IllegalArgumentException("Java runtime packaging");
+ }
+ }
+
+ /**
+ * Returns path to .cfg file of the given application launcher.
+ *
+ * E.g.:
+ * [jpackage --name Foo --type rpm] -> `/opt/foo/lib/app/Foo.cfg`
+ * [jpackage --name Foo --type app-image --dest bar] -> `bar/Foo/lib/app/Foo.cfg`
+ *
+ * @param launcher name of launcher or {@code null} for the main launcher
+ *
+ * @throws IllegalArgumentException if the command is configured for
+ * packaging Java runtime
+ */
+ public Path appLauncherCfgPath(String launcherName) {
+ verifyNotRuntime();
+ if (launcherName == null) {
+ launcherName = name();
+ }
+ return appLayout().appDirectory().resolve(launcherName + ".cfg");
+ }
+
+ public boolean isFakeRuntime(String msg) {
+ Path runtimeDir = appRuntimeDirectory();
+
+ final Collection<Path> criticalRuntimeFiles;
+ if (TKit.isWindows()) {
+ criticalRuntimeFiles = WindowsHelper.CRITICAL_RUNTIME_FILES;
+ } else if (TKit.isLinux()) {
+ criticalRuntimeFiles = LinuxHelper.CRITICAL_RUNTIME_FILES;
+ } else if (TKit.isOSX()) {
+ criticalRuntimeFiles = MacHelper.CRITICAL_RUNTIME_FILES;
+ } else {
+ throw TKit.throwUnknownPlatformError();
+ }
+
+ if (criticalRuntimeFiles.stream().filter(
+ v -> runtimeDir.resolve(v).toFile().exists()).findFirst().orElse(
+ null) == null) {
+ // Fake runtime
+ TKit.trace(String.format(
+ "%s because application runtime directory [%s] is incomplete",
+ msg, runtimeDir));
+ return true;
+ }
+ return false;
+ }
+
+ public static void useToolProviderByDefault() {
+ defaultWithToolProvider = true;
+ }
+
+ public static void useExecutableByDefault() {
+ defaultWithToolProvider = false;
+ }
+
+ public JPackageCommand useToolProvider(boolean v) {
+ verifyMutable();
+ withToolProvider = v;
+ return this;
+ }
+
+ public JPackageCommand saveConsoleOutput(boolean v) {
+ verifyMutable();
+ saveConsoleOutput = v;
+ return this;
+ }
+
+ public JPackageCommand dumpOutput(boolean v) {
+ verifyMutable();
+ suppressOutput = !v;
+ return this;
+ }
+
+ public JPackageCommand ignoreDefaultRuntime(boolean v) {
+ verifyMutable();
+ ignoreDefaultRuntime = v;
+ return this;
+ }
+
+ public boolean isWithToolProvider() {
+ return Optional.ofNullable(withToolProvider).orElse(
+ defaultWithToolProvider);
+ }
+
+ public JPackageCommand executePrerequisiteActions() {
+ verifyMutable();
+ if (!actionsExecuted) {
+ actionsExecuted = true;
+ if (actions != null) {
+ actions.stream().forEach(r -> r.accept(this));
+ }
+ }
+ return this;
+ }
+
+ public Executor createExecutor() {
+ verifyMutable();
+ Executor exec = new Executor()
+ .saveOutput(saveConsoleOutput).dumpOutput(!suppressOutput)
+ .addArguments(args);
+
+ if (isWithToolProvider()) {
+ exec.setToolProvider(JavaTool.JPACKAGE);
+ } else {
+ exec.setExecutable(JavaTool.JPACKAGE);
+ }
+
+ return exec;
+ }
+
+ public Executor.Result execute() {
+ executePrerequisiteActions();
+
+ if (isImagePackageType()) {
+ TKit.deleteDirectoryContentsRecursive(outputDir());
+ }
+
+ return new JPackageCommand(this)
+ .adjustArgumentsBeforeExecution()
+ .createExecutor()
+ .execute();
+ }
+
+ public JPackageCommand executeAndAssertHelloAppImageCreated() {
+ executeAndAssertImageCreated();
+ HelloApp.executeLauncherAndVerifyOutput(this);
+ return this;
+ }
+
+ public JPackageCommand executeAndAssertImageCreated() {
+ execute().assertExitCodeIsZero();
+ return assertImageCreated();
+ }
+
+ public JPackageCommand assertImageCreated() {
+ verifyIsOfType(PackageType.IMAGE);
+ TKit.assertDirectoryExists(appRuntimeDirectory());
+
+ if (!isRuntime()) {
+ TKit.assertExecutableFileExists(appLauncherPath());
+ TKit.assertFileExists(appLauncherCfgPath(null));
+ }
+
+ return this;
+ }
+
+ private JPackageCommand adjustArgumentsBeforeExecution() {
+ if (!hasArgument("--runtime-image") && !hasArgument("--app-image") && DEFAULT_RUNTIME_IMAGE != null && !ignoreDefaultRuntime) {
+ addArguments("--runtime-image", DEFAULT_RUNTIME_IMAGE);
+ }
+
+ if (!hasArgument("--verbose") && TKit.VERBOSE_JPACKAGE) {
+ addArgument("--verbose");
+ }
+
+ return this;
+ }
+
+ String getPrintableCommandLine() {
+ return new Executor()
+ .setExecutable(JavaTool.JPACKAGE)
+ .addArguments(args)
+ .getPrintableCommandLine();
+ }
+
+ public void verifyIsOfType(Collection<PackageType> types) {
+ verifyIsOfType(types.toArray(PackageType[]::new));
+ }
+
+ public void verifyIsOfType(PackageType ... types) {
+ final var typesSet = Stream.of(types).collect(Collectors.toSet());
+ if (!hasArgument("--type")) {
+ if (!isImagePackageType()) {
+ if (TKit.isLinux() && typesSet.equals(PackageType.LINUX)) {
+ return;
+ }
+
+ if (TKit.isWindows() && typesSet.equals(PackageType.WINDOWS)) {
+ return;
+ }
+
+ if (TKit.isOSX() && typesSet.equals(PackageType.MAC)) {
+ return;
+ }
+ } else if (typesSet.equals(Set.of(PackageType.IMAGE))) {
+ return;
+ }
+ }
+
+ if (!typesSet.contains(packageType())) {
+ throw new IllegalArgumentException("Unexpected type");
+ }
+ }
+
+ public CfgFile readLaunherCfgFile() {
+ return readLaunherCfgFile(null);
+ }
+
+ public CfgFile readLaunherCfgFile(String launcherName) {
+ verifyIsOfType(PackageType.IMAGE);
+ if (isRuntime()) {
+ return null;
+ }
+ return ThrowingFunction.toFunction(CfgFile::readFromFile).apply(
+ appLauncherCfgPath(launcherName));
+ }
+
+ public static String escapeAndJoin(String... args) {
+ return escapeAndJoin(List.of(args));
+ }
+
+ public static String escapeAndJoin(List<String> args) {
+ Pattern whitespaceRegexp = Pattern.compile("\\s");
+
+ return args.stream().map(v -> {
+ String str = v;
+ // Escape quotes.
+ str = str.replace("\"", "\\\"");
+ // Escape backslashes.
+ str = str.replace("\\", "\\\\");
+ // If value contains whitespace characters, put the value in quotes
+ if (whitespaceRegexp.matcher(str).find()) {
+ str = "\"" + str + "\"";
+ }
+ return str;
+ }).collect(Collectors.joining(" "));
+ }
+
+ public static Path relativePathInRuntime(JavaTool tool) {
+ Path path = tool.relativePathInJavaHome();
+ if (TKit.isOSX()) {
+ path = Path.of("Contents/Home").resolve(path);
+ }
+ return path;
+ }
+
+ public static Stream<String> filterOutput(Stream<String> jpackageOutput) {
+ // Skip "WARNING: Using incubator ..." first line of output
+ return jpackageOutput.skip(1);
+ }
+
+ public static List<String> filterOutput(List<String> jpackageOutput) {
+ return filterOutput(jpackageOutput.stream()).collect(Collectors.toList());
+ }
+
+ @Override
+ protected boolean isMutable() {
+ return !immutable;
+ }
+
+ private Boolean withToolProvider;
+ private boolean saveConsoleOutput;
+ private boolean suppressOutput;
+ private boolean ignoreDefaultRuntime;
+ private boolean immutable;
+ private boolean actionsExecuted;
+ private final List<Consumer<JPackageCommand>> actions;
+ private static boolean defaultWithToolProvider;
+
+ private final static Map<String, PackageType> PACKAGE_TYPES = Functional.identity(
+ () -> {
+ Map<String, PackageType> reply = new HashMap<>();
+ for (PackageType type : PackageType.values()) {
+ reply.put(type.getName(), type);
+ }
+ return reply;
+ }).get();
+
+ public final static Path DEFAULT_RUNTIME_IMAGE = Functional.identity(() -> {
+ // Set the property to the path of run-time image to speed up
+ // building app images and platform bundles by avoiding running jlink
+ // The value of the property will be automativcally appended to
+ // jpackage command line if the command line doesn't have
+ // `--runtime-image` parameter set.
+ String val = TKit.getConfigProperty("runtime-image");
+ if (val != null) {
+ return Path.of(val);
+ }
+ return null;
+ }).get();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JarBuilder.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jpackage.test;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Tool to compile Java sources and pack in a jar file.
+ */
+public final class JarBuilder {
+
+ public JarBuilder() {
+ sourceFiles = new ArrayList<>();
+ }
+
+ public JarBuilder setOutputJar(Path v) {
+ outputJar = v;
+ return this;
+ }
+
+ public JarBuilder setMainClass(String v) {
+ mainClass = v;
+ return this;
+ }
+
+ public JarBuilder addSourceFile(Path v) {
+ sourceFiles.add(v);
+ return this;
+ }
+
+ public JarBuilder setModuleVersion(String v) {
+ moduleVersion = v;
+ return this;
+ }
+
+ public void create() {
+ TKit.withTempDirectory("jar-workdir", workDir -> {
+ if (!sourceFiles.isEmpty()) {
+ new Executor()
+ .setToolProvider(JavaTool.JAVAC)
+ .addArguments("-d", workDir.toString())
+ .addPathArguments(sourceFiles)
+ .execute().assertExitCodeIsZero();
+ }
+
+ Files.createDirectories(outputJar.getParent());
+ if (Files.exists(outputJar)) {
+ TKit.trace(String.format("Delete [%s] existing jar file", outputJar));
+ Files.deleteIfExists(outputJar);
+ }
+
+ Executor jarExe = new Executor()
+ .setToolProvider(JavaTool.JAR)
+ .addArguments("-c", "-f", outputJar.toString());
+ if (moduleVersion != null) {
+ jarExe.addArguments(String.format("--module-version=%s",
+ moduleVersion));
+ }
+ if (mainClass != null) {
+ jarExe.addArguments("-e", mainClass);
+ }
+ jarExe.addArguments("-C", workDir.toString(), ".");
+ jarExe.execute().assertExitCodeIsZero();
+ });
+ }
+ private List<Path> sourceFiles;
+ private Path outputJar;
+ private String mainClass;
+ private String moduleVersion;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JavaAppDesc.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jpackage.test;
+
+import java.io.File;
+import java.nio.file.Path;
+
+
+public final class JavaAppDesc {
+ public JavaAppDesc() {
+ }
+
+ public JavaAppDesc setClassName(String v) {
+ qualifiedClassName = v;
+ return this;
+ }
+
+ public JavaAppDesc setModuleName(String v) {
+ moduleName = v;
+ return this;
+ }
+
+ public JavaAppDesc setJarFileName(String v) {
+ jarFileName = v;
+ return this;
+ }
+
+ public JavaAppDesc setModuleVersion(String v) {
+ moduleVersion = v;
+ return this;
+ }
+
+ public JavaAppDesc setJarWithMainClass(boolean v) {
+ jarWithMainClass = v;
+ return this;
+ }
+
+ public String className() {
+ return qualifiedClassName;
+ }
+
+ public Path classFilePath() {
+ return Path.of(qualifiedClassName.replace(".", File.separator)
+ + ".class");
+ }
+
+ public String moduleName() {
+ return moduleName;
+ }
+
+ public String packageName() {
+ int lastDotIdx = qualifiedClassName.lastIndexOf('.');
+ if (lastDotIdx == -1) {
+ return null;
+ }
+ return qualifiedClassName.substring(0, lastDotIdx);
+ }
+
+ public String jarFileName() {
+ return jarFileName;
+ }
+
+ public String moduleVersion() {
+ return moduleVersion;
+ }
+
+ public boolean jarWithMainClass() {
+ return jarWithMainClass;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ if (jarFileName != null) {
+ sb.append(jarFileName).append(':');
+ }
+ if (moduleName != null) {
+ sb.append(moduleName).append('/');
+ }
+ if (qualifiedClassName != null) {
+ sb.append(qualifiedClassName);
+ }
+ if (jarWithMainClass) {
+ sb.append('!');
+ }
+ if (moduleVersion != null) {
+ sb.append('@').append(moduleVersion);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Create Java application description form encoded string value.
+ *
+ * Syntax of encoded Java application description is
+ * [jar_file:][module_name/]qualified_class_name[!][@module_version].
+ *
+ * E.g.: `duke.jar:com.other/com.other.foo.bar.Buz!@3.7` encodes modular
+ * application. Module name is `com.other`. Main class is
+ * `com.other.foo.bar.Buz`. Module version is `3.7`. Application will be
+ * compiled and packed in `duke.jar` jar file. jar command will set module
+ * version (3.7) and main class (Buz) attributes in the jar file.
+ *
+ * E.g.: `Ciao` encodes non-modular `Ciao` class in the default package.
+ * jar command will not put main class attribute in the jar file.
+ * Default name will be picked for jar file - `hello.jar`.
+ *
+ * @param cmd jpackage command to configure
+ * @param javaAppDesc encoded Java application description
+ */
+ public static JavaAppDesc parse(String javaAppDesc) {
+ JavaAppDesc desc = HelloApp.createDefaltAppDesc();
+
+ if (javaAppDesc == null) {
+ return desc;
+ }
+
+ String moduleNameAndOther = Functional.identity(() -> {
+ String[] components = javaAppDesc.split(":", 2);
+ if (components.length == 2) {
+ desc.setJarFileName(components[0]);
+ }
+ return components[components.length - 1];
+ }).get();
+
+ String classNameAndOther = Functional.identity(() -> {
+ String[] components = moduleNameAndOther.split("/", 2);
+ if (components.length == 2) {
+ desc.setModuleName(components[0]);
+ }
+ return components[components.length - 1];
+ }).get();
+
+ Functional.identity(() -> {
+ String[] components = classNameAndOther.split("@", 2);
+ if (components[0].endsWith("!")) {
+ components[0] = components[0].substring(0,
+ components[0].length() - 1);
+ desc.setJarWithMainClass(true);
+ }
+ desc.setClassName(components[0]);
+ if (components.length == 2) {
+ desc.setModuleVersion(components[1]);
+ }
+ }).run();
+
+ return desc;
+ }
+
+ private String qualifiedClassName;
+ private String moduleName;
+ private String jarFileName;
+ private String moduleVersion;
+ private boolean jarWithMainClass;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JavaTool.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+
+package jdk.jpackage.test;
+
+
+import java.nio.file.Path;
+import java.util.spi.ToolProvider;
+import jdk.incubator.jpackage.ToolProviderFactory;
+
+public enum JavaTool {
+ JAVA("java"), JAVAC("javac"), JPACKAGE("jpackage"), JAR("jar"), JLINK("jlink");
+
+ JavaTool(String name) {
+ this.name = name;
+ this.path = Path.of(System.getProperty("java.home")).resolve(
+ relativePathInJavaHome()).toAbsolutePath().normalize();
+ if (!path.toFile().exists()) {
+ throw new RuntimeException(String.format(
+ "Unable to find tool [%s] at path=[%s]", name, path));
+ }
+ }
+
+ Path getPath() {
+ return path;
+ }
+
+ public ToolProvider asToolProvider() {
+ if (this == JPACKAGE) {
+ return ToolProviderFactory.findFirst("jpackage").orElseThrow(
+ () -> new RuntimeException("jpackage tool not found"));
+ } else {
+ return ToolProvider.findFirst(name).orElse(null);
+ }
+ }
+
+ Path relativePathInJavaHome() {
+ Path path = Path.of("bin", name);
+ if (TKit.isWindows()) {
+ path = path.getParent().resolve(path.getFileName().toString() + ".exe");
+ }
+ return path;
+ }
+
+ private Path path;
+ private String name;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,408 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jpackage.test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class LinuxHelper {
+ private static String getRelease(JPackageCommand cmd) {
+ return cmd.getArgumentValue("--linux-app-release", () -> "1");
+ }
+
+ public static String getPackageName(JPackageCommand cmd) {
+ cmd.verifyIsOfType(PackageType.LINUX);
+ return cmd.getArgumentValue("--linux-package-name",
+ () -> cmd.name().toLowerCase());
+ }
+
+ static String getBundleName(JPackageCommand cmd) {
+ cmd.verifyIsOfType(PackageType.LINUX);
+
+ final PackageType packageType = cmd.packageType();
+ String format = null;
+ switch (packageType) {
+ case LINUX_DEB:
+ format = "%s_%s-%s_%s";
+ break;
+
+ case LINUX_RPM:
+ format = "%s-%s-%s.%s";
+ break;
+ }
+
+ final String release = getRelease(cmd);
+ final String version = cmd.version();
+
+ return String.format(format, getPackageName(cmd), version, release,
+ getDefaultPackageArch(packageType)) + packageType.getSuffix();
+ }
+
+ public static Stream<Path> getPackageFiles(JPackageCommand cmd) {
+ cmd.verifyIsOfType(PackageType.LINUX);
+
+ final PackageType packageType = cmd.packageType();
+ final Path packageFile = cmd.outputBundle();
+
+ Executor exec = new Executor();
+ switch (packageType) {
+ case LINUX_DEB:
+ exec.setExecutable("dpkg")
+ .addArgument("--contents")
+ .addArgument(packageFile);
+ break;
+
+ case LINUX_RPM:
+ exec.setExecutable("rpm")
+ .addArgument("-qpl")
+ .addArgument(packageFile);
+ break;
+ }
+
+ Stream<String> lines = exec.executeAndGetOutput().stream();
+ if (packageType == PackageType.LINUX_DEB) {
+ // Typical text lines produced by dpkg look like:
+ // drwxr-xr-x root/root 0 2019-08-30 05:30 ./opt/appcategorytest/runtime/lib/
+ // -rw-r--r-- root/root 574912 2019-08-30 05:30 ./opt/appcategorytest/runtime/lib/libmlib_image.so
+ // Need to skip all fields but absolute path to file.
+ lines = lines.map(line -> line.substring(line.indexOf(" ./") + 2));
+ }
+ return lines.map(Path::of);
+ }
+
+ public static List<String> getPrerequisitePackages(JPackageCommand cmd) {
+ cmd.verifyIsOfType(PackageType.LINUX);
+ var packageType = cmd.packageType();
+ switch (packageType) {
+ case LINUX_DEB:
+ return Stream.of(getDebBundleProperty(cmd.outputBundle(),
+ "Depends").split(",")).map(String::strip).collect(
+ Collectors.toList());
+
+ case LINUX_RPM:
+ return new Executor().setExecutable("rpm")
+ .addArguments("-qp", "-R", cmd.outputBundle().toString())
+ .executeAndGetOutput();
+ }
+ // Unreachable
+ return null;
+ }
+
+ public static String getBundleProperty(JPackageCommand cmd,
+ String propertyName) {
+ return getBundleProperty(cmd,
+ Map.of(PackageType.LINUX_DEB, propertyName,
+ PackageType.LINUX_RPM, propertyName));
+ }
+
+ public static String getBundleProperty(JPackageCommand cmd,
+ Map<PackageType, String> propertyName) {
+ cmd.verifyIsOfType(PackageType.LINUX);
+ var packageType = cmd.packageType();
+ switch (packageType) {
+ case LINUX_DEB:
+ return getDebBundleProperty(cmd.outputBundle(), propertyName.get(
+ packageType));
+
+ case LINUX_RPM:
+ return getRpmBundleProperty(cmd.outputBundle(), propertyName.get(
+ packageType));
+ }
+ // Unrechable
+ return null;
+ }
+
+ static Path getLauncherPath(JPackageCommand cmd) {
+ cmd.verifyIsOfType(PackageType.LINUX);
+
+ final String launcherName = cmd.name();
+ final String launcherRelativePath = Path.of("/bin", launcherName).toString();
+
+ return getPackageFiles(cmd).filter(path -> path.toString().endsWith(
+ launcherRelativePath)).findFirst().or(() -> {
+ TKit.assertUnexpected(String.format(
+ "Failed to find %s in %s package", launcherName,
+ getPackageName(cmd)));
+ return null;
+ }).get();
+ }
+
+ static long getInstalledPackageSizeKB(JPackageCommand cmd) {
+ cmd.verifyIsOfType(PackageType.LINUX);
+
+ final Path packageFile = cmd.outputBundle();
+ switch (cmd.packageType()) {
+ case LINUX_DEB:
+ return Long.parseLong(getDebBundleProperty(packageFile,
+ "Installed-Size"));
+
+ case LINUX_RPM:
+ return Long.parseLong(getRpmBundleProperty(packageFile, "Size")) >> 10;
+ }
+
+ return 0;
+ }
+
+ static String getDebBundleProperty(Path bundle, String fieldName) {
+ return new Executor()
+ .setExecutable("dpkg-deb")
+ .addArguments("-f", bundle.toString(), fieldName)
+ .executeAndGetFirstLineOfOutput();
+ }
+
+ static String getRpmBundleProperty(Path bundle, String fieldName) {
+ return new Executor()
+ .setExecutable("rpm")
+ .addArguments(
+ "-qp",
+ "--queryformat",
+ String.format("%%{%s}", fieldName),
+ bundle.toString())
+ .executeAndGetFirstLineOfOutput();
+ }
+
+ static void verifyPackageBundleEssential(JPackageCommand cmd) {
+ String packageName = LinuxHelper.getPackageName(cmd);
+ TKit.assertNotEquals(0L, LinuxHelper.getInstalledPackageSizeKB(
+ cmd), String.format(
+ "Check installed size of [%s] package in KB is not zero",
+ packageName));
+
+ final boolean checkPrerequisites;
+ if (cmd.isRuntime()) {
+ Path runtimeDir = cmd.appRuntimeDirectory();
+ Set<Path> expectedCriticalRuntimePaths = CRITICAL_RUNTIME_FILES.stream().map(
+ runtimeDir::resolve).collect(Collectors.toSet());
+ Set<Path> actualCriticalRuntimePaths = getPackageFiles(cmd).filter(
+ expectedCriticalRuntimePaths::contains).collect(
+ Collectors.toSet());
+ checkPrerequisites = expectedCriticalRuntimePaths.equals(
+ actualCriticalRuntimePaths);
+ } else {
+ checkPrerequisites = true;
+ }
+
+ List<String> prerequisites = LinuxHelper.getPrerequisitePackages(cmd);
+ if (checkPrerequisites) {
+ final String vitalPackage = "libc";
+ TKit.assertTrue(prerequisites.stream().filter(
+ dep -> dep.contains(vitalPackage)).findAny().isPresent(),
+ String.format(
+ "Check [%s] package is in the list of required packages %s of [%s] package",
+ vitalPackage, prerequisites, packageName));
+ } else {
+ TKit.trace(String.format(
+ "Not cheking %s required packages of [%s] package",
+ prerequisites, packageName));
+ }
+ }
+
+ static void addBundleDesktopIntegrationVerifier(PackageTest test,
+ boolean integrated) {
+ final String xdgUtils = "xdg-utils";
+
+ test.addBundleVerifier(cmd -> {
+ List<String> prerequisites = getPrerequisitePackages(cmd);
+ boolean xdgUtilsFound = prerequisites.contains(xdgUtils);
+ if (integrated) {
+ TKit.assertTrue(xdgUtilsFound, String.format(
+ "Check [%s] is in the list of required packages %s",
+ xdgUtils, prerequisites));
+ } else {
+ TKit.assertFalse(xdgUtilsFound, String.format(
+ "Check [%s] is NOT in the list of required packages %s",
+ xdgUtils, prerequisites));
+ }
+ });
+
+ test.forTypes(PackageType.LINUX_DEB, () -> {
+ addDebBundleDesktopIntegrationVerifier(test, integrated);
+ });
+ }
+
+ private static void addDebBundleDesktopIntegrationVerifier(PackageTest test,
+ boolean integrated) {
+ Function<List<String>, String> verifier = (lines) -> {
+ // Lookup for xdg commands
+ return lines.stream().filter(line -> {
+ Set<String> words = Stream.of(line.split("\\s+")).collect(
+ Collectors.toSet());
+ return words.contains("xdg-desktop-menu") || words.contains(
+ "xdg-mime") || words.contains("xdg-icon-resource");
+ }).findFirst().orElse(null);
+ };
+
+ test.addBundleVerifier(cmd -> {
+ TKit.withTempDirectory("dpkg-control-files", tempDir -> {
+ // Extract control Debian package files into temporary directory
+ new Executor()
+ .setExecutable("dpkg")
+ .addArguments(
+ "-e",
+ cmd.outputBundle().toString(),
+ tempDir.toString()
+ ).execute().assertExitCodeIsZero();
+
+ Path controlFile = Path.of("postinst");
+
+ // Lookup for xdg commands in postinstall script
+ String lineWithXsdCommand = verifier.apply(
+ Files.readAllLines(tempDir.resolve(controlFile)));
+ String assertMsg = String.format(
+ "Check if %s@%s control file uses xdg commands",
+ cmd.outputBundle(), controlFile);
+ if (integrated) {
+ TKit.assertNotNull(lineWithXsdCommand, assertMsg);
+ } else {
+ TKit.assertNull(lineWithXsdCommand, assertMsg);
+ }
+ });
+ });
+ }
+
+ static void initFileAssociationsTestFile(Path testFile) {
+ try {
+ // Write something in test file.
+ // On Ubuntu and Oracle Linux empty files are considered
+ // plain text. Seems like a system bug.
+ //
+ // $ >foo.jptest1
+ // $ xdg-mime query filetype foo.jptest1
+ // text/plain
+ // $ echo > foo.jptest1
+ // $ xdg-mime query filetype foo.jptest1
+ // application/x-jpackage-jptest1
+ //
+ Files.write(testFile, Arrays.asList(""));
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private static Path getSystemDesktopFilesFolder() {
+ return Stream.of("/usr/share/applications",
+ "/usr/local/share/applications").map(Path::of).filter(dir -> {
+ return Files.exists(dir.resolve("defaults.list"));
+ }).findFirst().orElseThrow(() -> new RuntimeException(
+ "Failed to locate system .desktop files folder"));
+ }
+
+ static void addFileAssociationsVerifier(PackageTest test, FileAssociations fa) {
+ test.addInstallVerifier(cmd -> {
+ PackageTest.withTestFileAssociationsFile(fa, testFile -> {
+ String mimeType = queryFileMimeType(testFile);
+
+ TKit.assertEquals(fa.getMime(), mimeType, String.format(
+ "Check mime type of [%s] file", testFile));
+
+ String desktopFileName = queryMimeTypeDefaultHandler(mimeType);
+
+ Path desktopFile = getSystemDesktopFilesFolder().resolve(
+ desktopFileName);
+
+ TKit.assertFileExists(desktopFile);
+
+ TKit.trace(String.format("Reading [%s] file...", desktopFile));
+ String mimeHandler = Files.readAllLines(desktopFile).stream().peek(
+ v -> TKit.trace(v)).filter(
+ v -> v.startsWith("Exec=")).map(
+ v -> v.split("=", 2)[1]).findFirst().orElseThrow();
+
+ TKit.trace(String.format("Done"));
+
+ TKit.assertEquals(cmd.appLauncherPath().toString(),
+ mimeHandler, String.format(
+ "Check mime type handler is the main application launcher"));
+
+ });
+ });
+
+ test.addUninstallVerifier(cmd -> {
+ PackageTest.withTestFileAssociationsFile(fa, testFile -> {
+ String mimeType = queryFileMimeType(testFile);
+
+ TKit.assertNotEquals(fa.getMime(), mimeType, String.format(
+ "Check mime type of [%s] file", testFile));
+
+ String desktopFileName = queryMimeTypeDefaultHandler(fa.getMime());
+
+ TKit.assertNull(desktopFileName, String.format(
+ "Check there is no default handler for [%s] mime type",
+ fa.getMime()));
+ });
+ });
+ }
+
+ private static String queryFileMimeType(Path file) {
+ return new Executor()
+ .setExecutable("xdg-mime")
+ .addArguments("query", "filetype", file.toString())
+ .executeAndGetFirstLineOfOutput();
+ }
+
+ private static String queryMimeTypeDefaultHandler(String mimeType) {
+ return new Executor()
+ .setExecutable("xdg-mime")
+ .addArguments("query", "default", mimeType)
+ .executeAndGetFirstLineOfOutput();
+ }
+
+ public static String getDefaultPackageArch(PackageType type) {
+ if (archs == null) {
+ archs = new HashMap<>();
+ }
+
+ String arch = archs.get(type);
+ if (arch == null) {
+ Executor exec = new Executor();
+ switch (type) {
+ case LINUX_DEB:
+ exec.setExecutable("dpkg").addArgument(
+ "--print-architecture");
+ break;
+
+ case LINUX_RPM:
+ exec.setExecutable("rpmbuild").addArgument(
+ "--eval=%{_target_cpu}");
+ break;
+ }
+ arch = exec.executeAndGetFirstLineOfOutput();
+ archs.put(type, arch);
+ }
+ return arch;
+ }
+
+ static final Set<Path> CRITICAL_RUNTIME_FILES = Set.of(Path.of(
+ "lib/server/libjvm.so"));
+
+ static private Map<PackageType, String> archs;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacHelper.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jpackage.test;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathFactory;
+import jdk.jpackage.test.Functional.ThrowingConsumer;
+import jdk.jpackage.test.Functional.ThrowingSupplier;
+import org.xml.sax.SAXException;
+
+public class MacHelper {
+
+ public static void withExplodedDmg(JPackageCommand cmd,
+ ThrowingConsumer<Path> consumer) {
+ cmd.verifyIsOfType(PackageType.MAC_DMG);
+
+ var plist = readPList(new Executor()
+ .setExecutable("/usr/bin/hdiutil")
+ .dumpOutput()
+ .addArguments("attach", cmd.outputBundle().toString(), "-plist")
+ .executeAndGetOutput());
+
+ final Path mountPoint = Path.of(plist.queryValue("mount-point"));
+ try {
+ Path dmgImage = mountPoint.resolve(cmd.name() + ".app");
+ TKit.trace(String.format("Exploded [%s] in [%s] directory",
+ cmd.outputBundle(), dmgImage));
+ ThrowingConsumer.toConsumer(consumer).accept(dmgImage);
+ } finally {
+ new Executor()
+ .setExecutable("/usr/bin/hdiutil")
+ .addArgument("detach").addArgument(mountPoint)
+ .execute().assertExitCodeIsZero();
+ }
+ }
+
+ public static PListWrapper readPListFromAppImage(Path appImage) {
+ return readPList(appImage.resolve("Contents/Info.plist"));
+ }
+
+ public static PListWrapper readPList(Path path) {
+ TKit.assertReadableFileExists(path);
+ return ThrowingSupplier.toSupplier(() -> readPList(Files.readAllLines(
+ path))).get();
+ }
+
+ public static PListWrapper readPList(List<String> lines) {
+ return readPList(lines.stream());
+ }
+
+ public static PListWrapper readPList(Stream<String> lines) {
+ return ThrowingSupplier.toSupplier(() -> new PListWrapper(lines.collect(
+ Collectors.joining()))).get();
+ }
+
+ static String getBundleName(JPackageCommand cmd) {
+ cmd.verifyIsOfType(PackageType.MAC);
+ return String.format("%s-%s%s", getPackageName(cmd), cmd.version(),
+ cmd.packageType().getSuffix());
+ }
+
+ static Path getInstallationDirectory(JPackageCommand cmd) {
+ cmd.verifyIsOfType(PackageType.MAC);
+ return Path.of(cmd.getArgumentValue("--install-dir", () -> "/Applications"))
+ .resolve(cmd.name() + ".app");
+ }
+
+ private static String getPackageName(JPackageCommand cmd) {
+ return cmd.getArgumentValue("--mac-package-name",
+ () -> cmd.name());
+ }
+
+ public static final class PListWrapper {
+ public String queryValue(String keyName) {
+ XPath xPath = XPathFactory.newInstance().newXPath();
+ // Query for the value of <string> element preceding <key> element
+ // with value equal to `keyName`
+ String query = String.format(
+ "//string[preceding-sibling::key = \"%s\"][1]", keyName);
+ return ThrowingSupplier.toSupplier(() -> (String) xPath.evaluate(
+ query, doc, XPathConstants.STRING)).get();
+ }
+
+ PListWrapper(String xml) throws ParserConfigurationException,
+ SAXException, IOException {
+ doc = createDocumentBuilder().parse(new ByteArrayInputStream(
+ xml.getBytes(StandardCharsets.UTF_8)));
+ }
+
+ private static DocumentBuilder createDocumentBuilder() throws
+ ParserConfigurationException {
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newDefaultInstance();
+ dbf.setFeature(
+ "http://apache.org/xml/features/nonvalidating/load-external-dtd",
+ false);
+ return dbf.newDocumentBuilder();
+ }
+
+ private final org.w3c.dom.Document doc;
+ }
+
+ static final Set<Path> CRITICAL_RUNTIME_FILES = Set.of(Path.of(
+ "Contents/Home/lib/server/libjvm.dylib"));
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Main.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jpackage.test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import static jdk.jpackage.test.TestBuilder.CMDLINE_ARG_PREFIX;
+
+
+public final class Main {
+ public static void main(String args[]) throws Throwable {
+ boolean listTests = false;
+ List<TestInstance> tests = new ArrayList<>();
+ try (TestBuilder testBuilder = new TestBuilder(tests::add)) {
+ for (var arg : args) {
+ TestBuilder.trace(String.format("Parsing [%s]...", arg));
+
+ if ((CMDLINE_ARG_PREFIX + "list").equals(arg)) {
+ listTests = true;
+ continue;
+ }
+
+ boolean success = false;
+ try {
+ testBuilder.processCmdLineArg(arg);
+ success = true;
+ } catch (Throwable throwable) {
+ TKit.unbox(throwable);
+ } finally {
+ if (!success) {
+ TKit.log(
+ String.format("Error processing parameter=[%s]",
+ arg));
+ }
+ }
+ }
+ }
+
+ // Order tests by their full names to have stable test sequence.
+ List<TestInstance> orderedTests = tests.stream()
+ .sorted((a, b) -> a.fullName().compareTo(b.fullName()))
+ .collect(Collectors.toList());
+
+ if (listTests) {
+ // Just list the tests
+ orderedTests.stream().forEach(test -> System.out.println(String.format(
+ "%s; workDir=[%s]", test.fullName(), test.workDir())));
+ return;
+ }
+
+ TKit.withExtraLogStream(() -> runTests(orderedTests));
+ }
+
+ private static void runTests(List<TestInstance> tests) {
+ TKit.runTests(tests);
+
+ final long passedCount = tests.stream().filter(TestInstance::passed).count();
+ TKit.log(String.format("[==========] %d tests ran", tests.size()));
+ TKit.log(String.format("[ PASSED ] %d %s", passedCount,
+ passedCount == 1 ? "test" : "tests"));
+
+ reportDetails(tests, "[ SKIPPED ]", TestInstance::skipped, false);
+ reportDetails(tests, "[ FAILED ]", TestInstance::failed, true);
+
+ var withSkipped = reportSummary(tests, "SKIPPED", TestInstance::skipped);
+ var withFailures = reportSummary(tests, "FAILED", TestInstance::failed);
+
+ if (withFailures != null) {
+ throw withFailures;
+ }
+
+ if (withSkipped != null) {
+ tests.stream().filter(TestInstance::skipped).findFirst().get().rethrowIfSkipped();
+ }
+ }
+
+ private static long reportDetails(List<TestInstance> tests,
+ String label, Predicate<TestInstance> selector, boolean printWorkDir) {
+
+ final Function<TestInstance, String> makeMessage = test -> {
+ if (printWorkDir) {
+ return String.format("%s %s; workDir=[%s]", label,
+ test.fullName(), test.workDir());
+ }
+ return String.format("%s %s", label, test.fullName());
+ };
+
+ final long count = tests.stream().filter(selector).count();
+ if (count != 0) {
+ TKit.log(String.format("%s %d %s, listed below", label, count, count
+ == 1 ? "test" : "tests"));
+ tests.stream().filter(selector).map(makeMessage).forEachOrdered(
+ TKit::log);
+ }
+
+ return count;
+ }
+
+ private static RuntimeException reportSummary(List<TestInstance> tests,
+ String label, Predicate<TestInstance> selector) {
+ final long count = tests.stream().filter(selector).count();
+ if (count != 0) {
+ final String message = String.format("%d %s %s", count, label, count
+ == 1 ? "TEST" : "TESTS");
+ TKit.log(message);
+ return new RuntimeException(message);
+ }
+
+ return null;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MethodCall.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jpackage.test;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import jdk.jpackage.test.Functional.ThrowingConsumer;
+import jdk.jpackage.test.TestInstance.TestDesc;
+
+class MethodCall implements ThrowingConsumer {
+
+ MethodCall(Object[] instanceCtorArgs, Method method) {
+ this.ctorArgs = Optional.ofNullable(instanceCtorArgs).orElse(
+ DEFAULT_CTOR_ARGS);
+ this.method = method;
+ this.methodArgs = new Object[0];
+ }
+
+ MethodCall(Object[] instanceCtorArgs, Method method, Object arg) {
+ this.ctorArgs = Optional.ofNullable(instanceCtorArgs).orElse(
+ DEFAULT_CTOR_ARGS);
+ this.method = method;
+ this.methodArgs = new Object[]{arg};
+ }
+
+ TestDesc createDescription() {
+ var descBuilder = TestDesc.createBuilder().method(method);
+ if (methodArgs.length != 0) {
+ descBuilder.methodArgs(methodArgs);
+ }
+
+ if (ctorArgs.length != 0) {
+ descBuilder.ctorArgs(ctorArgs);
+ }
+
+ return descBuilder.get();
+ }
+
+ Method getMethod() {
+ return method;
+ }
+
+ Object newInstance() throws NoSuchMethodException, InstantiationException,
+ IllegalAccessException, IllegalArgumentException,
+ InvocationTargetException {
+ if ((method.getModifiers() & Modifier.STATIC) != 0) {
+ return null;
+ }
+
+ Constructor ctor = findRequiredConstructor(method.getDeclaringClass(),
+ ctorArgs);
+ if (ctor.isVarArgs()) {
+ // Assume constructor doesn't have fixed, only variable parameters.
+ return ctor.newInstance(new Object[]{ctorArgs});
+ }
+
+ return ctor.newInstance(ctorArgs);
+ }
+
+ void checkRequiredConstructor() throws NoSuchMethodException {
+ if ((method.getModifiers() & Modifier.STATIC) == 0) {
+ findRequiredConstructor(method.getDeclaringClass(), ctorArgs);
+ }
+ }
+
+ private static Constructor findVarArgConstructor(Class type) {
+ return Stream.of(type.getConstructors()).filter(
+ Constructor::isVarArgs).findFirst().orElse(null);
+ }
+
+ private Constructor findRequiredConstructor(Class type, Object... ctorArgs)
+ throws NoSuchMethodException {
+
+ Supplier<NoSuchMethodException> notFoundException = () -> {
+ return new NoSuchMethodException(String.format(
+ "No public contructor in %s for %s arguments", type,
+ Arrays.deepToString(ctorArgs)));
+ };
+
+ if (Stream.of(ctorArgs).allMatch(Objects::nonNull)) {
+ // No `null` in constructor args, take easy path
+ try {
+ return type.getConstructor(Stream.of(ctorArgs).map(
+ Object::getClass).collect(Collectors.toList()).toArray(
+ Class[]::new));
+ } catch (NoSuchMethodException ex) {
+ // Failed to find ctor that can take the given arguments.
+ Constructor varArgCtor = findVarArgConstructor(type);
+ if (varArgCtor != null) {
+ // There is one with variable number of arguments. Use it.
+ return varArgCtor;
+ }
+ throw notFoundException.get();
+ }
+ }
+
+ List<Constructor> ctors = Stream.of(type.getConstructors())
+ .filter(ctor -> ctor.getParameterCount() == ctorArgs.length)
+ .collect(Collectors.toList());
+
+ if (ctors.isEmpty()) {
+ // No public constructors that can handle the given arguments.
+ throw notFoundException.get();
+ }
+
+ if (ctors.size() == 1) {
+ return ctors.iterator().next();
+ }
+
+ // Revisit this tricky case when it will start bothering.
+ throw notFoundException.get();
+ }
+
+ @Override
+ public void accept(Object thiz) throws Throwable {
+ method.invoke(thiz, methodArgs);
+ }
+
+ private final Object[] methodArgs;
+ private final Method method;
+ private final Object[] ctorArgs;
+
+ final static Object[] DEFAULT_CTOR_ARGS = new Object[0];
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,487 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jpackage.test;
+
+import java.awt.Desktop;
+import java.awt.GraphicsEnvironment;
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import jdk.jpackage.test.Functional.ThrowingConsumer;
+import jdk.incubator.jpackage.internal.AppImageFile;
+import static jdk.jpackage.test.PackageType.*;
+
+/**
+ * Instance of PackageTest is for configuring and running a single jpackage
+ * command to produce platform specific package bundle.
+ *
+ * Provides methods to hook up custom configuration of jpackage command and
+ * verification of the output bundle.
+ */
+public final class PackageTest {
+
+ /**
+ * Default test configuration for jpackage command. Default jpackage command
+ * initialization includes:
+ * <li>Set --input and --dest parameters.
+ * <li>Set --name parameter. Value of the parameter is the name of the first
+ * class with main function found in the callers stack. Defaults can be
+ * overridden with custom initializers set with subsequent addInitializer()
+ * function calls.
+ */
+ public PackageTest() {
+ action = DEFAULT_ACTION;
+ excludeTypes = new HashSet<>();
+ forTypes();
+ setExpectedExitCode(0);
+ handlers = new HashMap<>();
+ namedInitializers = new HashSet<>();
+ currentTypes.forEach(v -> handlers.put(v, new Handler(v)));
+ }
+
+ public PackageTest excludeTypes(PackageType... types) {
+ excludeTypes.addAll(Stream.of(types).collect(Collectors.toSet()));
+ return forTypes(currentTypes);
+ }
+
+ public PackageTest excludeTypes(Collection<PackageType> types) {
+ return excludeTypes(types.toArray(PackageType[]::new));
+ }
+
+ public PackageTest forTypes(PackageType... types) {
+ Collection<PackageType> newTypes;
+ if (types == null || types.length == 0) {
+ newTypes = PackageType.NATIVE;
+ } else {
+ newTypes = Stream.of(types).collect(Collectors.toSet());
+ }
+ currentTypes = newTypes.stream()
+ .filter(PackageType::isSupported)
+ .filter(Predicate.not(excludeTypes::contains))
+ .collect(Collectors.toUnmodifiableSet());
+ return this;
+ }
+
+ public PackageTest forTypes(Collection<PackageType> types) {
+ return forTypes(types.toArray(PackageType[]::new));
+ }
+
+ public PackageTest notForTypes(PackageType... types) {
+ return notForTypes(List.of(types));
+ }
+
+ public PackageTest notForTypes(Collection<PackageType> types) {
+ Set<PackageType> workset = new HashSet<>(currentTypes);
+ workset.removeAll(types);
+ return forTypes(workset);
+ }
+
+ public PackageTest setExpectedExitCode(int v) {
+ expectedJPackageExitCode = v;
+ return this;
+ }
+
+ private PackageTest addInitializer(ThrowingConsumer<JPackageCommand> v,
+ String id) {
+ if (id != null) {
+ if (namedInitializers.contains(id)) {
+ return this;
+ }
+
+ namedInitializers.add(id);
+ }
+ currentTypes.stream().forEach(type -> handlers.get(type).addInitializer(
+ ThrowingConsumer.toConsumer(v)));
+ return this;
+ }
+
+ public PackageTest addInitializer(ThrowingConsumer<JPackageCommand> v) {
+ return addInitializer(v, null);
+ }
+
+ public PackageTest addBundleVerifier(
+ BiConsumer<JPackageCommand, Executor.Result> v) {
+ currentTypes.stream().forEach(
+ type -> handlers.get(type).addBundleVerifier(v));
+ return this;
+ }
+
+ public PackageTest addBundleVerifier(ThrowingConsumer<JPackageCommand> v) {
+ return addBundleVerifier(
+ (cmd, unused) -> ThrowingConsumer.toConsumer(v).accept(cmd));
+ }
+
+ public PackageTest addBundlePropertyVerifier(String propertyName,
+ BiConsumer<String, String> pred) {
+ return addBundleVerifier(cmd -> {
+ pred.accept(propertyName,
+ LinuxHelper.getBundleProperty(cmd, propertyName));
+ });
+ }
+
+ public PackageTest addBundlePropertyVerifier(String propertyName,
+ String expectedPropertyValue) {
+ return addBundlePropertyVerifier(propertyName, (unused, v) -> {
+ TKit.assertEquals(expectedPropertyValue, v, String.format(
+ "Check value of %s property is [%s]", propertyName, v));
+ });
+ }
+
+ public PackageTest addBundleDesktopIntegrationVerifier(boolean integrated) {
+ forTypes(LINUX, () -> {
+ LinuxHelper.addBundleDesktopIntegrationVerifier(this, integrated);
+ });
+ return this;
+ }
+
+ public PackageTest addInstallVerifier(ThrowingConsumer<JPackageCommand> v) {
+ currentTypes.stream().forEach(
+ type -> handlers.get(type).addInstallVerifier(
+ ThrowingConsumer.toConsumer(v)));
+ return this;
+ }
+
+ public PackageTest addUninstallVerifier(ThrowingConsumer<JPackageCommand> v) {
+ currentTypes.stream().forEach(
+ type -> handlers.get(type).addUninstallVerifier(
+ ThrowingConsumer.toConsumer(v)));
+ return this;
+ }
+
+ static void withTestFileAssociationsFile(FileAssociations fa,
+ ThrowingConsumer<Path> consumer) {
+ final String testFileDefaultName = String.join(".", "test",
+ fa.getSuffix());
+ TKit.withTempFile(testFileDefaultName, fa.getSuffix(), testFile -> {
+ if (TKit.isLinux()) {
+ LinuxHelper.initFileAssociationsTestFile(testFile);
+ }
+ consumer.accept(testFile);
+ });
+ }
+
+ PackageTest addHelloAppFileAssociationsVerifier(FileAssociations fa,
+ String... faLauncherDefaultArgs) {
+
+ // Setup test app to have valid jpackage command line before
+ // running check of type of environment.
+ addInitializer(cmd -> new HelloApp(null).addTo(cmd), "HelloApp");
+
+ String noActionMsg = "Not running file associations test";
+ if (GraphicsEnvironment.isHeadless()) {
+ TKit.trace(String.format(
+ "%s because running in headless environment", noActionMsg));
+ return this;
+ }
+
+ addInstallVerifier(cmd -> {
+ if (cmd.isFakeRuntime(noActionMsg)) {
+ return;
+ }
+
+ withTestFileAssociationsFile(fa, testFile -> {
+ testFile = testFile.toAbsolutePath().normalize();
+
+ final Path appOutput = testFile.getParent()
+ .resolve(HelloApp.OUTPUT_FILENAME);
+ Files.deleteIfExists(appOutput);
+
+ TKit.trace(String.format("Use desktop to open [%s] file",
+ testFile));
+ Desktop.getDesktop().open(testFile.toFile());
+ TKit.waitForFileCreated(appOutput, 7);
+
+ List<String> expectedArgs = new ArrayList<>(List.of(
+ faLauncherDefaultArgs));
+ expectedArgs.add(testFile.toString());
+
+ // Wait a little bit after file has been created to
+ // make sure there are no pending writes into it.
+ Thread.sleep(3000);
+ HelloApp.verifyOutputFile(appOutput, expectedArgs);
+ });
+ });
+
+ forTypes(PackageType.LINUX, () -> {
+ LinuxHelper.addFileAssociationsVerifier(this, fa);
+ });
+
+ return this;
+ }
+
+ PackageTest forTypes(Collection<PackageType> types, Runnable action) {
+ Set<PackageType> oldTypes = Set.of(currentTypes.toArray(
+ PackageType[]::new));
+ try {
+ forTypes(types);
+ action.run();
+ } finally {
+ forTypes(oldTypes);
+ }
+ return this;
+ }
+
+ PackageTest forTypes(PackageType type, Runnable action) {
+ return forTypes(List.of(type), action);
+ }
+
+ PackageTest notForTypes(Collection<PackageType> types, Runnable action) {
+ Set<PackageType> workset = new HashSet<>(currentTypes);
+ workset.removeAll(types);
+ return forTypes(workset, action);
+ }
+
+ PackageTest notForTypes(PackageType type, Runnable action) {
+ return notForTypes(List.of(type), action);
+ }
+
+ public PackageTest configureHelloApp() {
+ return configureHelloApp(null);
+ }
+
+ public PackageTest configureHelloApp(String encodedName) {
+ addInitializer(
+ cmd -> new HelloApp(JavaAppDesc.parse(encodedName)).addTo(cmd));
+ addInstallVerifier(HelloApp::executeLauncherAndVerifyOutput);
+ return this;
+ }
+
+ public void run() {
+ List<Handler> supportedHandlers = handlers.values().stream()
+ .filter(entry -> !entry.isVoid())
+ .collect(Collectors.toList());
+
+ if (supportedHandlers.isEmpty()) {
+ // No handlers with initializers found. Nothing to do.
+ return;
+ }
+
+ Supplier<JPackageCommand> initializer = new Supplier<>() {
+ @Override
+ public JPackageCommand get() {
+ JPackageCommand cmd = new JPackageCommand().setDefaultInputOutput();
+ if (bundleOutputDir != null) {
+ cmd.setArgumentValue("--dest", bundleOutputDir.toString());
+ }
+ cmd.setDefaultAppName();
+ return cmd;
+ }
+ };
+
+ supportedHandlers.forEach(handler -> handler.accept(initializer.get()));
+ }
+
+ public PackageTest setAction(Action value) {
+ action = value;
+ return this;
+ }
+
+ public Action getAction() {
+ return action;
+ }
+
+ private class Handler implements Consumer<JPackageCommand> {
+
+ Handler(PackageType type) {
+ if (!PackageType.NATIVE.contains(type)) {
+ throw new IllegalArgumentException(
+ "Attempt to configure a test for image packaging");
+ }
+ this.type = type;
+ initializers = new ArrayList<>();
+ bundleVerifiers = new ArrayList<>();
+ installVerifiers = new ArrayList<>();
+ uninstallVerifiers = new ArrayList<>();
+ }
+
+ boolean isVoid() {
+ return initializers.isEmpty();
+ }
+
+ void addInitializer(Consumer<JPackageCommand> v) {
+ initializers.add(v);
+ }
+
+ void addBundleVerifier(BiConsumer<JPackageCommand, Executor.Result> v) {
+ bundleVerifiers.add(v);
+ }
+
+ void addInstallVerifier(Consumer<JPackageCommand> v) {
+ installVerifiers.add(v);
+ }
+
+ void addUninstallVerifier(Consumer<JPackageCommand> v) {
+ uninstallVerifiers.add(v);
+ }
+
+ @Override
+ public void accept(JPackageCommand cmd) {
+ type.applyTo(cmd);
+
+ initializers.stream().forEach(v -> v.accept(cmd));
+ cmd.executePrerequisiteActions();
+
+ switch (action) {
+ case CREATE:
+ Executor.Result result = cmd.execute();
+ result.assertExitCodeIs(expectedJPackageExitCode);
+ if (expectedJPackageExitCode == 0) {
+ TKit.assertFileExists(cmd.outputBundle());
+ } else {
+ TKit.assertPathExists(cmd.outputBundle(), false);
+ }
+ verifyPackageBundle(cmd.createImmutableCopy(), result);
+ break;
+
+ case VERIFY_INSTALL:
+ if (expectedJPackageExitCode == 0) {
+ verifyPackageInstalled(cmd.createImmutableCopy());
+ }
+ break;
+
+ case VERIFY_UNINSTALL:
+ if (expectedJPackageExitCode == 0) {
+ verifyPackageUninstalled(cmd.createImmutableCopy());
+ }
+ break;
+ }
+ }
+
+ private void verifyPackageBundle(JPackageCommand cmd,
+ Executor.Result result) {
+ if (expectedJPackageExitCode == 0) {
+ if (PackageType.LINUX.contains(cmd.packageType())) {
+ LinuxHelper.verifyPackageBundleEssential(cmd);
+ }
+ }
+ bundleVerifiers.stream().forEach(v -> v.accept(cmd, result));
+ }
+
+ private void verifyPackageInstalled(JPackageCommand cmd) {
+ TKit.trace(String.format("Verify installed: %s",
+ cmd.getPrintableCommandLine()));
+ TKit.assertDirectoryExists(cmd.appRuntimeDirectory());
+ if (!cmd.isRuntime()) {
+ TKit.assertExecutableFileExists(cmd.appLauncherPath());
+
+ if (PackageType.WINDOWS.contains(cmd.packageType())) {
+ new WindowsHelper.AppVerifier(cmd);
+ }
+ }
+
+ TKit.assertPathExists(AppImageFile.getPathInAppImage(
+ cmd.appInstallationDirectory()), false);
+
+ installVerifiers.stream().forEach(v -> v.accept(cmd));
+ }
+
+ private void verifyPackageUninstalled(JPackageCommand cmd) {
+ TKit.trace(String.format("Verify uninstalled: %s",
+ cmd.getPrintableCommandLine()));
+ if (!cmd.isRuntime()) {
+ TKit.assertPathExists(cmd.appLauncherPath(), false);
+
+ if (PackageType.WINDOWS.contains(cmd.packageType())) {
+ new WindowsHelper.AppVerifier(cmd);
+ }
+ }
+
+ TKit.assertPathExists(cmd.appInstallationDirectory(), false);
+
+ uninstallVerifiers.stream().forEach(v -> v.accept(cmd));
+ }
+
+ private final PackageType type;
+ private final List<Consumer<JPackageCommand>> initializers;
+ private final List<BiConsumer<JPackageCommand, Executor.Result>> bundleVerifiers;
+ private final List<Consumer<JPackageCommand>> installVerifiers;
+ private final List<Consumer<JPackageCommand>> uninstallVerifiers;
+ }
+
+ private Collection<PackageType> currentTypes;
+ private Set<PackageType> excludeTypes;
+ private int expectedJPackageExitCode;
+ private Map<PackageType, Handler> handlers;
+ private Set<String> namedInitializers;
+ private Action action;
+
+ /**
+ * Test action.
+ */
+ static public enum Action {
+ /**
+ * Create bundle.
+ */
+ CREATE,
+ /**
+ * Verify bundle installed.
+ */
+ VERIFY_INSTALL,
+ /**
+ * Verify bundle uninstalled.
+ */
+ VERIFY_UNINSTALL;
+
+ @Override
+ public String toString() {
+ return name().toLowerCase().replace('_', '-');
+ }
+ };
+ private final static Action DEFAULT_ACTION;
+ private final static File bundleOutputDir;
+
+ static {
+ final String propertyName = "output";
+ String val = TKit.getConfigProperty(propertyName);
+ if (val == null) {
+ bundleOutputDir = null;
+ } else {
+ bundleOutputDir = new File(val).getAbsoluteFile();
+
+ if (!bundleOutputDir.isDirectory()) {
+ throw new IllegalArgumentException(String.format(
+ "Invalid value of %s sytem property: [%s]. Should be existing directory",
+ TKit.getConfigPropertyName(propertyName),
+ bundleOutputDir));
+ }
+ }
+ }
+
+ static {
+ final String propertyName = "action";
+ String action = Optional.ofNullable(TKit.getConfigProperty(propertyName)).orElse(
+ Action.CREATE.toString()).toLowerCase();
+ DEFAULT_ACTION = Stream.of(Action.values()).filter(
+ a -> a.toString().equals(action)).findFirst().orElseThrow(
+ () -> new IllegalArgumentException(String.format(
+ "Unrecognized value of %s property: [%s]",
+ TKit.getConfigPropertyName(propertyName), action)));
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageType.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jpackage.test;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * jpackage type traits.
+ */
+public enum PackageType {
+ WIN_MSI(".msi",
+ TKit.isWindows() ? "jdk.incubator.jpackage.internal.WinMsiBundler" : null),
+ WIN_EXE(".exe",
+ TKit.isWindows() ? "jdk.incubator.jpackage.internal.WinMsiBundler" : null),
+ LINUX_DEB(".deb",
+ TKit.isLinux() ? "jdk.incubator.jpackage.internal.LinuxDebBundler" : null),
+ LINUX_RPM(".rpm",
+ TKit.isLinux() ? "jdk.incubator.jpackage.internal.LinuxRpmBundler" : null),
+ MAC_DMG(".dmg", TKit.isOSX() ? "jdk.incubator.jpackage.internal.MacDmgBundler" : null),
+ MAC_PKG(".pkg", TKit.isOSX() ? "jdk.incubator.jpackage.internal.MacPkgBundler" : null),
+ IMAGE("app-image", null, null);
+
+ PackageType(String packageName, String bundleSuffix, String bundlerClass) {
+ name = packageName;
+ suffix = bundleSuffix;
+ if (bundlerClass != null && !Inner.DISABLED_PACKAGERS.contains(getName())) {
+ supported = isBundlerSupported(bundlerClass);
+ } else {
+ supported = false;
+ }
+
+ if (suffix != null && supported) {
+ TKit.trace(String.format("Bundler %s supported", getName()));
+ }
+ }
+
+ PackageType(String bundleSuffix, String bundlerClass) {
+ this(bundleSuffix.substring(1), bundleSuffix, bundlerClass);
+ }
+
+ void applyTo(JPackageCommand cmd) {
+ cmd.addArguments("--type", getName());
+ }
+
+ String getSuffix() {
+ return suffix;
+ }
+
+ boolean isSupported() {
+ return supported;
+ }
+
+ String getName() {
+ return name;
+ }
+
+ static PackageType fromSuffix(String packageFilename) {
+ if (packageFilename != null) {
+ for (PackageType v : values()) {
+ if (packageFilename.endsWith(v.getSuffix())) {
+ return v;
+ }
+ }
+ }
+ return null;
+ }
+
+ private static boolean isBundlerSupported(String bundlerClass) {
+ try {
+ Class clazz = Class.forName(bundlerClass);
+ Method supported = clazz.getMethod("supported", boolean.class);
+ return ((Boolean) supported.invoke(
+ clazz.getConstructor().newInstance(), true));
+ } catch (ClassNotFoundException | IllegalAccessException ex) {
+ } catch (InstantiationException | NoSuchMethodException
+ | InvocationTargetException ex) {
+ Functional.rethrowUnchecked(ex);
+ }
+ return false;
+ }
+
+ private final String name;
+ private final String suffix;
+ private final boolean supported;
+
+ public final static Set<PackageType> LINUX = Set.of(LINUX_DEB, LINUX_RPM);
+ public final static Set<PackageType> WINDOWS = Set.of(WIN_EXE, WIN_MSI);
+ public final static Set<PackageType> MAC = Set.of(MAC_PKG, MAC_DMG);
+ public final static Set<PackageType> NATIVE = Stream.concat(
+ Stream.concat(LINUX.stream(), WINDOWS.stream()),
+ MAC.stream()).collect(Collectors.toUnmodifiableSet());
+
+ private final static class Inner {
+
+ private final static Set<String> DISABLED_PACKAGERS = Optional.ofNullable(
+ TKit.tokenizeConfigProperty("disabledPackagers")).orElse(
+ Collections.emptySet());
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,852 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jpackage.test;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.lang.reflect.InvocationTargetException;
+import java.nio.file.*;
+import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
+import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiPredicate;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import jdk.jpackage.test.Functional.ExceptionBox;
+import jdk.jpackage.test.Functional.ThrowingConsumer;
+import jdk.jpackage.test.Functional.ThrowingRunnable;
+import jdk.jpackage.test.Functional.ThrowingSupplier;
+
+final public class TKit {
+
+ private static final String OS = System.getProperty("os.name").toLowerCase();
+
+ public static final Path TEST_SRC_ROOT = Functional.identity(() -> {
+ Path root = Path.of(System.getProperty("test.src"));
+
+ for (int i = 0; i != 10; ++i) {
+ if (root.resolve("apps").toFile().isDirectory()) {
+ return root.toAbsolutePath();
+ }
+ root = root.resolve("..");
+ }
+
+ throw new RuntimeException("Failed to locate apps directory");
+ }).get();
+
+ public final static String ICON_SUFFIX = Functional.identity(() -> {
+ if (isOSX()) {
+ return ".icns";
+ }
+
+ if (isLinux()) {
+ return ".png";
+ }
+
+ if (isWindows()) {
+ return ".ico";
+ }
+
+ throw throwUnknownPlatformError();
+ }).get();
+
+ public static void run(String args[], ThrowingRunnable testBody) {
+ if (currentTest != null) {
+ throw new IllegalStateException(
+ "Unexpeced nested or concurrent Test.run() call");
+ }
+
+ TestInstance test = new TestInstance(testBody);
+ ThrowingRunnable.toRunnable(() -> runTests(List.of(test))).run();
+ test.rethrowIfSkipped();
+ if (!test.passed()) {
+ throw new RuntimeException();
+ }
+ }
+
+ static void withExtraLogStream(ThrowingRunnable action) {
+ if (extraLogStream != null) {
+ ThrowingRunnable.toRunnable(action).run();
+ } else {
+ try (PrintStream logStream = openLogStream()) {
+ extraLogStream = logStream;
+ ThrowingRunnable.toRunnable(action).run();
+ } finally {
+ extraLogStream = null;
+ }
+ }
+ }
+
+ static void runTests(List<TestInstance> tests) {
+ if (currentTest != null) {
+ throw new IllegalStateException(
+ "Unexpeced nested or concurrent Test.run() call");
+ }
+
+ withExtraLogStream(() -> {
+ tests.stream().forEach(test -> {
+ currentTest = test;
+ try {
+ ignoreExceptions(test).run();
+ } finally {
+ currentTest = null;
+ if (extraLogStream != null) {
+ extraLogStream.flush();
+ }
+ }
+ });
+ });
+ }
+
+ static Runnable ignoreExceptions(ThrowingRunnable action) {
+ return () -> {
+ try {
+ try {
+ action.run();
+ } catch (Throwable ex) {
+ unbox(ex);
+ }
+ } catch (Throwable throwable) {
+ printStackTrace(throwable);
+ }
+ };
+ }
+
+ static void unbox(Throwable throwable) throws Throwable {
+ try {
+ throw throwable;
+ } catch (ExceptionBox | InvocationTargetException ex) {
+ unbox(ex.getCause());
+ }
+ }
+
+ public static Path workDir() {
+ return currentTest.workDir();
+ }
+
+ static Path defaultInputDir() {
+ return workDir().resolve("input");
+ }
+
+ static Path defaultOutputDir() {
+ return workDir().resolve("output");
+ }
+
+ static String getCurrentDefaultAppName() {
+ // Construct app name from swapping and joining test base name
+ // and test function name.
+ // Say the test name is `FooTest.testBasic`. Then app name would be `BasicFooTest`.
+ String appNamePrefix = currentTest.functionName();
+ if (appNamePrefix != null && appNamePrefix.startsWith("test")) {
+ appNamePrefix = appNamePrefix.substring("test".length());
+ }
+ return Stream.of(appNamePrefix, currentTest.baseName()).filter(
+ v -> v != null && !v.isEmpty()).collect(Collectors.joining());
+ }
+
+ public static boolean isWindows() {
+ return (OS.contains("win"));
+ }
+
+ public static boolean isOSX() {
+ return (OS.contains("mac"));
+ }
+
+ public static boolean isLinux() {
+ return ((OS.contains("nix") || OS.contains("nux")));
+ }
+
+ static void log(String v) {
+ System.out.println(v);
+ if (extraLogStream != null) {
+ extraLogStream.println(v);
+ }
+ }
+
+ public static void createTextFile(Path propsFilename, Collection<String> lines) {
+ createTextFile(propsFilename, lines.stream());
+ }
+
+ public static void createTextFile(Path propsFilename, Stream<String> lines) {
+ trace(String.format("Create [%s] text file...",
+ propsFilename.toAbsolutePath().normalize()));
+ ThrowingRunnable.toRunnable(() -> Files.write(propsFilename,
+ lines.peek(TKit::trace).collect(Collectors.toList()))).run();
+ trace("Done");
+ }
+
+ public static void createPropertiesFile(Path propsFilename,
+ Collection<Map.Entry<String, String>> props) {
+ trace(String.format("Create [%s] properties file...",
+ propsFilename.toAbsolutePath().normalize()));
+ ThrowingRunnable.toRunnable(() -> Files.write(propsFilename,
+ props.stream().map(e -> String.join("=", e.getKey(),
+ e.getValue())).peek(TKit::trace).collect(Collectors.toList()))).run();
+ trace("Done");
+ }
+
+ public static void createPropertiesFile(Path propsFilename,
+ Map.Entry<String, String>... props) {
+ createPropertiesFile(propsFilename, List.of(props));
+ }
+
+ public static void createPropertiesFile(Path propsFilename,
+ Map<String, String> props) {
+ createPropertiesFile(propsFilename, props.entrySet());
+ }
+
+ public static void trace(String v) {
+ if (TRACE) {
+ log("TRACE: " + v);
+ }
+ }
+
+ private static void traceAssert(String v) {
+ if (TRACE_ASSERTS) {
+ log("TRACE: " + v);
+ }
+ }
+
+ public static void error(String v) {
+ log("ERROR: " + v);
+ throw new AssertionError(v);
+ }
+
+ private final static String TEMP_FILE_PREFIX = null;
+
+ private static Path createUniqueFileName(String defaultName) {
+ final String[] nameComponents;
+
+ int separatorIdx = defaultName.lastIndexOf('.');
+ final String baseName;
+ if (separatorIdx == -1) {
+ baseName = defaultName;
+ nameComponents = new String[]{baseName};
+ } else {
+ baseName = defaultName.substring(0, separatorIdx);
+ nameComponents = new String[]{baseName, defaultName.substring(
+ separatorIdx + 1)};
+ }
+
+ final Path basedir = workDir();
+ int i = 0;
+ for (; i < 100; ++i) {
+ Path path = basedir.resolve(String.join(".", nameComponents));
+ if (!path.toFile().exists()) {
+ return path;
+ }
+ nameComponents[0] = String.format("%s.%d", baseName, i);
+ }
+ throw new IllegalStateException(String.format(
+ "Failed to create unique file name from [%s] basename after %d attempts",
+ baseName, i));
+ }
+
+ public static Path createTempDirectory(String role) throws IOException {
+ if (role == null) {
+ return Files.createTempDirectory(workDir(), TEMP_FILE_PREFIX);
+ }
+ return Files.createDirectory(createUniqueFileName(role));
+ }
+
+ public static Path createTempFile(String role, String suffix) throws
+ IOException {
+ if (role == null) {
+ return Files.createTempFile(workDir(), TEMP_FILE_PREFIX, suffix);
+ }
+ return Files.createFile(createUniqueFileName(role));
+ }
+
+ public static Path withTempFile(String role, String suffix,
+ ThrowingConsumer<Path> action) {
+ final Path tempFile = ThrowingSupplier.toSupplier(() -> createTempFile(
+ role, suffix)).get();
+ boolean keepIt = true;
+ try {
+ ThrowingConsumer.toConsumer(action).accept(tempFile);
+ keepIt = false;
+ return tempFile;
+ } finally {
+ if (tempFile != null && !keepIt) {
+ ThrowingRunnable.toRunnable(() -> Files.deleteIfExists(tempFile)).run();
+ }
+ }
+ }
+
+ public static Path withTempDirectory(String role,
+ ThrowingConsumer<Path> action) {
+ final Path tempDir = ThrowingSupplier.toSupplier(
+ () -> createTempDirectory(role)).get();
+ boolean keepIt = true;
+ try {
+ ThrowingConsumer.toConsumer(action).accept(tempDir);
+ keepIt = false;
+ return tempDir;
+ } finally {
+ if (tempDir != null && tempDir.toFile().isDirectory() && !keepIt) {
+ deleteDirectoryRecursive(tempDir, "");
+ }
+ }
+ }
+
+ private static class DirectoryCleaner implements Consumer<Path> {
+ DirectoryCleaner traceMessage(String v) {
+ msg = v;
+ return this;
+ }
+
+ DirectoryCleaner contentsOnly(boolean v) {
+ contentsOnly = v;
+ return this;
+ }
+
+ @Override
+ public void accept(Path root) {
+ if (msg == null) {
+ if (contentsOnly) {
+ msg = String.format("Cleaning [%s] directory recursively",
+ root);
+ } else {
+ msg = String.format("Deleting [%s] directory recursively",
+ root);
+ }
+ }
+
+ if (!msg.isEmpty()) {
+ trace(msg);
+ }
+
+ List<Throwable> errors = new ArrayList<>();
+ try {
+ final List<Path> paths;
+ if (contentsOnly) {
+ try (var pathStream = Files.walk(root, 0)) {
+ paths = pathStream.collect(Collectors.toList());
+ }
+ } else {
+ paths = List.of(root);
+ }
+
+ for (var path : paths) {
+ try (var pathStream = Files.walk(path)) {
+ pathStream
+ .sorted(Comparator.reverseOrder())
+ .sequential()
+ .forEachOrdered(file -> {
+ try {
+ if (isWindows()) {
+ Files.setAttribute(file, "dos:readonly", false);
+ }
+ Files.delete(file);
+ } catch (IOException ex) {
+ errors.add(ex);
+ }
+ });
+ }
+ }
+
+ } catch (IOException ex) {
+ errors.add(ex);
+ }
+ errors.forEach(error -> trace(error.toString()));
+ }
+
+ private String msg;
+ private boolean contentsOnly;
+ }
+
+ /**
+ * Deletes contents of the given directory recursively. Shortcut for
+ * <code>deleteDirectoryContentsRecursive(path, null)</code>
+ *
+ * @param path path to directory to clean
+ */
+ public static void deleteDirectoryContentsRecursive(Path path) {
+ deleteDirectoryContentsRecursive(path, null);
+ }
+
+ /**
+ * Deletes contents of the given directory recursively. If <code>path<code> is not a
+ * directory, request is silently ignored.
+ *
+ * @param path path to directory to clean
+ * @param msg log message. If null, the default log message is used. If
+ * empty string, no log message will be saved.
+ */
+ public static void deleteDirectoryContentsRecursive(Path path, String msg) {
+ if (path.toFile().isDirectory()) {
+ new DirectoryCleaner().contentsOnly(true).traceMessage(msg).accept(
+ path);
+ }
+ }
+
+ /**
+ * Deletes the given directory recursively. Shortcut for
+ * <code>deleteDirectoryRecursive(path, null)</code>
+ *
+ * @param path path to directory to delete
+ */
+ public static void deleteDirectoryRecursive(Path path) {
+ deleteDirectoryRecursive(path, null);
+ }
+
+ /**
+ * Deletes the given directory recursively. If <code>path<code> is not a
+ * directory, request is silently ignored.
+ *
+ * @param path path to directory to delete
+ * @param msg log message. If null, the default log message is used. If
+ * empty string, no log message will be saved.
+ */
+ public static void deleteDirectoryRecursive(Path path, String msg) {
+ if (path.toFile().isDirectory()) {
+ new DirectoryCleaner().traceMessage(msg).accept(path);
+ }
+ }
+
+ public static RuntimeException throwUnknownPlatformError() {
+ if (isWindows() || isLinux() || isOSX()) {
+ throw new IllegalStateException(
+ "Platform is known. throwUnknownPlatformError() called by mistake");
+ }
+ throw new IllegalStateException("Unknown platform");
+ }
+
+ public static RuntimeException throwSkippedException(String reason) {
+ trace("Skip the test: " + reason);
+ RuntimeException ex = ThrowingSupplier.toSupplier(
+ () -> (RuntimeException) Class.forName("jtreg.SkippedException").getConstructor(
+ String.class).newInstance(reason)).get();
+
+ currentTest.notifySkipped(ex);
+ throw ex;
+ }
+
+ public static Path createRelativePathCopy(final Path file) {
+ Path fileCopy = workDir().resolve(file.getFileName()).toAbsolutePath().normalize();
+
+ ThrowingRunnable.toRunnable(() -> Files.copy(file, fileCopy,
+ StandardCopyOption.REPLACE_EXISTING)).run();
+
+ final Path basePath = Path.of(".").toAbsolutePath().normalize();
+ try {
+ return basePath.relativize(fileCopy);
+ } catch (IllegalArgumentException ex) {
+ // May happen on Windows: java.lang.IllegalArgumentException: 'other' has different root
+ trace(String.format("Failed to relativize [%s] at [%s]", fileCopy,
+ basePath));
+ printStackTrace(ex);
+ }
+ return file;
+ }
+
+ static void waitForFileCreated(Path fileToWaitFor,
+ long timeoutSeconds) throws IOException {
+
+ trace(String.format("Wait for file [%s] to be available",
+ fileToWaitFor.toAbsolutePath()));
+
+ WatchService ws = FileSystems.getDefault().newWatchService();
+
+ Path watchDirectory = fileToWaitFor.toAbsolutePath().getParent();
+ watchDirectory.register(ws, ENTRY_CREATE, ENTRY_MODIFY);
+
+ long waitUntil = System.currentTimeMillis() + timeoutSeconds * 1000;
+ for (;;) {
+ long timeout = waitUntil - System.currentTimeMillis();
+ assertTrue(timeout > 0, String.format(
+ "Check timeout value %d is positive", timeout));
+
+ WatchKey key = ThrowingSupplier.toSupplier(() -> ws.poll(timeout,
+ TimeUnit.MILLISECONDS)).get();
+ if (key == null) {
+ if (fileToWaitFor.toFile().exists()) {
+ trace(String.format(
+ "File [%s] is available after poll timeout expired",
+ fileToWaitFor));
+ return;
+ }
+ assertUnexpected(String.format("Timeout expired", timeout));
+ }
+
+ for (WatchEvent<?> event : key.pollEvents()) {
+ if (event.kind() == StandardWatchEventKinds.OVERFLOW) {
+ continue;
+ }
+ Path contextPath = (Path) event.context();
+ if (Files.isSameFile(watchDirectory.resolve(contextPath),
+ fileToWaitFor)) {
+ trace(String.format("File [%s] is available", fileToWaitFor));
+ return;
+ }
+ }
+
+ if (!key.reset()) {
+ assertUnexpected("Watch key invalidated");
+ }
+ }
+ }
+
+ static void printStackTrace(Throwable throwable) {
+ if (extraLogStream != null) {
+ throwable.printStackTrace(extraLogStream);
+ }
+ throwable.printStackTrace();
+ }
+
+ private static String concatMessages(String msg, String msg2) {
+ if (msg2 != null && !msg2.isBlank()) {
+ return msg + ": " + msg2;
+ }
+ return msg;
+ }
+
+ public static void assertEquals(long expected, long actual, String msg) {
+ currentTest.notifyAssert();
+ if (expected != actual) {
+ error(concatMessages(String.format(
+ "Expected [%d]. Actual [%d]", expected, actual),
+ msg));
+ }
+
+ traceAssert(String.format("assertEquals(%d): %s", expected, msg));
+ }
+
+ public static void assertNotEquals(long expected, long actual, String msg) {
+ currentTest.notifyAssert();
+ if (expected == actual) {
+ error(concatMessages(String.format("Unexpected [%d] value", actual),
+ msg));
+ }
+
+ traceAssert(String.format("assertNotEquals(%d, %d): %s", expected,
+ actual, msg));
+ }
+
+ public static void assertEquals(String expected, String actual, String msg) {
+ currentTest.notifyAssert();
+ if ((actual != null && !actual.equals(expected))
+ || (expected != null && !expected.equals(actual))) {
+ error(concatMessages(String.format(
+ "Expected [%s]. Actual [%s]", expected, actual),
+ msg));
+ }
+
+ traceAssert(String.format("assertEquals(%s): %s", expected, msg));
+ }
+
+ public static void assertNotEquals(String expected, String actual, String msg) {
+ currentTest.notifyAssert();
+ if ((actual != null && !actual.equals(expected))
+ || (expected != null && !expected.equals(actual))) {
+
+ traceAssert(String.format("assertNotEquals(%s, %s): %s", expected,
+ actual, msg));
+ return;
+ }
+
+ error(concatMessages(String.format("Unexpected [%s] value", actual), msg));
+ }
+
+ public static void assertNull(Object value, String msg) {
+ currentTest.notifyAssert();
+ if (value != null) {
+ error(concatMessages(String.format("Unexpected not null value [%s]",
+ value), msg));
+ }
+
+ traceAssert(String.format("assertNull(): %s", msg));
+ }
+
+ public static void assertNotNull(Object value, String msg) {
+ currentTest.notifyAssert();
+ if (value == null) {
+ error(concatMessages("Unexpected null value", msg));
+ }
+
+ traceAssert(String.format("assertNotNull(%s): %s", value, msg));
+ }
+
+ public static void assertTrue(boolean actual, String msg) {
+ assertTrue(actual, msg, null);
+ }
+
+ public static void assertFalse(boolean actual, String msg) {
+ assertFalse(actual, msg, null);
+ }
+
+ public static void assertTrue(boolean actual, String msg, Runnable onFail) {
+ currentTest.notifyAssert();
+ if (!actual) {
+ if (onFail != null) {
+ onFail.run();
+ }
+ error(concatMessages("Failed", msg));
+ }
+
+ traceAssert(String.format("assertTrue(): %s", msg));
+ }
+
+ public static void assertFalse(boolean actual, String msg, Runnable onFail) {
+ currentTest.notifyAssert();
+ if (actual) {
+ if (onFail != null) {
+ onFail.run();
+ }
+ error(concatMessages("Failed", msg));
+ }
+
+ traceAssert(String.format("assertFalse(): %s", msg));
+ }
+
+ public static void assertPathExists(Path path, boolean exists) {
+ if (exists) {
+ assertTrue(path.toFile().exists(), String.format(
+ "Check [%s] path exists", path));
+ } else {
+ assertFalse(path.toFile().exists(), String.format(
+ "Check [%s] path doesn't exist", path));
+ }
+ }
+
+ public static void assertDirectoryExists(Path path) {
+ assertPathExists(path, true);
+ assertTrue(path.toFile().isDirectory(), String.format(
+ "Check [%s] is a directory", path));
+ }
+
+ public static void assertFileExists(Path path) {
+ assertPathExists(path, true);
+ assertTrue(path.toFile().isFile(), String.format("Check [%s] is a file",
+ path));
+ }
+
+ public static void assertExecutableFileExists(Path path) {
+ assertFileExists(path);
+ assertTrue(path.toFile().canExecute(), String.format(
+ "Check [%s] file is executable", path));
+ }
+
+ public static void assertReadableFileExists(Path path) {
+ assertFileExists(path);
+ assertTrue(path.toFile().canRead(), String.format(
+ "Check [%s] file is readable", path));
+ }
+
+ public static void assertUnexpected(String msg) {
+ currentTest.notifyAssert();
+ error(concatMessages("Unexpected", msg));
+ }
+
+ public static void assertStringListEquals(List<String> expected,
+ List<String> actual, String msg) {
+ currentTest.notifyAssert();
+
+ traceAssert(String.format("assertStringListEquals(): %s", msg));
+
+ String idxFieldFormat = Functional.identity(() -> {
+ int listSize = expected.size();
+ int width = 0;
+ while (listSize != 0) {
+ listSize = listSize / 10;
+ width++;
+ }
+ return "%" + width + "d";
+ }).get();
+
+ AtomicInteger counter = new AtomicInteger(0);
+ Iterator<String> actualIt = actual.iterator();
+ expected.stream().sequential().filter(expectedStr -> actualIt.hasNext()).forEach(expectedStr -> {
+ int idx = counter.incrementAndGet();
+ String actualStr = actualIt.next();
+
+ if ((actualStr != null && !actualStr.equals(expectedStr))
+ || (expectedStr != null && !expectedStr.equals(actualStr))) {
+ error(concatMessages(String.format(
+ "(" + idxFieldFormat + ") Expected [%s]. Actual [%s]",
+ idx, expectedStr, actualStr), msg));
+ }
+
+ traceAssert(String.format(
+ "assertStringListEquals(" + idxFieldFormat + ", %s)", idx,
+ expectedStr));
+ });
+
+ if (expected.size() < actual.size()) {
+ // Actual string list is longer than expected
+ error(concatMessages(String.format(
+ "Actual list is longer than expected by %d elements",
+ actual.size() - expected.size()), msg));
+ }
+
+ if (actual.size() < expected.size()) {
+ // Actual string list is shorter than expected
+ error(concatMessages(String.format(
+ "Actual list is longer than expected by %d elements",
+ expected.size() - actual.size()), msg));
+ }
+ }
+
+ public final static class TextStreamAsserter {
+ TextStreamAsserter(String value) {
+ this.value = value;
+ predicate(String::contains);
+ }
+
+ public TextStreamAsserter label(String v) {
+ label = v;
+ return this;
+ }
+
+ public TextStreamAsserter predicate(BiPredicate<String, String> v) {
+ predicate = v;
+ return this;
+ }
+
+ public TextStreamAsserter negate() {
+ negate = true;
+ return this;
+ }
+
+ public TextStreamAsserter orElseThrow(RuntimeException v) {
+ return orElseThrow(() -> v);
+ }
+
+ public TextStreamAsserter orElseThrow(Supplier<RuntimeException> v) {
+ createException = v;
+ return this;
+ }
+
+ public void apply(Stream<String> lines) {
+ String matchedStr = lines.filter(line -> predicate.test(line, value)).findFirst().orElse(
+ null);
+ String labelStr = Optional.ofNullable(label).orElse("output");
+ if (negate) {
+ String msg = String.format(
+ "Check %s doesn't contain [%s] string", labelStr, value);
+ if (createException == null) {
+ assertNull(matchedStr, msg);
+ } else {
+ trace(msg);
+ if (matchedStr != null) {
+ throw createException.get();
+ }
+ }
+ } else {
+ String msg = String.format("Check %s contains [%s] string",
+ labelStr, value);
+ if (createException == null) {
+ assertNotNull(matchedStr, msg);
+ } else {
+ trace(msg);
+ if (matchedStr == null) {
+ throw createException.get();
+ }
+ }
+ }
+ }
+
+ private BiPredicate<String, String> predicate;
+ private String label;
+ private boolean negate;
+ private Supplier<RuntimeException> createException;
+ final private String value;
+ }
+
+ public static TextStreamAsserter assertTextStream(String what) {
+ return new TextStreamAsserter(what);
+ }
+
+ private static PrintStream openLogStream() {
+ if (LOG_FILE == null) {
+ return null;
+ }
+
+ return ThrowingSupplier.toSupplier(() -> new PrintStream(
+ new FileOutputStream(LOG_FILE.toFile(), true))).get();
+ }
+
+ private static TestInstance currentTest;
+ private static PrintStream extraLogStream;
+
+ private static final boolean TRACE;
+ private static final boolean TRACE_ASSERTS;
+
+ static final boolean VERBOSE_JPACKAGE;
+ static final boolean VERBOSE_TEST_SETUP;
+
+ static String getConfigProperty(String propertyName) {
+ return System.getProperty(getConfigPropertyName(propertyName));
+ }
+
+ static String getConfigPropertyName(String propertyName) {
+ return "jpackage.test." + propertyName;
+ }
+
+ static Set<String> tokenizeConfigProperty(String propertyName) {
+ final String val = TKit.getConfigProperty(propertyName);
+ if (val == null) {
+ return null;
+ }
+ return Stream.of(val.toLowerCase().split(",")).map(String::strip).filter(
+ Predicate.not(String::isEmpty)).collect(Collectors.toSet());
+ }
+
+ static final Path LOG_FILE = Functional.identity(() -> {
+ String val = getConfigProperty("logfile");
+ if (val == null) {
+ return null;
+ }
+ return Path.of(val);
+ }).get();
+
+ static {
+ Set<String> logOptions = tokenizeConfigProperty("suppress-logging");
+ if (logOptions == null) {
+ TRACE = true;
+ TRACE_ASSERTS = true;
+ VERBOSE_JPACKAGE = true;
+ VERBOSE_TEST_SETUP = true;
+ } else if (logOptions.contains("all")) {
+ TRACE = false;
+ TRACE_ASSERTS = false;
+ VERBOSE_JPACKAGE = false;
+ VERBOSE_TEST_SETUP = false;
+ } else {
+ Predicate<Set<String>> isNonOf = options -> {
+ return Collections.disjoint(logOptions, options);
+ };
+
+ TRACE = isNonOf.test(Set.of("trace", "t"));
+ TRACE_ASSERTS = isNonOf.test(Set.of("assert", "a"));
+ VERBOSE_JPACKAGE = isNonOf.test(Set.of("jpackage", "jp"));
+ VERBOSE_TEST_SETUP = isNonOf.test(Set.of("init", "i"));
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TestBuilder.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,462 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jpackage.test;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.UnaryOperator;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import jdk.jpackage.test.Annotations.AfterEach;
+import jdk.jpackage.test.Annotations.BeforeEach;
+import jdk.jpackage.test.Annotations.Parameter;
+import jdk.jpackage.test.Annotations.ParameterGroup;
+import jdk.jpackage.test.Annotations.Parameters;
+import jdk.jpackage.test.Annotations.Test;
+import jdk.jpackage.test.Functional.ThrowingConsumer;
+import jdk.jpackage.test.Functional.ThrowingFunction;
+
+final class TestBuilder implements AutoCloseable {
+
+ @Override
+ public void close() throws Exception {
+ flushTestGroup();
+ }
+
+ TestBuilder(Consumer<TestInstance> testConsumer) {
+ argProcessors = Map.of(
+ CMDLINE_ARG_PREFIX + "after-run",
+ arg -> getJavaMethodsFromArg(arg).map(
+ this::wrap).forEachOrdered(afterActions::add),
+
+ CMDLINE_ARG_PREFIX + "before-run",
+ arg -> getJavaMethodsFromArg(arg).map(
+ this::wrap).forEachOrdered(beforeActions::add),
+
+ CMDLINE_ARG_PREFIX + "run",
+ arg -> addTestGroup(getJavaMethodsFromArg(arg).map(
+ ThrowingFunction.toFunction(
+ TestBuilder::toMethodCalls)).flatMap(s -> s).collect(
+ Collectors.toList())),
+
+ CMDLINE_ARG_PREFIX + "exclude",
+ arg -> (excludedTests = Optional.ofNullable(
+ excludedTests).orElse(new HashSet<String>())).add(arg),
+
+ CMDLINE_ARG_PREFIX + "include",
+ arg -> (includedTests = Optional.ofNullable(
+ includedTests).orElse(new HashSet<String>())).add(arg),
+
+ CMDLINE_ARG_PREFIX + "space-subst",
+ arg -> spaceSubstitute = arg,
+
+ CMDLINE_ARG_PREFIX + "group",
+ arg -> flushTestGroup(),
+
+ CMDLINE_ARG_PREFIX + "dry-run",
+ arg -> dryRun = true
+ );
+ this.testConsumer = testConsumer;
+ clear();
+ }
+
+ void processCmdLineArg(String arg) throws Throwable {
+ int separatorIdx = arg.indexOf('=');
+ final String argName;
+ final String argValue;
+ if (separatorIdx != -1) {
+ argName = arg.substring(0, separatorIdx);
+ argValue = arg.substring(separatorIdx + 1);
+ } else {
+ argName = arg;
+ argValue = null;
+ }
+ try {
+ ThrowingConsumer<String> argProcessor = argProcessors.get(argName);
+ if (argProcessor == null) {
+ throw new ParseException("Unrecognized");
+ }
+ argProcessor.accept(argValue);
+ } catch (ParseException ex) {
+ ex.setContext(arg);
+ throw ex;
+ }
+ }
+
+ private void addTestGroup(List<MethodCall> newTestGroup) {
+ if (testGroup != null) {
+ testGroup.addAll(newTestGroup);
+ } else {
+ testGroup = newTestGroup;
+ }
+ }
+
+ private static Stream<MethodCall> filterTests(Stream<MethodCall> tests,
+ Set<String> filters, UnaryOperator<Boolean> pred, String logMsg) {
+ if (filters == null) {
+ return tests;
+ }
+
+ // Log all matches before returning from the function
+ return tests.filter(test -> {
+ String testDescription = test.createDescription().testFullName();
+ boolean match = filters.stream().anyMatch(
+ v -> testDescription.contains(v));
+ if (match) {
+ trace(String.format(logMsg + ": %s", testDescription));
+ }
+ return pred.apply(match);
+ }).collect(Collectors.toList()).stream();
+ }
+
+ private Stream<MethodCall> filterTestGroup() {
+ Objects.requireNonNull(testGroup);
+
+ UnaryOperator<Set<String>> restoreSpaces = filters -> {
+ if (spaceSubstitute == null || filters == null) {
+ return filters;
+ }
+ return filters.stream().map(
+ filter -> filter.replace(spaceSubstitute, " ")).collect(
+ Collectors.toSet());
+ };
+
+ if (includedTests != null) {
+ return filterTests(testGroup.stream(), restoreSpaces.apply(
+ includedTests), x -> x, "Include");
+ }
+
+ return filterTests(testGroup.stream(),
+ restoreSpaces.apply(excludedTests), x -> !x, "Exclude");
+ }
+
+ private void flushTestGroup() {
+ if (testGroup != null) {
+ filterTestGroup().forEach(testBody -> createTestInstance(testBody));
+ clear();
+ }
+ }
+
+ private void createTestInstance(MethodCall testBody) {
+ final List<ThrowingConsumer> curBeforeActions;
+ final List<ThrowingConsumer> curAfterActions;
+
+ Method testMethod = testBody.getMethod();
+ if (Stream.of(BeforeEach.class, AfterEach.class).anyMatch(
+ type -> testMethod.isAnnotationPresent(type))) {
+ curBeforeActions = beforeActions;
+ curAfterActions = afterActions;
+ } else {
+ curBeforeActions = new ArrayList<>(beforeActions);
+ curAfterActions = new ArrayList<>(afterActions);
+
+ selectFrameMethods(testMethod.getDeclaringClass(), BeforeEach.class).map(
+ this::wrap).forEachOrdered(curBeforeActions::add);
+ selectFrameMethods(testMethod.getDeclaringClass(), AfterEach.class).map(
+ this::wrap).forEachOrdered(curAfterActions::add);
+ }
+
+ TestInstance test = new TestInstance(testBody, curBeforeActions,
+ curAfterActions, dryRun);
+ if (includedTests == null) {
+ trace(String.format("Create: %s", test.fullName()));
+ }
+ testConsumer.accept(test);
+ }
+
+ private void clear() {
+ beforeActions = new ArrayList<>();
+ afterActions = new ArrayList<>();
+ excludedTests = null;
+ includedTests = null;
+ spaceSubstitute = null;
+ testGroup = null;
+ }
+
+ private static Class probeClass(String name) {
+ try {
+ return Class.forName(name);
+ } catch (ClassNotFoundException ex) {
+ return null;
+ }
+ }
+
+ private static Stream<Method> selectFrameMethods(Class type, Class annotationType) {
+ return Stream.of(type.getMethods())
+ .filter(m -> m.getParameterCount() == 0)
+ .filter(m -> !m.isAnnotationPresent(Test.class))
+ .filter(m -> m.isAnnotationPresent(annotationType))
+ .sorted((a, b) -> a.getName().compareTo(b.getName()));
+ }
+
+ private static Stream<String> cmdLineArgValueToMethodNames(String v) {
+ List<String> result = new ArrayList<>();
+ String defaultClassName = null;
+ for (String token : v.split(",")) {
+ Class testSet = probeClass(token);
+ if (testSet != null) {
+ // Test set class specified. Pull in all public methods
+ // from the class with @Test annotation removing name duplicates.
+ // Overloads will be handled at the next phase of processing.
+ defaultClassName = token;
+ Stream.of(testSet.getMethods()).filter(
+ m -> m.isAnnotationPresent(Test.class)).map(
+ Method::getName).distinct().forEach(
+ name -> result.add(String.join(".", token, name)));
+
+ continue;
+ }
+
+ final String qualifiedMethodName;
+ final int lastDotIdx = token.lastIndexOf('.');
+ if (lastDotIdx != -1) {
+ qualifiedMethodName = token;
+ defaultClassName = token.substring(0, lastDotIdx);
+ } else if (defaultClassName == null) {
+ throw new ParseException("Default class name not found in");
+ } else {
+ qualifiedMethodName = String.join(".", defaultClassName, token);
+ }
+ result.add(qualifiedMethodName);
+ }
+ return result.stream();
+ }
+
+ private static boolean filterMethod(String expectedMethodName, Method method) {
+ if (!method.getName().equals(expectedMethodName)) {
+ return false;
+ }
+ switch (method.getParameterCount()) {
+ case 0:
+ return !isParametrized(method);
+ case 1:
+ return isParametrized(method);
+ }
+ return false;
+ }
+
+ private static boolean isParametrized(Method method) {
+ return method.isAnnotationPresent(ParameterGroup.class) || method.isAnnotationPresent(
+ Parameter.class);
+ }
+
+ private static List<Method> getJavaMethodFromString(
+ String qualifiedMethodName) {
+ int lastDotIdx = qualifiedMethodName.lastIndexOf('.');
+ if (lastDotIdx == -1) {
+ throw new ParseException("Class name not found in");
+ }
+ String className = qualifiedMethodName.substring(0, lastDotIdx);
+ String methodName = qualifiedMethodName.substring(lastDotIdx + 1);
+ Class methodClass;
+ try {
+ methodClass = Class.forName(className);
+ } catch (ClassNotFoundException ex) {
+ throw new ParseException(String.format("Class [%s] not found;",
+ className));
+ }
+ // Get the list of all public methods as need to deal with overloads.
+ List<Method> methods = Stream.of(methodClass.getMethods()).filter(
+ (m) -> filterMethod(methodName, m)).collect(Collectors.toList());
+ if (methods.isEmpty()) {
+ new ParseException(String.format(
+ "Method [%s] not found in [%s] class;",
+ methodName, className));
+ }
+
+ trace(String.format("%s -> %s", qualifiedMethodName, methods));
+ return methods;
+ }
+
+ private static Stream<Method> getJavaMethodsFromArg(String argValue) {
+ return cmdLineArgValueToMethodNames(argValue).map(
+ ThrowingFunction.toFunction(
+ TestBuilder::getJavaMethodFromString)).flatMap(
+ List::stream).sequential();
+ }
+
+ private static Parameter[] getMethodParameters(Method method) {
+ if (method.isAnnotationPresent(ParameterGroup.class)) {
+ return ((ParameterGroup) method.getAnnotation(ParameterGroup.class)).value();
+ }
+
+ if (method.isAnnotationPresent(Parameter.class)) {
+ return new Parameter[]{(Parameter) method.getAnnotation(
+ Parameter.class)};
+ }
+
+ // Unexpected
+ return null;
+ }
+
+ private static Stream<Object[]> toCtorArgs(Method method) throws
+ IllegalAccessException, InvocationTargetException {
+ Class type = method.getDeclaringClass();
+ List<Method> paremetersProviders = Stream.of(type.getMethods())
+ .filter(m -> m.getParameterCount() == 0)
+ .filter(m -> (m.getModifiers() & Modifier.STATIC) != 0)
+ .filter(m -> m.isAnnotationPresent(Parameters.class))
+ .sorted()
+ .collect(Collectors.toList());
+ if (paremetersProviders.isEmpty()) {
+ // Single instance using the default constructor.
+ return Stream.ofNullable(MethodCall.DEFAULT_CTOR_ARGS);
+ }
+
+ // Pick the first method from the list.
+ Method paremetersProvider = paremetersProviders.iterator().next();
+ if (paremetersProviders.size() > 1) {
+ trace(String.format(
+ "Found %d public static methods without arguments with %s annotation. Will use %s",
+ paremetersProviders.size(), Parameters.class,
+ paremetersProvider));
+ paremetersProviders.stream().map(Method::toString).forEach(
+ TestBuilder::trace);
+ }
+
+ // Construct collection of arguments for test class instances.
+ return ((Collection) paremetersProvider.invoke(null)).stream();
+ }
+
+ private static Stream<MethodCall> toMethodCalls(Method method) throws
+ IllegalAccessException, InvocationTargetException {
+ return toCtorArgs(method).map(v -> toMethodCalls(v, method)).flatMap(
+ s -> s).peek(methodCall -> {
+ // Make sure required constructor is accessible if the one is needed.
+ // Need to probe all methods as some of them might be static
+ // and some class members.
+ // Only class members require ctors.
+ try {
+ methodCall.checkRequiredConstructor();
+ } catch (NoSuchMethodException ex) {
+ throw new ParseException(ex.getMessage() + ".");
+ }
+ });
+ }
+
+ private static Stream<MethodCall> toMethodCalls(Object[] ctorArgs, Method method) {
+ if (!isParametrized(method)) {
+ return Stream.of(new MethodCall(ctorArgs, method));
+ }
+ Parameter[] annotations = getMethodParameters(method);
+ if (annotations.length == 0) {
+ return Stream.of(new MethodCall(ctorArgs, method));
+ }
+ return Stream.of(annotations).map((a) -> {
+ Class paramType = method.getParameterTypes()[0];
+ final Object annotationValue;
+ if (!paramType.isArray()) {
+ annotationValue = fromString(a.value()[0], paramType);
+ } else {
+ Class paramComponentType = paramType.getComponentType();
+ annotationValue = Array.newInstance(paramComponentType, a.value().length);
+ var idx = new AtomicInteger(-1);
+ Stream.of(a.value()).map(v -> fromString(v, paramComponentType)).sequential().forEach(
+ v -> Array.set(annotationValue, idx.incrementAndGet(), v));
+ }
+ return new MethodCall(ctorArgs, method, annotationValue);
+ });
+ }
+
+ private static Object fromString(String value, Class toType) {
+ Function<String, Object> converter = conv.get(toType);
+ if (converter == null) {
+ throw new RuntimeException(String.format(
+ "Failed to find a conversion of [%s] string to %s type",
+ value, toType));
+ }
+ return converter.apply(value);
+ }
+
+ // Wraps Method.invike() into ThrowingRunnable.run()
+ private ThrowingConsumer wrap(Method method) {
+ return (test) -> {
+ Class methodClass = method.getDeclaringClass();
+ String methodName = String.join(".", methodClass.getName(),
+ method.getName());
+ TKit.log(String.format("[ CALL ] %s()", methodName));
+ if (!dryRun) {
+ if (methodClass.isInstance(test)) {
+ method.invoke(test);
+ } else {
+ method.invoke(null);
+ }
+ }
+ };
+ }
+
+ private static class ParseException extends IllegalArgumentException {
+
+ ParseException(String msg) {
+ super(msg);
+ }
+
+ void setContext(String badCmdLineArg) {
+ this.badCmdLineArg = badCmdLineArg;
+ }
+
+ @Override
+ public String getMessage() {
+ String msg = super.getMessage();
+ if (badCmdLineArg != null) {
+ msg = String.format("%s parameter=[%s]", msg, badCmdLineArg);
+ }
+ return msg;
+ }
+ private String badCmdLineArg;
+ }
+
+ static void trace(String msg) {
+ if (TKit.VERBOSE_TEST_SETUP) {
+ TKit.log(msg);
+ }
+ }
+
+ private final Map<String, ThrowingConsumer<String>> argProcessors;
+ private Consumer<TestInstance> testConsumer;
+ private List<MethodCall> testGroup;
+ private List<ThrowingConsumer> beforeActions;
+ private List<ThrowingConsumer> afterActions;
+ private Set<String> excludedTests;
+ private Set<String> includedTests;
+ private String spaceSubstitute;
+ private boolean dryRun;
+
+ private final static Map<Class, Function<String, Object>> conv = Map.of(
+ boolean.class, Boolean::valueOf,
+ Boolean.class, Boolean::valueOf,
+ int.class, Integer::valueOf,
+ Integer.class, Integer::valueOf,
+ long.class, Long::valueOf,
+ Long.class, Long::valueOf,
+ String.class, String::valueOf);
+
+ final static String CMDLINE_ARG_PREFIX = "--jpt-";
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TestInstance.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,346 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jpackage.test;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Method;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import jdk.jpackage.test.Functional.ThrowingConsumer;
+import jdk.jpackage.test.Functional.ThrowingFunction;
+import jdk.jpackage.test.Functional.ThrowingRunnable;
+
+final class TestInstance implements ThrowingRunnable {
+
+ static class TestDesc {
+ private TestDesc() {
+ }
+
+ String testFullName() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(clazz.getSimpleName());
+ if (instanceArgs != null) {
+ sb.append('(').append(instanceArgs).append(')');
+ }
+ if (functionName != null) {
+ sb.append('.');
+ sb.append(functionName);
+ if (functionArgs != null) {
+ sb.append('(').append(functionArgs).append(')');
+ }
+ }
+ return sb.toString();
+ }
+
+ static Builder createBuilder() {
+ return new Builder();
+ }
+
+ static final class Builder implements Supplier<TestDesc> {
+ private Builder() {
+ }
+
+ Builder method(Method v) {
+ method = v;
+ return this;
+ }
+
+ Builder ctorArgs(Object... v) {
+ ctorArgs = ofNullable(v);
+ return this;
+ }
+
+ Builder methodArgs(Object... v) {
+ methodArgs = ofNullable(v);
+ return this;
+ }
+
+ @Override
+ public TestDesc get() {
+ TestDesc desc = new TestDesc();
+ if (method == null) {
+ desc.clazz = enclosingMainMethodClass();
+ } else {
+ desc.clazz = method.getDeclaringClass();
+ desc.functionName = method.getName();
+ desc.functionArgs = formatArgs(methodArgs);
+ desc.instanceArgs = formatArgs(ctorArgs);
+ }
+ return desc;
+ }
+
+ private static String formatArgs(List<Object> values) {
+ if (values == null) {
+ return null;
+ }
+ return values.stream().map(v -> {
+ if (v != null && v.getClass().isArray()) {
+ return String.format("%s(length=%d)",
+ Arrays.deepToString((Object[]) v),
+ Array.getLength(v));
+ }
+ return String.format("%s", v);
+ }).collect(Collectors.joining(", "));
+ }
+
+ private static List<Object> ofNullable(Object... values) {
+ List<Object> result = new ArrayList();
+ for (var v: values) {
+ result.add(v);
+ }
+ return result;
+ }
+
+ private List<Object> ctorArgs;
+ private List<Object> methodArgs;
+ private Method method;
+ }
+
+ static TestDesc create(Method m, Object... args) {
+ TestDesc desc = new TestDesc();
+ desc.clazz = m.getDeclaringClass();
+ desc.functionName = m.getName();
+ if (args.length != 0) {
+ desc.functionArgs = Stream.of(args).map(v -> {
+ if (v.getClass().isArray()) {
+ return String.format("%s(length=%d)",
+ Arrays.deepToString((Object[]) v),
+ Array.getLength(v));
+ }
+ return String.format("%s", v);
+ }).collect(Collectors.joining(", "));
+ }
+ return desc;
+ }
+
+ private Class clazz;
+ private String functionName;
+ private String functionArgs;
+ private String instanceArgs;
+ }
+
+ TestInstance(ThrowingRunnable testBody) {
+ assertCount = 0;
+ this.testConstructor = (unused) -> null;
+ this.testBody = (unused) -> testBody.run();
+ this.beforeActions = Collections.emptyList();
+ this.afterActions = Collections.emptyList();
+ this.testDesc = TestDesc.createBuilder().get();
+ this.dryRun = false;
+ this.workDir = createWorkDirName(testDesc);
+ }
+
+ TestInstance(MethodCall testBody, List<ThrowingConsumer> beforeActions,
+ List<ThrowingConsumer> afterActions, boolean dryRun) {
+ assertCount = 0;
+ this.testConstructor = v -> ((MethodCall)v).newInstance();
+ this.testBody = testBody;
+ this.beforeActions = beforeActions;
+ this.afterActions = afterActions;
+ this.testDesc = testBody.createDescription();
+ this.dryRun = dryRun;
+ this.workDir = createWorkDirName(testDesc);
+ }
+
+ void notifyAssert() {
+ assertCount++;
+ }
+
+ void notifySkipped(RuntimeException ex) {
+ skippedTestException = ex;
+ }
+
+ boolean passed() {
+ return status == Status.Passed;
+ }
+
+ boolean skipped() {
+ return status == Status.Skipped;
+ }
+
+ boolean failed() {
+ return status == Status.Failed;
+ }
+
+ String functionName() {
+ return testDesc.functionName;
+ }
+
+ String baseName() {
+ return testDesc.clazz.getSimpleName();
+ }
+
+ String fullName() {
+ return testDesc.testFullName();
+ }
+
+ void rethrowIfSkipped() {
+ if (skippedTestException != null) {
+ throw skippedTestException;
+ }
+ }
+
+ Path workDir() {
+ return workDir;
+ }
+
+ @Override
+ public void run() throws Throwable {
+ final String fullName = fullName();
+ TKit.log(String.format("[ RUN ] %s", fullName));
+ try {
+ Object testInstance = testConstructor.apply(testBody);
+ beforeActions.forEach(a -> ThrowingConsumer.toConsumer(a).accept(
+ testInstance));
+ try {
+ if (!dryRun) {
+ Files.createDirectories(workDir);
+ testBody.accept(testInstance);
+ }
+ } finally {
+ afterActions.forEach(a -> TKit.ignoreExceptions(() -> a.accept(
+ testInstance)));
+ }
+ status = Status.Passed;
+ } finally {
+ if (skippedTestException != null) {
+ status = Status.Skipped;
+ } else if (status == null) {
+ status = Status.Failed;
+ }
+
+ if (!KEEP_WORK_DIR.contains(status)) {
+ TKit.deleteDirectoryRecursive(workDir);
+ }
+
+ TKit.log(String.format("%s %s; checks=%d", status, fullName,
+ assertCount));
+ }
+ }
+
+ private static Class enclosingMainMethodClass() {
+ StackTraceElement st[] = Thread.currentThread().getStackTrace();
+ for (StackTraceElement ste : st) {
+ if ("main".equals(ste.getMethodName())) {
+ return Functional.ThrowingSupplier.toSupplier(() -> Class.forName(
+ ste.getClassName())).get();
+ }
+ }
+ return null;
+ }
+
+ private static boolean isCalledByJavatest() {
+ StackTraceElement st[] = Thread.currentThread().getStackTrace();
+ for (StackTraceElement ste : st) {
+ if (ste.getClassName().startsWith("com.sun.javatest.")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static Path createWorkDirName(TestDesc testDesc) {
+ Path result = Path.of(".");
+ if (!isCalledByJavatest()) {
+ result = result.resolve(testDesc.clazz.getSimpleName());
+ }
+
+ List<String> components = new ArrayList<>();
+
+ final String testFunctionName = testDesc.functionName;
+ if (testFunctionName != null) {
+ components.add(testFunctionName);
+ }
+
+ final boolean isPrametrized = Stream.of(testDesc.functionArgs,
+ testDesc.instanceArgs).anyMatch(Objects::nonNull);
+ if (isPrametrized) {
+ components.add(String.format("%08x", testDesc.testFullName().hashCode()));
+ }
+
+ if (!components.isEmpty()) {
+ result = result.resolve(String.join(".", components));
+ }
+
+ return result;
+ }
+
+ private enum Status {
+ Passed("[ OK ]"),
+ Failed("[ FAILED ]"),
+ Skipped("[ SKIPPED ]");
+
+ Status(String msg) {
+ this.msg = msg;
+ }
+
+ @Override
+ public String toString() {
+ return msg;
+ }
+
+ private final String msg;
+ }
+
+ private int assertCount;
+ private Status status;
+ private RuntimeException skippedTestException;
+ private final TestDesc testDesc;
+ private final ThrowingFunction testConstructor;
+ private final ThrowingConsumer testBody;
+ private final List<ThrowingConsumer> beforeActions;
+ private final List<ThrowingConsumer> afterActions;
+ private final boolean dryRun;
+ private final Path workDir;
+
+ private final static Set<Status> KEEP_WORK_DIR = Functional.identity(
+ () -> {
+ final String propertyName = "keep-work-dir";
+ Set<String> keepWorkDir = TKit.tokenizeConfigProperty(
+ propertyName);
+ if (keepWorkDir == null) {
+ return Set.of(Status.Failed);
+ }
+
+ Predicate<Set<String>> isOneOf = options -> {
+ return !Collections.disjoint(keepWorkDir, options);
+ };
+
+ Set<Status> result = new HashSet<>();
+ if (isOneOf.test(Set.of("pass", "p"))) {
+ result.add(Status.Passed);
+ }
+ if (isOneOf.test(Set.of("fail", "f"))) {
+ result.add(Status.Failed);
+ }
+
+ return Collections.unmodifiableSet(result);
+ }).get();
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.jpackage.test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class WindowsHelper {
+
+ static String getBundleName(JPackageCommand cmd) {
+ cmd.verifyIsOfType(PackageType.WINDOWS);
+ return String.format("%s-%s%s", cmd.name(), cmd.version(),
+ cmd.packageType().getSuffix());
+ }
+
+ static Path getInstallationDirectory(JPackageCommand cmd) {
+ cmd.verifyIsOfType(PackageType.WINDOWS);
+ Path installDir = Path.of(
+ cmd.getArgumentValue("--install-dir", () -> cmd.name()));
+ if (isUserLocalInstall(cmd)) {
+ return USER_LOCAL.resolve(installDir);
+ }
+ return PROGRAM_FILES.resolve(installDir);
+ }
+
+ private static boolean isUserLocalInstall(JPackageCommand cmd) {
+ return cmd.hasArgument("--win-per-user-install");
+ }
+
+ static class AppVerifier {
+
+ AppVerifier(JPackageCommand cmd) {
+ cmd.verifyIsOfType(PackageType.WINDOWS);
+ this.cmd = cmd;
+ verifyStartMenuShortcut();
+ verifyDesktopShortcut();
+ verifyFileAssociationsRegistry();
+ }
+
+ private void verifyDesktopShortcut() {
+ boolean appInstalled = cmd.appLauncherPath().toFile().exists();
+ if (cmd.hasArgument("--win-shortcut")) {
+ if (isUserLocalInstall(cmd)) {
+ verifyUserLocalDesktopShortcut(appInstalled);
+ verifySystemDesktopShortcut(false);
+ } else {
+ verifySystemDesktopShortcut(appInstalled);
+ verifyUserLocalDesktopShortcut(false);
+ }
+ } else {
+ verifySystemDesktopShortcut(false);
+ verifyUserLocalDesktopShortcut(false);
+ }
+ }
+
+ private Path desktopShortcutPath() {
+ return Path.of(cmd.name() + ".lnk");
+ }
+
+ private void verifyShortcut(Path path, boolean exists) {
+ if (exists) {
+ TKit.assertFileExists(path);
+ } else {
+ TKit.assertPathExists(path, false);
+ }
+ }
+
+ private void verifySystemDesktopShortcut(boolean exists) {
+ Path dir = Path.of(queryRegistryValueCache(
+ SYSTEM_SHELL_FOLDERS_REGKEY, "Common Desktop"));
+ verifyShortcut(dir.resolve(desktopShortcutPath()), exists);
+ }
+
+ private void verifyUserLocalDesktopShortcut(boolean exists) {
+ Path dir = Path.of(
+ queryRegistryValueCache(USER_SHELL_FOLDERS_REGKEY, "Desktop"));
+ verifyShortcut(dir.resolve(desktopShortcutPath()), exists);
+ }
+
+ private void verifyStartMenuShortcut() {
+ boolean appInstalled = cmd.appLauncherPath().toFile().exists();
+ if (cmd.hasArgument("--win-menu")) {
+ if (isUserLocalInstall(cmd)) {
+ verifyUserLocalStartMenuShortcut(appInstalled);
+ verifySystemStartMenuShortcut(false);
+ } else {
+ verifySystemStartMenuShortcut(appInstalled);
+ verifyUserLocalStartMenuShortcut(false);
+ }
+ } else {
+ verifySystemStartMenuShortcut(false);
+ verifyUserLocalStartMenuShortcut(false);
+ }
+ }
+
+ private Path startMenuShortcutPath() {
+ return Path.of(cmd.getArgumentValue("--win-menu-group",
+ () -> "Unknown"), cmd.name() + ".lnk");
+ }
+
+ private void verifyStartMenuShortcut(Path shortcutsRoot, boolean exists) {
+ Path shortcutPath = shortcutsRoot.resolve(startMenuShortcutPath());
+ verifyShortcut(shortcutPath, exists);
+ if (!exists) {
+ TKit.assertPathExists(shortcutPath.getParent(), false);
+ }
+ }
+
+ private void verifySystemStartMenuShortcut(boolean exists) {
+ verifyStartMenuShortcut(Path.of(queryRegistryValueCache(
+ SYSTEM_SHELL_FOLDERS_REGKEY, "Common Programs")), exists);
+
+ }
+
+ private void verifyUserLocalStartMenuShortcut(boolean exists) {
+ verifyStartMenuShortcut(Path.of(queryRegistryValueCache(
+ USER_SHELL_FOLDERS_REGKEY, "Programs")), exists);
+ }
+
+ private void verifyFileAssociationsRegistry() {
+ Stream.of(cmd.getAllArgumentValues("--file-associations")).map(
+ Path::of).forEach(this::verifyFileAssociationsRegistry);
+ }
+
+ private void verifyFileAssociationsRegistry(Path faFile) {
+ boolean appInstalled = cmd.appLauncherPath().toFile().exists();
+ try {
+ TKit.trace(String.format(
+ "Get file association properties from [%s] file",
+ faFile));
+ Map<String, String> faProps = Files.readAllLines(faFile).stream().filter(
+ line -> line.trim().startsWith("extension=") || line.trim().startsWith(
+ "mime-type=")).map(
+ line -> {
+ String[] keyValue = line.trim().split("=", 2);
+ return Map.entry(keyValue[0], keyValue[1]);
+ }).collect(Collectors.toMap(
+ entry -> entry.getKey(),
+ entry -> entry.getValue()));
+ String suffix = faProps.get("extension");
+ String contentType = faProps.get("mime-type");
+ TKit.assertNotNull(suffix, String.format(
+ "Check file association suffix [%s] is found in [%s] property file",
+ suffix, faFile));
+ TKit.assertNotNull(contentType, String.format(
+ "Check file association content type [%s] is found in [%s] property file",
+ contentType, faFile));
+ verifyFileAssociations(appInstalled, "." + suffix, contentType);
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private void verifyFileAssociations(boolean exists, String suffix,
+ String contentType) {
+ String contentTypeFromRegistry = queryRegistryValue(Path.of(
+ "HKLM\\Software\\Classes", suffix).toString(),
+ "Content Type");
+ String suffixFromRegistry = queryRegistryValue(
+ "HKLM\\Software\\Classes\\MIME\\Database\\Content Type\\" + contentType,
+ "Extension");
+
+ if (exists) {
+ TKit.assertEquals(suffix, suffixFromRegistry,
+ "Check suffix in registry is as expected");
+ TKit.assertEquals(contentType, contentTypeFromRegistry,
+ "Check content type in registry is as expected");
+ } else {
+ TKit.assertNull(suffixFromRegistry,
+ "Check suffix in registry not found");
+ TKit.assertNull(contentTypeFromRegistry,
+ "Check content type in registry not found");
+ }
+ }
+
+ private final JPackageCommand cmd;
+ }
+
+ private static String queryRegistryValue(String keyPath, String valueName) {
+ Executor.Result status = new Executor()
+ .setExecutable("reg")
+ .saveOutput()
+ .addArguments("query", keyPath, "/v", valueName)
+ .execute();
+ if (status.exitCode == 1) {
+ // Should be the case of no such registry value or key
+ String lookupString = "ERROR: The system was unable to find the specified registry key or value.";
+ status.getOutput().stream().filter(line -> line.equals(lookupString)).findFirst().orElseThrow(
+ () -> new RuntimeException(String.format(
+ "Failed to find [%s] string in the output",
+ lookupString)));
+ TKit.trace(String.format(
+ "Registry value [%s] at [%s] path not found", valueName,
+ keyPath));
+ return null;
+ }
+
+ String value = status.assertExitCodeIsZero().getOutput().stream().skip(2).findFirst().orElseThrow();
+ // Extract the last field from the following line:
+ // Common Desktop REG_SZ C:\Users\Public\Desktop
+ value = value.split(" REG_SZ ")[1];
+
+ TKit.trace(String.format("Registry value [%s] at [%s] path is [%s]",
+ valueName, keyPath, value));
+
+ return value;
+ }
+
+ private static String queryRegistryValueCache(String keyPath,
+ String valueName) {
+ String key = String.format("[%s][%s]", keyPath, valueName);
+ String value = REGISTRY_VALUES.get(key);
+ if (value == null) {
+ value = queryRegistryValue(keyPath, valueName);
+ REGISTRY_VALUES.put(key, value);
+ }
+
+ return value;
+ }
+
+ static final Set<Path> CRITICAL_RUNTIME_FILES = Set.of(Path.of(
+ "bin\\server\\jvm.dll"));
+
+ // jtreg resets %ProgramFiles% environment variable by some reason.
+ private final static Path PROGRAM_FILES = Path.of(Optional.ofNullable(
+ System.getenv("ProgramFiles")).orElse("C:\\Program Files"));
+
+ private final static Path USER_LOCAL = Path.of(System.getProperty(
+ "user.home"),
+ "AppData", "Local");
+
+ private final static String SYSTEM_SHELL_FOLDERS_REGKEY = "HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders";
+ private final static String USER_SHELL_FOLDERS_REGKEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders";
+
+ private static final Map<String, String> REGISTRY_VALUES = new HashMap<>();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/junit/jdk/incubator/jpackage/internal/AppImageFileTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.incubator.jpackage.internal;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.LinkedHashMap;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
+
+public class AppImageFileTest {
+
+ @Rule
+ public final TemporaryFolder tempFolder = new TemporaryFolder();
+
+ @Test
+ public void testIdentity() throws IOException {
+ Map<String, ? super Object> params = new LinkedHashMap<>();
+ params.put("name", "Foo");
+ params.put("app-version", "2.3");
+ params.put("description", "Duck is the King");
+ AppImageFile aif = create(params);
+
+ Assert.assertEquals("Foo", aif.getLauncherName());
+ }
+
+ @Test
+ public void testInvalidCommandLine() throws IOException {
+ // Just make sure AppImageFile will tolerate jpackage params that would
+ // never create app image at both load/save phases.
+ // People would edit this file just because they can.
+ // We should be ready to handle curious minds.
+ Map<String, ? super Object> params = new LinkedHashMap<>();
+ params.put("invalidParamName", "randomStringValue");
+ create(params);
+
+ params = new LinkedHashMap<>();
+ params.put("name", "foo");
+ params.put("app-version", "");
+ create(params);
+ }
+
+ @Test
+ public void testInavlidXml() throws IOException {
+ assertInvalid(createFromXml("<foo/>"));
+ assertInvalid(createFromXml("<jpackage-state/>"));
+ assertInvalid(createFromXml(
+ "<jpackage-state>",
+ "<main-launcher></main-launcher>",
+ "</jpackage-state>"));
+ assertInvalid(createFromXml(
+ "<jpackage-state>",
+ "<launcher>A</launcher>",
+ "<launcher>B</launcher>",
+ "</jpackage-state>"));
+ }
+
+ @Test
+ public void testValidXml() throws IOException {
+ Assert.assertEquals("Foo", (createFromXml(
+ "<jpackage-state>",
+ "<main-launcher>Foo</main-launcher>",
+ "</jpackage-state>")).getLauncherName());
+
+ Assert.assertEquals("Boo", (createFromXml(
+ "<jpackage-state>",
+ "<main-launcher>Boo</main-launcher>",
+ "<main-launcher>Bar</main-launcher>",
+ "</jpackage-state>")).getLauncherName());
+
+ var file = createFromXml(
+ "<jpackage-state>",
+ "<main-launcher>Foo</main-launcher>",
+ "<launcher></launcher>",
+ "</jpackage-state>");
+ Assert.assertEquals("Foo", file.getLauncherName());
+ Assert.assertArrayEquals(new String[0],
+ file.getAddLauncherNames().toArray(String[]::new));
+ }
+
+ @Test
+ public void testMainLauncherName() throws IOException {
+ Map<String, ? super Object> params = new LinkedHashMap<>();
+ params.put("name", "Foo");
+ params.put("description", "Duck App Description");
+ AppImageFile aif = create(params);
+
+ Assert.assertEquals("Foo", aif.getLauncherName());
+ }
+
+ @Test
+ public void testAddLauncherNames() throws IOException {
+ Map<String, ? super Object> params = new LinkedHashMap<>();
+ List<Map<String, ? super Object>> launchersAsMap = new ArrayList<>();
+
+ Map<String, ? super Object> addLauncher2Params = new LinkedHashMap();
+ addLauncher2Params.put("name", "Launcher2Name");
+ launchersAsMap.add(addLauncher2Params);
+
+ Map<String, ? super Object> addLauncher3Params = new LinkedHashMap();
+ addLauncher3Params.put("name", "Launcher3Name");
+ launchersAsMap.add(addLauncher3Params);
+
+ params.put("name", "Duke App");
+ params.put("description", "Duke App Description");
+ params.put("add-launcher", launchersAsMap);
+ AppImageFile aif = create(params);
+
+ List<String> addLauncherNames = aif.getAddLauncherNames();
+ Assert.assertEquals(2, addLauncherNames.size());
+ Assert.assertTrue(addLauncherNames.contains("Launcher2Name"));
+ Assert.assertTrue(addLauncherNames.contains("Launcher3Name"));
+
+ }
+
+ private AppImageFile create(Map<String, Object> params) throws IOException {
+ AppImageFile.save(tempFolder.getRoot().toPath(), params);
+ return AppImageFile.load(tempFolder.getRoot().toPath());
+ }
+
+ private void assertInvalid(AppImageFile file) {
+ Assert.assertNull(file.getLauncherName());
+ Assert.assertNull(file.getAddLauncherNames());
+ }
+
+ private AppImageFile createFromXml(String... xmlData) throws IOException {
+ Path directory = tempFolder.getRoot().toPath();
+ Path path = AppImageFile.getPathInAppImage(directory);
+ path.toFile().mkdirs();
+ Files.delete(path);
+
+ ArrayList<String> data = new ArrayList();
+ data.add("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>");
+ data.addAll(List.of(xmlData));
+
+ Files.write(path, data, StandardOpenOption.CREATE,
+ StandardOpenOption.TRUNCATE_EXISTING);
+
+ AppImageFile image = AppImageFile.load(directory);
+ return image;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/junit/jdk/incubator/jpackage/internal/ApplicationLayoutTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.incubator.jpackage.internal;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
+import static org.junit.Assert.*;
+
+
+public class ApplicationLayoutTest {
+
+ @Rule
+ public final TemporaryFolder tempFolder = new TemporaryFolder();
+
+ private void fillLinuxAppImage() throws IOException {
+ appImage = tempFolder.newFolder("Foo").toPath();
+
+ Path base = appImage.getFileName();
+
+ tempFolder.newFolder(base.toString(), "bin");
+ tempFolder.newFolder(base.toString(), "lib", "app", "mods");
+ tempFolder.newFolder(base.toString(), "lib", "runtime", "bin");
+ tempFolder.newFile(base.resolve("bin/Foo").toString());
+ tempFolder.newFile(base.resolve("lib/app/Foo.cfg").toString());
+ tempFolder.newFile(base.resolve("lib/app/hello.jar").toString());
+ tempFolder.newFile(base.resolve("lib/Foo.png").toString());
+ tempFolder.newFile(base.resolve("lib/libapplauncher.so").toString());
+ tempFolder.newFile(base.resolve("lib/runtime/bin/java").toString());
+ }
+
+ @Test
+ public void testLinux() throws IOException {
+ fillLinuxAppImage();
+ testApplicationLayout(ApplicationLayout.linuxAppImage());
+ }
+
+ private void testApplicationLayout(ApplicationLayout layout) throws IOException {
+ ApplicationLayout srcLayout = layout.resolveAt(appImage);
+ assertApplicationLayout(srcLayout);
+
+ ApplicationLayout dstLayout = layout.resolveAt(
+ appImage.getParent().resolve(
+ "Copy" + appImage.getFileName().toString()));
+ srcLayout.move(dstLayout);
+ Files.deleteIfExists(appImage);
+ assertApplicationLayout(dstLayout);
+
+ dstLayout.copy(srcLayout);
+ assertApplicationLayout(srcLayout);
+ assertApplicationLayout(dstLayout);
+ }
+
+ private void assertApplicationLayout(ApplicationLayout layout) throws IOException {
+ assertTrue(Files.isRegularFile(layout.appDirectory().resolve("Foo.cfg")));
+ assertTrue(Files.isRegularFile(layout.appDirectory().resolve("hello.jar")));
+ assertTrue(Files.isDirectory(layout.appModsDirectory()));
+ assertTrue(Files.isRegularFile(layout.launchersDirectory().resolve("Foo")));
+ assertTrue(Files.isRegularFile(layout.destktopIntegrationDirectory().resolve("Foo.png")));
+ assertTrue(Files.isRegularFile(layout.dllDirectory().resolve("libapplauncher.so")));
+ assertTrue(Files.isRegularFile(layout.runtimeDirectory().resolve("bin/java")));
+ }
+
+ private Path appImage;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/junit/jdk/incubator/jpackage/internal/CompareDottedVersionTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.incubator.jpackage.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import static org.junit.Assert.*;
+
+@RunWith(Parameterized.class)
+public class CompareDottedVersionTest {
+
+ public CompareDottedVersionTest(boolean greedy, String version1,
+ String version2, int result) {
+ this.version1 = version1;
+ this.version2 = version2;
+ this.expectedResult = result;
+
+ if (greedy) {
+ createTestee = DottedVersion::greedy;
+ } else {
+ createTestee = DottedVersion::lazy;
+ }
+ }
+
+ @Parameters
+ public static List<Object[]> data() {
+ List<Object[]> data = new ArrayList<>();
+ for (var greedy : List.of(true, false)) {
+ data.addAll(List.of(new Object[][] {
+ { greedy, "00.0.0", "0", 0 },
+ { greedy, "0.035", "0.0035", 0 },
+ { greedy, "1", "1", 0 },
+ { greedy, "2", "2.0", 0 },
+ { greedy, "2.00", "2.0", 0 },
+ { greedy, "1.2.3.4", "1.2.3.4.5", -1 },
+ { greedy, "34", "33", 1 },
+ { greedy, "34.0.78", "34.1.78", -1 }
+ }));
+ }
+
+ data.addAll(List.of(new Object[][] {
+ { false, "", "1", -1 },
+ { false, "1.2.4-R4", "1.2.4-R5", 0 },
+ { false, "1.2.4.-R4", "1.2.4.R5", 0 },
+ { false, "7+1", "7+4", 0 },
+ { false, "2+14", "2-14", 0 },
+ { false, "23.4.RC4", "23.3.RC10", 1 },
+ { false, "77.0", "77.99999999999999999999999999999999999999999999999", 0 },
+ }));
+
+ return data;
+ }
+
+ @Test
+ public void testIt() {
+ int actualResult = compare(version1, version2);
+ assertEquals(expectedResult, actualResult);
+
+ int actualNegateResult = compare(version2, version1);
+ assertEquals(actualResult, -1 * actualNegateResult);
+ }
+
+ private int compare(String x, String y) {
+ int result = createTestee.apply(x).compareTo(y);
+
+ if (result < 0) {
+ return -1;
+ }
+
+ if (result > 0) {
+ return 1;
+ }
+
+ return 0;
+ }
+
+ private final String version1;
+ private final String version2;
+ private final int expectedResult;
+ private final Function<String, DottedVersion> createTestee;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/junit/jdk/incubator/jpackage/internal/DeployParamsTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.incubator.jpackage.internal;
+
+import java.io.File;
+import java.io.IOException;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.junit.Rule;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * Test for JDK-8211285
+ */
+public class DeployParamsTest {
+
+ @Rule
+ public final TemporaryFolder tempFolder = new TemporaryFolder();
+
+ @Rule
+ public final ExpectedException thrown = ExpectedException.none();
+
+ @Before
+ public void setUp() throws IOException {
+ testRoot = tempFolder.newFolder();
+ }
+
+ @Test
+ public void testValidAppName() throws PackagerException {
+ initParamsAppName();
+
+ setAppNameAndValidate("Test");
+
+ setAppNameAndValidate("Test Name");
+
+ setAppNameAndValidate("Test - Name !!!");
+ }
+
+ @Test
+ public void testInvalidAppName() throws PackagerException {
+ initForInvalidAppNamePackagerException();
+ initParamsAppName();
+ setAppNameAndValidate("Test\nName");
+ }
+
+ @Test
+ public void testInvalidAppName2() throws PackagerException {
+ initForInvalidAppNamePackagerException();
+ initParamsAppName();
+ setAppNameAndValidate("Test\rName");
+ }
+
+ @Test
+ public void testInvalidAppName3() throws PackagerException {
+ initForInvalidAppNamePackagerException();
+ initParamsAppName();
+ setAppNameAndValidate("TestName\\");
+ }
+
+ @Test
+ public void testInvalidAppName4() throws PackagerException {
+ initForInvalidAppNamePackagerException();
+ initParamsAppName();
+ setAppNameAndValidate("Test \" Name");
+ }
+
+ private void initForInvalidAppNamePackagerException() {
+ thrown.expect(PackagerException.class);
+
+ String msg = "Error: Invalid Application name";
+
+ // Unfortunately org.hamcrest.core.StringStartsWith is not available
+ // with older junit, DIY
+
+ // thrown.expectMessage(startsWith("Error: Invalid Application name"));
+ thrown.expectMessage(new BaseMatcher() {
+ @Override
+ @SuppressWarnings("unchecked")
+ public boolean matches(Object o) {
+ if (o instanceof String) {
+ return ((String) o).startsWith(msg);
+ }
+ return false;
+ }
+
+ @Override
+ public void describeTo(Description d) {
+ d.appendText(msg);
+ }
+ });
+ }
+
+ // Returns deploy params initialized to pass all validation, except for
+ // app name
+ private void initParamsAppName() {
+ params = new DeployParams();
+
+ params.setOutput(testRoot);
+ params.addResource(testRoot, new File(testRoot, "test.jar"));
+ params.addBundleArgument(Arguments.CLIOptions.APPCLASS.getId(),
+ "TestClass");
+ params.addBundleArgument(Arguments.CLIOptions.MAIN_JAR.getId(),
+ "test.jar");
+ params.addBundleArgument(Arguments.CLIOptions.INPUT.getId(), "input");
+ }
+
+ private void setAppNameAndValidate(String appName) throws PackagerException {
+ params.addBundleArgument(Arguments.CLIOptions.NAME.getId(), appName);
+ params.validate();
+ }
+
+ private File testRoot = null;
+ private DeployParams params;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/junit/jdk/incubator/jpackage/internal/DottedVersionTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.incubator.jpackage.internal;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import static org.junit.Assert.*;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DottedVersionTest {
+
+ public DottedVersionTest(boolean greedy) {
+ this.greedy = greedy;
+ if (greedy) {
+ createTestee = DottedVersion::greedy;
+ } else {
+ createTestee = DottedVersion::lazy;
+ }
+ }
+
+ @Parameterized.Parameters
+ public static List<Object[]> data() {
+ return List.of(new Object[] { true }, new Object[] { false });
+ }
+
+ @Rule
+ public ExpectedException exceptionRule = ExpectedException.none();
+
+ @Test
+ public void testValid() {
+ final List<String> validStrings = List.of(
+ "1.0",
+ "1",
+ "2.234.045",
+ "2.234.0",
+ "0",
+ "0.1"
+ );
+
+ final List<String> validLazyStrings;
+ if (greedy) {
+ validLazyStrings = Collections.emptyList();
+ } else {
+ validLazyStrings = List.of(
+ "1.-1",
+ "5.",
+ "4.2.",
+ "3..2",
+ "2.a",
+ "0a",
+ ".",
+ " ",
+ " 1",
+ "1. 2",
+ "+1",
+ "-1",
+ "-0",
+ "1234567890123456789012345678901234567890"
+ );
+ }
+
+ Stream.concat(validStrings.stream(), validLazyStrings.stream())
+ .forEach(value -> {
+ DottedVersion version = createTestee.apply(value);
+ assertEquals(version.toString(), value);
+ });
+ }
+
+ @Test
+ public void testNull() {
+ exceptionRule.expect(NullPointerException.class);
+ createTestee.apply(null);
+ }
+
+ @Test
+ public void testEmpty() {
+ if (greedy) {
+ exceptionRule.expect(IllegalArgumentException.class);
+ exceptionRule.expectMessage("Version may not be empty string");
+ createTestee.apply("");
+ } else {
+ assertTrue(0 == createTestee.apply("").compareTo(""));
+ assertTrue(0 == createTestee.apply("").compareTo("0"));
+ }
+ }
+
+ private final boolean greedy;
+ private final Function<String, DottedVersion> createTestee;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/junit/jdk/incubator/jpackage/internal/InvalidDottedVersionTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.incubator.jpackage.internal;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InvalidDottedVersionTest {
+
+ public InvalidDottedVersionTest(String version) {
+ this.version = version;
+ }
+
+ @Parameters
+ public static List<Object[]> data() {
+ return Stream.of(
+ "1.-1",
+ "5.",
+ "4.2.",
+ "3..2",
+ "2.a",
+ "0a",
+ ".",
+ " ",
+ " 1",
+ "1. 2",
+ "+1",
+ "-1",
+ "-0",
+ "1234567890123456789012345678901234567890"
+ ).map(version -> new Object[] { version }).collect(Collectors.toList());
+ }
+
+ @Rule
+ public ExpectedException exceptionRule = ExpectedException.none();
+
+ @Test
+ public void testIt() {
+ exceptionRule.expect(IllegalArgumentException.class);
+ new DottedVersion(version);
+ }
+
+ private final String version;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/junit/jdk/incubator/jpackage/internal/OverridableResourceTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.incubator.jpackage.internal;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import jdk.incubator.jpackage.internal.resources.ResourceLocator;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.*;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class OverridableResourceTest {
+
+ @Rule
+ public final TemporaryFolder tempFolder = new TemporaryFolder();
+
+ @Test
+ public void testDefault() throws IOException {
+ byte[] actualBytes = saveToFile(new OverridableResource(DEFAULT_NAME));
+
+ try (InputStream is = ResourceLocator.class.getResourceAsStream(
+ DEFAULT_NAME)) {
+ assertArrayEquals(is.readAllBytes(), actualBytes);
+ }
+ }
+
+ @Test
+ public void testDefaultWithSubstitution() throws IOException {
+ OverridableResource resource = new OverridableResource(DEFAULT_NAME);
+
+ List<String> linesBeforeSubstitution = convertToStringList(saveToFile(
+ resource));
+
+ if (SUBSTITUTION_DATA.size() != 1) {
+ // Test setup issue
+ throw new IllegalArgumentException(
+ "Substitution map should contain only a single entry");
+ }
+
+ resource.setSubstitutionData(SUBSTITUTION_DATA);
+ List<String> linesAfterSubstitution = convertToStringList(saveToFile(
+ resource));
+
+ assertEquals(linesBeforeSubstitution.size(), linesAfterSubstitution.size());
+
+ Iterator<String> beforeIt = linesBeforeSubstitution.iterator();
+ Iterator<String> afterIt = linesAfterSubstitution.iterator();
+
+ var substitutionEntry = SUBSTITUTION_DATA.entrySet().iterator().next();
+
+ boolean linesMismatch = false;
+ while (beforeIt.hasNext()) {
+ String beforeStr = beforeIt.next();
+ String afterStr = afterIt.next();
+
+ if (beforeStr.equals(afterStr)) {
+ assertFalse(beforeStr.contains(substitutionEntry.getKey()));
+ } else {
+ linesMismatch = true;
+ assertTrue(beforeStr.contains(substitutionEntry.getKey()));
+ assertTrue(afterStr.contains(substitutionEntry.getValue()));
+ assertFalse(afterStr.contains(substitutionEntry.getKey()));
+ }
+ }
+
+ assertTrue(linesMismatch);
+ }
+
+ @Test
+ public void testCustom() throws IOException {
+ testCustom(DEFAULT_NAME);
+ }
+
+ @Test
+ public void testCustomNoDefault() throws IOException {
+ testCustom(null);
+ }
+
+ private void testCustom(String defaultName) throws IOException {
+ List<String> expectedResourceData = List.of("A", "B", "C");
+
+ Path customFile = createCustomFile("foo", expectedResourceData);
+
+ List<String> actualResourceData = convertToStringList(saveToFile(
+ new OverridableResource(defaultName)
+ .setPublicName(customFile.getFileName())
+ .setResourceDir(customFile.getParent())));
+
+ assertArrayEquals(expectedResourceData.toArray(String[]::new),
+ actualResourceData.toArray(String[]::new));
+ }
+
+ @Test
+ public void testCustomtWithSubstitution() throws IOException {
+ testCustomtWithSubstitution(DEFAULT_NAME);
+ }
+
+ @Test
+ public void testCustomtWithSubstitutionNoDefault() throws IOException {
+ testCustomtWithSubstitution(null);
+ }
+
+ private void testCustomtWithSubstitution(String defaultName) throws IOException {
+ final List<String> resourceData = List.of("A", "[BB]", "C", "Foo",
+ "GoodbyeHello");
+ final Path customFile = createCustomFile("foo", resourceData);
+
+ final Map<String, String> substitutionData = new HashMap(Map.of("B",
+ "Bar", "Foo", "B"));
+ substitutionData.put("Hello", null);
+
+ final List<String> expectedResourceData = List.of("A", "[BarBar]", "C",
+ "B", "Goodbye");
+
+ final List<String> actualResourceData = convertToStringList(saveToFile(
+ new OverridableResource(defaultName)
+ .setPublicName(customFile.getFileName())
+ .setSubstitutionData(substitutionData)
+ .setResourceDir(customFile.getParent())));
+ assertArrayEquals(expectedResourceData.toArray(String[]::new),
+ actualResourceData.toArray(String[]::new));
+
+ // Don't call setPublicName()
+ final Path dstFile = tempFolder.newFolder().toPath().resolve(customFile.getFileName());
+ new OverridableResource(defaultName)
+ .setSubstitutionData(substitutionData)
+ .setResourceDir(customFile.getParent())
+ .saveToFile(dstFile);
+ assertArrayEquals(expectedResourceData.toArray(String[]::new),
+ convertToStringList(Files.readAllBytes(dstFile)).toArray(
+ String[]::new));
+
+ // Verify setSubstitutionData() stores a copy of passed in data
+ Map<String, String> substitutionData2 = new HashMap(substitutionData);
+ var resource = new OverridableResource(defaultName)
+ .setResourceDir(customFile.getParent());
+
+ resource.setSubstitutionData(substitutionData2);
+ substitutionData2.clear();
+ Files.delete(dstFile);
+ resource.saveToFile(dstFile);
+ assertArrayEquals(expectedResourceData.toArray(String[]::new),
+ convertToStringList(Files.readAllBytes(dstFile)).toArray(
+ String[]::new));
+ }
+
+ @Test
+ public void testNoDefault() throws IOException {
+ Path dstFolder = tempFolder.newFolder().toPath();
+ Path dstFile = dstFolder.resolve(Path.of("foo", "bar"));
+
+ new OverridableResource(null).saveToFile(dstFile);
+
+ assertFalse(dstFile.toFile().exists());
+ }
+
+ private final static String DEFAULT_NAME;
+ private final static Map<String, String> SUBSTITUTION_DATA;
+ static {
+ if (Platform.isWindows()) {
+ DEFAULT_NAME = "WinLauncher.template";
+ SUBSTITUTION_DATA = Map.of("COMPANY_NAME", "Foo9090345");
+ } else if (Platform.isLinux()) {
+ DEFAULT_NAME = "template.control";
+ SUBSTITUTION_DATA = Map.of("APPLICATION_PACKAGE", "Package1967");
+ } else if (Platform.isMac()) {
+ DEFAULT_NAME = "Info-lite.plist.template";
+ SUBSTITUTION_DATA = Map.of("DEPLOY_BUNDLE_IDENTIFIER", "12345");
+ } else {
+ throw Platform.throwUnknownPlatformError();
+ }
+ }
+
+ private byte[] saveToFile(OverridableResource resource) throws IOException {
+ Path dstFile = tempFolder.newFile().toPath();
+ resource.saveToFile(dstFile);
+ assertThat(0, is(not(dstFile.toFile().length())));
+
+ return Files.readAllBytes(dstFile);
+ }
+
+ private Path createCustomFile(String publicName, List<String> data) throws
+ IOException {
+ Path resourceFolder = tempFolder.newFolder().toPath();
+ Path customFile = resourceFolder.resolve(publicName);
+
+ Files.write(customFile, data);
+
+ return customFile;
+ }
+
+ private static List<String> convertToStringList(byte[] data) {
+ return List.of(new String(data, StandardCharsets.UTF_8).split("\\R"));
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/junit/jdk/incubator/jpackage/internal/PathGroupTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.incubator.jpackage.internal;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.function.UnaryOperator;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.*;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+
+public class PathGroupTest {
+
+ @Rule
+ public final TemporaryFolder tempFolder = new TemporaryFolder();
+
+ @Test(expected = NullPointerException.class)
+ public void testNullId() {
+ new PathGroup(Map.of()).getPath(null);
+ }
+
+ @Test
+ public void testEmptyPathGroup() {
+ PathGroup pg = new PathGroup(Map.of());
+
+ assertNull(pg.getPath("foo"));
+
+ assertEquals(0, pg.paths().size());
+ assertEquals(0, pg.roots().size());
+ }
+
+ @Test
+ public void testRootsSinglePath() {
+ final PathGroup pg = new PathGroup(Map.of("main", PATH_FOO));
+
+ List<Path> paths = pg.paths();
+ assertEquals(1, paths.size());
+ assertEquals(PATH_FOO, paths.iterator().next());
+
+ List<Path> roots = pg.roots();
+ assertEquals(1, roots.size());
+ assertEquals(PATH_FOO, roots.iterator().next());
+ }
+
+ @Test
+ public void testDuplicatedRoots() {
+ final PathGroup pg = new PathGroup(Map.of("main", PATH_FOO, "another",
+ PATH_FOO, "root", PATH_EMPTY));
+
+ List<Path> paths = pg.paths();
+ Collections.sort(paths);
+
+ assertEquals(3, paths.size());
+ assertEquals(PATH_EMPTY, paths.get(0));
+ assertEquals(PATH_FOO, paths.get(1));
+ assertEquals(PATH_FOO, paths.get(2));
+
+ List<Path> roots = pg.roots();
+ assertEquals(1, roots.size());
+ assertEquals(PATH_EMPTY, roots.get(0));
+ }
+
+ @Test
+ public void testRoots() {
+ final PathGroup pg = new PathGroup(Map.of(1, Path.of("foo"), 2, Path.of(
+ "foo", "bar"), 3, Path.of("foo", "bar", "buz")));
+
+ List<Path> paths = pg.paths();
+ assertEquals(3, paths.size());
+ assertTrue(paths.contains(Path.of("foo")));
+ assertTrue(paths.contains(Path.of("foo", "bar")));
+ assertTrue(paths.contains(Path.of("foo", "bar", "buz")));
+
+ List<Path> roots = pg.roots();
+ assertEquals(1, roots.size());
+ assertEquals(Path.of("foo"), roots.get(0));
+ }
+
+ @Test
+ public void testResolveAt() {
+ final PathGroup pg = new PathGroup(Map.of(0, PATH_FOO, 1, PATH_BAR, 2,
+ PATH_EMPTY));
+
+ final Path aPath = Path.of("a");
+
+ final PathGroup pg2 = pg.resolveAt(aPath);
+ assertThat(pg, not(equalTo(pg2)));
+
+ List<Path> paths = pg.paths();
+ assertEquals(3, paths.size());
+ assertTrue(paths.contains(PATH_EMPTY));
+ assertTrue(paths.contains(PATH_FOO));
+ assertTrue(paths.contains(PATH_BAR));
+ assertEquals(PATH_EMPTY, pg.roots().get(0));
+
+ paths = pg2.paths();
+ assertEquals(3, paths.size());
+ assertTrue(paths.contains(aPath.resolve(PATH_EMPTY)));
+ assertTrue(paths.contains(aPath.resolve(PATH_FOO)));
+ assertTrue(paths.contains(aPath.resolve(PATH_BAR)));
+ assertEquals(aPath, pg2.roots().get(0));
+ }
+
+ @Test
+ public void testTransform() throws IOException {
+ for (var transform : TransformType.values()) {
+ testTransform(false, transform);
+ }
+ }
+
+ @Test
+ public void testTransformWithExcludes() throws IOException {
+ for (var transform : TransformType.values()) {
+ testTransform(true, transform);
+ }
+ }
+
+ enum TransformType { Copy, Move, Handler };
+
+ private void testTransform(boolean withExcludes, TransformType transform)
+ throws IOException {
+ final PathGroup pg = new PathGroup(Map.of(0, PATH_FOO, 1, PATH_BAR, 2,
+ PATH_EMPTY, 3, PATH_BAZ));
+
+ final Path srcDir = tempFolder.newFolder().toPath();
+ final Path dstDir = tempFolder.newFolder().toPath();
+
+ Files.createDirectories(srcDir.resolve(PATH_FOO).resolve("a/b/c/d"));
+ Files.createFile(srcDir.resolve(PATH_FOO).resolve("a/b/c/file1"));
+ Files.createFile(srcDir.resolve(PATH_FOO).resolve("a/b/file2"));
+ Files.createFile(srcDir.resolve(PATH_FOO).resolve("a/b/file3"));
+ Files.createFile(srcDir.resolve(PATH_BAR));
+ Files.createFile(srcDir.resolve(PATH_EMPTY).resolve("file4"));
+ Files.createDirectories(srcDir.resolve(PATH_BAZ).resolve("1/2/3"));
+
+ var dst = pg.resolveAt(dstDir);
+ var src = pg.resolveAt(srcDir);
+ if (withExcludes) {
+ // Exclude from transformation.
+ src.setPath(new Object(), srcDir.resolve(PATH_FOO).resolve("a/b/c"));
+ src.setPath(new Object(), srcDir.resolve(PATH_EMPTY).resolve("file4"));
+ }
+
+ var srcFilesBeforeTransform = walkFiles(srcDir);
+
+ if (transform == TransformType.Handler) {
+ List<Map.Entry<Path, Path>> copyFile = new ArrayList<>();
+ List<Path> createDirectory = new ArrayList<>();
+ src.transform(dst, new PathGroup.TransformHandler() {
+ @Override
+ public void copyFile(Path src, Path dst) throws IOException {
+ copyFile.add(Map.entry(src, dst));
+ }
+
+ @Override
+ public void createDirectory(Path dir) throws IOException {
+ createDirectory.add(dir);
+ }
+ });
+
+ Consumer<Path> assertFile = path -> {
+ var entry = Map.entry(srcDir.resolve(path), dstDir.resolve(path));
+ assertTrue(copyFile.contains(entry));
+ };
+
+ Consumer<Path> assertDir = path -> {
+ assertTrue(createDirectory.contains(dstDir.resolve(path)));
+ };
+
+ assertEquals(withExcludes ? 3 : 5, copyFile.size());
+ assertEquals(withExcludes ? 8 : 10, createDirectory.size());
+
+ assertFile.accept(PATH_FOO.resolve("a/b/file2"));
+ assertFile.accept(PATH_FOO.resolve("a/b/file3"));
+ assertFile.accept(PATH_BAR);
+ assertDir.accept(PATH_FOO.resolve("a/b"));
+ assertDir.accept(PATH_FOO.resolve("a"));
+ assertDir.accept(PATH_FOO);
+ assertDir.accept(PATH_BAZ);
+ assertDir.accept(PATH_BAZ.resolve("1"));
+ assertDir.accept(PATH_BAZ.resolve("1/2"));
+ assertDir.accept(PATH_BAZ.resolve("1/2/3"));
+ assertDir.accept(PATH_EMPTY);
+
+ if (!withExcludes) {
+ assertFile.accept(PATH_FOO.resolve("a/b/c/file1"));
+ assertFile.accept(PATH_EMPTY.resolve("file4"));
+ assertDir.accept(PATH_FOO.resolve("a/b/c/d"));
+ assertDir.accept(PATH_FOO.resolve("a/b/c"));
+ }
+
+ assertArrayEquals(new Path[] { Path.of("") }, walkFiles(dstDir));
+ return;
+ }
+
+ if (transform == TransformType.Copy) {
+ src.copy(dst);
+ } else if (transform == TransformType.Move) {
+ src.move(dst);
+ }
+
+ final List<Path> excludedPaths;
+ if (withExcludes) {
+ excludedPaths = List.of(
+ PATH_EMPTY.resolve("file4"),
+ PATH_FOO.resolve("a/b/c")
+ );
+ } else {
+ excludedPaths = Collections.emptyList();
+ }
+ UnaryOperator<Path[]> removeExcludes = paths -> {
+ return Stream.of(paths)
+ .filter(path -> !excludedPaths.stream().anyMatch(
+ path::startsWith))
+ .collect(Collectors.toList()).toArray(Path[]::new);
+ };
+
+ var dstFiles = walkFiles(dstDir);
+ assertArrayEquals(removeExcludes.apply(srcFilesBeforeTransform), dstFiles);
+
+ if (transform == TransformType.Copy) {
+ assertArrayEquals(dstFiles, removeExcludes.apply(walkFiles(srcDir)));
+ } else if (transform == TransformType.Move) {
+ assertFalse(Files.exists(srcDir));
+ }
+ }
+
+ private static Path[] walkFiles(Path root) throws IOException {
+ try (var files = Files.walk(root)) {
+ return files.map(root::relativize).sorted().collect(
+ Collectors.toList()).toArray(Path[]::new);
+ }
+ }
+
+ private final static Path PATH_FOO = Path.of("foo");
+ private final static Path PATH_BAR = Path.of("bar");
+ private final static Path PATH_BAZ = Path.of("baz");
+ private final static Path PATH_EMPTY = Path.of("");
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/junit/jdk/incubator/jpackage/internal/ToolValidatorTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.incubator.jpackage.internal;
+
+import java.nio.file.Path;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+
+public class ToolValidatorTest {
+
+ @Test
+ public void testAvailable() {
+ assertNull(new ToolValidator(TOOL_JAVA).validate());
+ }
+
+ @Test
+ public void testNotAvailable() {
+ assertValidationFailure(new ToolValidator(TOOL_UNKNOWN).validate(), true);
+ }
+
+ @Test
+ public void testVersionParserUsage() {
+ // Without minimal version configured, version parser should not be used
+ new ToolValidator(TOOL_JAVA).setVersionParser(unused -> {
+ throw new RuntimeException();
+ }).validate();
+
+ // Minimal version is 1, actual is 10. Should be OK.
+ assertNull(new ToolValidator(TOOL_JAVA).setMinimalVersion(
+ new DottedVersion("1")).setVersionParser(unused -> "10").validate());
+
+ // Minimal version is 5, actual is 4.99.37. Error expected.
+ assertValidationFailure(new ToolValidator(TOOL_JAVA).setMinimalVersion(
+ new DottedVersion("5")).setVersionParser(unused -> "4.99.37").validate(),
+ false);
+
+ // Minimal version is 8, actual is 10, lexicographical comparison is used. Error expected.
+ assertValidationFailure(new ToolValidator(TOOL_JAVA).setMinimalVersion(
+ "8").setVersionParser(unused -> "10").validate(), false);
+
+ // Minimal version is 8, actual is 10, Use DottedVersion class for comparison. Should be OK.
+ assertNull(new ToolValidator(TOOL_JAVA).setMinimalVersion(
+ new DottedVersion("8")).setVersionParser(unused -> "10").validate());
+ }
+
+ private static void assertValidationFailure(ConfigException v,
+ boolean withCause) {
+ assertNotNull(v);
+ assertThat("", is(not(v.getMessage().strip())));
+ assertThat("", is(not(v.advice.strip())));
+ if (withCause) {
+ assertNotNull(v.getCause());
+ } else {
+ assertNull(v.getCause());
+ }
+ }
+
+ private final static String TOOL_JAVA;
+ private final static String TOOL_UNKNOWN = Path.of(System.getProperty(
+ "java.home"), "bin").toString();
+
+ static {
+ String fname = "java";
+ if (Platform.isWindows()) {
+ fname = fname + ".exe";
+ }
+ TOOL_JAVA = Path.of(System.getProperty("java.home"), "bin", fname).toString();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/junit/junit.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @summary jpackage unit tests
+ * @library ${jtreg.home}/lib/junit.jar
+ * @run shell run_junit.sh
+ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/junit/run_junit.sh Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+set -x
+
+set -e
+if [ -z "$BASH" ]; then
+ # The script relies on Bash arrays, rerun in Bash.
+ /bin/bash $0 $@
+ exit
+fi
+
+sources=()
+classes=()
+for s in $(find "${TESTSRC}" -name "*.java" | grep -v junit.java); do
+ sources+=( "$s" )
+ classes+=( $(echo "$s" | sed -e "s|${TESTSRC}/||" -e 's|/|.|g' -e 's/.java$//') )
+done
+
+common_args=(\
+ --add-modules jdk.incubator.jpackage \
+ --patch-module jdk.incubator.jpackage="${TESTSRC}${PS}${TESTCLASSES}" \
+ --add-reads jdk.incubator.jpackage=ALL-UNNAMED \
+ --add-exports jdk.incubator.jpackage/jdk.incubator.jpackage.internal=ALL-UNNAMED \
+ -classpath "${TESTCLASSPATH}" \
+)
+
+# Compile classes for junit
+"${COMPILEJAVA}/bin/javac" ${TESTTOOLVMOPTS} ${TESTJAVACOPTS} \
+ "${common_args[@]}" -d "${TESTCLASSES}" "${sources[@]}"
+
+# Run junit
+"${TESTJAVA}/bin/java" ${TESTVMOPTS} ${TESTJAVAOPTS} \
+ "${common_args[@]}" org.junit.runner.JUnitCore "${classes[@]}"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/linux/AppCategoryTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+
+
+/**
+ * Test --linux-app-category parameter. Output of the test should be
+ * appcategorytest_1.0-1_amd64.deb or appcategorytest-1.0-1.amd64.rpm package
+ * bundle. The output package should provide the same functionality as the
+ * default package.
+ *
+ * deb:
+ * Section property of the package should be set to Foo value.
+ *
+ * rpm:
+ * Group property of the package should be set to Foo value.
+ */
+
+
+/*
+ * @test
+ * @summary jpackage with --linux-app-category
+ * @library ../helpers
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "linux")
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m AppCategoryTest
+ */
+public class AppCategoryTest {
+
+ public static void main(String[] args) {
+ final String CATEGORY = "Foo";
+
+ TKit.run(args, () -> {
+ new PackageTest()
+ .forTypes(PackageType.LINUX)
+ .configureHelloApp()
+ .addInitializer(cmd -> {
+ cmd.addArguments("--linux-app-category", CATEGORY);
+ })
+ .forTypes(PackageType.LINUX_DEB)
+ .addBundlePropertyVerifier("Section", CATEGORY)
+ .forTypes(PackageType.LINUX_RPM)
+ .addBundlePropertyVerifier("Group", CATEGORY)
+ .run();
+ });
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/linux/LicenseTypeTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+
+
+/**
+ * Test --linux-rpm-license-type parameter. Output of the test should be
+ * licensetypetest-1.0-1.amd64.rpm package bundle. The output package
+ * should provide the same functionality as the
+ * default package.
+ * License property of the package should be set to JP_LICENSE_TYPE.
+ */
+
+
+/*
+ * @test
+ * @summary jpackage with --linux-rpm-license-type
+ * @library ../helpers
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "linux")
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m LicenseTypeTest
+ */
+public class LicenseTypeTest {
+
+ public static void main(String[] args) {
+ final String LICENSE_TYPE = "JP_LICENSE_TYPE";
+
+ TKit.run(args, () -> {
+ new PackageTest().forTypes(PackageType.LINUX_RPM).configureHelloApp()
+ .addInitializer(cmd -> {
+ cmd.addArguments("--linux-rpm-license-type", LICENSE_TYPE);
+ })
+ .addBundlePropertyVerifier("License", LICENSE_TYPE)
+ .run();
+ });
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/linux/LinuxBundleNameTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+
+
+/**
+ * Test --linux-package-name parameter. Output of the test should be
+ * quickbrownfox2_1.0-1_amd64.deb or quickbrownfox2-1.0-1.amd64.rpm package
+ * bundle. The output package should provide the same functionality as the
+ * default package.
+ *
+ * deb:
+ * Package property of the package should be set to quickbrownfox2.
+ *
+ * rpm:
+ * Name property of the package should be set to quickbrownfox2.
+ */
+
+
+/*
+ * @test
+ * @summary jpackage with --linux-package-name
+ * @library ../helpers
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "linux")
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m LinuxBundleNameTest
+ */
+public class LinuxBundleNameTest {
+
+ public static void main(String[] args) {
+ final String PACKAGE_NAME = "quickbrownfox2";
+
+ TKit.run(args, () -> {
+ new PackageTest()
+ .forTypes(PackageType.LINUX)
+ .configureHelloApp()
+ .addInitializer(cmd -> {
+ cmd.addArguments("--linux-package-name", PACKAGE_NAME);
+ })
+ .forTypes(PackageType.LINUX_DEB)
+ .addBundlePropertyVerifier("Package", PACKAGE_NAME)
+ .forTypes(PackageType.LINUX_RPM)
+ .addBundlePropertyVerifier("Name", PACKAGE_NAME)
+ .run();
+ });
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/linux/LinuxResourceTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.IOException;
+import java.nio.file.Path;
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.LinuxHelper;
+import jdk.jpackage.test.Annotations.Test;
+import java.util.List;
+
+/*
+ * @test
+ * @summary jpackage with --resource-dir
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "linux")
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile LinuxResourceTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=LinuxResourceTest
+ */
+
+public class LinuxResourceTest {
+ @Test
+ public static void testHardcodedProperties() throws IOException {
+ new PackageTest()
+ .forTypes(PackageType.LINUX)
+ .configureHelloApp()
+ .addInitializer(cmd -> {
+ cmd
+ .setFakeRuntime()
+ .saveConsoleOutput(true)
+ .addArguments("--resource-dir", TKit.createTempDirectory("resources"));
+ })
+ .forTypes(PackageType.LINUX_DEB)
+ .addInitializer(cmd -> {
+ Path controlFile = Path.of(cmd.getArgumentValue("--resource-dir"),
+ "control");
+ TKit.createTextFile(controlFile, List.of(
+ "Package: dont-install-me",
+ "Version: 1.2.3-R2",
+ "Section: APPLICATION_SECTION",
+ "Maintainer: APPLICATION_MAINTAINER",
+ "Priority: optional",
+ "Architecture: bar",
+ "Provides: dont-install-me",
+ "Description: APPLICATION_DESCRIPTION",
+ "Installed-Size: APPLICATION_INSTALLED_SIZE",
+ "Depends: PACKAGE_DEFAULT_DEPENDENCIES"
+ ));
+ })
+ .addBundleVerifier((cmd, result) -> {
+ TKit.assertTextStream("Using custom package resource [DEB control file]")
+ .predicate(String::startsWith)
+ .apply(result.getOutput().stream());
+ TKit.assertTextStream(String.format(
+ "Expected value of \"Package\" property is [%s]. Actual value in output package is [dont-install-me]",
+ LinuxHelper.getPackageName(cmd)))
+ .predicate(String::startsWith)
+ .apply(result.getOutput().stream());
+ TKit.assertTextStream(
+ "Expected value of \"Version\" property is [1.0-1]. Actual value in output package is [1.2.3-R2]")
+ .predicate(String::startsWith)
+ .apply(result.getOutput().stream());
+ TKit.assertTextStream(String.format(
+ "Expected value of \"Architecture\" property is [%s]. Actual value in output package is [bar]",
+ LinuxHelper.getDefaultPackageArch(cmd.packageType())))
+ .predicate(String::startsWith)
+ .apply(result.getOutput().stream());
+ })
+ .forTypes(PackageType.LINUX_RPM)
+ .addInitializer(cmd -> {
+ Path specFile = Path.of(cmd.getArgumentValue("--resource-dir"),
+ LinuxHelper.getPackageName(cmd) + ".spec");
+ TKit.createTextFile(specFile, List.of(
+ "Name: dont-install-me",
+ "Version: 1.2.3",
+ "Release: R2",
+ "Summary: APPLICATION_SUMMARY",
+ "License: APPLICATION_LICENSE_TYPE",
+ "Prefix: %{dirname:APPLICATION_DIRECTORY}",
+ "Provides: dont-install-me",
+ "%description",
+ "APPLICATION_DESCRIPTION",
+ "%prep",
+ "%build",
+ "%install",
+ "rm -rf %{buildroot}",
+ "install -d -m 755 %{buildroot}APPLICATION_DIRECTORY",
+ "cp -r %{_sourcedir}APPLICATION_DIRECTORY/* %{buildroot}APPLICATION_DIRECTORY",
+ "%files",
+ "APPLICATION_DIRECTORY"
+ ));
+ })
+ .addBundleVerifier((cmd, result) -> {
+ TKit.assertTextStream("Using custom package resource [RPM spec file]")
+ .predicate(String::startsWith)
+ .apply(result.getOutput().stream());
+ TKit.assertTextStream(String.format(
+ "Expected value of \"Name\" property is [%s]. Actual value in output package is [dont-install-me]",
+ LinuxHelper.getPackageName(cmd)))
+ .predicate(String::startsWith)
+ .apply(result.getOutput().stream());
+ TKit.assertTextStream(
+ "Expected value of \"Version\" property is [1.0]. Actual value in output package is [1.2.3]")
+ .predicate(String::startsWith)
+ .apply(result.getOutput().stream());
+ TKit.assertTextStream(
+ "Expected value of \"Release\" property is [1]. Actual value in output package is [R2]")
+ .predicate(String::startsWith)
+ .apply(result.getOutput().stream());
+ })
+ .run();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/linux/MaintainerTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.TKit;
+
+
+/**
+ * Test --linux-deb-maintainer parameter. Output of the test should be
+ * maintainertest_1.0-1_amd64.deb package bundle. The output package
+ * should provide the same functionality as the
+ * default package.
+ * Value of Maintainer property of the package should contain
+ * jpackage-test@java.com email address.
+ */
+
+
+/*
+ * @test
+ * @summary jpackage with --linux-deb-maintainer
+ * @library ../helpers
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "linux")
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m MaintainerTest
+ */
+public class MaintainerTest {
+
+ public static void main(String[] args) {
+ final String MAINTAINER = "jpackage-test@java.com";
+
+ TKit.run(args, () -> {
+ new PackageTest().forTypes(PackageType.LINUX_DEB).configureHelloApp()
+ .addInitializer(cmd -> {
+ cmd.addArguments("--linux-deb-maintainer", MAINTAINER);
+ })
+ .addBundlePropertyVerifier("Maintainer", (propName, propValue) -> {
+ String lookupValue = "<" + MAINTAINER + ">";
+ TKit.assertTrue(propValue.endsWith(lookupValue),
+ String.format("Check value of %s property [%s] ends with %s",
+ propName, propValue, lookupValue));
+ })
+ .run();
+ });
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/linux/PackageDepsTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.LinuxHelper;
+
+
+/**
+ * Test --linux-package-deps parameter. Output of the test should be
+ * apackagedepstestprereq_1.0-1_amd64.deb and packagedepstest_1.0-1_amd64.deb or
+ * apackagedepstestprereq-1.0-1.amd64.rpm and packagedepstest-1.0-1.amd64.rpm
+ * package bundles. The output packages should provide the same functionality as
+ * the default package.
+ *
+ * deb: Value of Depends property of packagedepstest package should contain
+ * apackagedepstestprereq word.
+ *
+ * rpm: Value of Requires property of packagedepstest package should contain
+ * apackagedepstestprereq word.
+ */
+
+
+/*
+ * @test
+ * @summary jpackage with --linux-package-deps
+ * @library ../helpers
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "linux")
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m PackageDepsTest
+ */
+public class PackageDepsTest {
+
+ public static void main(String[] args) {
+ // Pick the name of prerequisite package to be alphabetically
+ // preceeding the main package name.
+ // This is needed to make Bash script batch installing/uninstalling packages
+ // produced by jtreg tests install/uninstall packages in the right order.
+ final String PREREQ_PACKAGE_NAME = "apackagedepstestprereq";
+
+ TKit.run(args, () -> {
+ new PackageTest()
+ .forTypes(PackageType.LINUX)
+ .configureHelloApp()
+ .addInitializer(cmd -> {
+ cmd.setArgumentValue("--name", PREREQ_PACKAGE_NAME);
+ })
+ .run();
+
+ new PackageTest()
+ .forTypes(PackageType.LINUX)
+ .configureHelloApp()
+ .addInitializer(cmd -> {
+ cmd.addArguments("--linux-package-deps", PREREQ_PACKAGE_NAME);
+ })
+ .forTypes(PackageType.LINUX)
+ .addBundleVerifier(cmd -> {
+ TKit.assertTrue(
+ LinuxHelper.getPrerequisitePackages(cmd).contains(
+ PREREQ_PACKAGE_NAME), String.format(
+ "Check package depends on [%s] package",
+ PREREQ_PACKAGE_NAME));
+ })
+ .run();
+ });
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/linux/ReleaseTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.TKit;
+
+
+/**
+ * Test --linux-app-release parameter. Output of the test should be
+ * releasetest_1.0-Rc3_amd64.deb or releasetest-1.0-Rc3.amd64.rpm package
+ * bundle. The output package should provide the same functionality as the
+ * default package.
+ *
+ * deb:
+ * Version property of the package should end with -Rc3 substring.
+ *
+ * rpm:
+ * Release property of the package should be set to Rc3 value.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --linux-app-release
+ * @library ../helpers
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "linux")
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m ReleaseTest
+ */
+public class ReleaseTest {
+
+ public static void main(String[] args) {
+ final String RELEASE = "Rc3";
+
+ TKit.run(args, () -> {
+ new PackageTest()
+ .forTypes(PackageType.LINUX)
+ .configureHelloApp()
+ .addInitializer(cmd -> {
+ cmd.addArguments("--linux-app-release", RELEASE);
+ })
+ .forTypes(PackageType.LINUX_RPM)
+ .addBundlePropertyVerifier("Release", RELEASE)
+ .forTypes(PackageType.LINUX_DEB)
+ .addBundlePropertyVerifier("Version", (propName, propValue) -> {
+ TKit.assertTrue(propValue.endsWith("-" + RELEASE),
+ String.format("Check value of %s property [%s] ends with %s",
+ propName, propValue, RELEASE));
+ })
+ .run();
+ });
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/linux/ShortcutHintTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.Map;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.stream.Collectors;
+import jdk.jpackage.test.FileAssociations;
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.Annotations.Test;
+import jdk.jpackage.test.*;
+
+/**
+ * Test --linux-shortcut parameter. Output of the test should be
+ * shortcuthinttest_1.0-1_amd64.deb or shortcuthinttest-1.0-1.amd64.rpm package
+ * bundle. The output package should provide the same functionality as the
+ * default package and also create a desktop shortcut.
+ *
+ * Finding a shortcut of the application launcher through GUI depends on desktop
+ * environment.
+ *
+ * deb:
+ * Search online for `Ways To Open A Ubuntu Application` for instructions.
+ *
+ * rpm:
+ *
+ */
+
+/*
+ * @test
+ * @summary jpackage with --linux-shortcut
+ * @library ../helpers
+ * @key jpackagePlatformPackage
+ * @requires jpackage.test.SQETest == null
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "linux")
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile ShortcutHintTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=ShortcutHintTest
+ */
+
+/*
+ * @test
+ * @summary jpackage with --linux-shortcut
+ * @library ../helpers
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "linux")
+ * @requires jpackage.test.SQETest != null
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile ShortcutHintTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=ShortcutHintTest.testBasic
+ */
+
+public class ShortcutHintTest {
+
+ @Test
+ public static void testBasic() {
+ createTest().addInitializer(cmd -> {
+ cmd.addArgument("--linux-shortcut");
+ }).run();
+ }
+
+ private static PackageTest createTest() {
+ return new PackageTest()
+ .forTypes(PackageType.LINUX)
+ .configureHelloApp()
+ .addBundleDesktopIntegrationVerifier(true);
+
+ }
+
+ /**
+ * Adding `--icon` to jpackage command line should create desktop shortcut
+ * even though `--linux-shortcut` is omitted.
+ */
+ @Test
+ public static void testCustomIcon() {
+ createTest().addInitializer(cmd -> {
+ cmd.setFakeRuntime();
+ cmd.addArguments("--icon", TKit.TEST_SRC_ROOT.resolve(
+ "apps/dukeplug.png"));
+ }).run();
+ }
+
+ /**
+ * Adding `--file-associations` to jpackage command line should create
+ * desktop shortcut even though `--linux-shortcut` is omitted.
+ */
+ @Test
+ public static void testFileAssociations() {
+ PackageTest test = createTest().addInitializer(
+ JPackageCommand::setFakeRuntime);
+ new FileAssociations("ShortcutHintTest_testFileAssociations").applyTo(
+ test);
+ test.run();
+ }
+
+ /**
+ * Additional launcher with icon should create desktop shortcut even though
+ * `--linux-shortcut` is omitted.
+ */
+ @Test
+ public static void testAdditionaltLaunchers() {
+ createTest().addInitializer(cmd -> {
+ cmd.setFakeRuntime();
+
+ final String launcherName = "Foo";
+ final Path propsFile = TKit.workDir().resolve(
+ launcherName + ".properties");
+
+ cmd.addArguments("--add-launcher", String.format("%s=%s",
+ launcherName, propsFile));
+
+ TKit.createPropertiesFile(propsFile, Map.entry("icon",
+ TKit.TEST_SRC_ROOT.resolve("apps/dukeplug.png").toString()));
+ }).run();
+ }
+
+ /**
+ * .desktop file from resource dir.
+ */
+ @Test
+ public static void testDesktopFileFromResourceDir() {
+ final String expectedVersionString = "Version=12345678";
+ TKit.withTempDirectory("resources", tempDir -> {
+ createTest().addInitializer(cmd -> {
+ cmd.setFakeRuntime();
+
+ cmd.addArgument("--linux-shortcut");
+ cmd.addArguments("--resource-dir", tempDir);
+
+ // Create custom .desktop file in resource directory
+ TKit.createTextFile(tempDir.resolve(cmd.name() + ".desktop"),
+ List.of(
+ "[Desktop Entry]",
+ "Name=APPLICATION_NAME",
+ "Exec=APPLICATION_LAUNCHER",
+ "Terminal=false",
+ "Type=Application",
+ "Categories=DEPLOY_BUNDLE_CATEGORY",
+ expectedVersionString
+ ));
+ })
+ .addInstallVerifier(cmd -> {
+ Path desktopFile = cmd.appLayout().destktopIntegrationDirectory().resolve(
+ String.format("%s-%s.desktop",
+ LinuxHelper.getPackageName(cmd), cmd.name()));
+ TKit.assertFileExists(desktopFile);
+ TKit.assertTextStream(expectedVersionString)
+ .label(String.format("[%s] file", desktopFile))
+ .predicate(String::equals)
+ .apply(Files.readAllLines(desktopFile).stream());
+ }).run();
+ });
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/macosx/MacPropertiesTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.MacHelper;
+import jdk.jpackage.test.JPackageCommand;
+import jdk.jpackage.test.Annotations.Test;
+import jdk.jpackage.test.Annotations.Parameter;
+
+
+/**
+ * Test --mac-package-name, --mac-package-identifier parameters.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --mac-package-name, --mac-package-identifier
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "mac")
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile MacPropertiesTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=MacPropertiesTest
+ */
+public class MacPropertiesTest {
+ @Test
+ @Parameter("MacPackageNameTest")
+ public void testPackageName(String packageName) {
+ testParameterInAppImage("--mac-package-name", "CFBundleName",
+ packageName);
+ }
+
+ @Test
+ @Parameter("Foo")
+ public void testPackageIdetifier(String packageId) {
+ testParameterInAppImage("--mac-package-identifier", "CFBundleIdentifier",
+ packageId);
+ }
+
+ private static void testParameterInAppImage(String jpackageParameterName,
+ String plistKeyName, String value) {
+ JPackageCommand cmd = JPackageCommand.helloAppImage()
+ .addArguments(jpackageParameterName, value);
+
+ cmd.executeAndAssertHelloAppImageCreated();
+
+ var plist = MacHelper.readPListFromAppImage(cmd.outputBundle());
+
+ TKit.assertEquals(value, plist.queryValue(plistKeyName), String.format(
+ "Check value of %s plist key", plistKeyName));
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/macosx/NameWithSpaceTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.Annotations.Test;
+
+/**
+ * Name with space packaging test. Output of the test should be
+ * "Name With Space-*.*" package bundle.
+ *
+ * macOS only:
+ *
+ * Test should generates basic pkg and dmg. Name of packages and application itself
+ * should have name: "Name With Space". Package should be installed into "/Applications"
+ * folder and verified that it can be installed and run.
+ */
+
+/*
+ * @test
+ * @summary jpackage test with name containing spaces
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile NameWithSpaceTest.java
+ * @requires (os.family == "mac")
+ * @key jpackagePlatformPackage
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=NameWithSpaceTest
+ */
+public class NameWithSpaceTest {
+
+ @Test
+ public static void test() {
+ new PackageTest()
+ .configureHelloApp()
+ .addBundleDesktopIntegrationVerifier(false)
+ .addInitializer(cmd -> {
+ cmd.setArgumentValue("--name", "Name With Space");
+ })
+ .run();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/macosx/SigningAppImageTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.nio.file.Path;
+import jdk.jpackage.test.JPackageCommand;
+import jdk.jpackage.test.TKit;
+
+/**
+ * Tests generation of app image with --mac-sign and related arguments. Test will
+ * generate app image and verify signature of main launcher and app bundle itself.
+ * This test requires that machine is configured with test certificate for
+ * "Developer ID Application: jpackage.openjdk.java.net" in jpackagerTest keychain with
+ * always allowed access to this keychain for user which runs test.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --type app-image --mac-sign
+ * @library ../helpers
+ * @library /test/lib
+ * @library base
+ * @build SigningBase
+ * @build SigningCheck
+ * @build jtreg.SkippedException
+ * @build jdk.jpackage.test.*
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @requires (os.family == "mac")
+ * @run main/othervm -Xmx512m SigningAppImageTest
+ */
+public class SigningAppImageTest {
+
+ public static void main(String[] args) throws Exception {
+ TKit.run(args, () -> {
+ SigningCheck.checkCertificates();
+
+ JPackageCommand cmd = JPackageCommand.helloAppImage();
+ cmd.addArguments("--mac-sign", "--mac-signing-key-user-name",
+ SigningBase.DEV_NAME, "--mac-signing-keychain",
+ "jpackagerTest.keychain");
+ cmd.executeAndAssertHelloAppImageCreated();
+
+ Path launcherPath = cmd.appLauncherPath();
+ SigningBase.verifyCodesign(launcherPath, true);
+
+ Path appImage = cmd.outputBundle();
+ SigningBase.verifyCodesign(appImage, true);
+ SigningBase.verifySpctl(appImage, "exec");
+ });
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/macosx/SigningPackageTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import jdk.jpackage.test.*;
+
+/**
+ * Tests generation of dmg and pkg with --mac-sign and related arguments. Test will
+ * generate pkg and verifies its signature. It verifies that dmg is not signed, but app
+ * image inside dmg is signed. This test requires that machine is configured with test
+ * certificate for "Developer ID Installer: jpackage.openjdk.java.net" in jpackagerTest
+ * keychain with always allowed access to this keychain for user which runs test.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --type pkg,dmg --mac-sign
+ * @library ../helpers
+ * @library /test/lib
+ * @library base
+ * @key jpackagePlatformPackage
+ * @build SigningBase
+ * @build SigningCheck
+ * @build jtreg.SkippedException
+ * @build jdk.jpackage.test.*
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @requires (os.family == "mac")
+ * @run main/othervm -Xmx512m SigningPackageTest
+ */
+public class SigningPackageTest {
+
+ private static void verifyPKG(JPackageCommand cmd) {
+ Path outputBundle = cmd.outputBundle();
+ SigningBase.verifyPkgutil(outputBundle);
+ SigningBase.verifySpctl(outputBundle, "install");
+ }
+
+ private static void verifyDMG(JPackageCommand cmd) {
+ Path outputBundle = cmd.outputBundle();
+ SigningBase.verifyCodesign(outputBundle, false);
+ }
+
+ private static void verifyAppImageInDMG(JPackageCommand cmd) {
+ MacHelper.withExplodedDmg(cmd, dmgImage -> {
+ Path launcherPath = dmgImage.resolve(Path.of("Contents", "MacOS", cmd.name()));
+ SigningBase.verifyCodesign(launcherPath, true);
+ SigningBase.verifyCodesign(dmgImage, true);
+ SigningBase.verifySpctl(dmgImage, "exec");
+ });
+ }
+
+ public static void main(String[] args) throws Exception {
+ TKit.run(args, () -> {
+ SigningCheck.checkCertificates();
+
+ new PackageTest()
+ .configureHelloApp()
+ .forTypes(PackageType.MAC)
+ .addInitializer(cmd -> {
+ cmd.addArguments("--mac-sign",
+ "--mac-signing-key-user-name", SigningBase.DEV_NAME,
+ "--mac-signing-keychain", "jpackagerTest.keychain");
+ })
+ .forTypes(PackageType.MAC_PKG)
+ .addBundleVerifier(SigningPackageTest::verifyPKG)
+ .forTypes(PackageType.MAC_DMG)
+ .addBundleVerifier(SigningPackageTest::verifyDMG)
+ .addBundleVerifier(SigningPackageTest::verifyAppImageInDMG)
+ .run();
+ });
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/macosx/base/SigningBase.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.nio.file.Path;
+import java.util.List;
+
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.Executor;
+
+public class SigningBase {
+
+ public static String DEV_NAME = "jpackage.openjdk.java.net";
+ public static String APP_CERT
+ = "Developer ID Application: " + DEV_NAME;
+ public static String INSTALLER_CERT
+ = "Developer ID Installer: " + DEV_NAME;
+ public static String KEYCHAIN = "jpackagerTest.keychain";
+
+ private static void checkString(List<String> result, String lookupString) {
+ TKit.assertTextStream(lookupString).predicate(
+ (line, what) -> line.trim().equals(what)).apply(result.stream());
+ }
+
+ private static List<String> codesignResult(Path target, boolean signed) {
+ int exitCode = signed ? 0 : 1;
+ List<String> result = new Executor()
+ .setExecutable("codesign")
+ .addArguments("--verify", "--deep", "--strict", "--verbose=2",
+ target.toString())
+ .saveOutput()
+ .execute()
+ .assertExitCodeIs(exitCode).getOutput();
+
+ return result;
+ }
+
+ private static void verifyCodesignResult(List<String> result, Path target,
+ boolean signed) {
+ result.stream().forEachOrdered(TKit::trace);
+ if (signed) {
+ String lookupString = target.toString() + ": valid on disk";
+ checkString(result, lookupString);
+ lookupString = target.toString() + ": satisfies its Designated Requirement";
+ checkString(result, lookupString);
+ } else {
+ String lookupString = target.toString()
+ + ": code object is not signed at all";
+ checkString(result, lookupString);
+ }
+ }
+
+ private static List<String> spctlResult(Path target, String type) {
+ List<String> result = new Executor()
+ .setExecutable("/usr/sbin/spctl")
+ .addArguments("-vvv", "--assess", "--type", type,
+ target.toString())
+ .executeAndGetOutput();
+
+ return result;
+ }
+
+ private static void verifySpctlResult(List<String> result, Path target, String type) {
+ result.stream().forEachOrdered(TKit::trace);
+ String lookupString = target.toString() + ": accepted";
+ checkString(result, lookupString);
+ lookupString = "source=" + DEV_NAME;
+ checkString(result, lookupString);
+ if (type.equals("install")) {
+ lookupString = "origin=" + INSTALLER_CERT;
+ } else {
+ lookupString = "origin=" + APP_CERT;
+ }
+ checkString(result, lookupString);
+ }
+
+ private static List<String> pkgutilResult(Path target) {
+ List<String> result = new Executor()
+ .setExecutable("/usr/sbin/pkgutil")
+ .addArguments("--check-signature",
+ target.toString())
+ .executeAndGetOutput();
+
+ return result;
+ }
+
+ private static void verifyPkgutilResult(List<String> result) {
+ result.stream().forEachOrdered(TKit::trace);
+ String lookupString = "Status: signed by a certificate trusted for current user";
+ checkString(result, lookupString);
+ lookupString = "1. " + INSTALLER_CERT;
+ checkString(result, lookupString);
+ }
+
+ public static void verifyCodesign(Path target, boolean signed) {
+ List<String> result = codesignResult(target, signed);
+ verifyCodesignResult(result, target, signed);
+ }
+
+ public static void verifySpctl(Path target, String type) {
+ List<String> result = spctlResult(target, type);
+ verifySpctlResult(result, target, type);
+ }
+
+ public static void verifyPkgutil(Path target) {
+ List<String> result = pkgutilResult(target);
+ verifyPkgutilResult(result);
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/macosx/base/SigningCheck.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.Executor;
+
+import jdk.incubator.jpackage.internal.MacCertificate;
+
+public class SigningCheck {
+
+ public static void checkCertificates() {
+ List<String> result = findCertificate(SigningBase.APP_CERT, SigningBase.KEYCHAIN);
+ String key = findKey(SigningBase.APP_CERT, result);
+ validateCertificate(key);
+ validateCertificateTrust(SigningBase.APP_CERT);
+
+ result = findCertificate(SigningBase.INSTALLER_CERT, SigningBase.KEYCHAIN);
+ key = findKey(SigningBase.INSTALLER_CERT, result);
+ validateCertificate(key);
+ validateCertificateTrust(SigningBase.INSTALLER_CERT);
+ }
+
+ private static List<String> findCertificate(String name, String keyChain) {
+ List<String> result = new Executor()
+ .setExecutable("security")
+ .addArguments("find-certificate", "-c", name, "-a", keyChain)
+ .executeAndGetOutput();
+
+ return result;
+ }
+
+ private static String findKey(String name, List<String> result) {
+ Pattern p = Pattern.compile("\"alis\"<blob>=\"([^\"]+)\"");
+ Matcher m = p.matcher(result.stream().collect(Collectors.joining()));
+ if (!m.find()) {
+ TKit.trace("Did not found a key for '" + name + "'");
+ return null;
+ }
+ String matchedKey = m.group(1);
+ if (m.find()) {
+ TKit.trace("Found more than one key for '" + name + "'");
+ return null;
+ }
+ TKit.trace("Using key '" + matchedKey);
+ return matchedKey;
+ }
+
+ private static void validateCertificate(String key) {
+ if (key != null) {
+ MacCertificate certificate = new MacCertificate(key);
+ if (!certificate.isValid()) {
+ TKit.throwSkippedException("Certifcate expired: " + key);
+ } else {
+ return;
+ }
+ }
+
+ TKit.throwSkippedException("Cannot find required certifciates: " + key);
+ }
+
+ private static void validateCertificateTrust(String name) {
+ List<String> result = new Executor()
+ .setExecutable("security")
+ .addArguments("dump-trust-settings")
+ .executeAndGetOutput();
+ result.stream().forEachOrdered(TKit::trace);
+ TKit.assertTextStream(name)
+ .predicate((line, what) -> line.trim().endsWith(what))
+ .orElseThrow(() -> TKit.throwSkippedException(
+ "Certifcate not trusted by current user: " + name))
+ .apply(result.stream());
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/manage_packages.sh Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,231 @@
+#!/bin/bash
+
+#
+# Script to install/uninstall packages produced by jpackage jtreg
+# tests doing platform specific packaging.
+#
+# The script will install/uninstall all packages from the files
+# found in the current directory or the one specified with command line option.
+#
+# When jtreg jpackage tests are executed with jpackage.test.output
+# Java property set, produced package files (msi, exe, deb, rpm, etc.) will
+# be saved in the directory specified with this property.
+#
+# Usage example:
+# # Set directory where to save package files from jtreg jpackage tests
+# JTREG_OUTPUT_DIR=/tmp/jpackage_jtreg_packages
+#
+# # Run tests and fill $JTREG_OUTPUT_DIR directory with package files
+# jtreg -Djpackage.test.output=$JTREG_OUTPUT_DIR ...
+#
+# # Install all packages
+# manage_pachages.sh -d $JTREG_OUTPUT_DIR
+#
+# # Uninstall all packages
+# manage_pachages.sh -d $JTREG_OUTPUT_DIR -u
+#
+
+#
+# When using with MSI installers, Cygwin shell from which this script is
+# executed should be started as administrator. Otherwise silent installation
+# won't work.
+#
+
+# Fail fast
+set -e; set -o pipefail;
+
+
+help_usage ()
+{
+ echo "Usage: `basename $0` [OPTION]"
+ echo "Options:"
+ echo " -h - print this message"
+ echo " -v - verbose output"
+ echo " -d <dir> - path to directory where to look for package files"
+ echo " -u - uninstall packages instead of the default install"
+ echo " -t - dry run, print commands but don't execute them"
+}
+
+error ()
+{
+ echo "$@" > /dev/stderr
+}
+
+fatal ()
+{
+ error "$@"
+ exit 1
+}
+
+fatal_with_help_usage ()
+{
+ error "$@"
+ help_usage
+ exit 1
+}
+
+# For macOS
+if !(type "tac" &> /dev/null;) then
+ tac_cmd='tail -r'
+else
+ tac_cmd=tac
+fi
+
+# Directory where to look for package files.
+package_dir=$PWD
+
+# Script debug.
+verbose=
+
+# Operation mode.
+mode=install
+
+dryrun=
+
+while getopts "vhd:ut" argname; do
+ case "$argname" in
+ v) verbose=yes;;
+ t) dryrun=yes;;
+ u) mode=uninstall;;
+ d) package_dir="$OPTARG";;
+ h) help_usage; exit 0;;
+ ?) help_usage; exit 1;;
+ esac
+done
+shift $(( OPTIND - 1 ))
+
+[ -d "$package_dir" ] || fatal_with_help_usage "Package directory [$package_dir] is not a directory"
+
+[ -z "$verbose" ] || set -x
+
+
+function find_packages_of_type ()
+{
+ # sort output alphabetically
+ find "$package_dir" -maxdepth 1 -type f -name '*.'"$1" | sort
+}
+
+function find_packages ()
+{
+ local package_suffixes=(deb rpm msi exe pkg dmg)
+ for suffix in "${package_suffixes[@]}"; do
+ if [ "$mode" == "uninstall" ]; then
+ packages=$(find_packages_of_type $suffix | $tac_cmd)
+ else
+ packages=$(find_packages_of_type $suffix)
+ fi
+ if [ -n "$packages" ]; then
+ package_type=$suffix
+ break;
+ fi
+ done
+}
+
+
+# RPM
+install_cmd_rpm ()
+{
+ echo sudo rpm --install "$@"
+}
+uninstall_cmd_rpm ()
+{
+ local package_name=$(rpm -qp --queryformat '%{Name}' "$@")
+ echo sudo rpm -e "$package_name"
+}
+
+# DEB
+install_cmd_deb ()
+{
+ echo sudo dpkg -i "$@"
+}
+uninstall_cmd_deb ()
+{
+ local package_name=$(dpkg-deb -f "$@" Package)
+ echo sudo dpkg -r "$package_name"
+}
+
+# MSI
+install_cmd_msi ()
+{
+ echo msiexec /qn /norestart /i $(cygpath -w "$@")
+}
+uninstall_cmd_msi ()
+{
+ echo msiexec /qn /norestart /x $(cygpath -w "$@")
+}
+
+# EXE
+install_cmd_exe ()
+{
+ echo "$@"
+}
+uninstall_cmd_exe ()
+{
+ error No implemented
+}
+
+# PKG
+install_cmd_pkg ()
+{
+ echo sudo /usr/sbin/installer -allowUntrusted -pkg "\"$@\"" -target /
+}
+uninstall_cmd_pkg ()
+{
+ local pname=`basename $@`
+ local appname="$(cut -d'-' -f1 <<<"$pname")"
+ if [ "$appname" = "CommonInstallDirTest" ]; then
+ echo sudo rm -rf "/Applications/jpackage/\"$appname.app\""
+ else
+ echo sudo rm -rf "/Applications/\"$appname.app\""
+ fi
+}
+
+# DMG
+install_cmd_dmg ()
+{
+ local pname=`basename $@`
+ local appname="$(cut -d'-' -f1 <<<"$pname")"
+ local command=()
+ if [ "$appname" = "CommonLicenseTest" ]; then
+ command+=("{" yes "|" hdiutil attach "\"$@\"" ">" /dev/null)
+ else
+ command+=("{" hdiutil attach "\"$@\"" ">" /dev/null)
+ fi
+
+ command+=(";" sudo cp -R "\"/Volumes/$appname/$appname.app\"" /Applications ">" /dev/null)
+ command+=(";" hdiutil detach "\"/Volumes/$appname\"" ">" /dev/null ";}")
+
+ echo "${command[@]}"
+}
+uninstall_cmd_dmg ()
+{
+ local pname=`basename $@`
+ local appname="$(cut -d'-' -f1 <<<"$pname")"
+ echo sudo rm -rf "/Applications/\"$appname.app\""
+}
+
+# Find packages
+packages=
+find_packages
+if [ -z "$packages" ]; then
+ echo "No packages found in $package_dir directory"
+ exit
+fi
+
+# Build list of commands to execute
+declare -a commands
+IFS=$'\n'
+for p in $packages; do
+ commands[${#commands[@]}]=$(${mode}_cmd_${package_type} "$p")
+done
+
+if [ -z "$dryrun" ]; then
+ # Run commands
+ for cmd in "${commands[@]}"; do
+ echo Running: $cmd
+ eval $cmd || true;
+ done
+else
+ # Print commands
+ for cmd in "${commands[@]}"; do echo $cmd; done
+fi
Binary file test/jdk/tools/jpackage/resources/icon.icns has changed
Binary file test/jdk/tools/jpackage/resources/icon.ico has changed
Binary file test/jdk/tools/jpackage/resources/icon.png has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/resources/license.txt Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,1 @@
+jpackage test license file (just some sample text).
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/run_tests.sh Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,275 @@
+#!/bin/bash
+
+#
+# Script to run jpackage tests.
+#
+
+
+# Fail fast
+set -e; set -o pipefail;
+
+
+# Link obtained from https://openjdk.java.net/jtreg/ page
+jtreg_bundle=https://ci.adoptopenjdk.net/view/Dependencies/job/jtreg/lastSuccessfulBuild/artifact/jtreg-4.2.0-tip.tar.gz
+workdir=/tmp/jpackage_jtreg_testing
+jtreg_jar=$workdir/jtreg/lib/jtreg.jar
+jpackage_test_selector=test/jdk/tools/jpackage
+
+
+find_packaging_tests ()
+{
+ (cd "$open_jdk_with_jpackage_jtreg_tests" && \
+ find "$jpackage_test_selector/$1" -type f -name '*.java' \
+ | xargs grep -E -l '@key[[:space:]]+jpackagePlatformPackage')
+}
+
+
+find_all_packaging_tests ()
+{
+ find_packaging_tests share
+ case "$(uname -s)" in
+ Darwin)
+ find_packaging_tests macosx;;
+ Linux)
+ find_packaging_tests linux;;
+ CYGWIN*|MINGW32*|MSYS*)
+ find_packaging_tests windows;;
+ *)
+ fatal Failed to detect OS type;;
+ esac
+}
+
+
+help_usage ()
+{
+ echo "Usage: `basename $0` [options] [test_names]"
+ echo "Options:"
+ echo " -h - print this message"
+ echo " -v - verbose output"
+ echo " -c - keep jtreg cache"
+ echo " -a - run all, not only SQE tests"
+ echo " -d - dry run. Print jtreg command line, but don't execute it"
+ echo " -t <jdk> - path to JDK to be tested [ mandatory ]"
+ echo " -j <openjdk> - path to local copy of openjdk repo with jpackage jtreg tests"
+ echo " Optional, default is openjdk repo where this script resides"
+ echo " -o <outputdir> - path to folder where to copy artifacts for testing."
+ echo " Optional, default is the current directory."
+ echo ' -r <runtimedir> - value for `jpackage.test.runtime-image` property.'
+ echo " Optional, for jtreg tests debug purposes only."
+ echo ' -l <logfile> - value for `jpackage.test.logfile` property.'
+ echo " Optional, for jtreg tests debug purposes only."
+ echo " -m <mode> - mode to run jtreg tests."
+ echo ' Should be one of `create`, `update`, `verify-install` or `verify-uninstall`.'
+ echo ' Optional, default mode is `update`.'
+ echo ' - `create`'
+ echo ' Remove all package bundles from the output directory before running jtreg tests.'
+ echo ' - `update`'
+ echo ' Run jtreg tests and overrite existing package bundles in the output directory.'
+ echo ' - `verify-install`'
+ echo ' Verify installed packages created with the previous run of the script.'
+ echo ' - `verify-uninstall`'
+ echo ' Verify packages created with the previous run of the script were uninstalled cleanly.'
+ echo ' - `print-default-tests`'
+ echo ' Print default list of packaging tests and exit.'
+}
+
+error ()
+{
+ echo "$@" > /dev/stderr
+}
+
+fatal ()
+{
+ error "$@"
+ exit 1
+}
+
+fatal_with_help_usage ()
+{
+ error "$@"
+ help_usage
+ exit 1
+}
+
+if command -v cygpath &> /dev/null; then
+to_native_path ()
+{
+ cygpath -m "$@"
+}
+else
+to_native_path ()
+{
+ echo "$@"
+}
+fi
+
+exec_command ()
+{
+ if [ -n "$dry_run" ]; then
+ echo "$@"
+ else
+ eval "$@"
+ fi
+}
+
+
+# Path to JDK to be tested.
+test_jdk=
+
+# Path to local copy of open jdk repo with jpackage jtreg tests
+# hg clone http://hg.openjdk.java.net/jdk/sandbox
+# cd sandbox; hg update -r JDK-8200758-branch
+open_jdk_with_jpackage_jtreg_tests=$(dirname $0)/../../../../
+
+# Directory where to save artifacts for testing.
+output_dir=$PWD
+
+# Script and jtreg debug.
+verbose=
+jtreg_verbose="-verbose:fail,error,summary"
+
+keep_jtreg_cache=
+
+# Mode in which to run jtreg tests
+mode=update
+
+# jtreg extra arguments
+declare -a jtreg_args
+
+# Run all tests
+run_all_tests=
+
+mapfile -t tests < <(find_all_packaging_tests)
+
+while getopts "vahdct:j:o:r:m:l:" argname; do
+ case "$argname" in
+ v) verbose=yes;;
+ a) run_all_tests=yes;;
+ d) dry_run=yes;;
+ c) keep_jtreg_cache=yes;;
+ t) test_jdk="$OPTARG";;
+ j) open_jdk_with_jpackage_jtreg_tests="$OPTARG";;
+ o) output_dir="$OPTARG";;
+ r) runtime_dir="$OPTARG";;
+ l) logfile="$OPTARG";;
+ m) mode="$OPTARG";;
+ h) help_usage; exit 0;;
+ ?) help_usage; exit 1;;
+ esac
+done
+shift $(( OPTIND - 1 ))
+
+[ -z "$verbose" ] || { set -x; jtreg_verbose=-va; }
+
+if [ -z "$open_jdk_with_jpackage_jtreg_tests" ]; then
+ fatal_with_help_usage "Path to openjdk repo with jpackage jtreg tests not specified"
+fi
+
+if [ "$mode" = "print-default-tests" ]; then
+ exec_command for t in ${tests[@]}";" do echo '$t;' done
+ exit
+fi
+
+if [ -z "$test_jdk" ]; then
+ fatal_with_help_usage Path to test JDK not specified
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ echo JAVA_HOME environment variable not set, will use java from test JDK [$test_jdk] to run jtreg
+ JAVA_HOME="$test_jdk"
+fi
+if [ ! -e "$JAVA_HOME/bin/java" ]; then
+ fatal JAVA_HOME variable is set to [$JAVA_HOME] value, but $JAVA_HOME/bin/java not found.
+fi
+
+if [ -n "$runtime_dir" ]; then
+ if [ ! -d "$runtime_dir" ]; then
+ fatal 'Value of `-r` option is set to non-existing directory'.
+ fi
+ jtreg_args+=("-Djpackage.test.runtime-image=$(to_native_path "$(cd "$runtime_dir" && pwd)")")
+fi
+
+if [ -n "$logfile" ]; then
+ if [ ! -d "$(dirname "$logfile")" ]; then
+ fatal 'Value of `-l` option specified a file in non-existing directory'.
+ fi
+ logfile="$(cd "$(dirname "$logfile")" && pwd)/$(basename "$logfile")"
+ jtreg_args+=("-Djpackage.test.logfile=$(to_native_path "$logfile")")
+fi
+
+if [ "$mode" = create ]; then
+ true
+elif [ "$mode" = update ]; then
+ true
+elif [ "$mode" = verify-install ]; then
+ jtreg_args+=("-Djpackage.test.action=$mode")
+elif [ "$mode" = verify-uninstall ]; then
+ jtreg_args+=("-Djpackage.test.action=$mode")
+else
+ fatal_with_help_usage 'Invalid value of -m option:' [$mode]
+fi
+
+if [ -z "$run_all_tests" ]; then
+ jtreg_args+=(-Djpackage.test.SQETest=yes)
+fi
+
+# All remaining command line arguments are tests to run that should override the defaults
+[ $# -eq 0 ] || tests=($@)
+
+
+installJtreg ()
+{
+ # Install jtreg if missing
+ if [ ! -f "$jtreg_jar" ]; then
+ exec_command mkdir -p "$workdir"
+ exec_command "(" cd "$workdir" "&&" wget "$jtreg_bundle" "&&" tar -xzf "$(basename $jtreg_bundle)" ";" rm -f "$(basename $jtreg_bundle)" ")"
+ fi
+}
+
+
+preRun ()
+{
+ local xargs_args=(-t --no-run-if-empty rm)
+ if [ -n "$dry_run" ]; then
+ xargs_args=(--no-run-if-empty echo rm)
+ fi
+
+ if [ ! -d "$output_dir" ]; then
+ exec_command mkdir -p "$output_dir"
+ fi
+ [ ! -d "$output_dir" ] || output_dir=$(cd "$output_dir" && pwd)
+
+ # Clean output directory
+ [ "$mode" != "create" ] || find $output_dir -maxdepth 1 -type f -name '*.exe' -or -name '*.msi' -or -name '*.rpm' -or -name '*.deb' | xargs "${xargs_args[@]}"
+}
+
+
+run ()
+{
+ local jtreg_cmdline=(\
+ $JAVA_HOME/bin/java -jar $(to_native_path "$jtreg_jar") \
+ "-Djpackage.test.output=$(to_native_path "$output_dir")" \
+ "${jtreg_args[@]}" \
+ -nr \
+ "$jtreg_verbose" \
+ -retain:all \
+ -automatic \
+ -ignore:run \
+ -testjdk:"$(to_native_path $test_jdk)" \
+ -dir:"$(to_native_path $open_jdk_with_jpackage_jtreg_tests)" \
+ -reportDir:"$(to_native_path $workdir/run/results)" \
+ -workDir:"$(to_native_path $workdir/run/support)" \
+ "${tests[@]}" \
+ )
+
+ # Clear previous results
+ [ -n "$keep_jtreg_cache" ] || exec_command rm -rf "$workdir"/run
+
+ # Run jpackage jtreg tests to create artifacts for testing
+ exec_command ${jtreg_cmdline[@]}
+}
+
+
+installJtreg
+preRun
+run
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/AddLauncherBase.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
+
+public class AddLauncherBase {
+ private static final String app = JPackagePath.getApp();
+ private static final String appOutput = JPackagePath.getAppOutputFile();
+
+ // Note: quotes in argument for add launcher is not support by test
+ private static final String ARGUMENT1 = "argument 1";
+ private static final String ARGUMENT2 = "argument 2";
+ private static final String ARGUMENT3 = "argument 3";
+
+ private static final List<String> arguments = new ArrayList<>();
+
+ private static final String PARAM1 = "-Dparam1=Some Param 1";
+ private static final String PARAM2 = "-Dparam2=Some Param 2";
+ private static final String PARAM3 = "-Dparam3=Some Param 3";
+
+ private static final List<String> vmArguments = new ArrayList<>();
+ private static final List<String> empty = new ArrayList<>();
+
+ private static void validateResult(List<String> args, List<String> vmArgs)
+ throws Exception {
+ File outfile = new File(appOutput);
+ if (!outfile.exists()) {
+ throw new AssertionError(appOutput + " was not created");
+ }
+
+ String output = Files.readString(outfile.toPath());
+ String[] result = output.split("\n");
+
+ int expected = 2 + args.size() + vmArgs.size();
+
+ if (result.length != expected) {
+ throw new AssertionError("Unexpected number of lines: "
+ + result.length + " expected: " + expected + " - results: " + output);
+ }
+
+ if (!result[0].trim().endsWith("jpackage test application")) {
+ throw new AssertionError("Unexpected result[0]: " + result[0]);
+ }
+
+ if (!result[1].trim().equals("args.length: " + args.size())) {
+ throw new AssertionError("Unexpected result[1]: " + result[1]);
+ }
+
+ int index = 2;
+ for (String arg : args) {
+ if (!result[index].trim().equals(arg)) {
+ throw new AssertionError("Unexpected result["
+ + index + "]: " + result[index]);
+ }
+ index++;
+ }
+
+ for (String vmArg : vmArgs) {
+ if (!result[index].trim().equals(vmArg)) {
+ throw new AssertionError("Unexpected result["
+ + index + "]: " + result[index]);
+ }
+ index++;
+ }
+ }
+
+ private static void validate(boolean includeArgs, String name)
+ throws Exception {
+ int retVal = JPackageHelper.execute(null, app);
+ if (retVal != 0) {
+ throw new AssertionError("Test application " + app
+ + " exited with error: " + retVal);
+ }
+ validateResult(new ArrayList<>(), new ArrayList<>());
+
+ String app2 = JPackagePath.getAppSL(name);
+ retVal = JPackageHelper.execute(null, app2);
+ if (retVal != 0) {
+ throw new AssertionError("Test application " + app2
+ + " exited with error: " + retVal);
+ }
+ if (includeArgs) {
+ validateResult(arguments, vmArguments);
+ } else {
+ validateResult(empty, empty);
+ }
+ }
+
+ public static void testCreateAppImage(String [] cmd) throws Exception {
+ testCreateAppImage(cmd, true, "test2");
+ }
+
+ public static void testCreateAppImage(String [] cmd,
+ boolean includeArgs, String name) throws Exception {
+ JPackageHelper.executeCLI(true, cmd);
+ validate(includeArgs, name);
+ }
+
+ public static void testCreateAppImageToolProvider(String [] cmd)
+ throws Exception {
+ testCreateAppImageToolProvider(cmd, true, "test2");
+ }
+
+ public static void testCreateAppImageToolProvider(String [] cmd,
+ boolean includeArgs, String name) throws Exception {
+ JPackageHelper.executeToolProvider(true, cmd);
+ validate(includeArgs, name);
+ }
+
+ public static void testCreateAppImage(String [] cmd,
+ ArrayList<String> argList, ArrayList <String> optionList)
+ throws Exception {
+ JPackageHelper.executeCLI(true, cmd);
+ int retVal = JPackageHelper.execute(null, app);
+ if (retVal != 0) {
+ throw new AssertionError("Test application " + app
+ + " exited with error: " + retVal);
+ }
+ validateResult(argList, optionList);
+ String name = "test4";
+
+ String app2 = JPackagePath.getAppSL(name);
+ retVal = JPackageHelper.execute(null, app2);
+ if (retVal != 0) {
+ throw new AssertionError("Test application " + app2
+ + " exited with error: " + retVal);
+ }
+ validateResult(arguments, vmArguments);
+ }
+
+ public static void createSLProperties() throws Exception {
+ arguments.add(ARGUMENT1);
+ arguments.add(ARGUMENT2);
+ arguments.add(ARGUMENT3);
+
+ String argumentsMap =
+ JPackageHelper.listToArgumentsMap(arguments, true);
+
+ vmArguments.add(PARAM1);
+ vmArguments.add(PARAM2);
+ vmArguments.add(PARAM3);
+
+ String vmArgumentsMap =
+ JPackageHelper.listToArgumentsMap(vmArguments, true);
+
+ try (PrintWriter out = new PrintWriter(new BufferedWriter(
+ new FileWriter("sl.properties")))) {
+ out.println("arguments=" + argumentsMap);
+ out.println("java-options=" + vmArgumentsMap);
+ }
+
+ try (PrintWriter out = new PrintWriter(new BufferedWriter(
+ new FileWriter("m1.properties")))) {
+ out.println("module=com.hello/com.hello.Hello");
+ out.println("main-jar=");
+ }
+
+ try (PrintWriter out = new PrintWriter(new BufferedWriter(
+ new FileWriter("j1.properties")))) {
+ out.println("main-jar hello.jar");
+ out.println("main-class Hello");
+ }
+
+
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/AddLauncherModuleTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+ /*
+ * @test
+ * @summary jpackage create image with additional launcher test
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @build AddLauncherBase
+ * @modules jdk.incubator.jpackage
+ * @run main/othervm -Xmx512m AddLauncherModuleTest
+ */
+public class AddLauncherModuleTest {
+ private static final String OUTPUT = "output";
+ private static final String [] CMD = {
+ "--type", "app-image",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--module", "com.hello/com.hello.Hello",
+ "--module-path", "input",
+ "--add-launcher", "test2=sl.properties"};
+
+ public static void main(String[] args) throws Exception {
+ JPackageHelper.createHelloModule();
+ AddLauncherBase.createSLProperties();
+ AddLauncherBase.testCreateAppImageToolProvider(
+ CMD);
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/AddLauncherTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.util.ArrayList;
+
+/*
+ * @test
+ * @summary jpackage create image with additional launcher test
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @build AddLauncherBase
+ * @modules jdk.incubator.jpackage
+ * @run main/othervm -Xmx512m AddLauncherTest
+ */
+public class AddLauncherTest {
+ private static final String OUTPUT = "output";
+ private static final String [] CMD = {
+ "--type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--main-jar", "hello.jar",
+ "--main-class", "Hello",
+ "--add-launcher", "test2=sl.properties"};
+
+ private final static String OPT1 = "-Dparam1=xxx";
+ private final static String OPT2 = "-Dparam2=yyy";
+ private final static String OPT3 = "-Dparam3=zzz";
+ private final static String ARG1 = "original-argument";
+
+ private static final String [] CMD1 = {
+ "--type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--main-jar", "hello.jar",
+ "--main-class", "Hello",
+ "--java-options", OPT1,
+ "--java-options", OPT2,
+ "--java-options", OPT3,
+ "--arguments", ARG1,
+ "--add-launcher", "test4=sl.properties"};
+
+
+ public static void main(String[] args) throws Exception {
+ JPackageHelper.createHelloImageJar();
+ AddLauncherBase.createSLProperties();
+ AddLauncherBase.testCreateAppImage(CMD);
+
+ ArrayList <String> argList = new ArrayList <String> ();
+ argList.add(ARG1);
+
+ ArrayList <String> optList = new ArrayList <String> ();
+ optList.add(OPT1);
+ optList.add(OPT2);
+ optList.add(OPT3);
+
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ AddLauncherBase.testCreateAppImage(CMD1, argList, optList);
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/AddLaunchersTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+ /*
+ * @test
+ * @summary jpackage create image with additional launcher test
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @build AddLauncherBase
+ * @modules jdk.incubator.jpackage
+ * @run main/othervm -Xmx512m AddLaunchersTest
+ */
+public class AddLaunchersTest {
+ private static final String OUTPUT = "output";
+ private static final String [] CMD1 = {
+ "--description", "Test non modular app with multiple add-launchers where one is modular app and other is non modular app",
+ "--type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--main-jar", "hello.jar",
+ "--main-class", "Hello",
+ "--module-path", "module",
+ "--add-modules", "com.hello,java.desktop",
+ "--add-launcher", "test3=j1.properties",
+ "--add-launcher", "test4=m1.properties"};
+
+ private static final String [] CMD2 = {
+ "--description", "Test modular app with multiple add-launchers where one is modular app and other is non modular app",
+ "--type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--module", "com.hello/com.hello.Hello",
+ "--module-path", "module",
+ "--add-launcher", "test5=jl.properties",
+ "--add-launcher", "test6=m1.properties"};
+
+ public static void main(String[] args) throws Exception {
+ JPackageHelper.createHelloImageJar();
+ JPackageHelper.createHelloModule();
+ AddLauncherBase.createSLProperties();
+
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ AddLauncherBase.testCreateAppImageToolProvider(
+ CMD1, false, "test3");
+
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ AddLauncherBase.testCreateAppImage(
+ CMD1, false, "test4");
+
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ AddLauncherBase.testCreateAppImage(
+ CMD2, false, "test5");
+
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ AddLauncherBase.testCreateAppImageToolProvider(
+ CMD2, false, "test6");
+
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/AdditionalLaunchersTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.List;
+import java.util.Optional;
+import java.lang.invoke.MethodHandles;
+import jdk.jpackage.test.HelloApp;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.FileAssociations;
+import jdk.jpackage.test.Annotations.Test;
+import jdk.jpackage.test.TKit;
+
+/**
+ * Test --add-launcher parameter. Output of the test should be
+ * additionallauncherstest*.* installer. The output installer should provide the
+ * same functionality as the default installer (see description of the default
+ * installer in SimplePackageTest.java) plus install three extra application
+ * launchers.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --add-launcher
+ * @key jpackagePlatformPackage
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile AdditionalLaunchersTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=AdditionalLaunchersTest
+ */
+
+public class AdditionalLaunchersTest {
+
+ @Test
+ public void test() {
+ // Configure a bunch of additional launchers and also setup
+ // file association to make sure it will be linked only to the main
+ // launcher.
+
+ PackageTest packageTest = new PackageTest().configureHelloApp();
+ packageTest.addInitializer(cmd -> {
+ cmd.addArguments("--arguments", "Duke", "--arguments", "is",
+ "--arguments", "the", "--arguments", "King");
+ });
+
+ new FileAssociations(
+ MethodHandles.lookup().lookupClass().getSimpleName()).applyTo(
+ packageTest);
+
+ new AdditionalLauncher("Baz2").setArguments().applyTo(packageTest);
+ new AdditionalLauncher("foo").setArguments("yep!").applyTo(packageTest);
+
+ AdditionalLauncher barLauncher = new AdditionalLauncher("Bar").setArguments(
+ "one", "two", "three");
+ if (TKit.isLinux()) {
+ barLauncher.setIcon(TKit.TEST_SRC_ROOT.resolve("apps/dukeplug.png"));
+ }
+ barLauncher.applyTo(packageTest);
+
+ packageTest.run();
+ }
+
+ private static Path replaceFileName(Path path, String newFileName) {
+ String fname = path.getFileName().toString();
+ int lastDotIndex = fname.lastIndexOf(".");
+ if (lastDotIndex != -1) {
+ fname = newFileName + fname.substring(lastDotIndex);
+ } else {
+ fname = newFileName;
+ }
+ return path.getParent().resolve(fname);
+ }
+
+ static class AdditionalLauncher {
+
+ AdditionalLauncher(String name) {
+ this.name = name;
+ }
+
+ AdditionalLauncher setArguments(String... args) {
+ arguments = List.of(args);
+ return this;
+ }
+
+ AdditionalLauncher setIcon(Path iconPath) {
+ icon = iconPath;
+ return this;
+ }
+
+ void applyTo(PackageTest test) {
+ final Path propsFile = TKit.workDir().resolve(name + ".properties");
+
+ test.addInitializer(cmd -> {
+ cmd.addArguments("--add-launcher", String.format("%s=%s", name,
+ propsFile));
+
+ Map<String, String> properties = new HashMap<>();
+ if (arguments != null) {
+ properties.put("arguments", String.join(" ",
+ arguments.toArray(String[]::new)));
+ }
+
+ if (icon != null) {
+ properties.put("icon", icon.toAbsolutePath().toString());
+ }
+
+ TKit.createPropertiesFile(propsFile, properties);
+ });
+ test.addInstallVerifier(cmd -> {
+ Path launcherPath = replaceFileName(cmd.appLauncherPath(), name);
+
+ TKit.assertExecutableFileExists(launcherPath);
+
+ if (cmd.isFakeRuntime(String.format(
+ "Not running %s launcher", launcherPath))) {
+ return;
+ }
+ HelloApp.executeAndVerifyOutput(launcherPath,
+ Optional.ofNullable(arguments).orElse(List.of()).toArray(
+ String[]::new));
+ });
+ test.addUninstallVerifier(cmd -> {
+ Path launcherPath = replaceFileName(cmd.appLauncherPath(), name);
+
+ TKit.assertPathExists(launcherPath, false);
+ });
+ }
+
+ private List<String> arguments;
+ private Path icon;
+ private final String name;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/AppImagePackageTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.nio.file.Path;
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.JPackageCommand;
+
+/**
+ * Test --app-image parameter. The output installer should provide the same
+ * functionality as the default installer (see description of the default
+ * installer in SimplePackageTest.java)
+ */
+
+/*
+ * @test
+ * @summary jpackage with --app-image
+ * @key jpackagePlatformPackage
+ * @library ../helpers
+ * @requires (jpackage.test.SQETest == null)
+ * @build jdk.jpackage.test.*
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @run main/othervm/timeout=540 -Xmx512m AppImagePackageTest
+ */
+public class AppImagePackageTest {
+
+ public static void main(String[] args) {
+ TKit.run(args, () -> {
+ Path appimageOutput = Path.of("appimage");
+
+ JPackageCommand appImageCmd = JPackageCommand.helloAppImage()
+ .setArgumentValue("--dest", appimageOutput)
+ .addArguments("--type", "app-image");
+
+ PackageTest packageTest = new PackageTest();
+ if (packageTest.getAction() == PackageTest.Action.CREATE) {
+ appImageCmd.execute();
+ }
+
+ packageTest.addInitializer(cmd -> {
+ Path appimageInput = appimageOutput.resolve(appImageCmd.name());
+
+ if (PackageType.MAC.contains(cmd.packageType())) {
+ // Why so complicated on macOS?
+ appimageInput = Path.of(appimageInput.toString() + ".app");
+ }
+
+ cmd.addArguments("--app-image", appimageInput);
+ cmd.removeArgumentWithValue("--input");
+ }).addBundleDesktopIntegrationVerifier(false).run();
+ });
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/ArgumentsTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.nio.file.Path;
+import java.util.List;
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.HelloApp;
+import jdk.jpackage.test.Functional.ThrowingConsumer;
+import jdk.jpackage.test.JPackageCommand;
+import jdk.jpackage.test.Annotations.*;
+
+
+/*
+ * Tricky arguments used in the test require a bunch of levels of character
+ * escaping for proper encoding them in a single string to be used as a value of
+ * `--arguments` option. String with encoded arguments doesn't go through the
+ * system to jpackage executable as is because OS is interpreting escape
+ * characters. This is true for Windows at least.
+ *
+ * String mapping performed by the system corrupts the string and jpackage exits
+ * with error. There is no problem with string corruption when jpackage is used
+ * as tool provider. This is not jpackage issue, so just always run this test
+ * with jpackage used as tool provider.
+ * /
+
+/*
+ * @test
+ * @summary jpackage create image with --arguments test
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile ArgumentsTest.java
+ * @run main/othervm -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=ArgumentsTest
+ */
+public class ArgumentsTest {
+
+ @BeforeEach
+ public static void useJPackageToolProvider() {
+ JPackageCommand.useToolProviderByDefault();
+ }
+
+ @Test
+ @Parameter("Goodbye")
+ @Parameter("com.hello/com.hello.Hello")
+ public static void testApp(String javaAppDesc) {
+ testIt(javaAppDesc, null);
+ }
+
+ private static void testIt(String javaAppDesc,
+ ThrowingConsumer<JPackageCommand> initializer) {
+
+ JPackageCommand cmd = JPackageCommand.helloAppImage(javaAppDesc).addArguments(
+ "--arguments", JPackageCommand.escapeAndJoin(TRICKY_ARGUMENTS));
+ if (initializer != null) {
+ ThrowingConsumer.toConsumer(initializer).accept(cmd);
+ }
+
+ cmd.executeAndAssertImageCreated();
+
+ Path launcherPath = cmd.appLauncherPath();
+ if (!cmd.isFakeRuntime(String.format(
+ "Not running [%s] launcher", launcherPath))) {
+ HelloApp.executeAndVerifyOutput(launcherPath, TRICKY_ARGUMENTS);
+ }
+ }
+
+ private final static List<String> TRICKY_ARGUMENTS = List.of(
+ "argument",
+ "Some Arguments",
+ "Value \"with\" quotes"
+ );
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/Base.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public abstract class Base {
+ private static final String appOutput = JPackagePath.getAppOutputFile();
+
+ private static void validateResult(String[] result) throws Exception {
+ if (result.length != 2) {
+ throw new AssertionError(
+ "Unexpected number of lines: " + result.length);
+ }
+
+ if (!result[0].trim().endsWith("jpackage test application")) {
+ throw new AssertionError("Unexpected result[0]: " + result[0]);
+ }
+
+ if (!result[1].trim().equals("args.length: 0")) {
+ throw new AssertionError("Unexpected result[1]: " + result[1]);
+ }
+ }
+
+ public static void validate(String app) throws Exception {
+ Path outPath = Path.of(appOutput);
+ int retVal = JPackageHelper.execute(null, app);
+
+ if (outPath.toFile().exists()) {
+ System.out.println("output contents: ");
+ System.out.println(Files.readString(outPath) + "\n");
+ } else {
+ System.out.println("no output file: " + outPath
+ + " from command: " + app);
+ }
+
+ if (retVal != 0) {
+ throw new AssertionError(
+ "Test application (" + app + ") exited with error: " + retVal);
+ }
+
+ if (!outPath.toFile().exists()) {
+ throw new AssertionError(appOutput + " was not created");
+ }
+
+ String output = Files.readString(outPath);
+ String[] result = JPackageHelper.splitAndFilter(output);
+ validateResult(result);
+ }
+
+ public static void testCreateAppImage(String [] cmd) throws Exception {
+ JPackageHelper.executeCLI(true, cmd);
+ validate(JPackagePath.getApp());
+ }
+
+ public static void testCreateAppImageToolProvider(String [] cmd) throws Exception {
+ JPackageHelper.executeToolProvider(true, cmd);
+ validate(JPackagePath.getApp());
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/ErrorTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+ /*
+ * @test
+ * @summary jpackage create app image error test
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @build Base
+ * @modules jdk.incubator.jpackage
+ * @run main/othervm -Xmx512m ErrorTest
+ */
+import java.util.*;
+import java.io.*;
+import java.nio.*;
+import java.nio.file.*;
+import java.nio.file.attribute.*;
+
+public class ErrorTest {
+
+ private static final String OUTPUT = "output";
+
+ private static final String ARG1 = "--no-such-argument";
+ private static final String EXPECTED1 =
+ "Invalid Option: [--no-such-argument]";
+ private static final String ARG2 = "--dest";
+ private static final String EXPECTED2 = "--main-jar or --module";
+
+ private static final String [] CMD1 = {
+ "--type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--main-jar", "non-existant.jar",
+ };
+ private static final String EXP1 = "main jar does not exist";
+
+ private static final String [] CMD2 = {
+ "--type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--main-jar", "hello.jar",
+ };
+ private static final String EXP2 = "class was not specified nor was";
+
+ private static void validate(String output, String expected, boolean single)
+ throws Exception {
+ String[] result = JPackageHelper.splitAndFilter(output);
+ if (single && result.length != 1) {
+ System.err.println(output);
+ throw new AssertionError("Unexpected multiple lines of output: "
+ + output);
+ }
+
+ if (!result[0].trim().contains(expected)) {
+ throw new AssertionError("Unexpected output: " + result[0]
+ + " - expected output to contain: " + expected);
+ }
+ }
+
+
+ public static void main(String[] args) throws Exception {
+ JPackageHelper.createHelloImageJar();
+
+ validate(JPackageHelper.executeToolProvider(false,
+ "--type", "app-image", ARG1), EXPECTED1, true);
+ validate(JPackageHelper.executeToolProvider(false,
+ "--type", "app-image", ARG2), EXPECTED2, true);
+
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ validate(JPackageHelper.executeToolProvider(false, CMD1), EXP1, false);
+
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ validate(JPackageHelper.executeToolProvider(false, CMD2), EXP2, false);
+
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/FileAssociationsTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.nio.file.Path;
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.FileAssociations;
+import jdk.jpackage.test.Annotations.Test;
+
+/**
+ * Test --file-associations parameter. Output of the test should be
+ * fileassociationstest*.* installer. The output installer should provide the
+ * same functionality as the default installer (see description of the default
+ * installer in SimplePackageTest.java) plus configure file associations. After
+ * installation files with ".jptest1" and ".jptest2" suffixes should be
+ * associated with the test app.
+ *
+ * Suggested test scenario is to create empty file with ".jptest1" suffix,
+ * double click on it and make sure that test application was launched in
+ * response to double click event with the path to test .jptest1 file on the
+ * commend line. The same applies to ".jptest2" suffix.
+ *
+ * On Linux use "echo > foo.jptest1" and not "touch foo.jptest1" to create test
+ * file as empty files are always interpreted as plain text and will not be
+ * opened with the test app. This is a known bug.
+ *
+ * Icon associated with the main launcher should be associated with files with
+ * ".jptest1" suffix. Different icon should be associated with files with with
+ * ".jptest2" suffix. Icon for files with ".jptest1" suffix is platform specific
+ * and is one of 'icon.*' files in test/jdk/tools/jpackage/resources directory.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --file-associations
+ * @library ../helpers
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile FileAssociationsTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=FileAssociationsTest
+ */
+public class FileAssociationsTest {
+ @Test
+ public static void test() {
+ PackageTest packageTest = new PackageTest();
+
+ // Not supported
+ packageTest.excludeTypes(PackageType.MAC_DMG);
+
+ new FileAssociations("jptest1").applyTo(packageTest);
+
+ Path icon = TKit.TEST_SRC_ROOT.resolve(Path.of("resources", "icon"
+ + TKit.ICON_SUFFIX));
+
+ icon = TKit.createRelativePathCopy(icon);
+
+ new FileAssociations("jptest2")
+ .setFilename("fa2")
+ .setIcon(icon)
+ .applyTo(packageTest);
+
+ packageTest.run();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/IconTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import jdk.incubator.jpackage.internal.IOUtils;
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.Functional;
+import jdk.jpackage.test.Annotations.*;
+import jdk.jpackage.test.JPackageCommand;
+
+/*
+ * @test
+ * @summary jpackage create image with custom icon
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile IconTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=IconTest
+ */
+
+public class IconTest {
+ @Test
+ public static void testResourceDir() throws IOException {
+ TKit.withTempDirectory("resources", tempDir -> {
+ JPackageCommand cmd = JPackageCommand.helloAppImage()
+ .addArguments("--resource-dir", tempDir);
+
+ Files.copy(GOLDEN_ICON, tempDir.resolve(appIconFileName(cmd)),
+ StandardCopyOption.REPLACE_EXISTING);
+
+ testIt(cmd);
+ });
+ }
+
+ @Test
+ @Parameter("true")
+ @Parameter("false")
+ public static void testParameter(boolean relativePath) throws IOException {
+ final Path iconPath;
+ if (relativePath) {
+ iconPath = TKit.createRelativePathCopy(GOLDEN_ICON);
+ } else {
+ iconPath = GOLDEN_ICON;
+ }
+
+ testIt(JPackageCommand.helloAppImage().addArguments("--icon", iconPath));
+ }
+
+ private static String appIconFileName(JPackageCommand cmd) {
+ return IOUtils.replaceSuffix(cmd.appLauncherPath().getFileName(),
+ TKit.ICON_SUFFIX).toString();
+ }
+
+ private static void testIt(JPackageCommand cmd) throws IOException {
+ cmd.executeAndAssertHelloAppImageCreated();
+
+ Path iconPath = cmd.appLayout().destktopIntegrationDirectory().resolve(
+ appIconFileName(cmd));
+
+ TKit.assertFileExists(iconPath);
+ TKit.assertTrue(-1 == Files.mismatch(GOLDEN_ICON, iconPath),
+ String.format(
+ "Check application icon file [%s] is a copy of source icon file [%s]",
+ iconPath, GOLDEN_ICON));
+ }
+
+ private final static Path GOLDEN_ICON = TKit.TEST_SRC_ROOT.resolve(Path.of(
+ "resources", "icon" + TKit.ICON_SUFFIX));
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/InstallDirTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Supplier;
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.Functional;
+import jdk.jpackage.test.JPackageCommand;
+import jdk.jpackage.test.Annotations.Parameter;
+
+/**
+ * Test --install-dir parameter. Output of the test should be
+ * commoninstalldirtest*.* package bundle. The output package should provide the
+ * same functionality as the default package but install test application in
+ * specified directory.
+ *
+ * Linux:
+ *
+ * Application should be installed in /opt/jpackage/commoninstalldirtest folder.
+ *
+ * Mac:
+ *
+ * Application should be installed in /Applications/jpackage/commoninstalldirtest.app
+ * folder.
+ *
+ * Windows:
+ *
+ * Application should be installed in %ProgramFiles%/TestVendor/InstallDirTest1234
+ * folder.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --install-dir
+ * @library ../helpers
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @compile InstallDirTest.java
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @run main/othervm/timeout=540 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=InstallDirTest.testCommon
+ */
+
+/*
+ * @test
+ * @summary jpackage with --install-dir
+ * @library ../helpers
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @compile InstallDirTest.java
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @requires (os.family == "linux")
+ * @requires (jpackage.test.SQETest == null)
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=InstallDirTest.testLinuxInvalid,testLinuxUnsupported
+ */
+public class InstallDirTest {
+
+ public static void testCommon() {
+ final Map<PackageType, Path> INSTALL_DIRS = Functional.identity(() -> {
+ Map<PackageType, Path> reply = new HashMap<>();
+ reply.put(PackageType.WIN_MSI, Path.of("TestVendor\\InstallDirTest1234"));
+ reply.put(PackageType.WIN_EXE, reply.get(PackageType.WIN_MSI));
+
+ reply.put(PackageType.LINUX_DEB, Path.of("/opt/jpackage"));
+ reply.put(PackageType.LINUX_RPM, reply.get(PackageType.LINUX_DEB));
+
+ reply.put(PackageType.MAC_PKG, Path.of("/Applications/jpackage"));
+
+ return reply;
+ }).get();
+
+ new PackageTest().excludeTypes(PackageType.MAC_DMG).configureHelloApp()
+ .addInitializer(cmd -> {
+ cmd.addArguments("--install-dir", INSTALL_DIRS.get(
+ cmd.packageType()));
+ }).run();
+ }
+
+ @Parameter("/")
+ @Parameter(".")
+ @Parameter("foo")
+ @Parameter("/opt/foo/.././.")
+ public static void testLinuxInvalid(String installDir) {
+ testLinuxBad(installDir, "Invalid installation directory");
+ }
+
+ @Parameter("/usr")
+ @Parameter("/usr/local")
+ @Parameter("/usr/foo")
+ public static void testLinuxUnsupported(String installDir) {
+ testLinuxBad(installDir, "currently unsupported");
+ }
+
+ private static void testLinuxBad(String installDir,
+ String errorMessageSubstring) {
+ new PackageTest().configureHelloApp()
+ .setExpectedExitCode(1)
+ .forTypes(PackageType.LINUX)
+ .addInitializer(cmd -> {
+ cmd.addArguments("--install-dir", installDir);
+ cmd.saveConsoleOutput(true);
+ })
+ .addBundleVerifier((cmd, result) -> {
+ String errorMessage = JPackageCommand.filterOutput(
+ result.getOutput().stream()).filter(line -> line.contains(
+ errorMessageSubstring)).findFirst().orElse(null);
+ TKit.assertNotNull(errorMessage, String.format(
+ "Check output contains [%s] substring",
+ errorMessageSubstring));
+ })
+ .run();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/InvalidArgTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+ /*
+ * @test
+ * @summary jpackage invalid argument test
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @modules jdk.incubator.jpackage
+ * @run main/othervm -Xmx512m InvalidArgTest
+ */
+public class InvalidArgTest {
+
+ private static final String ARG1 = "--no-such-argument";
+ private static final String ARG2 = "--dest";
+ private static final String RESULT1 =
+ "Invalid Option: [--no-such-argument]";
+ private static final String RESULT2 = "--main-jar or --module";
+
+ private static void validate(String arg, String output) throws Exception {
+ String[] result = JPackageHelper.splitAndFilter(output);
+ if (result.length != 1) {
+ System.err.println(output);
+ throw new AssertionError("Invalid number of lines in output: "
+ + result.length);
+ }
+
+ if (arg.equals(ARG1)) {
+ if (!result[0].trim().contains(RESULT1)) {
+ System.err.println("Expected: " + RESULT1);
+ System.err.println("Actual: " + result[0]);
+ throw new AssertionError("Unexpected output: " + result[0]);
+ }
+ } else if (arg.equals(ARG2)) {
+ if (!result[0].trim().contains(RESULT2)) {
+ System.err.println("Expected: " + RESULT2);
+ System.err.println("Actual: " + result[0]);
+ throw new AssertionError("Unexpected output: " + result[0]);
+ }
+ }
+ }
+
+ private static void testInvalidArg() throws Exception {
+ String output = JPackageHelper.executeCLI(false,
+ "--type", "app-image", ARG1);
+ validate(ARG1, output);
+
+ output = JPackageHelper.executeCLI(false,
+ "--type", "app-image", ARG2);
+ validate(ARG2, output);
+ }
+
+ private static void testInvalidArgToolProvider() throws Exception {
+ String output = JPackageHelper.executeToolProvider(false,
+ "--type", "app-image", ARG1);
+ validate(ARG1, output);
+
+ output = JPackageHelper.executeToolProvider(false,
+ "--type", "app-image", ARG2);
+ validate(ARG2, output);
+ }
+
+ public static void main(String[] args) throws Exception {
+ testInvalidArg();
+ testInvalidArgToolProvider();
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/JavaOptionsBase.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.File;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
+
+public class JavaOptionsBase {
+
+ private static final String app = JPackagePath.getApp();
+ private static final String appOutput = JPackagePath.getAppOutputFile();
+
+ private static final String ARGUMENT1 = "-Dparam1=Some Param 1";
+ private static final String ARGUMENT2 = "-Dparam2=Some \"Param\" 2";
+ private static final String ARGUMENT3 =
+ "-Dparam3=Some \"Param\" with \" 3";
+
+ private static final List<String> arguments = new ArrayList<>();
+
+ private static void initArguments(boolean toolProvider, String [] cmd) {
+ if (arguments.isEmpty()) {
+ arguments.add(ARGUMENT1);
+ arguments.add(ARGUMENT2);
+ arguments.add(ARGUMENT3);
+ }
+
+ String argumentsMap = JPackageHelper.listToArgumentsMap(arguments,
+ toolProvider);
+ cmd[cmd.length - 1] = argumentsMap;
+ }
+
+ private static void initArguments2(boolean toolProvider, String [] cmd) {
+ int index = cmd.length - 6;
+
+ cmd[index++] = "--java-options";
+ arguments.clear();
+ arguments.add(ARGUMENT1);
+ cmd[index++] = JPackageHelper.listToArgumentsMap(arguments,
+ toolProvider);
+
+ cmd[index++] = "--java-options";
+ arguments.clear();
+ arguments.add(ARGUMENT2);
+ cmd[index++] = JPackageHelper.listToArgumentsMap(arguments,
+ toolProvider);
+
+ cmd[index++] = "--java-options";
+ arguments.clear();
+ arguments.add(ARGUMENT3);
+ cmd[index++] = JPackageHelper.listToArgumentsMap(arguments,
+ toolProvider);
+
+ arguments.clear();
+ arguments.add(ARGUMENT1);
+ arguments.add(ARGUMENT2);
+ arguments.add(ARGUMENT3);
+ }
+
+ private static void validateResult(String[] result, List<String> args)
+ throws Exception {
+ if (result.length != (args.size() + 2)) {
+ for (String r : result) {
+ System.err.println(r.trim());
+ }
+ throw new AssertionError("Unexpected number of lines: "
+ + result.length);
+ }
+
+ if (!result[0].trim().equals("jpackage test application")) {
+ throw new AssertionError("Unexpected result[0]: " + result[0]);
+ }
+
+ if (!result[1].trim().equals("args.length: 0")) {
+ throw new AssertionError("Unexpected result[1]: " + result[1]);
+ }
+
+ int index = 2;
+ for (String arg : args) {
+ if (!result[index].trim().equals(arg)) {
+ throw new AssertionError("Unexpected result[" + index + "]: "
+ + result[index]);
+ }
+ index++;
+ }
+ }
+
+ private static void validate(List<String> expectedArgs) throws Exception {
+ int retVal = JPackageHelper.execute(null, app);
+ if (retVal != 0) {
+ throw new AssertionError("Test application exited with error: "
+ + retVal);
+ }
+
+ File outfile = new File(appOutput);
+ if (!outfile.exists()) {
+ throw new AssertionError(appOutput + " was not created");
+ }
+
+ String output = Files.readString(outfile.toPath());
+ String[] result = JPackageHelper.splitAndFilter(output);
+ validateResult(result, expectedArgs);
+ }
+
+ public static void testCreateAppImageJavaOptions(String [] cmd) throws Exception {
+ initArguments(false, cmd);
+ JPackageHelper.executeCLI(true, cmd);
+ validate(arguments);
+ }
+
+ public static void testCreateAppImageJavaOptionsToolProvider(String [] cmd) throws Exception {
+ initArguments(true, cmd);
+ JPackageHelper.executeToolProvider(true, cmd);
+ validate(arguments);
+ }
+
+ public static void testCreateAppImageJavaOptions2(String [] cmd) throws Exception {
+ initArguments2(false, cmd);
+ JPackageHelper.executeCLI(true, cmd);
+ validate(arguments);
+ }
+
+ public static void testCreateAppImageJavaOptions2ToolProvider(String [] cmd) throws Exception {
+ initArguments2(true, cmd);
+ JPackageHelper.executeToolProvider(true, cmd);
+ validate(arguments);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/JavaOptionsEqualsTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.File;
+import java.nio.file.Files;
+
+/*
+ * @test
+ * @summary jpackage create image with --java-options test
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @build JavaOptionsBase
+ * @modules jdk.incubator.jpackage
+ * @run main/othervm -Xmx512m JavaOptionsEqualsTest
+ */
+public class JavaOptionsEqualsTest {
+
+ private static final String app = JPackagePath.getApp();
+
+ private static final String OUTPUT = "output";
+
+ private static final String WARNING_1
+ = "WARNING: Unknown module: me.mymodule.foo";
+
+ private static final String WARNING_2
+ = "WARNING: Unknown module: other.mod.bar";
+
+ private static final String[] CMD = {
+ "--type", "app-image",
+ "--input", "input",
+ "--description", "the two options below should cause two app execution "
+ + "Warnings with two lines output saying: "
+ + "WARNING: Unknown module: <module-name>",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--main-jar", "hello.jar",
+ "--main-class", "Hello",
+ "--java-options",
+ "--add-exports=java.base/sun.util=me.mymodule.foo,ALL-UNNAMED",
+ "--java-options",
+ "--add-exports=java.base/sun.security.util=other.mod.bar,ALL-UNNAMED",
+ };
+
+ private static void validate() throws Exception {
+ File outfile = new File("app.out");
+
+ int retVal = JPackageHelper.execute(outfile, app);
+ if (retVal != 0) {
+ throw new AssertionError(
+ "Test application exited with error: " + retVal);
+ }
+
+ if (!outfile.exists()) {
+ throw new AssertionError(
+ "outfile: " + outfile + " was not created");
+ }
+
+ String output = Files.readString(outfile.toPath());
+ System.out.println("App output:");
+ System.out.print(output);
+
+ String[] result = JPackageHelper.splitAndFilter(output);
+ if (result.length != 4) {
+ throw new AssertionError(
+ "Unexpected number of lines: " + result.length
+ + " - output: " + output);
+ }
+
+ String nextWarning = WARNING_1;
+ if (!result[0].startsWith(nextWarning)){
+ nextWarning = WARNING_2;
+ if (!result[0].startsWith(WARNING_2)){
+ throw new AssertionError("Unexpected result[0]: " + result[0]);
+ } else {
+ nextWarning = WARNING_1;
+ }
+ }
+
+ if (!result[1].startsWith(nextWarning)) {
+ throw new AssertionError("Unexpected result[1]: " + result[1]);
+ }
+
+ if (!result[2].trim().endsWith("jpackage test application")) {
+ throw new AssertionError("Unexpected result[2]: " + result[2]);
+ }
+
+ if (!result[3].trim().equals("args.length: 0")) {
+ throw new AssertionError("Unexpected result[3]: " + result[3]);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ JPackageHelper.createHelloImageJar();
+ String output = JPackageHelper.executeCLI(true, CMD);
+ validate();
+
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ output = JPackageHelper.executeToolProvider(true, CMD);
+ validate();
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/JavaOptionsModuleTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @summary jpackage create image with --java-options test
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @build JavaOptionsBase
+ * @modules jdk.incubator.jpackage
+ * @run main/othervm -Xmx512m JavaOptionsModuleTest
+ */
+public class JavaOptionsModuleTest {
+ private static final String OUTPUT = "output";
+
+ private static final String[] CMD = {
+ "--type", "app-image",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--module", "com.hello/com.hello.Hello",
+ "--module-path", "input",
+ "--java-options", "TBD"};
+
+ private static final String[] CMD2 = {
+ "--type", "app-image",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--module", "com.hello/com.hello.Hello",
+ "--module-path", "input",
+ "--java-options", "TBD",
+ "--java-options", "TBD",
+ "--java-options", "TBD"};
+
+ public static void main(String[] args) throws Exception {
+ JPackageHelper.createHelloModule();
+
+ JavaOptionsBase.testCreateAppImageJavaOptions(CMD);
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ JavaOptionsBase.testCreateAppImageJavaOptionsToolProvider(CMD);
+
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ JavaOptionsBase.testCreateAppImageJavaOptions2(CMD2);
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ JavaOptionsBase.testCreateAppImageJavaOptions2ToolProvider(CMD2);
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/JavaOptionsTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @summary jpackage create image with --java-options test
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @build JavaOptionsBase
+ * @modules jdk.incubator.jpackage
+ * @run main/othervm -Xmx512m JavaOptionsTest
+ */
+public class JavaOptionsTest {
+ private static final String OUTPUT = "output";
+
+ private static final String[] CMD = {
+ "--type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--main-jar", "hello.jar",
+ "--main-class", "Hello",
+ "--java-options", "TBD"};
+
+ private static final String[] CMD2 = {
+ "--type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--main-jar", "hello.jar",
+ "--main-class", "Hello",
+ "--java-options", "TBD",
+ "--java-options", "TBD",
+ "--java-options", "TBD"};
+
+ public static void main(String[] args) throws Exception {
+ JPackageHelper.createHelloImageJar();
+ JavaOptionsBase.testCreateAppImageJavaOptions(CMD);
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ JavaOptionsBase.testCreateAppImageJavaOptionsToolProvider(CMD);
+
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ JavaOptionsBase.testCreateAppImageJavaOptions2(CMD2);
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ JavaOptionsBase.testCreateAppImageJavaOptions2ToolProvider(CMD2);
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/LicenseTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,281 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import jdk.jpackage.test.JPackageCommand;
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.LinuxHelper;
+import jdk.jpackage.test.Executor;
+import jdk.jpackage.test.TKit;
+
+/**
+ * Test --license-file parameter. Output of the test should be commonlicensetest*.*
+ * package bundle. The output package should provide the same functionality as
+ * the default package and also incorporate license information from
+ * test/jdk/tools/jpackage/resources/license.txt file from OpenJDK repo.
+ *
+ * deb:
+ *
+ * Package should install license file /opt/commonlicensetest/share/doc/copyright
+ * file.
+ *
+ * rpm:
+ *
+ * Package should install license file in
+ * %{_defaultlicensedir}/licensetest-1.0/license.txt file.
+ *
+ * Mac:
+ *
+ * Windows
+ *
+ * Installer should display license text matching contents of the license file
+ * during installation.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --license-file
+ * @library ../helpers
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @compile LicenseTest.java
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=LicenseTest.testCommon
+ */
+
+/*
+ * @test
+ * @summary jpackage with --license-file
+ * @library ../helpers
+ * @key jpackagePlatformPackage
+ * @compile LicenseTest.java
+ * @requires (os.family == "linux")
+ * @requires (jpackage.test.SQETest == null)
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=LicenseTest.testCustomDebianCopyright
+ * --jpt-run=LicenseTest.testCustomDebianCopyrightSubst
+ */
+
+public class LicenseTest {
+ public static void testCommon() {
+ new PackageTest().configureHelloApp()
+ .addInitializer(cmd -> {
+ cmd.addArguments("--license-file", TKit.createRelativePathCopy(
+ LICENSE_FILE));
+ })
+ .forTypes(PackageType.LINUX)
+ .addBundleVerifier(cmd -> {
+ verifyLicenseFileInLinuxPackage(cmd, linuxLicenseFile(cmd));
+ })
+ .addInstallVerifier(cmd -> {
+ TKit.assertReadableFileExists(linuxLicenseFile(cmd));
+ })
+ .addUninstallVerifier(cmd -> {
+ verifyLicenseFileNotInstalledLinux(linuxLicenseFile(cmd));
+ })
+ .forTypes(PackageType.LINUX_DEB)
+ .addInstallVerifier(cmd -> {
+ verifyLicenseFileInstalledDebian(debLicenseFile(cmd));
+ })
+ .forTypes(PackageType.LINUX_RPM)
+ .addInstallVerifier(cmd -> {
+ verifyLicenseFileInstalledRpm(rpmLicenseFile(cmd));
+ })
+ .run();
+ }
+
+ public static void testCustomDebianCopyright() {
+ new CustomDebianCopyrightTest().run();
+ }
+
+ public static void testCustomDebianCopyrightSubst() {
+ new CustomDebianCopyrightTest().withSubstitution(true).run();
+ }
+
+ private static Path rpmLicenseFile(JPackageCommand cmd) {
+ final Path licenseRoot = Path.of(
+ new Executor()
+ .setExecutable("rpm")
+ .addArguments("--eval", "%{_defaultlicensedir}")
+ .executeAndGetFirstLineOfOutput());
+ final Path licensePath = licenseRoot.resolve(String.format("%s-%s",
+ LinuxHelper.getPackageName(cmd), cmd.version())).resolve(
+ LICENSE_FILE.getFileName());
+ return licensePath;
+ }
+
+ private static Path linuxLicenseFile(JPackageCommand cmd) {
+ cmd.verifyIsOfType(PackageType.LINUX);
+ switch (cmd.packageType()) {
+ case LINUX_DEB:
+ return debLicenseFile(cmd);
+
+ case LINUX_RPM:
+ return rpmLicenseFile(cmd);
+
+ default:
+ return null;
+ }
+ }
+
+ private static Path debLicenseFile(JPackageCommand cmd) {
+ return cmd.appInstallationDirectory().resolve("share/doc/copyright");
+ }
+
+ private static void verifyLicenseFileInLinuxPackage(JPackageCommand cmd,
+ Path expectedLicensePath) {
+ TKit.assertTrue(LinuxHelper.getPackageFiles(cmd).filter(path -> path.equals(
+ expectedLicensePath)).findFirst().orElse(null) != null,
+ String.format("Check license file [%s] is in %s package",
+ expectedLicensePath, LinuxHelper.getPackageName(cmd)));
+ }
+
+ private static void verifyLicenseFileInstalledRpm(Path licenseFile) throws
+ IOException {
+ TKit.assertStringListEquals(Files.readAllLines(LICENSE_FILE),
+ Files.readAllLines(licenseFile), String.format(
+ "Check contents of package license file [%s] are the same as contents of source license file [%s]",
+ licenseFile, LICENSE_FILE));
+ }
+
+ private static void verifyLicenseFileInstalledDebian(Path licenseFile)
+ throws IOException {
+
+ List<String> actualLines = Files.readAllLines(licenseFile).stream().dropWhile(
+ line -> !line.startsWith("License:")).collect(
+ Collectors.toList());
+ // Remove leading `License:` followed by the whitespace from the first text line.
+ actualLines.set(0, actualLines.get(0).split("\\s+", 2)[1]);
+
+ actualLines = DEBIAN_COPYRIGT_FILE_STRIPPER.apply(actualLines);
+
+ TKit.assertNotEquals(0, String.join("\n", actualLines).length(),
+ "Check stripped license text is not empty");
+
+ TKit.assertStringListEquals(DEBIAN_COPYRIGT_FILE_STRIPPER.apply(
+ Files.readAllLines(LICENSE_FILE)), actualLines, String.format(
+ "Check subset of package license file [%s] is a match of the source license file [%s]",
+ licenseFile, LICENSE_FILE));
+ }
+
+ private static void verifyLicenseFileNotInstalledLinux(Path licenseFile) {
+ TKit.assertPathExists(licenseFile.getParent(), false);
+ }
+
+ private static class CustomDebianCopyrightTest {
+ CustomDebianCopyrightTest() {
+ withSubstitution(false);
+ }
+
+ private List<String> licenseFileText(String copyright, String licenseText) {
+ List<String> lines = new ArrayList(List.of(
+ String.format("Copyright=%s", copyright),
+ "Foo",
+ "Bar",
+ "Buz"));
+ lines.addAll(List.of(licenseText.split("\\R", -1)));
+ return lines;
+ }
+
+ private List<String> licenseFileText() {
+ if (withSubstitution) {
+ return licenseFileText("APPLICATION_COPYRIGHT",
+ "APPLICATION_LICENSE_TEXT");
+ } else {
+ return expetedLicenseFileText();
+ }
+ }
+
+ private List<String> expetedLicenseFileText() {
+ return licenseFileText(copyright, licenseText);
+ }
+
+ CustomDebianCopyrightTest withSubstitution(boolean v) {
+ withSubstitution = v;
+ // Different values just to make easy to figure out from the test log which test was executed.
+ if (v) {
+ copyright = "Duke (C)";
+ licenseText = "The quick brown fox\n jumps over the lazy dog";
+ } else {
+ copyright = "Java (C)";
+ licenseText = "How vexingly quick daft zebras jump!";
+ }
+ return this;
+ }
+
+ void run() {
+ final Path srcLicenseFile = TKit.workDir().resolve("license");
+ new PackageTest().configureHelloApp().forTypes(PackageType.LINUX_DEB)
+ .addInitializer(cmd -> {
+ // Create source license file.
+ Files.write(srcLicenseFile, List.of(
+ licenseText.split("\\R", -1)));
+
+ cmd.setFakeRuntime();
+ cmd.setArgumentValue("--name", String.format("%s%s",
+ withSubstitution ? "CustomDebianCopyrightWithSubst" : "CustomDebianCopyright",
+ cmd.name()));
+ cmd.addArguments("--license-file", srcLicenseFile);
+ cmd.addArguments("--copyright", copyright);
+ cmd.addArguments("--resource-dir", RESOURCE_DIR);
+
+ // Create copyright template file in a resource dir.
+ Files.createDirectories(RESOURCE_DIR);
+ Files.write(RESOURCE_DIR.resolve("copyright"),
+ licenseFileText());
+ })
+ .addInstallVerifier(cmd -> {
+ Path installedLicenseFile = debLicenseFile(cmd);
+ TKit.assertStringListEquals(expetedLicenseFileText(),
+ DEBIAN_COPYRIGT_FILE_STRIPPER.apply(Files.readAllLines(
+ installedLicenseFile)), String.format(
+ "Check contents of package license file [%s] are the same as contents of source license file [%s]",
+ installedLicenseFile, srcLicenseFile));
+ })
+ .run();
+ }
+
+ private boolean withSubstitution;
+ private String copyright;
+ private String licenseText;
+
+ private final Path RESOURCE_DIR = TKit.workDir().resolve("resources");
+ }
+
+ private static final Path LICENSE_FILE = TKit.TEST_SRC_ROOT.resolve(
+ Path.of("resources", "license.txt"));
+
+ private static final Function<List<String>, List<String>> DEBIAN_COPYRIGT_FILE_STRIPPER = (lines) -> Arrays.asList(
+ String.join("\n", lines).stripTrailing().split("\n"));
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/MissingArgumentsTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+ /*
+ * @test
+ * @summary jpackage create image missing arguments test
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @modules jdk.incubator.jpackage
+ * @run main/othervm -Xmx512m MissingArgumentsTest
+ */
+
+public class MissingArgumentsTest {
+ private static final String [] RESULT_1 = {"--input"};
+ private static final String [] CMD_1 = {
+ "--type", "app-image",
+ "--dest", "output",
+ "--name", "test",
+ "--main-jar", "hello.jar",
+ "--main-class", "Hello",
+ };
+
+ private static final String [] RESULT_2 = {"--input", "--app-image"};
+ private static final String [] CMD_2 = {
+ "--type", "app-image",
+ "--type", "invalid-type",
+ "--dest", "output",
+ "--name", "test",
+ "--main-jar", "hello.jar",
+ "--main-class", "Hello",
+ };
+
+ private static final String [] RESULT_3 = {"main class was not specified"};
+ private static final String [] CMD_3 = {
+ "--type", "app-image",
+ "--input", "input",
+ "--dest", "output",
+ "--name", "test",
+ "--main-jar", "hello.jar",
+ };
+
+ private static final String [] RESULT_4 = {"--main-jar"};
+ private static final String [] CMD_4 = {
+ "--type", "app-image",
+ "--input", "input",
+ "--dest", "output",
+ "--name", "test",
+ "--main-class", "Hello",
+ };
+
+ private static final String [] RESULT_5 = {"--module-path", "--runtime-image"};
+ private static final String [] CMD_5 = {
+ "--type", "app-image",
+ "--dest", "output",
+ "--name", "test",
+ "--module", "com.hello/com.hello.Hello",
+ };
+
+ private static final String [] RESULT_6 = {"--module-path", "--runtime-image",
+ "--app-image"};
+ private static final String [] CMD_6 = {
+ "--type", "invalid-type",
+ "--dest", "output",
+ "--name", "test",
+ "--module", "com.hello/com.hello.Hello",
+ };
+
+ private static void validate(String output, String [] expected,
+ boolean single) throws Exception {
+ String[] result = JPackageHelper.splitAndFilter(output);
+ if (single && result.length != 1) {
+ System.err.println(output);
+ throw new AssertionError("Invalid number of lines in output: "
+ + result.length);
+ }
+
+ for (String s : expected) {
+ if (!result[0].contains(s)) {
+ System.err.println("Expected to contain: " + s);
+ System.err.println("Actual: " + result[0]);
+ throw new AssertionError("Unexpected error message");
+ }
+ }
+ }
+
+ private static void testMissingArg() throws Exception {
+ String output = JPackageHelper.executeCLI(false, CMD_1);
+ validate(output, RESULT_1, true);
+
+ output = JPackageHelper.executeCLI(false, CMD_2);
+ validate(output, RESULT_2, true);
+
+ output = JPackageHelper.executeCLI(false, CMD_3);
+ validate(output, RESULT_3, false);
+
+ output = JPackageHelper.executeCLI(false, CMD_4);
+ validate(output, RESULT_4, true);
+
+ output = JPackageHelper.executeCLI(false, CMD_5);
+ validate(output, RESULT_5, true);
+
+ output = JPackageHelper.executeCLI(false, CMD_6);
+ validate(output, RESULT_6, true);
+
+ }
+
+ private static void testMissingArgToolProvider() throws Exception {
+ String output = JPackageHelper.executeToolProvider(false, CMD_1);
+ validate(output, RESULT_1, true);
+
+ output = JPackageHelper.executeToolProvider(false, CMD_2);
+ validate(output, RESULT_2, true);
+
+ output = JPackageHelper.executeToolProvider(false, CMD_3);
+ validate(output, RESULT_3, false);
+
+ output = JPackageHelper.executeToolProvider(false, CMD_4);
+ validate(output, RESULT_4, true);
+
+ output = JPackageHelper.executeToolProvider(false, CMD_5);
+ validate(output, RESULT_5, true);
+
+ output = JPackageHelper.executeToolProvider(false, CMD_6);
+ validate(output, RESULT_6, true);
+ }
+
+ public static void main(String[] args) throws Exception {
+ JPackageHelper.createHelloImageJar();
+ testMissingArg();
+ testMissingArgToolProvider();
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/RuntimePackageTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import jdk.jpackage.test.*;
+import jdk.jpackage.test.Annotations.Test;
+
+/**
+ * Test --runtime-image parameter.
+ * Output of the test should be RuntimePackageTest*.* installer.
+ * The installer should install Java Runtime without an application.
+ * Installation directory should not have "app" subfolder and should not have
+ * an application launcher.
+ *
+ *
+ * Windows:
+ *
+ * Java runtime should be installed in %ProgramFiles%\RuntimePackageTest directory.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --runtime-image
+ * @library ../helpers
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @comment Temporary disable for Linux and OSX until functionality implemented
+ * @requires (os.family != "mac")
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile RuntimePackageTest.java
+ * @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=RuntimePackageTest
+ */
+public class RuntimePackageTest {
+
+ @Test
+ public static void test() {
+ new PackageTest()
+ .addInitializer(cmd -> {
+ cmd.addArguments("--runtime-image", Optional.ofNullable(
+ JPackageCommand.DEFAULT_RUNTIME_IMAGE).orElse(Path.of(
+ System.getProperty("java.home"))));
+ // Remove --input parameter from jpackage command line as we don't
+ // create input directory in the test and jpackage fails
+ // if --input references non existant directory.
+ cmd.removeArgumentWithValue("--input");
+ })
+ .addInstallVerifier(cmd -> {
+ Set<Path> srcRuntime = listFiles(Path.of(cmd.getArgumentValue("--runtime-image")));
+ Set<Path> dstRuntime = listFiles(cmd.appRuntimeDirectory());
+
+ Set<Path> intersection = new HashSet<>(srcRuntime);
+ intersection.retainAll(dstRuntime);
+
+ srcRuntime.removeAll(intersection);
+ dstRuntime.removeAll(intersection);
+
+ assertFileListEmpty(srcRuntime, "Missing");
+ assertFileListEmpty(dstRuntime, "Unexpected");
+ })
+ .run();
+ }
+
+ private static Set<Path> listFiles(Path root) throws IOException {
+ try (var files = Files.walk(root)) {
+ return files.map(root::relativize).collect(Collectors.toSet());
+ }
+ }
+
+ private static void assertFileListEmpty(Set<Path> paths, String msg) {
+ TKit.assertTrue(paths.isEmpty(), String.format(
+ "Check there are no %s files in installed image",
+ msg.toLowerCase()), () -> {
+ String msg2 = String.format("%s %d files", msg, paths.size());
+ TKit.trace(msg2 + ":");
+ paths.stream().map(Path::toString).sorted().forEachOrdered(
+ TKit::trace);
+ TKit.trace("Done");
+ });
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/SimplePackageTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.Annotations.Test;
+
+/**
+ * Simple platform specific packaging test. Output of the test should be
+ * simplepackagetest*.* package bundle.
+ *
+ * Windows:
+ *
+ * The installer should not have license text. It should not have an option
+ * to change the default installation directory.
+ * Test application should be installed in %ProgramFiles%\SimplePackageTest directory.
+ * Installer should install test app for all users (machine wide).
+ * Installer should not create any shortcuts.
+ */
+
+/*
+ * @test
+ * @summary Simple jpackage command run
+ * @library ../helpers
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile SimplePackageTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=SimplePackageTest
+ */
+public class SimplePackageTest {
+
+ @Test
+ public static void test() {
+ new PackageTest()
+ .configureHelloApp()
+ .addBundleDesktopIntegrationVerifier(false)
+ .run();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/jdk/jpackage/tests/AppVersionTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jpackage.tests;
+
+import java.util.Collection;
+import java.util.List;
+import jdk.jpackage.test.Annotations.Parameters;
+import jdk.jpackage.test.Annotations.Test;
+import jdk.jpackage.test.JPackageCommand;
+import jdk.jpackage.test.TKit;
+
+/*
+ * @test
+ * @summary jpackage application version testing
+ * @library ../../../../helpers
+ * @build jdk.jpackage.test.*
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile AppVersionTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=jdk.jpackage.tests.AppVersionTest
+ */
+
+public final class AppVersionTest {
+
+ @Parameters
+ public static Collection input() {
+ return List.of(new Object[][]{
+ // Default jpackage version
+ {"1.0", "Hello", null},
+ {"1.0", "com.other/com.other.Hello", null},
+ // Version should be picked from --app-version
+ {"3.1", "Hello", new String[]{"--app-version", "3.1"}},
+ {"3.2", "com.other/com.other.Hello", new String[]{"--app-version",
+ "3.2"}},
+ // Version should be picked from the last --app-version
+ {"3.3", "Hello", new String[]{"--app-version", "4", "--app-version",
+ "3.3"}},
+ {"7.8", "com.other/com.other.Hello", new String[]{"--app-version",
+ "4", "--app-version", "7.8"}},
+ // Pick version from jar
+ {"3.10.17", "com.other/com.other.Hello@3.10.17", null},
+ // Ignore version in jar if --app-version given
+ {"7.5.81", "com.other/com.other.Hello@3.10.17", new String[]{
+ "--app-version", "7.5.81"}}
+ });
+ }
+
+ public AppVersionTest(String expectedVersion, String javaAppDesc,
+ String[] jpackageArgs) {
+ this.expectedVersion = expectedVersion;
+
+ cmd = JPackageCommand.helloAppImage(javaAppDesc);
+ if (jpackageArgs != null) {
+ cmd.addArguments(jpackageArgs);
+ }
+ }
+
+ @Test
+ public void test() {
+ cmd.executeAndAssertHelloAppImageCreated();
+ String actualVersion = cmd.readLaunherCfgFile().getValue("Application",
+ "app.version");
+ TKit.assertEquals(expectedVersion, actualVersion,
+ "Check application version");
+ }
+
+ private final String expectedVersion;
+ private final JPackageCommand cmd;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/jdk/jpackage/tests/BasicTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,348 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jpackage.tests;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import jdk.jpackage.test.*;
+import jdk.jpackage.test.Functional.ThrowingConsumer;
+import jdk.jpackage.test.Annotations.*;
+
+/*
+ * @test
+ * @summary jpackage basic testing
+ * @library ../../../../helpers
+ * @build jdk.jpackage.test.*
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile BasicTest.java
+ * @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=jdk.jpackage.tests.BasicTest
+ */
+
+public final class BasicTest {
+ @Test
+ public void testNoArgs() {
+ List<String> output =
+ getJPackageToolProvider().executeAndGetOutput();
+ TKit.assertStringListEquals(List.of("Usage: jpackage <options>",
+ "Use jpackage --help (or -h) for a list of possible options"),
+ output, "Check jpackage output");
+ }
+
+ @Test
+ public void testVersion() {
+ List<String> output =
+ getJPackageToolProvider()
+ .addArgument("--version")
+ .executeAndGetOutput();
+ TKit.assertStringListEquals(List.of(System.getProperty("java.version")),
+ output, "Check jpackage output");
+ }
+
+ @Test
+ public void testHelp() {
+ List<String> hOutput = getJPackageToolProvider()
+ .addArgument("-h").executeAndGetOutput();
+ List<String> helpOutput = getJPackageToolProvider()
+ .addArgument("--help").executeAndGetOutput();
+
+ TKit.assertStringListEquals(hOutput, helpOutput,
+ "Check -h and --help parameters produce the same output");
+
+ final String windowsPrefix = "--win-";
+ final String linuxPrefix = "--linux-";
+ final String osxPrefix = "--mac-";
+
+ final String expectedPrefix;
+ final List<String> unexpectedPrefixes;
+
+ if (TKit.isWindows()) {
+ expectedPrefix = windowsPrefix;
+ unexpectedPrefixes = List.of(osxPrefix, linuxPrefix);
+ } else if (TKit.isLinux()) {
+ expectedPrefix = linuxPrefix;
+ unexpectedPrefixes = List.of(windowsPrefix, osxPrefix);
+ } else if (TKit.isOSX()) {
+ expectedPrefix = osxPrefix;
+ unexpectedPrefixes = List.of(linuxPrefix, windowsPrefix);
+ } else {
+ throw TKit.throwUnknownPlatformError();
+ }
+
+ Function<String, Predicate<String>> createPattern = (prefix) -> {
+ return Pattern.compile("^ " + prefix).asPredicate();
+ };
+
+ Function<List<String>, Long> countStrings = (prefixes) -> {
+ return hOutput.stream().filter(
+ prefixes.stream().map(createPattern).reduce(x -> false,
+ Predicate::or)).peek(TKit::trace).count();
+ };
+
+ TKit.trace("Check parameters in help text");
+ TKit.assertNotEquals(0, countStrings.apply(List.of(expectedPrefix)),
+ "Check help text contains plaform specific parameters");
+ TKit.assertEquals(0, countStrings.apply(unexpectedPrefixes),
+ "Check help text doesn't contain unexpected parameters");
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testVerbose() {
+ JPackageCommand cmd = JPackageCommand.helloAppImage()
+ .setFakeRuntime().executePrerequisiteActions();
+
+ List<String> expectedVerboseOutputStrings = new ArrayList<>();
+ expectedVerboseOutputStrings.add("Creating app package:");
+ if (TKit.isWindows()) {
+ expectedVerboseOutputStrings.add("Result application bundle:");
+ expectedVerboseOutputStrings.add(
+ "Succeeded in building Windows Application Image package");
+ } else if (TKit.isLinux()) {
+ expectedVerboseOutputStrings.add(
+ "Succeeded in building Linux Application Image package");
+ } else if (TKit.isOSX()) {
+ expectedVerboseOutputStrings.add("Preparing Info.plist:");
+ expectedVerboseOutputStrings.add(
+ "Succeeded in building Mac Application Image package");
+ } else {
+ TKit.throwUnknownPlatformError();
+ }
+
+ TKit.deleteDirectoryContentsRecursive(cmd.outputDir());
+ List<String> nonVerboseOutput = cmd.createExecutor().executeAndGetOutput();
+ List<String>[] verboseOutput = (List<String>[])new List<?>[1];
+
+ // Directory clean up is not 100% reliable on Windows because of
+ // antivirus software that can lock .exe files. Setup
+ // diffreent output directory instead of cleaning the default one for
+ // verbose jpackage run.
+ TKit.withTempDirectory("verbose-output", tempDir -> {
+ cmd.setArgumentValue("--dest", tempDir);
+ verboseOutput[0] = cmd.createExecutor().addArgument(
+ "--verbose").executeAndGetOutput();
+ });
+
+ TKit.assertTrue(nonVerboseOutput.size() < verboseOutput[0].size(),
+ "Check verbose output is longer than regular");
+
+ expectedVerboseOutputStrings.forEach(str -> {
+ TKit.assertTextStream(str).label("regular output")
+ .predicate(String::contains).negate()
+ .apply(nonVerboseOutput.stream());
+ });
+
+ expectedVerboseOutputStrings.forEach(str -> {
+ TKit.assertTextStream(str).label("verbose output")
+ .apply(verboseOutput[0].stream());
+ });
+ }
+
+ @Test
+ public void testNoName() {
+ final String mainClassName = "Greetings";
+
+ JPackageCommand cmd = JPackageCommand.helloAppImage(mainClassName)
+ .removeArgumentWithValue("--name");
+
+ Path expectedImageDir = cmd.outputDir().resolve(mainClassName);
+ if (TKit.isOSX()) {
+ expectedImageDir = expectedImageDir.getParent().resolve(
+ expectedImageDir.getFileName().toString() + ".app");
+ }
+
+ cmd.executeAndAssertHelloAppImageCreated();
+ TKit.assertEquals(expectedImageDir.toAbsolutePath().normalize().toString(),
+ cmd.outputBundle().toAbsolutePath().normalize().toString(),
+ String.format(
+ "Check [%s] directory is filled with application image data",
+ expectedImageDir));
+ }
+
+ @Test
+ // Regular app
+ @Parameter("Hello")
+ // Modular app
+ @Parameter("com.other/com.other.Hello")
+ public void testApp(String javaAppDesc) {
+ JPackageCommand.helloAppImage(javaAppDesc)
+ .executeAndAssertHelloAppImageCreated();
+ }
+
+ @Test
+ public void testWhitespaceInPaths() {
+ JPackageCommand.helloAppImage("a/b c.jar:Hello")
+ .setArgumentValue("--input", TKit.workDir().resolve("The quick brown fox"))
+ .setArgumentValue("--dest", TKit.workDir().resolve("jumps over the lazy dog"))
+ .executeAndAssertHelloAppImageCreated();
+ }
+
+ @Test
+ @Parameter("ALL-MODULE-PATH")
+ @Parameter("ALL-DEFAULT")
+ @Parameter("java.desktop")
+ @Parameter("java.desktop,jdk.jartool")
+ @Parameter({ "java.desktop", "jdk.jartool" })
+ public void testAddModules(String... addModulesArg) {
+ JPackageCommand cmd = JPackageCommand
+ .helloAppImage("goodbye.jar:com.other/com.other.Hello");
+ Stream.of(addModulesArg).map(v -> Stream.of("--add-modules", v)).flatMap(
+ s -> s).forEachOrdered(cmd::addArgument);
+ cmd.executeAndAssertHelloAppImageCreated();
+ }
+
+ /**
+ * Test --temp option. Doesn't make much sense for app image as temporary
+ * directory is used only on Windows. Test it in packaging mode.
+ * @throws IOException
+ */
+ @Test
+ public void testTemp() throws IOException {
+ TKit.withTempDirectory("temp-root", tempRoot -> {
+ Function<JPackageCommand, Path> getTempDir = cmd -> {
+ return tempRoot.resolve(cmd.outputBundle().getFileName());
+ };
+
+ ThrowingConsumer<JPackageCommand> addTempDir = cmd -> {
+ Path tempDir = getTempDir.apply(cmd);
+ Files.createDirectories(tempDir);
+ cmd.addArguments("--temp", tempDir);
+ };
+
+ new PackageTest().configureHelloApp().addInitializer(addTempDir)
+ .addBundleVerifier(cmd -> {
+ // Check jpackage actually used the supplied directory.
+ Path tempDir = getTempDir.apply(cmd);
+ TKit.assertNotEquals(0, tempDir.toFile().list().length,
+ String.format(
+ "Check jpackage wrote some data in the supplied temporary directory [%s]",
+ tempDir));
+ })
+ .run();
+
+ new PackageTest().configureHelloApp().addInitializer(addTempDir)
+ .addInitializer(cmd -> {
+ // Clean output from the previus jpackage run.
+ Files.delete(cmd.outputBundle());
+ })
+ // Temporary directory should not be empty,
+ // jpackage should exit with error.
+ .setExpectedExitCode(1)
+ .run();
+ });
+ }
+
+ @Test
+ public void testAtFile() throws IOException {
+ JPackageCommand cmd = JPackageCommand.helloAppImage();
+
+ // Init options file with the list of options configured
+ // for JPackageCommand instance.
+ final Path optionsFile = TKit.workDir().resolve("options");
+ Files.write(optionsFile,
+ List.of(String.join(" ", cmd.getAllArguments())));
+
+ // Build app jar file.
+ cmd.executePrerequisiteActions();
+
+ // Make sure output directory is empty. Normally JPackageCommand would
+ // do this automatically.
+ TKit.deleteDirectoryContentsRecursive(cmd.outputDir());
+
+ // Instead of running jpackage command through configured
+ // JPackageCommand instance, run vanilla jpackage command with @ file.
+ getJPackageToolProvider()
+ .addArgument(String.format("@%s", optionsFile))
+ .execute().assertExitCodeIsZero();
+
+ // Verify output of jpackage command.
+ cmd.assertImageCreated();
+ HelloApp.executeLauncherAndVerifyOutput(cmd);
+ }
+
+ @Parameter("Hello")
+ @Parameter("com.foo/com.foo.main.Aloha")
+ @Test
+ public void testJLinkRuntime(String javaAppDesc) {
+ JPackageCommand cmd = JPackageCommand.helloAppImage(javaAppDesc);
+
+ // If `--module` parameter was set on jpackage command line, get its
+ // value and extract module name.
+ // E.g.: foo.bar2/foo.bar.Buz -> foo.bar2
+ // Note: HelloApp class manages `--module` parameter on jpackage command line
+ final String moduleName = cmd.getArgumentValue("--module", () -> null,
+ (v) -> v.split("/", 2)[0]);
+
+ if (moduleName != null) {
+ // Build module jar.
+ cmd.executePrerequisiteActions();
+ }
+
+ TKit.withTempDirectory("runtime", tempDir -> {
+ final Path runtimeDir = tempDir.resolve("data");
+
+ // List of modules required for test app.
+ final var modules = new String[] {
+ "java.base",
+ "java.desktop"
+ };
+
+ Executor jlink = getToolProvider(JavaTool.JLINK)
+ .saveOutput(false)
+ .addArguments(
+ "--add-modules", String.join(",", modules),
+ "--output", runtimeDir.toString(),
+ "--strip-debug",
+ "--no-header-files",
+ "--no-man-pages");
+
+ if (moduleName != null) {
+ jlink.addArguments("--add-modules", moduleName, "--module-path",
+ Path.of(cmd.getArgumentValue("--module-path")).resolve(
+ "hello.jar").toString());
+ }
+
+ jlink.execute().assertExitCodeIsZero();
+
+ cmd.addArguments("--runtime-image", runtimeDir);
+ cmd.executeAndAssertHelloAppImageCreated();
+ });
+ }
+
+ private static Executor getJPackageToolProvider() {
+ return getToolProvider(JavaTool.JPACKAGE);
+ }
+
+ private static Executor getToolProvider(JavaTool tool) {
+ return new Executor().dumpOutput().saveOutput().setToolProvider(tool);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/jdk/jpackage/tests/MainClassTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,316 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jpackage.tests;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.Collection;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.jar.JarFile;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.nio.file.Path;
+import java.util.function.Predicate;
+import java.util.jar.JarEntry;
+import jdk.jpackage.test.Annotations.Parameters;
+import jdk.jpackage.test.Annotations.Test;
+import jdk.jpackage.test.*;
+import jdk.jpackage.test.Functional.ThrowingConsumer;
+import static jdk.jpackage.tests.MainClassTest.Script.MainClassType.*;
+
+
+/*
+ * @test
+ * @summary test different settings of main class name for jpackage
+ * @library ../../../../helpers
+ * @build jdk.jpackage.test.*
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile MainClassTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=jdk.jpackage.tests.MainClassTest
+ */
+
+public final class MainClassTest {
+
+ static final class Script {
+ Script() {
+ appDesc = JavaAppDesc.parse("test.Hello");
+ }
+
+ Script modular(boolean v) {
+ appDesc.setModuleName(v ? "com.other" : null);
+ return this;
+ }
+
+ Script withJLink(boolean v) {
+ withJLink = v;
+ return this;
+ }
+
+ Script withMainClass(MainClassType v) {
+ mainClass = v;
+ return this;
+ }
+
+ Script withJarMainClass(MainClassType v) {
+ appDesc.setJarWithMainClass(v != NotSet);
+ jarMainClass = v;
+ return this;
+ }
+
+ Script expectedErrorMessage(String v) {
+ expectedErrorMessage = v;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return Stream.of(
+ format("modular", appDesc.moduleName() != null ? 'y' : 'n'),
+ format("main-class", mainClass),
+ format("jar-main-class", jarMainClass),
+ format("jlink", withJLink ? 'y' : 'n'),
+ format("error", expectedErrorMessage)
+ ).filter(Objects::nonNull).collect(Collectors.joining("; "));
+ }
+
+ private static String format(String key, Object value) {
+ if (value == null) {
+ return null;
+ }
+ return String.join("=", key, value.toString());
+ }
+
+ enum MainClassType {
+ NotSet("n"),
+ SetWrong("b"),
+ SetRight("y");
+
+ MainClassType(String label) {
+ this.label = label;
+ }
+
+ @Override
+ public String toString() {
+ return label;
+ }
+
+ private final String label;
+ };
+
+ private JavaAppDesc appDesc;
+ private boolean withJLink;
+ private MainClassType mainClass;
+ private MainClassType jarMainClass;
+ private String expectedErrorMessage;
+ }
+
+ public MainClassTest(Script script) {
+ this.script = script;
+
+ nonExistingMainClass = Stream.of(
+ script.appDesc.packageName(), "ThereIsNoSuchClass").filter(
+ Objects::nonNull).collect(Collectors.joining("."));
+
+ cmd = JPackageCommand
+ .helloAppImage(script.appDesc)
+ .ignoreDefaultRuntime(true);
+ if (!script.withJLink) {
+ cmd.addArguments("--runtime-image", Path.of(System.getProperty(
+ "java.home")));
+ }
+
+ final String moduleName = script.appDesc.moduleName();
+ switch (script.mainClass) {
+ case NotSet:
+ if (moduleName != null) {
+ // Don't specify class name, only module name.
+ cmd.setArgumentValue("--module", moduleName);
+ } else {
+ cmd.removeArgumentWithValue("--main-class");
+ }
+ break;
+
+ case SetWrong:
+ if (moduleName != null) {
+ cmd.setArgumentValue("--module",
+ String.join("/", moduleName, nonExistingMainClass));
+ } else {
+ cmd.setArgumentValue("--main-class", nonExistingMainClass);
+ }
+ }
+ }
+
+ @Parameters
+ public static Collection scripts() {
+ final var withMainClass = Set.of(SetWrong, SetRight);
+
+ List<Script[]> scripts = new ArrayList<>();
+ for (var withJLink : List.of(true, false)) {
+ for (var modular : List.of(true, false)) {
+ for (var mainClass : Script.MainClassType.values()) {
+ for (var jarMainClass : Script.MainClassType.values()) {
+ Script script = new Script()
+ .modular(modular)
+ .withJLink(withJLink)
+ .withMainClass(mainClass)
+ .withJarMainClass(jarMainClass);
+
+ if (withMainClass.contains(jarMainClass)
+ || withMainClass.contains(mainClass)) {
+ } else if (modular) {
+ script.expectedErrorMessage(
+ "Error: Main application class is missing");
+ } else {
+ script.expectedErrorMessage(
+ "A main class was not specified nor was one found in the jar");
+ }
+
+ scripts.add(new Script[]{script});
+ }
+ }
+ }
+ }
+ return scripts;
+ }
+
+ @Test
+ public void test() throws IOException {
+ if (script.jarMainClass == SetWrong) {
+ initJarWithWrongMainClass();
+ }
+
+ if (script.expectedErrorMessage != null) {
+ // This is the case when main class is not found nor in jar
+ // file nor on command line.
+ List<String> output = cmd
+ .saveConsoleOutput(true)
+ .execute()
+ .assertExitCodeIs(1)
+ .getOutput();
+ TKit.assertTextStream(script.expectedErrorMessage).apply(output.stream());
+ return;
+ }
+
+ // Get here only if main class is specified.
+ boolean appShouldSucceed = false;
+
+ // Should succeed if valid main class is set on the command line.
+ appShouldSucceed |= (script.mainClass == SetRight);
+
+ // Should succeed if main class is not set on the command line but set
+ // to valid value in the jar.
+ appShouldSucceed |= (script.mainClass == NotSet && script.jarMainClass == SetRight);
+
+ if (appShouldSucceed) {
+ cmd.executeAndAssertHelloAppImageCreated();
+ } else {
+ cmd.executeAndAssertImageCreated();
+ if (!cmd.isFakeRuntime(String.format("Not running [%s]",
+ cmd.appLauncherPath()))) {
+ List<String> output = new Executor()
+ .setDirectory(cmd.outputDir())
+ .setExecutable(cmd.appLauncherPath())
+ .dumpOutput().saveOutput()
+ .execute().assertExitCodeIs(1).getOutput();
+ TKit.assertTextStream(String.format(
+ "Error: Could not find or load main class %s",
+ nonExistingMainClass)).apply(output.stream());
+ }
+ }
+ }
+
+ private void initJarWithWrongMainClass() throws IOException {
+ // Call JPackageCommand.executePrerequisiteActions() to build app's jar.
+ // executePrerequisiteActions() is called by JPackageCommand instance
+ // only once.
+ cmd.executePrerequisiteActions();
+
+ final Path jarFile;
+ if (script.appDesc.moduleName() != null) {
+ jarFile = Path.of(cmd.getArgumentValue("--module-path"),
+ script.appDesc.jarFileName());
+ } else {
+ jarFile = cmd.inputDir().resolve(cmd.getArgumentValue("--main-jar"));
+ }
+
+ // Create new jar file filtering out main class from the old jar file.
+ TKit.withTempDirectory("repack-jar", workDir -> {
+ // Extract app's class from the old jar.
+ explodeJar(jarFile, workDir,
+ jarEntry -> Path.of(jarEntry.getName()).equals(
+ script.appDesc.classFilePath()));
+
+ // Create app's jar file with different main class.
+ var badAppDesc = JavaAppDesc.parse(script.appDesc.toString()).setClassName(
+ nonExistingMainClass);
+ JPackageCommand.helloAppImage(badAppDesc).executePrerequisiteActions();
+
+ // Extract new jar but skip app's class.
+ explodeJar(jarFile, workDir,
+ jarEntry -> !Path.of(jarEntry.getName()).equals(
+ badAppDesc.classFilePath()));
+
+ // At this point we should have:
+ // 1. Manifest from the new jar referencing non-existing class
+ // as the main class.
+ // 2. Module descriptor referencing non-existing class as the main
+ // class in case of modular app.
+ // 3. App's class from the old jar. We need it to let jlink find some
+ // classes in the package declared in module descriptor
+ // in case of modular app.
+
+ Files.delete(jarFile);
+ new Executor().setToolProvider(JavaTool.JAR)
+ .addArguments("-v", "-c", "-M", "-f", jarFile.toString())
+ .addArguments("-C", workDir.toString(), ".")
+ .dumpOutput()
+ .execute().assertExitCodeIsZero();
+ });
+ }
+
+ private static void explodeJar(Path jarFile, Path workDir,
+ Predicate<JarEntry> filter) throws IOException {
+ try (var jar = new JarFile(jarFile.toFile())) {
+ jar.stream()
+ .filter(Predicate.not(JarEntry::isDirectory))
+ .filter(filter)
+ .sequential().forEachOrdered(ThrowingConsumer.toConsumer(
+ jarEntry -> {
+ try (var in = jar.getInputStream(jarEntry)) {
+ Path fileName = workDir.resolve(jarEntry.getName());
+ Files.createDirectories(fileName.getParent());
+ Files.copy(in, fileName);
+ }
+ }));
+ }
+ }
+
+ private final JPackageCommand cmd;
+ private final Script script;
+ private final String nonExistingMainClass;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/jdk/jpackage/tests/ModulePathTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jpackage.tests;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import jdk.jpackage.test.Annotations.Parameters;
+import jdk.jpackage.test.Annotations.Test;
+import jdk.jpackage.test.JPackageCommand;
+import jdk.jpackage.test.TKit;
+
+
+/*
+ * @test
+ * @summary jpackage with --module-path testing
+ * @library ../../../../helpers
+ * @build jdk.jpackage.test.*
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile ModulePathTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=jdk.jpackage.tests.ModulePathTest
+ */
+
+public final class ModulePathTest {
+
+ @Parameters
+ public static Collection data() {
+ return List.of(new String[][]{
+ {GOOD_PATH, EMPTY_DIR, NON_EXISTING_DIR},
+ {EMPTY_DIR, NON_EXISTING_DIR, GOOD_PATH},
+ {GOOD_PATH + "/a/b/c/d", GOOD_PATH},
+ {String.join(File.pathSeparator, EMPTY_DIR, NON_EXISTING_DIR,
+ GOOD_PATH)},
+ {String.join(File.pathSeparator, EMPTY_DIR, NON_EXISTING_DIR),
+ String.join(File.pathSeparator, EMPTY_DIR, NON_EXISTING_DIR,
+ GOOD_PATH)},
+ {},
+ {EMPTY_DIR}
+ });
+ }
+
+ public ModulePathTest(String... modulePathArgs) {
+ this.modulePathArgs = List.of(modulePathArgs);
+ }
+
+ @Test
+ public void test() {
+ final String moduleName = "com.foo";
+ JPackageCommand cmd = JPackageCommand.helloAppImage(
+ "benvenuto.jar:" + moduleName + "/com.foo.Hello");
+ // Build app jar file.
+ cmd.executePrerequisiteActions();
+
+ // Ignore runtime that can be set for all tests. Usually if default
+ // runtime is set, it is fake one to save time on running jlink and
+ // copying megabytes of data from Java home to application image.
+ // We need proper runtime for this test.
+ cmd.ignoreDefaultRuntime(true);
+
+ // --module-path should be set in JPackageCommand.helloAppImage call
+ String goodModulePath = Objects.requireNonNull(cmd.getArgumentValue(
+ "--module-path"));
+ cmd.removeArgumentWithValue("--module-path");
+ TKit.withTempDirectory("empty-dir", emptyDir -> {
+ Path nonExistingDir = TKit.withTempDirectory("non-existing-dir",
+ unused -> {
+ });
+
+ Function<String, String> substitute = str -> {
+ String v = str;
+ v = v.replace(GOOD_PATH, goodModulePath);
+ v = v.replace(EMPTY_DIR, emptyDir.toString());
+ v = v.replace(NON_EXISTING_DIR, nonExistingDir.toString());
+ return v;
+ };
+
+ boolean withGoodPath = modulePathArgs.stream().anyMatch(
+ s -> s.contains(GOOD_PATH));
+
+ cmd.addArguments(modulePathArgs.stream().map(arg -> Stream.of(
+ "--module-path", substitute.apply(arg))).flatMap(s -> s).collect(
+ Collectors.toList()));
+
+ if (withGoodPath) {
+ cmd.executeAndAssertHelloAppImageCreated();
+ } else {
+ final String expectedErrorMessage;
+ if (modulePathArgs.isEmpty()) {
+ expectedErrorMessage = "Error: Missing argument: --runtime-image or --module-path";
+ } else {
+ expectedErrorMessage = String.format(
+ "Error: Module %s not found", moduleName);
+ }
+
+ List<String> output = cmd
+ .saveConsoleOutput(true)
+ .execute()
+ .assertExitCodeIs(1)
+ .getOutput();
+ TKit.assertTextStream(expectedErrorMessage).apply(output.stream());
+ }
+ });
+ }
+
+ private final List<String> modulePathArgs;
+
+ private final static String GOOD_PATH = "@GoodPath@";
+ private final static String EMPTY_DIR = "@EmptyDir@";
+ private final static String NON_EXISTING_DIR = "@NonExistingDir@";
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/test_jpackage.sh Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,68 @@
+#!/bin/bash
+
+#
+# Complete testing of jpackage platform-specific packaging.
+#
+# The script does the following:
+# 1. Create packages.
+# 2. Install created packages.
+# 3. Verifies packages are installed.
+# 4. Uninstall created packages.
+# 5. Verifies packages are uninstalled.
+#
+# For the list of accepted command line arguments see `run_tests.sh` script.
+#
+
+# Fail fast
+set -e; set -o pipefail;
+
+# Script debug
+dry_run=${JPACKAGE_TEST_DRY_RUN}
+
+# Default directory where jpackage should write bundle files
+output_dir=~/jpackage_bundles
+
+
+set_args ()
+{
+ args=()
+ local arg_is_output_dir=
+ local arg_is_mode=
+ local output_dir_set=
+ for arg in "$@"; do
+ if [ "$arg" == "-o" ]; then
+ arg_is_output_dir=yes
+ output_dir_set=yes
+ elif [ "$arg" == "-m" ]; then
+ arg_is_mode=yes
+ continue
+ elif [ -n "$arg_is_output_dir" ]; then
+ arg_is_output_dir=
+ output_dir="$arg"
+ elif [ -n "$arg_is_mode" ]; then
+ arg_is_mode=
+ continue
+ fi
+
+ args+=( "$arg" )
+ done
+ [ -n "$output_dir_set" ] || args=( -o "$output_dir" "${args[@]}" )
+}
+
+
+exec_command ()
+{
+ if [ -n "$dry_run" ]; then
+ echo "$@"
+ else
+ eval "$@"
+ fi
+}
+
+set_args "$@"
+basedir="$(dirname $0)"
+exec_command "$basedir/run_tests.sh" -m create "${args[@]}"
+exec_command "$basedir/manage_packages.sh" -d "$output_dir"
+exec_command "$basedir/run_tests.sh" -m verify-install "${args[@]}"
+exec_command "$basedir/manage_packages.sh" -d "$output_dir" -u
+exec_command "$basedir/run_tests.sh" -m verify-uninstall "${args[@]}"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/windows/WinConsoleTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.nio.file.Path;
+import java.io.InputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.JPackageCommand;
+import jdk.jpackage.test.Annotations.Test;
+import jdk.jpackage.test.Annotations.Parameter;
+
+/*
+ * @test
+ * @summary jpackage with --win-console
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "windows")
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile WinConsoleTest.java
+ *
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-before-run=jdk.jpackage.test.JPackageCommand.useToolProviderByDefault
+ * --jpt-run=WinConsoleTest
+ *
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=WinConsoleTest
+ */
+public class WinConsoleTest {
+
+ @Test
+ @Parameter("true")
+ @Parameter("false")
+ public static void test(boolean withWinConsole) throws IOException {
+ JPackageCommand cmd = JPackageCommand.helloAppImage();
+ if (!withWinConsole) {
+ cmd.removeArgument("--win-console");
+ }
+ cmd.executeAndAssertHelloAppImageCreated();
+ checkSubsystem(cmd.appLauncherPath(), withWinConsole);
+ }
+
+ private static void checkSubsystem(Path path, boolean isConsole) throws
+ IOException {
+ final int subsystemGui = 2;
+ final int subsystemConsole = 3;
+ final int bufferSize = 512;
+
+ final int expectedSubsystem = isConsole ? subsystemConsole : subsystemGui;
+
+ try (InputStream inputStream = new FileInputStream(path.toString())) {
+ byte[] bytes = new byte[bufferSize];
+ TKit.assertEquals(bufferSize, inputStream.read(bytes),
+ String.format("Check %d bytes were read from %s file",
+ bufferSize, path));
+
+ // Check PE header for console or Win GUI app.
+ // https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ns-winnt-_image_nt_headers
+ for (int i = 0; i < (bytes.length - 4); i++) {
+ if (bytes[i] == 0x50 && bytes[i + 1] == 0x45
+ && bytes[i + 2] == 0x0 && bytes[i + 3] == 0x0) {
+
+ // Signature, File Header and subsystem offset.
+ i = i + 4 + 20 + 68;
+ byte subsystem = bytes[i];
+ TKit.assertEquals(expectedSubsystem, subsystem,
+ String.format("Check subsystem of PE [%s] file",
+ path));
+ return;
+ }
+ }
+ }
+
+ TKit.assertUnexpected(String.format(
+ "Subsystem not found in PE header of [%s] file", path));
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/windows/WinDirChooserTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+
+/**
+ * Test --win-dir-chooser parameter. Output of the test should be
+ * WinDirChooserTest-1.0.exe installer. The output installer should provide the
+ * same functionality as the default installer (see description of the default
+ * installer in SimplePackageTest.java) plus provide an option for user to
+ * change the default installation directory.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --win-dir-chooser
+ * @library ../helpers
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "windows")
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m WinDirChooserTest
+ */
+
+public class WinDirChooserTest {
+ public static void main(String[] args) {
+ TKit.run(args, () -> {
+ new PackageTest()
+ .forTypes(PackageType.WINDOWS)
+ .configureHelloApp()
+ .addInitializer(cmd -> cmd.addArgument("--win-dir-chooser")).run();
+ });
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/windows/WinMenuGroupTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.Annotations.Test;
+
+/**
+ * Test --win-menu and --win-menu-group parameters.
+ * Output of the test should be WinMenuGroupTest-1.0.exe installer.
+ * The output installer should provide the
+ * same functionality as the default installer (see description of the default
+ * installer in SimplePackageTest.java) plus
+ * it should create a shortcut for application launcher in Windows Menu in
+ * "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\WinMenuGroupTest_MenuGroup" folder.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --win-menu and --win-menu-group
+ * @library ../helpers
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "windows")
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile WinMenuGroupTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=WinMenuGroupTest
+ */
+
+public class WinMenuGroupTest {
+ @Test
+ public static void test() {
+ new PackageTest()
+ .forTypes(PackageType.WINDOWS)
+ .configureHelloApp()
+ .addInitializer(cmd -> cmd.addArguments(
+ "--win-menu", "--win-menu-group", "WinMenuGroupTest_MenuGroup"))
+ .run();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/windows/WinMenuTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.Annotations.Test;
+
+/**
+ * Test --win-menu parameter. Output of the test should be WinMenuTest-1.0.exe
+ * installer. The output installer should provide the same functionality as the
+ * default installer (see description of the default installer in
+ * SimplePackageTest.java), except it should create a menu shortcut.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --win-menu
+ * @library ../helpers
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "windows")
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile WinMenuTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=WinMenuTest
+ */
+
+public class WinMenuTest {
+ @Test
+ public static void test() {
+ new PackageTest()
+ .forTypes(PackageType.WINDOWS)
+ .configureHelloApp()
+ .addInitializer(cmd -> cmd.addArgument("--win-menu")).run();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/windows/WinPerUserInstallTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.Annotations.Test;
+
+/**
+ * Test --win-per-user-install, --win-menu, --win-menu-group parameters.
+ * Output of the test should be WinPerUserInstallTest-1.0.exe installer. The
+ * output installer should provide the same functionality as the default
+ * installer (see description of the default installer in
+ * SimplePackageTest.java) plus it should create application menu in Windows
+ * Menu and installation should be per user and not machine wide.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --win-per-user-install, --win-menu, --win-menu-group
+ * @library ../helpers
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "windows")
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile WinPerUserInstallTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=WinPerUserInstallTest
+ */
+
+public class WinPerUserInstallTest {
+ @Test
+ public static void test() {
+ new PackageTest()
+ .forTypes(PackageType.WINDOWS)
+ .configureHelloApp()
+ .addInitializer(cmd -> cmd.addArguments(
+ "--win-menu",
+ "--win-menu-group", "WinPerUserInstallTest_MenuGroup",
+ "--win-per-user-install"))
+ .run();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/windows/WinResourceTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.IOException;
+import java.nio.file.Path;
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.Annotations.Test;
+import jdk.jpackage.test.Annotations.Parameters;
+import java.util.List;
+
+/**
+ * Test --resource-dir option. The test should set --resource-dir to point to
+ * a dir with an empty "main.wxs" file. As a result, jpackage should try to
+ * use the customized resource and fail.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --resource-dir
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "windows")
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile WinResourceTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=WinResourceTest
+ */
+
+public class WinResourceTest {
+
+ public WinResourceTest(String wixSource, String expectedLogMessage) {
+ this.wixSource = wixSource;
+ this.expectedLogMessage = expectedLogMessage;
+ }
+
+ @Parameters
+ public static List<Object[]> data() {
+ return List.of(new Object[][]{
+ {"main.wxs", "Using custom package resource [Main WiX project file]"},
+ {"overrides.wxi", "Using custom package resource [Overrides WiX project file]"},
+ });
+ }
+
+ @Test
+ public void test() throws IOException {
+ new PackageTest()
+ .forTypes(PackageType.WINDOWS)
+ .configureHelloApp()
+ .addInitializer(cmd -> {
+ Path resourceDir = TKit.createTempDirectory("resources");
+
+ // 1. Set fake run time to save time by skipping jlink step of jpackage.
+ // 2. Instruct test to save jpackage output.
+ cmd.setFakeRuntime().saveConsoleOutput(true);
+
+ cmd.addArguments("--resource-dir", resourceDir);
+ // Create invalid WiX source file in a resource dir.
+ TKit.createTextFile(resourceDir.resolve(wixSource), List.of(
+ "any string that is an invalid WiX source file"));
+ })
+ .addBundleVerifier((cmd, result) -> {
+ // Assert jpackage picked custom main.wxs and failed as expected by
+ // examining its output
+ TKit.assertTextStream(expectedLogMessage)
+ .predicate(String::startsWith)
+ .apply(result.getOutput().stream());
+ TKit.assertTextStream("error CNDL0104 : Not a valid source file")
+ .apply(result.getOutput().stream());
+ })
+ .setExpectedExitCode(1)
+ .run();
+ }
+
+ final String wixSource;
+ final String expectedLogMessage;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/windows/WinScriptTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.ArrayList;
+import jdk.incubator.jpackage.internal.IOUtils;
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.Annotations.Test;
+import jdk.jpackage.test.Annotations.Parameter;
+import jdk.jpackage.test.Annotations.Parameters;
+import jdk.jpackage.test.JPackageCommand;
+
+/*
+ * @test usage of scripts from resource dir
+ * @summary jpackage with
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "windows")
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile WinScriptTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=WinScriptTest
+ */
+
+public class WinScriptTest {
+
+ public WinScriptTest(PackageType type) {
+ this.packageType = type;
+
+ test = new PackageTest()
+ .forTypes(type)
+ .configureHelloApp()
+ .addInitializer(cmd -> {
+ cmd.setFakeRuntime().saveConsoleOutput(true);
+ });
+ }
+
+ @Parameters
+ public static List<Object[]> data() {
+ return List.of(new Object[][]{
+ {PackageType.WIN_MSI},
+ {PackageType.WIN_EXE}
+ });
+ }
+
+ @Test
+ @Parameter("0")
+ @Parameter("10")
+ public void test(int wsfExitCode) {
+ final ScriptData appImageScriptData;
+ if (wsfExitCode != 0 && packageType == PackageType.WIN_EXE) {
+ appImageScriptData = new ScriptData(PackageType.WIN_MSI, 0);
+ } else {
+ appImageScriptData = new ScriptData(PackageType.WIN_MSI, wsfExitCode);
+ }
+
+ final ScriptData msiScriptData = new ScriptData(PackageType.WIN_EXE, wsfExitCode);
+
+ test.setExpectedExitCode(wsfExitCode == 0 ? 0 : 1);
+ TKit.withTempDirectory("resources", tempDir -> {
+ test.addInitializer(cmd -> {
+ cmd.addArguments("--resource-dir", tempDir);
+
+ appImageScriptData.createScript(cmd);
+ msiScriptData.createScript(cmd);
+ });
+
+ if (packageType == PackageType.WIN_MSI) {
+ test.addBundleVerifier((cmd, result) -> {
+ appImageScriptData.assertJPackageOutput(result.getOutput());
+ });
+ }
+
+ if (packageType == PackageType.WIN_EXE) {
+ test.addBundleVerifier((cmd, result) -> {
+ appImageScriptData.assertJPackageOutput(result.getOutput());
+ msiScriptData.assertJPackageOutput(result.getOutput());
+ });
+ }
+
+ test.run();
+ });
+ }
+
+ private static class ScriptData {
+ ScriptData(PackageType scriptType, int wsfExitCode) {
+ if (scriptType == PackageType.WIN_MSI) {
+ echoText = "post app image wsf";
+ envVarName = "JpAppImageDir";
+ scriptSuffixName = "post-image";
+ } else {
+ echoText = "post msi wsf";
+ envVarName = "JpMsiFile";
+ scriptSuffixName = "post-msi";
+ }
+ this.wsfExitCode = wsfExitCode;
+ }
+
+ void assertJPackageOutput(List<String> output) {
+ TKit.assertTextStream(String.format("jp: %s", echoText))
+ .predicate(String::equals)
+ .apply(output.stream());
+
+ String cwdPattern = String.format("jp: CWD(%s)=", envVarName);
+ TKit.assertTextStream(cwdPattern)
+ .predicate(String::startsWith)
+ .apply(output.stream());
+ String cwd = output.stream().filter(line -> line.startsWith(
+ cwdPattern)).findFirst().get().substring(cwdPattern.length());
+
+ String envVarPattern = String.format("jp: %s=", envVarName);
+ TKit.assertTextStream(envVarPattern)
+ .predicate(String::startsWith)
+ .apply(output.stream());
+ String envVar = output.stream().filter(line -> line.startsWith(
+ envVarPattern)).findFirst().get().substring(envVarPattern.length());
+
+ TKit.assertTrue(envVar.startsWith(cwd), String.format(
+ "Check value of %s environment variable [%s] starts with the current directory [%s] set for %s script",
+ envVarName, envVar, cwd, echoText));
+ }
+
+ void createScript(JPackageCommand cmd) throws IOException {
+ IOUtils.createXml(Path.of(cmd.getArgumentValue("--resource-dir"),
+ String.format("%s-%s.wsf", cmd.name(), scriptSuffixName)), xml -> {
+ xml.writeStartElement("job");
+ xml.writeAttribute("id", "main");
+ xml.writeStartElement("script");
+ xml.writeAttribute("language", "JScript");
+ xml.writeCData(String.join("\n", List.of(
+ "var shell = new ActiveXObject('WScript.Shell')",
+ "WScript.Echo('jp: " + envVarName + "=' + shell.ExpandEnvironmentStrings('%" + envVarName + "%'))",
+ "WScript.Echo('jp: CWD(" + envVarName + ")=' + shell.CurrentDirectory)",
+ String.format("WScript.Echo('jp: %s')", echoText),
+ String.format("WScript.Quit(%d)", wsfExitCode)
+ )));
+ xml.writeEndElement();
+ xml.writeEndElement();
+ });
+ }
+
+ private final int wsfExitCode;
+ private final String scriptSuffixName;
+ private final String echoText;
+ private final String envVarName;
+ }
+
+ private final PackageType packageType;
+ private PackageTest test;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/windows/WinShortcutTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.Annotations.Test;
+
+/**
+ * Test --win-shortcut parameter. Output of the test should be
+ * WinShortcutTest-1.0.exe installer. The output installer should provide the
+ * same functionality as the default installer (see description of the default
+ * installer in SimplePackageTest.java) plus install application shortcut on the
+ * desktop.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --win-shortcut
+ * @library ../helpers
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "windows")
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile WinShortcutTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=WinShortcutTest
+ */
+
+public class WinShortcutTest {
+ @Test
+ public static void test() {
+ new PackageTest()
+ .forTypes(PackageType.WINDOWS)
+ .configureHelloApp()
+ .addInitializer(cmd -> cmd.addArgument("--win-shortcut"))
+ .run();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/windows/WinUpgradeUUIDTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+
+/**
+ * Test both --win-upgrade-uuid and --app-version parameters. Output of the test
+ * should be WinUpgradeUUIDTest-1.0.exe and WinUpgradeUUIDTest-2.0.exe
+ * installers. Both output installers should provide the same functionality as
+ * the default installer (see description of the default installer in
+ * SimplePackageTest.java) but have the same product code and different
+ * versions. Running WinUpgradeUUIDTest-2.0.exe installer should automatically
+ * uninstall older version of the test application previously installed with
+ * WinUpgradeUUIDTest-1.0.exe installer.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --win-upgrade-uuid and --app-version
+ * @library ../helpers
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "windows")
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m WinUpgradeUUIDTest
+ */
+
+public class WinUpgradeUUIDTest {
+ public static void main(String[] args) {
+ TKit.run(args, () -> {
+ PackageTest test = init();
+ if (test.getAction() != PackageTest.Action.VERIFY_INSTALL) {
+ test.run();
+ }
+
+ test = init();
+ test.addInitializer(cmd -> {
+ cmd.setArgumentValue("--app-version", "2.0");
+ cmd.setArgumentValue("--arguments", "bar");
+ });
+ test.run();
+ });
+ }
+
+ private static PackageTest init() {
+ return new PackageTest()
+ .forTypes(PackageType.WINDOWS)
+ .configureHelloApp()
+ .addInitializer(cmd -> cmd.addArguments("--win-upgrade-uuid",
+ "F0B18E75-52AD-41A2-BC86-6BE4FCD50BEB"));
+ }
+}
--- a/test/jdk/tools/launcher/HelpFlagsTest.java Tue Nov 12 12:19:24 2019 +0100
+++ b/test/jdk/tools/launcher/HelpFlagsTest.java Tue Nov 12 10:09:40 2019 -0500
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018 SAP SE. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
@@ -156,13 +156,12 @@
new ToolHelpSpec("jstatd", 1, 1, 1, 0, 0, 0, 1), // -?, -h, --help
new ToolHelpSpec("keytool", 1, 1, 1, 0, 1, 0, 1), // none, prints help message anyways.
new ToolHelpSpec("pack200", 1, 1, 1, 0, 1, 0, 2), // -?, -h, --help, -help accepted but not documented.
- new ToolHelpSpec("rmic", 0, 0, 0, 0, 0, 0, 1), // none, pirnts help message anyways.
+ new ToolHelpSpec("rmic", 0, 0, 0, 0, 0, 0, 1), // none, prints help message anyways.
new ToolHelpSpec("rmid", 0, 0, 0, 0, 0, 0, 1), // none, prints help message anyways.
new ToolHelpSpec("rmiregistry", 0, 0, 0, 0, 0, 0, 1), // none, prints help message anyways.
new ToolHelpSpec("serialver", 0, 0, 0, 0, 0, 0, 1), // none, prints help message anyways.
new ToolHelpSpec("unpack200", 1, 1, 1, 0, 1, 0, 2), // -?, -h, --help, -help accepted but not documented.
- // Oracle proprietary tools:
- new ToolHelpSpec("javapackager",0, 0, 0, 0, 1, 0, 255), // -help accepted but not documented.
+ new ToolHelpSpec("jpackage", 0, 1, 1, 0, 0, 1, 1), // -h, --help,
};
// Returns true if the file is not a tool.
--- a/test/jdk/tools/launcher/VersionCheck.java Tue Nov 12 12:19:24 2019 +0100
+++ b/test/jdk/tools/launcher/VersionCheck.java Tue Nov 12 10:09:40 2019 -0500
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2007, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -62,7 +62,7 @@
"jmc",
"jmc.ini",
"jweblauncher",
- "packager",
+ "jpackage",
"ssvagent",
"unpack200",
};
@@ -108,7 +108,7 @@
"klist",
"ktab",
"pack200",
- "packager",
+ "jpackage",
"rmic",
"rmid",
"rmiregistry",