mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 03:58:21 +00:00
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:
parent
73676cff72
commit
264573c9ce
@ -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 \
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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) \
|
||||
|
||||
30
make/launcher/Launcher-jdk.incubator.jpackage.gmk
Normal file
30
make/launcher/Launcher-jdk.incubator.jpackage.gmk
Normal 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, \
|
||||
))
|
||||
140
make/lib/Lib-jdk.incubator.jpackage.gmk
Normal file
140
make/lib/Lib-jdk.incubator.jpackage.gmk
Normal 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
|
||||
@ -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,
|
||||
|
||||
@ -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)
|
||||
);
|
||||
}
|
||||
@ -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}+\\)");
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
);
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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 |
@ -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
|
||||
@ -0,0 +1,5 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
|
||||
Files: *
|
||||
Copyright: APPLICATION_COPYRIGHT
|
||||
License: APPLICATION_LICENSE_TEXT
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
@ -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 "$@"
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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
@ -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
|
||||
@ -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
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
@ -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>
|
||||
@ -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>
|
||||
@ -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>
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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 |
Binary file not shown.
@ -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>
|
||||
@ -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
|
||||
@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
if [ ! -d "INSTALL_LOCATION" ]
|
||||
then
|
||||
mkdir -p "INSTALL_LOCATION"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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
|
||||
@ -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;
|
||||
}
|
||||
@ -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
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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 + "}}";
|
||||
}
|
||||
|
||||
}
|
||||
@ -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+");
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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";
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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("[.][^.]+$", "");
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
@ -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 + "}";
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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\
|
||||
|
||||
@ -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\
|
||||
|
||||
@ -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\
|
||||
|
||||
@ -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}
|
||||
@ -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}
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user