# HG changeset patch
# User herrick
# Date 1539037385 14400
# Node ID cf8d1b2388b896559086e97f95ec582af5086fdf
# Parent 20568f1e1aa7da82aed86247fb2cb077cee39376# Parent 2a85adf3c330e950db0d660c69be77658c1bdfb8
Merge
diff -r 2a85adf3c330 -r cf8d1b2388b8 make/CompileJavaModules.gmk
--- a/make/CompileJavaModules.gmk Mon Oct 08 14:14:52 2018 -0700
+++ b/make/CompileJavaModules.gmk Mon Oct 08 18:23:05 2018 -0400
@@ -384,6 +384,29 @@
################################################################################
+jdk.packager_ADD_JAVAC_FLAGS += -parameters -XDstringConcat=inline
+
+jdk.packager_SETUP := GENERATE_JDKBYTECODE_NOWARNINGS
+jdk.packager_COPY += .gif .png .txt .spec .script .prerm .preinst .postrm .postinst .list \
+ .desktop .copyright .control .plist .template .icns .scpt .entitlements .wxs .iss .ico .bmp
+
+jdk.packager_CLEAN_FILES += $(wildcard \
+ $(TOPDIR)/src/jdk.packager/share/classes/jdk/packager/internal/resources/*.properties \
+ $(TOPDIR)/src/jdk.packager/share/classes/jdk/packager/internal/resources/builders/*.properties \
+ $(TOPDIR)/src/jdk.packager/linux/classes/jdk/packager/internal/resources/builders/linux/*.properties \
+ $(TOPDIR)/src/jdk.packager/macosx/classes/jdk/packager/internal/resources/builders/mac/*.properties \
+ $(TOPDIR)/src/jdk.packager/windows/classes/jdk/packager/internal/resources/builders/windows/*.properties \
+ $(TOPDIR)/src/jdk.packager/linux/classes/jdk/packager/internal/resources/linux/*.properties \
+ $(TOPDIR)/src/jdk.packager/macosx/classes/jdk/packager/internal/resources/mac/*.properties \
+ $(TOPDIR)/src/jdk.packager/windows/classes/jdk/packager/internal/resources/windows/*.properties)
+
+################################################################################
+
+jdk.packager.services_SETUP := GENERATE_JDKBYTECODE_NOWARNINGS
+jdk.packager.services_COPY += .gif .png .txt
+
+################################################################################
+
jdk.jconsole_COPY += .gif .png
jdk.jconsole_CLEAN_FILES += $(wildcard \
diff -r 2a85adf3c330 -r cf8d1b2388b8 make/common/Modules.gmk
--- a/make/common/Modules.gmk Mon Oct 08 14:14:52 2018 -0700
+++ b/make/common/Modules.gmk Mon Oct 08 18:23:05 2018 -0400
@@ -128,6 +128,8 @@
JRE_TOOL_MODULES += \
jdk.jdwp.agent \
jdk.pack \
+ jdk.packager \
+ jdk.packager.services \
jdk.scripting.nashorn.shell \
#
@@ -168,6 +170,8 @@
jdk.naming.rmi \
jdk.net \
jdk.pack \
+ jdk.packager \
+ jdk.packager.services \
jdk.rmic \
jdk.scripting.nashorn \
jdk.sctp \
@@ -227,6 +231,14 @@
endif
################################################################################
+# Some platforms don't have jpackager
+
+ifeq ($(OPENJDK_TARGET_OS), solaris)
+ MODULES_FILTER += jdk.packager
+ MODULES_FILTER += jdk.packager.services
+endif
+
+################################################################################
# Module list macros
# Use append so that the custom extension may add to these variables
diff -r 2a85adf3c330 -r cf8d1b2388b8 make/launcher/Launcher-jdk.packager.gmk
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/make/launcher/Launcher-jdk.packager.gmk Mon Oct 08 18:23:05 2018 -0400
@@ -0,0 +1,73 @@
+#
+# Copyright (c) 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.
+#
+
+include LauncherCommon.gmk
+
+
+################################################################################
+
+ifeq ($(OPENJDK_TARGET_OS), windows)
+
+JPACKAGEREXE_SRC := $(TOPDIR)/src/jdk.packager/windows/native/jpackager
+
+$(eval $(call SetupJdkExecutable, BUILD_JPACKAGEREXE, \
+ NAME := jpackager, \
+ SRC := $(JPACKAGEREXE_SRC), \
+ TOOLCHAIN := TOOLCHAIN_LINK_CXX, \
+ OPTIMIZATION := LOW, \
+ CFLAGS := $(CXXFLAGS_JDKEXE) -nologo -DFULL -EHsc \
+ -DWIN32 -D_LITTLE_ENDIAN -DWIN32_LEAN_AND_MEAN \
+ -DUNICODE -D_UNICODE, \
+ CFLAGS_release := -DPRODUCT, \
+ DISABLED_WARNINGS_gcc := unused-result implicit-fallthrough, \
+ LDFLAGS := $(LDFLAGS_JDKEXE) $(LDFLAGS_CXX_JDK) -nologo \
+ $(call SET_SHARED_LIBRARY_ORIGIN), \
+ LIBS := $(LIBCXX) user32.lib shell32.lib advapi32.lib ole32.lib, \
+ VERSIONINFO_RESOURCE := $(GLOBAL_VERSION_INFO_RESOURCE), \
+))
+
+TARGETS += $(BUILD_JPACKAGEREXE)
+
+else
+
+ # Chmod to avoid permission issues for exploded JDK
+define copy-and-chmod
+ $(install-file)
+ $(CHMOD) +x $@
+endef
+
+#copy script for Unix
+$(eval $(call SetupCopyFiles, COPY_JPACKAGERSCRIPT, \
+ SRC := $(TOPDIR)/src/jdk.packager/unix/scripts/, \
+ DEST := $(SUPPORT_OUTPUTDIR)/modules_cmds/$(MODULE), \
+ FILES := jpackager, \
+ MACRO := copy-and-chmod, \
+))
+
+TARGETS += $(COPY_JPACKAGERSCRIPT)
+
+endif
+
+################################################################################
diff -r 2a85adf3c330 -r cf8d1b2388b8 make/lib/Lib-jdk.packager.gmk
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/make/lib/Lib-jdk.packager.gmk Mon Oct 08 18:23:05 2018 -0400
@@ -0,0 +1,125 @@
+#
+# Copyright (c) 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.
+#
+
+include LibCommon.gmk
+
+################################################################################
+
+LIBPACKAGER_SRC_ROOT := $(TOPDIR)/src/jdk.packager
+LIBPACKAGER_SHARED_SRC := $(LIBPACKAGER_SRC_ROOT)/share/native/library/common
+LIBPACKAGER_PLATFORM_SRC := $(LIBPACKAGER_SRC_ROOT)/$(OPENJDK_TARGET_OS)/native/library
+
+BUILD_LIBPACKAGER_SRC :=
+
+#FIXME: make separate directories for each platform
+ifeq ($(OPENJDK_TARGET_OS), macosx)
+ BUILD_LIBPACKAGER_SRC := $(LIBPACKAGER_PLATFORM_SRC)
+endif
+BUILD_LIBPACKAGER_SRC += $(LIBPACKAGER_SHARED_SRC)
+
+
+# Output shared library and debug symbols files in the same directory as .obj files.
+$(eval $(call SetupJdkLibrary, BUILD_LIBPACKAGER, \
+ NAME := packager, \
+ SRC := $(BUILD_LIBPACKAGER_SRC), \
+ OUTPUT_DIR := $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/libpackager, \
+ TOOLCHAIN := TOOLCHAIN_LINK_CXX, \
+ OPTIMIZATION := LOW, \
+ CFLAGS := $(CXXFLAGS_JDKLIB) -I$(LIBPACKAGER_SHARED_SRC), \
+ CFLAGS_windows := -nologo -EHsc -D_WINDOWS -DUNICODE -D_UNICODE -DWIN32 -D_LITTLE_ENDIAN -DWIN32_LEAN_AND_MEAN, \
+ 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, \
+ CFLAGS_linux := -Wextra -Wformat -Wformat-security -c -fPIC, \
+ LDFLAGS_macosx := -dynamiclib -stdlib=libc++, \
+ LIBS_macosx := -ldl -framework Cocoa, \
+ VERSIONINFO_RESOURCE := $(GLOBAL_VERSION_INFO_RESOURCE), \
+))
+
+$(BUILD_LIBPACKAGER): $(call FindLib, java.base, java)
+
+TARGETS += $(BUILD_LIBPACKAGER)
+
+################################################################################
+
+PACKAGERAPPLAUNCHEREXE_SRC := $(TOPDIR)/src/jdk.packager/$(OPENJDK_TARGET_OS)/native/launcher
+
+
+# Output executable and debug symbols files in the same directory as .obj files.
+$(eval $(call SetupJdkExecutable, BUILD_PACKAGERAPPLAUNCHEREXE, \
+ NAME := papplauncher, \
+ SRC := $(PACKAGERAPPLAUNCHEREXE_SRC), \
+ OUTPUT_DIR := $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/papplauncher, \
+ TOOLCHAIN := TOOLCHAIN_LINK_CXX, \
+ OPTIMIZATION := LOW, \
+ CFLAGS := $(CXXFLAGS_JDKEXE) -DFULL, \
+ CFLAGS_release := -DPRODUCT, \
+ CFLAGS_linux := -fPIC, \
+ CFLAGS_solaris := -KPIC, \
+ CFLAGS_macosx := -fPIC, \
+ CFLAGS_windows := -nologo -EHsc -D_WINDOWS -DUNICODE -D_UNICODE -DWIN32 -D_LITTLE_ENDIAN -DWIN32_LEAN_AND_MEAN, \
+ DISABLED_WARNINGS_gcc := unused-result implicit-fallthrough, \
+ LDFLAGS := $(LDFLAGS_JDKEXE) $(LDFLAGS_CXX_JDK) \
+ $(call SET_SHARED_LIBRARY_ORIGIN), \
+ LDFLAGS_macosx := -stdlib=libstdc++, \
+ LIBS_macosx := -framework Cocoa, \
+ LIBS := $(LIBCXX), \
+ LIBS_solaris := -lc, \
+ LIBS_linux := -ldl, \
+ LIBS_windows := user32.lib shell32.lib advapi32.lib, \
+ VERSIONINFO_RESOURCE := $(GLOBAL_VERSION_INFO_RESOURCE), \
+))
+
+TARGETS += $(BUILD_PACKAGERAPPLAUNCHEREXE)
+
+################################################################################
+
+
+# Copy debug symbols to module lib output directory so that JDK build system put them in jdk/bin directory.
+# Copy binaries to module classes output directory so that JDK build system put them in module resources.
+
+ifeq ($(OPENJDK_TARGET_OS), macosx)
+ RESOURCE_SUBDIR := mac
+else
+ RESOURCE_SUBDIR := $(OPENJDK_TARGET_OS)
+endif
+
+SetupCopyTargetFiles = \
+ $(eval $(call SetupCopyFiles, COPY_DEBUG_SYMBOLS_$1, \
+ SRC := $(dir $(firstword $($1))), \
+ DEST := $(INSTALL_LIBRARIES_HERE), \
+ FILES := $(notdir $(filter %.diz %.pdb %.map, $($1))) \
+ )) \
+ $(eval $(call SetupCopyFiles, COPY_BINARIES_$1, \
+ SRC := $(dir $(firstword $($1))), \
+ DEST := $(JDK_OUTPUTDIR)/modules/$(MODULE)/jdk/packager/internal/resources/$(RESOURCE_SUBDIR), \
+ FILES := $(notdir $(firstword $($1))) \
+ )) \
+ $(eval TARGETS += $(COPY_DEBUG_SYMBOLS_$1) $(COPY_BINARIES_$1))
+
+$(call SetupCopyTargetFiles,BUILD_LIBPACKAGER)
+$(call SetupCopyTargetFiles,BUILD_PACKAGERAPPLAUNCHEREXE)
diff -r 2a85adf3c330 -r cf8d1b2388b8 src/jdk.jlink/share/classes/module-info.java
--- a/src/jdk.jlink/share/classes/module-info.java Mon Oct 08 14:14:52 2018 -0700
+++ b/src/jdk.jlink/share/classes/module-info.java Mon Oct 08 18:23:05 2018 -0400
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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
@@ -76,4 +76,7 @@
jdk.tools.jlink.internal.plugins.IncludeLocalesPlugin,
jdk.tools.jlink.internal.plugins.GenerateJLIClassesPlugin,
jdk.tools.jlink.internal.plugins.ReleaseInfoPlugin;
+
+ exports jdk.tools.jlink.internal.packager to
+ jdk.packager;
}
diff -r 2a85adf3c330 -r cf8d1b2388b8 src/jdk.packager.services/share/classes/jdk/packager/services/package.html
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.packager.services/share/classes/jdk/packager/services/package.html Mon Oct 08 18:23:05 2018 -0400
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+ jdk.packager.services
+
+
+Defines the services used by the jpackager tool. This package is
+not typically available in the Java Runtime, you must explicitly include
+the 'jdk.packager.services' module from the jmod directory of
+the JDK as part of your application bundle.
+
+
diff -r 2a85adf3c330 -r cf8d1b2388b8 src/jdk.packager.services/share/classes/jdk/packager/services/singleton/SingleInstanceImpl.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.packager.services/share/classes/jdk/packager/services/singleton/SingleInstanceImpl.java Mon Oct 08 18:23:05 2018 -0400
@@ -0,0 +1,456 @@
+/*
+ * Copyright (c) 2017, 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.packager.services.singleton;
+
+import java.awt.Desktop;
+import java.awt.desktop.OpenFilesHandler;
+import java.awt.desktop.OpenFilesEvent;
+import java.net.ServerSocket;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.io.File;
+import java.io.PrintStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+import java.security.PrivilegedAction;
+import java.security.AccessController;
+import java.security.SecureRandom;
+
+
+class SingleInstanceImpl {
+
+ static final String SI_FILEDIR = getTmpDir() + File.separator
+ + "si" + File.separator;
+ static final String SI_MAGICWORD = "javapackager.singleinstance.init";
+ static final String SI_ACK = "javapackager.singleinstance.ack";
+ static final String SI_STOP = "javapackager.singleinstance.stop";
+ static final String SI_EOF = "javapackager.singleinstance.EOF";
+
+ private final ArrayList siListeners =
+ new ArrayList<>();
+ private SingleInstanceServer siServer;
+
+ private static final SecureRandom random = new SecureRandom();
+ private static volatile boolean serverStarted = false;
+ private static int randomNumber;
+
+ private final Object lock = new Object();
+
+ static String getSingleInstanceFilePrefix(final String stringId) {
+ String filePrefix = stringId.replace('/','_');
+ filePrefix = filePrefix.replace(':','_');
+ return filePrefix;
+ }
+
+ static String getTmpDir() {
+ String os = System.getProperty("os.name").toLowerCase();
+ if (os.contains("win")) {
+ return System.getProperty("user.home")
+ + "\\AppData\\LocalLow\\Sun\\Java\\Packager\\tmp";
+ } else if (os.contains("mac") || os.contains("os x")) {
+ return System.getProperty("user.home")
+ + "/Library/Application Support/Oracle/Java/Packager/tmp";
+ } else if (os.contains("nix") || os.contains("nux")
+ || os.contains("aix")) {
+ return System.getProperty("user.home") + "/.java/packager/tmp";
+ }
+
+ return System.getProperty("java.io.tmpdir");
+ }
+
+ void addSingleInstanceListener(SingleInstanceListener sil, String id) {
+
+ if (sil == null || id == null) {
+ return;
+ }
+
+ // start a new server thread for this unique id
+ // first time
+ synchronized (lock) {
+ if (!serverStarted) {
+ SingleInstanceService.trace("unique id: " + id);
+ try {
+ siServer = new SingleInstanceServer(id);
+ siServer.start();
+ } catch (Exception e) {
+ SingleInstanceService.trace(
+ "addSingleInstanceListener failed");
+ SingleInstanceService.trace(e);
+ return; // didn't start
+ }
+ serverStarted = true;
+ }
+ }
+
+ synchronized (siListeners) {
+ // add the sil to the arrayList
+ if (!siListeners.contains(sil)) {
+ siListeners.add(sil);
+ }
+ }
+
+ }
+
+ class SingleInstanceServer {
+
+ private final SingleInstanceServerRunnable runnable;
+ private final Thread thread;
+
+ SingleInstanceServer(SingleInstanceServerRunnable runnable)
+ throws IOException {
+ thread = new Thread(null, runnable, "JavaPackagerSIThread",
+ 0, false);
+ thread.setDaemon(true);
+ this.runnable = runnable;
+ }
+
+ SingleInstanceServer(String stringId) throws IOException {
+ this(new SingleInstanceServerRunnable(stringId));
+ }
+
+ int getPort() {
+ return runnable.getPort();
+ }
+
+ void start() {
+ thread.start();
+ }
+ }
+
+ private class SingleInstanceServerRunnable implements Runnable {
+
+ ServerSocket ss;
+ int port;
+ String stringId;
+ String[] arguments;
+
+ int getPort() {
+ return port;
+ }
+
+ SingleInstanceServerRunnable(String id) throws IOException {
+ stringId = id;
+
+ // open a free ServerSocket
+ ss = null;
+
+ // we should bind the server to the local InetAddress 127.0.0.1
+ // port number is automatically allocated for current SI
+ ss = new ServerSocket(0, 0, InetAddress.getByName("127.0.0.1"));
+
+ // get the port number
+ port = ss.getLocalPort();
+ SingleInstanceService.trace("server port at: " + port);
+
+ // create the single instance file with canonical home and port num
+ createSingleInstanceFile(stringId, port);
+ }
+
+ private String getSingleInstanceFilename(final String id,
+ final int port) {
+ String name = SI_FILEDIR + getSingleInstanceFilePrefix(id)
+ + "_" + port;
+ SingleInstanceService.trace("getSingleInstanceFilename: " + name);
+ return name;
+ }
+
+ private void removeSingleInstanceFile(final String id, final int port) {
+ new File(getSingleInstanceFilename(id, port)).delete();
+ SingleInstanceService.trace("removed SingleInstanceFile: "
+ + getSingleInstanceFilename(id, port));
+ }
+
+ private void createSingleInstanceFile(final String id, final int port) {
+ String filename = getSingleInstanceFilename(id, port);
+ final File siFile = new File(filename);
+ final File siDir = new File(SI_FILEDIR);
+ AccessController.doPrivileged(new PrivilegedAction() {
+ @Override
+ public Void run() {
+ siDir.mkdirs();
+ String[] fList = siDir.list();
+ if (fList != null) {
+ String prefix = getSingleInstanceFilePrefix(id);
+ for (String file : fList) {
+ // if file with the same prefix exist, remove it
+ if (file.startsWith(prefix)) {
+ SingleInstanceService.trace(
+ "file should be removed: "
+ + SI_FILEDIR + file);
+ new File(SI_FILEDIR + file).delete();
+ }
+ }
+ }
+
+ PrintStream out = null;
+ try {
+ siFile.createNewFile();
+ siFile.deleteOnExit();
+ // write random number to single instance file
+ out = new PrintStream(new FileOutputStream(siFile));
+ randomNumber = random.nextInt();
+ out.print(randomNumber);
+ } catch (IOException ioe) {
+ SingleInstanceService.trace(ioe);
+ } finally {
+ if (out != null) {
+ out.close();
+ }
+ }
+ return null;
+ }
+ });
+ }
+
+ @Override
+ public void run() {
+ // start sil to handle all the incoming request
+ // from the server port of the current url
+ AccessController.doPrivileged(new PrivilegedAction() {
+ @Override
+ public Void run() {
+ List recvArgs = new ArrayList<>();
+ while (true) {
+ recvArgs.clear();
+ InputStream is = null;
+ BufferedReader in = null;
+ InputStreamReader isr = null;
+ Socket s = null;
+ String line = null;
+ boolean sendAck = false;
+ int port = -1;
+ String charset = null;
+ try {
+ SingleInstanceService.trace("waiting connection");
+ s = ss.accept();
+ is = s.getInputStream();
+ // read first byte for encoding type
+ int encoding = is.read();
+ if (encoding ==
+ SingleInstanceService.ENCODING_PLATFORM) {
+ charset = Charset.defaultCharset().name();
+ } else if (encoding ==
+ SingleInstanceService.ENCODING_UNICODE) {
+ charset =
+ SingleInstanceService.ENCODING_UNICODE_NAME;
+ } else {
+ SingleInstanceService.trace(
+ "SingleInstanceImpl - unknown encoding");
+ return null;
+ }
+ isr = new InputStreamReader(is, charset);
+ in = new BufferedReader(isr);
+ // first read the random number
+ line = in.readLine();
+ if (line.equals(String.valueOf(randomNumber)) ==
+ false) {
+ // random number does not match
+ // should not happen
+ // shutdown server socket
+ removeSingleInstanceFile(stringId, port);
+ ss.close();
+ serverStarted = false;
+ SingleInstanceService.trace("Unexpected Error, "
+ + "SingleInstanceService disabled");
+ return null;
+ } else {
+ line = in.readLine();
+ // no need to continue reading if MAGICWORD
+ // did not come first
+ SingleInstanceService.trace("recv: " + line);
+ if (line.equals(SI_MAGICWORD)) {
+ SingleInstanceService.trace(
+ "got magic word.");
+ while (true) {
+ // Get input string
+ try {
+ line = in.readLine();
+ if (line != null
+ && line.equals(SI_EOF)) {
+ // end of file reached
+ break;
+ } else {
+ recvArgs.add(line);
+ }
+ } catch (IOException ioe) {
+ SingleInstanceService.trace(ioe);
+ }
+ }
+ arguments = recvArgs.toArray(
+ new String[recvArgs.size()]);
+ sendAck = true;
+ } else if (line.equals(SI_STOP)) {
+ // remove the SingleInstance file
+ removeSingleInstanceFile(stringId, port);
+ break;
+ }
+ }
+ } catch (IOException ioe) {
+ SingleInstanceService.trace(ioe);
+ } finally {
+ try {
+ if (sendAck) {
+ // let the action listener handle the rest
+ for (String arg : arguments) {
+ SingleInstanceService.trace(
+ "Starting new instance with "
+ + "arguments: arg:" + arg);
+ }
+
+ performNewActivation(arguments);
+
+ // now the event is handled, we can send
+ // out the ACK
+ SingleInstanceService.trace(
+ "sending out ACK");
+ if (s != null) {
+ try (OutputStream os =
+ s.getOutputStream();
+ PrintStream ps = new PrintStream(os,
+ true, charset)) {
+ // send OK (ACK)
+ ps.println(SI_ACK);
+ ps.flush();
+ }
+ }
+ }
+
+ if (in != null) {
+ in.close();
+ }
+
+ if (isr != null) {
+ isr.close();
+ }
+
+ if (is != null) {
+ is.close();
+ }
+
+ if (s != null) {
+ s.close();
+ }
+ } catch (IOException ioe) {
+ SingleInstanceService.trace(ioe);
+ }
+ }
+ }
+ return null;
+ }
+ });
+ }
+ }
+
+ private void performNewActivation(final String[] args) {
+ // enumerate the sil list and call
+ // each sil with arguments
+ @SuppressWarnings("unchecked")
+ ArrayList silal =
+ (ArrayList)siListeners.clone();
+ silal.forEach(sil -> sil.newActivation(args));
+ }
+
+ void setOpenFileHandler() {
+ String os = System.getProperty("os.name").toLowerCase();
+ if (!os.contains("mac") && !os.contains("os x")) {
+ return;
+ }
+
+ Desktop.getDesktop().setOpenFileHandler(new OpenFilesHandler() {
+ @Override
+ public void openFiles(OpenFilesEvent e) {
+ List arguments = new ArrayList<>();
+ e.getFiles().forEach(file -> arguments.add(file.toString()));
+ performNewActivation(arguments.toArray(
+ new String[arguments.size()]));
+ }
+ });
+ }
+
+ void removeSingleInstanceListener(SingleInstanceListener sil) {
+ if (sil == null) {
+ return;
+ }
+
+ synchronized (siListeners) {
+
+ if (!siListeners.remove(sil)) {
+ return;
+ }
+
+ if (siListeners.isEmpty()) {
+ AccessController.doPrivileged(new PrivilegedAction() {
+ @Override
+ public Void run() {
+ // stop server
+ Socket socket = null;
+ PrintStream out = null;
+ OutputStream os = null;
+ try {
+ socket = new Socket("127.0.0.1",
+ siServer.getPort());
+ os = socket.getOutputStream();
+ byte[] encoding = new byte[1];
+ encoding[0] =
+ SingleInstanceService.ENCODING_PLATFORM;
+ os.write(encoding);
+ String charset = Charset.defaultCharset().name();
+ out = new PrintStream(os, true, charset);
+ out.println(randomNumber);
+ out.println(SingleInstanceImpl.SI_STOP);
+ out.flush();
+ serverStarted = false;
+ } catch (IOException ioe) {
+ SingleInstanceService.trace(ioe);
+ } finally {
+ try {
+ if (out != null) {
+ out.close();
+ }
+ if (os != null) {
+ os.close();
+ }
+ if (socket != null) {
+ socket.close();
+ }
+ } catch (IOException ioe) {
+ SingleInstanceService.trace(ioe);
+ }
+ }
+ return null;
+ }
+ });
+ }
+ }
+ }
+}
diff -r 2a85adf3c330 -r cf8d1b2388b8 src/jdk.packager.services/share/classes/jdk/packager/services/singleton/SingleInstanceListener.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.packager.services/share/classes/jdk/packager/services/singleton/SingleInstanceListener.java Mon Oct 08 18:23:05 2018 -0400
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2017, 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.packager.services.singleton;
+
+/**
+ * The {@code SingleInstanceListener} interface is used for implementing
+ * Single Instance functionality for Java Packager.
+ *
+ * @since 10
+ */
+public interface SingleInstanceListener {
+
+ /**
+ * This method should be implemented by the application to
+ * handle the single instance behaviour - how should the application
+ * handle the arguments when another instance of the application is
+ * invoked with params.
+ *
+ * @param params parameters for the application main
+ */
+ public void newActivation(String... params);
+}
diff -r 2a85adf3c330 -r cf8d1b2388b8 src/jdk.packager.services/share/classes/jdk/packager/services/singleton/SingleInstanceNewActivation.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.packager.services/share/classes/jdk/packager/services/singleton/SingleInstanceNewActivation.java Mon Oct 08 18:23:05 2018 -0400
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2017, 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.packager.services.singleton;
+
+import java.util.Arrays;
+
+// This class is used for notifying Single Instance for Java Packager.
+
+public class SingleInstanceNewActivation {
+
+ public static void main(String[] args) {
+
+ if (args.length < 2) {
+ // no user args specified
+ return;
+ }
+
+ // the first arg is process id of the single instance
+ String appId = SingleInstanceService.APP_ID_PREFIX + args[0];
+
+ if (SingleInstanceService.isServerRunning(appId)) {
+ String[] newArgs = Arrays.copyOfRange(args, 1, args.length);
+ SingleInstanceService.connectToServer(newArgs);
+ }
+ }
+}
diff -r 2a85adf3c330 -r cf8d1b2388b8 src/jdk.packager.services/share/classes/jdk/packager/services/singleton/SingleInstanceService.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.packager.services/share/classes/jdk/packager/services/singleton/SingleInstanceService.java Mon Oct 08 18:23:05 2018 -0400
@@ -0,0 +1,274 @@
+/*
+ * Copyright (c) 2017, 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.packager.services.singleton;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.net.Socket;
+import java.nio.charset.Charset;
+
+/**
+ * The {@code SingleInstanceService} class provides public methods for using
+ * Single Instance functionality for Java Packager. To use these methods,
+ * the option named "-singleton" must be specified on javapackager command line.
+ *
+ * @since 10
+ */
+public class SingleInstanceService {
+
+ static private boolean DEBUG = false;
+ static private PrintStream DEBUG_STREAM = null;
+
+ static private int currPort;
+ static private String stringId = null;
+ static private String randomNumberString = null;
+
+ static private SingleInstanceImpl instance = null;
+
+ static final int ENCODING_PLATFORM = 1;
+ static final int ENCODING_UNICODE = 2;
+
+ static final String ENCODING_PLATFORM_NAME = "UTF-8";
+ static final String ENCODING_UNICODE_NAME = "UTF-16LE";
+
+ static final String APP_ID_PREFIX = "javapackager.si.";
+
+ private SingleInstanceService() {}
+
+ static void enableDebug(boolean enable, PrintStream stream) {
+ DEBUG = enable;
+ DEBUG_STREAM = stream;
+ }
+
+ static void trace(String message) {
+ if (DEBUG && DEBUG_STREAM != null) {
+ DEBUG_STREAM.println(message);
+ }
+ }
+
+ static void trace(Throwable t) {
+ if (DEBUG && DEBUG_STREAM != null) {
+ t.printStackTrace(DEBUG_STREAM);
+ }
+ }
+
+ /**
+ * Registers {@code SingleInstanceListener} for current process.
+ * If the {@code SingleInstanceListener} object is already registered, or
+ * {@code slistener} is {@code null}, then the registration is skipped.
+ *
+ * @param slistener the listener to handle the single instance behaviour.
+ */
+ public static void registerSingleInstance(
+ SingleInstanceListener slistener) {
+ registerSingleInstance(slistener, false);
+ }
+
+ /**
+ * Registers {@code SingleInstanceListener} for current process.
+ * If the {@code SingleInstanceListener} object is already registered, or
+ * {@code slistener} is {@code null}, then the registration is skipped.
+ *
+ * @param slistener the listener to handle the single instance behaviour.
+ * @param setFileHandler if {@code true}, the listener is notified when the
+ * application is asked to open a list of files. If OS is not MacOS,
+ * the parameter is ignored.
+ */
+ public static void registerSingleInstance(SingleInstanceListener slistener,
+ boolean setFileHandler) {
+ String appId = APP_ID_PREFIX + ProcessHandle.current().pid();
+ registerSingleInstanceForId(slistener, appId, setFileHandler);
+ }
+
+ static void registerSingleInstanceForId(SingleInstanceListener slistener,
+ String stringId, boolean setFileHandler) {
+ // register SingleInstanceListener for given Id
+ instance = new SingleInstanceImpl();
+ instance.addSingleInstanceListener(slistener, stringId);
+ if (setFileHandler) {
+ instance.setOpenFileHandler();
+ }
+ }
+
+ /**
+ * Unregisters {@code SingleInstanceListener} for current process.
+ * If the {@code SingleInstanceListener} object is not registered, or
+ * {@code slistener} is {@code null}, then the unregistration is skipped.
+ *
+ * @param slistener the listener for unregistering.
+ */
+ public static void unregisterSingleInstance(
+ SingleInstanceListener slistener) {
+ instance.removeSingleInstanceListener(slistener);
+ }
+
+ /**
+ * Returns true if single instance server is running for the id
+ */
+ static boolean isServerRunning(String id) {
+ trace("isServerRunning ? : "+ id);
+ File siDir = new File(SingleInstanceImpl.SI_FILEDIR);
+ String[] fList = siDir.list();
+ if (fList != null) {
+ String prefix = SingleInstanceImpl.getSingleInstanceFilePrefix(id);
+ for (String file : fList) {
+ trace("isServerRunning: " + file);
+ trace("\t String id: " + id);
+ trace("\t SingleInstanceFilePrefix: " + prefix);
+ // if file with the same prefix already exist, server is running
+ if (file.startsWith(prefix)) {
+ try {
+ currPort = Integer.parseInt(
+ file.substring(file.lastIndexOf('_') + 1));
+ trace("isServerRunning: " + file
+ + ": port: " + currPort);
+ } catch (NumberFormatException nfe) {
+ trace("isServerRunning: " + file
+ + ": port parsing failed");
+ trace(nfe);
+ return false;
+ }
+
+ trace("Server running at port: " + currPort);
+ File siFile = new File(SingleInstanceImpl.SI_FILEDIR, file);
+
+ // get random number from single instance file
+ try (BufferedReader br = new BufferedReader(
+ new FileReader(siFile))) {
+ randomNumberString = br.readLine();
+ trace("isServerRunning: " + file + ": magic: "
+ + randomNumberString);
+ } catch (IOException ioe ) {
+ trace("isServerRunning: " + file
+ + ": reading magic failed");
+ trace(ioe);
+ }
+ trace("isServerRunning: " + file + ": setting id - OK");
+ stringId = id;
+ return true;
+ } else {
+ trace("isServerRunning: " + file + ": prefix NOK");
+ }
+ }
+ } else {
+ trace("isServerRunning: empty file list");
+ }
+ trace("isServerRunning: false");
+ return false;
+ }
+
+ /**
+ * Returns true if we connect successfully to the server for the stringId
+ */
+ static boolean connectToServer(String[] args) {
+ trace("Connect to: " + stringId + " " + currPort);
+
+ if (randomNumberString == null) {
+ // should not happen
+ trace("MAGIC number is null, bail out.");
+ return false;
+ }
+
+ // Now we open the tcpSocket and the stream
+ Socket socket = null;
+ OutputStream os = null;
+ PrintStream out = null;
+ InputStreamReader isr = null;
+ BufferedReader br = null;
+ try {
+ socket = new Socket("127.0.0.1", currPort);
+ os = socket.getOutputStream();
+ byte[] encoding = new byte[1];
+ encoding[0] = ENCODING_PLATFORM;
+ os.write(encoding);
+ String encodingName = Charset.defaultCharset().name();
+
+ out = new PrintStream(os, true, encodingName);
+ isr = new InputStreamReader(socket.getInputStream(), encodingName);
+ br = new BufferedReader(isr);
+
+ // send random number
+ out.println(randomNumberString);
+ // send MAGICWORD
+ out.println(SingleInstanceImpl.SI_MAGICWORD);
+
+ for (String arg : args) {
+ out.println(arg);
+ }
+
+ // indicate end of file transmission
+ out.println(SingleInstanceImpl.SI_EOF);
+ out.flush();
+
+ // wait for ACK (OK) response
+ trace("Waiting for ack");
+ final int tries = 5;
+
+ // try to listen for ACK
+ for (int i=0; i < tries; i++) {
+ String str = br.readLine();
+ if (str != null && str.equals(SingleInstanceImpl.SI_ACK)) {
+ trace("Got ACK");
+ return true;
+ }
+ }
+ } catch (java.net.SocketException se) {
+ // no server is running - continue launch
+ trace("No server is running - continue launch.");
+ trace(se);
+ } catch (Exception ioe) {
+ trace(ioe);
+ }
+ finally {
+ try {
+ if (br != null) {
+ br.close();
+ }
+ if (isr != null) {
+ isr.close();
+ }
+ if (out != null) {
+ out.close();
+ }
+ if (os != null) {
+ os.close();
+ }
+ if (socket != null) {
+ socket.close();
+ }
+ } catch (IOException ioe) {
+ trace(ioe);
+ }
+ }
+ trace("No ACK from server, bail out.");
+ return false;
+ }
+}
diff -r 2a85adf3c330 -r cf8d1b2388b8 src/jdk.packager.services/share/classes/module-info.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.packager.services/share/classes/module-info.java Mon Oct 08 18:23:05 2018 -0400
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 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.
+ */
+
+/**
+ * Defines the services used by the jpackager tool.
+ *
+ * @moduleGraph
+ * @since 11
+ */
+module jdk.packager.services {
+ exports jdk.packager.services.singleton;
+
+ requires java.desktop;
+
+}
diff -r 2a85adf3c330 -r cf8d1b2388b8 src/jdk.packager/linux/classes/jdk/packager/internal/builders/linux/LinuxAppImageBuilder.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.packager/linux/classes/jdk/packager/internal/builders/linux/LinuxAppImageBuilder.java Mon Oct 08 18:23:05 2018 -0400
@@ -0,0 +1,272 @@
+/*
+ * Copyright (c) 2015, 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.packager.internal.builders.linux;
+
+
+import jdk.packager.internal.BundlerParamInfo;
+import jdk.packager.internal.IOUtils;
+import jdk.packager.internal.Log;
+import jdk.packager.internal.RelativeFileSet;
+import jdk.packager.internal.StandardBundlerParam;
+import jdk.packager.internal.resources.linux.LinuxResources;
+import jdk.packager.internal.builders.AbstractAppImageBuilder;
+
+import java.io.File;
+import java.io.FileInputStream;
+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.nio.file.Files;
+import java.nio.file.Path;
+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 static jdk.packager.internal.StandardBundlerParam.*;
+
+public class LinuxAppImageBuilder extends AbstractAppImageBuilder {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.packager.internal.resources.builders.linux.LinuxAppImageBuilder");
+
+ protected static final String LINUX_BUNDLER_PREFIX =
+ BUNDLER_PREFIX + "linux" + File.separator;
+ private static final String EXECUTABLE_NAME = "JavaAppLauncher";
+ private static final String LIBRARY_NAME = "libpackager.so";
+
+ private final Path root;
+ private final Path appDir;
+ private final Path runtimeDir;
+ private final Path resourcesDir;
+ private final Path mdir;
+
+ private final Map params;
+
+ public static final BundlerParamInfo ICON_PNG =
+ new StandardBundlerParam<>(
+ I18N.getString("param.icon-png.name"),
+ I18N.getString("param.icon-png.description"),
+ "icon.png",
+ File.class,
+ params -> {
+ File f = ICON.fetchFrom(params);
+ if (f != null && !f.getName().toLowerCase().endsWith(".png")) {
+ Log.info(MessageFormat.format(I18N.getString(
+ "message.icon-not-png"), f));
+ return null;
+ }
+ return f;
+ },
+ (s, p) -> new File(s));
+
+ public LinuxAppImageBuilder(Map config, Path imageOutDir)
+ throws IOException {
+ super(config,
+ imageOutDir.resolve(APP_NAME.fetchFrom(config) + "/runtime"));
+
+ Objects.requireNonNull(imageOutDir);
+
+ this.root = imageOutDir.resolve(APP_NAME.fetchFrom(config));
+ this.appDir = root.resolve("app");
+ this.runtimeDir = root.resolve("runtime");
+ this.resourcesDir = root.resolve("resources");
+ this.mdir = runtimeDir.resolve("lib");
+ this.params = new HashMap<>();
+ config.entrySet().stream().forEach(e -> params.put(
+ e.getKey().toString(), e.getValue()));
+ Files.createDirectories(appDir);
+ Files.createDirectories(runtimeDir);
+ Files.createDirectories(resourcesDir);
+ }
+
+ public LinuxAppImageBuilder(String appName, Path imageOutDir)
+ throws IOException {
+ super(null, imageOutDir.resolve(appName));
+
+ Objects.requireNonNull(imageOutDir);
+
+ this.root = imageOutDir.resolve(appName);
+ this.appDir = null;
+ this.runtimeDir = null;
+ this.resourcesDir = null;
+ this.mdir = null;
+ this.params = new HashMap<>();
+ }
+
+ private Path destFile(String dir, String filename) {
+ return runtimeDir.resolve(dir).resolve(filename);
+ }
+
+ private void writeEntry(InputStream in, Path dstFile) throws IOException {
+ Files.createDirectories(dstFile.getParent());
+ Files.copy(in, dstFile);
+ }
+
+ private void writeSymEntry(Path dstFile, Path target) throws IOException {
+ Files.createDirectories(dstFile.getParent());
+ Files.createLink(dstFile, target);
+ }
+
+ /**
+ * chmod ugo+x file
+ */
+ private void setExecutable(Path file) {
+ try {
+ Set perms =
+ Files.getPosixFilePermissions(file);
+ perms.add(PosixFilePermission.OWNER_EXECUTE);
+ perms.add(PosixFilePermission.GROUP_EXECUTE);
+ perms.add(PosixFilePermission.OTHERS_EXECUTE);
+ Files.setPosixFilePermissions(file, perms);
+ } catch (IOException ioe) {
+ throw new UncheckedIOException(ioe);
+ }
+ }
+
+ private static void createUtf8File(File file, String content)
+ throws IOException {
+ try (OutputStream fout = new FileOutputStream(file);
+ Writer output = new OutputStreamWriter(fout, "UTF-8")) {
+ output.write(content);
+ }
+ }
+
+
+ //it is static for the sake of sharing with "installer" bundlers
+ // that may skip calls to validate/bundle in this class!
+ public static File getRootDir(File outDir, Map p) {
+ return new File(outDir, APP_FS_NAME.fetchFrom(p));
+ }
+
+ public static String getLauncherName(Map p) {
+ return APP_FS_NAME.fetchFrom(p);
+ }
+
+ public static String getLauncherCfgName(Map p) {
+ return "app/" + APP_FS_NAME.fetchFrom(p) + ".cfg";
+ }
+
+ @Override
+ public InputStream getResourceAsStream(String name) {
+ return LinuxResources.class.getResourceAsStream(name);
+ }
+
+ @Override
+ public void prepareApplicationFiles() throws IOException {
+ Map originalParams = new HashMap<>(params);
+
+ try {
+ // create the primary launcher
+ createLauncherForEntryPoint(params, root);
+
+ // Copy library to the launcher folder
+ try (InputStream is_lib = getResourceAsStream(LIBRARY_NAME)) {
+ writeEntry(is_lib, root.resolve(LIBRARY_NAME));
+ }
+
+ // create the secondary launchers, if any
+ List