8212780: Packaging Tool Implementation

Co-authored-by: Alexey Semenyuk <alexey.semenyuk@oracle.com>
Co-authored-by: Alexander Matveev <alexander.matveev@oracle.com>
Co-authored-by: Kevin Rushforth <kevin.rushforth@oracle.com>
Co-authored-by: Philip Race <philip.race@oracle.com>
Reviewed-by: asemenyuk, almatvee, herrick, kcr, prr, erikj, ihse, rriggs, mchung, alanb
This commit is contained in:
Andy Herrick 2019-12-05 11:25:33 -05:00
parent 73676cff72
commit 264573c9ce
296 changed files with 45687 additions and 168 deletions

View File

@ -380,6 +380,13 @@ endif
################################################################################
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 \

View File

@ -128,6 +128,7 @@ endif
JRE_TOOL_MODULES += \
jdk.jdwp.agent \
jdk.incubator.jpackage \
jdk.pack \
jdk.scripting.nashorn.shell \
#
@ -149,6 +150,7 @@ DOCS_MODULES += \
jdk.editpad \
jdk.hotspot.agent \
jdk.httpserver \
jdk.incubator.jpackage \
jdk.jartool \
jdk.javadoc \
jdk.jcmd \
@ -242,6 +244,13 @@ ifeq ($(ENABLE_AOT), false)
MODULES_FILTER += jdk.aot
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

View File

@ -397,6 +397,7 @@ endef
# 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 @@ define SetupNativeCompilationBody
$$(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 @@ define SetupNativeCompilationBody
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 @@ define SetupNativeCompilationBody
$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 @@ define SetupNativeCompilationBody
$$($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 @@ define SetupNativeCompilationBody
# 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) \

View File

@ -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, \
))

View File

@ -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

View File

@ -207,7 +207,8 @@ module java.base {
java.management.rmi,
jdk.jartool,
jdk.jfr,
jdk.jlink;
jdk.jlink,
jdk.incubator.jpackage;
exports jdk.internal.perf to
java.management,
jdk.management.agent,

View File

@ -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)
);
}

View File

@ -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}+\\)");
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}
}

View File

@ -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();
}
}

View File

@ -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
);
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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 not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -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

View File

@ -0,0 +1,5 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Files: *
Copyright: APPLICATION_COPYRIGHT
License: APPLICATION_LICENSE_TEXT

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 "$@"
}

View File

@ -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;

View File

@ -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;
}

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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 not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -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>

View File

@ -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

View File

@ -0,0 +1,8 @@
#!/usr/bin/env sh
if [ ! -d "INSTALL_LOCATION" ]
then
mkdir -p "INSTALL_LOCATION"
fi
exit 0

View File

@ -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;

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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();
}
}

View File

@ -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());
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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));
}
}
}

View File

@ -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;
}
}

View File

@ -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 + "}}";
}
}

View File

@ -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+");
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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");
}
}
}

View File

@ -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";
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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("[.][^.]+$", "");
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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");
}
}

View File

@ -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();
}

View File

@ -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 + "}";
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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\

View File

@ -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\

View File

@ -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\

View File

@ -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}

View File

@ -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}

View File

@ -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}

Some files were not shown because too many files have changed in this diff Show More