8212780: JEP 343: Packaging Tool Implementation Reviewed-by: almatvee, kcr, prr, rriggs, ssadetsky, erikj, ihse Contributed-by: andy.herrick@oracle.com, alexander.matveev@oracle.com, semyon.sadetsky@oracle.com, alexey.semenyuk@oracle.com, kevin.rushforth@oracle.com diff --git a/make/CompileJavaModules.gmk b/make/CompileJavaModules.gmk --- a/make/CompileJavaModules.gmk +++ b/make/CompileJavaModules.gmk @@ -380,6 +380,15 @@ ################################################################################ +jdk.jpackage_ADD_JAVAC_FLAGS += -parameters -XDstringConcat=inline + +jdk.jpackage_COPY += .gif .png .txt .spec .script .prerm .preinst .postrm .postinst .list \ + .desktop .copyright .control .plist .template .icns .scpt .entitlements .wxs .iss .ico .bmp + +jdk.jpackage_CLEAN += .properties + +################################################################################ + jdk.jconsole_COPY += .gif .png jdk.jconsole_CLEAN_FILES += $(wildcard \ diff --git a/make/common/Modules.gmk b/make/common/Modules.gmk --- a/make/common/Modules.gmk +++ b/make/common/Modules.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. +# 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 @@ -128,6 +128,7 @@ JRE_TOOL_MODULES += \ jdk.jdwp.agent \ jdk.pack \ + jdk.jpackage \ jdk.scripting.nashorn.shell \ # @@ -168,6 +169,7 @@ jdk.naming.rmi \ jdk.net \ jdk.pack \ + jdk.jpackage \ jdk.rmic \ jdk.scripting.nashorn \ jdk.sctp \ @@ -227,6 +229,13 @@ endif ################################################################################ +# jpackage is only on windows, macosx, and linux + +ifeq ($(filter $(OPENJDK_TARGET_OS), windows macosx linux), ) + MODULES_FILTER += jdk.jpackage +endif + +################################################################################ # Module list macros # Use append so that the custom extension may add to these variables diff --git a/make/common/NativeCompilation.gmk b/make/common/NativeCompilation.gmk --- a/make/common/NativeCompilation.gmk +++ b/make/common/NativeCompilation.gmk @@ -397,6 +397,7 @@ # ARFLAGS the archiver flags to be used # OBJECT_DIR the directory where we store the object files # OUTPUT_DIR the directory where the resulting binary is put +# SYMBOLS_DIR the directory where the debug symbols are put, defaults to OUTPUT_DIR # INCLUDES only pick source from these directories # EXCLUDES do not pick source from these directories # INCLUDE_FILES only compile exactly these files! @@ -511,8 +512,6 @@ $$(call SetIfEmpty, $1_SYSROOT_CFLAGS, $$($$($1_TOOLCHAIN)_SYSROOT_CFLAGS)) $$(call SetIfEmpty, $1_SYSROOT_LDFLAGS, $$($$($1_TOOLCHAIN)_SYSROOT_LDFLAGS)) - # Make sure the dirs exist. - $$(call MakeDir, $$($1_OBJECT_DIR) $$($1_OUTPUT_DIR)) $$(foreach d, $$($1_SRC), $$(if $$(wildcard $$d), , \ $$(error SRC specified to SetupNativeCompilation $1 contains missing directory $$d))) @@ -883,30 +882,31 @@ ifeq ($$($1_COPY_DEBUG_SYMBOLS), true) ifneq ($$($1_DEBUG_SYMBOLS), false) + $$(call SetIfEmpty, $1_SYMBOLS_DIR, $$($1_OUTPUT_DIR)) # Only copy debug symbols for dynamic libraries and programs. ifneq ($$($1_TYPE), STATIC_LIBRARY) # Generate debuginfo files. ifeq ($(call isTargetOs, windows), true) - $1_EXTRA_LDFLAGS += -debug "-pdb:$$($1_OUTPUT_DIR)/$$($1_NOSUFFIX).pdb" \ - "-map:$$($1_OUTPUT_DIR)/$$($1_NOSUFFIX).map" - $1_DEBUGINFO_FILES := $$($1_OUTPUT_DIR)/$$($1_NOSUFFIX).pdb \ - $$($1_OUTPUT_DIR)/$$($1_NOSUFFIX).map + $1_EXTRA_LDFLAGS += -debug "-pdb:$$($1_SYMBOLS_DIR)/$$($1_NOSUFFIX).pdb" \ + "-map:$$($1_SYMBOLS_DIR)/$$($1_NOSUFFIX).map" + $1_DEBUGINFO_FILES := $$($1_SYMBOLS_DIR)/$$($1_NOSUFFIX).pdb \ + $$($1_SYMBOLS_DIR)/$$($1_NOSUFFIX).map else ifeq ($(call isTargetOs, linux solaris), true) - $1_DEBUGINFO_FILES := $$($1_OUTPUT_DIR)/$$($1_NOSUFFIX).debuginfo + $1_DEBUGINFO_FILES := $$($1_SYMBOLS_DIR)/$$($1_NOSUFFIX).debuginfo # Setup the command line creating debuginfo files, to be run after linking. # It cannot be run separately since it updates the original target file $1_CREATE_DEBUGINFO_CMDS := \ $$($1_OBJCOPY) --only-keep-debug $$($1_TARGET) $$($1_DEBUGINFO_FILES) $$(NEWLINE) \ - $(CD) $$($1_OUTPUT_DIR) && \ + $(CD) $$($1_SYMBOLS_DIR) && \ $$($1_OBJCOPY) --add-gnu-debuglink=$$($1_DEBUGINFO_FILES) $$($1_TARGET) else ifeq ($(call isTargetOs, macosx), true) $1_DEBUGINFO_FILES := \ - $$($1_OUTPUT_DIR)/$$($1_BASENAME).dSYM/Contents/Info.plist \ - $$($1_OUTPUT_DIR)/$$($1_BASENAME).dSYM/Contents/Resources/DWARF/$$($1_BASENAME) + $$($1_SYMBOLS_DIR)/$$($1_BASENAME).dSYM/Contents/Info.plist \ + $$($1_SYMBOLS_DIR)/$$($1_BASENAME).dSYM/Contents/Resources/DWARF/$$($1_BASENAME) $1_CREATE_DEBUGINFO_CMDS := \ - $(DSYMUTIL) --out $$($1_OUTPUT_DIR)/$$($1_BASENAME).dSYM $$($1_TARGET) + $(DSYMUTIL) --out $$($1_SYMBOLS_DIR)/$$($1_BASENAME).dSYM $$($1_TARGET) endif # Since the link rule creates more than one file that we want to track, @@ -928,14 +928,14 @@ $1 += $$($1_DEBUGINFO_FILES) ifeq ($$($1_ZIP_EXTERNAL_DEBUG_SYMBOLS), true) - $1_DEBUGINFO_ZIP := $$($1_OUTPUT_DIR)/$$($1_NOSUFFIX).diz + $1_DEBUGINFO_ZIP := $$($1_SYMBOLS_DIR)/$$($1_NOSUFFIX).diz $1 += $$($1_DEBUGINFO_ZIP) # The dependency on TARGET is needed for debuginfo files # to be rebuilt properly. $$($1_DEBUGINFO_ZIP): $$($1_DEBUGINFO_FILES) $$($1_TARGET) - $(CD) $$($1_OUTPUT_DIR) && \ - $(ZIPEXE) -q -r $$@ $$(subst $$($1_OUTPUT_DIR)/,, $$($1_DEBUGINFO_FILES)) + $(CD) $$($1_SYMBOLS_DIR) && \ + $(ZIPEXE) -q -r $$@ $$(subst $$($1_SYMBOLS_DIR)/,, $$($1_DEBUGINFO_FILES)) endif endif # !STATIC_LIBRARY @@ -971,6 +971,7 @@ $$($1_TARGET): $$($1_TARGET_DEPS) $$(call LogInfo, Building static library $$($1_BASENAME)) + $$(call MakeDir, $$($1_OUTPUT_DIR) $$($1_SYMBOLS_DIR)) $$(call ExecuteWithLog, $$($1_OBJECT_DIR)/$$($1_SAFE_NAME)_link, \ $$($1_AR) $$($1_ARFLAGS) $(AR_OUT_OPTION)$$($1_TARGET) $$($1_ALL_OBJS) \ $$($1_RES)) @@ -1072,7 +1073,9 @@ # Keep as much as possible on one execution line for best performance # on Windows $$(call LogInfo, Linking $$($1_BASENAME)) + $$(call MakeDir, $$($1_OUTPUT_DIR) $$($1_SYMBOLS_DIR)) ifeq ($(call isTargetOs, windows), true) + $$(call ExecuteWithLog, $$($1_OBJECT_DIR)/$$($1_SAFE_NAME)_link, \ $$($1_LD) $$($1_LDFLAGS) $$($1_EXTRA_LDFLAGS) $$($1_SYSROOT_LDFLAGS) \ $(LD_OUT_OPTION)$$($1_TARGET) $$($1_LD_OBJ_ARG) $$($1_RES) $$(GLOBAL_LIBS) \ diff --git a/make/launcher/Launcher-jdk.jpackage.gmk b/make/launcher/Launcher-jdk.jpackage.gmk new file mode 100644 --- /dev/null +++ b/make/launcher/Launcher-jdk.jpackage.gmk @@ -0,0 +1,78 @@ +# +# 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.jpackage.main.Main, \ +)) + +################################################################################ + +JPACKAGE_APPLAUNCHEREXE_SRC := \ + $(TOPDIR)/src/jdk.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/jpackage/internal/resources, \ + SYMBOLS_DIR := $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/jpackageapplauncher, \ + SRC := $(JPACKAGE_APPLAUNCHEREXE_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) + +# Build non-console version of launcher +ifeq ($(OPENJDK_TARGET_OS), windows) + + $(eval $(call SetupJdkExecutable, BUILD_JPACKAGE_APPLAUNCHERWEXE, \ + NAME := jpackageapplauncherw, \ + OUTPUT_DIR := $(JDK_OUTPUTDIR)/modules/$(MODULE)/jdk/jpackage/internal/resources, \ + SYMBOLS_DIR := $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/jpackageapplauncherw, \ + SRC := $(JPACKAGE_APPLAUNCHEREXE_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 + diff --git a/make/lib/Lib-jdk.jpackage.gmk b/make/lib/Lib-jdk.jpackage.gmk new file mode 100644 --- /dev/null +++ b/make/lib/Lib-jdk.jpackage.gmk @@ -0,0 +1,87 @@ +# +# Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please 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/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) + +################################################################################ + +ifeq ($(OPENJDK_TARGET_OS), windows) + + $(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) + +endif + +# Build Wix custom action helper +# Output library in resources dir, and symbols in the object dir +ifeq ($(OPENJDK_TARGET_OS), windows) + + $(eval $(call SetupJdkLibrary, BUILD_LIB_WIXHELPER, \ + NAME := wixhelper, \ + OUTPUT_DIR := $(JDK_OUTPUTDIR)/modules/$(MODULE)/jdk/jpackage/internal/resources, \ + SYMBOLS_DIR := $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/libwixhelper, \ + OPTIMIZATION := LOW, \ + CFLAGS := $(CXXFLAGS_JDKLIB), \ + CFLAGS_windows := -EHsc -DUNICODE -D_UNICODE, \ + LDFLAGS := $(LDFLAGS_JDKLIB) $(LDFLAGS_CXX_JDK), \ + LIBS := $(LIBCXX), \ + LIBS_windows := msi.lib Shlwapi.lib User32.lib, \ + )) + + TARGETS += $(BUILD_LIB_WIXHELPER) +endif diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/packager/AppRuntimeImageBuilder.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/packager/AppRuntimeImageBuilder.java deleted file mode 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/packager/AppRuntimeImageBuilder.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.tools.jlink.internal.packager; - - -import jdk.tools.jlink.builder.DefaultImageBuilder; -import jdk.tools.jlink.internal.Jlink; -import jdk.tools.jlink.internal.JlinkTask; -import jdk.tools.jlink.plugin.Plugin; - -import java.io.File; -import java.io.IOException; -import java.lang.module.ModuleFinder; -import java.nio.ByteOrder; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * AppRuntimeImageBuilder is a private API used only by the Java Packager to generate - * a Java runtime image using jlink. AppRuntimeImageBuilder encapsulates the - * arguments that jlink requires to generate this image. To create the image call the - * build() method. - */ -public final class AppRuntimeImageBuilder { - private Path outputDir = null; - private Map launchers = Collections.emptyMap(); - private List modulePath = null; - private Set addModules = null; - private Set limitModules = null; - private String excludeFileList = null; - private Map userArguments = null; - private Boolean stripNativeCommands = null; - - public AppRuntimeImageBuilder() {} - - public void setOutputDir(Path value) { - outputDir = value; - } - - public void setLaunchers(Map value) { - launchers = value; - } - - public void setModulePath(List value) { - modulePath = value; - } - - public void setAddModules(Set value) { - addModules = value; - } - - public void setLimitModules(Set value) { - limitModules = value; - } - - public void setExcludeFileList(String value) { - excludeFileList = value; - } - - public void setStripNativeCommands(boolean value) { - stripNativeCommands = value; - } - - public void setUserArguments(Map value) { - userArguments = value; - } - - public void build() throws IOException { - // jlink main arguments - Jlink.JlinkConfiguration jlinkConfig = - new Jlink.JlinkConfiguration(new File("").toPath(), // Unused - addModules, - ByteOrder.nativeOrder(), - moduleFinder(modulePath, - limitModules, addModules)); - - // plugin configuration - List plugins = new ArrayList(); - - if (stripNativeCommands) { - plugins.add(Jlink.newPlugin( - "strip-native-commands", - Collections.singletonMap("strip-native-commands", "on"), - null)); - } - - if (excludeFileList != null && !excludeFileList.isEmpty()) { - plugins.add(Jlink.newPlugin( - "exclude-files", - Collections.singletonMap("exclude-files", excludeFileList), - null)); - } - - // add user supplied jlink arguments - for (Map.Entry entry : userArguments.entrySet()) { - String key = entry.getKey(); - String value = entry.getValue(); - plugins.add(Jlink.newPlugin(key, - Collections.singletonMap(key, value), - null)); - } - - // build the image - Jlink.PluginsConfiguration pluginConfig = new Jlink.PluginsConfiguration( - plugins, new DefaultImageBuilder(outputDir, launchers), null); - Jlink jlink = new Jlink(); - jlink.build(jlinkConfig, pluginConfig); - } - - /* - * Returns a ModuleFinder that limits observability to the given root - * modules, their transitive dependences, plus a set of other modules. - */ - public static ModuleFinder moduleFinder(List modulepaths, - Set roots, - Set otherModules) { - return JlinkTask.newModuleFinder(modulepaths, roots, otherModules); - } -} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppBundler.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppBundler.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppBundler.java @@ -0,0 +1,205 @@ +/* + * 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.jpackage.internal; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.ResourceBundle; + +import static jdk.jpackage.internal.StandardBundlerParam.*; + +public class LinuxAppBundler extends AbstractImageBundler { + + private static final ResourceBundle I18N = ResourceBundle.getBundle( + "jdk.jpackage.internal.resources.LinuxResources"); + + public static final BundlerParamInfo 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)); + + public static final BundlerParamInfo 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 + ); + + public static final BundlerParamInfo LINUX_PACKAGE_DEPENDENCIES = + new StandardBundlerParam<>( + Arguments.CLIOptions.LINUX_PACKAGE_DEPENDENCIES.getId(), + String.class, + params -> { + return ""; + }, + (s, p) -> s + ); + + @Override + public boolean validate(Map p) + throws UnsupportedPlatformException, ConfigException { + try { + if (p == null) throw new ConfigException( + I18N.getString("error.parameters-null"), + I18N.getString("error.parameters-null.advice")); + + return doValidate(p); + } catch (RuntimeException re) { + if (re.getCause() instanceof ConfigException) { + throw (ConfigException) re.getCause(); + } else { + throw new ConfigException(re); + } + } + } + + private boolean doValidate(Map p) + throws UnsupportedPlatformException, ConfigException { + if (Platform.getPlatform() != Platform.LINUX) { + throw new UnsupportedPlatformException(); + } + + imageBundleValidation(p); + + return true; + } + + // it is static for the sake of sharing with "installer" bundlers + // that may skip calls to validate/bundle in this class! + public static File getRootDir(File outDir, Map p) { + return new File(outDir, APP_NAME.fetchFrom(p)); + } + + public static String getLauncherCfgName(Map p) { + return "app/" + APP_NAME.fetchFrom(p) +".cfg"; + } + + File doBundle(Map p, File outputDirectory, + boolean dependentTask) throws PackagerException { + if (StandardBundlerParam.isRuntimeInstaller(p)) { + return PREDEFINED_RUNTIME_IMAGE.fetchFrom(p); + } else { + return doAppBundle(p, outputDirectory, dependentTask); + } + } + + private File doAppBundle(Map p, + File outputDirectory, boolean dependentTask) throws PackagerException { + try { + File rootDirectory = createRoot(p, outputDirectory, dependentTask, + APP_NAME.fetchFrom(p)); + AbstractAppImageBuilder appBuilder = new LinuxAppImageBuilder(p, + outputDirectory.toPath()); + if (PREDEFINED_RUNTIME_IMAGE.fetchFrom(p) == null ) { + JLinkBundlerHelper.execute(p, appBuilder); + } else { + StandardBundlerParam.copyPredefinedRuntimeImage(p, 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 getDescription() { + return I18N.getString("app.bundler.description"); + } + + @Override + public String getID() { + return "linux.app"; + } + + @Override + public String getBundleType() { + return "IMAGE"; + } + + @Override + public Collection> getBundleParameters() { + return getAppBundleParameters(); + } + + public static Collection> getAppBundleParameters() { + return Arrays.asList( + APP_NAME, + APP_RESOURCES, + ARGUMENTS, + CLASSPATH, + JAVA_OPTIONS, + MAIN_CLASS, + MAIN_JAR, + VERSION, + VERBOSE + ); + } + + @Override + public File execute(Map params, + File outputParentDir) throws PackagerException { + return doBundle(params, outputParentDir, false); + } + + @Override + public boolean supported(boolean runtimeInstaller) { + return (Platform.getPlatform() == Platform.LINUX); + } +} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppImageBuilder.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppImageBuilder.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppImageBuilder.java @@ -0,0 +1,248 @@ +/* + * 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.jpackage.internal; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.UncheckedIOException; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.PosixFilePermission; +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.ResourceBundle; +import java.util.Set; + +import static jdk.jpackage.internal.StandardBundlerParam.*; + +public class LinuxAppImageBuilder extends AbstractAppImageBuilder { + + private static final ResourceBundle I18N = ResourceBundle.getBundle( + "jdk.jpackage.internal.resources.LinuxResources"); + + private static final String LIBRARY_NAME = "libapplauncher.so"; + + private final Path root; + private final Path appDir; + private final Path appModsDir; + private final Path runtimeDir; + private final Path resourcesDir; + private final Path mdir; + + private final Map params; + + public static final BundlerParamInfo 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)); + + public LinuxAppImageBuilder(Map config, Path imageOutDir) + throws IOException { + super(config, + imageOutDir.resolve(APP_NAME.fetchFrom(config) + "/runtime")); + + Objects.requireNonNull(imageOutDir); + + this.root = imageOutDir.resolve(APP_NAME.fetchFrom(config)); + this.appDir = root.resolve("app"); + this.appModsDir = appDir.resolve("mods"); + this.runtimeDir = root.resolve("runtime"); + this.resourcesDir = root.resolve("resources"); + this.mdir = runtimeDir.resolve("lib"); + this.params = new HashMap<>(); + config.entrySet().stream().forEach(e -> params.put( + e.getKey().toString(), e.getValue())); + Files.createDirectories(appDir); + Files.createDirectories(runtimeDir); + Files.createDirectories(resourcesDir); + } + + public LinuxAppImageBuilder(String appName, Path imageOutDir) + throws IOException { + super(null, imageOutDir.resolve(appName)); + + Objects.requireNonNull(imageOutDir); + + this.root = imageOutDir.resolve(appName); + this.appDir = null; + this.appModsDir = null; + this.runtimeDir = null; + this.resourcesDir = null; + this.mdir = null; + this.params = new HashMap<>(); + } + + private Path destFile(String dir, String filename) { + return runtimeDir.resolve(dir).resolve(filename); + } + + private void writeEntry(InputStream in, Path dstFile) throws IOException { + Files.createDirectories(dstFile.getParent()); + Files.copy(in, dstFile); + } + + private void writeSymEntry(Path dstFile, Path target) throws IOException { + Files.createDirectories(dstFile.getParent()); + Files.createLink(dstFile, target); + } + + /** + * chmod ugo+x file + */ + private void setExecutable(Path file) { + try { + Set perms = + Files.getPosixFilePermissions(file); + perms.add(PosixFilePermission.OWNER_EXECUTE); + perms.add(PosixFilePermission.GROUP_EXECUTE); + perms.add(PosixFilePermission.OTHERS_EXECUTE); + Files.setPosixFilePermissions(file, perms); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + } + + private static void createUtf8File(File file, String content) + throws IOException { + try (OutputStream fout = new FileOutputStream(file); + Writer output = new OutputStreamWriter(fout, "UTF-8")) { + output.write(content); + } + } + + + // it is static for the sake of sharing with "installer" bundlers + // that may skip calls to validate/bundle in this class! + public static File getRootDir(File outDir, Map p) { + return new File(outDir, APP_NAME.fetchFrom(p)); + } + + public static String getLauncherName(Map p) { + return APP_NAME.fetchFrom(p); + } + + public static String getLauncherCfgName(Map p) { + return "app/" + APP_NAME.fetchFrom(p) + ".cfg"; + } + + @Override + public Path getAppDir() { + return appDir; + } + + @Override + public Path getAppModsDir() { + return appModsDir; + } + + @Override + public void prepareApplicationFiles() throws IOException { + Map originalParams = new HashMap<>(params); + + // create the primary launcher + createLauncherForEntryPoint(params); + + // Copy library to the launcher folder + try (InputStream is_lib = getResourceAsStream(LIBRARY_NAME)) { + writeEntry(is_lib, root.resolve(LIBRARY_NAME)); + } + + // create the additional launchers, if any + List> entryPoints + = StandardBundlerParam.ADD_LAUNCHERS.fetchFrom(params); + for (Map entryPoint : entryPoints) { + createLauncherForEntryPoint( + AddLauncherArguments.merge(originalParams, entryPoint)); + } + + // Copy class path entries to Java folder + copyApplication(); + + // Copy icon to Resources folder + copyIcon(); + } + + @Override + public void prepareJreFiles() throws IOException {} + + private void createLauncherForEntryPoint(Map p) + throws IOException { + // Copy executable to Linux folder + Path executableFile = root.resolve(getLauncherName(p)); + try (InputStream is_launcher = + getResourceAsStream("jpackageapplauncher")) { + writeEntry(is_launcher, executableFile); + } + + executableFile.toFile().setExecutable(true, false); + executableFile.toFile().setWritable(true, true); + + writeCfgFile(p, root.resolve(getLauncherCfgName(p)).toFile(), + "$APPDIR/runtime"); + } + + private void copyIcon() throws IOException { + File icon = ICON_PNG.fetchFrom(params); + if (icon != null) { + File iconTarget = new File(resourcesDir.toFile(), + APP_NAME.fetchFrom(params) + ".png"); + IOUtils.copyFile(icon, iconTarget); + } + } + + private void copyApplication() 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(appDir, srcdir, fname); + } + } + } + +} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java @@ -0,0 +1,880 @@ +/* + * 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.jpackage.internal; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.*; +import java.nio.file.Files; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; +import java.text.MessageFormat; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Pattern; + +import static jdk.jpackage.internal.StandardBundlerParam.*; +import static jdk.jpackage.internal.LinuxAppBundler.ICON_PNG; +import static jdk.jpackage.internal.LinuxAppBundler.LINUX_INSTALL_DIR; +import static jdk.jpackage.internal.LinuxAppBundler.LINUX_PACKAGE_DEPENDENCIES; + +public class LinuxDebBundler extends AbstractBundler { + + private static final ResourceBundle I18N = ResourceBundle.getBundle( + "jdk.jpackage.internal.resources.LinuxResources"); + + public static final BundlerParamInfo APP_BUNDLER = + new StandardBundlerParam<>( + "linux.app.bundler", + LinuxAppBundler.class, + params -> new LinuxAppBundler(), + (s, p) -> null); + + // 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_BUNDLE_NAME_PATTERN = + Pattern.compile("^[a-z][a-z\\d\\+\\-\\.]+"); + + public static final BundlerParamInfo BUNDLE_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_BUNDLE_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; + }); + + public static final BundlerParamInfo FULL_PACKAGE_NAME = + new StandardBundlerParam<> ( + "linux.deb.fullPackageName", + String.class, + params -> BUNDLE_NAME.fetchFrom(params) + "-" + + VERSION.fetchFrom(params), + (s, p) -> s); + + public static final BundlerParamInfo DEB_IMAGE_DIR = + new StandardBundlerParam<>( + "linux.deb.imageDir", + File.class, + params -> { + File imagesRoot = IMAGES_ROOT.fetchFrom(params); + if (!imagesRoot.exists()) imagesRoot.mkdirs(); + return new File(new File(imagesRoot, "linux-deb.image"), + FULL_PACKAGE_NAME.fetchFrom(params)); + }, + (s, p) -> new File(s)); + + public static final BundlerParamInfo APP_IMAGE_ROOT = + new StandardBundlerParam<>( + "linux.deb.imageRoot", + File.class, + params -> { + File imageDir = DEB_IMAGE_DIR.fetchFrom(params); + return new File(imageDir, LINUX_INSTALL_DIR.fetchFrom(params)); + }, + (s, p) -> new File(s)); + + public static final BundlerParamInfo CONFIG_DIR = + new StandardBundlerParam<>( + "linux.deb.configDir", + File.class, + params -> new File(DEB_IMAGE_DIR.fetchFrom(params), "DEBIAN"), + (s, p) -> new File(s)); + + public static final BundlerParamInfo EMAIL = + new StandardBundlerParam<> ( + Arguments.CLIOptions.LINUX_DEB_MAINTAINER.getId(), + String.class, + params -> "Unknown", + (s, p) -> s); + + public static final BundlerParamInfo MAINTAINER = + new StandardBundlerParam<> ( + BundleParams.PARAM_MAINTAINER, + String.class, + params -> VENDOR.fetchFrom(params) + " <" + + EMAIL.fetchFrom(params) + ">", + (s, p) -> s); + + public static final BundlerParamInfo LICENSE_TEXT = + new StandardBundlerParam<> ( + "linux.deb.licenseText", + String.class, + params -> { + try { + String licenseFile = LICENSE_FILE.fetchFrom(params); + if (licenseFile != null) { + return Files.readString(new File(licenseFile).toPath()); + } + } catch (Exception e) { + Log.verbose(e); + } + return "Unknown"; + }, + (s, p) -> s); + + public static final BundlerParamInfo XDG_FILE_PREFIX = + new StandardBundlerParam<> ( + "linux.xdg-prefix", + String.class, + params -> { + try { + String vendor; + if (params.containsKey(VENDOR.getID())) { + vendor = VENDOR.fetchFrom(params); + } else { + vendor = "jpackage"; + } + String appName = APP_NAME.fetchFrom(params); + + return (appName + "-" + vendor).replaceAll("\\s", ""); + } catch (Exception e) { + Log.verbose(e); + } + return "unknown-MimeInfo.xml"; + }, + (s, p) -> s); + + public static final BundlerParamInfo MENU_GROUP = + new StandardBundlerParam<>( + Arguments.CLIOptions.LINUX_MENU_GROUP.getId(), + String.class, + params -> I18N.getString("param.menu-group.default"), + (s, p) -> s + ); + + private final static String DEFAULT_ICON = "javalogo_white_32.png"; + private final static String DEFAULT_CONTROL_TEMPLATE = "template.control"; + private final static String DEFAULT_PRERM_TEMPLATE = "template.prerm"; + private final static String DEFAULT_PREINSTALL_TEMPLATE = + "template.preinst"; + private final static String DEFAULT_POSTRM_TEMPLATE = "template.postrm"; + private final static String DEFAULT_POSTINSTALL_TEMPLATE = + "template.postinst"; + private final static String DEFAULT_COPYRIGHT_TEMPLATE = + "template.copyright"; + private final static String DEFAULT_DESKTOP_FILE_TEMPLATE = + "template.desktop"; + + public final static String TOOL_DPKG = "dpkg-deb"; + + public static boolean testTool(String toolName, String minVersion) { + try { + ProcessBuilder pb = new ProcessBuilder( + toolName, + "--version"); + // not interested in the output + IOUtils.exec(pb, Log.isDebug(), true); + } catch (Exception e) { + Log.verbose(MessageFormat.format(I18N.getString( + "message.test-for-tool"), toolName, e.getMessage())); + return false; + } + return true; + } + + @Override + public boolean validate(Map p) + throws UnsupportedPlatformException, ConfigException { + try { + if (p == null) throw new ConfigException( + I18N.getString("error.parameters-null"), + I18N.getString("error.parameters-null.advice")); + + //run basic validation to ensure requirements are met + //we are not interested in return code, only possible exception + APP_BUNDLER.fetchFrom(p).validate(p); + + // NOTE: Can we validate that the required tools are available + // before we start? + if (!testTool(TOOL_DPKG, "1")){ + throw new ConfigException(MessageFormat.format( + I18N.getString("error.tool-not-found"), TOOL_DPKG), + I18N.getString("error.tool-not-found.advice")); + } + + + // Show warning is license file is missing + String licenseFile = LICENSE_FILE.fetchFrom(p); + if (licenseFile == null) { + Log.verbose(I18N.getString("message.debs-like-licenses")); + } + + // only one mime type per association, at least one file extention + List> associations = + FILE_ASSOCIATIONS.fetchFrom(p); + if (associations != null) { + for (int i = 0; i < associations.size(); i++) { + Map assoc = associations.get(i); + List 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), i), + I18N.getString(msgKey + ".advise")); + + } else if (mimes.size() > 1) { + String msgKey = + "error.too-many-content-types-for-file-association"; + throw new ConfigException( + MessageFormat.format(I18N.getString(msgKey), i), + I18N.getString(msgKey + ".advise")); + } + } + } + + // bundle name has some restrictions + // the string converter will throw an exception if invalid + BUNDLE_NAME.getStringConverter().apply(BUNDLE_NAME.fetchFrom(p), p); + + return true; + } catch (RuntimeException re) { + if (re.getCause() instanceof ConfigException) { + throw (ConfigException) re.getCause(); + } else { + throw new ConfigException(re); + } + } + } + + private boolean prepareProto(Map p) + throws PackagerException, IOException { + File appImage = StandardBundlerParam.getPredefinedAppImage(p); + File appDir = null; + + // we either have an application image or need to build one + if (appImage != null) { + appDir = new File(APP_IMAGE_ROOT.fetchFrom(p), + APP_NAME.fetchFrom(p)); + // copy everything from appImage dir into appDir/name + IOUtils.copyRecursive(appImage.toPath(), appDir.toPath()); + } else { + appDir = APP_BUNDLER.fetchFrom(p).doBundle(p, + APP_IMAGE_ROOT.fetchFrom(p), true); + } + return appDir != null; + } + + //@Override + public File bundle(Map p, + File outdir) throws PackagerException { + if (!outdir.isDirectory() && !outdir.mkdirs()) { + throw new PackagerException ("error.cannot-create-output-dir", + outdir.getAbsolutePath()); + } + if (!outdir.canWrite()) { + throw new PackagerException("error.cannot-write-to-output-dir", + outdir.getAbsolutePath()); + } + + // we want to create following structure + // + // DEBIAN + // control (file with main package details) + // menu (request to create menu) + // ... other control files if needed .... + // opt (by default) + // AppFolder (this is where app image goes) + // launcher executable + // app + // runtime + + File imageDir = DEB_IMAGE_DIR.fetchFrom(p); + File configDir = CONFIG_DIR.fetchFrom(p); + + try { + + imageDir.mkdirs(); + configDir.mkdirs(); + if (prepareProto(p) && prepareProjectConfig(p)) { + return buildDeb(p, outdir); + } + return null; + } catch (IOException ex) { + Log.verbose(ex); + throw new PackagerException(ex); + } + } + + /* + * 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 filePermissions = + PosixFilePermissions.fromString(permissions); + try { + if (file.exists()) { + Files.setPosixFilePermissions(file.toPath(), filePermissions); + } + } catch (IOException ex) { + Logger.getLogger(LinuxDebBundler.class.getName()).log( + Level.SEVERE, null, ex); + } + + } + + private String getArch() { + String arch = System.getProperty("os.arch"); + if ("i386".equals(arch)) + return "i386"; + else + return "amd64"; + } + + private long getInstalledSizeKB(Map params) { + return getInstalledSizeKB(APP_IMAGE_ROOT.fetchFrom(params)) >> 10; + } + + private long getInstalledSizeKB(File dir) { + long count = 0; + File[] children = dir.listFiles(); + if (children != null) { + for (File file : children) { + if (file.isFile()) { + count += file.length(); + } + else if (file.isDirectory()) { + count += getInstalledSizeKB(file); + } + } + } + return count; + } + + private boolean prepareProjectConfig(Map params) + throws IOException { + Map data = createReplacementData(params); + File rootDir = LinuxAppBundler.getRootDir(APP_IMAGE_ROOT.fetchFrom( + params), params); + + File iconTarget = getConfig_IconFile(rootDir, params); + File icon = ICON_PNG.fetchFrom(params); + if (!StandardBundlerParam.isRuntimeInstaller(params)) { + // prepare installer icon + if (icon == null || !icon.exists()) { + fetchResource(iconTarget.getName(), + I18N.getString("resource.menu-icon"), + DEFAULT_ICON, + iconTarget, + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + } else { + fetchResource(iconTarget.getName(), + I18N.getString("resource.menu-icon"), + icon, + iconTarget, + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + } + } + + StringBuilder installScripts = new StringBuilder(); + StringBuilder removeScripts = new StringBuilder(); + for (Map addLauncher : + ADD_LAUNCHERS.fetchFrom(params)) { + Map addLauncherData = + createReplacementData(addLauncher); + addLauncherData.put("APPLICATION_FS_NAME", + data.get("APPLICATION_FS_NAME")); + addLauncherData.put("DESKTOP_MIMES", ""); + + if (!StandardBundlerParam.isRuntimeInstaller(params)) { + // prepare desktop shortcut + Writer w = new BufferedWriter(new FileWriter( + getConfig_DesktopShortcutFile( + rootDir, addLauncher))); + String content = preprocessTextResource( + getConfig_DesktopShortcutFile(rootDir, + addLauncher).getName(), + I18N.getString("resource.menu-shortcut-descriptor"), + DEFAULT_DESKTOP_FILE_TEMPLATE, + addLauncherData, + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + w.write(content); + w.close(); + } + + // prepare installer icon + iconTarget = getConfig_IconFile(rootDir, addLauncher); + icon = ICON_PNG.fetchFrom(addLauncher); + if (icon == null || !icon.exists()) { + fetchResource(iconTarget.getName(), + I18N.getString("resource.menu-icon"), + DEFAULT_ICON, + iconTarget, + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + } else { + fetchResource(iconTarget.getName(), + I18N.getString("resource.menu-icon"), + icon, + iconTarget, + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + } + + // postinst copying of desktop icon + installScripts.append( + " xdg-desktop-menu install --novendor "); + installScripts.append(LINUX_INSTALL_DIR.fetchFrom(params)); + installScripts.append("/"); + installScripts.append(data.get("APPLICATION_FS_NAME")); + installScripts.append("/"); + installScripts.append( + addLauncherData.get("APPLICATION_LAUNCHER_FILENAME")); + installScripts.append(".desktop\n"); + + // postrm cleanup of desktop icon + removeScripts.append( + " xdg-desktop-menu uninstall --novendor "); + removeScripts.append(LINUX_INSTALL_DIR.fetchFrom(params)); + removeScripts.append("/"); + removeScripts.append(data.get("APPLICATION_FS_NAME")); + removeScripts.append("/"); + removeScripts.append( + addLauncherData.get("APPLICATION_LAUNCHER_FILENAME")); + removeScripts.append(".desktop\n"); + } + data.put("ADD_LAUNCHERS_INSTALL", installScripts.toString()); + data.put("ADD_LAUNCHERS_REMOVE", removeScripts.toString()); + + List> associations = + FILE_ASSOCIATIONS.fetchFrom(params); + data.put("FILE_ASSOCIATION_INSTALL", ""); + data.put("FILE_ASSOCIATION_REMOVE", ""); + data.put("DESKTOP_MIMES", ""); + if (associations != null) { + String mimeInfoFile = XDG_FILE_PREFIX.fetchFrom(params) + + "-MimeInfo.xml"; + StringBuilder mimeInfo = new StringBuilder( + "\n\n"); + StringBuilder registrations = new StringBuilder(); + StringBuilder deregistrations = new StringBuilder(); + StringBuilder desktopMimes = new StringBuilder("MimeType="); + boolean addedEntry = false; + + for (Map assoc : associations) { + // + // Awesome document + // + // + // + + if (assoc == null) { + continue; + } + + String description = FA_DESCRIPTION.fetchFrom(assoc); + File faIcon = FA_ICON.fetchFrom(assoc); + List extensions = FA_EXTENSIONS.fetchFrom(assoc); + if (extensions == null) { + Log.error(I18N.getString( + "message.creating-association-with-null-extension")); + } + + List mimes = FA_CONTENT_TYPE.fetchFrom(assoc); + if (mimes == null || mimes.isEmpty()) { + continue; + } + String thisMime = mimes.get(0); + String dashMime = thisMime.replace('/', '-'); + + mimeInfo.append(" \n"); + if (description != null && !description.isEmpty()) { + mimeInfo.append(" ") + .append(description) + .append("\n"); + } + + if (extensions != null) { + for (String ext : extensions) { + mimeInfo.append(" \n"); + } + } + + mimeInfo.append(" \n"); + if (!addedEntry) { + registrations.append(" xdg-mime install ") + .append(LINUX_INSTALL_DIR.fetchFrom(params)) + .append("/") + .append(data.get("APPLICATION_FS_NAME")) + .append("/") + .append(mimeInfoFile) + .append("\n"); + + deregistrations.append(" xdg-mime uninstall ") + .append(LINUX_INSTALL_DIR.fetchFrom(params)) + .append("/") + .append(data.get("APPLICATION_FS_NAME")) + .append("/") + .append(mimeInfoFile) + .append("\n"); + addedEntry = true; + } else { + desktopMimes.append(";"); + } + desktopMimes.append(thisMime); + + if (faIcon != null && faIcon.exists()) { + int size = getSquareSizeOfImage(faIcon); + + if (size > 0) { + File target = new File(rootDir, + APP_NAME.fetchFrom(params) + + "_fa_" + faIcon.getName()); + IOUtils.copyFile(faIcon, target); + + // xdg-icon-resource install --context mimetypes + // --size 64 awesomeapp_fa_1.png + // application-x.vnd-awesome + registrations.append( + " xdg-icon-resource install " + + "--context mimetypes --size ") + .append(size) + .append(" ") + .append(LINUX_INSTALL_DIR.fetchFrom(params)) + .append("/") + .append(data.get("APPLICATION_FS_NAME")) + .append("/") + .append(target.getName()) + .append(" ") + .append(dashMime) + .append("\n"); + + // x dg-icon-resource uninstall --context mimetypes + // --size 64 awesomeapp_fa_1.png + // application-x.vnd-awesome + deregistrations.append( + " xdg-icon-resource uninstall " + + "--context mimetypes --size ") + .append(size) + .append(" ") + .append(LINUX_INSTALL_DIR.fetchFrom(params)) + .append("/") + .append(data.get("APPLICATION_FS_NAME")) + .append("/") + .append(target.getName()) + .append(" ") + .append(dashMime) + .append("\n"); + } + } + } + mimeInfo.append(""); + + if (addedEntry) { + Writer w = new BufferedWriter(new FileWriter( + new File(rootDir, mimeInfoFile))); + w.write(mimeInfo.toString()); + w.close(); + data.put("FILE_ASSOCIATION_INSTALL", registrations.toString()); + data.put("FILE_ASSOCIATION_REMOVE", deregistrations.toString()); + data.put("DESKTOP_MIMES", desktopMimes.toString()); + } + } + + if (!StandardBundlerParam.isRuntimeInstaller(params)) { + //prepare desktop shortcut + Writer w = new BufferedWriter(new FileWriter( + getConfig_DesktopShortcutFile(rootDir, params))); + String content = preprocessTextResource( + getConfig_DesktopShortcutFile( + rootDir, params).getName(), + I18N.getString("resource.menu-shortcut-descriptor"), + DEFAULT_DESKTOP_FILE_TEMPLATE, + data, + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + w.write(content); + w.close(); + } + // prepare control file + Writer w = new BufferedWriter(new FileWriter( + getConfig_ControlFile(params))); + String content = preprocessTextResource( + getConfig_ControlFile(params).getName(), + I18N.getString("resource.deb-control-file"), + DEFAULT_CONTROL_TEMPLATE, + data, + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + w.write(content); + w.close(); + + w = new BufferedWriter(new FileWriter( + getConfig_PreinstallFile(params))); + content = preprocessTextResource( + getConfig_PreinstallFile(params).getName(), + I18N.getString("resource.deb-preinstall-script"), + DEFAULT_PREINSTALL_TEMPLATE, + data, + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + w.write(content); + w.close(); + setPermissions(getConfig_PreinstallFile(params), "rwxr-xr-x"); + + w = new BufferedWriter(new FileWriter(getConfig_PrermFile(params))); + content = preprocessTextResource( + getConfig_PrermFile(params).getName(), + I18N.getString("resource.deb-prerm-script"), + DEFAULT_PRERM_TEMPLATE, + data, + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + w.write(content); + w.close(); + setPermissions(getConfig_PrermFile(params), "rwxr-xr-x"); + + w = new BufferedWriter(new FileWriter( + getConfig_PostinstallFile(params))); + content = preprocessTextResource( + getConfig_PostinstallFile(params).getName(), + I18N.getString("resource.deb-postinstall-script"), + DEFAULT_POSTINSTALL_TEMPLATE, + data, + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + w.write(content); + w.close(); + setPermissions(getConfig_PostinstallFile(params), "rwxr-xr-x"); + + w = new BufferedWriter(new FileWriter(getConfig_PostrmFile(params))); + content = preprocessTextResource( + getConfig_PostrmFile(params).getName(), + I18N.getString("resource.deb-postrm-script"), + DEFAULT_POSTRM_TEMPLATE, + data, + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + w.write(content); + w.close(); + setPermissions(getConfig_PostrmFile(params), "rwxr-xr-x"); + + w = new BufferedWriter(new FileWriter(getConfig_CopyrightFile(params))); + content = preprocessTextResource( + getConfig_CopyrightFile(params).getName(), + I18N.getString("resource.deb-copyright-file"), + DEFAULT_COPYRIGHT_TEMPLATE, + data, + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + w.write(content); + w.close(); + + return true; + } + + private Map createReplacementData( + Map params) { + Map data = new HashMap<>(); + + data.put("APPLICATION_NAME", APP_NAME.fetchFrom(params)); + data.put("APPLICATION_FS_NAME", APP_NAME.fetchFrom(params)); + data.put("APPLICATION_PACKAGE", BUNDLE_NAME.fetchFrom(params)); + data.put("APPLICATION_VENDOR", VENDOR.fetchFrom(params)); + data.put("APPLICATION_MAINTAINER", MAINTAINER.fetchFrom(params)); + data.put("APPLICATION_VERSION", VERSION.fetchFrom(params)); + data.put("APPLICATION_LAUNCHER_FILENAME", APP_NAME.fetchFrom(params)); + data.put("INSTALLATION_DIRECTORY", LINUX_INSTALL_DIR.fetchFrom(params)); + data.put("XDG_PREFIX", XDG_FILE_PREFIX.fetchFrom(params)); + data.put("DEPLOY_BUNDLE_CATEGORY", MENU_GROUP.fetchFrom(params)); + data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params)); + data.put("APPLICATION_COPYRIGHT", COPYRIGHT.fetchFrom(params)); + data.put("APPLICATION_LICENSE_TEXT", LICENSE_TEXT.fetchFrom(params)); + data.put("APPLICATION_ARCH", getArch()); + data.put("APPLICATION_INSTALLED_SIZE", + Long.toString(getInstalledSizeKB(params))); + String deps = LINUX_PACKAGE_DEPENDENCIES.fetchFrom(params); + data.put("PACKAGE_DEPENDENCIES", + deps.isEmpty() ? "" : "Depends: " + deps); + data.put("RUNTIME_INSTALLER", "" + + StandardBundlerParam.isRuntimeInstaller(params)); + + return data; + } + + private File getConfig_DesktopShortcutFile(File rootDir, + Map params) { + return new File(rootDir, APP_NAME.fetchFrom(params) + ".desktop"); + } + + private File getConfig_IconFile(File rootDir, + Map params) { + return new File(rootDir, APP_NAME.fetchFrom(params) + ".png"); + } + + private File getConfig_InitScriptFile(Map params) { + return new File(LinuxAppBundler.getRootDir( + APP_IMAGE_ROOT.fetchFrom(params), params), + BUNDLE_NAME.fetchFrom(params) + ".init"); + } + + private File getConfig_ControlFile(Map params) { + return new File(CONFIG_DIR.fetchFrom(params), "control"); + } + + private File getConfig_PreinstallFile(Map params) { + return new File(CONFIG_DIR.fetchFrom(params), "preinst"); + } + + private File getConfig_PrermFile(Map params) { + return new File(CONFIG_DIR.fetchFrom(params), "prerm"); + } + + private File getConfig_PostinstallFile(Map params) { + return new File(CONFIG_DIR.fetchFrom(params), "postinst"); + } + + private File getConfig_PostrmFile(Map params) { + return new File(CONFIG_DIR.fetchFrom(params), "postrm"); + } + + private File getConfig_CopyrightFile(Map params) { + return new File(CONFIG_DIR.fetchFrom(params), "copyright"); + } + + private File buildDeb(Map 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())); + + outFile.getParentFile().mkdirs(); + + // run dpkg + ProcessBuilder pb = new ProcessBuilder( + "fakeroot", TOOL_DPKG, "-b", + FULL_PACKAGE_NAME.fetchFrom(params), + outFile.getAbsolutePath()); + pb = pb.directory(DEB_IMAGE_DIR.fetchFrom(params).getParentFile()); + IOUtils.exec(pb, false); + + 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 getDescription() { + return I18N.getString("deb.bundler.description"); + } + + @Override + public String getID() { + return "deb"; + } + + @Override + public String getBundleType() { + return "INSTALLER"; + } + + @Override + public Collection> getBundleParameters() { + Collection> results = new LinkedHashSet<>(); + results.addAll(LinuxAppBundler.getAppBundleParameters()); + results.addAll(getDebBundleParameters()); + return results; + } + + public static Collection> getDebBundleParameters() { + return Arrays.asList( + BUNDLE_NAME, + COPYRIGHT, + MENU_GROUP, + DESCRIPTION, + EMAIL, + ICON_PNG, + LICENSE_FILE, + VENDOR + ); + } + + @Override + public File execute(Map params, + File outputParentDir) throws PackagerException { + return bundle(params, outputParentDir); + } + + @Override + public boolean supported(boolean runtimeInstaller) { + return (Platform.getPlatform() == Platform.LINUX); + } + + public int getSquareSizeOfImage(File f) { + try { + BufferedImage bi = ImageIO.read(f); + if (bi.getWidth() == bi.getHeight()) { + return bi.getWidth(); + } else { + return 0; + } + } catch (Exception e) { + Log.verbose(e); + return 0; + } + } +} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmBundler.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmBundler.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmBundler.java @@ -0,0 +1,725 @@ +/* + * 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.jpackage.internal; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.*; +import java.nio.file.Files; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; +import java.text.MessageFormat; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static jdk.jpackage.internal.StandardBundlerParam.*; +import static jdk.jpackage.internal.LinuxAppBundler.LINUX_INSTALL_DIR; +import static jdk.jpackage.internal.LinuxAppBundler.LINUX_PACKAGE_DEPENDENCIES; + +public class LinuxRpmBundler extends AbstractBundler { + + private static final ResourceBundle I18N = ResourceBundle.getBundle( + "jdk.jpackage.internal.resources.LinuxResources"); + + public static final BundlerParamInfo APP_BUNDLER = + new StandardBundlerParam<>( + "linux.app.bundler", + LinuxAppBundler.class, + params -> new LinuxAppBundler(), + null); + + public static final BundlerParamInfo RPM_IMAGE_DIR = + new StandardBundlerParam<>( + "linux.rpm.imageDir", + File.class, + params -> { + File imagesRoot = IMAGES_ROOT.fetchFrom(params); + if (!imagesRoot.exists()) imagesRoot.mkdirs(); + return new File(imagesRoot, "linux-rpm.image"); + }, + (s, p) -> new File(s)); + + // 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_BUNDLE_NAME_PATTERN = + Pattern.compile("[a-z\\d\\+\\-\\.\\_]+", Pattern.CASE_INSENSITIVE); + + public static final BundlerParamInfo BUNDLE_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_BUNDLE_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 MENU_GROUP = + new StandardBundlerParam<>( + Arguments.CLIOptions.LINUX_MENU_GROUP.getId(), + String.class, + params -> I18N.getString("param.menu-group.default"), + (s, p) -> s + ); + + public static final BundlerParamInfo 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 XDG_FILE_PREFIX = + new StandardBundlerParam<> ( + "linux.xdg-prefix", + String.class, + params -> { + try { + String vendor; + if (params.containsKey(VENDOR.getID())) { + vendor = VENDOR.fetchFrom(params); + } else { + vendor = "jpackage"; + } + String appName = APP_NAME.fetchFrom(params); + + return (vendor + "-" + appName).replaceAll("\\s", ""); + } catch (Exception e) { + if (Log.isDebug()) { + e.printStackTrace(); + } + } + return "unknown-MimeInfo.xml"; + }, + (s, p) -> s); + + private final static String DEFAULT_ICON = "javalogo_white_32.png"; + private final static String DEFAULT_SPEC_TEMPLATE = "template.spec"; + private final static String DEFAULT_DESKTOP_FILE_TEMPLATE = + "template.desktop"; + + public final static String TOOL_RPMBUILD = "rpmbuild"; + public final static double TOOL_RPMBUILD_MIN_VERSION = 4.0d; + + public static boolean testTool(String toolName, double minVersion) { + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream ps = new PrintStream(baos)) { + ProcessBuilder pb = new ProcessBuilder(toolName, "--version"); + IOUtils.exec(pb, Log.isDebug(), false, ps); + //not interested in the above's output + String content = new String(baos.toByteArray()); + Pattern pattern = Pattern.compile(" (\\d+\\.\\d+)"); + Matcher matcher = pattern.matcher(content); + + if (matcher.find()) { + String v = matcher.group(1); + double version = Double.parseDouble(v); + return minVersion <= version; + } else { + return false; + } + } catch (Exception e) { + Log.verbose(MessageFormat.format(I18N.getString( + "message.test-for-tool"), toolName, e.getMessage())); + return false; + } + } + + @Override + public boolean validate(Map p) + throws UnsupportedPlatformException, ConfigException { + try { + if (p == null) throw new ConfigException( + I18N.getString("error.parameters-null"), + I18N.getString("error.parameters-null.advice")); + + // run basic validation to ensure requirements are met + // we are not interested in return code, only possible exception + APP_BUNDLER.fetchFrom(p).validate(p); + + // validate presense of required tools + if (!testTool(TOOL_RPMBUILD, TOOL_RPMBUILD_MIN_VERSION)){ + throw new ConfigException( + MessageFormat.format( + I18N.getString("error.cannot-find-rpmbuild"), + TOOL_RPMBUILD_MIN_VERSION), + MessageFormat.format( + I18N.getString("error.cannot-find-rpmbuild.advice"), + TOOL_RPMBUILD_MIN_VERSION)); + } + + // only one mime type per association, at least one file extension + List> associations = + FILE_ASSOCIATIONS.fetchFrom(p); + if (associations != null) { + for (int i = 0; i < associations.size(); i++) { + Map assoc = associations.get(i); + List 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), i), + I18N.getString(msgKey + ".advice")); + } else if (mimes.size() > 1) { + String msgKey = + "error.no-content-types-for-file-association"; + throw new ConfigException( + MessageFormat.format(I18N.getString(msgKey), i), + I18N.getString(msgKey + ".advice")); + } + } + } + + // bundle name has some restrictions + // the string converter will throw an exception if invalid + BUNDLE_NAME.getStringConverter().apply(BUNDLE_NAME.fetchFrom(p), p); + + return true; + } catch (RuntimeException re) { + if (re.getCause() instanceof ConfigException) { + throw (ConfigException) re.getCause(); + } else { + throw new ConfigException(re); + } + } + } + + private boolean prepareProto(Map p) + throws PackagerException, IOException { + File appImage = StandardBundlerParam.getPredefinedAppImage(p); + File appDir = null; + + // we either have an application image or need to build one + if (appImage != null) { + appDir = new File(RPM_IMAGE_DIR.fetchFrom(p), + APP_NAME.fetchFrom(p)); + // copy everything from appImage dir into appDir/name + IOUtils.copyRecursive(appImage.toPath(), appDir.toPath()); + } else { + appDir = APP_BUNDLER.fetchFrom(p).doBundle(p, + RPM_IMAGE_DIR.fetchFrom(p), true); + } + return appDir != null; + } + + public File bundle(Map p, + File outdir) throws PackagerException { + if (!outdir.isDirectory() && !outdir.mkdirs()) { + throw new PackagerException( + "error.cannot-create-output-dir", + outdir.getAbsolutePath()); + } + if (!outdir.canWrite()) { + throw new PackagerException( + "error.cannot-write-to-output-dir", + outdir.getAbsolutePath()); + } + + File imageDir = RPM_IMAGE_DIR.fetchFrom(p); + try { + + imageDir.mkdirs(); + + if (prepareProto(p) && prepareProjectConfig(p)) { + return buildRPM(p, outdir); + } + return null; + } catch (IOException ex) { + Log.verbose(ex); + throw new PackagerException(ex); + } + } + + private String getLicenseFileString(Map params) + throws IOException { + StringBuilder sb = new StringBuilder(); + + String licenseStr = LICENSE_FILE.fetchFrom(params); + if (licenseStr != null) { + File licenseFile = new File(licenseStr); + File rootDir = + LinuxAppBundler.getRootDir(RPM_IMAGE_DIR.fetchFrom(params), + params); + File target = new File(rootDir + File.separator + "app" + + File.separator + licenseFile.getName()); + Files.copy(licenseFile.toPath(), target.toPath()); + + sb.append("%license "); + sb.append(LINUX_INSTALL_DIR.fetchFrom(params)); + sb.append("/"); + sb.append(APP_NAME.fetchFrom(params)); + sb.append("/app/"); + sb.append(licenseFile.getName()); + } + + return sb.toString(); + } + + private boolean prepareProjectConfig(Map params) + throws IOException { + Map data = createReplacementData(params); + File rootDir = + LinuxAppBundler.getRootDir(RPM_IMAGE_DIR.fetchFrom(params), params); + + // prepare installer icon + File iconTarget = getConfig_IconFile(rootDir, params); + File icon = LinuxAppBundler.ICON_PNG.fetchFrom(params); + if (!StandardBundlerParam.isRuntimeInstaller(params)) { + if (icon == null || !icon.exists()) { + fetchResource(iconTarget.getName(), + I18N.getString("resource.menu-icon"), + DEFAULT_ICON, + iconTarget, + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + } else { + fetchResource(iconTarget.getName(), + I18N.getString("resource.menu-icon"), + icon, + iconTarget, + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + } + } + + StringBuilder installScripts = new StringBuilder(); + StringBuilder removeScripts = new StringBuilder(); + for (Map addLauncher : + ADD_LAUNCHERS.fetchFrom(params)) { + Map addLauncherData = + createReplacementData(addLauncher); + addLauncherData.put("APPLICATION_FS_NAME", + data.get("APPLICATION_FS_NAME")); + addLauncherData.put("DESKTOP_MIMES", ""); + + // prepare desktop shortcut + Writer w = new BufferedWriter(new FileWriter( + getConfig_DesktopShortcutFile(rootDir, addLauncher))); + String content = preprocessTextResource( + getConfig_DesktopShortcutFile(rootDir, + addLauncher).getName(), + I18N.getString("resource.menu-shortcut-descriptor"), + DEFAULT_DESKTOP_FILE_TEMPLATE, addLauncherData, + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + w.write(content); + w.close(); + + // prepare installer icon + iconTarget = getConfig_IconFile(rootDir, addLauncher); + icon = LinuxAppBundler.ICON_PNG.fetchFrom(addLauncher); + if (icon == null || !icon.exists()) { + fetchResource(iconTarget.getName(), + I18N.getString("resource.menu-icon"), + DEFAULT_ICON, + iconTarget, + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + } else { + fetchResource(iconTarget.getName(), + I18N.getString("resource.menu-icon"), + icon, + iconTarget, + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + } + + // post copying of desktop icon + installScripts.append("xdg-desktop-menu install --novendor "); + installScripts.append(LINUX_INSTALL_DIR.fetchFrom(params)); + installScripts.append("/"); + installScripts.append(data.get("APPLICATION_FS_NAME")); + installScripts.append("/"); + installScripts.append(addLauncherData.get( + "APPLICATION_LAUNCHER_FILENAME")); + installScripts.append(".desktop\n"); + + // preun cleanup of desktop icon + removeScripts.append("xdg-desktop-menu uninstall --novendor "); + removeScripts.append(LINUX_INSTALL_DIR.fetchFrom(params)); + removeScripts.append("/"); + removeScripts.append(data.get("APPLICATION_FS_NAME")); + removeScripts.append("/"); + removeScripts.append(addLauncherData.get( + "APPLICATION_LAUNCHER_FILENAME")); + removeScripts.append(".desktop\n"); + + } + data.put("ADD_LAUNCHERS_INSTALL", installScripts.toString()); + data.put("ADD_LAUNCHERS_REMOVE", removeScripts.toString()); + + StringBuilder cdsScript = new StringBuilder(); + + data.put("APP_CDS_CACHE", cdsScript.toString()); + + List> associations = + FILE_ASSOCIATIONS.fetchFrom(params); + data.put("FILE_ASSOCIATION_INSTALL", ""); + data.put("FILE_ASSOCIATION_REMOVE", ""); + data.put("DESKTOP_MIMES", ""); + if (associations != null) { + String mimeInfoFile = XDG_FILE_PREFIX.fetchFrom(params) + + "-MimeInfo.xml"; + StringBuilder mimeInfo = new StringBuilder( + "\n\n"); + StringBuilder registrations = new StringBuilder(); + StringBuilder deregistrations = new StringBuilder(); + StringBuilder desktopMimes = new StringBuilder("MimeType="); + boolean addedEntry = false; + + for (Map assoc : associations) { + // + // Awesome document + // + // + // + + if (assoc == null) { + continue; + } + + String description = FA_DESCRIPTION.fetchFrom(assoc); + File faIcon = FA_ICON.fetchFrom(assoc); //TODO FA_ICON_PNG + List extensions = FA_EXTENSIONS.fetchFrom(assoc); + if (extensions == null) { + Log.verbose(I18N.getString( + "message.creating-association-with-null-extension")); + } + + List mimes = FA_CONTENT_TYPE.fetchFrom(assoc); + if (mimes == null || mimes.isEmpty()) { + continue; + } + String thisMime = mimes.get(0); + String dashMime = thisMime.replace('/', '-'); + + mimeInfo.append(" \n"); + if (description != null && !description.isEmpty()) { + mimeInfo.append(" ") + .append(description) + .append("\n"); + } + + if (extensions != null) { + for (String ext : extensions) { + mimeInfo.append(" \n"); + } + } + + mimeInfo.append(" \n"); + if (!addedEntry) { + registrations.append("xdg-mime install ") + .append(LINUX_INSTALL_DIR.fetchFrom(params)) + .append("/") + .append(data.get("APPLICATION_FS_NAME")) + .append("/") + .append(mimeInfoFile) + .append("\n"); + + deregistrations.append("xdg-mime uninstall ") + .append(LINUX_INSTALL_DIR.fetchFrom(params)) + .append("/") + .append(data.get("APPLICATION_FS_NAME")) + .append("/") + .append(mimeInfoFile) + .append("\n"); + addedEntry = true; + } else { + desktopMimes.append(";"); + } + desktopMimes.append(thisMime); + + if (faIcon != null && faIcon.exists()) { + int size = getSquareSizeOfImage(faIcon); + + if (size > 0) { + File target = new File(rootDir, + APP_NAME.fetchFrom(params) + + "_fa_" + faIcon.getName()); + IOUtils.copyFile(faIcon, target); + + // xdg-icon-resource install --context mimetypes + // --size 64 awesomeapp_fa_1.png + // application-x.vnd-awesome + registrations.append( + "xdg-icon-resource install " + + "--context mimetypes --size ") + .append(size) + .append(" ") + .append(LINUX_INSTALL_DIR.fetchFrom(params)) + .append("/") + .append(data.get("APPLICATION_FS_NAME")) + .append("/") + .append(target.getName()) + .append(" ") + .append(dashMime) + .append("\n"); + + // xdg-icon-resource uninstall --context mimetypes + // --size 64 awesomeapp_fa_1.png + // application-x.vnd-awesome + deregistrations.append( + "xdg-icon-resource uninstall " + + "--context mimetypes --size ") + .append(size) + .append(" ") + .append(LINUX_INSTALL_DIR.fetchFrom(params)) + .append("/") + .append(data.get("APPLICATION_FS_NAME")) + .append("/") + .append(target.getName()) + .append(" ") + .append(dashMime) + .append("\n"); + } + } + } + mimeInfo.append(""); + + if (addedEntry) { + Writer w = new BufferedWriter(new FileWriter( + new File(rootDir, mimeInfoFile))); + w.write(mimeInfo.toString()); + w.close(); + data.put("FILE_ASSOCIATION_INSTALL", registrations.toString()); + data.put("FILE_ASSOCIATION_REMOVE", deregistrations.toString()); + data.put("DESKTOP_MIMES", desktopMimes.toString()); + } + } + + if (!StandardBundlerParam.isRuntimeInstaller(params)) { + //prepare desktop shortcut + Writer w = new BufferedWriter(new FileWriter( + getConfig_DesktopShortcutFile(rootDir, params))); + String content = preprocessTextResource( + getConfig_DesktopShortcutFile(rootDir, params).getName(), + I18N.getString("resource.menu-shortcut-descriptor"), + DEFAULT_DESKTOP_FILE_TEMPLATE, data, + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + w.write(content); + w.close(); + } + + // prepare spec file + Writer w = new BufferedWriter( + new FileWriter(getConfig_SpecFile(params))); + String content = preprocessTextResource( + getConfig_SpecFile(params).getName(), + I18N.getString("resource.rpm-spec-file"), + DEFAULT_SPEC_TEMPLATE, data, + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + w.write(content); + w.close(); + + return true; + } + + private Map createReplacementData( + Map params) throws IOException { + Map data = new HashMap<>(); + + data.put("APPLICATION_NAME", APP_NAME.fetchFrom(params)); + data.put("APPLICATION_FS_NAME", APP_NAME.fetchFrom(params)); + data.put("APPLICATION_PACKAGE", BUNDLE_NAME.fetchFrom(params)); + data.put("APPLICATION_VENDOR", VENDOR.fetchFrom(params)); + data.put("APPLICATION_VERSION", VERSION.fetchFrom(params)); + data.put("APPLICATION_LAUNCHER_FILENAME", APP_NAME.fetchFrom(params)); + data.put("INSTALLATION_DIRECTORY", LINUX_INSTALL_DIR.fetchFrom(params)); + data.put("XDG_PREFIX", XDG_FILE_PREFIX.fetchFrom(params)); + data.put("DEPLOY_BUNDLE_CATEGORY", MENU_GROUP.fetchFrom(params)); + // TODO rpm categories + data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params)); + data.put("APPLICATION_SUMMARY", APP_NAME.fetchFrom(params)); + data.put("APPLICATION_LICENSE_TYPE", LICENSE_TYPE.fetchFrom(params)); + data.put("APPLICATION_LICENSE_FILE", getLicenseFileString(params)); + String deps = LINUX_PACKAGE_DEPENDENCIES.fetchFrom(params); + data.put("PACKAGE_DEPENDENCIES", + deps.isEmpty() ? "" : "Requires: " + deps); + data.put("RUNTIME_INSTALLER", "" + + StandardBundlerParam.isRuntimeInstaller(params)); + return data; + } + + private File getConfig_DesktopShortcutFile(File rootDir, + Map params) { + return new File(rootDir, APP_NAME.fetchFrom(params) + ".desktop"); + } + + private File getConfig_IconFile(File rootDir, + Map params) { + return new File(rootDir, APP_NAME.fetchFrom(params) + ".png"); + } + + private File getConfig_SpecFile(Map params) { + return new File(RPM_IMAGE_DIR.fetchFrom(params), + APP_NAME.fetchFrom(params) + ".spec"); + } + + private File buildRPM(Map params, + File outdir) throws IOException { + Log.verbose(MessageFormat.format(I18N.getString( + "message.outputting-bundle-location"), + outdir.getAbsolutePath())); + + File broot = new File(TEMP_ROOT.fetchFrom(params), "rmpbuildroot"); + + outdir.mkdirs(); + + //run rpmbuild + ProcessBuilder pb = new ProcessBuilder( + TOOL_RPMBUILD, + "-bb", getConfig_SpecFile(params).getAbsolutePath(), + "--define", "%_sourcedir " + + RPM_IMAGE_DIR.fetchFrom(params).getAbsolutePath(), + // save result to output dir + "--define", "%_rpmdir " + outdir.getAbsolutePath(), + // do not use other system directories to build as current user + "--define", "%_topdir " + broot.getAbsolutePath() + ); + pb = pb.directory(RPM_IMAGE_DIR.fetchFrom(params)); + IOUtils.exec(pb, false); + + Log.verbose(MessageFormat.format( + I18N.getString("message.output-bundle-location"), + outdir.getAbsolutePath())); + + // presume the result is the ".rpm" file with the newest modified time + // not the best solution, but it is the most reliable + File result = null; + long lastModified = 0; + File[] list = outdir.listFiles(); + if (list != null) { + for (File f : list) { + if (f.getName().endsWith(".rpm") && + f.lastModified() > lastModified) { + result = f; + lastModified = f.lastModified(); + } + } + } + + return result; + } + + @Override + public String getName() { + return I18N.getString("rpm.bundler.name"); + } + + @Override + public String getDescription() { + return I18N.getString("rpm.bundler.description"); + } + + @Override + public String getID() { + return "rpm"; + } + + @Override + public String getBundleType() { + return "INSTALLER"; + } + + @Override + public Collection> getBundleParameters() { + Collection> results = new LinkedHashSet<>(); + results.addAll(LinuxAppBundler.getAppBundleParameters()); + results.addAll(getRpmBundleParameters()); + return results; + } + + public static Collection> getRpmBundleParameters() { + return Arrays.asList( + BUNDLE_NAME, + MENU_GROUP, + DESCRIPTION, + LinuxAppBundler.ICON_PNG, + LICENSE_FILE, + LICENSE_TYPE, + VENDOR + ); + } + + @Override + public File execute(Map params, + File outputParentDir) throws PackagerException { + return bundle(params, outputParentDir); + } + + @Override + public boolean supported(boolean runtimeInstaller) { + return (Platform.getPlatform() == Platform.LINUX); + } + + public int getSquareSizeOfImage(File f) { + try { + BufferedImage bi = ImageIO.read(f); + if (bi.getWidth() == bi.getHeight()) { + return bi.getWidth(); + } else { + return 0; + } + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } +} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources.properties b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources.properties new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources.properties @@ -0,0 +1,78 @@ +# +# 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 +app.bundler.description=A Directory based image of a linux Application with an optionally co-bundled JRE. Used as a base for the Installer bundlers. +deb.bundler.name=DEB Installer +deb.bundler.description=Linux Debian Bundle. +rpm.bundler.name=RPM Bundle +rpm.bundler.description=Redhat Package Manager (RPM) bundler. + +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.deb-copyright-file=DEB copyright file +resource.deb-init-script=DEB init script +resource.menu-shortcut-descriptor=Menu shortcut descriptor +resource.menu-icon=menu icon +resource.rpm-spec-file=RPM spec file +resource.rpm-init-script=RPM init script + +error.parameters-null=Parameters map is null. +error.parameters-null.advice=Pass in a non-null parameters map. +error.parameters-null=Parameters map is null. +error.parameters-null.advice=Pass in a non-null parameters map. +error.tool-not-found=Can not find {0}. +error.tool-not-found.advice=Please install required packages. +error.launcher-name-too-long=The bundle name "{0}" is too long for a daemon. +error.launcher-name-too-long.advice=Set a bundler argument "{0}" to a bundle name that is shorter than 16 characters. +error.cannot-create-output-dir=Output directory {0} cannot be created. +error.cannot-write-to-output-dir=Output directory {0} is not writable. +error.no-content-types-for-file-association=No MIME types were specified for File Association number {0}. +error.no-content-types-for-file-association.advice=For Linux Bundling specify one and only one MIME type for each file association. +error.too-many-content-types-for-file-association=More than one MIME types was specified for File Association number {0}. +error.too-many-content-types-for-file-association.advice=For Linux Bundling specify one and only one MIME type for each file association. +error.no-support-for-peruser-daemons=Bundler doesn't support per-user daemons. +error.no-support-for-peruser-daemons.advice=Make sure that the system wide hint is set to true. +error.invalid-value-for-package-name=Invalid value "{0}" for the package name. +error.invalid-value-for-package-name.advice=Set the "linux.bundleName" parameter 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. +error.cannot-find-rpmbuild=Can not find rpmbuild {0} or newer. +error.cannot-find-rpmbuild.advice=\ Install packages needed to build RPM, version {0} or newer. + + +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.one-shortcut-required=At least one type of shortcut is required. Enabling menu shortcut. +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. diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_ja.properties b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_ja.properties new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_ja.properties @@ -0,0 +1,78 @@ +# +# 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 +app.bundler.description=A Directory based image of a linux Application with an optionally co-bundled JRE. Used as a base for the Installer bundlers. +deb.bundler.name=DEB Installer +deb.bundler.description=Linux Debian Bundle. +rpm.bundler.name=RPM Bundle +rpm.bundler.description=Redhat Package Manager (RPM) bundler. + +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.deb-copyright-file=DEB copyright file +resource.deb-init-script=DEB init script +resource.menu-shortcut-descriptor=Menu shortcut descriptor +resource.menu-icon=menu icon +resource.rpm-spec-file=RPM spec file +resource.rpm-init-script=RPM init script + +error.parameters-null=Parameters map is null. +error.parameters-null.advice=Pass in a non-null parameters map. +error.parameters-null=Parameters map is null. +error.parameters-null.advice=Pass in a non-null parameters map. +error.tool-not-found=Can not find {0}. +error.tool-not-found.advice=Please install required packages. +error.launcher-name-too-long=The bundle name "{0}" is too long for a daemon. +error.launcher-name-too-long.advice=Set a bundler argument "{0}" to a bundle name that is shorter than 16 characters. +error.cannot-create-output-dir=Output directory {0} cannot be created. +error.cannot-write-to-output-dir=Output directory {0} is not writable. +error.no-content-types-for-file-association=No MIME types were specified for File Association number {0}. +error.no-content-types-for-file-association.advice=For Linux Bundling specify one and only one MIME type for each file association. +error.too-many-content-types-for-file-association=More than one MIME types was specified for File Association number {0}. +error.too-many-content-types-for-file-association.advice=For Linux Bundling specify one and only one MIME type for each file association. +error.no-support-for-peruser-daemons=Bundler doesn't support per-user daemons. +error.no-support-for-peruser-daemons.advice=Make sure that the system wide hint is set to true. +error.invalid-value-for-package-name=Invalid value "{0}" for the package name. +error.invalid-value-for-package-name.advice=Set the "linux.bundleName" parameter 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. +error.cannot-find-rpmbuild=Can not find rpmbuild {0} or newer. +error.cannot-find-rpmbuild.advice=\ Install packages needed to build RPM, version {0} or newer. + + +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.one-shortcut-required=At least one type of shortcut is required. Enabling menu shortcut. +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. diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_zh_CN.properties b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_zh_CN.properties new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_zh_CN.properties @@ -0,0 +1,78 @@ +# +# 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 +app.bundler.description=A Directory based image of a linux Application with an optionally co-bundled JRE. Used as a base for the Installer bundlers. +deb.bundler.name=DEB Installer +deb.bundler.description=Linux Debian Bundle. +rpm.bundler.name=RPM Bundle +rpm.bundler.description=Redhat Package Manager (RPM) bundler. + +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.deb-copyright-file=DEB copyright file +resource.deb-init-script=DEB init script +resource.menu-shortcut-descriptor=Menu shortcut descriptor +resource.menu-icon=menu icon +resource.rpm-spec-file=RPM spec file +resource.rpm-init-script=RPM init script + +error.parameters-null=Parameters map is null. +error.parameters-null.advice=Pass in a non-null parameters map. +error.parameters-null=Parameters map is null. +error.parameters-null.advice=Pass in a non-null parameters map. +error.tool-not-found=Can not find {0}. +error.tool-not-found.advice=Please install required packages. +error.launcher-name-too-long=The bundle name "{0}" is too long for a daemon. +error.launcher-name-too-long.advice=Set a bundler argument "{0}" to a bundle name that is shorter than 16 characters. +error.cannot-create-output-dir=Output directory {0} cannot be created. +error.cannot-write-to-output-dir=Output directory {0} is not writable. +error.no-content-types-for-file-association=No MIME types were specified for File Association number {0}. +error.no-content-types-for-file-association.advice=For Linux Bundling specify one and only one MIME type for each file association. +error.too-many-content-types-for-file-association=More than one MIME types was specified for File Association number {0}. +error.too-many-content-types-for-file-association.advice=For Linux Bundling specify one and only one MIME type for each file association. +error.no-support-for-peruser-daemons=Bundler doesn't support per-user daemons. +error.no-support-for-peruser-daemons.advice=Make sure that the system wide hint is set to true. +error.invalid-value-for-package-name=Invalid value "{0}" for the package name. +error.invalid-value-for-package-name.advice=Set the "linux.bundleName" parameter 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. +error.cannot-find-rpmbuild=Can not find rpmbuild {0} or newer. +error.cannot-find-rpmbuild.advice=\ Install packages needed to build RPM, version {0} or newer. + + +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.one-shortcut-required=At least one type of shortcut is required. Enabling menu shortcut. +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. diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/javalogo_white_32.png b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/javalogo_white_32.png new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0390d7890b16e2dae314c91c3f731780a0868f37 GIT binary patch literal 2505 zc$}S9dpMNq79UI+m!aKu3;UQx7s;6UTxP~#Gh<8?VU%chVlJPVG3H`sWKhnyB@qv~ z79!e6cS(^pQSP^F`%rDtJ#vuBU3;|CInR0a>5ub1-}k+1t#|!?YrX4T@AG_#p1XIz zRrOUN5D47Wg-n&th?N(nEdPolBHqiV4H9QxiMJq75=Ik&5F%4R2NAA38VjU?G-h~6 zD`*FSD6u(vd?mi_ZUlyaXHHx3F_-d$kdtfHhOoc@7HEw8!J+^wf;E<4fkS*5 zNO?37Gmt=3$oR0uc)h*x1;ta9CKN2fvjswglQ$M& z;!b05_$!8OD+lx?S5U+`1TrZi0T1!H!UWD=WI(2nN%By54C#OE{7)?N|EU^Uo(%fG z+GW)w@4*%EYxB#Muh$3U%kPXxemT%oH8Tig&5SFVxJUZ%_C7zi1AFzBSud}tE2-N; z?_b;EnYE2;4EM~UURMRIJ^Pv{C(1uMFEHJV#to(#O0H^?PFbmHI`=&^r5A0^L?+2v z=rGg~lVns&{gn>&M(pyZk)f%f_Btn{Qw7h@R6e$x`P3D5=aa*$prG^TW#~I>qr(2P z=ZZHxxu;|MaF~6Oy{{L`_H0x-=3JW+cr@Fa(mUF-JTjYU`6{}hp{C_Edl+%yDqD{P zuh8Na<>#MN+Le@qv9v5E{DZ}6=KZw);f;=t;0pu6fdz>o)beeQE;x}`6Zf212W*P# z3#!tue$=^aa=CwJ6fwFKh1b^I?KzF0zqq)l}Qg9Vlf2l&o@OjFw>%u5yN3C zZa7@Jr7YT0@!74@;Txykv&@2^x2MeT#9A5YL^T;cgOiR;MCOA1&@lY>q1L+kNedbCgV+;82ejwU|nEJ>!F^& zGj`F-LkFc}&s}fOsdkyOanSnrquO!$YWrRF+HxjkB$nc_(>OuQJ$m$UMst8jwEk}% z9+jWG-+phfbN{3=<*ux$yo2OKLa2y;yqLGX+w|y0mEJd!yNioC24&aTUx~iHdcL|kPpTJY zigc>dju%B%mAmeZ%NtEhSgPi7{n~yX7?5RTXkl{Q3Yy6_mQXKW-+1wx(Hf1ynU^Pqc_9Y?)2$t*h%sR0|T`+37UOJ6r!rpl4WfqQIt6L}oa}HdRnJ zJvb+c*WdQ$4KY%7%+ZsF(W{rmsB35_>-!xz5ECB#ofpj8HfdOcLs>F%V?9DwjH<&} z(d@;fBwfD&_vF;ZnEp0sc3z%hT3XsV?H}MuN=m}Zl_&F!Bw5KLb~dmFN0V6~9L6i| zg-z-8^=TTB{QZrzbhU}Cy~U9}6fe@+K7V85E3%ES#la%wyIidv!M1`3@Wl&EYh`}!+PUS;p|9djk>3n&ySpsVejY&I+^ipv!CBz8K$>YE43cKljna?;hq zXfvI7w0L@7sv(V~pfEi>{iyDGq1WjTA$N?1)Z$`WjwADPa%3sCVF7CK+k;Wj^>-qf z?3P>XD*m$`78EiJnK2P9eisLrbz}#@HOV4dADBDftJ!&NZqc&3{mtY1u)!z!sl}OT z$_Ey8r$Qg&rfcp!J*;9>%DC;3Z61mXrBY`#I*;o_2bN5~Tw@zLS+T4-?rmiNU*1{| YF=U~bE#+IxSAMQsop+Njkpg1=0B24SNdN!< diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.control b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.control new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.control @@ -0,0 +1,10 @@ +Package: APPLICATION_PACKAGE +Version: APPLICATION_VERSION +Section: unknown +Maintainer: APPLICATION_MAINTAINER +Priority: optional +Architecture: APPLICATION_ARCH +Provides: APPLICATION_PACKAGE +Description: APPLICATION_DESCRIPTION +Installed-Size: APPLICATION_INSTALLED_SIZE +PACKAGE_DEPENDENCIES diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.copyright b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.copyright new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.copyright @@ -0,0 +1,8 @@ + +Copyright: + + APPLICATION_COPYRIGHT + +License: + + APPLICATION_LICENSE_TEXT diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.desktop b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.desktop new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Name=APPLICATION_NAME +Comment=APPLICATION_DESCRIPTION +Exec=INSTALLATION_DIRECTORY/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME +Icon=INSTALLATION_DIRECTORY/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME.png +Terminal=false +Type=Application +Categories=DEPLOY_BUNDLE_CATEGORY +DESKTOP_MIMES diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.postinst b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.postinst new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.postinst @@ -0,0 +1,61 @@ +#!/bin/sh +# postinst script for APPLICATION_NAME +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `configure' +# * `abort-upgrade' +# * `abort-remove' `in-favour' +# +# * `abort-remove' +# * `abort-deconfigure' `in-favour' +# `removing' +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + +case "$1" in + configure) + if [ "RUNTIME_INSTALLER" != "true" ]; then + echo Adding shortcut to the menu +ADD_LAUNCHERS_INSTALL + xdg-desktop-menu install --novendor INSTALLATION_DIRECTORY/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME.desktop +FILE_ASSOCIATION_INSTALL + fi + if [ "SERVICE_HINT" = "true" ]; then + echo Installing daemon + cp INSTALLATION_DIRECTORY/APPLICATION_FS_NAME/APPLICATION_PACKAGE.init /etc/init.d/APPLICATION_PACKAGE + + if [ -x "/etc/init.d/APPLICATION_PACKAGE" ]; then + update-rc.d APPLICATION_PACKAGE defaults + + if [ "START_ON_INSTALL" = "true" ]; then + if which invoke-rc.d >/dev/null 2>&1; then + invoke-rc.d APPLICATION_PACKAGE start + else + /etc/init.d/APPLICATION_PACKAGE start + fi + fi + fi + + fi + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.postrm b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.postrm new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.postrm @@ -0,0 +1,44 @@ +#!/bin/sh +# postrm script for APPLICATION_NAME +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `remove' +# * `purge' +# * `upgrade' +# * `failed-upgrade' +# * `abort-install' +# * `abort-install' +# * `abort-upgrade' +# * `disappear' +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + +case "$1" in + purge|remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) + if [ "$1" = "purge" ] ; then + if [ "SERVICE_HINT" = "true" ]; then + echo Uninstalling daemon + rm -f /etc/init.d/APPLICATION_PACKAGE + + update-rc.d APPLICATION_PACKAGE remove + fi + fi + ;; + + *) + echo "postrm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.preinst b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.preinst new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.preinst @@ -0,0 +1,35 @@ +#!/bin/sh +# preinst script for APPLICATION_NAME +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `install' +# * `install' +# * `upgrade' +# * `abort-upgrade' +# for details, see http://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 + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.prerm b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.prerm new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.prerm @@ -0,0 +1,45 @@ +#!/bin/sh +# prerm script for APPLICATION_NAME +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `remove' +# * `upgrade' +# * `failed-upgrade' +# * `remove' `in-favour' +# * `deconfigure' `in-favour' +# `removing' +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +case "$1" in + remove|upgrade|deconfigure) + if [ "RUNTIME_INSTALLER" != "true" ]; then + echo Removing shortcut +ADD_LAUNCHERS_REMOVE + xdg-desktop-menu uninstall --novendor INSTALLATION_DIRECTORY/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME.desktop +FILE_ASSOCIATION_REMOVE + fi + ;; + + failed-upgrade) + ;; + + *) + echo "prerm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 + diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.spec b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.spec new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.spec @@ -0,0 +1,60 @@ +Summary: APPLICATION_SUMMARY +Name: APPLICATION_PACKAGE +Version: APPLICATION_VERSION +Release: 1 +License: APPLICATION_LICENSE_TYPE +Vendor: APPLICATION_VENDOR +Prefix: INSTALLATION_DIRECTORY +Provides: APPLICATION_PACKAGE +Autoprov: 0 +Autoreq: 0 +PACKAGE_DEPENDENCIES + +#avoid ARCH subfolder +%define _rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm + +#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} +mkdir -p %{buildroot}INSTALLATION_DIRECTORY +cp -r %{_sourcedir}/APPLICATION_FS_NAME %{buildroot}INSTALLATION_DIRECTORY + +%files +APPLICATION_LICENSE_FILE +INSTALLATION_DIRECTORY/APPLICATION_FS_NAME + +%post +if [ "RUNTIME_INSTALLER" != "true" ]; then +ADD_LAUNCHERS_INSTALL + xdg-desktop-menu install --novendor INSTALLATION_DIRECTORY/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME.desktop +FILE_ASSOCIATION_INSTALL +fi + +%preun +if [ "RUNTIME_INSTALLER" != "true" ]; then +ADD_LAUNCHERS_REMOVE + xdg-desktop-menu uninstall --novendor INSTALLATION_DIRECTORY/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME.desktop +FILE_ASSOCIATION_REMOVE +fi +if [ "SERVICE_HINT" = "true" ]; then + if [ -x "/etc/init.d/APPLICATION_PACKAGE" ]; then + if [ "STOP_ON_UNINSTALL" = "true" ]; then + /etc/init.d/APPLICATION_PACKAGE stop + fi + /sbin/chkconfig --del APPLICATION_PACKAGE + rm -f /etc/init.d/APPLICATION_PACKAGE + fi +fi + +%clean diff --git a/src/jdk.jpackage/linux/classes/module-info.java.extra b/src/jdk.jpackage/linux/classes/module-info.java.extra new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/linux/classes/module-info.java.extra @@ -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.jpackage.internal.Bundler with + jdk.jpackage.internal.LinuxAppBundler, + jdk.jpackage.internal.LinuxDebBundler, + jdk.jpackage.internal.LinuxRpmBundler; + diff --git a/src/jdk.jpackage/linux/native/jpackageapplauncher/launcher.cpp b/src/jdk.jpackage/linux/native/jpackageapplauncher/launcher.cpp new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/linux/native/jpackageapplauncher/launcher.cpp @@ -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 +#include +#include +#include +#include +#include + + +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 += "/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; +} diff --git a/src/jdk.jpackage/linux/native/libapplauncher/LinuxPlatform.cpp b/src/jdk.jpackage/linux/native/libapplauncher/LinuxPlatform.cpp new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/linux/native/libapplauncher/LinuxPlatform.cpp @@ -0,0 +1,1083 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "Platform.h" + +#include "JavaVirtualMachine.h" +#include "LinuxPlatform.h" +#include "PlatformString.h" +#include "IniFile.h" +#include "Helpers.h" +#include "FilePath.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LINUX_JPACKAGE_TMP_DIR "/.java/jpackage/tmp" + +TString GetEnv(const TString &name) { + TString result; + + char *value = ::getenv((TCHAR*) name.c_str()); + + if (value != NULL) { + result = value; + } + + return result; +} + +LinuxPlatform::LinuxPlatform(void) : Platform(), +PosixPlatform() { + FMainThread = pthread_self(); +} + +LinuxPlatform::~LinuxPlatform(void) { +} + +TString LinuxPlatform::GetPackageAppDirectory() { + return FilePath::IncludeTrailingSeparator( + GetPackageRootDirectory()) + _T("app"); +} + +TString LinuxPlatform::GetAppName() { + TString result = GetModuleFileName(); + result = FilePath::ExtractFileName(result); + return result; +} + +TString LinuxPlatform::GetPackageLauncherDirectory() { + return GetPackageRootDirectory(); +} + +TString LinuxPlatform::GetPackageRuntimeBinDirectory() { + return FilePath::IncludeTrailingSeparator(GetPackageRootDirectory()) + _T("runtime/bin"); +} + +void LinuxPlatform::ShowMessage(TString title, TString description) { + printf("%s %s\n", PlatformString(title).toPlatformString(), + PlatformString(description).toPlatformString()); + fflush(stdout); +} + +void LinuxPlatform::ShowMessage(TString description) { + TString appname = GetModuleFileName(); + appname = FilePath::ExtractFileName(appname); + ShowMessage(PlatformString(appname).toPlatformString(), + PlatformString(description).toPlatformString()); +} + +TCHAR* LinuxPlatform::ConvertStringToFileSystemString(TCHAR* Source, + bool &release) { + // Not Implemented. + return NULL; +} + +TCHAR* LinuxPlatform::ConvertFileSystemStringToString(TCHAR* Source, + bool &release) { + // Not Implemented. + return NULL; +} + +TString LinuxPlatform::GetModuleFileName() { + ssize_t len = 0; + TString result; + DynamicBuffer buffer(MAX_PATH); + if (buffer.GetData() == NULL) { + return result; + } + + if ((len = readlink("/proc/self/exe", buffer.GetData(), + MAX_PATH - 1)) != -1) { + buffer[len] = '\0'; + result = buffer.GetData(); + } + + return result; +} + +void LinuxPlatform::SetCurrentDirectory(TString Value) { + chdir(PlatformString(Value).toPlatformString()); +} + +TString LinuxPlatform::GetPackageRootDirectory() { + TString filename = GetModuleFileName(); + return FilePath::ExtractFilePath(filename); +} + +TString LinuxPlatform::GetAppDataDirectory() { + TString result; + TString home = GetEnv(_T("HOME")); + + if (home.empty() == false) { + result += FilePath::IncludeTrailingSeparator(home) + _T(".local"); + } + + return result; +} + +ISectionalPropertyContainer* LinuxPlatform::GetConfigFile(TString FileName) { + IniFile *result = new IniFile(); + if (result == NULL) { + return NULL; + } + + result->LoadFromFile(FileName); + + return result; +} + +TString LinuxPlatform::GetBundledJavaLibraryFileName(TString RuntimePath) { + TString result = FilePath::IncludeTrailingSeparator(RuntimePath) + + "lib/libjli.so"; + + if (FilePath::FileExists(result) == false) { + result = FilePath::IncludeTrailingSeparator(RuntimePath) + + "lib/jli/libjli.so"; + if (FilePath::FileExists(result) == false) { + printf("Cannot find libjli.so!"); + } + } + + return result; +} + +bool LinuxPlatform::IsMainThread() { + bool result = (FMainThread == pthread_self()); + return result; +} + +TString LinuxPlatform::getTmpDirString() { + return TString(LINUX_JPACKAGE_TMP_DIR); +} + +TPlatformNumber LinuxPlatform::GetMemorySize() { + long pages = sysconf(_SC_PHYS_PAGES); + long page_size = sysconf(_SC_PAGE_SIZE); + TPlatformNumber result = pages * page_size; + result = result / 1048576; // Convert from bytes to megabytes. + return result; +} + +void PosixProcess::Cleanup() { + if (FOutputHandle != 0) { + close(FOutputHandle); + FOutputHandle = 0; + } + + if (FInputHandle != 0) { + close(FInputHandle); + FInputHandle = 0; + } +} + +#define PIPE_READ 0 +#define PIPE_WRITE 1 + +bool PosixProcess::Execute(const TString Application, + const std::vector Arguments, bool AWait) { + bool result = false; + + if (FRunning == false) { + FRunning = true; + + int handles[2]; + + if (pipe(handles) == -1) { + return false; + } + + struct sigaction sa; + sa.sa_handler = SIG_IGN; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + + FChildPID = fork(); + + // PID returned by vfork is 0 for the child process and the + // PID of the child process for the parent. + if (FChildPID == -1) { + // Error + TString message = PlatformString::Format( + _T("Error: Unable to create process %s"), + Application.data()); + throw Exception(message); + } else if (FChildPID == 0) { + Cleanup(); + TString command = Application; + + for (std::vector::const_iterator iterator = + Arguments.begin(); iterator != Arguments.end(); + iterator++) { + command += TString(_T(" ")) + *iterator; + } +#ifdef DEBUG + printf("%s\n", command.data()); +#endif // DEBUG + + dup2(handles[PIPE_READ], STDIN_FILENO); + dup2(handles[PIPE_WRITE], STDOUT_FILENO); + + close(handles[PIPE_READ]); + close(handles[PIPE_WRITE]); + + execl("/bin/sh", "sh", "-c", command.data(), (char *) 0); + + _exit(127); + } else { + FOutputHandle = handles[PIPE_READ]; + FInputHandle = handles[PIPE_WRITE]; + + if (AWait == true) { + ReadOutput(); + Wait(); + Cleanup(); + FRunning = false; + result = true; + } else { + result = true; + } + } + } + + return result; +} + + +//---------------------------------------------------------------------------- + +#ifndef __UNIX_JPACKAGE_PLATFORM__ +#define __UNIX_JPACKAGE_PLATFORM__ + +/** Provide an abstraction for difference in the platform APIs, + e.g. string manipulation functions, etc. */ +#include +#include +#include +#include + +#define TCHAR char + +#define _T(x) x + +#define JPACKAGE_MULTIBYTE_SNPRINTF snprintf + +#define JPACKAGE_SNPRINTF(buffer, sizeOfBuffer, count, format, ...) \ + snprintf((buffer), (count), (format), __VA_ARGS__) + +#define JPACKAGE_PRINTF(format, ...) \ + printf((format), ##__VA_ARGS__) + +#define JPACKAGE_FPRINTF(dest, format, ...) \ + fprintf((dest), (format), __VA_ARGS__) + +#define JPACKAGE_SSCANF(buf, format, ...) \ + sscanf((buf), (format), __VA_ARGS__) + +#define JPACKAGE_STRDUP(strSource) \ + strdup((strSource)) + +//return "error code" (like on Windows) + +static int JPACKAGE_STRNCPY(char *strDest, size_t numberOfElements, + const char *strSource, size_t count) { + char *s = strncpy(strDest, strSource, count); + // Duplicate behavior of the Windows' _tcsncpy_s() by adding a NULL + // terminator at the end of the string. + if (count < numberOfElements) { + s[count] = '\0'; + } else { + s[numberOfElements - 1] = '\0'; + } + return (s == strDest) ? 0 : 1; +} + +#define JPACKAGE_STRICMP(x, y) \ + strcasecmp((x), (y)) + +#define JPACKAGE_STRNICMP(x, y, cnt) \ + strncasecmp((x), (y), (cnt)) + +#define JPACKAGE_STRNCMP(x, y, cnt) \ + strncmp((x), (y), (cnt)) + +#define JPACKAGE_STRLEN(x) \ + strlen((x)) + +#define JPACKAGE_STRSTR(x, y) \ + strstr((x), (y)) + +#define JPACKAGE_STRCHR(x, y) \ + strchr((x), (y)) + +#define JPACKAGE_STRRCHR(x, y) \ + strrchr((x), (y)) + +#define JPACKAGE_STRPBRK(x, y) \ + strpbrk((x), (y)) + +#define JPACKAGE_GETENV(x) \ + getenv((x)) + +#define JPACKAGE_PUTENV(x) \ + putenv((x)) + +#define JPACKAGE_STRCMP(x, y) \ + strcmp((x), (y)) + +#define JPACKAGE_STRCPY(x, y) \ + strcpy((x), (y)) + +#define JPACKAGE_STRCAT(x, y) \ + strcat((x), (y)) + +#define JPACKAGE_ATOI(x) \ + atoi((x)) + +#define JPACKAGE_FOPEN(x, y) \ + fopen((x), (y)) + +#define JPACKAGE_FGETS(x, y, z) \ + fgets((x), (y), (z)) + +#define JPACKAGE_REMOVE(x) \ + remove((x)) + +#define JPACKAGE_SPAWNV(mode, cmd, args) \ + spawnv((mode), (cmd), (args)) + +#define JPACKAGE_ISDIGIT(ch) isdigit(ch) + +// for non-unicode, just return the input string for +// the following 2 conversions +#define JPACKAGE_NEW_MULTIBYTE(message) message + +#define JPACKAGE_NEW_FROM_MULTIBYTE(message) message + +// for non-unicode, no-op for the relase operation +// since there is no memory allocated for the +// string conversions +#define JPACKAGE_RELEASE_MULTIBYTE(tmpMBCS) + +#define JPACKAGE_RELEASE_FROM_MULTIBYTE(tmpMBCS) + +// The size will be used for converting from 1 byte to 1 byte encoding. +// Ensure have space for zero-terminator. +#define JPACKAGE_GET_SIZE_FOR_ENCODING(message, theLength) (theLength + 1) + +#endif +#define xmlTagType 0 +#define xmlPCDataType 1 + +typedef struct _xmlNode XMLNode; +typedef struct _xmlAttribute XMLAttribute; + +struct _xmlNode { + int _type; // Type of node: tag, pcdata, cdate + TCHAR* _name; // Contents of node + XMLNode* _next; // Next node at same level + XMLNode* _sub; // First sub-node + XMLAttribute* _attributes; // List of attributes +}; + +struct _xmlAttribute { + TCHAR* _name; // Name of attribute + TCHAR* _value; // Value of attribute + XMLAttribute* _next; // Next attribute for this tag +}; + +// Public interface +static void RemoveNonAsciiUTF8FromBuffer(char *buf); +XMLNode* ParseXMLDocument(TCHAR* buf); +void FreeXMLDocument(XMLNode* root); + +// Utility methods for parsing document +XMLNode* FindXMLChild(XMLNode* root, const TCHAR* name); +TCHAR* FindXMLAttribute(XMLAttribute* attr, const TCHAR* name); + +// Debugging +void PrintXMLDocument(XMLNode* node, int indt); + +#include +#include +#include +#include +#include + +#define JWS_assert(s, msg) \ + if (!(s)) { Abort(msg); } + + +// Internal declarations +static XMLNode* ParseXMLElement(void); +static XMLAttribute* ParseXMLAttribute(void); +static TCHAR* SkipWhiteSpace(TCHAR *p); +static TCHAR* SkipXMLName(TCHAR *p); +static TCHAR* SkipXMLComment(TCHAR *p); +static TCHAR* SkipXMLDocType(TCHAR *p); +static TCHAR* SkipXMLProlog(TCHAR *p); +static TCHAR* SkipPCData(TCHAR *p); +static int IsPCData(TCHAR *p); +static void ConvertBuiltInEntities(TCHAR* p); +static void SetToken(int type, TCHAR* start, TCHAR* end); +static void GetNextToken(void); +static XMLNode* CreateXMLNode(int type, TCHAR* name); +static XMLAttribute* CreateXMLAttribute(TCHAR *name, TCHAR* value); +static XMLNode* ParseXMLElement(void); +static XMLAttribute* ParseXMLAttribute(void); +static void FreeXMLAttribute(XMLAttribute* attr); +static void PrintXMLAttributes(XMLAttribute* attr); +static void indent(int indt); + +static jmp_buf jmpbuf; +static XMLNode* root_node = NULL; + +/** definition of error codes for setjmp/longjmp, + * that can be handled in ParseXMLDocument() + */ +#define JMP_NO_ERROR 0 +#define JMP_OUT_OF_RANGE 1 + +#define NEXT_CHAR(p) { \ + if (*p != 0) { \ + p++; \ + } else { \ + longjmp(jmpbuf, JMP_OUT_OF_RANGE); \ + } \ +} +#define NEXT_CHAR_OR_BREAK(p) { \ + if (*p != 0) { \ + p++; \ + } else { \ + break; \ + } \ +} +#define NEXT_CHAR_OR_RETURN(p) { \ + if (*p != 0) { \ + p++; \ + } else { \ + return; \ + } \ +} +#define SKIP_CHARS(p,n) { \ + int i; \ + for (i = 0; i < (n); i++) { \ + if (*p != 0) { \ + p++; \ + } else { \ + longjmp(jmpbuf, JMP_OUT_OF_RANGE); \ + } \ + } \ +} +#define SKIP_CHARS_OR_BREAK(p,n) { \ + int i; \ + for (i = 0; i < (n); i++) { \ + if (*p != 0) { \ + p++; \ + } else { \ + break; \ + } \ + } \ + if (i < (n)) { \ + break; \ + } \ +} + +/** Iterates through the null-terminated buffer (i.e., C string) and + * replaces all UTF-8 encoded character >255 with 255 + * + * UTF-8 encoding: + * + * Range A: 0x0000 - 0x007F + * 0 | bits 0 - 7 + * Range B : 0x0080 - 0x07FF : + * 110 | bits 6 - 10 + * 10 | bits 0 - 5 + * Range C : 0x0800 - 0xFFFF : + * 1110 | bits 12-15 + * 10 | bits 6-11 + * 10 | bits 0-5 + */ +static void RemoveNonAsciiUTF8FromBuffer(char *buf) { + char* p; + char* q; + char c; + p = q = buf; + // We are not using NEXT_CHAR() to check if *q is NULL, as q is output + // location and offset for q is smaller than for p. + while (*p != '\0') { + c = *p; + if ((c & 0x80) == 0) { + /* Range A */ + *q++ = *p; + NEXT_CHAR(p); + } else if ((c & 0xE0) == 0xC0) { + /* Range B */ + *q++ = (char) 0xFF; + NEXT_CHAR(p); + NEXT_CHAR_OR_BREAK(p); + } else { + /* Range C */ + *q++ = (char) 0xFF; + NEXT_CHAR(p); + SKIP_CHARS_OR_BREAK(p, 2); + } + } + /* Null terminate string */ + *q = '\0'; +} + +static TCHAR* SkipWhiteSpace(TCHAR *p) { + if (p != NULL) { + while (iswspace(*p)) + NEXT_CHAR_OR_BREAK(p); + } + return p; +} + +static TCHAR* SkipXMLName(TCHAR *p) { + TCHAR c = *p; + /* Check if start of token */ + if (('a' <= c && c <= 'z') || + ('A' <= c && c <= 'Z') || + c == '_' || c == ':') { + + while (('a' <= c && c <= 'z') || + ('A' <= c && c <= 'Z') || + ('0' <= c && c <= '9') || + c == '_' || c == ':' || c == '.' || c == '-') { + NEXT_CHAR(p); + c = *p; + if (c == '\0') break; + } + } + return p; +} + +static TCHAR* SkipXMLComment(TCHAR *p) { + if (p != NULL) { + if (JPACKAGE_STRNCMP(p, _T(""), 3) == 0) { + SKIP_CHARS(p, 3); + return p; + } + NEXT_CHAR(p); + } while (*p != '\0'); + } + } + return p; +} + +static TCHAR* SkipXMLDocType(TCHAR *p) { + if (p != NULL) { + if (JPACKAGE_STRNCMP(p, _T("') { + NEXT_CHAR(p); + return p; + } + NEXT_CHAR(p); + } + } + } + return p; +} + +static TCHAR* SkipXMLProlog(TCHAR *p) { + if (p != NULL) { + if (JPACKAGE_STRNCMP(p, _T(""), 2) == 0) { + SKIP_CHARS(p, 2); + return p; + } + NEXT_CHAR(p); + } while (*p != '\0'); + } + } + return p; +} + +/* Search for the built-in XML entities: + * & (&), < (<), > (>), ' ('), and "e(") + * and convert them to a real TCHARacter + */ +static void ConvertBuiltInEntities(TCHAR* p) { + TCHAR* q; + q = p; + // We are not using NEXT_CHAR() to check if *q is NULL, + // as q is output location and offset for q is smaller than for p. + while (*p) { + if (IsPCData(p)) { + /* dont convert &xxx values within PData */ + TCHAR *end; + end = SkipPCData(p); + while (p < end) { + *q++ = *p; + NEXT_CHAR(p); + } + } else { + if (JPACKAGE_STRNCMP(p, _T("&"), 5) == 0) { + *q++ = '&'; + SKIP_CHARS(p, 5); + } else if (JPACKAGE_STRNCMP(p, _T("<"), 4) == 0) { + *q = '<'; + SKIP_CHARS(p, 4); + } else if (JPACKAGE_STRNCMP(p, _T(">"), 4) == 0) { + *q = '>'; + SKIP_CHARS(p, 4); + } else if (JPACKAGE_STRNCMP(p, _T("'"), 6) == 0) { + *q = '\''; + SKIP_CHARS(p, 6); + } else if (JPACKAGE_STRNCMP(p, _T(""e;"), 7) == 0) { + *q = '\"'; + SKIP_CHARS(p, 7); + } else { + *q++ = *p; + NEXT_CHAR(p); + } + } + } + *q = '\0'; +} + +/* ------------------------------------------------------------- */ +/* XML tokenizer */ + +#define TOKEN_UNKNOWN 0 +#define TOKEN_BEGIN_TAG 1 /* */ +#define TOKEN_EMPTY_CLOSE_BRACKET 4 /* /> */ +#define TOKEN_PCDATA 5 /* pcdata */ +#define TOKEN_CDATA 6 /* cdata */ +#define TOKEN_EOF 7 + +static TCHAR* CurPos = NULL; +static TCHAR* CurTokenName = NULL; +static int CurTokenType; +static int MaxTokenSize = -1; + +/* Copy token from buffer to Token variable */ +static void SetToken(int type, TCHAR* start, TCHAR* end) { + int len = end - start; + if (len > MaxTokenSize) { + if (CurTokenName != NULL) free(CurTokenName); + CurTokenName = (TCHAR *) malloc((len + 1) * sizeof (TCHAR)); + if (CurTokenName == NULL) { + return; + } + MaxTokenSize = len; + } + + CurTokenType = type; + JPACKAGE_STRNCPY(CurTokenName, len + 1, start, len); + CurTokenName[len] = '\0'; +} + +/* Skip XML comments, doctypes, and prolog tags */ +static TCHAR* SkipFilling(void) { + TCHAR *q = CurPos; + + /* Skip white space and comment sections */ + do { + q = CurPos; + CurPos = SkipWhiteSpace(CurPos); + CurPos = SkipXMLComment(CurPos); /* Must be called befor DocTypes */ + CurPos = SkipXMLDocType(CurPos); /* directives */ + CurPos = SkipXMLProlog(CurPos); /* directives */ + } while (CurPos != q); + + return CurPos; +} + +/* Parses next token and initializes the global token variables above + The tokennizer automatically skips comments () and + directives. + */ +static void GetNextToken(void) { + TCHAR *p, *q; + + /* Skip white space and comment sections */ + p = SkipFilling(); + + if (p == NULL || *p == '\0') { + CurTokenType = TOKEN_EOF; + return; + } else if (p[0] == '<' && p[1] == '/') { + /* TOKEN_END_TAG */ + q = SkipXMLName(p + 2); + SetToken(TOKEN_END_TAG, p + 2, q); + p = q; + } else if (*p == '<') { + /* TOKEN_BEGIN_TAG */ + q = SkipXMLName(p + 1); + SetToken(TOKEN_BEGIN_TAG, p + 1, q); + p = q; + } else if (p[0] == '>') { + CurTokenType = TOKEN_CLOSE_BRACKET; + NEXT_CHAR(p); + } else if (p[0] == '/' && p[1] == '>') { + CurTokenType = TOKEN_EMPTY_CLOSE_BRACKET; + SKIP_CHARS(p, 2); + } else { + /* Search for end of data */ + q = p + 1; + while (*q && *q != '<') { + if (IsPCData(q)) { + q = SkipPCData(q); + } else { + NEXT_CHAR(q); + } + } + SetToken(TOKEN_PCDATA, p, q); + /* Convert all entities inside token */ + ConvertBuiltInEntities(CurTokenName); + p = q; + } + /* Advance pointer to beginning of next token */ + CurPos = p; +} + +static XMLNode* CreateXMLNode(int type, TCHAR* name) { + XMLNode* node; + node = (XMLNode*) malloc(sizeof (XMLNode)); + if (node == NULL) { + return NULL; + } + node->_type = type; + node->_name = name; + node->_next = NULL; + node->_sub = NULL; + node->_attributes = NULL; + return node; +} + +static XMLAttribute* CreateXMLAttribute(TCHAR *name, TCHAR* value) { + XMLAttribute* attr; + attr = (XMLAttribute*) malloc(sizeof (XMLAttribute)); + if (attr == NULL) { + return NULL; + } + attr->_name = name; + attr->_value = value; + attr->_next = NULL; + return attr; +} + +XMLNode* ParseXMLDocument(TCHAR* buf) { + XMLNode* root; + int err_code = setjmp(jmpbuf); + switch (err_code) { + case JMP_NO_ERROR: +#ifndef _UNICODE + /* Remove UTF-8 encoding from buffer */ + RemoveNonAsciiUTF8FromBuffer(buf); +#endif + + /* Get first Token */ + CurPos = buf; + GetNextToken(); + + /* Parse document*/ + root = ParseXMLElement(); + break; + case JMP_OUT_OF_RANGE: + /* cleanup: */ + if (root_node != NULL) { + FreeXMLDocument(root_node); + root_node = NULL; + } + if (CurTokenName != NULL) free(CurTokenName); + fprintf(stderr, "Error during parsing jnlp file...\n"); + exit(-1); + break; + default: + root = NULL; + break; + } + + return root; +} + +static XMLNode* ParseXMLElement(void) { + XMLNode* node = NULL; + XMLNode* subnode = NULL; + XMLNode* nextnode = NULL; + XMLAttribute* attr = NULL; + + if (CurTokenType == TOKEN_BEGIN_TAG) { + + /* Create node for new element tag */ + node = CreateXMLNode(xmlTagType, JPACKAGE_STRDUP(CurTokenName)); + /* We need to save root node pointer to be able to cleanup + if an error happens during parsing */ + if (!root_node) { + root_node = node; + } + /* Parse attributes. This section eats a all input until + EOF, a > or a /> */ + attr = ParseXMLAttribute(); + while (attr != NULL) { + attr->_next = node->_attributes; + node->_attributes = attr; + attr = ParseXMLAttribute(); + } + + /* This will eihter be a TOKEN_EOF, TOKEN_CLOSE_BRACKET, or a + * TOKEN_EMPTY_CLOSE_BRACKET */ + GetNextToken(); + + /* Skip until '>', '/>' or EOF. This should really be an error, */ + /* but we are loose */ + // if(CurTokenType == TOKEN_EMPTY_CLOSE_BRACKET || + // CurTokenType == TOKEN_CLOSE_BRACKET || + // CurTokenType == TOKEN_EOF) { + // println("XML Parsing error: wrong kind of token found"); + // return NULL; + // } + + if (CurTokenType == TOKEN_EMPTY_CLOSE_BRACKET) { + GetNextToken(); + /* We are done with the sublevel - fall through to continue */ + /* parsing tags at the same level */ + } else if (CurTokenType == TOKEN_CLOSE_BRACKET) { + GetNextToken(); + + /* Parse until end tag if found */ + node->_sub = ParseXMLElement(); + + if (CurTokenType == TOKEN_END_TAG) { + /* Find closing bracket '>' for end tag */ + do { + GetNextToken(); + } while (CurTokenType != TOKEN_EOF && + CurTokenType != TOKEN_CLOSE_BRACKET); + GetNextToken(); + } + } + + /* Continue parsing rest on same level */ + if (CurTokenType != TOKEN_EOF) { + /* Parse rest of stream at same level */ + node->_next = ParseXMLElement(); + } + return node; + + } else if (CurTokenType == TOKEN_PCDATA) { + /* Create node for pcdata */ + node = CreateXMLNode(xmlPCDataType, JPACKAGE_STRDUP(CurTokenName)); + /* We need to save root node pointer to be able to cleanup + if an error happens during parsing */ + if (!root_node) { + root_node = node; + } + GetNextToken(); + return node; + } + + /* Something went wrong. */ + return NULL; +} + +/* Parses an XML attribute. */ +static XMLAttribute* ParseXMLAttribute(void) { + TCHAR* q = NULL; + TCHAR* name = NULL; + TCHAR* PrevPos = NULL; + + do { + /* We need to check this condition to avoid endless loop + in case if an error happend during parsing. */ + if (PrevPos == CurPos) { + if (name != NULL) { + free(name); + name = NULL; + } + + return NULL; + } + + PrevPos = CurPos; + + /* Skip whitespace etc. */ + SkipFilling(); + + /* Check if we are done witht this attribute section */ + if (CurPos[0] == '\0' || + CurPos[0] == '>' || + (CurPos[0] == '/' && CurPos[1] == '>')) { + + if (name != NULL) { + free(name); + name = NULL; + } + + return NULL; + } + + /* Find end of name */ + q = CurPos; + while (*q && !iswspace(*q) && *q != '=') NEXT_CHAR(q); + + SetToken(TOKEN_UNKNOWN, CurPos, q); + if (name) { + free(name); + name = NULL; + } + name = JPACKAGE_STRDUP(CurTokenName); + + /* Skip any whitespace */ + CurPos = q; + CurPos = SkipFilling(); + + /* Next TCHARacter must be '=' for a valid attribute. + If it is not, this is really an error. + We ignore this, and just try to parse an attribute + out of the rest of the string. + */ + } while (*CurPos != '='); + + NEXT_CHAR(CurPos); + CurPos = SkipWhiteSpace(CurPos); + /* Parse CDATA part of attribute */ + if ((*CurPos == '\"') || (*CurPos == '\'')) { + TCHAR quoteChar = *CurPos; + q = ++CurPos; + while (*q != '\0' && *q != quoteChar) NEXT_CHAR(q); + SetToken(TOKEN_CDATA, CurPos, q); + CurPos = q + 1; + } else { + q = CurPos; + while (*q != '\0' && !iswspace(*q)) NEXT_CHAR(q); + SetToken(TOKEN_CDATA, CurPos, q); + CurPos = q; + } + + //Note: no need to free name and CurTokenName duplicate; they're assigned + // to an XMLAttribute structure in CreateXMLAttribute + + return CreateXMLAttribute(name, JPACKAGE_STRDUP(CurTokenName)); +} + +void FreeXMLDocument(XMLNode* root) { + if (root == NULL) return; + FreeXMLDocument(root->_sub); + FreeXMLDocument(root->_next); + FreeXMLAttribute(root->_attributes); + free(root->_name); + free(root); +} + +static void FreeXMLAttribute(XMLAttribute* attr) { + if (attr == NULL) return; + free(attr->_name); + free(attr->_value); + FreeXMLAttribute(attr->_next); + free(attr); +} + +/* Find element at current level with a given name */ +XMLNode* FindXMLChild(XMLNode* root, const TCHAR* name) { + if (root == NULL) return NULL; + + if (root->_type == xmlTagType && JPACKAGE_STRCMP(root->_name, name) == 0) { + return root; + } + + return FindXMLChild(root->_next, name); +} + +/* Search for an attribute with the given name and returns the contents. Returns NULL if + * attribute is not found + */ +TCHAR* FindXMLAttribute(XMLAttribute* attr, const TCHAR* name) { + if (attr == NULL) return NULL; + if (JPACKAGE_STRCMP(attr->_name, name) == 0) return attr->_value; + return FindXMLAttribute(attr->_next, name); +} + +void PrintXMLDocument(XMLNode* node, int indt) { + if (node == NULL) return; + + if (node->_type == xmlTagType) { + JPACKAGE_PRINTF(_T("\n")); + indent(indt); + JPACKAGE_PRINTF(_T("<%s"), node->_name); + PrintXMLAttributes(node->_attributes); + if (node->_sub == NULL) { + JPACKAGE_PRINTF(_T("/>\n")); + } else { + JPACKAGE_PRINTF(_T(">")); + PrintXMLDocument(node->_sub, indt + 1); + indent(indt); + JPACKAGE_PRINTF(_T(""), node->_name); + } + } else { + JPACKAGE_PRINTF(_T("%s"), node->_name); + } + PrintXMLDocument(node->_next, indt); +} + +static void PrintXMLAttributes(XMLAttribute* attr) { + if (attr == NULL) return; + + JPACKAGE_PRINTF(_T(" %s=\"%s\""), attr->_name, attr->_value); + PrintXMLAttributes(attr->_next); +} + +static void indent(int indt) { + int i; + for (i = 0; i < indt; i++) { + JPACKAGE_PRINTF(_T(" ")); + } +} + +const TCHAR *CDStart = _T(""); + +static TCHAR* SkipPCData(TCHAR *p) { + TCHAR *end = JPACKAGE_STRSTR(p, CDEnd); + if (end != NULL) { + return end + sizeof (CDEnd); + } + return (++p); +} + +static int IsPCData(TCHAR *p) { + const int size = sizeof (CDStart); + return (JPACKAGE_STRNCMP(CDStart, p, size) == 0); +} diff --git a/src/jdk.jpackage/linux/native/libapplauncher/LinuxPlatform.h b/src/jdk.jpackage/linux/native/libapplauncher/LinuxPlatform.h new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/linux/native/libapplauncher/LinuxPlatform.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef LINUXPLATFORM_H +#define LINUXPLATFORM_H + +#include "Platform.h" +#include "PosixPlatform.h" +#include +#include +#include +#include + +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 void SetCurrentDirectory(TString Value); + 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 diff --git a/src/jdk.jpackage/linux/native/libapplauncher/PlatformDefs.h b/src/jdk.jpackage/linux/native/libapplauncher/PlatformDefs.h new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/linux/native/libapplauncher/PlatformDefs.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 +#include +#include +#include +#include +#include + +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 diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppBundler.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppBundler.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppBundler.java @@ -0,0 +1,382 @@ +/* + * 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.jpackage.internal; + +import java.io.File; +import java.io.IOException; +import java.math.BigInteger; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.ResourceBundle; + +import static jdk.jpackage.internal.StandardBundlerParam.*; +import static jdk.jpackage.internal.MacBaseInstallerBundler.*; +import jdk.jpackage.internal.AbstractAppImageBuilder; + +public class MacAppBundler extends AbstractImageBundler { + + private static final ResourceBundle I18N = ResourceBundle.getBundle( + "jdk.jpackage.internal.resources.MacResources"); + + private static final String TEMPLATE_BUNDLE_ICON = "GenericApp.icns"; + + public static Map getMacCategories() { + Map map = new HashMap<>(); + map.put("Business", "public.app-category.business"); + map.put("Developer Tools", "public.app-category.developer-tools"); + map.put("Education", "public.app-category.education"); + map.put("Entertainment", "public.app-category.entertainment"); + map.put("Finance", "public.app-category.finance"); + map.put("Games", "public.app-category.games"); + map.put("Graphics & Design", "public.app-category.graphics-design"); + map.put("Healthcare & Fitness", + "public.app-category.healthcare-fitness"); + map.put("Lifestyle", "public.app-category.lifestyle"); + map.put("Medical", "public.app-category.medical"); + map.put("Music", "public.app-category.music"); + map.put("News", "public.app-category.news"); + map.put("Photography", "public.app-category.photography"); + map.put("Productivity", "public.app-category.productivity"); + map.put("Reference", "public.app-category.reference"); + map.put("Social Networking", "public.app-category.social-networking"); + map.put("Sports", "public.app-category.sports"); + map.put("Travel", "public.app-category.travel"); + map.put("Utilities", "public.app-category.utilities"); + map.put("Video", "public.app-category.video"); + map.put("Weather", "public.app-category.weather"); + + map.put("Action Games", "public.app-category.action-games"); + map.put("Adventure Games", "public.app-category.adventure-games"); + map.put("Arcade Games", "public.app-category.arcade-games"); + map.put("Board Games", "public.app-category.board-games"); + map.put("Card Games", "public.app-category.card-games"); + map.put("Casino Games", "public.app-category.casino-games"); + map.put("Dice Games", "public.app-category.dice-games"); + map.put("Educational Games", "public.app-category.educational-games"); + map.put("Family Games", "public.app-category.family-games"); + map.put("Kids Games", "public.app-category.kids-games"); + map.put("Music Games", "public.app-category.music-games"); + map.put("Puzzle Games", "public.app-category.puzzle-games"); + map.put("Racing Games", "public.app-category.racing-games"); + map.put("Role Playing Games", "public.app-category.role-playing-games"); + map.put("Simulation Games", "public.app-category.simulation-games"); + map.put("Sports Games", "public.app-category.sports-games"); + map.put("Strategy Games", "public.app-category.strategy-games"); + map.put("Trivia Games", "public.app-category.trivia-games"); + map.put("Word Games", "public.app-category.word-games"); + + return map; + } + + public static final EnumeratedBundlerParam MAC_CATEGORY = + new EnumeratedBundlerParam<>( + Arguments.CLIOptions.MAC_APP_STORE_CATEGORY.getId(), + String.class, + params -> "Unknown", + (s, p) -> s, + getMacCategories(), + false //strict - for MacStoreBundler this should be strict + ); + + public static final BundlerParamInfo MAC_CF_BUNDLE_NAME = + new StandardBundlerParam<>( + Arguments.CLIOptions.MAC_BUNDLE_NAME.getId(), + String.class, + params -> null, + (s, p) -> s); + + public static final BundlerParamInfo MAC_CF_BUNDLE_IDENTIFIER = + new StandardBundlerParam<>( + Arguments.CLIOptions.MAC_BUNDLE_IDENTIFIER.getId(), + String.class, + IDENTIFIER::fetchFrom, + (s, p) -> s); + + public static final BundlerParamInfo 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 DEFAULT_ICNS_ICON = + new StandardBundlerParam<>( + ".mac.default.icns", + String.class, + params -> TEMPLATE_BUNDLE_ICON, + (s, p) -> s); + + public static final BundlerParamInfo 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, + VERBOSE.fetchFrom(params)); + + if (!certificate.isValid()) { + Log.error(MessageFormat.format(I18N.getString( + "error.certificate.expired"), result)); + } + } + + return result; + }, + (s, p) -> s); + + public static final BundlerParamInfo 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 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 params) + throws UnsupportedPlatformException, 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 p) + throws UnsupportedPlatformException, ConfigException { + if (Platform.getPlatform() != Platform.MAC) { + throw new UnsupportedPlatformException(); + } + + imageBundleValidation(p); + + if (StandardBundlerParam.getPredefinedAppImage(p) != null) { + return true; + } + + // validate short version + if (!validCFBundleVersion(MAC_CF_BUNDLE_VERSION.fetchFrom(p))) { + 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(p)).orElse(Boolean.FALSE)) { + String signingIdentity = DEVELOPER_ID_APP_SIGNING_KEY.fetchFrom(p); + if (signingIdentity == null) { + throw new ConfigException( + I18N.getString("error.explicit-sign-no-cert"), + I18N.getString("error.explicit-sign-no-cert.advice")); + } + } + + return true; + } + + File doBundle(Map p, File outputDirectory, + boolean dependentTask) throws PackagerException { + if (StandardBundlerParam.isRuntimeInstaller(p)) { + return PREDEFINED_RUNTIME_IMAGE.fetchFrom(p); + } else { + return doAppBundle(p, outputDirectory, dependentTask); + } + } + + File doAppBundle(Map p, File outputDirectory, + boolean dependentTask) throws PackagerException { + try { + File rootDirectory = createRoot(p, outputDirectory, dependentTask, + APP_NAME.fetchFrom(p) + ".app"); + AbstractAppImageBuilder appBuilder = + new MacAppImageBuilder(p, outputDirectory.toPath()); + if (PREDEFINED_RUNTIME_IMAGE.fetchFrom(p) == null ) { + JLinkBundlerHelper.execute(p, appBuilder); + } else { + StandardBundlerParam.copyPredefinedRuntimeImage(p, 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 getDescription() { + return I18N.getString("app.bundler.description"); + } + + @Override + public String getID() { + return "mac.app"; + } + + @Override + public String getBundleType() { + return "IMAGE"; + } + + @Override + public Collection> getBundleParameters() { + return getAppBundleParameters(); + } + + public static Collection> getAppBundleParameters() { + return Arrays.asList( + APP_NAME, + APP_RESOURCES, + ARGUMENTS, + BUNDLE_ID_SIGNING_PREFIX, + CLASSPATH, + DEVELOPER_ID_APP_SIGNING_KEY, + ICON_ICNS, + JAVA_OPTIONS, + MAC_CATEGORY, + MAC_CF_BUNDLE_IDENTIFIER, + MAC_CF_BUNDLE_NAME, + MAC_CF_BUNDLE_VERSION, + MAIN_CLASS, + MAIN_JAR, + SIGNING_KEYCHAIN, + VERSION, + VERBOSE + ); + } + + + @Override + public File execute(Map params, + File outputParentDir) throws PackagerException { + return doBundle(params, outputParentDir, false); + } + + @Override + public boolean supported(boolean runtimeInstaller) { + return Platform.getPlatform() == Platform.MAC; + } + +} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppImageBuilder.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppImageBuilder.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppImageBuilder.java @@ -0,0 +1,957 @@ +/* + * 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.jpackage.internal; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.UncheckedIOException; +import java.io.Writer; +import java.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 static jdk.jpackage.internal.StandardBundlerParam.*; +import static jdk.jpackage.internal.MacBaseInstallerBundler.*; +import static jdk.jpackage.internal.MacAppBundler.*; + +public class MacAppImageBuilder extends AbstractAppImageBuilder { + + private static final ResourceBundle I18N = ResourceBundle.getBundle( + "jdk.jpackage.internal.resources.MacResources"); + + private static final String LIBRARY_NAME = "libapplauncher.dylib"; + private static final String TEMPLATE_BUNDLE_ICON = "GenericApp.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 javaDir; + 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 final Map params; + + private static List keyChains; + + public static final BundlerParamInfo + 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 EnumeratedBundlerParam MAC_CATEGORY = + new EnumeratedBundlerParam<>( + Arguments.CLIOptions.MAC_APP_STORE_CATEGORY.getId(), + String.class, + params -> "Unknown", + (s, p) -> s, + MacAppBundler.getMacCategories(), + false //strict - for MacStoreBundler this should be strict + ); + + public static final BundlerParamInfo MAC_CF_BUNDLE_NAME = + new StandardBundlerParam<>( + "mac.CFBundleName", + String.class, + params -> null, + (s, p) -> s); + + public static final BundlerParamInfo MAC_CF_BUNDLE_IDENTIFIER = + new StandardBundlerParam<>( + Arguments.CLIOptions.MAC_BUNDLE_IDENTIFIER.getId(), + String.class, + IDENTIFIER::fetchFrom, + (s, p) -> s); + + public static final BundlerParamInfo 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 DEFAULT_ICNS_ICON = + new StandardBundlerParam<>( + ".mac.default.icns", + String.class, + params -> TEMPLATE_BUNDLE_ICON, + (s, p) -> s); + + public static final BundlerParamInfo 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 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 config, Path imageOutDir) + throws IOException { + super(config, imageOutDir.resolve(APP_NAME.fetchFrom(config) + + ".app/Contents/runtime/Contents/Home")); + + Objects.requireNonNull(imageOutDir); + + this.params = config; + this.root = imageOutDir.resolve(APP_NAME.fetchFrom(params) + ".app"); + this.contentsDir = root.resolve("Contents"); + this.javaDir = contentsDir.resolve("Java"); + this.javaModsDir = javaDir.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(javaDir); + Files.createDirectories(resourcesDir); + Files.createDirectories(macOSDir); + Files.createDirectories(runtimeDir); + } + + public MacAppImageBuilder(Map config, String jreName, + Path imageOutDir) throws IOException { + super(null, imageOutDir.resolve(jreName + "/Contents/Home")); + + Objects.requireNonNull(imageOutDir); + + this.params = config; + this.root = imageOutDir.resolve(jreName ); + this.contentsDir = root.resolve("Contents"); + this.javaDir = null; + this.javaModsDir = null; + this.resourcesDir = null; + this.macOSDir = null; + this.runtimeDir = this.root; + this.runtimeRoot = runtimeDir.resolve("Contents/Home"); + this.mdir = runtimeRoot.resolve("lib"); + + Files.createDirectories(runtimeDir); + } + + private void writeEntry(InputStream in, Path dstFile) throws IOException { + Files.createDirectories(dstFile.getParent()); + Files.copy(in, dstFile); + } + + // chmod ugo+x file + private void setExecutable(Path file) { + try { + Set perms = + Files.getPosixFilePermissions(file); + perms.add(PosixFilePermission.OWNER_EXECUTE); + perms.add(PosixFilePermission.GROUP_EXECUTE); + perms.add(PosixFilePermission.OTHERS_EXECUTE); + Files.setPosixFilePermissions(file, perms); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + } + + private static void createUtf8File(File file, String content) + throws IOException { + try (OutputStream fout = new FileOutputStream(file); + Writer output = new OutputStreamWriter(fout, "UTF-8")) { + output.write(content); + } + } + + 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 javaDir; + } + + @Override + public Path getAppModsDir() { + return javaModsDir; + } + + @Override + public void prepareApplicationFiles() throws IOException { + Map 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, "$APPDIR/runtime"); + + // create additional app launcher(s) and config file(s) + List> entryPoints = + StandardBundlerParam.ADD_LAUNCHERS.fetchFrom(params); + for (Map entryPoint : entryPoints) { + Map 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, "$APPDIR/runtime"); + } + + // Copy class path entries to Java folder + copyClassPathEntries(javaDir); + + /*********** Take care of "config" files *******/ + File icon = ICON_ICNS.fetchFrom(params); + + InputStream in = locateResource( + APP_NAME.fetchFrom(params) + ".icns", + "icon", + DEFAULT_ICNS_ICON.fetchFrom(params), + icon, + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + Files.copy(in, + resourcesDir.resolve(APP_NAME.fetchFrom(params) + ".icns"), + StandardCopyOption.REPLACE_EXISTING); + + // copy file association icons + for (Map 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(); + sign(); + } + + @Override + public void prepareJreFiles() throws IOException { + copyRuntimeFiles(); + sign(); + } + + private void copyRuntimeFiles() throws IOException { + // Generate Info.plist + writeInfoPlist(contentsDir.resolve("Info.plist").toFile()); + + // generate java runtime info.plist + writeRuntimeInfoPlist( + runtimeDir.resolve("Contents/Info.plist").toFile()); + + // 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() 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 params) { + if (APP_NAME.fetchFrom(params) != null) { + return APP_NAME.fetchFrom(params); + } else { + return MAIN_CLASS.fetchFrom(params); + } + } + + public static String getLauncherCfgName(Map p) { + return "Contents/Java/" + APP_NAME.fetchFrom(p) + ".cfg"; + } + + private void copyClassPathEntries(Path javaDirectory) throws IOException { + List 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 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) throws IOException { + Map 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)); + + Writer w = new BufferedWriter(new FileWriter(file)); + w.write(preprocessTextResource("Runtime-Info.plist", + I18N.getString("resource.runtime-info-plist"), + TEMPLATE_RUNTIME_INFO_PLIST, + data, + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params))); + w.close(); + } + + private void writeInfoPlist(File file) 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 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_JAVA_RUNTIME_NAME", "$APPDIR/runtime"); + 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"); + data.put("DEPLOY_BUNDLE_CATEGORY", MAC_CATEGORY.fetchFrom(params)); + + 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 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( + " ").append(o).append(""); + newline = "\n"; + } + + data.put("DEPLOY_JAVA_OPTIONS", sb.toString()); + + sb = new StringBuilder(); + List args = ARGUMENTS.fetchFrom(params); + newline = ""; + // So we don't add unneccessary extra line after last append + + for (String o : args) { + sb.append(newline).append(" ").append(o).append( + ""); + newline = "\n"; + } + data.put("DEPLOY_ARGUMENTS", sb.toString()); + + newline = ""; + + data.put("DEPLOY_LAUNCHER_CLASS", MAIN_CLASS.fetchFrom(params)); + + StringBuilder macroedPath = new StringBuilder(); + for (String s : CLASSPATH.fetchFrom(params).split("[ ;:]+")) { + macroedPath.append(s); + macroedPath.append(":"); + } + macroedPath.deleteCharAt(macroedPath.length() - 1); + + data.put("DEPLOY_APP_CLASSPATH", macroedPath.toString()); + + StringBuilder bundleDocumentTypes = new StringBuilder(); + StringBuilder exportedTypes = new StringBuilder(); + for (Map + fileAssociation : FILE_ASSOCIATIONS.fetchFrom(params)) { + + List extensions = FA_EXTENSIONS.fetchFrom(fileAssociation); + + if (extensions == null) { + Log.verbose(I18N.getString( + "message.creating-association-with-null-extension")); + } + + List 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); //TODO FA_ICON_ICNS + + bundleDocumentTypes.append(" \n") + .append(" LSItemContentTypes\n") + .append(" \n") + .append(" ") + .append(itemContentType) + .append("\n") + .append(" \n") + .append("\n") + .append(" CFBundleTypeName\n") + .append(" ") + .append(description) + .append("\n") + .append("\n") + .append(" LSHandlerRank\n") + .append(" Owner\n") + // TODO make a bundler arg + .append("\n") + .append(" CFBundleTypeRole\n") + .append(" Editor\n") + // TODO make a bundler arg + .append("\n") + .append(" LSIsAppleDefaultForType\n") + .append(" \n") + // TODO make a bundler arg + .append("\n"); + + if (icon != null && icon.exists()) { + bundleDocumentTypes + .append(" CFBundleTypeIconFile\n") + .append(" ") + .append(icon.getName()) + .append("\n"); + } + bundleDocumentTypes.append(" \n"); + + exportedTypes.append(" \n") + .append(" UTTypeIdentifier\n") + .append(" ") + .append(itemContentType) + .append("\n") + .append("\n") + .append(" UTTypeDescription\n") + .append(" ") + .append(description) + .append("\n") + .append(" UTTypeConformsTo\n") + .append(" \n") + .append(" public.data\n") + //TODO expose this? + .append(" \n") + .append("\n"); + + if (icon != null && icon.exists()) { + exportedTypes.append(" UTTypeIconFile\n") + .append(" ") + .append(icon.getName()) + .append("\n") + .append("\n"); + } + + exportedTypes.append("\n") + .append(" UTTypeTagSpecification\n") + .append(" \n") + // TODO expose via param? .append( + // " com.apple.ostype\n"); + // TODO expose via param? .append( + // " ABCD\n") + .append("\n"); + + if (extensions != null && !extensions.isEmpty()) { + exportedTypes.append( + " public.filename-extension\n") + .append(" \n"); + + for (String ext : extensions) { + exportedTypes.append(" ") + .append(ext) + .append("\n"); + } + exportedTypes.append(" \n"); + } + if (mimeTypes != null && !mimeTypes.isEmpty()) { + exportedTypes.append(" public.mime-type\n") + .append(" \n"); + + for (String mime : mimeTypes) { + exportedTypes.append(" ") + .append(mime) + .append("\n"); + } + exportedTypes.append(" \n"); + } + exportedTypes.append(" \n") + .append(" \n"); + } + String associationData; + if (bundleDocumentTypes.length() > 0) { + associationData = + "\n CFBundleDocumentTypes\n \n" + + bundleDocumentTypes.toString() + + " \n\n" + + " UTExportedTypeDeclarations\n \n" + + exportedTypes.toString() + + " \n"; + } else { + associationData = ""; + } + data.put("DEPLOY_FILE_ASSOCIATIONS", associationData); + + + Writer w = new BufferedWriter(new FileWriter(file)); + w.write(preprocessTextResource( + // getConfig_InfoPlist(params).getName(), + "Info.plist", + I18N.getString("resource.app-info-plist"), + TEMPLATE_INFO_PLIST_LITE, + data, VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params))); + w.close(); + } + + private void writePkgInfo(File file) throws IOException { + //hardcoded as it does not seem we need to change it ever + String signature = "????"; + + try (Writer out = new BufferedWriter(new FileWriter(file))) { + out.write(OS_TYPE_CODE + signature); + out.flush(); + } + } + + public static void addNewKeychain(Map 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 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 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, false); + } + + public static void restoreKeychainList(Map 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 args = new ArrayList<>(); + args.add("security"); + args.add("list-keychains"); + args.add("-s"); + + args.addAll(keyChains); + + ProcessBuilder pb = new ProcessBuilder(args); + IOUtils.exec(pb, false); + } + + public static void signAppBundle( + Map params, Path appLocation, + String signingIdentity, String identifierPrefix, + String entitlementsFile, String inheritedEntitlements) + throws IOException { + AtomicReference toThrow = new AtomicReference<>(); + String appExecutable = "/Contents/MacOS/" + APP_NAME.fetchFrom(params); + String keyChain = SIGNING_KEYCHAIN.fetchFrom(params); + + // sign all dylibs and jars + Files.walk(appLocation) + // fix permissions + .peek(path -> { + try { + Set 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.debug(e); + } + }) + .filter(p -> Files.isRegularFile(p) && + !(p.toString().contains("/Contents/MacOS/libjli.dylib") + || p.toString().contains( + "/Contents/MacOS/JavaAppletPlugin") + || p.toString().endsWith(appExecutable)) + ).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 { + List 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 oldPermissions = + Files.getPosixFilePermissions(p); + File f = p.toFile(); + f.setWritable(true, true); + + ProcessBuilder pb = new ProcessBuilder(args); + IOUtils.exec(pb, false); + + 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 signIdentifiedByPList = path -> { + //noinspection ThrowableResultOfMethodCallIgnored + if (toThrow.get() != null) return; + + try { + List 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, false); + + 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, false); + } catch (IOException e) { + toThrow.set(e); + } + }; + + Path javaPath = appLocation.resolve("Contents/runtime"); + if (Files.isDirectory(javaPath)) { + Files.list(javaPath) + .forEach(signIdentifiedByPList); + + 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 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, false); + } + +} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppStoreBundler.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppStoreBundler.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppStoreBundler.java @@ -0,0 +1,368 @@ +/* + * 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.jpackage.internal; + +import java.io.File; +import java.io.IOException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.ResourceBundle; + +import static jdk.jpackage.internal.StandardBundlerParam.*; +import static jdk.jpackage.internal.MacAppBundler.*; + +public class MacAppStoreBundler extends MacBaseInstallerBundler { + + private static final ResourceBundle I18N = ResourceBundle.getBundle( + "jdk.jpackage.internal.resources.MacResources"); + + private static final String TEMPLATE_BUNDLE_ICON_HIDPI = + "GenericAppHiDPI.icns"; + private final static String DEFAULT_ENTITLEMENTS = + "MacAppStore.entitlements"; + private final static String DEFAULT_INHERIT_ENTITLEMENTS = + "MacAppStore_Inherit.entitlements"; + + public static final BundlerParamInfo 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, + VERBOSE.fetchFrom(params)); + + if (!certificate.isValid()) { + Log.error(MessageFormat.format( + I18N.getString("error.certificate.expired"), + result)); + } + } + + return result; + }, + (s, p) -> s); + + public static final BundlerParamInfo 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, VERBOSE.fetchFrom(params)); + + if (!certificate.isValid()) { + Log.error(MessageFormat.format( + I18N.getString("error.certificate.expired"), + result)); + } + } + + return result; + }, + (s, p) -> s); + + public static final StandardBundlerParam 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 INSTALLER_SUFFIX = + new StandardBundlerParam<> ( + "mac.app-store.installerName.suffix", + String.class, + params -> "-MacAppStore", + (s, p) -> s); + + //@Override + public File bundle(Map p, + File outdir) throws PackagerException { + Log.verbose(MessageFormat.format(I18N.getString( + "message.building-bundle"), APP_NAME.fetchFrom(p))); + if (!outdir.isDirectory() && !outdir.mkdirs()) { + throw new PackagerException( + "error.cannot-create-output-dir", + outdir.getAbsolutePath()); + } + if (!outdir.canWrite()) { + throw new PackagerException( + "error.cannot-write-to-output-dir", + outdir.getAbsolutePath()); + } + + // first, load in some overrides + // icns needs @2 versions, so load in the @2 default + p.put(DEFAULT_ICNS_ICON.getID(), TEMPLATE_BUNDLE_ICON_HIDPI); + + // now we create the app + File appImageDir = APP_IMAGE_TEMP_ROOT.fetchFrom(p); + try { + appImageDir.mkdirs(); + + try { + MacAppImageBuilder.addNewKeychain(p); + } catch (InterruptedException e) { + Log.error(e.getMessage()); + } + // first, make sure we don't use the local signing key + p.put(DEVELOPER_ID_APP_SIGNING_KEY.getID(), null); + File appLocation = prepareAppBundle(p, false); + + prepareEntitlements(p); + + String signingIdentity = MAC_APP_STORE_APP_SIGNING_KEY.fetchFrom(p); + String identifierPrefix = BUNDLE_ID_SIGNING_PREFIX.fetchFrom(p); + String entitlementsFile = getConfig_Entitlements(p).toString(); + String inheritEntitlements = + getConfig_Inherit_Entitlements(p).toString(); + + MacAppImageBuilder.signAppBundle(p, appLocation.toPath(), + signingIdentity, identifierPrefix, + entitlementsFile, inheritEntitlements); + MacAppImageBuilder.restoreKeychainList(p); + + ProcessBuilder pb; + + // create the final pkg file + File finalPKG = new File(outdir, INSTALLER_NAME.fetchFrom(p) + + INSTALLER_SUFFIX.fetchFrom(p) + + ".pkg"); + outdir.mkdirs(); + + String installIdentify = + MAC_APP_STORE_PKG_SIGNING_KEY.fetchFrom(p); + + List 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(p); + if (keychainName != null && !keychainName.isEmpty()) { + buildOptions.add("--keychain"); + buildOptions.add(keychainName); + } + buildOptions.add(finalPKG.getAbsolutePath()); + + pb = new ProcessBuilder(buildOptions); + + IOUtils.exec(pb, false); + return finalPKG; + } catch (PackagerException pe) { + throw pe; + } catch (Exception ex) { + Log.verbose(ex); + throw new PackagerException(ex); + } + } + + private File getConfig_Entitlements(Map params) { + return new File(CONFIG_ROOT.fetchFrom(params), + APP_NAME.fetchFrom(params) + ".entitlements"); + } + + private File getConfig_Inherit_Entitlements( + Map params) { + return new File(CONFIG_ROOT.fetchFrom(params), + APP_NAME.fetchFrom(params) + "_Inherit.entitlements"); + } + + private void prepareEntitlements(Map params) + throws IOException { + File entitlements = MAC_APP_STORE_ENTITLEMENTS.fetchFrom(params); + if (entitlements == null || !entitlements.exists()) { + fetchResource(getEntitlementsFileName(params), + I18N.getString("resource.mac-app-store-entitlements"), + DEFAULT_ENTITLEMENTS, + getConfig_Entitlements(params), + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + } else { + fetchResource(getEntitlementsFileName(params), + I18N.getString("resource.mac-app-store-entitlements"), + entitlements, + getConfig_Entitlements(params), + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + } + fetchResource(getInheritEntitlementsFileName(params), + I18N.getString("resource.mac-app-store-inherit-entitlements"), + DEFAULT_INHERIT_ENTITLEMENTS, + getConfig_Inherit_Entitlements(params), + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + } + + private String getEntitlementsFileName(Map params) { + return APP_NAME.fetchFrom(params) + ".entitlements"; + } + + private String getInheritEntitlementsFileName( + Map params) { + return APP_NAME.fetchFrom(params) + "_Inherit.entitlements"; + } + + + /////////////////////////////////////////////////////////////////////// + // Implement Bundler + /////////////////////////////////////////////////////////////////////// + + @Override + public String getName() { + return I18N.getString("store.bundler.name"); + } + + @Override + public String getDescription() { + return I18N.getString("store.bundler.description"); + } + + @Override + public String getID() { + return "mac.appStore"; + } + + @Override + public Collection> getBundleParameters() { + Collection> results = new LinkedHashSet<>(); + results.addAll(getAppBundleParameters()); + results.addAll(getMacAppStoreBundleParameters()); + return results; + } + + public Collection> getMacAppStoreBundleParameters() { + Collection> results = new LinkedHashSet<>(); + + results.addAll(getAppBundleParameters()); + results.remove(DEVELOPER_ID_APP_SIGNING_KEY); + results.addAll(Arrays.asList( + INSTALLER_SUFFIX, + MAC_APP_STORE_APP_SIGNING_KEY, + MAC_APP_STORE_ENTITLEMENTS, + MAC_APP_STORE_PKG_SIGNING_KEY, + SIGNING_KEYCHAIN + )); + + return results; + } + + @Override + public boolean validate(Map params) + throws UnsupportedPlatformException, ConfigException { + try { + if (Platform.getPlatform() != Platform.MAC) { + throw new UnsupportedPlatformException(); + } + + if (params == null) { + throw new ConfigException( + I18N.getString("error.parameters-null"), + I18N.getString("error.parameters-null.advice")); + } + + // hdiutil is always available so there's no need to test for + // availability. + // run basic validation to ensure requirements are met + + // TODO Mac App Store apps cannot use the system runtime + + // 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 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 + } +} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBaseInstallerBundler.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBaseInstallerBundler.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBaseInstallerBundler.java @@ -0,0 +1,212 @@ +/* + * 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.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.Arrays; +import java.util.Collection; +import java.util.LinkedHashSet; +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.jpackage.internal.StandardBundlerParam.*; + +public abstract class MacBaseInstallerBundler extends AbstractBundler { + + private static final ResourceBundle I18N = ResourceBundle.getBundle( + "jdk.jpackage.internal.resources.MacResources"); + + // This could be generalized more to be for any type of Image Bundler + public static final BundlerParamInfo APP_BUNDLER = + new StandardBundlerParam<>( + "mac.app.bundler", + MacAppBundler.class, + params -> new MacAppBundler(), + (s, p) -> null); + + public final BundlerParamInfo 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 SIGNING_KEY_USER = + new StandardBundlerParam<>( + Arguments.CLIOptions.MAC_SIGNING_KEY_NAME.getId(), + String.class, + params -> "", + null); + + public static final BundlerParamInfo SIGNING_KEYCHAIN = + new StandardBundlerParam<>( + Arguments.CLIOptions.MAC_SIGNING_KEYCHAIN.getId(), + String.class, + params -> "", + null); + + public static final BundlerParamInfo 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 params) + throws ConfigException, UnsupportedPlatformException { + 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")); + } + if (IDENTIFIER.fetchFrom(params) == null) { + throw new ConfigException( + I18N.getString("message.app-image-requires-identifier"), + I18N.getString( + "message.app-image-requires-identifier.advice")); + } + } else { + APP_BUNDLER.fetchFrom(params).validate(params); + } + } + + protected File prepareAppBundle(Map p, + boolean pkg) throws PackagerException { + File predefinedImage = StandardBundlerParam.getPredefinedAppImage(p); + if (predefinedImage != null) { + return predefinedImage; + } + File appImageRoot = APP_IMAGE_TEMP_ROOT.fetchFrom(p); + if (pkg) { + // create pkg in dmg + return new MacPkgBundler().bundle(p, appImageRoot); + } else { + return APP_BUNDLER.fetchFrom(p).doBundle(p, appImageRoot, true); + } + } + + @Override + public Collection> getBundleParameters() { + Collection> results = new LinkedHashSet<>(); + + results.addAll(MacAppBundler.getAppBundleParameters()); + results.addAll(Arrays.asList( + APP_BUNDLER, + CONFIG_ROOT, + APP_IMAGE_TEMP_ROOT, + PREDEFINED_APP_IMAGE + )); + + return results; + } + + @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 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, verbose, false, ps); + Pattern p = Pattern.compile("\"alis\"=\"([^\"]+)\""); + 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.debug("Using key '" + matchedKey + "'"); + return matchedKey; + } catch (IOException ioe) { + Log.verbose(ioe); + return null; + } + } +} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacCertificate.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacCertificate.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacCertificate.java @@ -0,0 +1,163 @@ +/* + * 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.jpackage.internal; + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintStream; +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; + +final class MacCertificate { + private final String certificate; + private final boolean verbose; + + MacCertificate(String certificate) { + this.certificate = certificate; + this.verbose = false; + } + + MacCertificate(String certificate, boolean verbose) { + this.certificate = certificate; + this.verbose = verbose; + } + + boolean isValid() { + return verifyCertificate(this.certificate, verbose); + } + + private static File findCertificate(String certificate, boolean verbose) { + File result = null; + + List 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, verbose, false, ps); + + File output = File.createTempFile("tempfile", ".tmp"); + PrintStream p = new PrintStream( + new BufferedOutputStream( + new FileOutputStream(output, true))); + BufferedReader bfReader = new BufferedReader( + new InputStreamReader( + new ByteArrayInputStream(baos.toByteArray()))); + String line = null; + + while((line = bfReader.readLine()) != null){ + p.println(line); + } + + p.close(); + result = output; + } + catch (IOException ignored) {} + + return result; + } + + private static Date findCertificateDate(String filename, boolean verbose) { + Date result = null; + + List 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, verbose, 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.debug(ex); + } + + return result; + } + + private static boolean verifyCertificate( + String certificate, boolean verbose) { + boolean result = false; + + try { + File file = null; + Date certificateDate = null; + + try { + file = findCertificate(certificate, verbose); + + if (file != null) { + certificateDate = findCertificateDate( + file.getCanonicalPath(), verbose); + } + } + 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; + } +} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgBundler.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgBundler.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgBundler.java @@ -0,0 +1,483 @@ +/* + * 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.jpackage.internal; + +import java.io.*; +import java.nio.file.Files; +import java.text.MessageFormat; +import java.util.*; + +import static jdk.jpackage.internal.StandardBundlerParam.*; + +public class MacDmgBundler extends MacBaseInstallerBundler { + + private static final ResourceBundle I18N = ResourceBundle.getBundle( + "jdk.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 = "GenericApp.icns"; + + static final String DEFAULT_LICENSE_PLIST="lic_template.plist"; + + public static final BundlerParamInfo INSTALLER_SUFFIX = + new StandardBundlerParam<> ( + "mac.dmg.installerName.suffix", + String.class, + params -> "", + (s, p) -> s); + + public File bundle(Map params, + File outdir) throws PackagerException { + Log.verbose(MessageFormat.format(I18N.getString("message.building-dmg"), + APP_NAME.fetchFrom(params))); + if (!outdir.isDirectory() && !outdir.mkdirs()) { + throw new PackagerException( + "error.cannot-create-output-dir", + outdir.getAbsolutePath()); + } + if (!outdir.canWrite()) { + throw new PackagerException( + "error.cannot-write-to-output-dir", + outdir.getAbsolutePath()); + } + + File appImageDir = APP_IMAGE_TEMP_ROOT.fetchFrom(params); + try { + appImageDir.mkdirs(); + + if (prepareAppBundle(params, true) != 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, false); + } + + 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 p) throws IOException { + File dmgSetup = getConfig_VolumeScript(p); + Log.verbose(MessageFormat.format( + I18N.getString("message.preparing-dmg-setup"), + dmgSetup.getAbsolutePath())); + + //prepare config for exe + Map data = new HashMap<>(); + data.put("DEPLOY_ACTUAL_VOLUME_NAME", volumeName); + data.put("DEPLOY_APPLICATION_NAME", APP_NAME.fetchFrom(p)); + + data.put("DEPLOY_INSTALL_LOCATION", "(path to desktop folder)"); + data.put("DEPLOY_INSTALL_NAME", "Desktop"); + + Writer w = new BufferedWriter(new FileWriter(dmgSetup)); + w.write(preprocessTextResource(dmgSetup.getName(), + I18N.getString("resource.dmg-setup-script"), + DEFAULT_DMG_SETUP_SCRIPT, data, VERBOSE.fetchFrom(p), + RESOURCE_DIR.fetchFrom(p))); + w.close(); + } + + private File getConfig_VolumeScript(Map params) { + return new File(CONFIG_ROOT.fetchFrom(params), + APP_NAME.fetchFrom(params) + "-dmg-setup.scpt"); + } + + private File getConfig_VolumeBackground( + Map params) { + return new File(CONFIG_ROOT.fetchFrom(params), + APP_NAME.fetchFrom(params) + "-background.png"); + } + + private File getConfig_VolumeIcon(Map params) { + return new File(CONFIG_ROOT.fetchFrom(params), + APP_NAME.fetchFrom(params) + "-volume.icns"); + } + + private File getConfig_LicenseFile(Map params) { + return new File(CONFIG_ROOT.fetchFrom(params), + APP_NAME.fetchFrom(params) + "-license.plist"); + } + + private void prepareLicense(Map 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 data = new HashMap<>(); + data.put("APPLICATION_LICENSE_TEXT", licenseInBase64); + + Writer w = new BufferedWriter( + new FileWriter(getConfig_LicenseFile(params))); + w.write(preprocessTextResource( + getConfig_LicenseFile(params).getName(), + I18N.getString("resource.license-setup"), + DEFAULT_LICENSE_PLIST, data, VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params))); + w.close(); + + } catch (IOException ex) { + Log.verbose(ex); + } + } + + private boolean prepareConfigFiles(Map params) + throws IOException { + File bgTarget = getConfig_VolumeBackground(params); + fetchResource(bgTarget.getName(), + I18N.getString("resource.dmg-background"), + DEFAULT_BACKGROUND_IMAGE, + bgTarget, + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + + File iconTarget = getConfig_VolumeIcon(params); + if (MacAppBundler.ICON_ICNS.fetchFrom(params) == null || + !MacAppBundler.ICON_ICNS.fetchFrom(params).exists()) { + fetchResource(iconTarget.getName(), + I18N.getString("resource.volume-icon"), + TEMPLATE_BUNDLE_ICON, + iconTarget, + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + } else { + fetchResource(iconTarget.getName(), + I18N.getString("resource.volume-icon"), + MacAppBundler.ICON_ICNS.fetchFrom(params), + iconTarget, + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + } + + + fetchResource(getConfig_Script(params).getName(), + I18N.getString("resource.post-install-script"), + (String) null, + getConfig_Script(params), + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(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 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"}; + + for (String path: typicalPaths) { + File f = new File(path); + if (f.exists() && f.canExecute()) { + return path; + } + } + + // 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 p, File outdir) + throws IOException { + File imagesRoot = IMAGES_ROOT.fetchFrom(p); + if (!imagesRoot.exists()) imagesRoot.mkdirs(); + + File protoDMG = new File(imagesRoot, APP_NAME.fetchFrom(p) +"-tmp.dmg"); + File finalDMG = new File(outdir, INSTALLER_NAME.fetchFrom(p) + + INSTALLER_SUFFIX.fetchFrom(p) + + ".dmg"); + + File srcFolder = APP_IMAGE_TEMP_ROOT.fetchFrom(p); + File predefinedImage = StandardBundlerParam.getPredefinedAppImage(p); + 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 = Log.isDebug() ? "-verbose" : "-quiet"; + + // create temp image + ProcessBuilder pb = new ProcessBuilder( + hdiutil, + "create", + hdiUtilVerbosityFlag, + "-srcfolder", srcFolder.getAbsolutePath(), + "-volname", APP_NAME.fetchFrom(p), + "-ov", protoDMG.getAbsolutePath(), + "-fs", "HFS+", + "-format", "UDRW"); + IOUtils.exec(pb, false); + + // mount temp image + pb = new ProcessBuilder( + hdiutil, + "attach", + protoDMG.getAbsolutePath(), + hdiUtilVerbosityFlag, + "-mountroot", imagesRoot.getAbsolutePath()); + IOUtils.exec(pb, false); + + File mountedRoot = + new File(imagesRoot.getAbsolutePath(), APP_NAME.fetchFrom(p)); + + // volume icon + File volumeIconFile = new File(mountedRoot, ".VolumeIcon.icns"); + IOUtils.copyFile(getConfig_VolumeIcon(p), + volumeIconFile); + + pb = new ProcessBuilder("osascript", + getConfig_VolumeScript(p).getAbsolutePath()); + IOUtils.exec(pb, false); + + // 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 + // http://endrift.com/blog/2010/06/14/dmg-files-volume-icons-cli + // (might not work on Mac 10.13 with old XCode) + pb = new ProcessBuilder( + setFileUtility, + "-c", "icnC", + volumeIconFile.getAbsolutePath()); + IOUtils.exec(pb, false); + volumeIconFile.setReadOnly(); + + pb = new ProcessBuilder( + setFileUtility, + "-a", "C", + mountedRoot.getAbsolutePath()); + IOUtils.exec(pb, false); + } catch (IOException ex) { + Log.error(ex.getMessage()); + Log.verbose("Cannot enable custom icon using SetFile utility"); + } + } else { + Log.verbose( + "Skip enabling custom icon as SetFile utility is not found"); + } + + // Detach the temporary image + pb = new ProcessBuilder( + hdiutil, + "detach", + hdiUtilVerbosityFlag, + mountedRoot.getAbsolutePath()); + IOUtils.exec(pb, false); + + // Compress it to a new image + pb = new ProcessBuilder( + hdiutil, + "convert", + protoDMG.getAbsolutePath(), + hdiUtilVerbosityFlag, + "-format", "UDZO", + "-o", finalDMG.getAbsolutePath()); + IOUtils.exec(pb, false); + + //add license if needed + if (getConfig_LicenseFile(p).exists()) { + //hdiutil unflatten your_image_file.dmg + pb = new ProcessBuilder( + hdiutil, + "unflatten", + finalDMG.getAbsolutePath() + ); + IOUtils.exec(pb, false); + + //add license + pb = new ProcessBuilder( + hdiutil, + "udifrez", + finalDMG.getAbsolutePath(), + "-xml", + getConfig_LicenseFile(p).getAbsolutePath() + ); + IOUtils.exec(pb, false); + + //hdiutil flatten your_image_file.dmg + pb = new ProcessBuilder( + hdiutil, + "flatten", + finalDMG.getAbsolutePath() + ); + IOUtils.exec(pb, false); + + } + + //Delete the temporary image + protoDMG.delete(); + + Log.verbose(MessageFormat.format(I18N.getString( + "message.output-to-location"), + APP_NAME.fetchFrom(p), finalDMG.getAbsolutePath())); + + return finalDMG; + } + + + ////////////////////////////////////////////////////////////////////////// + // Implement Bundler + ////////////////////////////////////////////////////////////////////////// + + @Override + public String getName() { + return I18N.getString("dmg.bundler.name"); + } + + @Override + public String getDescription() { + return I18N.getString("dmg.bundler.description"); + } + + @Override + public String getID() { + return "dmg"; + } + + @Override + public Collection> getBundleParameters() { + Collection> results = new LinkedHashSet<>(); + results.addAll(MacAppBundler.getAppBundleParameters()); + results.addAll(getDMGBundleParameters()); + return results; + } + + public Collection> getDMGBundleParameters() { + Collection> results = new LinkedHashSet<>(); + + results.addAll(MacAppBundler.getAppBundleParameters()); + results.addAll(Arrays.asList( + INSTALLER_SUFFIX, + LICENSE_FILE + )); + + return results; + } + + + @Override + public boolean validate(Map params) + throws UnsupportedPlatformException, ConfigException { + try { + if (params == null) throw new ConfigException( + I18N.getString("error.parameters-null"), + I18N.getString("error.parameters-null.advice")); + + //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 params, + File outputParentDir) throws PackagerException { + return bundle(params, outputParentDir); + } + + @Override + public boolean supported(boolean runtimeInstaller) { + return Platform.getPlatform() == Platform.MAC; + } +} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgBundler.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgBundler.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgBundler.java @@ -0,0 +1,579 @@ +/* + * 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.jpackage.internal; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.Writer; +import java.net.URLEncoder; +import java.nio.file.Files; +import java.nio.file.Path; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.ResourceBundle; + +import static jdk.jpackage.internal.StandardBundlerParam.*; +import static jdk.jpackage.internal.MacBaseInstallerBundler.SIGNING_KEYCHAIN; +import static jdk.jpackage.internal.MacBaseInstallerBundler.SIGNING_KEY_USER; + +public class MacPkgBundler extends MacBaseInstallerBundler { + + private static final ResourceBundle I18N = ResourceBundle.getBundle( + "jdk.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 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 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 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, VERBOSE.fetchFrom(params)); + + if (!certificate.isValid()) { + Log.error(MessageFormat.format( + I18N.getString("error.certificate.expired"), + result)); + } + } + + return result; + }, + (s, p) -> s); + + public static final BundlerParamInfo 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 INSTALLER_SUFFIX = + new StandardBundlerParam<> ( + "mac.pkg.installerName.suffix", + String.class, + params -> "", + (s, p) -> s); + + public File bundle(Map params, + File outdir) throws PackagerException { + Log.verbose(MessageFormat.format(I18N.getString("message.building-pkg"), + APP_NAME.fetchFrom(params))); + if (!outdir.isDirectory() && !outdir.mkdirs()) { + throw new PackagerException( + "error.cannot-create-output-dir", + outdir.getAbsolutePath()); + } + if (!outdir.canWrite()) { + throw new PackagerException( + "error.cannot-write-to-output-dir", + outdir.getAbsolutePath()); + } + + File appImageDir = null; + try { + appImageDir = prepareAppBundle(params, false); + + 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, false); + } + + return createPKG(params, outdir, appImageDir); + } + return null; + } catch (IOException ex) { + Log.verbose(ex); + throw new PackagerException(ex); + } + } + + private File getPackages_AppPackage(Map params) { + return new File(PACKAGES_ROOT.fetchFrom(params), + APP_NAME.fetchFrom(params) + "-app.pkg"); + } + + private File getPackages_DaemonPackage(Map params) { + return new File(PACKAGES_ROOT.fetchFrom(params), + APP_NAME.fetchFrom(params) + "-daemon.pkg"); + } + + private File getConfig_DistributionXMLFile( + Map params) { + return new File(CONFIG_ROOT.fetchFrom(params), "distribution.dist"); + } + + private File getConfig_BackgroundImage(Map params) { + return new File(CONFIG_ROOT.fetchFrom(params), + APP_NAME.fetchFrom(params) + "-background.png"); + } + + private File getScripts_PreinstallFile(Map params) { + return new File(SCRIPTS_DIR.fetchFrom(params), "preinstall"); + } + + private File getScripts_PostinstallFile( + Map params) { + return new File(SCRIPTS_DIR.fetchFrom(params), "postinstall"); + } + + private String getAppIdentifier(Map params) { + return IDENTIFIER.fetchFrom(params); + } + + private String getDaemonIdentifier(Map params) { + return IDENTIFIER.fetchFrom(params) + ".daemon"; + } + + private void preparePackageScripts(Map params) + throws IOException { + Log.verbose(I18N.getString("message.preparing-scripts")); + + Map data = new HashMap<>(); + + data.put("INSTALL_LOCATION", MAC_INSTALL_DIR.fetchFrom(params)); + + Writer w = new BufferedWriter( + new FileWriter(getScripts_PreinstallFile(params))); + String content = preprocessTextResource( + getScripts_PreinstallFile(params).getName(), + I18N.getString("resource.pkg-preinstall-script"), + TEMPLATE_PREINSTALL_SCRIPT, + data, + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + w.write(content); + w.close(); + getScripts_PreinstallFile(params).setExecutable(true, false); + + w = new BufferedWriter( + new FileWriter(getScripts_PostinstallFile(params))); + content = preprocessTextResource( + getScripts_PostinstallFile(params).getName(), + I18N.getString("resource.pkg-postinstall-script"), + TEMPLATE_POSTINSTALL_SCRIPT, + data, + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + w.write(content); + w.close(); + getScripts_PostinstallFile(params).setExecutable(true, false); + } + + private void prepareDistributionXMLFile(Map params) + throws IOException { + File f = getConfig_DistributionXMLFile(params); + + Log.verbose(MessageFormat.format(I18N.getString( + "message.preparing-distribution-dist"), f.getAbsolutePath())); + + PrintStream out = new PrintStream(f); + + out.println( + ""); + out.println(""); + + out.println("" + APP_NAME.fetchFrom(params) + ""); + out.println(""); + + String licFileStr = LICENSE_FILE.fetchFrom(params); + if (licFileStr != null) { + File licFile = new File(licFileStr); + out.println(""); + } + + /* + * Note that the content of the distribution file + * below is generated by productbuild --synthesize + */ + + String appId = getAppIdentifier(params); + + out.println(""); + + out.println(""); + out.println(""); + out.println(" "); + out.println(" "); + out.println(" "); + out.println(""); + out.println(""); + out.println(""); + out.println(" "); + out.println(""); + out.println("" + + URLEncoder.encode(getPackages_AppPackage(params).getName(), + "UTF-8") + ""); + + out.println(""); + + out.close(); + } + + private boolean prepareConfigFiles(Map params) + throws IOException { + File imageTarget = getConfig_BackgroundImage(params); + fetchResource(imageTarget.getName(), + I18N.getString("resource.pkg-background-image"), + DEFAULT_BACKGROUND_IMAGE, + imageTarget, + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + + prepareDistributionXMLFile(params); + + fetchResource(getConfig_Script(params).getName(), + I18N.getString("resource.post-install-script"), + (String) null, + getConfig_Script(params), + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + + return true; + } + + // name of post-image script + private File getConfig_Script(Map 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(new BufferedWriter( + new FileWriter(cpl)))) { + boolean skip = false; // 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("BundleIsRelocatable")) { + out.println(lines[i]); + out.println(""); + i++; + } else if (lines[i].trim().equals("ChildBundles")) { + skip = true; + } else if (skip && lines[i].trim().equals("")) { + skip = false; + } else { + if (!skip) { + 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 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 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, false); + + 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, false); + + // build final package + File finalPKG = new File(outdir, INSTALLER_NAME.fetchFrom(params) + + INSTALLER_SUFFIX.fetchFrom(params) + + ".pkg"); + outdir.mkdirs(); + + List 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, false); + + 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 getDescription() { + return I18N.getString("pkg.bundler.description"); + } + + @Override + public String getID() { + return "pkg"; + } + + @Override + public Collection> getBundleParameters() { + Collection> results = new LinkedHashSet<>(); + results.addAll(MacAppBundler.getAppBundleParameters()); + results.addAll(getPKGBundleParameters()); + return results; + } + + public Collection> getPKGBundleParameters() { + Collection> results = new LinkedHashSet<>(); + + results.addAll(MacAppBundler.getAppBundleParameters()); + results.addAll(Arrays.asList( + DEVELOPER_ID_INSTALLER_SIGNING_KEY, + // IDENTIFIER, + INSTALLER_SUFFIX, + LICENSE_FILE, + // SERVICE_HINT, + SIGNING_KEYCHAIN)); + + return results; + } + + @Override + public boolean validate(Map params) + throws UnsupportedPlatformException, ConfigException { + try { + if (params == null) throw new ConfigException( + I18N.getString("error.parameters-null"), + I18N.getString("error.parameters-null.advice")); + + // run basic validation to ensure requirements are met + // we are not interested in return code, only possible exception + validateAppImageAndBundeler(params); + + // 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 params, + File outputParentDir) throws PackagerException { + return bundle(params, outputParentDir); + } + + @Override + public boolean supported(boolean runtimeInstaller) { + return Platform.getPlatform() == Platform.MAC; + } + +} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/DMGsetup.scpt b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/DMGsetup.scpt new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/DMGsetup.scpt @@ -0,0 +1,15 @@ +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 + + set the bounds of container window to {400, 100, 917, 370} + + set theViewOptions to the icon view options of container window + set arrangement of theViewOptions to not arranged + set icon size of theViewOptions to 128 + end tell +end tell + diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/GenericApp.icns b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/GenericApp.icns new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..9904c588fb7cf491c28c2b7fc752ca5ef1a171a9 GIT binary patch literal 129702 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≦2Pc8zy|dZT7p< z$JW+Xt;WID6}uqtjs;XxQuF{%Dp4~E-Kih?VgF9TtbVH*kBaC+KKD~VX4 z`D$){erTmyDngZZ{NmxTV4O(=WAXQI$~YNXIYW+xIX4+mJOt^Y@`(cvViF(UaA_n3F@glof9eHiqr(lV7e- zAUTt2zKkfxIOk{X`frVj>l!T1uC~mI8gec!$F_W=@f*0sWiml%GaA&BK;u>4`ue&F z87ZkH0u*}_4=fU-O1!3uFt;MX;sf$=Sk_`%yxI?<<=HUw|Dx#60%T&RE-Ojzh z%baw0KsQ&n%Y`Lji9${X)$BLm$TJfU0)Z2h&QvQDdb0Z>fLFI z?ih8ebr$-~G}rQ)3TSR?yS945=lSY-MT82E2@K0?{-l0fa(xXj0a(Dxh8}a$SYpi- z(9BJt5)Bqt^2`q?Go<_bio|67%&0Js%gLl=Ku5R$()G`7=JQ5OI_uQ0UvZ7fK#V+n ziV-3jVkj~~DCn*)T6MEXEM;MlMNE~Mmo0x^Ou+XRIm2qhvty=;ytMQrGviZpHF_xn z12a4On~ENIE0WJ;j}=Waw85ap=DzmA1i5M$q~b7m6BeFWpYd9Q_}g#qe=W7Paz~i; zHagCqzUsWWu;j`1KWbUH8s0ob0nC6+KnjqBi35m#7}5bDzz`6opbW(aYU}D;%-!8D zTtG|zi2TFU(O=YfpYuHrn`s7PVTMadwc*oXEYbVZKW`qk`mxk?OcMVyT?hts;SY@a z2M1OMu+x?M1OJ(Mn4!G+?`mmu^x^Y}3~aJ4dV@TCv?XrxSgYx8yYHnMIpVuChJ+tF z41v+UP0vuX>DQP}*>ebOD|t`f<+r8jRLbzg3pl&RvX`!15I~7~p|(`v0H2Kq9}oby z*@e&A4YLc(f{VW4(5_GcpyS=~mH~rJJvTqN(bBYG&JH6$b?ovQ=U;drPWp{cZ<#e^ z%C&#)OHrL?g~|>OJSK>K@lV*CT%8O5SIgo5#YS~LTFby7_AVl#+Q~p&J-McT!?i?^ z07XjY5j$C#amJ4=B7%#LFDy7X_=0KV?k-BgSA%(|ELpYhVZ=LyjF_Ch@eyYI=5$jm z$>vFB`v)Fu80S{oZKm14U!`sY{|C$0N?p&JGDJHnz@h*I8Lu_QxglUos0iBDa7*#QH8XSi3l+6oa;0QAOV* zK~te4Mz<~~&M%b|^C{sQ?0oC|FyOzfoQdzi3_BVLVtZV!-brI z*IAhd{RWXoABw%sP})iw+eg-3Wze>%$M!0o}oA6)7W@2`04J%Cw~Mw1lx% z&k4ik@|H7n$dNOQNvwnebP6$2)Njq7MvK{zFMfs% zvfRAuqA(;Tq21c-ptKKWL9ypv)daXe@wS{B0kG5czz2bUx5OE zalqjAXWr{);@Ck%{9_fUE?dSS8F3W-=E$KDHVRB^mLY~Ux;R}N+n~EO8sf53%Dm+H z(z(0r0Zq>3NOnQT;t~l;WdF279&T1vRurYEcT(Hq*6dT(IxboWK0Y5%RF2~SfY&kQ zBEH-H=wggB^@^4plgaP?zCVW#rnerOx`^8%Nsj^j?|yN>)ITZ=5gK+35mJk2dB5?z z+kT79%>ghUm;_vE`H4~zy?zL53dH{cOC4coQ&@=z{H0=)|C53rO(EpEuB)T7uy*FG zvH=s;zhUX6s!ETeq(%fgC@^zS(?mSy-!ETxWuj{QU@`m}j2b(d$E2B*u3)M% z-aX8XdSkvsRz?FK=ES`9F8Pe~;AMl80(2=!m!Qk()amK+#Bwo=F{=D1dV`<&d0-7q z@||O0VI>}|w5gWgq0I%-%*esWxGDfqdC&n;;F&dzPp<_W1qnfSdu0ZA(K3OyS_t&G zD7Bjpx6xo%cPuxk3IkN7m2_=O38o#a?0M%tu-+Gzksn=4dhks4K6Hr%H1Mtk2;#&J z^VeL<7Ej&$TkSBQ(a(t#IY)a7t$xHR!Fv22e4IP02vwVl^7Qh$27lo?UGvqOhc~#C zk8p5PKVywHy!j|jl5~-EZ`{W5;rn=Gv$7-NReh&+3-TNJ>?*pvLK=`uhCSGDm0I$h z(7Zf7tgt>L{ksKobOmSk%^yc*{1`~~EnWM-!vn(>khY4#95IPStu1_m*k7hv@bvrO zvb)7?>qjI^J-RgI*=s(54CDx@=*l08Hdb%KQ(Kz$%aDUFLYWrpr4Mq&S@M!H(u`}X z^Unr>V*O}J8Z%)EGb6)SgC1VjLtnpkSO8HyHX|<2TS5sdJwZVRs;XF2hwtpasD6LI zDN%(-Z2?Z5A%CFMkZ$aCGQvu-4LZ35@}?!n-&Ug;s~F`}#JI(EJreUP3q(~_H6B{< zu$Y6B=9#bPLC6wAT%bh`yU_pfJzY>EQLoS{WVfP>IE$BCz=gwVny9ig4IK#2h| z4ip$99;x+{jefaLQ_OAlbCp!Q=+euwl$##c7XvSKXcgGg)mc4etgmn1wsdr++6XK| zjeX5XYPK$BO_aI*$Z>_{4^c6W(oYq3-k zPQ%A0(10R1HPL%KG=PPjI{4!7XBD+)2?FFwB=F(pw4tHlWVyxR*iDE$qMKTnMkE7rIWrpI&G9xxYB5{JLovJJhVP1dBxRfvyIEDQN&nlfLh3?{>%%Q+pJ`@6dVIsYx|CzL4$claR$ zs}I&e-^1WjVO4woXtZ-70YrcxV6y!RVSU2T3V7Js+Ts`d<=TG)hM7yZjehy*xwE** z=}utx2T@t-6DPXYcp2JE$yvI1O$~ghPt^3Yo1Nlb$t@q} z7d0cd<~|$RbO}*W@`wGZx@9It#+&v0KU>$#9N`b7qlS_t(m@EkI?SmO35JqM*t00u zj_`kI5^$(B<=nL4Toq8`;fSd~xb=U2yk2v0a(a}#N(%)BD_c`cd9L&D!{?PzSG$Ru z@A}`9dtDta)%H9dxv-;-O?+IDYALXB&p!^sgcO}~?u1fLn!^6Q!21FV6@=3*d4$m* zLWAC(jdEUlczRw{>$mLdx2+gS?c3sow$jxRbG_{2hRi?n39vKS+U?tLy;y9ppa`)N z=ilC8TI4pw1v;^j=@#xeJs-Bv`PgBh*9ax>J)MNT)*?)V@Qx7~2)(UsZEYs)u4bll zm0GOU6Zu_S(iWsK@eMIG5}9<2sq6Bk8Wjl%3Hi#R5@z)(EG|8hQ%v~aX|esDORb-) z_b09;s}Pfp{I&(7`9g_!T1UXqWA67{qaPN;g_?R{r&w;wXgPZ8gX%& zaRIo+x_8Xh(<1}TZj-)PkB^W2Oe(qU#Hit)+6QtU>b!$`X2&=!15XbpI3J@efNjY{ zl-@dIZCps($mw@1Wig zN;o(^rg8?ol#+x5g)Yd)NbzVVFYsYZBBD7p(d*(=3gUPl+w~;eDuE?N3Z2AXr(psH zLZF-l>7L*{RZK`c`_p41r*Rs#yE;2LIdzR&&s1gO!)v2uQ&p;cgh%IC>H&lIA$So- zC#R$Fu^Y$}Q^()d>zKTNPGFrMU>PpZgg1rIdPb_t;wf@6&e%bG8HwyMd^TbuBO_Bm zr<}{`-w@*cuss%WcX#&{LX8a_bCzOPHf8L`PkndhhJ!x%bC-Tyh=ZSp2Y3JIhytue zQWg5fQ_e8nMxmHG@7S)%f0^$d!<%}3eWudeB>?`6OMd5z8p8l^?R(Y*IhVPD1~*S- zl$4a@!0$3J@A3->XzOTcA^ZT#FXy&@7;grDkZU|nPwQk@or?!NO3QE~$GZxVQ<8y& zloa*y`a02?_6qn?*}XW$dA-Z|~d_Kmxb2+heK{sbZM+!~D#txa-r0G2E zgcP@5VE@YIvk|3Kb$54{c`YLH6=u`C(G0Gh>KHV zB8KA8M(hL)#&HsKn0$Zee8ri28%_!ZqZn_`VPFumElkO4m~`r24BzYE7n6DnCgU$6 zWxKLRnQpdbd`N9R=kmUb6#IaZNmTlP-ba@F0it4t%11M3=@!RH)Mi5bHP*&{Lry$A z1k(A7$?95(<5nR!qUr4}zUBK7eC!!{1i}$Sk~hu}X|HV;7Mfl(*osnFh#s->%SnbW zAmW=!@qQJilz&3S{*ajX|1=RihjWLAVrxi2oXHv%uW%&Ljge2};(ch=Ay3wH8cvXZ z&n(Kp@a7TIA_r)p`#;{CC5tRESwEB)cGf!!e=sC$h6m~pfLkdDGtSl_)#-F3)A>Ok z13Q!mwv!s$MVOFN0``X=&Sx$JI&4)(=*4vYFYnBl4I@Hdr?PUz0AM!!{4c>Q6JI!!6m6~mPvCz=-I%zX^Ulq@2oE<-`~ zVmiw@Lr|8)N^=Z49iptrHu*VA@Qj*mJa%LLLF&+bwnfEpJHwy|d);+}v+yqo!M`xv zi92&Vh$0&HM_A}OwFcrlWr>#>uecR?(r@ zvxF)vX=#uZrq6`I1UgB>{E*j&e*qEQ{K0-5-C}TB=N&;b!_3{y;b(%%Cjg7n#&Ba$ z{V7)L&U1>v7&grS1$&q}=?qT((gyXNT4)*xq;W=-4S7H*7&I>qCIRlhcg1Mhr~tJI z0Z{=hK2Jl|x6;`F{_V_n&t+LbsJ_%~2dbj4M^RuJ^r%&cGVtb^nlA^B$U^3Zod>Jn zr6gdUKg#4}r?!_+l!udViaqx|0=;RE5(pm(|01mq*So%fs1PG#4Ms1VzqRtGe3rQW z8D7qg+2{YVx3|I@6`5Ko(-1ogE@1!|3;BQd_J{@}G)V#7|LfdK&m(Y-@ayH&Jkfv^>Hz>oAEkB>#Ml^?ST@ zuOC-~0uQ=&^6&FMXu``NBB>3);FC-C>LKD-;b-%k%cDzETX@}F%k56?X?iWxm+jY1 z=<6wby$;Mb>hL_;S(>0!M1_Z~Z}?A9V6*&<@IS|9iATWVe+oTyE1aptOG$+;VnX9z z%3E0T*MPu(3QE-f{l$b6EP91M>X>BzEfI@2z<&-t!Vh=?j{hmRNgVG0fsDcYj+d*T zUg0#unl8h5A2tkQi>e4+v}I4oe_l8 zd?6XMn%iQ?5&<^28bY0f(2Iu*p8`;0BU>6iV&DX(s#IOS)+pG!6@NIEcY33gV|&E* z&_ym@z-g{OEkwTXHN+Mz*h(*8p6lUUo=0jX+$%=^N#HD?c_QxfLh6vbK2DH>vfqaN zvNaH&LZ64EM)Z(OUxC`24WV`&+Bwf#?hf#yn2jc-S*$mc9*`Arm#U6cOn{B!&C-V3Of;)84VIMBB13qx6IWJ^TgLhWVF9M!@>h9`%LOBXOl2&mWc zs@pm^#1d0Py%F`Q7vNSv;!;d0QBR@&w&6<6$vGkDu|FFMcp}0EbAm11v)CPQ@3JfE zgpGVFfqmseG+$$|P#P?D(hFz!ni}V>r`-+EB~9Pow`btwbPEX+ z@iw+opu;T#?;e+u20aOHzSaMu01!tTMTvXq?Ck8EWf}%A=#Bv#6$5m^nK?KL(Oq zwhoHI=Z>4=-?GUZO}>#<60?)1gCnLLJxeMnDd~wL<=;1?#`d1rId@v_@H!^4naIcB z$Ur%Amnw$CH5Y;hTW*iJR~k#qvhuu9V3u+lK&4)XBidM_x15Rba&@4 z4AO#xGz>B{g2WFADQS>yBt}FS8irC@S_J73L6DM=Zcw_rJD%ac)^pdIH}hidIs5MT ze9t|z57ZtXXz$8~HyeC$wtqdT@Y-DJb!J4J;slZjWlce>igI?9%+yLwO$`nnlsUh- z?uej_0&%?zUim8btEC{GRbr>!!M56AoW~xmZr#So4bQk1>~Q4EE2nDg6RGg9a5OR{ zy}smf&0}8BzWO%+At)uvakATd?OD(i<95POYPYE<8YDfWOKUVjQEFVC%N4i2vJxPJ zmTR^yEw^Xxo=@+LGTtF$TYHmv3zB(Xb}1@zYq)f@A2b~gNa>(6$^1iC_X4P@8PLFr zi1L%i0-NSiLy~@lvDUqd)eISrQ%G5*-`w0}9n6xs{*G2;KQ|n16oNe!vGq$0*vbql z+eVmU1xWz)xu9z&v~5!7ye3WhtPJlMS~WIG31z~<^0liP-&Jq7J(_!BX=TVvK@%4X zOL9bUsHUYPCRRJ0?Mz2;K2OslIJJMLG61{K;>-%Z^RvG^T20xB=34~fw>HF_|8j|n zi4|$4Ki!?DQk=$Kkgb^y;JLEv73Edm{irjCd_hCr3l`#w{bM69%>YaP$Q7CUr@OCj z_6gb_@5jr{5Kps-bOdQA1I^{CsEN#gqH1elt4IMUwJsHC35#Ed;t1zZwY6e^7_4tdgD^w&#C6 z25)EZMhAlKJD53D zZHOym3it*U9tg)3t?Wlepxdxk>s+sHz`J+`7xdfOK_rkMH07YdlqcHA_PW zkP-QO$daVHF(y!D%-7{?crz`853mH^xLHUk=aK^nG^pmwwe%6E`hRO_vVm8@Xm1y; z$sX2f;>#6T1Lr3H?s}wMk!kLT4r*jbNC>ITVAgHw&CQL~t0&%G)!`&DPVf~6k)*rx*F-ukJGoPp{91_ zVQsydxVW#IOyVYP?D=40a?(DdC}|8J4#QCEZ-&k&yuX_d>CWB+vP2f(b1tU zZ1czWS<0%DT`+NZObi=*DJcoa!p6bk}XesQj+V}7^K5%ysF6X*G z#YYW#@nf;P`790V;ct0#o37%rAi)H$@6nLgxaO9W0tUlwRM_NOv0<2H>y1n#zdwR7 zahd|JckAQe*Bf#o4B+tFSSk>bj@WnYlWhYdpe_M?E;X*#Bw)BVthSNKF2gdt?5*xxZ|wd36>4{y%Yu`xhqj*hMC$Jha~W;x^Qgf(?_IBDtWv*-`z^ZtG(^wlOUqFqSMgwC(`XRVl| zL^liXN$+;+=Yt%Tu03H5Zlu{X$dbo9_Xi)sG;)@Jp#bWGujM}(h%Rl#nPO8x(P9Ni z!OyFj?%$#1lV9_TillT4ITc-H=Nt6Quuxr4Kh7}84PrFyh4NXl=py^9L__AuhceU>>WkR2g^>J#S*E!lsKOWX} zgD(z$vkvC{>kgO1aN=oE7^K3}Z=4yxfL}!O6fy%TQ8lK1tI6iwEe6_2r|)1ntdyOS zu0Q^{H%@+Cv9gsC5)$qaH>g{*g_!C6d%6`mPdPy%UmRrA$iIw-wf(F}+E6=xY)p?s zRP?`|{(dhE*lmIO&tuxh7h3{mXZ1BTaAwtNQw_8~ePcyC7VoD|pW=3BYPX4I6mElF zd0>UC63d9=y&T1sBNRP(l?n7^V3LX4p|d+ZSak#kxP-2z8`hR_MXsFsC*L$l+v@An zOiR`x4vXxW{OC<*7mP`>NI*`uh(dN4FN1lKNcm*K+NmV4FwzaanP(Z|APJ+|g!HHjC0a9!!vjOm>Mhjv$n0eGLn55r9WQN=<&$<>l_7 zi|*%_F~NliBx#;CZOez<-z6ks(PMa-`rW^JDl9wg4f)CRPr6f`D&1tf{5*WFPm?7dBn%n{c0^BWNfz724>OW^@W>R>GiSMQ) zHrZ5B=!fF@y?yawIfdWYJMTE8b*L5uJK=t}aQ5UOG3WBYhfGWN5 z1wAl6W@F|QSbSi(f0%y*RJC0G{b7-!l7C}fZjrziAzCP<@+X!C?)s$U_`Sgq>Z8fz>Y3t^;I?}dHA?osj1d}j2&@=9M-2T)(_$! zgW;|Or49XYVeH`7Xy?TT02lwX(e$jf(z464)%wQ96uTngt>!|5O>4seCNw;Z(msA= z;Cpj%X!J^WZE+aLIhI7&NnsUBfSxrGE_|Rh-DsHF94-2RE-DN_LFJL+4|0FTlF4l^ zA}#@~I@BSjgqE_UGwLjWGp;NocfgX9L3$tid2_B@IJ{5U*90am@qPN+T(dt%QDMGd z=9WHH!S%)A^1U!WKR^AhBgU91Z|NDyyPWrAHGN}~4-p=UN=Z4RLTHzssG>qqORs$l z4Zr+OUePNf)sB(lhVQu=bjK77^5Do#v`Goe**xqCl9va4{;C%~*<7V#Oj|XWvx6o6 zisZYkHmO#92!>c+aYElJ!$X7Ed#zUdE<5`p7Hr z3i`GG6f^YY6)r{_eemK-65SNW=c#x&*#!xOhAC)WBz-ZYprWe!*{Dlp)%uH;=lA?P zP$*WyHY5AV^c+!nb+PTBm5&a}Cf)YzN=HY>IUUXSw;OM6+L_-CO%gD=VQBms2N^W` zd;P}l&y@1XhpOJ2<=5ml*Z<96vYii_EPxjIQq}G&GM^HjAA~qhp=vEGi3Q$krVnj5 z1Y8Q;1j*S9q;)p)@y${ae}}Fnpe&Q^5VMK-RH;-2RyJ0}Bo}t^sj25v&&B?UVC*W* ze{eL$QV51Pk&i85eH4oR%Y=Bbe2*@NYkgHWkQrX2GpX`UvDqLkl)EZY?AxkfgP-2m~#(H&<$u)-v6p!wsm2S7P3|Civ}b)@kuv!RJ7FJ zJ&xQVk_@@@!8)X0ikWere{wi7q)UT8JxRD&-7y4OlfLLCWM-D7g(){z4sl!^Lte4A zx8iqqYxq#Ft@{vRsG77eRPouyu*%&9OEASkJxxh-avZ5(P8Q||VtPUBZTD&5J)lFV zi}&TXour(GhKoVq(MEzrjdTGAM^^No6XYW6@Y=QF@qprfH>AgA)nKYiR_H&udm`YW z;mX@9&;Cn;2>~bOTsgUCJ#1s)7zx*>dM+i^Z|j}IG(V2Njd8Aj+g)Qnx~Kd$NlKiW ztOx&vF=fsgR%qU=tGfxNpvzp7JPLYCO4I0nzO|})swDduReZ<^Q3A&?c}@@wZP*!l zE!9;{{+`~dW)4n0-F$yXy&7g+*?_C>n3pSYnfn|czBQkyD@~H8i7-7nI$C>JD7YQ= zlR>0WlG$72%vAm=G~WV3WAD&lwu!R#qvsmD?yHI(F>OZajzIRYV_q(6VUR1t@LA8| zWE1hn^cRLM4RXJKy z{5l4WWgr&z5554koruCoOhFF-t-OZ6m_Gm(hB?r$mTFDU(b?I<{iUab_ZCzcEf;}n z&~Pc;RyDRVLhyA4C!0@neZcD`Gcz-WO(9%o*s{}`ZRz-tPB45H-<9)6^4^CJC7M9b z^{%I6RFTFfch;GhU=dERClAqa^d+MO)UTVKGcD=O0czY-x)+!K>*M#>+}zx24|jJV@=F9*loR%BQt{od`9>cc zGB*H1zqQ(z8ZK~i?E)OBZs_HK<>a{eWnz9Fit&fE5DiOGj~w~ZQO*u27s+>m0K_7@ zlIK;nk4)qYjv`MgUf|S!Y-@_}8OX=9o$i6&T<_O8xmcy7JT1 zBsn@d#l|t5CnTT!!Zlchcgygc6U-|$y6gVkieXm+1zqn>V7G--EymTcgHzbaD0xU= z7aPHMH)!ZWZXGWS9c@w&s76f-RPe5=Z>8>jbLv0XN&cmQy+KR=)JhPM_s zA&VU)^nelEtK*#or+gL>` zn7cUBJ@f7dPEGlspR<`eh39H!1Ei^3q(xlJG`pH}C3yk0ejX!oBAX`qN*Xj@-J`}* zmOp2Mby$-!fR`v;wn`Ro+iB0FS@5mQ3p31|vx!!_ZIGuT2q^`iMK~erZK^tIY;%`2 zpXm!)Nh$k>z4QuFqFNr5lG_)G=OOGLc%}DVAkxi7I}3%Pc&&^XuJ9uZ?l5~eWQ*Lw z%Fll!CFAVVp}1xe^|_e|tx?31&{Zzvs;>2b3nroro16w}*a5VzyrCqPsp3<|_VX)Y z`Q&udxqMwp1eSja4AGi7pZ2eYt;f+Wo^`~m(S7?Sk5CFDTd@8j(8fbrHY)CC zzkjQJqsfeL*cn%GSAO-C5ZIU~W30cTsN6jaVo_Zcdymvah{^P?x=Y$mXrM$#ePn0;Ey|pTwmtKGzssQeq9`o^*bi987Vu$&prJ%g+R~s%958)gX=4{A74AZK}bKPb`(>{~ajL5R*Ac0!RvC z@DjjhSOxw2Bi_o;6!y$pTrbw9#;%=a@g2>tgK9y7GrW1}IX#!LePLlsDd2NOBd3Vx zfj8ElmDTC_Pa&?1hlK_7b&A8^3i9)<_#_$c7q&y*OMzaOVIxk(L;!!X<$?hO zTmEWce!h6y`As^@t?|@FdQV>-C3ykmIPZKUjv0TzxASlj`X@w9eXAk7T_W-#29lUf ztA-K`=GL6xvAKr7k2}jOjC1CMit_VMHYUr>T-D8oF2Y^#^=4`4c8TA4l3?gC-b!T- z;MT5;zh!}4{B3bUO90}v^&LkY!&KM2w$w6+}t*{zN3?a!G4yzL=*zq zM`euMY?IP);ucrg*r#-E%&)`Z#IH>hG`+nw=xrc?QLmEvkujsGL)K0m1U5z{EmYq18{NR zIaJ#AlpGBsWu2qVRJb?N!j7)vuh4y%4NN=fhf3c`!@tPut&RDTuK7q({6SZ_7|pcs zU(ex|`>T$5FI|28r+6mo z_m&c%U$wySvwP-vPauD;`7JLB`kal65{S>r+QY!33r;i`MpXY%chE$g{N>?ocVxWo zrlO{{SDXAh(pJD?zp->FZSZAYFg^9$gT@Y1hZ_!Z_wcx0o`JN!SZ3|P$74SIEy+!W zkigC9O9{cJd_;szCN)6&n5*{+NbTb&%72t7@qFlYOrmXfc7d#pkX>(7LAVbmRFb3F z-(HRO3yU8*BzlA;`CD~ZxtmM9(qb?GG~utfN;a}QcL`mn70(9DoW&K=^Qw)od9g)iDq1V(7bLpin5L$Xv32rt@XS^;s{n@8i69azeimVmvPgZ7eAQ@laI; zdze1_@7{pz-FBPvU+v?~p1wxQTz$;soGTTwt>|GlICe}V~ktqpF=(kLjg2=`ZA<%HKZCoW`PMKn{` zg7e}x$P{<;YqwP4?^y>9$k|1T%4FZB_%%mNb(K;OBhLerhL1wP{8)Xvr~b$2q5s`r zIGU`Y1aH|R-~%8oUS3Az873hzhbwaB%s2YKTt$Y|?n~deD^>4azUT<2o$;Q&L1j0$ z%C(ag{g*zM(f|EJP006>n-`d^+#zjc+0Qyw-6g0pqbBo`wNTK4nwlCQVGzKRBjUO+ z4!M#9+DQ-&7xSB*4#ul_WQ6fAE$A?U(Q4=7?1Q*7e?r0VnBafM|Fr2Z8u?~&j zBlKkQH0rjmR)QjIW#4PU;EXo;OxW1imm6;m*fX(;z4=^=7kmG~`2T+|0I3O)oddx_ z8kyna$xMmfrW{Ug)%%TXH{~}Uy87Iy$o9kLSys2%(M9~_#r;Z8V%W1uk^Kmj-4Tr- znqfKKlkmc!FN5eg%v2|E+uGRJJkDhW@~xR+Xsk&P$AzJw+$ESfw)=8`xXI(dSo~#I zQAc)7W7>V!7DGEPoLmO~gkI{Ngci3ku7-ck{qCBX()NvfpHH715dkQ7OpwQQd(0ZC z>HCQ$rIU?u3#HWIfWj>~vY`Ma#QUMjR{4?9QIDbVA!QGoF`7GUIl@=BPj&!+?HI3; z321J}&|Gri)_ZTQ?7$f8?x)&o&OOO+=ONRtN&sRHSvcg zeo4lC3P{_HfvOJ2VoyZ1FS`0Q&sl8{-YWYunk#dkiUTHdP%Hoqh%%ZgB#r55WaRJ1ETD}l?tfCe<)hzZ zs~>|iH1@yWeLc;ln36Xm)a*ZszJ|P#%AX#^tzcoX0}WSV8Uh$e&@V%w(U0j4)9fJb z%hb~sQC1iB3B_Tu6uN~P!?j1Oz|ZSFsf&Q0A2-{!C9~#FY##-KVZn=8{@YtIP}ARY8trUU&@C{M?+cq;LmZP zrVQ?op4ymT+sMc$w|14=a?I&kk0Q1}k^T2vPVuKnFo-gS9UNrUmeoDJjL4&ZTe};) znC*2n%m6}qvQZ$I%4l;|r4hxvoKDN-ym85B8^>KJikv}`xTjhqm`MI)2`iuD6%`hC zQC#2qDPb8XW1ehK`sodd_>c#uW$fHcjgI@qghSO)vI zMVVT@%cYbkO~kj|9R}C$uT)=%9U0YuO87BEnsC4;WH}mnU8cm%0@^vCsDYOv%Px#T zK}wSZU9uE1{wJ-W=`ItQgX_l@=UFCHYM#U+0|34K1Ov&)@P1;G*_r7Lt+tC3-f}h`az6R3=6;vDnh~_|w|0DzLY#@@ z?|Ccc3#!}wy_s4^+AVpo&tyxZwafWY+&JsAJU^8cj)d$QChe1hg>UwwL?7-j z-vP4IKbuGIKMyh0CPWj&N8(k);uuI!fNzo%y7dqm4a`zZB%F-{tG^}B<;FJHZ(M}Q z5t@!5_%l6)kKJAKJH-)Wc(G^2Ql?7hm2{GC@VcUz9l7?;=L5rMW@eD{)g=nrGgRAl zi1BN-;cwG?lBaN?vtd&VDp_W8J7NUYnUqRN*2r%?ibfpR0wcdp9`$Kj|C(@mY8cZ* zl_HOVD?BMZJ?c~S8eK~D?sA-00jEqVtE%ii67k6np^~>08i$3I2k_AMm#U*Vzqss- zG!th-Xq`31Y?KO-e^hTIE-#!$LrgIAMJYtlR=w{cN_GVNDLaXXMo4jlHUb7l1KbBc zLYfWP1J3SWn79YWgL3IS3S7C)*Oq~J@!HFUFKM51!UXs{=^k0}AOsiLFOkRBFXFH~ z(=Y_92w|y*oZl6|H2SPP+s_4FPycV|`9x_)zMF#Yp{fn{zi_n;>~;2sM(n#|DI@l{<>eRxTGZ4v%MEgey4Jw*!y{`IINCfx#D*f)RV!DF&aL^qYVDVSWo1)M z_PRxbb{~#am9bS6v6ifBTjow`lsDakN+#7r6j(R0T9G6nS$MF9McTqM_z43#kY%zB zf4>{MEn@5A$-A~oN=~*oV_J^70~VH@W?c`LI*aY?#y+W3p@SLDu!0;3PJ8#;gGsUI z5oVmDcViz3Vj;JoiH;X};K>C-_N#nh@wEZVa$7Cu0tl7-E_w5`3z2hw}pn>t`#hc|pfj~jhz5tu! zkt9kjB{>ym=f^C8C)(}2ckkc$Zu8gF)uo}>P#{jW@Y8B`wR<)-iRePHwD26FTJn}G zFW|E~(JsaYv=tsyD1`-Fv1P#h?4sA6Bo4kY9kv<@(VHF-9zC3*)BMH~}7VPT=@wdYVy7-tz#6=xyC%@PiYobT3$+_(?L#FJ~tvS{hAA zF^c8hwQ*n+jD#85!myNK`RxG)1+<@u_%gVkZHQhh`bpkC{ed?`fwZBk%}0N4GjiNZ zo0fepi7C?vH&BO-T(HV|;+X}WQrB-U2i_|No5EEDp3(owUx%kA-)P_ItIuz~&07aN z@yG*N<|B{qaz#DAmXa@gc}c~r#AbiP$iPtH{I3x?|8*R*-?c)U*@hj%=GnEf8J6`?NXO?Dq2RGg*;*{wZGH{cD5%~=lF6K!9i+&)wC;-kA?TV!0uZWSG zmPY$So!$h$x>y@;P@5;6_tLye-3ch#~ginS;FpMd3Al zy3@p;woS7zk(wkYm?HL?fB{f6stUK&)ul8wXl6i@0(?a=BuvLC=n|7pn1`1vwg@RsA6 zFJN^roGKgP6&rEMaDtSyT79L!q`m{!#{Vv6!?Md+6t!LFZ_h0|PA4go-=1t}HyxkN zlDCkyJf-2SQr&za+8!2%B+gs-cPl^KaYtq`1yvO zfq_Az$6}Ez9!XFUjf1Qaf06Ght?E~khm(;F`$C9cJ<5Ny}C((u)*+n`dT?-b)yG`9&^gD(^NXUNIi)L==&qK@d>6K)9Fi5?Ay zrb*(*Gf1xo_IIz*U)47r+6E5V*U*zk2gT!y%}3k&%s(#}HM*a1M57~@+xNvGcN~@f z?LjErz%sCxK@4*AK{cM#kGm&-y)G#jEt0E-M=hOo#&EnA& ziB+oQE-iA$Zpanqgw94$8>~tZAe;U9=GRFbr8!?2-l_-&$EDHQrKlq2M{&SEx>+SJ z7=<(+qC^*_H=Iqgub{W|pFSBh*pd66?Wij5zcdSqIG=x8K58qyCZQ^YnLy-+HexR> z5lmhlM8YDCRKO)w`r{%6+8H_O;8|@VI8{WLP*pHZkBYo#nxI1JC?$+U32oUrU7T)g zo9KsOlgerAXxzL0oUJF_HhWvvx1P}3U6KeTA0UOOQuv?m#Y9FDoUjT$=!2=sK&iR8 zh9_(I;r_jBGfe36)kO4z%ev+xB^fo_Brw#<^EnvhAG1ZvEwiuPQatd6%d#t`J(^xP zhl}7Bg~@U_yYfS)w2w1;xI?2(i(ofa-lL?_bMBE<{v4Y#Om<3*WM&8!xPQlqnkDt*l=Z)6BM{4B>dhOyN0 z2fpYQRV-buFP>jN?;~ijnXv`nsC`e#C+4jH5&cu5+w1f2zSXUZZ-tEvF3wX3Rua~y zuaugHfwZ^xih^$r?U05kiG`2*Bmw^g(JNZE41s4x&3@4g%EQERs?(dBo4&q_&ytdo z$P(!XHb;vp-}F#82wZ%wtk8P?6RH|-O&@r5)`?tf*M8z^o+<{etEpMYBbDoj-JO+^ zkg_SHtMUF@Gl|_{Y((CxLO5Yvk&g)u(T=M33)U{A@}&=+8x`@-k(nn2?Rs>4iUn_Ih86uhfjoa{m)b(hI#p?3N{F2by&qyjy3n=UhAVfOvfoj1`; z5)_m3u9#I-Rgl8#hY0W5E?@2*ca8qT6vaQa(X!GaLWO-cTO32U2*dOc;ZO}*H- z+;frmbfhNJ01bNAtiFIkIB{Od`pM>~hVotIkmU0PwB>yd%%hn^39PNHwMVZ4WCNPK zcB*>I$P7uUm5P#Aaz00Kw(ogvM_wOsd zS2cLpA3o@vJ{$(5P9qR&f8*!n>oD-(_)2jsF^o#FW1cIAq{^`iF_UJ>&!0c1HB{<- z=D0T(sfo}pHJ`FimKQ;a1CX#ozvS?B*Mtx^A;h_1cqu2d$t|)7r>x zhu~UC$aq3wL4n1esY*I2=ZtUI@#uWP;NtR^?zI1d%>)yj7e>uSlr1NWx6LIAjy<}2c z0XY3XRD9=KxR$z26Mb-6m|WV9^S>XKi@AzuTKHTj*Igv#y--#^H{#XCieJOGlL*DX zy*gL6w&v))IvM7e-#@q9PT_wzg~*7Q?}#uZ!CC}SL0UMmmQd@=ZMPiE(??_PC|U9Dl8rVUn)~WIm$=Or7N5@0A`}a*Omz~OnKtD z@j|0_&ZNFc*mhtcS=7<>Y-d{2y+0{tjZMb?XviJISDC1$&5;KI%~j+Hbps$4 zwF!dwhSGUh5V~NNyr3*6b@?lm_@&IC9CjKv|u=A8++VYK^ z<-NV@_HJaeN%x)R9uA)KiLm?lX`Xb=_6Y}>izZ?t?=6FnqVPfG!V+*WCrT!uWWp=0 zT+0{TqLLTXT&1Igq9{seLVW@~{Ck}#ojiTrU213Sj_JEI1&T`}K%FM_7zGajByGU#lP+C3N znWk(puIa8=9BuE6qJ79lH#f(FVMmF3T-JtNF?mXh6SL030k>uUQy`SG)0GP6$Ka(p zIhjqrLCdHezA0^Bmkga)<92Gn?w6&4iaDZX-Cf!Bi?|Q(lo#%BSrDU}P@NPzJrkLk z^R^F!Vb<^;`ZAfBP*plfPp-X&?Z>)=%vzb^??fFZJSB&eNo|-wv-mlQvAB@b80@xh zAeEZo7V2DlqtTJ4cTLK-HdZRAP^rBJ%zDA8dD5DejkSl&hP)e-fs>sGp@@iE9>`McNznR(v~OFv(CGdQmf;+w1F9t$O;mG*q6a@ z;vkY@$osEv>{WlSikezm6E@)sZEcH2KBK9l#qg;bd&<-^rjqKM3bN6?pR*@k*)nfw z1ez}(CC>_<{0Teh?-hJKcDk{&iq+BHJ>@Xjo5%D_5@=Gs&-d(7mn^ei9=}Mmv;1Ue z=zB7(tUXnhZf{t5a2eohy=$~HR~yy%&CXYvMI%cHd)cM2XKa=bE&jdV%*XbpKUMrY zsnz>G6!GMQop?frjR$kt^D$wVI!fA}mdtTo^SxoLKxAap;k7dBxK}`#_TyI#2u91i>4J3uv z!WK}>&p;J;GKiTq`3c#TZ52q$B7{ZSpZ?pbaz28Rv*Puo``F~9?j&^ti;TbdM1}+x zKffsd_Rz2Q2FFKz4FQ)`drc2QP#2>q0~kTR9zlGuN@^DEt>FmKc6tJt*6r?$QKm8B{h&Wza05tbd*r-P^0CyBGu z>f!9j6)&Z^!ag?qg})#wS(gVa!8$?a7CV0QhB{YEY?)3s5uKvzs|NZMtskpoP`lB@Y#lOLVr8#?mu!^P$0zmL|l z+IuKDHTp($Nq@x-uDUM{{Mc)IPWgyzlc?o-PpZ^!lS4#Q^h-)iM~YmYJDLdNCwQye zpw!qZB7EQR;%k3VPLquy@`d5E7snzui$c;Yq%<-|cvphr6ZIt}1br!wJ1-k0w;J2< zu`swEbN{-*CX7!{cQndE`+1?y+^@PgxmbU*XD!Q>yMrpSS?J`Vq^0*;0j?aL@>pFG z$hWYMS0veVRJCOcKE3(!<%{o41m{q7ad*-i{)dACzP|jH@86&WMc13M`hQ_Px`iLJ zwsB|j=y+e2wjNsk_FQErrxu*~GGdC=)s9al!;BC2L55WD6|FJ>U*qolHfmy84yBEb z-j0qQt|V#+O)s}tUVAs4uBDsI`ToWzpNzUB9sKPzdCCsiAN!)~8+7UZs?m42f~Y$7 zVZmq@D_*tegKrGRfwx+yD^OaY(CW~5vQ^W21*G`ZtPFN{w`Sk1nrUP@mZP&?0OhRg z-N!~2(QiNYB>8+VEb{%D;VZk-&5Y%xCX9#Xtjz}-$QU{=#11#Y-!tkdWc_nu(XUmf zopJO3<-$_p;_fbCmaR4dwtG9jYLDu}b-8RmWdwm-#UM^>BBtS??778ttQd zS+bc^-43Ht`wuv_a}k}NAk02kOquhNGo~}BG)5f)2h~7wEhY*6!$nMXo5I<7suE#B zC*{<`3U`%9Hda;(J)HL{|1Rq5>Rw+~S&Ii;RM;C1e^V{P^pMQ-HQ##`P4}d&#yRKK zNdKF`JIw2IWjCalakt zYeg_xiPFg3ifCp^Qa>Rst9@*5D!_7kyRZFh0WOylSUF$Cq5hghTjRAGE50FLA(maY zHQL- zL7NxR$&?vpFA$vf2e?+&*5nB=YsYqc!BtcChkq&=Vx1nt9C1EDK zZtFliVlqTrrwSgEGNj=MZJrBIbrxd@K2n8n&MC6n-Fz)6Dc;h~duh+j^7h6Jjq+p( z=c8J37ClRyVe?wBUUEUonaOl5EKUTLzUZEa)geOhl7A7X$U3&+cwfY)lXnk)au?bD z?gJt1!xoRQ6T1R5YiY@yQUxuV-&~*VM*Yt64Vj9HdN{7FQ%f$Ut4qll=hgFAySMj# zd;~QyKMWipz7o98Bmju7qrTV<)M~n`MHmHL{CT~n&~kT6yg96fHnG^Uoa+$y#(DAC z>#qBa;XGgG&5>BkKvn(|_C>rYO+-*;ey^3aVLsUx4IzO}e+H>I&l+naPgK{*JG(A) zy4&a#^XsLGZaP%@=YKLZG7@5&6_gfrSWYKS7RBNhT6}1BwNq^}a6(E^8Z7J-^D-!H zH}Sk$SvRb>%5Ii+VzknKaScEs?P7G}_$Ib7eU zwDXN#>l@PcnZfV{;}a4zU;3_`x1hz- zNV1kd`ctR2tbaegY~hX2;CbFy+1f52a%O?O18YS^nF;Uyw9R~E9H-aP1bFHFtqd#8 z>KUH#W8~xG3w-p#BmK|1$#uqNXv=0HDX@CD|`$cV-^ z4g&p<`3$SDXrDKYDHG=r-bJqwu2HXY$njMkwMp?`y)Ga>{Rw#XJ=$RaCOj3Ys}@~9I+k$MaC;(v#&M_yIUFpb4A z`23&G!^sd&`~+G~P_OHrKl@TA7TRl9rAekz+RGT7vRtETJVYl0Sf9RiAdLel5 z^bRU{N-u1yLwqYHD!TXqjeo|*jKX=WRu>1059RoY-~7(>drVxCk0|+e@|?^$9=1C; z%Si>q^jj1X6TEd6VNmgA1tLm^vi|I5Qqx|%=;wd36Y)zSY$GKvJcjXUPGMo#mqlD4 zZZ_s&VJ>lUY%El4-uL}SX_Pi7O=(7jWo{5h-OxsL?B_?ZzmvdFhl7|=K8|MN9z0^F zxTj_N+mGTc>7W~N+3OSC5=qDtzay*sq9V$~^WkvTrJE0g9V;K6jgaRgOyD)mG&5rh z#ul#@Xg`!_2!r@n*wMRH=0<43&x#7NRZN@>R32{2eB8^)Z`MzbNMl3LU|1z2{G_r9 z^>K&yks_)$8(h;-cDG>g=5< za$)8&*GsgVB2PXa{UmH>_XY}ys$n;;A%cz$qa2k%e=KIBrVM&PLm|n|VO)8^36r=( zL#%jMFGjx7Qr`j_zxwg7DX~{{8!ApVW8~FuTFR5N^w(t zsVuIp>aM{9$hGJsOXfOorLfBM<4pGA0kS}rhK21e{44E4?U|5cVD`2gSbaCjNXJ`K(5+*{`?tB&I%I- zr%|>#YMbYt5!;+1$66TQ=}(86VS9zlA^S=jd zq8 z0f{B@?JoJ@`tGA9(BQdA!H}NjxyoiwrK_V;ybEyg@r6!qb3mfTM59))kUYFu#6{$9 zq=u>4n1HlxJb0shjg)M{H)^uC)F*yz;jxEOwiqU)(B>?l{Z&1hsvPki(N#nvg z7=VUIMMuZ4-vWm1qN~c{I|CgjzTd$FB6H2D7AJ|q+h_2>%`9N;Jm7F5+-TGP52S+u z_-cv0|6dI`HmPB2t>*mvM4098Sn|Z#TnbQ*=oV=&adL3M=XVWRu8%a+u)=daho4ha zyW2~F@=vQ#oyMuuSKV;90WK^lonNiJc4SG8Kc1(WMPf1Yc2OZ5nFQMKuL8$>uo#ZE zL>Id>E@hCAkdWZg)wWl1A6Kp%fRAE4%ewevLpEfI+1J~hv|pt*+s90a0_>_ z0wm1R#S+1M&t5JjvS2$TzdWCH={QkZij~?A<@@Txn9mIQDhVp3t(&oM3Pjq8`Tk@5 z%mkpe@eKYerk0kuUVK@!m1_3F>EIyC6@@|}8DQ*H4i1hH8?j2x&X2odnVCh2_x&rL zgi&9y@t$r=TWxoA)|D)+DfZ}+b?%@P?2=0*J1PSgM90COQ;O;KL9Ref}oZu2X z=;0dVa3@%BcfD`#_pgr{uJ*3#_GV|g`@PrGyOZyKGt49#I~_u=Ff=+bpB8!b0=xPf%Ff|;@Hg}s;PH@F_K51uY!Ih^;W7&LuHeQ)QAVv!}xeVw6! z#kaFt=YR8kE|rFHW?$iOp^3w<1)w-R(_89(5~B`AFtLE-p%M@W-TIW${nxalNV-Z$ReS-|nzBf7w<_^YC%6(2~b3iC@U6aTh{_CXEPu<|7KG4%N5>Q329-1LI0U{@0Z{V zIq0!S%&wQ4{LNHYtJ|*1kM}M;&M$`#H!_NfXrQ|5LOJc{ozaCxJ7U~bUuHJ8{u!YY zPBytddC)6eQI3N7@OTm&7f_|ek&j;|t9##c?}Y6Y)x+w;ywN*@$e4n^5y^DXjqeIw zMH!r%LU3UVw^K$PN~5^Pugza0;UGaCsVlB_it^O8N0s4p_amZX;t>c}H6HU%h&Dx7< zZ`49h2e<)^Vpws<>IX#WHz?w}PdCkL{?lhoT_>w;g+;#?)QnQDTYWCA%<8U^va(_y zPry{c_lKq%h}JSiw}?>}Pm2NBcNc}YI#{4rm3XZMZ-k7fgheh$NlDKRSZYY-c=yxw z-FImMI$)cHF4ljJ9+?>_g%rB^Z5ddPeVd5UjZ*3 zT@=9qk0hY0);j!Psh5b0L=rCePoF+zdO()4I&G#IH(eXKy-p0ax3}HDhVT5S+jEGO zCWS;t+C_Q&eh<>-yU})@Vi&%$%O#A}D4jKmxhs({ag$CiP6bF?;g#TJtP zTZEF;prLenPF(%A_tM@;wsxdm3BMvEmw$!=lpNRbky4Se?$g09yz2~(t(^=b?yp`e z%KS9Eyu6#YtOu=CRhWmye(|k2@>eITWB_~qTr#ogk(C>z+cELW_g*Hl6hVA>VX!wo z2Iv@41rF0OBq-?=45bwEJep*si&{P&*j{$xH)QNU>^+vC5PLd9=v;12%!1TD^%9C?sPc;7Tr`#5D2K3OooSg*n$eLw*t`2+g9k#*3vch> z(0{R;Bc-XC%KofUg6|daBSFr*zlG@guC48FcGRP_4k5;m+Mf3Y4IM8x@60F`qd~_H z^%NkAK0ouODL8uJ8w=>eVGS(h+j<%QnjdSm)q_rth_2!QxF9Jhbqo!qY~k0#l-RhR z+H+;Jr$O|84Rso#GY+?0D4GyVa4V;VWY#Yov`+!Et94lBGpr+&>Hgt&%0Lvu^LFrm z2{_myY(_8Y8ylO__Uk$J+(9>zP1tpPQ2BwoS&~5 ztcB$%_t(K<5fL44)Hvv?cejUAy-z!Cl}5P~EiD;@->QR%G~tSNVqCfNm?s$6D%ZSX z{e!*e-hIgjYm{5lwoAvFVC$-3_t!M1Qnp0rYIo|JfxJLkNtqpke zJn)?%;c=$s`XtMaVGD3_q`-5 zpXt+1GN1eV2zMKrx#3_5e^IKOfSXSS?cR_E6_G1;HU`0fp{*Q!WmRGGS;1{XM}S`X zLGlDPO#w;Hoiek3`&etg$o)gWO;*#lWHcO!AYqWV&idEMlMid)gN-3N>DZ=wi=Yu< zrEGhZU}EIwNwj%tZrV92Hxz24_>L(43@?DRu<=@_aFJ=(mR|)Mws~%>j%7s;O@+=M#y@nd9o8+oxJy@4;f^p$ zzSgDdBM4SD;}a#du9C~VHVil+e>uMe7nap>S?NXom&_bkA;ft#HD7Dmi|h|w^^&%f z?NQ$A0LkLyXVc?(ip0R!>sLr;Jf>_+}Z>)}U2ypXo6- zxe!IjqOBHWqh>CJ@bSLDl*-Dg5QXS=+7o2Y3Ri;^Y(>>`dV4ykfvFN)zznNu_??$N6wv zUS4wf-PlVBTK@uIXXg2ajQ3uTSaE40*aPR@C|ZM0eFP5mA4pnHE-7lmoW-=!udA@V z&GWcN5mTk>?iD_##hHoh&lr^tAJFG&j8&Ci0MIELo(o-dbhyHEYKqE2FySDj4o>97-;Q*M@NjQGBm5Bb!EQNU|7Apy0|5mIy;JfBJGx0ll;jYx6pt6E?0%>y=BuN&9SWemnhu zYl|5iAO8`ud~fXnZ#9vLf8Fk{edjLgYbBJsOaQ%!WWG;*5o7vdX=zRdqIPP zY64)ax9Rmk)NR+Xr#TSxC8cLOpKtWd>TK=oTta(Uq8yHPSBu}l9$DC%JRt@pXVIyQ zTHv}poX1W}WyGB(e#kTt;lO#=h@_e<*wB(JsTgF*jDD8S^`9%RDBOCXBzMj`T zf4aoQM$Z&AM@GB2RNjxWI%<3m9|)!@QO)N57?Jlw)$@pxZE#@VAnGron-@qL)oE$6 z|7dMd`n{#Y?s&Ftt3`A<{$JOYvtQA~$jO^m`_sjiM~fX~9=j9x%<X z1#9}nA~6m}4)(P5PV1tqhINRdxizC+9e%e1Rn2R*^6X{&>$Bfhnon;Z&)3>cGgC~> z&84txv4Vl`NK0#}3J-M#Jf2Miz2Gaq-Q89N?^15>?zk|26e4Cf;7o&7)O1SDa)5E( zV!oDk!sg@Sq1gn{x7Icb;gY403;2@+LBUXBooa*qtCrr4E~(3ZE`Do&SmEt$ZF`o< z`0-fdl?zO7>$f*Lk*O)=-BE@jgkgF0pqJyC-`?kDB@>U=r-_h4Ikz{d-{%pWR(`?l zZn>YVM_@*;O-wll+oNiy;21oSRZyKJGI-38R3#(itGCR5@L1cEU;MGY7`zx2SPDD>=>-F#{!;;?f#gg1j7&N2-RuDk}5UJ&b<%Mr_cNnm}x~YPoLN_O?=pbe) zPSEq??K~UHlN<}|20RtN&AwNahHct9i}v>R3$%X-f-$M=QR1;>4_9|DP4`n67F@31 zuYRw$;%bS!_wgAT8k+pt)YL>YA$%Ek5F#sa7w_M{a3gK$eG(=dVV9$oTy1#!8FZ-A;z_*@usWdiweT zM&Zis5;Jg7I`)GD5&X6e&RY?fzYUBNmz}`btq7cEeNo?RNXf{~CxV_3z%LIhvvs;~ zGU}5RIviIV9CD|1+yunLUfdV5XOGXN<>ftq@wnXml^_=jTv;>34V}%F=7kRd%^ucK z1iv5jG>VgIJCPq+Z}nhdH)@YMtZu`SOQdAbsvFyy&4`qTuC_ z`s@5&jmw!DK>P?eLPF=ZRcTnYTR{OW0U=?2T^-)__4UH-ar529D2Mv><%G0*Um+5tPDa@?!1(nQ|2p~g zW8S%#%O+wY_Zhu*$&vee=gYq6Z(5&T)y$8UY8~%N#F2?nZVe<3!YT-M3KRdUU{R@v z_wgq2<7M3dNgpmaAPiBdpM=nk%B!l%X?yay*3~B`e~-S3!|?7f0_1F0!0cZ?VM=e| zoCJSmW##vtM9gs(9qS@)PeyvuuV)r&BTNfQ_%N^3Kh{AFke{Uh{UGo8BS$_kXrV) zYR9dxbol5tM=mp|o@}#*YiMeQ1wB2VzV|x$VdWz$D5)+q5kg3)d-b^9yYKGl@0nkX zHK_Wdt4y;5wx0A}#}6xm!%!B$GmB3VgIf}|wr?*kE|YU}6Q|ZX)U~x`?OXGH|0W5# zo6bEO%MtND9OM5kR9~>hB7RhN9{L@ipWI}0@5!;QSl>#jyKu$C_~w;<=DI*f<9Yp{ z1_Oq_U9e0xqFm5j{%F<>Esa^5<;UW6x5350c&Ub(ogUmLyA?-76_ zad2nA`A9|0X4lJe@y=o&lNLD$S4f#`E9rNxUWto_hKAZw8^3QxdVyE2DD&I5p}qOw z?+?#`Z!3kJuDlHJ|%M64GV1yHIcc{3vU64VVY;q{FxIgw5uh8{SPIdvCFx z>E&h~yjhYPD5+0vazOI8sfmwV#Fqf@jedQn&S=1?%zOHxhek%YKaPI|u-kVV*Z=8! zaN%iO=q3R!i~SowYRsL?kpb2P3^AnHSnTWVX(FYFA1NOJU}_8CATbzULBS@^2iW= z<6oA`p+occ1XQI=o>%wx_ui1;X)8M9hB;haGh7qeB>4m_D=S81EYdF_2q*=BFRQqf z`o3}ue{ftEgjNiT|N2DCrnICSX@}>@Z%2@_9X}$$ybIDA0*H5d#wq>)b~>m;J%gKFFpnq z!s|8j)_sM^EzTS8>8wU^SxhQ@tJ5V}tplO~4}ZO4i8fk(eki1z{)2#fl8tH!S~ww< zM@0!nq9yYtDm4spG@`5qzxc-ee(~M@_b)T$^z!~}wKHI$#f=GzN)RzO`0;AYtQZqX z2&3M?`Dmf`_xS)BT*Lf5Y+Bj$o4%#6C=M#3FV$3FA;K?oI*^qIJriD11uo2d70(q7 zaE69qZ|)EqH)sVB);L7n5=qdI2TSwkSVeyfVm+Kcyf%X^8M^dh7Rx<=)INR;=jgnH z_XDUUVC<8!XM)yq=ru{5ujTRpao|Sfiz@EdAqMsr2;}qW`2iV^w<;Mb>}ANmoXnT< z_B=NeKHp9){t;~28IB|&!)gMtQ@J4o7mSZ`L(_H`Fv39d3f^V8?|P!Qg@T@pjVn8 z^kJyirP>jH{wT#!zC$jQjSuA)S#3y&h(JDC=%mO!S}+}+zU#r==tLUDjla5bf7;;) zl2CSFGkU6lN`CxESeQ~~?)48M;Bi4e*KvVH*-l!;6Qtn5>(d+e||py-ODIFFgWrbQ_)9 z7%;ROXjPP}*ckg!p%3Qw@83zd-I>$b zKS?&cjFEg&mrbT0HPzNGdUA4FiMqIO)kHyF)%X<60TAT)`1lA*T{yhk=^;Om^dL8u zI4yMy1dTyJ;I;WcLccOTBQ+aa{I`|XR>4Z64pOE>DkjEMB;lqzxPLmg73#*uiO0)L zxT&9K`#prYG++J|Gq%?TictDosrPcPP+||PyW@*K{YB{IHZ#5U>q94sMRq_5rV-Z3 zKAg=4&<^H4dyvA9iDA6Gz~r6*!%`4(R=UpI5Z_vg`!>a|+a~dsJkEzeSzp0ckHZLH zvS87;5&SIMC`|v}`7fxvynM35&#RYvAb~70vDq=cm56%H(=rOVh5}wv0MtPC`Zzf& ziq5+O5QAGy!>L?We@HZ$u4D?TtFdBpeFn6K)DHzgdbeToi_Zm397Y4{@|2&_lhe{> zUx!Lb_hVlGs{uFKCwQ%j+3A{^a{?R&^3SctD z#d{H0WL6_^IQ@Ci5%Pe?F=C0glRSF2Bsd$e6Q2bAg~NC)__ekA;X_8$4Bh@J$aiCv z$XgJe*4_Gl&+i6c-RJ&(XlzWz$dMhu%cR`g8XMD3g!5O7e?P@VlSKXS|@WfhtWO0d_UwTEsEzH^I#Hvlv08hVN{pWov% zs`PF2Mn)ftBp{7B1hyB$_w2yglfzL)9(xN@T73RY$InkJARqwaqJe>;7Z(>vX=#?F zO1!+hu-aSC=b>SWLyCor8CrOEbYu_2^9JblKUyZC zwr4MGA`R3N;WB_Aw`K8fT~3IaU$DPQcbhL!$@#K=8d<;`VM%y>RCWN;U5(N=4SCDK zx&!J9GsW2_)tH{*blqnD=lghb7zuqog(lR1Ur3Du&XH8I1uf;V;`@VH3qV#EHfa=n z){FAgquku@xRU^j2fAQkX-V_$-R08hk$r*w2Rg{oQcR-g11eR(o#{+}fL=}3$2;SR z0Bc7_Sq~3h!0Iy^Q&RuG7QpJaQh0cH#3d;MB?il}U0zEp?*tDZRaTt!+ zc?HY>P-6_ly$;LFBqbMi2FWFmch^z{ZC`Y{zKhyJ{Bl4Y-@6e!e<*+1Tyw?E%3Tie)d z7?OXKkA#Nt*t~C}CRI>SD87iqS3MqKr3L6d?A8)F@eTSVp6+?$0HjYToaS?^CmT!F zzgHY#73m^=H*wzvb}u(Bc1EpjZEc}8_V&=#m8gLTMjk%Cg7bsVfnch!u`!A^+-e>k z5NPS?1*;b_H3e^MXU9arWygmTOh0;N5MbO^$xJ~}0Zi@XL0JrYNxiw4Z^|R|^Qm$e zdCNEpBrKrfZQf0z&W*;HBi|yM<7(C1z`ufUT82+wl@!t$MtspGyje#*|77u>Gj8@j zk@;BBGFs@TH-2H&PiahI9LqjpTvX;8UYjpJs4lNYYAgT|+^{8gJ*p3x5xs@mc71or zh{i;Vz&z8j?)5JD_wLGR;B7HJQT=PI5`4Z2LIAperk#bUQiAW_|GF|k1yZ2&ZiBwOy&@c)iugwq?%C5gTA|3%- z{3aNEW?V`tN+xDz@TsY(-+ug%E+?qM4W04RKJ+uFIuiqV>+4y}ZYAYyFOtiKLLTL5 z;k)FE?7u$4e;t1_S|!9M7z6aAp{Z%}@2b~Um!X$wp5j7B2(mm4IhMJV74o}4vEt`I zB^4Dj0dnoJ+^^$V#oJ&-wF_Mn6B+yS{B46cyfuEjkcw`B)?E;fI8%B?#`y!FGOl%u zW&Gq&Zcf(|4ck0T2gkGfa~jVmTd0hY=N?y zk9E_K#j+tsXB!3eXXGJ?ilXia5RSM=;@QpuDpHvDR^O|3K-&JXde>yX8j~&LWi~oj zwNPgnOOqbXnl>gG@}YOo#~h-8;a-AM#${!&pR3;xBB{&5vMPx z5*>vnh#xv-cZz<{61CjN%OJUnJi>`wXEz7JR1#y-UAlf zQvNQUge@YdimY+(RYxJB*|Rh)t~9%0&(4PoZL#L%=OD~OZ zmdr%4QG;fv5t32Tw7OW4xM-2M@zOYGk+tMlba>G?ZG#O*pxLvZvwL8T?|`0Wx#;-y z>lY(R@C`%+Gs!Kd;TR<^7E~&$(h#|BQ=8um)f?JzJ$rlx8fcF3*ZXW0cX!X8!^`}! zT`4;|7XOuv%kJGv-nr(AFG`GI-ogn$v!)NVW{uG0iP7b0y$~gm;=z+(rIZuRoi9?> zONl4V4@wc;>Dq8MIqeLHv5$*d;Yto$gAXAhlZ`xpHz|ypJ0vXX6zj|e&i5Kj^^U=% z3q@zZvAMA2ClcG(*clIh@A6b=@jol?702Hq?A|LZLW?aTIcYl0e!Wo<{-TNQg7I2! z!Ww9YsSl`kaf_zgUokFK-Kfd_^ z5m@G$jFT}6E!daVPfvEiSMF8AP4D8!jw@`>ZKN=}7vr(SpACmOqdTF!`(Jt;BUd+q zfwsI8I%XR_YZbQY7&&WexENn^)!c83Y!VTBYk(Nr4=-0{p62HHT?x5g()8LX!ZgX8 zga8f6qW%*BdiX2~m5bf-^gfSk3;8z#tbHZqMSG%8jIjockxGp97vQ?pSMjJ-!^Ur| z$)9!VJit9nzU=JmJrjNy&gSl57jb1QkOE2a-PJ|St$@5t<4qlFtk&B`t3nbkY_v`4 zUBRT^p0cekiupRb)!KW;P6e+Q8eX+|9&1=^T?w7azXiT!w_|*IIRkIiK}Jp7kVxGZ zn(nEEP_{bSmt$Au$`kHNR!Dfx34fp3@Zlh^M*ClWBqiQXAs+WW&v-kula@?Fe6+)n z;6=^PS^OucG$ZTFS4_Icc`^;D7T=g)-{)%i>@1>nOy$Stc}Z1O?3;C;5?V5+j_bJt z9)i4}Aa(2jc9gQ?Njg0FpCD@!w(?bZUk)*i!nkj)wpMP30wa}7G~yQq)^tInvd~14 zm)`}4WQl|4|FWDE%L_e?8`Q-|8*;1R#mC47YSe8J%(;>wp-2U~BD8s5Y z8yJBu{^i(H2)5~gy69U|TU)f#Y|*Nr2zAKcc%KHi&g3pWSgAPv@(A?vrt!D7@7?BO{i1#O(btkI|KBPy6`195 zh;(sS*HH~;rnS0fgX>Sl1t>O1VG>W59D%hbltm!~@y^)RJ5r-4$m$|UCPfK-NkqG^5m7!Fn#t~4cD{oV=#)4QH1u7NLoAIUB!?cydvli3|_V>HHsnlkDh`e9lOX8P+s(dSbYd-=`@o1158Xm=k?0%erGGS7?m4-b)&^Cr3h420E z&TQYi1G67=8%9N)F{q6=obJO z%^bMjl0w?9Ck;R(Y4(k@GE4t1(j@Km?b4R(EYT_7dt!J|y#Mttk|eSJtdQyjr^P^} zP8G{H1~iVPwk|2|M2j>-6Eu|{6xbrtyq-|=451npT{o+Z3cBzAuhQq!wVg!8z9gwD z!2w}8Kp*HQY%mMy5mE?f6~}!Jn;hT_tgq+tYEGuo2&uJPQ_ zz%>@bF9|K5w@d`Ud1?DB3n9+3{t*r(?ACWaiyZ@v8lJ zRpGPX0(a_)Rz=EZmHQJDN~&ENYS#ybRekp6{w8DyaTtGgoF)^dRJ(v!ROt}9oc#k;muPY0`}j{ zaCBW=-8>^iko%WoIA1|+(RMaA5&$O<5D@mLNV3LGwBtTY#T^!Zy>7XEQ_jx3ZXe@b9l`bKh68Z;@nriVw%wyT6@tAzAz z%<2g;4`vtI7~Xf^6bra*yBkgniaW36DwhH6lI6*v?!mQ6nuY+DD?v|;ZK;0(4$_)#XgPe@1IfgOfIZ>ZTpSB#<^&mTgF%)*L&UM^fwuqdUyyqal)00nY)c^XK33Wl;9 z1>ReJNtY}bCr(o7Q@n)#`oNE8ugxOZ);hVqp18JV1nX3D4K@V@MM{AP@OKA>js|tB z23_D|U}9E1f;0O#e*PdJ2f~5z{eg~Y^D@J{p`Kn(P2l~96;i4UgN$?tI2mF68UGZ9{iHlFzgh@EhW770qQ`%!zlxSI>g($}IlCI4lS2tm@R4yVj>y5S?x3f7vEC0Hol(Tb-f;H$pkUF9nvZL? zjhk+{(DusvK)}P70E&HgSX1-is)B!t0Vxv?*Csq45%emF)@!L4|EnzAmH5wJH3r|5 z7>`yO(S^K@doq4q+)eNA+x)qE2DC3KI~xrN25staWf`3gB50%#&VcOwir_ZvGp45!L5892Wy{TGdvZ%_Kgo}YGgwKlr>Tt}kW)@NtKYl61O zV4G&qw}Ci?h2u(spnd>LPB~PBV=D||LyE^8(jOBJZ6yFT;87k=3Zmg8_R}}2z7-S{ zoPS{m^RZ1$e0O^KkP!+82WvY!DL1!wAG5dTs`SsHPxH_i*ys%!2T|Pn8@U^H3_;J5 z>F%D0JE;Y>!GNWI&hbH}gV&@o3y5W#?(*GpS#3}u<@o`kmN;pM`_sr;I4>O3=sI6x z*c-y_ItLF}n6;y0b;}0MXkFCcR+Ma^JR_cbv4%WjD$8lALQ#rB(UeWmyVn-eHbok5 zU9l1YeE>u-DuPl-voX7WQ7wq1{XQ zlIe=m`tKOJ=|~_8Hvt00Q@d`f=3d*{E(^Zi9alKrPjG!sej3dv^c_&+3t8YY&iqai03E!)9?NM#r-WT z7?lWpQf9=*q7aQ66uT9G+`4d*^pP>C{C&P%L+It~qa%!e3LdeJ`a+0<3u;JtQ*~U9 z$VP`mza%-bc6aywW%m}G=p=ki@bK^ub#HI71yj!U_88qJNei8KjhvjE!Av>c@cPY8 z#2;S(&AgSt5%=5zv&dFooso}l)hE^IbP8|%re?HyH zm!s^iH6M(sqH)UWOdhpx-ukDEkr-!k_u#?G#ui=8Qc}1(?*eeOKcB^70fIEs(9el6 zo+}os>6G2`=TDpkSn?$;l(FF55(bD28bYxI2BSD<^J#n2N6H5=DfR>;@< zZVi8kxFMHmDXQzwC|Z5BE6f>gBAOB9s7gUD1Afc2!9qNRIvJ-_uEqI%91LE(_rrrX0nQ^gAQdo;RN1~lUFpuhNG5N-4%WCuF`DV#%o9Mr#F z;AgfRznf1h0TUts4dmo=SgVvkC!0j?{>h-EtZe!;#}=2EVCLwk;>^~rL7Smh($+?n z8*o$`v#hgw>0{95iMczG-8*@GSir|TRWS{qeI9RveQFZNQaZu`El3_WF?|~8ngog3 zK`Mshjip-)`I^t81I#+Doty@{+fD}zy;d3Q^ksXJ%$bxCEm2%igHBao8%n zuo-cPUYDtEbUvOMXJqu$u|`Bjx@>Iv7=T?J#)zirm!%tW!M$YI~Izjp&u zRs0-yS5k}h;^-1;ZH!PCvTV*tOkOpzb8qtc`ucCVS^5l5_xC;!vK3Sb$3eE{oLSp`8v9_2U5FP-(sUMAb!W>NN`?CS&TW36aO6R&~)=#=hf^)9F-d6NN!3YC<+iws7K^ z3pS%aN|c#r3Q?JBQWY7w1O;P&ap8r&%NjdtY$~WPL^x)UfZ-ny&}$}|9|%O~=u(HN z0k{NEk~EeEH%l@>x!fI z%E>GW=+g&m1lP9JUwW*+((r~ysIV;R7fl)i&))s9;67Z+P$_|{jQjH`UFL0?q-ZWm z>L^X>80`e-kOen@J}~6Wpw#@K_%li6XVP1Wq8v^+JYPKU%7ZLz5Zg0E_Q-ajoL`Lt zbfqG!If`7>H1ka*UmX!N)jVhMi6~j#g4Zv*&!fQRK#^sV({}nTSRC_0XB3>tXWjU7 zUJIS=5$@ZyyGkz-Gl~O$DP_|PNqFfZ?m;r^z$}wx*uXlxLXx1W246#I$?a)a(OQpm60J-@&R8VZTVd&wOU1$ZxFxh0p!9@Eha*~G>Dl9)L^*n zn+QMoo>2V5CkBtNGx&9dbN=j@1>&)uNevY{&*HtO!2^?G4t*+zCW>}yxrjsE76oqj zlw3RVOL*uSqCXoHs!rt-3|kyF0?&%H6_e^djz*HqJmlDb*rfA+p3&RP59J4O?N~E7 zHa?f7@mQ0tY@^;KDu zSceX(8V%^`2*XAz$M?$lEfhx%ook4ktIFNwmr= zwAj<8mVELTyf~VCGUXVZEAJDjGU9?gAGx4AD%Jg-90ao1@-GPBX#O%6yN?J5cE@0d zcx&%;=H1Rr1@Q$jdeyQVewj|Fkt#}&BFH&f#P?KKj_gWUl`aaDH5O4wb&m?mX;3D9 zUWKL()2on+u@5teC;TQe%M+a0yZgFS>2;xlAREg$96ohfw4>e?oB-cdn(vb-bz%Dz zUtik+7lfL+OYwNS1xOX)rKP2}bGhe_-F21LL1s@QGEx|o;#F9a%zYeM0Tvd?APxiJ?iSnT* zFzo5Do?M~SCHwjdBN-gTmBzZWR$eOJ?PDQb3O@Jsga6%{@pBsU1xoPEji>P0-!N&} zNZU2C9|4We)^Jth;a>t6__kNTzR-X2>4mR(ey|)D*+ievY1fcg1oh#46O*AJR6*Un?4?k3G!+VtIEaCmK38 zr@bkvumBkWlkV7@GF`v@{3S32Sxo>bkMZgoHxBxe)RV@DV#m0dL*Qju$=}O+>E)fR zUb)20uI{k+9vAd$L8l^LdQCrh-W|g=e7v6(Bod(#vai*hT%8W<>=eDazAXgTUB}tRhXP`gF87I^@_w6%OGqfzPN&A~>YuWbsw!R9*ht+l)Mf5* zD{#!}ou0lvWOX^Vb8YQo&*{lYM88>?-n?V;*1JT}7;Gw$nBAA>Pd~)mRIWsn(CCZN zXR0+-w>gmFA>bPp#8M(aBh%9y3fK~(Hx^Lp#Rfl~fQ>aCZS%oks^{lt@4I9{DnuOg;ll@AG(;Q>e7iIoK0ZF4DQ%1{nu{HD z)mXC?qDtQTPE{;Ei{G)5rO=B>rM!A=TXm^fkMA0X(l_zNMi)!I1C%F%mKVV$B6n*W zexktoxLju$YfYwM%Kd$EdO9p8C&vRiYQYWYx+vKU+}Hu!Xla~yS=?CJS_5CI#Qr%p z-l9(!x>bNWZp?3NBy>1FwzhMU^V25-#1X(+Ff&Gf`jS07KAuxSk0v;|xkHJKyy2!( zQ_JtTdPC)i_@r!?eKwNlhympbTGjtktwH{EfkRU= z{psB~C!l-3%Tp&S(E_h&iZtJqXw!gWrD}g8LGJ$+8Mwb=B>N5SyZ3Ck_v|}&?R)la zo#zc7%WAs4%goH2Tv@@WnMbdgh)ho>>c&jH>iPSZ8W{z}<@v#*{b@7yTTTw6nGW`5 zhJMZ3kTGTw#G8<|N4X z{(&Cl`MS*1nlx117Ti_U7S4j%oZpr3Q82dtzZM`fZmtn__gJmhsZksTHejd5{dWuB zScT{Xm*OQ~Y!@ptnBm3>LJ_s_LB0twv!F3E3CwlZr;0c5|80;8vkLlIUU+*xGJ8J# zRh$+LjFZK%ZV^9DQNDcNUH_bB>Qg-DX-#8QJR&W;IC@6{@W;pe||8`WRx+lUwuY-{1-zEy7Ux2|iuX!VU9{=&F*zwipadl#~(k_f4+K9Dz^o7bmGv_uaI1qJ8#A&0mb>L6~*>m~&}9=ul4=bU`RU_Ij)BgF~kOLvV?#Wx(Y zCzF#nfw#-+isjSd0bN_?e;2~AH=)l?P)z8&S7%s&*GfJ!msWICl76$%urXR3XKsx) zS)XKuM8Zi0s3#OBV{a(pa{8vSL);fu3yTjv=O$u>R77{I^%BFgQ)mrWi4uG z)!~<9pEV9isw#~FuZcwUq&(ondxwlTXp2IEZJ1da69|@p%TR@$czHuRZqL3?_#?NW zpfEI+!TGw_`IGOC=MMfvCr(oQNk2cIdiQh2Aqm`2$J0=2B(4RrR=D9oMdzAiDW@jZ zSVouspsQ}DXyR#w3_a_mG8;^wfZ3Syasoxc@(f7i+xer*{uILxF)u8D;@8=egx~Gl zskgP0mdfGrDGJ$zWRim#^LOPJ zVn#;i`w>EeR&RBkDJIjwq3s%e_0*i(`A7dakQ6#dFREni0NKU&s!?NF9``)kZUqE?C(m*h5SyN2eTCUU{whFr5fGgO20V z5Hj<1Si;10iAGv^6P`?dB(PKqR4V0w#-i#c5miDc#E zxC8{EmG&j%T=R8?zFRya-;Z*9kP=b|P2UM_MGID>5K~qUjzGmRUbAiNv8spDr3tYQ zXIlDr0T;#O$*}HItJ@d1o(LcU2lxt4m=fFbcnOxLUTAe7n^h$*!X382d)c>vB`JW3 znAX%B+Hs9JE}xaK&8BCbHx7H6y=EiiQ6(G-w}KZxR_icwyRls^mO=dA+IK-)1v*7` z_SwDvnQzCLPlt2ss&m`wVal;?x(xSsX<^DiKrsKa`s`wEffynlydVG?X&U4Ct|b3c z11A31joML7EJqg|mYYnMZ;oModU$xyrs{5k;3f`k+&*sP=0vQY4*sd6N_PJ8Eg)0S z8IP}l$y{yN*7N`d z7|+%1ZDBK*3gP*AZ~r)aVz*+M5P8Fy1c26%a3oe_cvMz)GZz>2ixa=bJefgT0wD4w z5?y4ViS&mq-BPTJvTX#?l)_RQ0x9dJxC~6|)SYhMUj=cWstAvyWb#9qA;fw;>Xa+~k%eq-@UPlK- zwxQwP-k$j9&rD0p%aG0Mp2O>&t#fAESiS!$AGUOjj0Rla$H~)&?(A3sCq~Rj2jsgn z{TB|zDnZGVKx>Ae#{ST?p3rn=8Igz=0YF^YCeGMMc0_^d7vtFUo6Ve^aoRkOC7q0O z3$pmgv3kWs;=g}?{pZgg6Vt%X%_;Vl<=?iiF>&l-Ef>?&UBlmCR?MO~DPS}-G<5Bp zcOAQSAA9y5yL9hdbZvR|S~d4t0Y=)`V^-g7RbTbw&>Ut;k{1dk@1@S2pvzXF%bfr| zu;6|Tgkdf2Xld?f3*iK5!DuboMsAAKC>qLmmXV$e_$r}ZUxq-SgNAKh=9_bDfL;c? zg4Ir|lu#zm6D=4)SshfCrvM}LM@#i&k2h-}-Q5zuR~;dqBe=2soW7Tg1A~KHe0&i* z&br!JMdITv2o~YNT7LKblt84v@x zJ8eM$KkRVeR@1QC&NlzpP@JLpQY^1;xdG2~Ie+r`A>v0A z8ylOlgfNt~mGL1@4?R(jPhr`?1j*LpN{`|2O_hFN5GFR}#^gq?bBn>?Z}7=S<124H#N7`#;BR-AU3&)>mS%%X}7)|u;k)N%c&@!4;PQyJPwIez;0+# z^N~9EecO1f#}Lp@(f);JaD2 zp?+P~nU)g~k5vJ9oldWl=HkT^;HR*RU~g})R#MB%1<%;yXh<>=0vBA~>`+dzdWYU| znRanuJwCR`70$^5K+1(ixnR^aC+Pob`Unc?(72y`O9DdwNp*} z=f##BtpykQ^R$B_RUH$rCdz7pYyCNs@IymGcjT5q=u=fyjk+BDF5{f%IX9Ol_N@n^ z&mg*q86{w8QP{Q9r9>(l_LGEBzvkLpg7G&(_L#_JsUbHyevFO2Z4$R8(0E0#8>d}H?{D?*H$$C6Yy9~fcf*HOv`3Fj{-vGu>Bc^4O$ zq}-;zaRFmN%ijmE+W?zKY0s`rZN~V>As(oNs$AoXRs%KVYT=yR;|a%3-9FXQCQveD z2cV8aZZono*#4;PAVq5wbTcMfw`$etMe_mRZ$GYn$64WE#u2V1iWTJc)Yk#WgW^ZX z7@?P8aGUITFH8^Iz!z)6GnGlzGx>E{g)PeDb5i2;ZpQ_PLsY zHg|0vjvc3?0kii<(HZimzSqw6t9+I>-@&`d3DQ$A|8WB9 z-hWiH3AlxpCnmz~=8EcEmj&lk1sGvB-o1Ndn6eZ%>Vv~cbxQ3jZw=e**}*Mb*}g{i%Yh+sik)2|^U! z^c$|`WVC?kz~Ij6Q)lv#>FG$=3(pd}fUFAvt1p z>JOfdr+j8dbf|q?S-RdYX$(KlaBY57yUvnrIBtUwk&sN8uVrX+9$hcrkW81qNZO3ExzCm)KbX&tX)Q(hOXLI}fTrM$ zcuZ)HMfTdXQ(B@uy0DliDopq`C)Pbv$ z;~iF5C(FVhm`W(^yWfeHKIq&FIB$2Lo+h?z4ZL@Ib}|k!jKrWmd#-5YsYqh#q@d!~ z*Vk0>k@x&Hwq>5-wX*o z6Ybtp7c-5NB6oJZfEwry4`vrejtM^}M3mgF|GNSxD@qG7HsCfJE3E+s9M)?5pdX*@ z%|`}ZpRrIxub>(|8~j!=LVj6I`kU(E_nc}p5l=dKjIh2pB=p}i{q}gm36p2u=jZ*5 zI;USCji?Bk7jv3zdN##r0cNjm%K&0pL31*Ji2~@LRL#eY$DzGp*17tpT1lE`L>(K{ z^(912dJx0C4W29lW@^cq8rv9l1pDgo0d5U|UPpy}o#PUjhGGvIm{w^HEr2D%Zw$v)8GVd)UD7aQCzTDYGNNbdR zXiR(xMT5sb1w}E00)rVlfgE_K3}wZ#W+%5wX^5;hy{p*t^%a*xN9!@u#9BKAGQN8C zijL{NiYWaHfj1{ku*c!RvK%g3_<{X9BWu;~OsW0hcXbD6r@2M-v_U~Ze|L5$Wg{(# zyR6Cc^2FW#ZOqfPynT8Sq?9;e+HBG3hwzpR7L&O=hF;YVSMdjt!>ubnvt5P?(8Q=v z$`?WZ7{Z?@x3#xp_y+`VTpBV6SP0$y+CnHUDTWEY$`Uu6m;McNpM)HgJz$NGPUE z&A)ZoMr8FDE>q_L_8GNZJR<*niEB(VlXUbu=iJ|)LKx%80P>_>QGImd!pBy9ewyjn z=p7TRFLK@lmVRsa8t59;fB7hlIQ zMa#sXLG}0XvnTu0dq+7frNkgNFFE&B3y^N|_jf4!WlWG_`%pyYL-m`XmEW-pU)xcE z-xSc{myc=hRX;4q2dMP+}%>`50>`9rK0fKPF3F}I-olXZ1T{}pkMsRJQd9xS( zPaIr#Fj~G}vEdhuN6-80wmD1t{0ROnOhiLVBaEnkhlBKZV3@6;Cd^LM^vHCK7fXFM zY2BFg)qbhX zfXhSk9sP6k>*{-PvS^9@mIJwF^qgZAYBc?MxvvG$HhF+phOIhCLzIC|0bbhZ_p~}4 z+;B$jXhzhTI6^@zwPVqDxzu21B69F`M_Zu|-As-B;1)>rw6wJF!WBUh&q_zc%1FXi z)%?Lu^u1y()V2<>6dhD6B}9)E^5FiDS*M7fy|F2;g#zW6nwOHNY07CWmU5>GO%39Q zw;sxgMSxUmWvp{5-UwPozOr)L<~ zXQh`+{Z)6PYX227S2j0)FyW5?PZw<96B|~RxBl4ruCq+UorHhon^^Ec8eZQWT3E>G zr&&HYIOrIcLL(|Jo>yEP{m(N_nH5y5?LCLYAC{W&BVJO~8kaWByZ$8BlR3g_E~r3& zl54F!gdHZDgviQ;k?{Aw|Ht3?2Y16mvXLDAeovnlcz+ovmoxI4exljVn->Gr>SY#< zA*-t|8Rfk2B0n;Maw~>iS~V0DUSPM*&CS6H^yF=x(?ly{RN7HR+fW?&F&5iU^p0IC zv%UixNcIh-TCPkCYyX_uQ(=m>f#hV< z5mc-V?&~-ak&%_c@`x+>q~3hOOXHF^z7km3!hXoodio6^+-Lvr-P00n+6;L)f|kDF zP^ke`v%CD!O2`RtR^KCu#B9@)SOmpg%H~*EOOB#-$1&6Vt|yJ`bf z*QL&h2`aim5B3yQ3!#$px%fncIK66Ne1Kh$ISiG%jD8n=Ap0vK}-z zU;8-C$-tMEf=|xQ#H75btwhpRyrjej{?ut65dVtqD>+=B`%{Q$dpSM_Mh+f8D`a7gzUY?E z0!EZsGk$_bQqlY5#7y3%2Ihc*7+Ux3wXR8I>E7PoooxkUU-3JUJ~i*|6lNdLOQstL~r~^73;aZ}M7XmU8SNUtrypJnJxq)$_B$E^A5kb{atVG7wbS z(ev5?q3;1XFizWlF@w!*Z!X;*goXbq)6{V9lGy0zyP+Z73sW=dJ>w-PrYfYr%7g$* zSX@4CcJ}$e(14YTkI|HAAW0250>sfw-^K8|(YpQp{q$4UrRM0nO0~(khJ;=wX7Q`7 z-|dwq{453OJ;OaRL{Tq4KS{)37E%GY;YI;iJ*O&)`JL9gQK2|QxGA>ecvxg6=E_#?gV=E= z9jCh+iM+YFN&J^B?@MxfHt^_sJYp)(eK0&CtSQaO$m7hlHthg(stwxB7EM+Z(&PO( z1b}aDZYE4k8HE=bfldp?PJX{PHY=2*h6>`bqd`sjB~CFHuFf6;w&>_+?@fN1e%z3p znn{QMjdB^K?mkYWV2CGl3H`RYX?wTWMoU@)CsZLUL4w-)V#F zF4SsygnhJqe&_Fxw@|UrHO+s$5z4S_?Lyv2jYU#sIwB8{JI$7iTx)hqO;7-eY`5MK zLs_!YRYBK2IPi-n(!5Egx%%9HJQ|x9T~ayqiQ-A20kRKmpQpUo{v4|C>y|b~MdFKY zO-;=Yr}4S55HN+xmHXQEPo<=9a?sTo6 zuMBV_yWVfW3^?0O5cuA)Dja;gWBJP8zsW%|FJ7Yb7|kfM?Pakm2)72~j{sz>56M-+ zI~69YJlFt7Mlil!BQH@=lMXF;2H3?L$-OrDgm7v#V)C4dtOa}A;2S5-&;o57t#^G4 zDyIA?lcko%_3xZrB8rOfbru5Ra393sf`L-9GtZDi+GXu-+x^>wDB6#)(u4@iVWW0b zM>dQ{J$=iSCO`g3U3u6&9%I{6E0YDK(_NvuY`~dS_fY10Pj}E-F|-yJz8fNw^$Fqx z_r`TWL{Tq@azT^Fs!|elRG8=;)_#R7EN_luRTTleWeNi}BB>ea>H9zr`?T$*HYPXL zVEzv^S&rpAI`Ust{MMCp)|LO(QmS88&yFmNg>c5bvF)E7o2quHXCNu^wC#T$hpQWj z36hbrF&A%B`OCq-D?V!4MdRw6NuW65D_^)aB+ziV-ZUhL_M+HUem{emSrfL@3A%fw z{rG?7t^qEH7sxNc9H1arTh4lm^E4^xaX)?dhggvp@ftr`gGiwdd>{Hzbf2GEW=lVF^nFaHkNqEH42hBdiT1nRdpf!Z z1qmue393Y=yL<>{`=R->sZB65R{wIS{>4)TZ}Np*4T${V6&}?ziO6$MiG+cMIWvRnKdVIk|X6zRgy|l;LK3r z%up4~R4J@^EOMT1bl53XJi?b?NQ^LW^6~M0d6xm7%*`fk1SM&6x}#2~-T&O2bGX_H zV5_pYmnNbm@}CyVBn{K5FTFZGK28{$cgO&qF>D+6;p*3rwx$0zZ}vM1w6tU*gwI@6 zdPxoTPykRREV8l^A5o~YblO)AQ;a_{5QUY+D z@W_E4HX%t?Xqgbjsm6SzGkAW*(u~+?n24`yCR&(}#Klb$WJ74s5zZ~Nj)ks!lb!!Ri*nkhV9;6xbYFfZK0v+lXn@?ZwghwC};veX)!| z=VT%c4tmaPA)B$;=JCx6F=hCb9K<;flJ9ir4EaZ1ORk7KC8@=~)7E*_lr1eScj_2) zsViKZk+HF_-ynI?b(OXzp*bVWxA0U!8Ao)Th3353DWd-!bj9n+};HGB|m!s{|su8AX z*K*mUvfxp-w-xmCNSgxuM?n=hm<&p-F=jpFQ+>axUA zI5xEk_yQM2^tyn6faNE9GqN;o&S4eE2VRU%6VG^=H>Q$J<*_QR-v9qvfF#}ZSn;K{ zl_lAW88x|xL9;r*WVVxwiinkt;3W^4$>Md8B%>-jMR9}E$1@I?X3)+gj%-eTb)7Wh z;boxR>E()}v&!8%RZz#uG0kblRZIo2n-F$clY>Fh@K7XVbo9wZQ4w}_U;UFP%i$&t z%)aj#@0_IiIg@(5GgHT&i9LP#RN=?{##B#1n2n`lD-(t-pq_!jy%x$2MZekk2=*SW z{7Eh;IXMv6{-JBV(MK(V(4_H#K?m#ab7O~_gq;q$M!64YbnrsZLCXvRcH3v}=vr=d z=_k{ACH&cK`!Rhi>ytS>xisyveW$_{x{;(XM{gQcGCU7ynfm$IyLGND7NU-1^47bq{^y?VwL`?mwJ$*U{gT9wvr*ogbD&t7sr2O9pFuGF^gW2^UQgJK3z*TC zd7#Em{~{<``*%VVm+{#*JN5)i;3B$1VEo^QAVd3Mw|{06*ipVwP@aC z_u443Q)r<7h$9*SIClKj{l+RbLgo(Xv1Yc8fI(#679d=}CgjTn-LL&hkB@~hOiVP5QFiDq%hDv>ZGr4x!^n-FL2$?3;Cu#^_qE{NPn(vx!*Nt3ZwJu{GVbanJ>S{Rz#tBd+UBy7hHbzRnUayxYrW4Nn0 z5HPE=^0XI!Eb!pRQ~ZVTL*%6l$RR*p0;>038>6~Q+voXDd4w-zKym3L7NfXrKDHpW zXH5>KbToDfNGEh0-`OTmHrhH$YI9OD+D>*iGtOcXyFN2-*_Yc0Oj5cGnBkY<*aO3ZFLO`Q)!C_LCm*f9(8CZD zxx{3`gWIqsXJ=+;#oQZBg&FO^E(5b}wqWS3M+1FlA5o+7;tpGJ8<*q&`djvG7GFmk zqg&m*0!d(DYiB}5m`+=bHG$U5%nb3(Yrl~Z?DK9~;C&tdf5`@655(-tgLr_}po{qR zcQAvl<*n!GF{lFnE004k{WrpTF#X~47u`3ZSmJbG2Ko0rxowRV;~kI!>LZeH2f1f@ z1Rpd}{?kR#C>qbNoIM;1W{9=p{lycy04 zx?4GYC4(o&LI%)9@N|AXa*C;+{S{A z5Zlwr6jVp85*0ul@bc ziY(|mEwfKGl3;SFLh*elp~&R}QkCg_ZjxZ*EJGgAC(Rz4eXbuF zC6Vrd73pw1f^bFmw{IoqM`v4KU(}DP0K}(~3~(vM9L6+loJWryeNIawubG^fsA>8# zuygvgu^4}_nd6O)j!vR6hiDElNQx*5*oTx^-`TLf!*hLW$4{dKHr}*PPG$mQ#ts>V zKTkLvf4^loTooCowyw>ZHc;YDdU=C(Vm z$W5)0*`%XapBcF5wA?=S{KRtQ;HEMm)(R zBtga^Nz5!!#vo48EK%euj?D1>H!El#sZ8OFaG02R9l}JIs5(MF&76eDHB+iXnEMc& zJ!qrf*0=u=Gz_R@?(~c{j+0!RxxFn`hp?50q)pZH4Y1MS%>H+egl&qW9sI`{*(-0j2_dE|Htt(g)4Q`O`Z$CXJ8+tf%}m7C-Mx+bTsy^6xKaX$T0KgZNTuqBF}{IC@Hx)26f@(W^#q-jU^P00gP1mW$C zo?EY(wXbUbp!r&iOz&5GEJ+@D#Zkce%_Jf+atlPSEaR1ORuHq-jwC4KIIyZ8f&M7R z5NIaqWo3X8#d?Pxx%!!NBv-ImudReAq`4G|WNSP`BAecoOyJoRAYZ+D^=@<1*5%Mt z3kxs&(lPqocSeC!`hHKy#qn+OC&KGF$MTzxwzE;%*^kKyBCCsSOx=$6{3#%!sFjJo z1L`jr1eDr{6>^AA8m975qZekYyMGr+(tG7;Vz;(vX`+U%j}!VgKRC^TS3(fr4pYqS1h;aV3#!<*F$1ZjzKL2q=9i&1T#Alc zg^zhslMI!pdP0|`6Q%;<3w=gMB9lLGi?g&5_WFq&WT$EHiCJK$3R!0LzOH`zgA462 z4)9OCK@kQSZ3DxR`FQY=sBBkl?rp0~kGIgs=%jXF4JuHU}eQ-~Hzlky7*ag|xL zG=g<@U(tGrrNBqn3=M}G8^xAQ#Z>WnMS=%s+mEUK6NjLD`u;izS z0sVQ1oj2}+FBa7m4=V_Khg6q^a^uGbSO$wwKcXwQXpwp-FMrFr{F(lKJE~n#N~;mi zht@LiXj_}}5!X1PL6s(^Ys?F(g!R`dvPPg) zr?wN@IgA7AVSdS^g7kz#x#^bPx@%l|tHx%+ZRduK=|59g@f!1kxT58t^B3q8#Ac^Z zXIio{oa{0+2Cn%8o3$Tkoc2&?8@8r06xsHYI!0d~+b9I1j=sDzMRlm_WeEb^A9_ z>>k(_*LL-!aYRNg`>-S-xS$wroi_XoVX$3}!kA3QS9d6y9{_7dYp|`iZknpg(@_SWc`{35`u;5qXVub?L{Sz=ZJT zE0`Mk8c0M;JSZS2h*Y|rR76iI-UhO?(PAlj^B##nxiQ*W${!Q!FSW@4XW_IKesT}u z;<)W1?FNR1izq9@R$RhPc`@w%kzLe%prIzKS|-D~g2o)Q*`0Gcv$o@HFg^b$QqRe0 zsB^JSMuc-$gO?$6^IYzq&{mHJs}R>UQXL+T_bvT~G+Yv33@e0u-@|48_Sz!obTV6n zh%$N2WS>|J^!8|ewaNL1WX$!-;UY+*M?b#f)G#}+4aG3vn_5_wDr1+56;~;JBgGE8 zJ}rO>i$9iaak=(OOaO-Nbn24-PN|uRUYm6yx=#t69EC8VU}kQc))dEBV<^cN3q8!C@7Wbt&`Mso4H$Pb&@9A{jO^r`vnn zz+}WBsYx!v#zr|uK07n5e_&`(V`)|IqXf6W<YYIqBWEsekx@Wp5O8r2 zPC?&C$q=Udz9sMyIapa)`KImI{+ld$6pYA@aWS4}Q*(3s@Ne~2 zQxUF*<_~MU2S@S%`}uvhWi|P^a|^2IQ&5+`vbW!6d?yyID3&3heSK4GBHFqik@E~o z|2#Cj>3(L4HI!7bOgTc87FRSgH3dD1RGQ*)`N&peS2?Q735SI2Wdlq%z_Bn8fW3r18+cU95R4~ONhcgrcFJM#y8 zAn`t)v@t3JLi{$bQPtalf`~tVgwwYB2~8fVti~B*2RO`^{)xfCC{z7vO3A0=oG@o0 za5L(X+NXfk+gkZ!c*r)bede#9zlQ_wQ|@o=Hz9bD9ns>;fu0n}p6d>=sBqOA#|Db_ zr+}g)kbIG5f|WCt{@YYDvfPFacpLFjM5y&*ZMbUIDGEBT%$A`2r<;q@AlJ(8MF?~0 zC}5BiRP(iN(G+dRQfnHoIi|{bVmeaNqS`cFy!*(I@pO1tU?Ut35mA2=scOTsHem zYdSX-K^K@dd;$Ym@5OOUEr`~<2Tb!I4znBclj)y7sJ=fXl0^_cz8OPHiz@cWQ+(Vg zf5D(RG&E>?eA)b?2JL_kn?$GfZz92?Hzep5-J2k}F&>QRz}*mH$LQpQ7*SvXz|+fK zb(F_t_+SA-r{J7ssigMAfv=@L^*#wOgq+@+uXnwf>6Sa=GAfyT#!q7`)I3r$Go1hF zykRi~)FYAe>s4-C&)M&(7`*JoFFl*6DlQ1htI1u0hR%#xzYq7xP~IzsqN7bhGBY#! zf3Lawbamm%4ZMDIk$q$tE|+wS2$UPnl5n?Xrz7&nxB(_{5sZ~vSRaYtPTM_)XhE1j zxp5XBbB#E_rD#R%8?BNXr~;@Z7bbdAhSiJJ9MU_lw)}BjY}%|hEtQw|Klv*@@VYwG z%1nhNLAxs2Mr3m1$mC~J)2?9sxU~o+6=Z=}6E5125l;n4s4Gg*VYzm=iXX!vA%4RA zKr~(!S72_8IXL{!0H4|A(7@22uLb&u2j}{7*>Ci+WZpiaBPy&&L6vZH8JlhmGaV7O zl#AY4+MifH#DE$AhfjEf;FK7D#sVIFj{*7zGsL+{jdp}c?@OILuAX&zR{iL4ns!L0 zw@gA$H)V97=hLLx>b#D97Z0;2RSa!T>ruGo7Boh7h%-m<;TSWHgyFyV6PMJioan!o zBO>-Acs}-=x5_d@LjwF8tO-#roumMm2oBHYUE>7!`PnP7&im9BCp}if{#-RqBkkcx zMkzFl#In$YPXR_w6lozu0+;j0Gw8w_6gM!fuZw3$4N+23devopWJ$s#hsy7@sfu=H zv`4=0TEDd8Pr2*fk7qs^^^=Zl^u#${CuKA*9a*q0f@=;ruxq9G)8}}pYbct@?(QfN z@?vd|-A;&+IUVYL`MmCMNc!Nz(BNQKMQ~R3t;K+WJWIO}*Ddm}RUa!S z@PyQm4AVgwka(ishFe0!%OUSZ4m>(oNs>QzcmQrfR0IeaufHRy0yHsy#vy_1PkF?^ zE**kD+B)_xbuKOSDH$w839?IYR9Fw0C_;(n{jUrNitlF_kth2@W#e1v>+Cs#IaB!r zaWES7CBEnt7+{ySBCIP-j?y=d-=6K&FF(l4&W=<1jN%hGFR4k31G5YbHrX5|Tu7$P z5!WxfRMDP-8GTiLOTT=JKEF(EAd|1inrRHx!l^kT5xf@*1KBg!a!1D zR5v3PS#u3^fr{WqIbYKyfdxZoqsUr2`iuU`7S>@NgyzQ=c1vYpN=iz!jmV{5_s9DI z?b}{;?j)0+OK4&gbM-&`w!Z7-`gx~6g@v42j*ca@jg8OIrv)oRxM-dA`VqJx`L5riQ^CzJ&W!3~q?|&1ZEbWK-m1Sl`(wgoJ z(_$*J1)}=^UFJ`z)SApx3pIFZl0fmuN@p*>O=!%j8gk;eHzVQS9h`A=!!u_p*lZJ zXNx9%G1qdoM8nBb@xNOef0Y%>7UFzbtSFMkul%YqP?X-*v(Y@kG2`>-+tMmV5E#}t zVEz65M@d|IA8o5=Wu_>c-lib*d$RR*lT~j~-0+ca+45Jz3Vu@1iN8$xuGGL*jtqf5 zchGMRY!9|u%}YRgcDY0i3_tnv1y4l4;V)dK?fv}+hDS0}P|bXnZs9IBi7%w#if^sL zm46tO^uDam*z@_>yXyAYSu6u2@o7J3VtO6GKj3TDVi};uu@d=ZUHSO~8L|GjOJmUE zps1tL$XlE#rGsXyJV8psE|=a18N;-V?wm}n>TiY49_8L@ z)fSz=*mwS3emA;b%L)MRLBv7l=H>!ZNhKyoA<`XBT$qie+_|OP)$oziC&Cb<4y-{e zxttbB!R_G2`jCwo97eznOKsrKy z8d!E3=t4Y#S?Sqsu=PFbaiN$&N zbr`RG1kYJir*>;EhM+6n}+x(e!Um#I1R=8_s)mK z5KR9a9UW-#CBOC8yvcnESIl*RT9zyZv>U=dNRN#JF5<|FJPzKSZXifH)BXi4UuVU4 zupZQ+*6?36AeEyvX?2Bp4?)RcDHDvk@a(k_QM^J)aC6|7sz(@!$U-Zv!hWPg&Ak#51SvL5e{=?v<{65kV^gaA0y!}cs8m<@uweyAM z9wOq^$q8-<6{!t=kYTUrKY)-C6ns*H@P=!AzppKqJ^V#3=nCan-f|YmPeZ6FOKK`b zZaSxcaq1;VOvhA%r(`;p%V8(EWha{McRVN_d8a9HS0lF#*S~ANyhRoST3={I?SxgK z!snjG9Ae)VZSn%S!x!$YntYh(+q1IT`e=^m6s9CBibNfCs+pV^tT}4tU-Ijx?{0tX zL^9Wm2~uFlxpU<6u~e44V8agQ;CKHb@zHvXEKc^CWjo)z?BD*;E19f`1l94yM|E%f z`LoB1zR!DgW^;8zGBNF7As&t9Z!eLX%dVh}rWwRt7Vtw>%whF;>J(BT#@wo*HH|!S z)9V3)k&%&u!`bpM%NH*eae_AnIBuR$q$J*$9|iN=8=*cg%Eis@lqXlJ?qZS)?0J4$ zXG4%p)F+XtDSdu8UZht*D`bIkc6KhMQo+1YTF!De@_?w`ToQCOu=S!awyUd{JM*|5 zU^iE_sH@W*y^@j=Gpl;kwnwX|eP;EJvy95ti6<*aa10ifyRT@O=JeOsKH#;jxU{`7 z%~3uv==O<%zpkG3yG7qHffl~%{{8zm?AKEs4fVtsoTKNOoi`nmk#pUdJs9>j)Pn6r zTd|T~V`F3Yd=HjxcGkP&meU6d1!9)OWd?5=^%fL(-(1fZ-ndt9cLvmXtB)mx9(8?Y zhc-dUbO^Bi)?F2E3eDYaRO?N=s5M=DdwsTd5EBzqAnm)qU}|c5RlR=to`{9-Wq6>R zeBHfc4Q65*ANsOiZRT2tY0;hldZXK%^H?yVbc+qImiPDf-${Nr^O_(XvA@(3tnEd{ zNnaK1T#i4H7xdg17Pz47zY?cw0{jkF6izOVUZK3UvFlcQqH`_*Q7&d&wfmvNzXAx1 zo!BvenYp>S;Nw4o2U=QMgyODCsD2PYGzC+4+*GR+Y3MpyxlnB5J6krD%hc;}?#zgM z;qR@71K4&TMbDg^oQyRsof00jbB#IuS$I8K9K67Eb0IOgAgZt{>L!U>H*ot=u)>Fo zeFG3?1y;OHgm{DruKcI%vK{l%GRYaw6$%Vy%M5vI)6&vl%T3-pSABhblWP(}*?;gk zo=exjhQ!98BcTluq6=yUcpVj|pwatMg)GJ5Xps;%N_jQ(=M#H8fKr|sFjs{MRl>3x zOS<#|+A6i;kuFemZ`F3`8 zq+il$(ntzweQAqrT4yin9z@C8EKS!<$v@zpSB6t!k^^$`U%>aqfh3$Y9K++AE-ilh z){3lvTK;a;?RIGQeKRMqj6H+m8YAB1R*()4SRjIL>a|h@Ul4>?jrr%{TgQh@?gTt3 zvDB!coMU=}Y0umy%4}c%^^UWNiuH8(d055Km7@d&AO!0N$qb@WayPq!h8LyJADARyOHEMstQZ_l*Rd}(%JVIg$? ziEw>=>_vf}eCnlMbau?mPnzp!hVvo~SWwr^!!fe;LRu$6Ty@yttDp9oS9QfyetZuO z2)oS~fH}~H8Q^XP#9LZeWFZ1Ck#|!7v~OO&E@TO<##2?e&@?CL@_W^W!G3jl{3g$+ zWY5LLWgnDu*(;nuPtQ1l>5uD6wb*oS*FAvzC-N3N`legi)i=%?ObpS4@PPAu6}@b^ z&DMJoLTy}JaLhNS^HOn?*bVE1ZoF8BDY$S&@FE0L`=x;j|NVu=-p$R;%kc2<%f`mW z+2gHYU;Ge-EsQU=cJe$`ANk|foTI*SJBZT9f-c}+YBWE;w6t`6x?>!rM}Af~FpFx~ zb-eUEMhBr+A~V*us&0aMvzJ`ert$Y!Ro8%pmH=`8v)y~d*c)vE7=e#&gMKjCcZ6A5 z&AA1j8@{-T(wElO)`o&RG0;OB)76ebp>{#@4uliZ77`;b1AkR#H0Rn#NZBp(v403@ z>(&viF)cM($GblDzsa#EKRDRk-Bt2UWDuMW-f@3Ue>Bby6P%5ssNOpWDj^&54>%NZ#}x`>y4cbl7=8@L}j!eF;K zvrZ8M^KMvrJ14%&w&&~1{{K$qda_+U^)!;fV^4yNuJRbW@EM8`)+}nU__bgjCRu+k zFYG51wLjRGw4A{_vsHMqI(gH80Oany?I7o2@DSTvX>dBD{3hq#Osbxl_2%rNW$=1^ z&|HgT(lbRCxK&eK*u}}FCfRnRA_!;QJ7g)9Fha2Snb*Z$RpM17AVim$gmi`*Aty4u zNYc^A>X?T6!S+n2VXfG0KwFTNIk{C&1Gm%XnS$7?r(qYYaP+HOa-5TFVjTwGa_GOZ z$E1Nwp9!$S6%(ieqkC2CfELZ85j3Y4%Z97~hY55==-(@TJGBMxB0CHgD%q>NH%Pe>l_XPzRV*>z` z3Y=xI&MiypzDK?*#jQlU_)9q1ebUtl>^5t71P5|N2kj-~3@lm$N{gW0f2ezS|1?7? z6_eSy4)WlprJI%6=FG1tdjoKL7|cum_sz)g@9F6B%%zeNrU2D+EC8O3CwxC#1<2HC z3wj8kc478$*zb5~SN^V!xdAj-p*@6$8U!cKPr0eyv#)AdGp=R?$sYv|?Vk@`rK+AA zGLDS43iL03l3ZF8{62wY`D8KqxT?CJut+v;(_PngO~+WmC?X96#yJ5!|{n% zJne8jEYp)VgI2wQ<%6vlFEjK3mkttu3qyhnN`Mc1&$vF(APM{IeAiJ}{`YRQY@*iJ z1^csO&OK9_`7ZV7H?Y2Tt*KVl(B>#h0?0}FrE5QSMKtEN3{S~&^5QT)V8+A|;Z6+znR!8FnRV{U z?-5orEN!;DSlVfGS9L#Nf@jMmz#$8aBlr#*)Ta{V`ui< z6Zzi~gg~f-3ot}%2VtUVdkgveqmoRxyq4p#(HbqbgD}R0s@;N+7`xJwH5K3uxN)~7 zQlLI({;s8@>JI2Ll zb}ZrTE$D;Id9mrg%Wvp&%7OSf5TLE;D#)|rfGA3mctwmdV#=TFoDB)VkVA=2YGm#)7FlL-8&Vi99Y_g^TW30_2U(VL~kt*F^jNI0>lKV%#74 z+y>Z8U)Q53nL3o`UiUHbcnZP?St=Uw#@>vGv|7x!6eXe{RYCxfKRL!2CwYAwT}8`K z>V^IDrs3cRHk=47g7B|GCrUP-L>pfvVggpQ81C5bhIjIbEstj?5WnsW5lRJ84`mc#gIAXN5so{;t za+qi9zthWb#4O~0QNFiyXSa!!FF^628ZgrdcV!Tmw?>`#7X&a6qL@0y8UHz*jXnXu YO+!UjxlGY2#Qv3w5%@2W5)J_TALsX{ApigX diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/GenericAppHiDPI.icns b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/GenericAppHiDPI.icns new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..192bb97e4f882d501ab5ff54246a86e51675ab8c GIT binary patch literal 457575 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%0ABx?!~bU?u~4JUl;i)uT76#v<6!ngFO865( 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%0DF6Z;Lhh_^a9| zipGbs^#*XYzD7S88@SeLDYN+gnp)y%gVJ8&Cv4MU7?BQD^cBa!7_KH|`*$and8v$& z#Zi+c#m2nR1CLwnd~55V(ZV3Xx2pB39*TJ{X3xC3nk92VcPvfZ=i0WhHsmBychPG$ zTtyV9j+c8rykeY~-;vMIV3s)(grH7f_MuSmr|YS?A_G3~^1Cz<`=1)X`eEJ`ObP!e zqFNlgl|}1GjPvNQz`$c#+g9#XP3wu;|0_UI+lIyA{!KNHM z{@P`8hPUIPv0AXhk%!{5m?pkt(wjK3M-Ic@7A%H{1E$y(0sGC#ki;$t@j~9MB-zAg zg3VN?$6MHDI*Aoqy0~~seOB|z+XoSmi=&!8?r{+UKGa$&VWRMUSoDFQmau9+gO(uY z61jp4leZnIdF*bm1DmTW9j4S)z?Vl?I%1>bw5wNv+N>{!`cN`9Aut|O{3R@U(NZ36*=c>+lqg=Px>tS|NYitC2EkU zGraaaDcey_)Q~U)YZb8Zr8m*CK83%!&TgrGvzp~}-4)>38o$|#RHo6!9C!IyWV&53 z>tO}qC@F$of!w3N4rJ!fO~GxJI2}5swnT$pEdw}$WscRU8te= z1W^dNGLX>aR;J56)Fl${CAx>^lbT?{cclf}bz;ZNw_4wT4?IrG8p5WjsP0y8BDV%& zUsL};#mK~w>jCQw@5$HKiSGY-EV%vlz-`n#$<*oV&jBiJ~|{& zF@4Gxay_!S2`fR{rYY1ZE~ZjgH_>Zz45+b2)3u!IH?ZpYv;4nZQ#u4leu_r=5(n@F z+ZdC^qV%km;R~378o*kq!(en2z0%ja-+0#5QxOI&WooV2)KM>$V6iCVLY;UQwH7R) zXF}f}eWl(ZY>_$;n;{&P*2{pGC|~?wA$geI(#D_~prGAooh}TjA4f>_UOm^>tqgpg zvEVY6dC$Wyx}=q`S#(m_Aa^W_Evd~_Y=G1srQJUKF9blADRtvLLJgc{=5wAatb{Sn z95S^CGNEWOH?mQ_iimS~*TZRe$w(U@8c}s1*N7~lgg%_^Fu%PR%Z~>##^!4 zz@Xjz(}0fKu4xtG7LQZ5`r%bT43?M{a9Dy(y#crQq5{M1-;HxNf8jIjdV%w>_^X?B zGy%}@8`Fokip7-dL5u~?=^-nym`M!&N3eu9UhIZ~|e zPZx%i2+G!QGMwga<&DZv0shZjxX_~7o4nzQVYb_?gf1bkO)1PH6 zuyeC>eCzGNXg+{tUzaN7;R+~_24mTkqe9+@rSsJe0U^%5gC?x;-4zUBCQ)bs3Oq$6 z^@nfYw7fZtaehY=7Pz~u3AhH`{yp{ANyn$vECG5+cKtWO*IqoL1nH=kZ6}r}`A@&4 zQq#GTNz!O?Qme_*H$9f??aE`8f~afH(?4}T^iC}nksSoOq90BF9HAfctx9PN(WNTm z&jrk;yF5}Hj@RYcrg2;WBm}qr5WbgB$MbVjY7i?)loVz!j&?TjL_gmB%~lkrkjyr0 z^`plC1+dAmLUGqu@)x&*HfIglc^dg=d6FQx!*)|jgLmtil@uc*NoXX zzgrK0ADV0&n6U#0{SCVpXmI)*OVU57el18yHSA)8Tzn+t%-o z6{yA@R+Hb^Jnd5K&79Ueip~OQQT-1lMA?;e9$mFgL?|A;xWVfGtpE6?Ii)##GXcdr ztO!`rfl1~|g00Ig^XDGdX3&0^ab@Jtt~xn3{m=oLM);h{sR^m|b|hTQyZ*5wkz*zP zyko)#SY{yabIOp+$)dLrVTX&I8X0_2aDu3uj0F49V_Cfe+H8im5(839!JUU81i*Ol z=K+$}hJTm-D6q<&GHEP}MxzATv`cO@Uo7f){YLrGBI5owYGFv4p20Im>Vs(|6~ONA zQ6UjZ!xtlI0oPvFKxemAMiV+4Ury4%3DPheC)1FG<#v9;>}+rygXQRbz`H`AFM}~) zAeIW-rn)QtxY9VWv*omj-mrH!Y!s@t#F}wzQuoyT6acr*3XQr|MJk7@j6?v$A2)EL z+8UPCj|kc0heyQCf~SH+ptC2Mp*2z0U-9ky`ke4p7#^jL9f;2}7UC9TD;E8&`(-qp zQSRELr5R2~QEUK%ISUzjOrh{Vvz^Pg9<_J07+xnW6p&EU|%00rGwp;H3P;}(f03-FPyZdCBfJid>ml;g1eitz;$hroI z2$_QteSHM1w7l3cBUcdL{>Anbzow6=on+g}3u?`#z^gR*71N-cHcW%OASk6WrC zY)GCPp~M($(ZCFBb)oo*KWo-2_bQNpqid$u871olYp{4LUzbjZ&|YM0`LL1nNmBcg zWJ_)0`+%Hev{4eRtN(E6h3G)Kr1_KRU97Q;31h;iquT)H$B#@pztsMcZ~GRRtB#|% zQVfJknzk`y*2O|ghW;Bv<7SR#7JzdHV%C_}%GR@1!+t^kDHS*m`Wfq9F17O9Q)KoG zVQofqIR%n{332_bKY1K@wKT$O;JxoT0i73uuutf2Ywz=zG`w|Ew@yBvq0KvJcfgcW zwFSSUZL)=DXvOBP+T|b7&V#$2#kwek!_c zKf(;Omaxu2^18S#(zDwQGa>XAitn+iTQ5cgSQ=}vw>4mpRI%_cJ_!%Va^xaA)oq8Q z)(qVF1i##$?ju!`@S3&iyb5@_zmfzl)7+Q>0#Wj%PFY{&qd8`|Nk+C4gs^B)rQo0b zp)xzGlBXg87J0-5Z(9Hkp5J@T4wV)T>kc?KVE8|u0Ps}>zw?q))0B+;hv3KGvNYsK zf#XMo#kQ0p1w(D#Fn9Vu5QnbsDf8c7M3>cC4lz zRDN=V697lTCA=BAJL8XdOCs;E7c}+#d4~J>;!6udSPHKAi;oPaO1Xls-n8q8Jl}5h ztyB~D8pBY&x}Xoc-Qs*f;d5`v*X^_KP@gQmylO_5WjdI~W>!_vz{) zxqbb>2XLv;p8ieNv=%KJaPT(!T61!;n{w8S`(Zqq1qE=z0JTGo?s-}0?RKN$0x;|+ z(Q?k~Kg|+75n<9Nmhcvb$K;aK&?Aj-3Tgc?=j5ZOJ|Gh`Z|ZS}fg4$<@WS=Nx*tda z!MJ5+wqwz?0Vgr=$4gaH3h)0#p zwgHE|3l~DdEa*-1{VdgOnH1f(jw^j}w$an4C-~}YD^8rv{diGpIRO6Dg~w4cIzP@K zu^s2f0MLRqqY?0nvv(hja}>>tVO`s4Hz?+x3qvBDE%6 zquq#nUaV2rUCKSoaCosqf~SE(53HmWvE(}ZYwP%p9RR7(vl}b5F188n`fs-97sce@ z(XDdVXNHgjuUll=KwFTPoP~XfXYl%yquMqe={qp8oii@3I$SNUeN|d4gvI%WLjZi# zP;S?~p5hwDM7w^jO3D<#=>lE`#D7IDwq(&b*QaB~DwzSX(=DMiYJK=dxO3&n`XzRt z&T}uI(PNiNAoAod|0;v~u;Sy2#~LNy7b#8^JFo<>_Ylwrch^I0mx~6VIw2EFb65`6 zt#sjCAoz*!#Rb~0*3gqkt8Y%Ytqu|^NSuMaP8O`A%n?>Q;Bh)Dvc?QNdHt>qYphpd z?_0@Z{c!4#bxinT=E|N-g3ZLCfvB;RZ*ai<57eg*)ceY+e$y-uv9fFXS=zmk`22uU zwD}@qS;k46Rw-oUf1h@>&vkyHFuX|}>;fblzgBSlj|E8r-yB8%2J}Vht{W{7Xc{#N z&I7E`$eaGjK8~@ou@%v3(zOc`Y>BT=00xBokjdzZorv2$K^ zt9K55Wy$SRelfKc?@;F^Ad})DMZ4cUn}MByHo^|%{1ws6-^xa27X{b|)0Y&tJN1_e z!x$?vwDGg0ERX9>kOL(HQ}q&SW`g(x(@|(JI934+tzG{bKQH}u=2a-L5>@!STm4v! zWYrG)KG6oKcUWs@S)lRPsUS$o2}G(6rw@GO*6N0+VF~It+_MZBK~+xQ7EW+qWh$r_ zSugT|>RG=?m+x>EIrpEkSzoAmy)Df~tl>C9+oAzh4FC{=lb^JaaFhD&ln1xEpTsD+*WbQ12@m_jfv)5dy*R z9dQ96#Ti!(P8X4~2>HpT1s|0vkXU$p3@0`Khh8i-=;~r3FdJIp6J4j!j zeV1UAxYO|^L?z_U%vT$#BgEjOBT%sbbI^I$N`n=T>uFV3&v&E+L9v~;cqB2X0Ho=e zkZD-NxO41^dJ9EfqQiD@0GlgMpAa8)W94D&g3?!7Kim7n0=>ITVx&%+iZ<=m`-74` zbiq8z5Ws0Z7DK=&K>U6$Z{u6_W%enzH$&$`FTvK9iWno#a3K?oG)vk?>l3t%DXU;>y@I{cs9`N$IwK>LWDJamqXI zaKD0-y>`X_3FBQyt>UfsxvxUeS!Z};h@gHG`$@zFjDwVyu@Bqjguo3}* zG0)Z$!NIe|tNRK+Y_htA<$@tnnPNbI6@Ne zZz{*@s?Bgyy`u}PF}ySDb@>4zAtCDC>nhSu^4whQ@7Zo;EqCr50-kIHNCB2SZl<&F z_J>pUlYsRgv+#7>4ln6EsjMZ)VdZPfo7S1GG>2AvHa<%V1=FUUOi`am;giO^+k_+Z zvrAlmyl5KBXM+j{2mTSQb$FqKsf6?F08IVxSRRA)*Y<+!BSAOkuhJDHQy#4qahWnL zUWS|}^PDxpr3Gf;r3o$_HdI!m_76^26zvJT^YoUi_c6KgRV;zL`=c_27$m+!)D9tk z4eGz=p^rxhKUVX7fDX1>Zx!z;rLteTV7-Ly9yzShzz|F~j<7&{KnUId%_h1;dOm({ zf>Q8o)hcNt0WOlK@ej({vKirijm>4zODZ6QmPC$8b|hZ?4&!^HFKY<%_36T(q;0=Q z$0W^!#Xz>RLjnAd@)We`4S}274ARCOH6nBa-hj2|o``Q9Kaigx0OLb~MleIMH}xBX z9{yM|uap#woIE8tv0rp>o#kVpXyDSd>pwJX-G`6UApPtbQ{4vMcnJr7(a$R0Zk2Cm zFkr~G-d<$lKDV}%A=9}`B%ey)FFzrnR(;0teY%?ZiDoveHhr(p`;_aw&YuKW_j6iW zZIc8RQ1w?60`w$S1<1}ibxe@kyFg8~l1n%zWzqDt$B8}%=ju>uW|Kvsa@{-LzlC!5 zDb6$BV~cHRO&;^k`JQsTe=|}lO*7}0?9?1Q=qul?zN79lhUTVwK;UVKDs9ZzTEqI6 z%Cti7w)m!}PcuY4PTt-)H(fku-57FCu3e3}#2osqazCUulH{_tU(Fh1DXjUbJ^vf~ zA*{5~`T!j|bvyl4Q7{KTHi~TgFfLGlZaOFB2HpHtfSM&JWH2ilz~>oYGDwKz@AhG$ckh0u*=+FqdG`aYcb2Ay-Bq>dY#V#Qy2A5jKF5 zJKk1RJAob25m{kh+@LH=vpF>HN{_#z zdB4{1MAE-VY4o-->du$Di(mg7im5FXcRs-XUuF2Us~%%2`cvNZ*+V~ zYPe#Ts!k>8m>mY!RD)U@T7ok^7>Z{g&xhlNsDQ^U>OM(5%sBr36h35&{T}l;F@2Hi zdGkI#LF^!SjcPsWe0e!jM^kM8V@HP;=hw&f5iN7HmN8S+^LgGB{9$SZzeXTUaHS_v zY-cuJCB~nahSA?zKmR|jzC0Yt_lx_P1!E@)*-1z-C_6)nA|csIC|Sx*_GLzdtQFZA z5?btptYepb%g)$k9s4rO%uC;X?|WVEzt3FP+|P6Fb3UKXInQ~{{kNl~;v%e25|RHW zg$mPLWVa(_`TafN#@&P&%Q*I7$EZ3B1aJe0&~+H023QVnj8aUOUWH7DV#L{icNEW6 zqBgHd{W1mE7yL67l38*i&OTYNMU>lrjU4XoWpaOu`goq*G4-;D=HIrK^iyx$r37Ii z=J)NzL*J=<#)04z=rXPz?y?`Z>t8OI%W~W@f}1;p*XbNEDO7z39N9~(det<~k`?3j zhZQKZ8$A~XaWe1;BLJ7z-HL9SBq89DYdu z0MI%2gQF%MJx2Vj;@9yQjVZ1`FLCOIXZ@=V9XND*3>o6e8fbnbl6b zBzrU?N}h_X-I$6WI4Yr5u!KW8i#%19*~4@l-ry~rf>_AOSqqgqmT8I~K8xuL9#?6n zGTvX&_pI*QAmiD>lutj{(A!1U3>=J<3ChhGaJ?104!xB#DVf5TzTFV`d0XOIAHOW$ zw97k-JQ*#02G(_PlpJj3WpK8}!l0Km>;$@il(^GhZ^U_3LCXV1qe5r#iN$9UiQc@; zXw3~+J#&m3^{NIOelEoQQ3IGKYkUqaJZ>JJCY}nDs9#>2XD^v6c*q0FRO$8tL8us3 zBk<E&emDMkW1+dt+?kl`%uvHId1$D@8ABGj9M7$=a~C z+#p-6@}b}R!dvIgRI)JmRk!uRb%6YMg|g7+U}Kt*;W6l09DB32M*!(&1bCKQXM*Q? zMBX)!$7Fbb7F#_x4hGkl3Fv!x|5=WD8Qgt(4|6+(pUKMj9g5WVzWkqLF(y?*0#b~F z%~Iw0F<}yi+lr{jAT84)g@}jS9nFl!@!g%T!W>@(3V9>8U+tD@CTj zZ`+^v*2-Sac3-+fbiK$r^M)C-*(B5SD?SU=mOt8eAs%s0+k)>l|6&phb9?QxiAksx zU%NBHelr5NO2%7zb)=7M>$w^rAJyk*$>vO~`fmvoKLaRWq2_7O%_OU*=l$6olL~S? zpo_fI7iPTLc2|E6TZHVrDKZG7r~2LGkkxSQEgPga)JyG`R=BASGV zYtoy99JFW}o_G*=>@j(AAxrP5|M-)KJRvd@WXaZYb_S?Z5i|eqqKMRO*-R%Z0=MUM zfRp|fknsL?l6n@7UyCa$sS^)oC$qdZ*l+VN_3-0B?YF*uF7V+gS6itu>wX z=8^^6Tfuf$k}dR!?Og#3l7 zJA);SI3B|)sBx?&7)z}P&>9&=-_oGE$CHG0;z#Zi zR4N!B^DUuLLI3v41e_OvzQmNtMIfBtHpB(_<^LD9tNB&L@>jj`i@Lg8xy>Axx{5&kSBRzc15(C@V5+g9 z3&XKbioYT4cWJ37w7-ngeAVQ^^h-^gep=O_iV|*2s>hV^3%~tsa6xUrvnoCJ+NIq9 z0vboJ$e6z=4v%=>s;@B~UXNf&1{Jd?Q%#J|Wk0(662NS3Z3&`-ls0aX$@Z3o`1Cu6 z`0{7uvSBrD&@I({4BId|YH3$z{E}=o`6`xO?|xD*W87o1*Gag*-=TEy7eXCw1H)Pr zc0rey_t*`*Dn87;L?*|g^#~%vZbx4~L@61?{diL9SCl<)tRT1RQ6uK9jqQ`ija3k^ zO7TkjvyUBX_I}=TaiZyyt`-cCZ>;z7kXo8KsPyPE99Sjoi;kLQ^e5 zXofi%--pO}Z9jB2wrXhgz5)aa#F=nVc+^usXu`y0Xk~dGZ+;a7rwsZ#fCkFei|qSf zU)uAZ_31OOk5QhI!$ooRC^yF@#AER?>S`w<$W>oPq20$nI$?9X%i1!wdO8F0&I(^Vv9> ztyojsBoUD$vjl?F^IT@F*TWjj*O6=!2fywe&ZvfjjXT!W00%h#gQ`A7c#_^lX^IIF zu68xwNuo)2OYUvW!7cljN2ZstcdpLG_kXxz)>XIsWL!NC;^hQAE5`T0=BK9OiroH= z*2gmbcTou}Bq^7U&O8a}T#z1G!18Fwr2#^q&O57}etzJ5t-Jnf;;7+(tIJnJH4!hQ<~QZHBhPm_m#_S0p39FCmZcZV=&FupKsp?YN4y;l7nGRwWn*G zA}l5N+n}oGmOege!uS_X?j9I4erNaf^sQTRbZ=3dPGv%wq{Agdc;2Vaj@Y8i zk)N*C&LqO`Zo2|7bv`x)hPmvd9U4+Ez6JHfO?m`^O$7Y7r@rn?7Ol+|6x0~3F?Twi zk{RlltA1In-Zm6OkCK00@K!6P3;4br+E8#v_hQrVBNADl2sP=>H(!@26?8T2e+ISL z3AqyO02rAZ5WoM!)AP`^wXD?NCLiJwJ*Bc$w-m&UlerWipXYAB2xo+Yoz9`ssFZ6v zJqL0$mh(_*6NLv68rR~X+?g-cdy+)A+?M?lwF`w=Utvl%?_>#}QP2%~m=)FsiR zA4J3DC{6tLg6ebCfKhq0AYePFJ7octs`S}@EBKrr6VqJlcif=nnRBVTA7#nQa$?cX z_>zJj#nF69PGTW701-#pt7`~;>#0feB3+g6Z1bnrDU&vES981@K5zLdG37dbvNuM4 zKkV_wD}%h~j4i#7jz}>vs0rP8UUbpiX-4F}(52~m<}&69uo2^qi(%(ew~S@cT(@Zj zQjgOVeZU6d5rB|LCy+U@@)dBo)nlVg1{lFx0T=x4GV0*0vdx+D%see7U5~UGbU<-1 z4dNqb;Dm~1{!S_hJvIe87+93b)xeTq*d8n#LP z25A<)QWrQ}7j}w$DRP=&n222*l5{-#y`Yv#at|B&=>q@6r4C@kcKT=uP0tdx@u~O5 z{YSM^EPam-qze_V1;1-7mLzJ{9Y5I$i8rMp53qCpRnRdK@oJDn{&k0w4`RX!`WBqN z8Sbm0%4v2~pmD3~;6b~#)`t(5*s0NXF_MW=%|$P~Rm0Rd13of39epQH^Z_0={=&Wl zf#knGvwr+YXe_)902PpEu4HJQ)5*V-{w`mf|C*Yy``1=@M?_8jxT6N220D$Z@OCvw z^cUTsh0=LtarGI)9%==C<{aLOEyEoy4Eg%6*JJD+|IpwfZaAGT!|E_;Fw?nON2Ni$ ze;@Wx8o6|9kE-;rlSpOmcJdN!Z6p`$z|c(pl_-mS)KkHAYoWBWiA=X$yG;Ux?Km}& z|MB?jyAW@kY z$KiD+M?KB91+{WtV$Lzwxjh5Mc#k!m7!P_C!KyZ5A%jRaF-AfrX0;CKCgmPB2{B(g z_l}X|Zjf~ZNAS0}2$E%KSQ)E^rknq^9A2#xb>YkjzlQk0U5vWv9eiV%FS+xcMW>~! zGV@_}N&oy)Z89Ez-3U5Q?k2dzD--@7VIhuiijcXO0alqS4B9A5O@Wu^NA&sLXYLKw znGCo)l0CA+J|E9LUu<;p%|39w{D;g((wI2-*TSbONKu6T`!Kwz)E@UoksiCY{|<-1 z6wwW}-&~e4QE74((o}mxvQ$=BufEiM(U3k$*%Es07d>{HO;@=m+!l{(K5NP$7Lbk* zfyYWzU-v;Dm#@~e<&v1RmAn=nqO=jT8e**spP=QOE-%aBfbaG%!RGpcI#GA5ud58_ z^j0euwe8Y)i@WIxC&kXw%5ZaTRBA~0sYE*3y$48tl;2J@9j z8~MshGJ_W~Oa<2RK|=R$i)8aP zcmJdUEN6fmtU_T(rE4JKJV+Xd__3Jk_!lB1)Re6a*#?~z%y~6IGUrk0OGB=??2^B= zz{}#`LTaLcGwsqtlZz8v&TrUPrI8IhcPfs%Mh&t1@io*J6+xm6f@6VuBVEo9969=2Q4VFCR}uy<^pIj zJD>lVA(c-D*)%9TYFcr&W)Pt9fG+I86SuT7kYZJWV6iKS+syyxVF#M{ zFva*WH5XV5J}rd7DdKIm4wJ*M0YojlZ+BiZmm*1esVQEQExISA6v!yPl?gWTH~ z`OEp=7s(JNT)}UZCvmwBl_NrnH`VQG6%gv?Fqk>>(u$80J*sv%eE|2DrCDI1&txVu zQam*S>cv<0oU0bAy}>?UhJ?iY)hI2wi3Vx=F=ho2{CeR$O5QR`7K4y+(g3S5?%%{Ixp(9g(rWuEP95xLio^+D;VX z^p04q5FMwI$pnvvun)K}nRH>atzhG_+h=|PwOBPn1hslJ2c4(lP`FnABA+%Z zKo_>u%q9-5ChU)caIM@vxi;SCR9Q}(8$gCb0XW-jt=~6ZT6SS+W=je4f9}%%wS4JY z%*fbdX`od2$SGJpZz{xf=a6qSHOWl{iU^fo*c0GRb5D1Z-928@9523tygOyBfLrgo(JRL_j1eca(gJ!y z`D%6+7ITJJR-=fPn-rok!4~Ema`J5asMjp9N&lSLSQW#RxW#RcGlS>DmtueFvL8@MMg zt}Y+HMRlkmR_);r$Hf(#ImhXPRje;v06PE)0hb^g`X}G%_P^B{isn01x+oFr_9!2pyIGL7w3ohfnX0$a*B=j-2 ziY{c_9c??8PqKY>iY9sGVUtxJEC|A~Hl$l?+g{{al{EUD0F8<2$giJt4r5$@WOD9v zrCCXVHy9<@S&(u{(3LVtw}D9d87qz|kU6=3nO^A@TLGT`6k2RTQK>gdN(5<3_U8Fj zQ7VmZe)TyNDS+0#0@%?0s*UXA2070@4)ygs@DMBLetRD(g$iX2ZGSb{#WK#UZxdpA zuxP`yw-sx73?DC#qyQ}qGTwY*1V#1seA@4)JIqna9H+8!Zd#_) z#6)yuSSSrZ`<^&&Ld~U}6+mgJp5Vb>A1y%W_q&~}Na3n!SApIssrySiC&{oKKgnqifB`ZING z0{h}jng6zm(R>fj9RX4n!BRD^IC)a3r<=hlst>-`lB?ti@#y{Fwrsj0#3tT$52w!U z$2^o>lEIw@An;q!f!3HS&%e!}CO6Dt2gpXKr={zTwCdjnTF{pw*0M<(^0=TH0Fw~O z;z}b#YHfNvQ!&WBH3h9L|0SJ0monA65ww316kla(h-SjUuHG_-lcV04;G_S6xif<@ zTc2^;y^ly%%gr!BKqF>@5_9jyQ+;h<+dQwz8%Up@WmG20-JxTP-lTWfwTo@bek(LI z{XX*()e}~cZs!y34(WN)S6-1NZzj2%fC_(~fe%YM{#F13@`lnYZ0D_&!pG zKGPZ63D#Zu4dF=f0KYrKI|+_%|;9&QroUqAR78&Skb z<0IN;3V5n12nGXAkA7o59cEQqq{w*w(Mc~cg zen7L0mD6Gk?@{r@iX{4MHY?D1mJ3_0p*^RKrq&_~Ls`fqd( z1tG&1H1_y70uoR2uK^J&RARwz|2u+R7oA>vgA3nt_sFgkyj<$?sqX0{mD!Isr24j0 z-=n3OG1G?hf^^s0!%`T=oQ@HKt8>}Z$RzP_f3xRFTWD5oo*AW z%qeiOdXmZeL}5&z&z0evr4llQkl>$hPcb*W4wioS6nZ#ZM?Aj|RTe%)EbBBscL9E& z1CZGvzF{$=k!-Ic{w_GpMTMC=YSTHbM(PgvSV3GqCtG%LARR$goBL@B#vFM3s`<$b zOqo6klg^lqMDH%^Mm}F+XF;f0O*sYdgD%m7jBglRZc1_C-MBknX5T|5dmFat zX%H+~`J!+8S_Apvp3qwxr;fY7_n+eih@W+QiVxTBY@F>i<_=>857jY+iyW|V5`SB~ ziD~-h61u<+I&Fk`>{#<-q|(iHYo19Ps*NhgdRbR?@=zFkzcQ;I$rbL|z_7g#0BJVX zE$wMn^I7|d=|erCppisxjI*pzr~(O1)8)3c)xLitg(Kh<%!VX8NNk`5mY=t}y8jnK zZOoulwqS)SL4ahq2ZgCm6>%uvCo@l#r4lRY#I|28NksB@)KhFW!B7od{ZyN6Dx<;V z3JQZA7Mtg1X#RQsnLs!-;W^Za^}n@d_)rb_tyXRdZ6NmcY4jzNiVIb&D!EzcfuRIk zOrli;rQoHhX5*b+Gr`z_->F-eO=sgx1@~Q>5xx1L8Xsx}fl4Y?UZc3J`Vj8>ia_P3 z<_@=ivXl$7j$7Gxopfag0ZRi-^?k1Y;NTB0n1-X)Fb5PyDD@!>B)bkL90isCuS%~@^#2X>>ySw@SLs8vKe zY3EdxStTL&&?L49pi?>e8b&gh89y|RtH_kx|Ju`YfD?=2mcQ_aQe7#kP)Y7Pik!^W z$J?SYa<$ZN*x6M7!%Ft|q`JDLQ@d_4ZsOc;+*5Z1jA%uXtLqLj6L9C?vTPF=;iLpWef4g)%U7J-+Jf6)GeL*{7I#PTzhQ}>2 zt?qx?vzG!&ZVTvyJH|uaDZx+!fBw7dbRJ|yLL9G^%-YIIrlABe)MB-)3ZM&l)=TAz zKKntmQ1of?DUt&|*+J)}P%%`Y2Tn@4Yr?v>S-F6#85ogIBJ{9!%XN}Gu6EUy6mOVV zH}l^=!zG~JmOx=33&jr8qmqNlfvddPXF;P238oL2YN0G zqyeh}N-cUI_$Mbf#!_N;hjUN`p&&2M%_{WIYoyq~|F+F2Sh;`(c(o>X$hT4d&sIv{ zsr%inR-~dffF?in$`h{uY?}AV*V*~EOi#5z5wIq)=;s7kiPm5ryNHoD zC%TSkfBXyh6o;HA+><}To~=b;^eSQ)-{-7<5CShluM9v2iAQy3RghZNIe_D?Z$8TcIY!p6_C z*TbpUaJ+MtNI%8V5qi*UL?dQDx3+NBFtrC85zpluHzl17zpK1`*2YWsnN`MBFu5(?5%|`%|?Ph*Qmd^8=Gqc4_9vc(JCu>UxdUlA!c!6L10tf-z*N~`5) zo?8H{f+yWS{*^7eVd_uxj;}kk)Y+B}uc|A1s=;mwly6Kvwqw*rsdIT<`g}YcNH3Sj zW|%Zq@%SUQMK8W98;Pskx$kVUveed>`IP&0(bcLuk$FpJY29xInEllAMMQ-CcY&R9 z@*u$b9``FYX@KU=AAEaQJ}JcC4BR{X=v5_Wz*BzZkSIo!(@m+pIivyz#^IH73=X1* zK}b)&8rXdZ^iN$<3(%C1*-d;A{$NLDq}U9SeC>hs)!&EimwZI8tM|B`J&)0UyV=r? z-B~6FeeY!wV?U|N_-6do0k8>1=llg_p2yJAnBr8!vZnnYxKyIN|E`hq;^6Y^aB&%0 z5#)cwO=464IUN~xe&m#bo)8DLL!K-ni1RV8^MIc4?|7Y6C~-JEBk+5~n`h^CqPCF_ ztxk;7wzRhZjL<+c5hCvOQlvm?CFh!HHOF7He!#2z_^aQ(&X|@o@cF}qp5cGK?F56v zgnYnX)B#&UM;btL!H5q-vv=XA8H3?oc(HNnqWE=G!d|6rEHYop3iE}2au2Nt7*VA| zeq=mdJ^8n{rO~K$y-@mk*eU1jFSKdC`UlmrU{5f7y!vu-7XgnTm5dZwsBdwT=8(*1 z^Jko;K$qY;7;4lK;;^2+!4xQ{-fsVRW92=(w?35f^YvekcD{cM*{pgx(Bee7^P~7n z(j5;1sl$wT&Ph=)nRHZ1`u69@=O40+IZCY@c*8Q;0dJ!h_ly5|Z#fKZGyN*63N{85kw;<)bg@;hFzTitl&ay!F>Oycrx*JOJe4GGEIHz2VS zA%Pd!f|_fiNQVnzfyKz3G{u<&WgX>Z z{273>KX$BTb5eNqM`!cProTK{|N7F`^%nylR@==cUuiAx?Vc4A=t1lBCq8MBU;jJP z?O%oa>>L<#XvL+Hp4{7lg}j`SedA5*+}eOBIq#~posd+!63FgCTN+R;eG3BQa5`mO z^vpOx%lnkc@M^-NI@U-6^&jLmr~enRGT;tI)<}>t1>Z}?M|6?@1NUjzLsbArUk&K2 zkyh)7B5ROMYFSQH4QPL3Qht)--2w=KpP_F|+G`A<4>|b$vGC;D`qFOKH^LLy)a(LR zYg}Mzl}Cu?fy$Sdp6ct<$G=Vm#N4l{;#Jb}Ku+c}nF15!#{X8LAq(hZ1%e+u?GE|3 zrD-rYb!30!0}b_vr-gG;x&+s9FeH z2BEfl(kO+9Na7AUkP(2ODE_zfDT&brOtAw77Q@)H&v}KxF_Ehcq^8@>TW3$%#fX7n z=ty>SZH8_qBSr6)ua!@nvmzuOStC&Evy>S89wdezk{mzrgpT9TBQW>aOnP(&<#QDR zO*_vDyjmnhOt)`^I~?NjYis5^84LX?*HMd+{;$a|_Zy}3OrB{4tH&Ib^c#1+4^eDpd0mbmX z5;CN7zXV$^Iuor$S8g{dVQ-c+cqt)$GxLN*JgB4ZxN?YI`aYYrhYjM=UWM&~L3j^= zhqK$_D@cEjzvg#>pr`}*;Ret(6sG$^#CiIqDn01hX6zxuiLQ=L;!mrxB&T}{AkSc4 zl-sr)tnH$E1gJ7#`kRJ*n@k`=Xn3>!jW|zmao%B34p{K&G_mK&!J{_Rx-jKgmZfdB zc}E0>QcM$S!)kdBwJEk`digir;x%AP^)P(Bmpter<9u^FJ#u!me2u{CTVJcsGvzYtL2c?>~GwT(?*Oj2C=Cv4zQR%QAhlj=_~Ow%6?O(`YkmMqut-A}5* z&Ui_v^8kxP!s*t=%L~fo$FP^kY4>H!*AZLU0kx!)qn)R!%~-;Ic{Amx9tc8}41|%T zzu4~^;I9)(4&v$HT||!E0&XKyO&&KhkwM10wj+7N#}|-N;}cg*g(t)xjxzqDPP)@^^fJDu z6Om7vIVV7!MRnpr^EZXPV{7&TJnW>kA8*uiug+KUTSeyXV+ba{i5#Fgb$Dq?&I!tT zWdZDUlG!`Qi(btP7!M-tc86nI;Y8#42S?n0?_lgq5UkHCj`Ro1xk`^vxwf3qq8wO% zOeK~mjrHWOAfaQ}ZVh$JfiQNnA>96~5#T;bTV7QwgB&j4oZ~snp9mpRn?py7@{~9^_Em{*1rsUGX2?0D}{WpXNp5P`LOD}`QhH8m-`X3Ex z0G8YS((TgG6b$63l5LvAS|JaGz)*{O6<6cSb9Qw0&SsS`Fo8}}s#^m)F3?A6$`qG{ zn7B9s3>X-USF0u#lo4U|XWt((C)`LRsDG_ET1;+qK|v_!pjqi3@peYA67Jk0t98=U zxPyp$n=sJ9{MqYva3WHdXcni(q(el+4=`fxe*EgS9H%&pH{CaltJ|p&epsLv(Nf^)3J51k0syRi0FV(mg&cL;sR? zFAZpYWLHT{dL6dMHB~|dbB;YnFTxLGS5g4B6Z&tB4FpA4Vr4+Kg=f>G-OKXN1%D`z zB-!n^b|O~4ZUwWt6=D+z`{dBD?!yu`?l8&YJMhdNwZtaf&$8lyq@(+XdGsoEWHqXi zSVc9O=gD1=8Ma!fP{BsBEhsDvw8qJUW29gI{xLuazH$AOU6l7>6(FADD=y%W2o&9Z zfo@@NXW@n2x#N}Sm9Snu7far6q&jP+#pVLyx>%`OH36UKoWf^0*d+c~B6WBs@L zy9X8*wiaP1k*YV3BaUCYjg%|LN=ix0Kk&*8qw>XfBK1py54geEtW@?R^i903{xbQ%hZ-t!-FlE-R~~kd;9~Vxd!ffJC4b zdFO;Nbw_G|1a3~W;|ayk=NaUNd8cwrm3!<(^_it-rL9{k_`MYfW zQPfGDB@87-1!T}0@yoM*c-M8jzzm5k*SE5n(X}Qb55}?N@g&0gp%bx2@3lHTk0%sk zLi>>zr`;Iz$)yR$xGwmM@9+3+Is>}PoWS&xBGW^U9ckCMcxA7lEX8prbKElzBOoyY zkg-p{I*v%AKJ?{x!$c-`8cRKTJ;y%)Y?lH)zgM{!neXntzcGb8nwpfCl;~`W2zuuP zhBioFpDdMr)tH7o+P*g1k^ikgLQ+Dqc%^CQ;qA}iGFvUbLM{0CAOWJVQa@y}LTk-M zv1EneEYsC(J62#QOQ33y|2+AfkT*Z6leX+sp$~LpcjfTX57o0PCGI%i^Zicg2Mt2d znl|}dX;v%;0W(QF^axWytsTKG<#z2Qw>5g&UBO%-hbKo*8Dq~qzwktCSz(Gquu6Xx zQ%10YVO)i|rXufeamW+UWm=fb5it zeh#^-KujV-)jv?d1>!~a)OWR^hc*}Z%UQL(zx2?0@!&U zw$6*+Vs%jq-CX{TU6m*Cj#=okd6Me(=3_RR-#8{dDb@g1sil4-q7_Jj+G($dF*nOK zl)g{!pBVluUsufc*MD8M<1ii}H7|=VKc&o(V+93LCy&Ze4(fa_%U3M^ za4woK;B_xDr%nz@oEUOj9nLqcou!g|;nPbCV*T>RR=jwlJVD8buv+wuWZ44p4PWA{ ztf-3xE_A*#x9U30Ue*cwrLNqX>%254dhLVtE2d^4mQ)DL)-?a%+qZO#gSb@y9% z>XD1YetYQrSWR9{$lsHAv<>oAkB|^7*eG7otR}B}*>umVTrzK2{$s6esw9^nq_BQR zn-sFdT_80;N>q7$PLtFqxOr!QzR&R~fRdhO+Ix(CMEX4hV7LS~uUVR-nq^QRpz%s; zr;YPe+0O3xS}(%tjx`j;jvfiJA`NE$o|E*6cKCwr9wbE>V};})VA~F8?6e zYar$6-P(4%$TLds2cMaxztZ_}t=?u#IEqpTQ%uv3US|d2i~VdLk%KnWm-*BV9XQs0 z{&6%^wNG_=Elr>FDd=45#=>^?MliJ6eN3T3h(to?9SJH~NIsaZVJ%onZ#~W$h%1ZN zQjdvPKIZbB1}(*-iEGv+e)s;wD3$tC1zsXt*b;ucdLKy^ZO&X_+I0(`LTrDDYvF4Q zA_KbRO(@Pg@v$0w79ID}sj|~slBK9)Y;3HK3DezOZpBp{m|{H^-ZKgMUKk3bSNcPc zX+IbN;kk+vnRcs)(^(9tNAp*_J?hgD5trWvi?_dE&I%VvHsBW^i#zD z%>qz`T6wk4j~aF=el8JyY(O&Rxi;hDE^%%(6u?0=A10JV9xyN5A&YY@=Z#wIB0cL$ACr!8wX3Ft z1Es%BRNUtTjjl{(Ij?@yKk8WcTv5zMeW3gdwg=0}gO1CtpY0cFTGqAeUi!922-BR$C8~2K?x6ZQ;|*%JKNcLi zylGAIY-k11fFnEFf`k?%#v_cW5k-p3@Rg~9eoY4xAL7p?UyeUZdF>Aa+Ag0)O_;K| z1UZVYmx>P@plqJUpqwxpQm7u$uu;TW1=zgiqN3ySW9B~C%{(-;JxV05=^d~CR05mo zK`mnagPuk)CKxk`XtU|rxJ7*UZD6wzNEG$TR=V2%Rj@#_cFRs4y6bO#`0OsV=^eJk zr3DzaA@ijBVqN=AkD$Oz-&gj`2Wj`b+_)|{JB%OxkyMHhn#9v2b zq90>5rd?Au<*#o@k|h(*k7YbYn`B{^o=n$fKrFZCM8tdR?*Z~fZX;87t-d}Pw0^9f zZf|q-Q}wCa+k{~LkOOYd^;pQq>bbSaPF=zX##8oUWvRyVuxr2-otJkDQ#CyFT7q+z zZpVn%e7y*bt2Se!+T3NT7*aRK3xfr1pXqL5zbg=MwL4Kh-T9_fkJzB4%<0?dd-J;M znNd{>Z*@dxyeC^igomipthKJo#~}7cEH)UQ%oEh>TP2T9(8PI-0rb`2Xx%SEDn}t6 zPb~A8uP*(bWc5}p2&A3b>li{47T&OMklF7qRqe-zg|i8aL@-<=;oC)OkES#iUs(Gd z2Ky1fkde9H5daOyf~?)Y;SWY8LggZVGmf}}D2JEc7#noUrMmSq0F%VSBYS(!XZiQV zNkB(VS;g!$-tV!44tgGj9ZzYzw-&Ul*7E869{p^N%b#g2lyUO`eVrA>Zoq5uh{_yN zn6PF_zx#M5IYTzCt0AblQTCO);miSqZ~ye`QyG5{7+Fq1S- zdG}O?#o_y6W7VCb&dz}QN_lM&zW@U-j#E1)GO23Ie49)fqW{(jk8LgchtF{Is z4kPfWEjLuvMrB9q^|(OA}cNPVBj^7+i zy!uW`#X2mY-VDKfp=K1@X4JRL+mlCEnvOp!$@rBQpJ!nEp@JEo7<*Hjzy>ORWY*0JL6)?V zbzC&MfGK9BX*Xe)gGcF#zG8?<=%c&REwPc?w+$~aIXR6bWkF~I&`VkC!Zl>BSNaJK zm;|U&ztc)LrkK#th|*8q+p2laOrYo-=!x|ox@G|zv29>;YlbUNl+P9nDgSC6AQ6vo zRsEy`Y3wV<<-3pP5Xt?H;IW*`x3=k!!IzNegXcp1Y)czOiT12Nk@yny(Y+Ki!Yqr@MsBjW zsC4#=x;S~9@B)j>n%%W!{ncPiR=~Z?l_N4#`O4xucNN=P-cs3r9%3#<_R8Kg`axI@ zI+{tWuGuavyvtq1-BF@#a{<^*+Ky*HhP;wE~E5A@{r_ zA3quJ5c+Qm7nCtx7|#?-5K9-VC0Eo6+)H{=swkGg=!CKAh{(8Z!v^kfhd+b|E=gvt zo_l4vfp#_jN$0gYC6)`hqZ{&x0R~al?J62=f~UVMjBU0rVI0jJD;OgR5kp2oJDFp| z6!BlErRfpdp*|)$4@Pn$9JLxX%afmC&PirvIo{d|{?uFNh<&eCj~q}S2%T_bGH>nN zAh>fYv(!}38HzY2Z2+|J?me7 z93R;umMA@Vo)qwrBD?F8oBh#{M5ImWRY+9ThN6)-Lg>pS)vH{PU_j_z+(swfl`c4CE*(Yhl`A7%c=Ddk$vD-}{kN zsbuy{K7EtFuyFBESAigvx_v4XPbRkJTAzIB&sN{QM-{@m637?yI(l}eh28ORW66;~ zz&&OKiH_X=a}TuO>UZzw)vf`tlaREWYMa~b7W|w%!sIz9KjyGI0$LPBP z-)>`+fLV{&BvRpIvo8KeNVC3G8O`A3hSwEQ+TPbZ#(oQIaa%5lrnjC1H+^k=xt8Qc zx!1&clGb%N=DIR?0Y}Dlh&)-=_B**~aT&0fSKZ%ZucdQp*+zOye+35_NIc>RN5)Db-!9nXC`=tjg?4L1ZebSU}T z%=UcLV70dFb*;|pF zCUfub8rQ36St}e%7&W@52^P}I&`j<>EJ|vMw~M3ZYSw}Y_heNkdUo}g@fUiF7Ii7= zX{Kc19qREn=z|sOebcS`;!(7m34WV<1<1*Gfz2Bitu}q_<_uXu!47Ym7xwBy!ZpK1 zlA|z}Xn|$Tn{pwWCCd>Ed-PCpRe)@YYHqS-GW(ze#Ef1Vw*XVwM*YY}cf~Lyecq*4 zL!bYwMAjpyLC3Bj{`-`!OIo;juRy<27V0n>3Kh&0BR6{|s^uQw z8R94DISdDXc3ETC)!_<$W%JC6b1N|#5w&Ft+suqmPFIFregPMZ>fzIi90n(2}N z42p0FETW>DaIJU%uYH#0pUs9o?2k8*k$wJ}k_usp+~DWpPe0u8A^aHjZQrrIU?%>C zq=44_W(9%QpkD<&WmIN+{H7;3>*Yj$kUIMnojdR{am7Q@Nj%bd0f<0c$4xu>@31eC z=(W>!6M<@`$>%+-EEtvhtg=UBA4}{UV+q}nV^~r>z3+>z-t<3D zNFm5W3bSt4F)N|MoDlXa!I!P>dOg9H2#mI5or>K%46N#v_hB|M_v9)1pF(W{pD8fG zXo65RnLavywEC1Y)awsYo4XoATe%^?>5~a~y}Q(-EkRDQr1&C^ouw@>HN*?FOwWOuQp z3bj;xUErzED?KhvFR{l*%km#;<5M6#b8iS{3{;?rhfW z2*k#Fp3et$EYISehZXnW_J++oqL(zqstfZrlhYR)9&7gG)H*&{u|hS-+ahhjj$%im zFX}Qx^Ym)qZ5crN2`MH|OYMj>p6pmzO#0^6lcJqe)cHmLZjrZFQ2u?h0FB-i$Aw=( z6?NhpO)_3p4MC|h^ZLcc}KYr^tIK|_&xb9+8$&H)x=6DMw&?Cpq zo{c`OKEZrZ&R!AD?~45{o9^iYQEUa$M}FMVff64%2zM^?NDC&zf#(jk#uLfzpY72d z8nv`Kw|n3Jc<_F-9roOYG_Lgy^PMEHV^`QsDk|{*JbXn3p-c4%8XlLfn{z+ziBmE@Yg-w7giwM1F3Bxcbh0%H;3!+ zA4LR{R-!*WIESOg?f-BferAuo@@B#-&+CHiK@;nGAyueF|L<7s5uJT)BtAwg(86kh zQLn*^J0u?xeAH^M#?|3x>c7>z-sM2;gEM?_T#<7VIMOmV8e`Q^4{Wvf`nm2JXy~Lx z#bHQg#>6n3eep%e`$tT}*{^lqXTh%9BdgH7!S~s-xWyX$!w-_4W&9v`B+T}BG$ro0 z?G**adNXq>qPQJW`{km;8{8jnU^<=;NiI{NxFOK&T~O!2T$UGtVUTjU>eTE+!Pm-qN>8)9^x19mN0?8-rRa)oIdm3) z;YvGmldVFgA7&i+uIi8{_MvI|>6VXGyxY~n;Q&p8gl^j)L$79P?V8?qRqQU`VW9)7 z_N*ZnazdKXr{S3rcd&Y(aB!|9wBtc6l9@A>geC5mE79)M988Vl2dlX}EqzmKq1(0f z<3T4e_0CVQ{Ci9>43*_HD^%+|5s%kwmV#Weu$-U#r;D4RnVl;4+g>xJsBIa*v1mk&v12*^QSuR3=9KSaHCSXAE^ zFMJ4*lopVduL7ckl;jW+N~=gagp`zYGlM7~jdTu;NOv~`4G?eHVU#>uj<`oaE@_@ooVAq&~%KgYUQ{gT6{gRb6+q7e@?Ee8vV5)t_)VILu0 zsm;lOreMnSI4$epV9K&@q1w7a%n?dbaYlqB-}na7@ES^Oj3Dp$ouBP`yEiGIWzqfk z6crUQdu)S#_)(3Ie`pwo=Gw>$4dp4cF^at|_>l`f>sqhe`>1XQ_TH2`^6BfclhWjs zlH~@Sttd7)ZvFf&o{(BCVLYb2h-DwbjA={awPo>H@sgu!rD$!DOXzb-myRo8U*07Zg<}KrH@G<4yu@Na5}WTFH|*xIRc8@#>g+E>=Ra`` zm@3mf@`2I|<(LJ>A=$~r4;}~CPBZPuwsmF@Y^5Kr)~FOnklFyIKo-T1YZogpBvFh0OMF$Cax*$1zefay_N%BCEn^VTfqRWoxx}84gzM zhe1@oKZp$P@!!9s-Sb0H&j_C7TPV<~dOX1oFq~P1>)rK^J-U@*Ez9n_AfXn-yb;Lz z!boTSS7GsiE2Gy{f&#u}6`c@N*M zIJL3rChpKTngqP#umvu5e~EP(;V+Jn5r@}$I|nnvemI0GUL5sVM7idMpsQP3l;cZ1 zMLZ|0@dHmMeL5@NhqYPIZGawxI$7XkXNSGRP)Ynal7TZYt`JA0b&CDDhUlwO4Rp5;!~M=~XG%>Hn9yP!dgne?3AF*%20 zCZD?lD$x6jNU)*<@{BbPDmDvUfwx3@Gltz^yH;Y73Hev+cgDBGuj!ZeLVS0( zGNlxMvhj%b&$&v7$LT0=)S^X7xMC+bwK;boJX9V z&)=LV-oV$fW_%C`N{?6UZz6wR=Dyr(rQ#};0&;ShQNWONOI4lE@D?d$25nCWJ^SB? zibBdn%G6vt&E820$M;hrlr3ot7THJUx9Csx4N6}J%9RA?Q>n-uHFx>Ea>rdGl_r12 z!@p>u9oQ4ZR+40ASqhm-w0j3@y}0>&Axq{^vQgK_#TMYBPeW$S@jg|;^J^Nh7}A!o zlQd~jOUy{og}e!c@BDGhjrG`q)lA$t?|cJ#>qyHZKQ=H76ylr?3x6k`kcR(dH030N z%2zR*L^&1(-q8#mRxe%fZq0dzSRvwhalNwV3ZAR)X>y+7rt;UPR~&{G0P6{k+Sx=E z?tuf|rgQ0;SAgtH$BJax+vvmr_o#ZqXLw^m*@|Ip>kiyV0tYs}@M8(Otgofc4}MUc z9e7YFd>LTp$P?O0EN~l*Qai*)pE;haMTmhoZj>DDKI-RKCwA?Bc=wIC8Y|Xuas@*y zps;N0@^z0dM4fF80jpb}Fb6)%N(a(9#}Q@&#lImE|H&K;E1?&VNM%K;!(JLjtX6?k z*dBbqNR5iJDxRc(Ts$ID#rh2;U(&;|#!;AWK>&_X^Nizj#ZG+T+JgzWLDLK+bS5LmlC2ynVp+U<60sJysBi; zcoa{4Pg*mU-B`|{x`!n&j`Sfr19RKmz32&)<&5-rB}G7tFhM9oY%X6YAQKEZo7%U+ zyG4Lf^~zv~=cjY2t@o@vm-WeK3(h0Z%jhf)FcCZB|B7oXS|Y^&wB!sH=mp+lCxw?E z`4_BmvN7ZSur~zoF=X%fElv>pxuI0hf#k`$a&FgY%0dZ{IRH?WOFc zAEjAUkM+=DdHW;TRzExJaZJE1$-L0SvkcwMWg9^J1FQn;4kYobPyh;IWB}&8w71|C zW%m>mbB71gBc1rqAMp@f%5b%@UqAX~2NgWH7`_dcn7@|)_{Pc%8$`dmI;LoO&gE`# zN&*9ARkR8_@RIz^=lRLM339+#7K`ejVF8=zD8t2JBMCv_%Eh&85($^B=W~;NzD5M+ zXXI|vQcE@i@kAty_~*mYGLe8Le5|CoOKwMQDoy*Fr*fPLEv?RHe6G1~V!~$;@)F+s zqJ!H9$JWX4zX})0BSoHRYd~C}Sd;qfS-y2Fa;*+K|G&x|D z`Q9-a_^t+{x&aDUzZVS4yp8!(eXR8t(%&PQXdC!*R19_Dc(=555Bt4VwrKNp?lhVD zh}MRuW7(}=5>A50Be`0^3L05)S!3tFf2N3}rA*tEUSBN>q2#L-k=2=~jc0vFKcLoQ z)ouegMa>6+(){>JwqM+?VkPyN6uPAB5yicHZZ`Af(@T!`emg@B<{|U!Un8XxC`~9W zbwlmdl@oM~8>XxE9IhG@mi(w>mX1{O@#&k!^voCU{BR2agO}n$wj5`?<20Q)c2XlzA7QQ$O<(R4>#yUh@s zSd};M5a>nh<@MG6w{$T5#sw#ZPkep)4qGIx8RGWkO7P`pKjU^3VpSsPvTgZ&Lze8? zUETQ2=MX{tke~i|$2#x~6bh*wA#dBP>1;XZJXA&}9Ov1+UxVZKJ$g1ykcrB-UObw1 zpKb4<7-NH>(`r%KR1dm0z;;e1I1v)(H~5CPUyPl_i>HcCc28*oY4xJ2dF}chVkOa& zCOjcB66@WL5N2EWt9~vNmnJl{h5YFN*$ML2S2`-z+PAOJlgMXp|2_}XMMKupOhU?- z^C5m1$9+)D>j^r7*NhH+F$pJ8Tg7$e)o0Vs`S9+jNacv!9F@QgP0oOLy}*8Nw-Xl> z?fRn4y6pmc$DYioU*O&AARl2+{-UP!&b|t-PUhSt69v?b5KAOw)UMtw>9flyPY$t` zRb?Z|)T^2{!?4*3QN8cG+4$>3!hzIuF^jSHxcsgU|6_2i18K+ri>~6sspCl&&8g0< zHm0Hx>N>rqahqO13@rXs!2r#GKfFBglg`Y#Uf4irm}`r3A_Wi*G`y(k-e7>tbIaZacr3ug%z2TGi( zjLg+WQd!y9#_XLBp9pVN{?r+OIoCzGBCoiHdYAFZE@%u;S1EtV~9;O1lhNFT! z+zJ_6u0hyE;b2wx;~(xjsv^}(8WVPh=ShdZQZLhj6sTLVn4*Mg`a!bcPCydjzs+Y{who+V;-dr>n2x-=wRAGbGw{t5M|cmD6=!$HzKchhT$U`EB;!R}lW{YNuDlameW{z_HeBy;TLXq(D3YUv; z4&zFwQY>pf(^9mo@Mi;!HqZOfl`To*g~&fjY2QL=+)@`{OV=Z}QI^O|uqYWoL9*WY z#7&c^2I`aYObzivJ9a$EhfL#8vC2+ZuD^}HHM2>8Aof2O8m4hjtii#UX~V@q8xv(} z)D?~nxui4~X}>E>t!nh>9zy=zz%^TF!s}S7jutvnB_j=G>8VzX9+5<+=R2kN6Z$ z#VSGo=2R(InI@(v!RCIvZ+;kQKXhI4``M*<)p72@j4yYD^kEM6&cPia{n})G#LOY; zy6hG`&(ua3(H<;$)sG%644uK-$CUo6T;U60 z%CijfjZ9;XGy7aK9{r6OmT1SlfZ}&l`Hd=bZ?`2sOZDH2eI_^Kv2D!p(%^m4qT&wI zi}6|Ky^}5_9qzxOWh=pog8<-hSUo*zCv-G|dP6}fjE$uQEZO$I$*oQ?wmA%C(k8p^ zZMxt`PzFI&&8wBq;XM9jp=#ZTA7<)U!fA%&q4CKHK^q^_&qx|B%$GEzTVmJsr+exf zP^X~jsl_M0t2?gmFjQA3H>ttQ0}lCnZQ1$aiW+Nz_scBHm7N?JzE1pfB&X7Hn^0jJ zxi-(g!xA z+Q+Uk)n>N2PpX|6xb9MSg7*jbvq)dxq!Y%5YL%`M_P>U`R^csn#2&>>kwp1=#k241 z*=>-U^O06u%cFt$n_>B_$kQ<4ogjB`8hmjNT^af_w!p4GSCCHmyF56NP3bUeV0nlV zD)}4_4&IkWUl5NIAq)9C;1Mb6b%?Z3Kdf?5i6|;dc?-eZg~)_hq&MDIMasUk7SIbT zjpW)ld$ahi#*ea>`BrNL=w)Bsf^3L17Fnar_?bdM>|GO;0OK?ni8+(lsT}Hd77N_B zpT+1gCrk2{qKCVkuvCmkkKH9L1@KmbQT>sYXCDC62w{WuK6{Zt6WZN3(LaGY$6AmW z8I(dQaUXB@hr(HLfFSeouw+Shu{MiB{^j0_mzax7OC?0$dU;oiOA{WB3_LJy|B2eh zTTP&x_z)UhW*{)=rqZv`K4NO;gVkUgaKHxqNAL3?tz))~R@f__{L3yb7p();5x@SP zXWvVYbW~^RIf1>fZdiwM>~k_8l0@My5sUXFgPv4RRAL`1(vk+jwj{Mt1g2t~WH+pt zfXh5T3{Hvr%ylC|JFxUK1lnN6Ys8afCR7B9R_3Qk#oODut=T=gleBbHpv=OqeVuqP z%E0LXFr<_WH!8StdTE_7>2bpFf)O`tj#$V*;&`b1odH&q7+aBHDQ`f2Q zsHcX*&q%(x3PVc+GfE0XrZ4Y&g=O-714(F-9{fI7VrP9sbY=y^-6bjZF6PSTsmXKL zo|TE2)S-O)ZT(!)LM50>xveJNO%HK_!j5FSIQH)_Gt94d`DT8I1()$16O1zTzim9JaQnmk*P5t(wo}R0cX%(pyH@2ojx>}|>kk%X;=(@%-9(z* z&aEx`?2@rB@qt0msR)uST+rq$U2^16MWO;V4jsLrp}3v#Ohb`%C;b+%7ieW7k^K-M zff-grPSw}=O8|y z49AvNyIhWpo?#x1kRC$`Lv9hUS9VR_9UvF{+$HFl?Vnskcgi1;@f@fUCJ z39T>TA@|Of;zxtvb@O;TtElCRGw!l?S}_sDwsxf9r>?F?k~tXJe!qp`^f|NLW@%ur z%7IIdBfKf%&LXi+%DyRk;zmAYVL+n!9c2~pV7;9-(cU|xQ?W4I*fXPR z_0Lt(e#Amk{~MWA{jL03znzuhX=9@T!QG+Y(`5TYTb)H zHIv_0a>${#2ez2|dQNyEc(Gq});j-C>W%^h!p*-ZetpV*k=I84;gb`MxyaQ&8|30fza|$|IiBM)<`6!q_D<5#QGTn_{l(jhQlH1bIBMZL@X7(dsC(n6u#lrOj zrfaaUl#9uV=Z)+Lc>2j(F~m>9UJ09I#p1AF$Df!4>?nx0bl+>p2wtevGZxZIdx-NI zTV|2Fcn;>LY;P70@`Nyx53C&CJ*~d%PLTwvf+HT|Ny8-=wUtb&{FPmT$?ASD$VpQ3 zRULrd_GVB!#Y;(&Zhg1UVCW^dQ zVH68MX}_h0viQqr^+zN|EhC@)9_&Lb3A61f%igf&6CVnU+&n{O8fIG-AAkS&>mnm~ z9|HDqsPa0m`YhSFe-Up431GFm3D*cwps)gzUbeS$wxcG28hae#Avqc! z|NWjfT!ROHs+f<^DVv?x{0Ts{V93cvghVIW8}eR-kBxNr(F&@NK=s=)OJ+FA6Kn_R z(?XvAn=9=j@L2TKu7$l*6Wcb30Olp8w0L8dM@$8Y_f3 zRS>#Y;mCW46Kru7-pu-jkU-m%q$Wch)9$8!?X^=#KLIhFnEJ^N#3Dx`vO-ZGq<0!xr33M{~$(kEU>7x6-)15>|5pKUtIaycQ@9d z>yaURGPz?MLk>J@rqkc|1Y=bhk@otdphhVEQ6yaOZt*nGzyBB^7q6_5j zjps>{ImNzJBr2acDnAMwN4}`nnziW`xFlcjow0KNZ>5ry3rWYX{5qe}ftsr&v%xrw z#EG2hs;R~pNK*Y-Fgj(K$U^@_<>#lRv-7&Qd(_?)!GbQw_(96q10p)~sgl zWzrWhzC}o_q4LS|u;phyrQm9v(D9B3I#)J=L*BDmh3 z#kRWo=cIm>V7UFqj~iaBMCPIKq`H*jlY`x57s%#5pciZ9z*O+aT0AL=yzoh$jKn6@ znJIctNbv?~xJm&lDO2V%gc8Sb4^}#u?c?r@+=k+z1juh5{uAg;4b2+_pf*rp{7pNc zw;*6@oaQp`rF28oWCr8^+n3m@eW{hSW4YWo;&ka+#jka*<*k*YyU;@h1`R_21rIxdzos+;8~5H@ZK>>anX~`p!J~^QV8b6&ow`U zCF<9d#B-EfkL_U1ib;MLOV}u4dd=e650B(RmGy0TM8~QDJV}9_yV3H&K1KL~eOxLG z`zJXD+&;^+<>T$w4=!<3toM4*(^mf^l)d(V+miw~WDj<(^BB;G6`SuIy3B~Yd-epj zo6S?XP~$U*LNT&`QR;`Qr^&)w*7NDeRNCE`Q?HTcgag#TJBs>h1xt2ap?McIrguze zNy%QR>oB=qS`rpW84a_fM&_lVa&(q4lpiW(BctAX^7`#tRmAEa%$vL66gTp~+7_0S z2|HY57*I=-A%NvR%v0d;MU~H$a_#f;8G45Wn-S{G^-$y3>KU=@gsw zJLSP_u)%Vp`rl2<_wkRR>1_UV->wa~w6YWHb8U&?WIZGW19^p<&|3QoLCJWY{ar~Z zI-^(r8*1piGNO}(PkYT+U*#ibPTP%9ygNNDX9&IJvWfcbv%~KOcT+9<$egHuG$(@m z!tuq=%UhJB%IYE>BYSdLelrxzFV`@^JvcpS&|tKAcjl`CShQ2~8oj$A3?8Vb9-+15RbV z<$k%hfST;p@A!v6fr^dp3K!;QZ-MgXu;D5%)y zaga?X`RC*j(Q|W<0pS6`czUu=fAJJL0T&f8js~2N$Q4aQYFLj`095iSH@TXyvAdYP7{``s?vCOW@;$=36unSN_Z35 zOTs+&sCA}GHW%b1c`X`kJh*4pdz{}rf85hO4^wzNHm?ltma_44-TW%$aElUi7JZ!G zml7~-SaAH^dyl%K-}P-ds>XWW*Y;xC4ryUKy~GTb(0H}!m>($8SAOWVcz``Ppo1}7Fkqe43vx$U{TS{9eGYdTR zXr3ed#a7DLq};>?M@f<3wp(&bpz`r!EpqC=*Qab5!H*!BPy?c2j#z;f+ki7njaO0< zcp*t%GffK!`B>2b>~}KbSMV04nZsoZFEc9-Em$hmIz}a&cbOPOADtaG{1^Ggp^j__13;<#b2s9Wpd6?DI+jmrBBY#B}1xf`(h*qb~?8u%>m z_)&}@CF%6dJ54lPMx@+LL#94g!UUXa`X=;Q$5t%ECsTpYuYON9<}gnSRkjTWRNcy0a%M|`qC-1df zsTe+qe1A|4&+Ce)ci>u!MUWV)o$LS?;0YJ%s0up>Y{28 z2sJq`{Vl<6)GUzk9(k{Ub=>=rF8?S0>G9`t+@|4gd7+pyrokJ$`kPsq%$oKP6iNYJ zf8+w2Q?w5Tj_v?hgp^c;0oHQn)O5YfCm3OVR$Ss3svR`JuuY1s<^RHAPaCCDPXYbQ z7yap_CLinFCjyV-8OCp9o<4dJXLu~+g}5h23-}S9Z>z|5 zVQats1rL(;0piIo)9y4?s~MR~&r~bx9X}`~l#cyofmbXn-&{r*aaDgZd7!yvZC*yh zJJk#N#m2X18LCNAPSxRY>2zjyCV94*mbkiDv)iLlKV;?;`Wv&DUkL^WSPK8B$+p0v z9{*V7YGb zYe7ut20d;>WY z5rP#|$*N%Czd}qu4ZbuhZG+!JOcYS79Jxa}+fS*1C+T&Ht@Xda`in;{ooczn>4`rss-v>ev(pNyx9Om=3f`Z9fG_7}LMVW33DTVDR}E)GkFMi^b6Ox> zrP|18oX6$G{s&_Y&cv%AD0O3LCN)kDUhFn^dmG0N4GM6^sqAPxnIKbt$b3?@Af+VE zVPrW(<{b3j>u0aDCj^q2`_mJk0qKwMs;xC?ucGwI4BS#%tb*KA>OXcI#;C|^mTX(` zx&}uam(}81w+T`IOo7z+0Z~XSMCSA!$ij95_Me+Yxpau=`Ufec0OP3)qqo^Sxk*Ce z8Y)Ae&2gU~gYE51k~&+OpWh^>B45>KS1!Jc9;~7_ID7-7dYc^C%ehHV+%eCYpLqr? zym0yKmEMw!mF^lajwZa)X5pG}Pb&9Q*S@P%RChn@fU~*hml1K?Ur9z7(o^8Iw`GLJ zfliP5EVL51j^lx-0jNR35DIY$N!Z0(E#stp@^Vr)i&dtg+N%Dn{mn&xfe0XyFPn3x zc0RF%{1NonQQB&Z6Fozmxv~RZ{OwOLCGS|Bsa}!UC7Ihlf$+D`>uR&K#;fp$6mC!w z7?>Dq-$O4+6+KD>)a zL^PhTLJP^LZ*<~O7*H7Y&Cr-ns^yX?&Xf7>&S4t^eopV``^R%i+GSKzxpY1)d5nfi zx|-PylRHiO-jy5holvta@xt3W%9=h2Aq#K7beVed_KfaU+dEj??T)pI*eoEH;f?1j zT&@tV{o?aA>EmdI0xRuGtN-}e__%iQH!DK>Lo)5J z+a8d4M__Lr4w0EIZ92;;qerGdga~4A+{tvi_vXUovR0Pf+b6#~E-kaQO31?HNpHa; z;4R5iWGZ>;tBeAvv`5x0Y}mu{Y!nEdll~Z7l$$H2leSCiOnrIWt~kMQ5$YV6 z)oC;F{|^y1nUgGglrxo5AeLV*iwnV zGQ>Raf6;LCzml49giW@Dxxrk%UGH2}xCvW$oPpI69n-ow%e(a08zJ~~B{9G9k<3P+-#n^6FCC`s%=hVqX4Dc2ua7sr{^O6IXKJUmay>z>;L%q+e=L|kYdb?! z3O;Xo&~WD6vrCK>>)0d~(}Pa8iLy7@ftM8~E@~73bu3t&LOb9`aWs_x0|k^y_Ei!O zB}=@@y;+@EG3d(VWs3PF4c`N8Q!VmS9wB#gE#aTBEZb$6^$zf%&wP-oR)(YZl)#CZ z3;DRR^8RITWz+F#b-LUVWFHHM5W`?pV-{I74;=;}w7}dMOpkcMr1D{8pOU7!O$5Z8#usb_uB^=i_!xutg$aT*ZO&fA z5#;FTZ?*clf-5|t1qH_*pJ&MiGii_?f}VnwATr+f@-YARE&zP26*0r^5j*U@pxwOn zJq~#+VsR{H_y3fam*SUjn{m<}-HREqvVVgz5iy4z_=9iB@=zCGsK_nMyYsJ z>IvKlarm~tLTfIJ*T3k6+ynRPH}f*!c`6}khHc2z=<8|UBsJ(E+|BaKleP4|mekyC zy9&No|4Ov`S?9>oNA(RnWFyP5C4pOE19VP0yWhSzpv zAHNZEs{L-KYN9DSDgWKKNuE)G=$zle(iJmOzNcj$J~Zeu;BvAp7mpa~-Vd=2pwsV+hn zKot92yFepo+!v zs#8dGBl1SyMbJ5zo&2#Qr2<&0)_CH*zW){?MWB55~luFESMLTrKR&5Ud zo^jgu00)zN{`fv3!F#NxEBkwqUDZj>*e&#pMCgUx#iv#su8xpyV*74RB~SZFVOR~$ zggO;{)-Gc8E76y*d`UoGRp)h{fZiEA>JOZsYyxM5BO+(u=T<~SzS1u8M+X`rhbhwu zPE*s$kWamE1-I>VZ;23@>UEu|$y8!AOJ+#JC+A@1zB5H$&#NB4>A(A6_xy)1M}z^p z(*-~KDz|OW=UR?tgU45#VDbCACEQ5w(qFZOVOZ0>K}h+3uX)lKv{mP z2Ka2q^zXegHPmid(zxUDdo?}6-6Ckd#bADOZd|T;^XZ@?PblTB*39w5w-;j(^w<}_ zwMLMTUh=`HVY~PID^=C=T}~4+u+VceSWn>Z1dM~Xdt$d@cst7KyB6xm)W?H&)41m( z-y9^?A#f0>l1JaMK26vz+#b7J%3lqGaotKRYc|v59U{v1oJ(J+TEchcCD_CuJ%=Tx zwSoYzmnH__|KN$)p(lwb_4oD{_eJ3Q-oH*284^Y#(5%PPJ6;g)V}>R1o|Z zJfk=PMHwF8>c2jZ3e z?#!RY4;4f$*FMlRzVSLB*%nC~#~H_3bp~=AB4WHZTa=ss*aZ*;_7ZxnaSaN>o>~;- zCAbvUZE@236wvE9G?#@(Y&jEzB>ZvQ-cq<_|T|nHgGr@}B<+J+b3g4hf(QOK( z0luu-IkwFF44M3fLXc<|)=t&lX!oIR+Moc;;)KUHerkdQ>eLSC{dd2Xfja+xtJ14s zEC!*qm|Q*dAZ0HTP==3sCXdY3{+DCEIu;VF$Jt_sQD{d(4XNQKYaP){qf%zVd4R15bBD&o&!A0f1Jrj`a|ne))OsC@*qv?eLA4w@dxomNx|f9 z-&gHNqiF^+=9@(VCdyX3jqZre?^7QJ->kSzfEOHwh{v6WegJdG;vIwzWp0Q z-pDB}Dq70kQkvx89hEyAyDqjEfgB>{Iuqq~4?>B#~}FN#<7<6W9wW& zKECgmnvoViW&xD|!W7e$Q2qC|hBn+M&fxOIq5XdQJoa*a1Z4mgLx37PjtGd|<&3q- z?N1^^bCgFu#34aqpE&VRh?sW|cKmk;N5@6YoAva_EOODH<|I&MYxwtXpQ0aN|0S@#(JOOZesZV-mb;X!iPx(R`t z%v(66)aH4KM^V*{Eyv3oGQ>u^-LdqWi~dUMa$G@oq4)A)c~imjrjK?p&$gJ97(E0G zJ<6ZG<%kvShKK%5s(jUc!qJc~L_{z9!#I73$Ah4Aj#uo#QB^c2?mHOBS@Vo}Ai?{0 zt_ZAW&eN>tfZ-FDt?aq2l)0vg;J|CGjcp-t)r z)!nf_5>RCPd;~*UNZb#>Gjp+3P6~lAc3!i?k6yLS_!At8Zp*lbIDBIyz}2+%UmrU+d5p|k?VhnuG^+8u?wE_ckJuO;&K#;=TqrqWFka%{w#?y>8m#1hIEG-i4_yoQK)9nm?}ZMe(aD!J z8AHyuVr#J868ck&+nWK51bphW@=X2KLMM_odfbM_r8$8s)>B1~N=`UD5Cs@7g2+r^s0fiEh@*xA%Wq9Nn zJm9#9Gd@dtdhI!6pCnrFx+);2{qW)cpG~P0%j>t_q{Jq#KTy<}x2$syXsW(qxt+sj z&X~#V-JsmcF#RaHfjy~jKE&GjTg2kBJrHIs#p2R!qMU3G>#9SNeUG>nE4x_@Cx-4a zTcDRGQHMTvAZEKcYn3I6u@cNG(R*>7rSZ!4Mv`V9vU6j>ZPf_-@_ zUsX;HF4sM^`NB#1GB9jO$vJd;p0~>H60#u(I#zlWd5W8eD%dK<{ku4y@NxFvNG#pJ zeGV7qacpzoD@4;s`M&qP)~0~Poe9=uy|3!X%gJ3%3TO=%FM=&m<<0Xu+(m&ldgBM6`S|DpqFTH?aOOhm8f)jnCz|=hY{3oSrO_eUc=r&#z0}8oFsy)N~G$8zDC3d=9 z{g>J>#nvr`6@3V<9s&HgGkg{gQ;fG8A!pcl9tfG@DY7_zvBlOM+8=wgUD>sONzj8Y z4KAEL3v1uakc2BM62HhwNmZ@K#3~cfjTTDw3_FwxSy5?Nf>$rT9 z=mVI1s8!y_(yxQ?D$t7G#sOvk3DQ{jvKdqL&z za<-}`YEfqaZ_b$TmD2`jgF8H(ug=h-Z-2!C`>`j)wHf$93~^!^GdfL@bCM`w{kcW! z7=q68UqBK&m+_;l#Ml<{-V8ErBUiJ1OJApdvFuEZq!}nJ8zm$s8jJ7Gf2B;K$*{zH zHfPL;|2M|$4s(8<;pbGMaKqN>Yo>2YI^%XG)fnsVVMJoT*vp#3bIWRs;>$oHBQWoJ z(#m;{wqfm;7{$Ur_w;Y5igJ&uaiYG9S!SB8F>5#Ma)>6Ulc%}1ldHM=)$HY+L9be4 zr@q<28S9JsY49!{^Hg_6&(=g3^8Db5eUB?{;+onQf8;XV(-fI&bOvfu;8!wM^Zr#q>-QWP!Rw-eY~7`6 zk}}Fc@W^ihCZXQzDbw?G=V?W2c7Z|ldUGeXA~Sf03;Y4PXETO4({oIBn1MN6W(c>p z-a94YP>9Y|;!Fnke8i^qO~kArZKJfhK4Cio-e~OeKJMwDIug9?b7RrR24KH+EaV|Y z{)|(^nU_Jc_%3x16$zC>OnpL1dK#|mx_o|JmxtsbeU36+`+OtBYGp;$1Sg^Vb{HsC z{bEfJe#@9zwoAQMnfAGMH)S_irNAq5WqlZxX8YEc8CfMw41Bky)AAyC zz%O`%6QRC$9tXu-FdJN$BV-BZtykIrDM`w=RA6~_rRZX28k$(j>m5m>2YRsn>!4;+ zoVavHR0&(0A%(=yV&G<;ML_sro$%l9LeX8V{&N!O!ji%&{0STU=;90a=9*l$f%+7P z4YD>QSnaQmcz{t8aC>S&T)V>Jme3y#(BD1k!o-jzdIDx2LMZpRK!1(m#rhBpMAkuU z33i*3ei&h1jgAoV>=T+$QFs|5E%CcJOU(xoKDUr@x$CPo*)}6O^)gx4#?QZLQj*`i zElv(Myf&l)*!w$A=Zu4`B*ezb(+}f^Bq!^m0~D{Y znU*A8v32$9X4}cyvL>$`e6lgI6-EHape&I7hrrw6%UBY?J{g^XI7(wpFtrEY4)uUw1kZj#=AN;Ck|ik)(=aME_~9U=6IdsjWa^5u+pif5DoNRVH% zXm#b)_{5n=U2FjD2P%a)5ARPbSv3K#e+t%*v8p1p#pds_qQfSBb`+)Dz{8~u=m3-z;XwW~ zUJ_8f>O2t-B1FMk;TLEK8l9P?XcY$pK_b=K3HePAWKEQvG3`pkCJ_p*R3b0&VzB07 zITXFPRQ6`3376}9@xcJsO~CFdqjnK38^li!cz=r$pAjy5B^;GxlxpwqM=0Lsdl1jx z;Pyi;ku_fg)^ZlTIiPC&fEEx&eY#=e!R26X`eyEHa@ho@M9JcVZO;l0#^~|bgO46t z0t{HAZ$`Mwg(DA+?_;VM%Ce9^=tYAbjfUa(=kIS9HW&Rm`REefTgJZ<({7Bto7mH> zz63YPCxo&Cx>+y|K$j%f1*k)<{aMR7?1Bd*e#GOOFJj!bB5hV8yZa<+e2_yvuKI&} z1MTLWM`>(vsy#EY9HB?Ji9h5wJHuZ)Z8`@SAR zN>Wg|M7lw`MY>BmloAl>h8aRc8fmGaqy-e|ls`YW*G&)KB?wUBp%%9O7ObSpSbdT{et;O{CpCv}A8OH0FPiV9t_M6kLuf)8_ zg@~@w7ulPbTAD_E;-PXWB1L%d;N`eb9p0nxqVJZqddd%4i~P0xXR&wfk9+k8@1-v* zs^L8V8+a5UppAWm5Fm71p0h$4{gDu3B60%*@$WdI&i@2)RsI)Nt%HC2-PUvVT#P*y zIP3qJn(3lQC0-}o zyuS*MFe+*bC&k1&5W1-`4;kLd6yg}39dADKFcgAd%ijc@gyA}@bw5eM<#)i$5kF;x z3o`^AdKe?*e#u}gFrDOHuboq4z_W3q=?6@uVifXS2bW~INttza7ap>?V!vj~XRp)< z%-MVL$#+%GIfmNcw`mIGF8Y^pT=!>3@OL3PjRKX$5~8OmS6joGQ3K$Kj7{*SMsr*1 z#I26p-8Y>h)Cd@Ts1AJ$x{YOYiEqrCzRTb0uzf#D?YWyVHMeN%;?uqgpv!~_CeG454Nhb=^k~i=DWGdpHlYa^BW(I`(4N5=$~yD* z1IK}fA-Jx`ujaIkONk)85+mN zp~VRK+Lu$uez+q*v_Cs;j6qe!<`|3lX&q^TSbiq7=SYT>PMY z$gjn7>o`m3Sp1*Id9K=KhsQ97xty;_%!1bHWd+KEWHoZh@>@tEV~4ZNMiI`bNcxzz z+>`+ZB|=#T-j1LX*Yenb*`;9V-IH}<#V~p=KAD<>7>z#stUcq7)9n-#O6KCE2Q}0P zMIHO0wDTIT>t$*4AuaMc<4IDTsiMa5T!A4UwE3%(K8qz#cmDB`tMa|=oJZ)~Xnmf2 z`pYT!@%=%RsKG%YwIljs#ft$Hyr0@ASBFuu5cS)2w*fM%0Qst83{-a-R!9`=ru>Yj zVUC;eOi*1?qg}b6E$7uAUHHw+2_creMDTA^YB4>7ljI*V#ijdXhKww-uuS#I$WsVu z?ND6|sfG;{6Nh%ZXq}u?NUuuoj4WINa?h|FBAb%`$iA z$yg#)sQwrShmuDriY$vIza!CS3j3xXLPo1|W4gKDf=$~A87CJXD7_S)?+0{wQ32)z zZLsC{>^QONd=@IZpr&nqu7JRbYY%#EXstF)P+y94UWwmW^`_#{8Kmm5^n)z>cE-=J zCIZG0n*dEQPF)Y5RMew1uN~P!uYVGq#hIT!XXv~%4!QXa~47%*8 z@@}WfqdavrZ}NJ8iw&-?2%pvoL(?9-%bTslJ419S7#MgEdRi-6!v)kBImjxJQ(7$L zzn=!9w)gy?z~ws=S40`mzwwvnzhkAsA@XRt0c!a(3tuLVvF_O8T^k#+LMD7{Zi(tJWH|{goXzu*Np{q)#Eb zse}WN{zXONfCFe{#Oli9s-^|JlyB*Bfe93aUVl4pQ)x8(c|6@8seg0dv7NUV;;a3; z^e5P2OAeoAw#PBq93R{OQX-Q7`A7{JVYJ%J~_3_@d7E zE<^3Z&TX90z4zG;y>BQe;5g3$LrG{;?^jkJ$Dq)Q}t!J zqW(1K`a{i6XscT%cXIGt9R0E2Oup)`udJsXTx%{zqd!YzO9f*%AcCxxv^Fi`&ahKL z=6EBOci*QER%*_RS$LEIhu@S=x5Ya52)1n#Wp)U*y&4>lZAa4u@E-p0xAol3%Pf9C zCteFyp|$5XR5?}_$)8IU=MsMoLH$4{5xgZxIm95#`1FNWv(rv)38?1_B6)13$>!)1 zaZqz7L0EX9W|mO@>5x9dbGnJ;oEX=2&-u^^Umu@$I0B4k+kSx7HbN6Ze zR5Q=TcE!9p1%D@}aY%ZG=ebe*v~3zlSZONXVG8QJ&&+)GjVPTem z8ZX^F*_t&)ziMFvRLJCq{pLP^VGKtf@x_IQ-o$nrZ&r4czwF2zYWkQrLs9XxInw zu=|y5=%FNSBL5BQR2k1%L|hr@G-JrSC=c`paluAHLn#yTP454;WqR@DnDXeL+U^GV zW#Eu+>#uoqvP(3*cx*palM5e&X5LXCn}jUhbB0&@LM(HWQ+yuF})Z|rWzJtotk}FHtn$hu4uC-YS?E(>2I4x`{R%5D-#rpOvBjg9V(e;O12=N-o zU$XHGlE>+fw7xsAw%kBh_VHxDo1?mqhp3vpI?&BPd?+p0Cx!hR-Y$;!p$)@^ZlTCf zCI1PggA+M$fUw+k?rEA}bo4Jjm3&roJ`Z!(hDT3!&xM6+MPpEbhe}K+wypr(b5%|= z6|64O8zpLcCMHEfb)L4->PwC$|KHL4*<=8?a0~}Q=}kM~{<2kq=txA2pcAoQDJ6q) z;2CFDyNY(BWO%P(yu7>rduOne6Ey3jn&>qVaVEFrp^+_+@obVKH!@F|b-c5gLUPA| z(lPsaZ39kdiG1%p3saKsm*Tw_Duhngnt>M1%GmF?lwv^?E=jBLi4_N$SOe2N`TZYP zvedcYD~2`;c9N_~9BXzwg~!uM^1|;0KHSCu&SmS?EQK-oOy&Cev;6b-8fAZ^H!WO* zZVqt9f4@BSdFx^d%Bj9|Y{qUyk_+3uGT{vdMm{zZ?TveWlK0~jy-h9#sBM+2kim#j)0_mc8!}cBd&8zPVbCYD&Kzm1{%Vg z;TE@e8&$W9Ov`L?9gZNJFo^8#MWr=r$`MogJO$j8L@kUlqyO(DsVfPuSQzuT6%B+w z4jtP)(eJ$Nr$t=?-p!TOeRE2<=a~Ur=|R}?O#kM?VchsCAF|K}P*j84iQix zTfzS|iiI#SDP>SaQn*zJZgcmEG+5$3p!WI1eu&2V(e-riEex5;q%`ygx zIftzW3E^K=T~`j1ZGA39>;z0x4Z;LGX|G9 zfN^HY#}Enw^Z-3)2Furk+`UZarJ|sP)0;}rr3k8Fk+BtduJmjUaqcwMfPu95*?7}( zZznF{!T=%!9PY!fiyx_8F=-DZU*xG$Ms>ZCrH$1;C4=EI#?KPy9TWU(ynB>1clylq zg6c)BGa>(Q6rae&^t(b^U$V!rgRwQCkSqx0;k*sovMfTblav`zFo^l>Zv zroXzT{i-DhCD%Yr0e&;TB4Fc0QvRzUgzW%LD&EN4WZ1Ga!@4VM>aLLYRId$89|th~ zD`@5kD{@w9qrPAFg3V%>n8C~+G z2g~^ZpM<~gnWp-dke$PRBXN+ixXK7asn!?CF`!vg5^fS@>&pu3874vd-;p8AIS-W^0;!&z4PU$UpEwFo(X2*$mhjr8d?)eO>eyWRr)g zoa+p>%L5U{k79S-+%&I_n6M;*66jF|hIfZG7Lm$tDhN^^7TmmdjDjL}#6tW)vPT0| zT-72(97xS0Tu5iIOv7k=w|pl$HB-(Uavl53E35n4X4X!Q8s9SQ$nY%cp{eC9D5>36EQ6jNLJ z!p}(!xXpr#O3 z9OW)mTc2BNoemTwI-2E$B>r$?zH%JXU007oRNeMb1iZ1em9&y9c$2aG16~C`)`14h zh#K3ArXe6k+&G`t2^^5)-!SA`Z%4L3V}t(!1*w1JM?(fxM&;jvqGh5D&=nrs`w|NMW_qd{eaw#KdINX}?1 zzv(kZA7bwyZL>4^XWpqomq{SGG`ez=*My=mL>rR9;8|N^iA`#?cVug>U-#zh z$zPiG1h&GWLm@NI9DLk(qVE1dPz8qd8^=4^yA5PPmN2I+{nGdYG z2%fr~haRNm)vRv2=(rk`do_$-#AzwmM|UY?6LpQxB?50pj`t8#(d6cLA7T!2MGcrb z=yb6Jh_S-(ndGfKCS`lrA2B|Z`0j~)PurDK`C8wiPGQu! z>~|T(F>qkWIbQ5m2RtFpUQ0|y^Pb-n-P9QmGaOI~;>k~c3}OEK?8G4vTQ=ZkeAz7s zxz-9t5CYKHg>MgVFk>^SkF2qUw!*4Sm++=1m--b?dbrkM`99{qRGkE?%YQ*-D3(+e*KUe%isiF+D6!k^BX(MZ|hETZ`zuO>E@-H!VNoXjxV!&0s zFW43nF0#v)`H*iHwt%uA#45lB;tnstjt{%AMfGgCg)oND{;P3*CZkhjJr1Pn$inrQ zw#(9W;?JX505|tOKvMr`OOrNuwE8JY^ANY zdb&gVTg=hkR2UyW=Jd&&k6mjc;%MaTV+6TVv-4JN>-*Rq(+`a-)wlnen0^+Ak;nZV z*t|EG3l>vS?x+ysW$1EY>+N`;y|NQ+H{Fk`Z~hH-Xtz$xyGefBPa=I1$~Z6@DjDuU zhesFzuq7C_-(Q^PyRX_%nRL{Wo9AEl_L@6{);juYw_aE``sLb+Vq5kxRiUcjG{kxd z^Yf_;B?#g3=yu(X+aGSX{bh+l=Rx}&9T`*_|^K{X6xiyqg=uhT%jJj_aS!6bK*iN!DRcbtgbS!y(SwsLAE}zqC|snM$@OYb4I|XP)nQAdC-&LDwyrmdp&qe} z23LA3i*&@S$(-B0jy%dggRfw672vpGJQ5RlW2T zz=@o&pRjyZqFy;s%^-CZGpyid_-6K+K;TKu8wRy9`~AaR@TxoiQrJCiLvgBuSq(iOKL93~sa#u|K5=v%>gdnVn02k~|A834zJf>eg&T&$P7q=Mh%-3vQ5 zR4<-NNWmF^QaiF!{&2yz)3%^9qRbiHuG^Kf@U`oLC7X$}D(gZwNjrmk^sP0@{vgxU zeVBpmLXnEM@viP@?8#y~I)=CAWPt*eva^StaR5y4*&GM4n;Bo<}-TCQBTnM*!icPkN1GSSg76tlx~ceZ%My6UNLOVBE;!$FJ=Xh0Z$!^3H!_ z?Xtq6k`ts7;23wnTabXTUp;ZB+pOWio~pmq{jC25Qw^$J1!*a$_0lpJbQ1!sPwEf? z{BHu3V}Pdhd2Avy%YUsiHzC!i!;{3*uc*&g@A@AddH;F0L_i{k14!gfloJ*TR>4^c z7_{;3F_xkLjw@wSU~VdY;j6gpwbnM#OVz3fiC8!#<(LC7X(T?z-P#atPHx?ty9~fc z=VpW%>&HvJ*4A8KwB>* z7l~X9>#+26|Fq=|FS%HgClO5BY*AcAfw zhM%nsc{b9au!+mAgWw@rBHD_Fbmd!Cg}iS(xH=DgF3Wyl#*gqzQ@brdK8}y{s5$ys zI|HN4@v3O>T6DQRN@>e_d|iF(9Rf^VcN3WGqXCFEX%+7KJP5?>3(U8LC==@e;eu`g zCK>DBdzBZ1Vzii>+*c*& zbyD{6)VWKn`Eg+V_acVO$F^tJ!QvLl4l6Q=i2)@PR5;U6{SnzRsHiT zZA^$UR#$#nV)vU@^0vm3d3y?-4o8p{U^j=$tPcN$}36r%H?OlV1P%?`|RQvQ~ICc4_+J#gz%?#RD^?|4ZjxaI!mCs%@UIRqgE) z(qR=fb?z0Y-xY_ARF+IY-&wV|IN-?(bd34Je7}Cr5?tSJ&Aq0^zB=S} zAY>X#fKm}BEVWn@VppMAWo-_^1Sx7I3I-bV3K9Ym4thV>aPaI-Qtcv?RWr za>Ms9t{tjKtJcG!g*%_~8Lzy%;2%KP-Y&3TQjC<8_Ez?4D8fzfPX=J&GFuwy`;p-7 zhdlbCV7G7TdRHARP8`%Uj-`?fwl$Z+OAh-~-fw)eCfAt1KE&~CL`w&^%rTK~Yh+Q2 zn@zonu{h(EF{d|=^LW8tij|mmbL>8J{#F2R5&2;|n=#B=o_$r1YEM1?6H(ujyx{fy ztN>{_Y0ORjz9-LBcIryxzcAiQ7>rt&)?wXDl^*g9Chf|K!RR05eFIPd7F+;*j3fer z1V!A)7<)jiZQitx1Fd@%#%~6%_@q4cu`Z3AJRSIXlrv;+<<~=u5vm$Cx@)lsl)1*% zB^?oWH~8c~e#{%Xz=ZjC=#Ju^gO>sM2qozdY-C8IbMd`W))fs6|5L`lH3bIuqS&Q- zo7|+>C3sc68GBfY-}@X>DUl)+`+C}m4-6+?#l1AA9@`y1Q)%Y=lP?9|U$l^Z_1s9~ zW~?_pMy9$gi;KxLpPvU3bvd?ASh}RX3655o-v^a1*{&BJ^=rG^1En?yu)r*@@jN5{ zY#J8quJDR#gnHrL+GTz>_3ioz8lKp^cMxCquh|^7$v+6*0K^H1k{$%4)6GDHE#sWR5KKeFuF9; zuus3e!%vGmS+1y!;eUD{apT1Rj&akwTHp>1#aw>rA5cK-%zRNQ*JS_PE z(PS8JMpW?ya;ZD5)yj3ROpelfmTWP=f=gDaymzlohG{pDL%Y>k<;-QxqQj&Z!M3@4Mq+|2m7L6t%bLdT<} zMVWeCdP5?u4qT+=idLsC_?utVc<(-+I^&ES*Z*6lNc-Pfb7;X+=hHWt+2gro+PfTt zfN7`7d_%lqtPrjIIbPe|@*4%S@HwjAufI`~l7czYlhmXOm^uu3rplEVgZKYZDfiO2 z87p~1k5OHPS06IWuS?bQ2Z%grGOE-S%LLlzvQZ{l zwtJlZD6g$akr7Sjn2}~E$5+v_Tw0BlcG5N{o2LB8<->nJvEg%QX@miyjYp-{=dXC{%HzegbQn*FYQk>*JyAS6W%EE3Nw$biRy*{CD_Y)1t znD$C}^y0{6`ofNz;!`Q!7Zr(&lIb92wg0o}Q38keJ!$4BA;2`V`&@{a{8=;_z_2L# z{(8fd_X%2boyWhV2634T2L!XXJTeB~=iSW8d^QRTNiUV;jg6{83rRk+teiIPbo+qZ zztDvFi&q$ z(tI+M3k|ODlQSov_fhwng3TQ|lMc-SeFB#m{C+sj?@~_6ln3Kx;xpwSx|MEAJ)(mv z5wYV_YwVNXo3v}j=eMQ~^hz6#PAmL>kAcc|)vE@m7-kgQ|4w2pQ{I2yS*EnKTx*rR zY3L0M*W>htm9Y2$EpIsUTodF+0q+w`rS)hY_M)>vwnr8pQAd6$>wl)L3bpDpkk>pZ zmHRtgum7G%5;IIoQ66rC_*k;bmZ-9nQL;B=%)^!RoGA}xk);i%X64QE>W(S=Kp|c@ zF-xi%f9n)jUpmn2azPE)GHmVCwp!hnuGJ$ ziWTFI*x>{34qTM^Ye-Vld$!OZXRdJdz4=cO?p%?4lXcD(H3JopBVTKCX+)u;SfEC` z)pLfkoc;FKN~eB408-pPMJf=GHR)xf#(Z$Atn~o5AMg^n%;5N)n+0wEeWYtB*V?@8 zF7V^&_|2rGP)@!$^wJxH z;L4`F#MA_#wO^7x9&~tBxR^A;&)TDAJQwSS^5xOx9R{x}b!I(!Q&i-Uno2R7%j=y( z=X04)lg>zpoB}noe7U6v0j-K0PQ>ObG-rP~Ev9#et@Qg4$%&=3@rP7ZgM0(ud=pZIEVWtvofyD6fle6d2S_X@ zN_*|HOKQZks#KY`_W*Rc1|C_Ck*T&n`n4kpfuSAY?O@jC_+Tj8fI0D)`+Fr8^+Jvx zzs-JBUmYE{lU<`0`QL#$PDQutxs#JsqYK4_FSgnPZift@Y{c3=em<1Ck^4B$Tgc@L zosU6i7(*u(+zRDBqV+eftEuUxo@)dpC%5YYTJYvcUTvKEcn}G%Qz7%TziI22S0y&gxmSX{}7GSwK?xnH^^` zjKe^*s6flIW9CL*kd4(+w%i3Lor`kSnS&MGL+7<|DQ%*L$4>t2TWd#MI=h|4dWJL> z%rnE_);n@jBd6n~q%Fc(oCNpTkYe77XwUx95Q3z>Qnb%Wcytv?^GV0dBkiqWh=Z`Q zZ>g`(umwylUQDE8Hiw!oayyIev{V>S?~5qCE7i?Bc?q?%x0fpePF6DPI0C%8E0DY!h9ST0 z>B?|IOJ3Rk`wi&Ge!yFghF>dYVfFfMK_Mm8oijJj2-dPlJSSP(p0mfc5ppDQ>7;vp zQG1-vjT$Qk#vv(_W@&h3`$#lOrAitls;`2#xGWJ0=-pU_vom zIe0sKDj$*AznDcV%THa+s;l5cOo^s@*5SeI`)+Et2SqSCf6v=3#BWcVa5_BWXRkoa zO5b#Vad{KgcmZi~GjvIWn2MnNVC8;xQ=HFogHv^1)v6Yai79$8>Z5CaeDdfc&5B8? zg+IP8KQWe8!ihT-{Qt86JmrOmJPJCg-m%7N+kB;X)>ENA=<8u~;0GAQ6rCH^-bWg2 z9dM7A@2SPtxZPd=)muEPQcH>xg|7SNuR~5{J~y9mfvav*7Vz^ui5Agn9U)3}%r=8a zqaCgMeb8j90q4MTL?bKENJgt5$L!QtO5yUPL73?@;>;s7ZEgiN6xqqY)&U=Qp78{{ zog=M3TA#1<2I&)FnKi?SKdIem?sC5zP(17vI{Nkn+7Px@x&mP*D zvL<#-blQ*N*_#P5yo#xOY5wNIEN3_@k}hd9%9Z)zyl{%=wNM}KTWd;0sY$xJmm{%i zI_p+jS<|?xCtE6y8IEW4g>LjQk)nm*HGksHC@o&si6yh4|^TT`k}j7k;w@Qvih_uzY*^ zM0RprGI;w-?K02d>ET)gF7YV|?8R<)?Ah!K5Aoq=diq=3xT3qEh%=)zZDPx*586Yj zZc{bo{IP0Z?%Hkwh}?*9XNWm;^*qcc*Wfv~Ztoz~acRNCI1)xhs!GqWGpoQ=d-ds1_3aUx{c%vBN?a_I$O=srrTOnS*+ z|EmcK^xZWa!e`a;GRWq{LwG~&E7*VbX-lEoZ6hbbc49j1m~Za|zvWc2!#?cDY~1H| z>Zx1wo<+6!wmHEh)$l|pR^H4FBHhgx%0CXMt$eCClKT~FqgOHIYuw_GSjimHO*ezMxjxfrolvY~ z4TY~&YWVKo>aHz(2@4N}%NQiR-(PK~=!|RUhzm-+i$M!Ue1b~{HxW+fLtencV}(jD z>ScH~wd46Cv63f^&IsErr&9H+>-X{*4WLguN9*=|!?9+5D_1{vvGi1THEdqN|5g?G zVOL%R@cZ1}(boRkvbTK&{LbKQ@UUk~;WXjv>@%4ib9KbKm5}b{zr&Hn_G9tT^PfZK zlD7iQlFl)5mG{uV%h|=~6n01cj*F?oo71g2M{tPmRo9z)D0++0alXbHY}pnZ;0s@C zUkphAtY@huoh`@Op{mQX-+hj7#$|qFJ0<*j$QD&P>}AjaD`YonR`1<9b3Rh@Ls!Lo&(X6CP1gIAQ*cNC90~aTbhg}*7lRdHT=rU?Ro}}OSy?nPyBE1(YT2>vs+S+l?h+YY+5n#Wij2o&vdcLDdl=uhZ zax8Y6A}d{nHe#nLz?sLW6GpheNo;2iH%w}*k1Z^g_cVifaXmNM8yyXE1`Lj_Q?>JW zXID>b_fu{$O68Y~Au9!V-ILDh zx0j!Nzaq`z(yxzgKsIn{Up8skbA9LGOPtfu;9I5u|9nsXeQmGC(1tL!fLZ=D+UEES z)(s|jSV2WX>dg#aSNwx#hd*FtCd($2jykEoC{^2A4wY-RR{K|XJu^ULz%j)Vt2}3b ziD8cj@J>8GIyxT*!v;e;A|)SZQmBkORH60ND;6*zpUMYTM*q=NPy+&7{}h$dun*@( z`F5ZW7wdvrF%A#qBAyJ(W*`fY=dE6M4N;|olJ!`BlZ_pd7evZakr|5|{D9hXVZy?rvJR@0F9oDGsMts9o zKz}th;$4=r{}P?ML>*8(y-_sMu`0R$$eUD>N^S^`1yHT^XDKN8P|i=nYeOee>?wnh zs(P?9`+7h8S3#V5XtMEp1qX5tH0Ld@X~^ki2qVp_3>xb%t#+L|WUczB2R}cLf2&fW z08%?cyyv3B!v$zr5fru$TPmapGI7pGaiczL$vIKp($_I^-%|ndg!<5#GCfdHaY1*P zz>D|3M@u5sx7Ux+AK1G@CF&plF|wbwn_(}jd@{c)HG;9N_G>B)-_pL8yF;yb+Jrg* z{Jf1<8^X2XOk8%aJNSG#a(fP+C%OAadg9N=?B5wAPa4O-{Ush%$jXP9?^>$<70UPQl-ul8CJo%|^-S-Ocx~n7w~CeWlisIU4=}722~I=WHf*Ag;6Oj$ozZWL=1^1v zE#SnKbRwqn%m5{iAf-UjihUeiL-#Q5;DicjS%Y%i)b7){-R5*2z?gM%v5|QHEP_FwD3-^yLIHCu=XWcr8=3-s0snJvp5|-{(NT4}6s@9)va~};a^nt$ z?UbPB)87u(M1UV37GrZre4owO+5GdTGhJ5`OB7g5OMx%Hde~awGC7%o0%U#K=yf05 z0nhnyaTnA116j8T5e(&vm{VNdhb*F7eQ^Puzg|eN4iInZOOZ)uz5@}qF(2`rzL&%G z6)u*9bFsTS<^DDwFp7`f5HEk#(6?tADfI`Vo}IwlPU6RNPK!3I4YgIgA^|O&TFTE6~H0H7(uf9?%VL=L$TB6BTv%N{$d~J?@kMgtv$Q^^M%ge>A->%WPa9XRbX-m$)(SiiDcavCD#oEC6gGQg z1$D~R^Jx@`(VE3=ad$R>j)fHGX@$&JTolC-J8Q_`QBKKg+jshibyG;g%yq~3% zl3>Adm3J$f5K++h4Y=0){&rTO2{$6bc=jW9go;LceJ-!P&QFqZ$Jy1E*VWuC2b*BS zz<$N}NWl#n?yVb|HEQDD^>u|yI@!oK&8sxGsYt#2!tIaYbk#Va#A!PzF(p`P?ViO$ zK<7647?JnMGce5u)*z*JO6_Q{Il>I$+La1KIOkQZL#n1(R zVZi)WyQ~(9a<;PZvF(#uL(0Y?y}x$ShW)XG7=d|s#74*e-=(|{F3o*#X>(QYw`wwN zH7H*dvgXf{UJBcf`H~l2x8_sxiDrNC?X8Fa^jzGB>nHqKHs10^DXD`P2dX>7aw&P? zIc4NC`R32k9tl>->x=I~;1}fvbp}^kUcf5k)-H!U;{elLF+Hugo&SQV`#d4WbrY_9 z%60d4vyQ1jTcz)dCcg0{)Evwt>tZIuzmv0H>mYa<$#$C8-{ww7vEk|w_+~@fBmc+! z)5W|OkQwbX=#Lb=p#o$0T-wJ4%**TmVvU2Tl}w8*aT0S&M4Gng+5Yf?{}e0*>L9OS zMAE!3^3S)MX5?0VrJJQE_{k)lB8WKen4$mDCbrWF$uabT6I{OyBb4+RNMb0K8?Vgk*U$Gd0)X=MyiQ6Uav- z%6@vg0<$&mq_k*RD3@rCJC4E?qAy_p&IVL5YD^+u7K5s_p-Ehe2u^`Jqs{3Z+ZzXY zJXA++@*;BU{CzhC1wV*<+w~L&VCt)G;&F;^DJ)P@Qd;cEm$KF2eH9{C!pqswm7G}t zOqrsjlzl5snOlBR(p~q(Ug9LefE$gzLn9fD$H72^ld--&7n+M)Inr+}U_cIQFc&ai zW0`$_<=2_L+8h$(3za1kFL};|ikUM!(zyBji3Ydj#A_Hv7gIW};=)~dQ;wfsa@A$a zc1{-(&`;>1G74HEv^m6V3*Jj+jA#a)JLt9QZ2AgNB$}VUZJWl5doi}6tS7Oq3xnk9 za3LQmgHPIHw;Ua)yS}9GpqD-(?!VgC=@#M>B8Qtx!=C?k)?_km+)~H54I{spAd4hj znF7W2iFH;R)9fY2lbK>XlcY*d6J2{A=0~jPV$Myo`}qJgT?C6=XvPS3J$a~rHD#B8 z`!oeJMG-Cu!zcr7@Fz`5ws{Q`(;1d}xj!?#lgpYO!DM6YH?N>Bor>WNh47myUS`N! z=~kD7tTwL;Sot8peL2pGE-b+27iz)|#eh?5nGxVmKoa`XB{p_?=0w{Q;512CrsUbb ztCnqH9Kg_N^~6B8jgE~EPWrb3!v>EpF*7PPzU4{1GV8(%P7$d7rq`na>UI5j7j;R+ zgwlVRuWJ5RCz4V6Lk}8+f>~SO{~JDMp+|-ifXtv+R1!w5KwCt{H`YN%^^5cdpHOET zQFYl5g;ExIU~M7T5^z43N5m!u?T&^vL?fM|ym|V&( zoCrx(Yf&I3(H|Nq4X(=-?yB?kC{Z-uQ#qCUh$A}Xav(>#_VeHoR#z*$Q?3 ziGE9IyCxRVmGrsBWPJW>*tQe(ynGB9V1O!|F>{fqFKXohyTA|F#=rQ!pfpq***i_ubH8G+!uH?y9!V-s1M z8x4GY^}P;$C%9LB+IM3RX)q%xfSV03G{Evd1}1EB)sa6-F+5N?ty*20kMIA&Md1-8YdmcoEiQ~QLmt((oZ=>lJ zQ@bSMuC7g9ud61%zgPh}n;BYs`{n|2{Nt}i0ce=euGcoda5d*=S_s8evjUSq4l_&d z7B=M&5l$1!=R_$%hOBP%Dm>WHcf|J*avpRK^zHG*4!)R#A`80=&+5Mrv-?)b8KBzv z1Tfb#>#BtC4!>(&$x&E)?xqlcxTHNE!MYCgBStgNWjV*aBixB4#h7ei+~s?Ru@3^A z`*9A30gt!D22RFVwxDJifeO+7?r;vuj=gTtqa|r^^TTLDR2mtzxL7weJJ%h+#NZXz$26Q_` z0SG<~m`nBr(P41XJbfPk_IEbt|F(pb{+2#=CSKaM{&X4z_>_yY zLo@FBPYX3bqY|p5q}?}f=&wIDq`u=)(|IffILX-2t=8>w4^9Jo^LW&$_emGww#2m; zB8**Y0EhtWmW7EO3jM+LEm$II)d8qV@7*WY6FxG!?=LHTzALzDKx{xcpcTEt5l!LS zuvwdo;jtoOSrZtG{|fcmla9uN)$|xk^rLQfXAN5J}EHd<2N4k@pb2Qt03R9 zHq~}e8kX^Cf``Uyp*0fK^9kfouJu2JE;I9b1!fi%+VYCc`F8myS7F>>-oQCHWZ`Lt zIhQ4Kyu^K$+@U1naCYua)n#wfZba2aADrHCQ<|%VOd>xzC7;cpD?I04Sba?mpY_&` z77LWoHF{km)#+`1chejbhVlyGUwaJV{$ozcRn0hPsHbu4G%+~H8mXo-4 zcsO(s;J%;QSsL8G$#|rD^n}_Ap_MN9NiXlfVPaF35PJI#vWOqirroVqIzbR;X>O5I z@|hbq4oj8f38T8+U;fW(u4h)Tw#k8QhU=P2{l@~wJEU`wbia!D@N3;JtL?+6C(NY zd7kHYUBBxj*Ok9=z2C2M->>_1pL3u4e0fwv-WXV}<^Ckv*OKo#N&ZM`f@r!qZsrghDcN5=Nn5`@l4p8f z(m#h*&5u_JZ|$B}jdPgbp)8yPh)Qo#%sag~z2BSr%#Ndgvf~=NSkNG zvak|hmc}4TiK{bc;aV+u%b7zqn1WwF&7A2;@v5HgN591WC1EPw4-QCJFR!3W@KUub zM7WUUdW;+y+^kmrnF$%(CK?F${L2D}ks$+gumE&LBmiK?c^f$(vYGMhQJiwhje~I# z9UJS_z|2g{T7YlxvdlZ`J^vgMLI8KZYIhc&QE)L`XpriO^t+!P;`-;uAH zgwxGFNQ03AN75;1jj!Rz;g0x%1=0aS14t8i*HOQnh$v|e+&>9~6(1+%&X!^2KsG`R zL-*}W?27gZI~YbL)+`h0P>BWG7tWD3j`G@jTT{Q-Nn`J9ry^=T0x~({%j(TO-0u^# zhHeR{Ni^9-uvm){KMEN?i?5qUVW-0M9FD9 z#|3mSI*WFzYpOVf-Cd-YiUTZ(-B7Rhf+NP*z62~*7_TQj69#|b` zl8yq%mx%E|b>BRxs?ty+5VH@SCh7<&N6M$)Y<)^EX-iAYM1A~4)E}8jPXW>&-Sb^I zWoD9Lo_~a+VJ~+NnCVZAC^vftpb+M04dG@QeXVRA{<-yb(u%E&vNx^t$2?~B_%f1y zL$o_y>ppI^ZZN`tEjeF4&0!<M>zA@50Ls?y!!AyOBy#m?JEZ?Hih$!gPCNYY`uJ;;5`Z>;qxCNe)F9eHfLb&4 zHI^G4%IYtDrcmzW-~iV-dMl6Mfrj?)UpR~#!>!V~)7HZj|1E`*{rDhV?t##(Eo%sB(>c0>o{dUO_Wg=|K79T^lOB_Ai4C? z%`wgd)0U)>npNw>e%^sT{HH{FC<~z23<28gyf)cWG0!G&QZGRbSQY+APT1_ms5SE} z)>;_#%>f#D7L(@QNi5J?T|fv8`c)f#ZS^@4Z3OrON`bpm-@jLQjXQ z0HxCKaK!QRh|L*FT6@6Fu`E)NUd=n#vdfTf2$ZkQ6cKf!?_VeMDOO-G{zx$GnDOYu zc#{6w3-?46@OIc(0j}|yWyNp|oXux7++Scv)>muMm*=KOwY~YUR5VZj-(lXts?&ef z$e=J#8Ym>t^fLbGIdA9IetccbFT?fq>E12D*di)(K+9`tK$;u7$$LS#riG^4wUUi2 z{bU|ym{wPMWLcT_l%Pe{-~8fTFgi2GJc3w+@%k!$&dW8Z?Qnn*sVOk&d$DP|Jd0<#EYpMNYi=#@nER z;m4Flg!81P-Sf&>0cGB11++f?yBmZ&LISYG@0gZd!PB)%qB59cSlr~M2Kt&sCir~4 zagl{HdR6~M!;gd1*nJjec_OUG==#+4woU6_fhruW!fVK&Yy~(yKx5`@4_)iZ+f^k; z4JfTCI8ID=wpJBx*Qk7xs9#uV`!46K!ow<%)_!1889<#43yeyg&!Vvx%@=WKy1`$2 zOG{PI>WjzJm&2vy5hS67XUM#-7|6+NC$*ILNQ|-SnJH6{toM6^6;Hk?t3*~;hk)UY z2$)n{K@tLd6h@N53UdhHDwPBPV8@Y`SQP;Apq!|vWR0*jXt6%qEC+uFr-+hcM@W(b zGZnoZP7G#_JM(sXAcRUbAcU}OBQwgIHtDZKlJ-*@EMwbsHpXr$|18RuTPx__n zXRw+`A)%EHn2rNKokhVxYRxd={3^@%(dZKhm5A&|hD@BxPDj zYOy3~F1pUP{GsDr8$IpM0Q)27!P_i$L44iZ* z;R$5WP5G?r+}mJsTa6pc*X~p*k;sdxLEs$nHNY&=J1nsXY@W7b35X1s#c*vtVa&hw~h>b_i|-cJpLqtT(xoL@y;$G=nOF0JUNML)EK1p*d<){nQ4>cTVuW0 zL7-{rHJML9DzC3P_n+TH=GCf^`P{wE!;T3Gnk=(8tV_XHbN6a-ejF;JQD!$aj( zEDa<%?0;U@58SQf`Gf*Z>zo3fHNq3I%ko(b1>N2h)E8)ar?GcscbBEZ_3JxISKzA& z!A$oMW<5ApN<$jJ!d^#U+9$`wm^+h;Vc;iM37xFSG$G zdeJ@=;^0Xi9UmU{v$|Cy2Apb9+220&O+o5>3I&x_dVjFGl`zMB2Xl{A=*N}M-XULC za+z!1pd@Mp(6r&dU0@{J*xpk727I~?_bQ47M9n2rXTZwv9!rVRn6_QAfM`sTo)%h& z{FeaHlXcJ)l>OHL{NGwbgpDq*z5Z3T2(VaqYiMthX#$v{`t}xU8>!6q|H13~50A8# zoRoD>FuYGy3HYG5uCw9(x5&=olB-AIBe!3u@e~C-YPTyy%{BSj8JEBE1_9#4!+;|% zj1}S{K&;g=F(2iQ3X%M`@2qLm00-m0E#?Sn8y#6Zen?Y-Dfy{Vc6TcZsCKJPg3QAB zdz!YWH=zM3Bhwt1u9tK$y_j^ZoJp2^UJzNdX@S?3f1$)MZ-~vJ>sl-kYyD>NvmAtP zt!03-10ZLAV&4`zYqb5lN|{L$%8>@vmytuQ0o<0-LyLIIEOy2ZAvy>X>b9MTyyuh@ zkAuz&jv!V0@Td1Tp6Z|QyX7YkUfV0`U;5?(_vgVcvBEFX?6?^sB^NkV{};Jd)Qt*Y z^pddYI*rudVMJt^;L@OAA)8VEP)n^88C=5AZ@%v~J@ywcN>g{xpXE}n?gvr; z4%kf=Yut(rEyoi#Vqr&-K-gX<$Jr1ZgMiJ)lKn=PEFpuCLGJQd?8;7P;<_u$GnBYX z7Ug&G8t+y332d2ervN&4^6CFSILPIgRm`GCVF7bms&s z?@6sQZQU&l)-^-R=!9n9-CKDrUQ6jI2Y3uLx)B7XTO3i_A+xH$WWqOvE{P^=3DD%V zUaq$~XJzL+HdFZ1U|Qr7$22CG573H~)8aB~Yf#9NFkO+xg%W0Qu2GP0pis};X!;4} z!Zu)^aE0}1Rd1Bw;dGXGzhPqjUkwqN`WvK$gDUoMYG zC{YnW7-B=k$mf254^-TC$gs_4|nYuUIVThNaWeBiC!MXEyf2DevCNf9Z#}sO1CCYB-qWU;C0;k;-=%gPye%-?uU-n>?*e1$|qfGd@$O+ZDc; z$dn&NQ9^_-j70{j&Ajtr>0Q?PZ$?u}+*y{@VU?lQEx8RbpT@vXmMH6zE^(!lzy5x1 zI!)fB!T})P8^0w{rY6I_IA&z{=KZthzM{>6!ctTgvMg`g9YY60nN_t-U3kftV>^mg z<(Xgoxo*pQ5GmHOr%2d3Ku^WLy&?bR6tp+hHn|Wmda-aeQKuYb%~>G$`^BZMY5EOJ zc%MKlZ^4f+T}0N7#CE{?vV_kHj>7HEIMTjE6GSJqTKy~?*w`<%3vR8WV)MX-Z0fLV}%E{m7Ruit>S+i!fX ze^c@u>ZE?Q#Uk$n8L3nU$c+afKbON2LBjHjbth)5(*a_p~Z@#mCPJU~JDB3$aVa+(;^y_Xi4jz|_7t~!Mcy7UKS0Uk>< z?=-sg-qt5#DP>&h?25AQyQF%uHU* zY|rd`@J4q80psIK=DcDsBdC6Eg`rjg%!*r)0q5q+6A5E0j|Pm7$=w44mnt{<6Jesw z?!V9j;rkP0UMNXf(%)WAtV0hVhivq=(8T+CfmXLZL}!`%ohNt~@6vMBHTdQ3Tg{-e zSpgoJy5!A=Lw`n)FDI6MrrDaw4^wX*2I+mgTVciDz%-VZ=TuDf)}C&Jm6`006fsrG&YVl}9W!~A0lY>T)3expEm*!|`~_{o5YVT2@$dDIBDqJPhCH$6 zpNPajYJl41C-dLMNWbYu33&LM?urQb`bC$=r}xo6`*6+O*Ab-Xt}3sH5WST&h9uqd zCz**z@$6lo*y3wBuViHCd_?FF8MbzJZyA`jNtAdp3zTT!gmmoD@AV z2)0&RK|9s5GU-TC)d``|5OJ!D0lSB}xYOGtjICS)56b_1y{eRw@Gl6HD`*W7S02L) zGee#VU=*bdL2Zh&$ONBxQ&z&TLy}-qM6L9tO27wXjb&XI`K#%2O-6I$>CZag%60MI z?90X)uf+Wy^F9~O&=1HI@A~wDz&}U<2QDUTQ&_z@`i{G9b9WJ^C&ef(@6pbws|4CK zzV^6us1#+sZR$aFm$qvmk@z;$k__(XW7cYPB0*{{(+PLomuq!%RvC2w)_n!35={G~ zPxN|+EuFGcd+c|(xj&b<6w0ebJJOoyZ-=jt5FAKT27M*~SxCCj*QUZ2au<^3`%=bE zX)#s8DiSf39Szw3d0ANXB8_7Hm`7abe3)P#dUCoW{^a9ZqcZiL=W}~ueNcxxUt+O& z$_5xNe9?Zy{3BiE(IL2)6Poemtbi2~a!Pv1?>H*vSx&ch&Lumio;Lat&<^eQ$M8q9 zrTc;MaHpK#=aj@r%k#|(W6MApNKDTNJUM3ZBO8COvQ! zRrFZ0Fw{^u^hEcaz5Nzj8u@9R9uW#%6U*tt%eD!OvAxZn{H}FX36!GOu-A>tqo(6+ zHCTPFrCQkbvziRfIPf%7eJ(FcG0^o%kz|n{5(6iDcAqAvEjVL!f~#g`R~M8oO>&_y zoT;Mup1PQUj%y>+-YBb~E7QxP5~#)i4eu@tKj|czgaxlfRfLDyhG!d@{FDN`YWJ^w zmb=bTZ3FC?VzP?M0J2bEg8?NKV_ZEH&4jcMW3<3__P5UWK2VX5sq*^1oH~`&@^_vV z#2kOoG&%`HY!H zs`gb_b3;Mg6!#nz(PuTQ2!b=x^8GOIoX5!_WB9a-ket_qW#$c!?sXU_&Y7+|CP?~y zZEUF;ECDPKE-KF*@EeJc8q?YwbvCW=-!;E| zy|0Ng8| zfIn|fvubbOXcgX!kn5Fd(QZeo*6odo2G#W3IKZzRv;9=*_`T^914{cBs`7eWEQ#xA zDJ}g|?{s-2gAzG!4`aa1gC@weX*M&elcfP3`Vf83crYV@7Z4m`z6Qc?qNhQ$rN<4s!j?o3J zaii!|LSRw8GiF8B-}tSb{dTxVP*re8yJ!%d)0^|P!Sw3Zi{tXLPhQSF5*8KjR zd~m}?Z#1MVlIy^;udd}rI3)ZaAs;;P7E?0Pp3*!bYM+-#OjRgNo8f~O|jt71QlnIO^wCB>D-mHyTO~O&Dcf#J<*)e zIWxQ0np%ETz)Q!PLi~}A*+u?ebXl>9yVXy6{;mg5!IXrL95p#vRWoqJJIVoyk%mf|>(3Za1U#Mc2Df{ay0r;y_Cz7$}Q&?5-1N`?Dt-Ut$(4paU zt-Ojb*-L~=^oQ0C9dmD`KA^)ZMXki(cc0sAsR7T(-1DFDd5oyrAU|`cu60-{&W~*5 zy3oqAIeCuIxFkpo`L$XtgxWctO8Io3rwE?C+_yvD;*FNho1}X~%P*ivm%yb*I&)@0 zUzzxb@yiUEp5pEe*El8zIyvE3bMVPu#$Q)XD-mH{*N4C*?R& z=&`mL>#?%?() zlk~8+zBR7GPaTp&U3es~D%4)LJf{&mdYXyW5u@4Iytj5*7S3-2_?sU=$#w%u!`Sru zWr3=c zR(P&a_n_>Q@s>GbIYtdSEDYJT+Ul_0>e#q_=C}G? zy{n&dP2*@O-6$%OmD`&%LHT=6^43K|t{ADZpFiF(j?f_-eQ=7I}CVBae` zviSIb%zTC1gA2J*^A4yXIy_ zzxwA8-khSb19X9~9HIWhSF~LqwPn-$rH*U6axGZAr)#G}&k?Wuhe#C!c|4I$%miUs6kizd%dD^YarYo zZF{P=Xlg(hC|Aw<>LeUazHJr35lF6)%5qr$kcnqng*t44wzI)PRqLj8ZI#+#z&!Bf za`DVNT2A1IX0h}$1IA)tmff$uk$Ck#S?=iHI)6M@cKhZ-%L{XqE~Ve_mF@A}7zBX( z*@bfj&M}ttizT^Yzp!6B4{MiW^%fs`CcPBS6p)o?%rvKEO$A8MzkWoTzji3r;)Z^{ zlrHB||2j8OdwvN+NmX(E{Glz)1LpmAG>neCgBggu$nW(8CWv&@hCZQTT|LPyxX~Z} zS+E8h@^E+r7aH4EF!%e0xoY4PopCBsS@`duQ$+UQ;F_TN^ zeN|;&<+oq}@=3vI%~!giuNj;9wyd7RMjsl&b6md%7>4&9R-c^qp7FKBv=_P(FJKp9 za?hZ>lXgx~=OuwE*LX6g^VgqTi%%x+_~Dv*>bGV&MRe9p5%FSU|F85D==kB+*Laui z+kL^YD!&ArVG8^g+X}Tk#lMms=qM%r>oEegohh?z(ReooJJ`%#RH3XCA!C8*j zwrO&PbANNV4joiip-$EnYM7u2fkqdeih`_5NpBPUhSnH-2Wtat0D!6wCE`keBNy#6 zp`Gnzxb5SLgpqe0x|Ax3F<0x0&EVnYs9MyJY*c(y1N8UM&aid(O(hbRQ}IvLR6{i0 z0^_`jw8>}o!qKI5Eay%izkK$|F0t(TOn%E zFxrFONw*ZmZ9FyJ^f<}46WUE4s>@zy6GE7XdaU*T^@)G#;M{zE$U$63^j@^n!PZxf z(_LE>nyi(8(Bpo~Gk(ePQ~a~HLmp#^MAx;hN_-wArnuGIPp~e%Brn!PKKuX?PpH_D zE~8DfO!2)=Z~zEo-!dA$AuDdWVAQP7;d-0==$Dr$%Y!1j7eV(f4w;~(SUKSf<^GMD zWueeawocQ1tA9MqaqdZo7qp`94-=u080tL5Sx}7(8C+IQOu_X3iuA+EISD&41IFwa9&lD*XXpI z^HR8+h|$s}O+8VnnFrV0^z%&-e^t>MOOW%UQPm*7i=UHkXZ;8{rC1wM`KbVA3%fH< zVL}2JjWCB|Ucw`@59GYM>&ZoS;mbxT}l% z1pC*KqWTMBs3Oo>)czD#lx{)!Vp(C!$bV#I%~IF9DPN z5HgfwCi&G{k-yh>8fZ35ru5AA?Z$~w8C*wF7qaecfHqQ+ zH;~g*sNma|8yeY@TCL?<>0FDofe{0Bg!KE1WM-^`Y85f@=2V*&a?~bm{Du)fDm=3O zZ``1@7rhm0`6CBX;qNMcliYSz`47w1?M^+COEs*eg!pxA0TP(Scf0*K=%IvJ;5c&m zA_fdRXx+(ee?5s1)+@Pk1)6Z%FYz7K5$=(hg&m_|3rsl$>N`*E*r3ZEBM1hVP@}=p zR6?u3G5!@4&18IJ>3`vEj7QWaf`;~OsNvmQMrU6n5&{y4tuTD0@ENSyENW^R*2O3C z%)Vv>HQ4>^&e=1Iy9-<~<{o8deHIHy{xEfPNk9V31uBH_wtiMPFP8Y~kFhlx05-YX zn(tv}{~>%xCxC<7lB3+|*+f0>qrU9u&r$J+z_X&*bOFNFdZ!zTg>Z-<&hC=7J!#uf z=zbA{yys!sZ&NeGq;zPL@?^Ve#PxT~nLq}^ta7;r6 z;|IQbPRE!IZa)5R+pTYy*>Kwz-?r=ekX4_Nd|p~**FtlV4f1;b?Im4}>tL&IBYzo3 z8Dgvllq7N-nKMQlnlTD1O_n*1E2RtNA4yab)c?dXL6?h5z~ZzueeV-vwr@n2$^wQf zkrd+IXb7VmhgTY{qH#k3vd++}u!CX3Y@Hm^8egc5b7*68ZI)1aLoj|7I8Fn=rgBzn z>NI9wEy124jfGV4izvcIOJuU>`R~v}LO>NoC%}wpyw;K8ihEi%n!U=!Z(d`}zuv&*T#b4Xem|B<1SBDp2_=t$3Z@Y9gk zPI*j9C9%eW42yzX6tPVbS0Dr%pV6pm`&^Ab(62K^$kGLV3LnX?S1Se~n4m*Pj$H^dyh7`G`pr}@_4-{*@aijcU>oIk(-*Xc|BbB` zcsFPCQ)TLl^P6(Owamyh!~i+V`BY#}Ga#3!!WOw5Dr>m0tee&c5@0@KGG5a51Z>3J zCHq=6syvm{p)&#CL__ro6@9KHYFio;R)sV~n}v+8N?6sHk;K$B&%g!TkZ-^+Y@>@g zu#5QJ`H)|%mPof`FDHOUOiw+mJt6L2oLcM?M_hRLRHdoGDR)Up3s!aDK8Q&s;@j7f zhW;Em=I4}CqzUDjXwltaT#!Z8 z(0-w-+OU&kLb|RfzF`lljuUTj7VHt9)k^NSr2%Z1h+8~69GZ!MTN*WPB-x(}Oh!N= zRFVRX@Cul6DMA2qir)y$Qr_Qx9uPjZfS6BA#F@7givzHKQ&2t%9)-S!tX{WH__glb zX9!!)T+H^o~d9Py5eH%_S~mW}`tC z=xnxH5B=yDWt9g^nDBvx9yYcTnVlyRn>md4`N;^FOxQdG7yh-iSHjY7ZOTsNq6ZNm z=|4PRaVvj8zNHM0GEFv%@mN7H-~FiJLFa(CSPit0EEqs|_rH97woDD+l`Jw$8{@n- z{zOJMQR`H8G~eu^VV2)~ABL(R*?vL zve3qZ)=w8QElXhl-0-~>oen4P z_ze$^QDs})a`Sazm`eZnolr}+9|4IlQI=#!JyrGn_j&}GmJE=^zw(Rijjos%^OXnD zj`mL!18m2dkrbxJ*I)mD?dLNGqKW3!fQkqLzY{Z{Rb;69?EUT6>S{UE#);*wvR4(c z)!&s3x)+RhnD_I#lY4@eYQ7vGTVxoE44A#TdbKb#?;W*FUGA@MRqog{(4nxN)ne9s zBjF)5~Zt#m8PtWG%^8aA{>q0A@X)mIu zDbp>`%s3qYKN=v{p2EVAJ{Rm&3Tn(0FWLC(iH>gqH|!S{yZrh58bwJobNz)DCb&**6Mmd<)Oiw>*$$#w zkzv8Gjpav{`^52b*fts5%zVdAn4oMU$$944|JfdyNQVuN^G{UQ#T&@m;Sx01z>zWX zmVab#aU{S*>%TWg9?Tf2iU_$DW4&?Tgce5jq6Wh&4~+w(?vt;4ris&j;6!o5qMuV5 zQM->wzaZ}S5Z5CC1N24;G0PU!{eC!Jm82fO@&Oxl__BY;>~R7c47H51L_N3D#s{LI zLm{-8-_YxEfxCBE7Ybj+H~`k~QKC6hd=DOf{qY{HTW67AHK);z>3+osbvecaKt8r0 zaBO=Xt#Q?-J9FuCbqn4G*wiPp6;i~y-P!&6_ll;(YchfW?P1NGYcEKj&li{qCFXQo ziT$f#4jwiayaVYizvsfmE}Qu^v!PuxovPvh^$Vg%sM}NVhd&4k%53_8)oeF88A94c z?Xu1!n%J~-Fd5NGgVlUwj9)(85hmyF4{`vo)Nk5KXpOYfJ@ZeQGwlBo>_X<}^6yO~ zF&2uq3C9d&90qZGY%_^T$3w7-A8#{XToCG7kE@AK`59ytp0vk`qS?7QjIqwax%a6X z!yN2{5PlMdEl3?VC&gv_qe&CIEl};CXVv7@&AdNzm^wK-XzV338m_ zLaU`T5+e8O-^i%Uh2cg_zwrkJ>G>pWyMf8)o#TKCK@26-b$){_nstDZ&sQ?}HxE#D1Z?!wyNaI(O;YBIjRIH#F@FfTqOBWGx=5O_`bziF6^Z}<0XMuPae+Hx zZ40;R#4Xt%bTck$LpaYhL<10Vi=t70D8L>y?wgkJfo>p9?2)Z?X8#@WOh*x8#pr!X zV-tm8H??i};!6)p1^M?^4HSZf9;UJxWSglO71|s7rO~!ds*MQNDS!TbZhllAi3s{O zez6PP^;fO;oUoiB_9QWh(wuFL>Rg7wRguR8}Fz)()dlios03}a`G@j zGY9c}1429u(BRY{ZQ8qyCep#MA33I0*5_5E0DmLTui0nZSZNsP`44ytMNDun(z<-Rl@o(`&#ca=2DEY6E{dwN~s@pYsj@!hh z@qcXH-jUAn=SO5;pQjau8c;Phs5Jeh*o{Ilpu&+1ZjK(bh>_Tjmt)4deL?9!&kQVa zu%%FDd^Oa<<(OToCgou>ao)cr7!I?siw~d0-*QH3ESVo%hyDa*nGfL;$lZE9!f>Jo z$=b2&cZ#72mZ6CtLHQeX<&5$MivrPQ66^bfg>J8CkTa#O@hd3;M|DHH(gLvsMK$kt zIQWY_Xi%?B6ut6yzJ~+0wQ>^90hMohgV@?uftCO&xzzSmtdl*C`?zHYW7y@Y=eJv@ zL2o(`1~}AUVf`zIdNy*OA;56-y_qjsG%t0d%|VMk36eU36^n-}U=E1rA+Ty2f&B_b z-o%jOj`hFQyyX1;z(ErDEh`>RRZQ_yxKDi&Gv*v2V4h zohIf*6ZxG*+sfLiWyKanIYkL(b{bZGc664T)?2Us{k;=%KfZfSAv>D?PUi|nrn&kG z?2MWAArEIIsumeCfN9DHK{cMS{Tp8Ai<8t84cJSXS6{a4hnV4XvBaKMd?ejo<6xaM zn&V6006joSqn8fmY8Ow5@y*)9d}aHS)&xzYbNh61V~zRjg?1Nut>nOz)aFA%*$D5I znYhJ)BBux26FR#M8)*;H3G<0>K#|ly@&(G*PCH(|*GM%6u_!GFCGqP1JYVwHq*g5z zLosRz#7IQIvhN9g8A2R2CQPc9b((yZ_kHYxERn>&5{y%Ezb5ryDRu3x#IJ=WmzFoPq zq5DdHB{mo~Yebm3@SRXS{pSfv=dikAqOs4FI>4MWJZ5z7Tx=qcte;t2q3x=Ph49{ zW7{*Y!w&}Uk*6>ASnY3?l%JV)oH{?KJTdBck?NoU&+nh3?ZV9BbZsx`T{6FYcqQ0^ zF2qwQ(HKXq=Ysx8tsPocPo2LfR!|aF*cD_#c$(j0sMwR&e}0%{ncOk@Vk+olw6+R$ z8)k%`x{lp+EN=k9y8vq-7SM$Ew_V~Q3U-o6Tp;fFLaobpr_GqZ7!lsPe&MpR_(nB? zDeL)Yh#_n|Z-2d~A`S1FQltn`rQqwf%!`c?t&EIO z2Tq5rE^-%#P4B|anr5eXYJF1890sl-L!n(SE-AkQnD_YL))U>QNYuuEuWY zH*#%0dhZ<+xjxpE|3|n{2%H2eS+zBBFv1uq{^A|RNU;vOM&%19f}i@2MG?ZC@d1BY zb~@h^t~%i?9Wsj!FhP;sEKOy(<@fh7!L18~HMwfG+HE2aTk_`>xx%`C=282`5BIbU zj2`8OH2jnL&t+a|vqR9S{bnnOetq-5tam@423MunrL;!687`Bw{{`EU>nuA|a+!v{#^cuh7Yjh|&b7@1N$kVU zT6RhLB~#ZzA5QuGlN*QX+YfY6j!N|Zr}YIsg3_9PU&xGgxvOT*)OV>Uyxiz%LkU5U zD#90y;|se3dY6e+3#4jhXNWaYF*P> z@XLW@cm)2auw^i|6#A`*dA{X{=2(@1iBBAYD};G;5qZt}>fgo3`Yr@c8gsf#ude7N z_dR9~risN$CE%%35z`v>ENsQ!B+g&Bbj3ajKZv#7I4aGNgO<6y3%F&$B#KcJiS0K? zKCe0QNKiwZqGX{Z2p0pd@7DHRYC~GY&_hgMvt2&Iop{X*$DfIb$`-iDv!ksjIy57Q zz4Ag6>soO?*#IuH^$PyB8b^M$wKmP;PotI|TZ$$(y)szQi56RPbiunr6*{h3=pb}H zoJ_xrk=*Kh=M4AoYp|!gCO^nrz$cb|>TyjxQ2b^GEi;VkLESstIFO>)KpuaSe^sa~9Y$g^d5gKQXbA~iQ8V*@_vQP> zzScGf>FB@y0>;Y%__5#>Z7pnDZR|`c*8jDpCvEZmgL1lp=OAPv>INFlo4)!n2Y(=3 zjztm=QIK8X^=G#VcACw_eyuXxY@uxng(r(yo+yzJPA-9!{f!dAw^5V)+MG)(Jyg-f z`Y-)!!#>|^08Qs>j%mX^8v2U@UVlFjhKeXAz^HA`j+&(vA5C)Ce+Ng7=#&OTBK2P4 z8;^BaFG|)^wQyQlqvw|C<=;7InZS>oTb2)xq)=E4?p4M9H#0n$VUjbYex*l+a!ES`VU22L9~HdH&RvpLmwg z1pRNi-x|B%kz#hL4q~gJkiy`^YVh(ni|)!-;A+@(2lMhg}kHMC*RzEH4SUfcV z%pC5bU#dUhHHMq_QGWHtC9^j!yoqC(xq)Qye_@K?_D0$_La=`M<0Hhl=I2E!U5#4VpXoY zoo-P%;6Jl6 zaG}X{fqZi=_Gceb0PYj?^R2Iy?#NTNp&vRKqEoBIsQHf(2t#rRS%CwX;D*12+BLj64}yyoX{OOEwI9m*gqdd5Vr`e z7ih8SCfUq`QTL{wZ<6*6#8NG_Vsi95vN!)bft0_xp;%v=5B`_X;QP{%VdRd9;=BQJ zqK9%qqbBm=un()FTwv{_hRk ztKz>2|gZZlVmGZVvp>7D7J3u9rtgACyEe7Rcb)X>(}m+^}=nn!rc9IgMhYT zvJLTD6lJTiegaRL+URDv`8DYc{|NB9&%Ah669VRUrxS&tQ#;;L6cub%xh)IGb(_1o zqDR74e@P40ZQ_2%smDGqBCR>58bF{dk}aRj`?cTlZ3Q_x(Yu-+m0)e{W(ZQd-fR`u z9{Qm2t0-yt`*J9g6>r@M@83@g=i(xCKRyyQ)5YCnIBGTBh!8*%8Nh09im^x((T?U8rXj8oF-+td^od^=KHZIlm z{l7<_VqSa6bZx2wYow<_g)RU7sVM3Y+{hwWkF3x45H+^_f|>trQd_5RhC+CFsR z&-p_GnBe?A^Zp{~_gb6){xFQuXE@yp%gZDwkc=#>3PX!Ut=g`NyZ0)*fAZM=^+VQA z@?xJ?EX|0feT&LG43a}q^D@0L03+2q9>#F*4!Vtm?b9c|Sdi+re}j-n@+;jsf6;XEt;xZ!+t|^h&rXAt46fir z4RFcpF|lgpn(YktS6bXzSYSKF%fig|F+Q-NQ_H$)LR5V0vim(SxiNjQZ;(f}r8|$> z_NQkj8;kVF3P)4!M`@nlw}Na19X{x8lOok&*}T`8?@1SmCX7v8lEA-#?fS9YCRyFR zryt1SWD%-Ni_zhehgbud$v{-g&9z*i)`g z`+B?S3zVh09`)LR@hA0xw{H73Ih$KPm{Y`w+?RqSur7ezSNk$_T=0xN81`stocUcL z8|2~*wCQ6p=Zd{bV$dH{xxHL5Fl&igF2?!)@$?pKQU1}^@DPGD3P_hIA>GY@sHl{P zl*E9v#LyuyL!-0;0@5NS-ObPq11KFscMmZP6R-bsp7Y)xVP9*nz1DsG*4};x#uh8b z8H1*Vq%AvDJ^i|TDpp13WWVXseM-I?-6eSl`Qad>+m6+hp8O#FB0m1RX4>4H&fX{< zLTBivZ$eIlKQ-qob;Yz3+Eoz(_1<uD<+eCNS3QTE+g-n=cy_Ukom{JTg;kN5K$>6Rv(JLMFbV#^I^DYs>Rt#5HbtEM!>a9 z#M-*VeDt7t2Xu{CK~CzPP%w%mnKt5HK}Ta{5%`ts%%33KdFnsI@Z@}E%-$g|=3G3EDOa?F?Uv-4)Agfx8lfF_{>ft9_q)e^WevuQNRQ`eK7Q{^CJ1 zgCg-?2yuIG7owq|H23qg>3_eP?vl1~MjM_Nd?sR4E;<2z#41I%gJv4L0l{zKk84>Z z$pt!+)=E(_9ROxeIXGNI#t_|n9a)PVC3CGhA2X&`>@W0YN2_6iPFR9!p z#N<@9s7#tsn|)x9@BHzw_E-ByYHq@IDnT)Wxfu)ck9_8HACd2U&8Oni?cNaq6ai(Y zvNv7jB3W@$#^-9N$gD3eU`^KVlRs0&TIeimKMt8!x}fw7G&F%e=lk1R8C;3~+< z#X#pqR4p1RAh!r_LU#5GphK$?j?GDWWOIN&5NUr{x1JCmoabEpJ)?nQHkd&q>lGxF z+%1%DdXR(d+Pews`WtjBOxxppvXJ2oiG{)YxXicG&-}N&=^J2oj_$ON#;5SQ6e2MJ zpJM9z!R)#n@+<}H|Vq_)kp0>VUGI|I z)tjB19Kp-MWcr|eVmuYE-8O8-tfSXHO^iv*il|0Em?6tl=F0}wd+V)7Zg<&j$I1cG zbVWA@_#f1UX^|R^uk&;-euc&ivu+?}&Ih)Hl$Yu|UW*PWTnHH=66#gh)(@y zad`l*fsRb34kMkLTPEx(vk!c^CZq3`a`v-w>aYJ&5**~#99(jjq_=vs&h%(NEs@6W z%UL)`u#)`AfXKIX>@)q-nqR-|U5c^6cFzOUL^M;hcWZ8VO#!2eQ?nN~`opst(txyr z8NZ9&NGdJavNyIYo@Mv-R21aHYwj(%v{2;g&(l4g3g`3fAwydIJS_#`7kmkq8mEcI ztN%N8@6D(BQ^A}xF5WUT0PP4p+Oy9-i|6oPwZ}#b+%nz~8nuaF$jLaoYq_IZD;ZpQ zU+mtaYzorm)h}W>UJuzdfNUUjGk%(bi42KyLCg#^T>ZtdPn8IET_0oN=+%EwKrZ?O zYJ_R+E;7LEjtm%}V*JOKx)v^~tpfzF2NF&|c|a?2#{SWyF>)1hCUT|H8u4c#17cJs zryJDL6A1u^<9kX*3SA#iQ%SUFHwb9K$o>r)c(a*wlMbOo^J8Nfe&vl-k0F$hHzx4I zMUX7UGh!4|nw;kI=%CMaD&8-um1~aSyfz0r>Z?*ibbnsK8P+Nz1Nq>~Q$-XkF6ouA z$=JbptG^L*CfMe3Q*73}_9hVNF`;6R%WyQXP$xL|T7~{Esy$lN&Y{kAP~m6yaxqD8 zr0j**Z9w|dG#SPh`K0(ZPtY@vBmk}mArp6_Ff)c=7WIC&Pk)F7$dM?s$dmJw3S%4& zOIg}rX83RJs&s~3&p)THicebC{HyL!g(`yE5>A{xM+<)xWx&(j#(2Uu5&jjENaT%@ zP)g(|Y9(!>dh`+T5fqxOcUq+4V7g%brf$ESjVXm_Wq|b#)2SnL1(5*~yxhm%*-H(i zbe_7kb&Sh7gJzGTA7eC#h*X=NiblZ30ID2px;)Aov{tJk@|UoSWz;mH_M0x7{f#(a zR|}X}IqtTNu#g-4-Ovq1ASYhSo;G2W69c!wqcmXV|J`12# zqf%cRMj4BM?y!U6tb*yaM+7Q$qc#Ca)(~f_-YiAm@jSunF|LrZ*C9qqNQR4 z-s&E&fB8>A((wkRMgh*$nnL_(vzloLT_07SEZ?k+v&ul8u(Yk2VKQl(I*^U0Hp{e0+=mY`UPLbUUp1@Yb=Toe^gRFc^D z|N0Ue2XWSaQM{}W^QCC|2~OM~S;{_wyFA^m@`o((7D(_W?sTvdhC3p!dboCj*dQ@{ z&`cnyjFp=}J10*~)j|f0787D`x&V4pw!iF7-PC3pKrnu5(*O2W`2E*w-2sQ^@zRDiaP)IH|EF6aTh)1zf;VG_WUq=g^HCTRI7 z^+eHncM4=(?ss1_ZrZK@-_oTx77biPAN>$M5#8~q5sM0^Db_^qH7K~mXr^7Z6h8`x zb;8uGS1#hd(48>Fq~T9D=GY>^%BHf2A1dsmZ9qvcE1LW0KU-2_g$+J`^RN!%#PxYl z%Y8<>*@<3ac%kR$)UdZT3t)~nq}A?l&1jPvkPqL!&nhEZifN(C%{h&^Zf1R(Mw67N zdw2lQT2+x5+=rWjJN6zRXIK1s_`J(bce5am%fJa;1Ofo(u-;ePG~5yz>_iNlPxgE1 zwwX)yRe5z)%e4eXANgdD^BNtpRdv|<(1g*QNS`3UT&BidL2#Q%zKj?7f|!X7`V;Jy zE2Ekrt3PA?*Qvdg;;PGn6*g51l;alLorng7DE=k>8EqvE|84k@GQHo*j(mUcO~C6} z=a2^wdaw4%HUVM?8{Cb2z>)q$5cA9F)yds`kRa(U(T#ci2zpl%Sf3Jvo*JQgl)aN@ z72FXfhkkwrWALwddCuU9WW7gj8DDKeIO#MWP>!5t97pTAR8BSC6UFYTCqUMtc;;PmDmH6@CDk>!N($Ge`aTqdF= z5{+_6z`AqkS6!{Sd!4_C1_99$MdhAeA}wvMtiv!(+lfw9^L66ZbAY_6g$+QJfQ$-2 z-b&E9sSyoSBsDt&PjR}41A;Z^5bfEYMQkcl$llwnRQ4jOx0@SBNS?Ikq94|q^eH|{ zD1=QR{XeZqlZ@qf+Zt-iDv&1nqFwgKZ^8S_z(o7s0SPYamu+8&ikI*1PqIOibeTBU zX9HXkdcK3+8xHPLoX#!vyGN zzy-|kp>>p!v4eW{$BDB`@ohhS#NltfMtS~=`_l8ICr_Ad=R045-6Eux~FHKi;Hoj1ln6Gz?9s?ipL7#kkyH+X zI$2BtP_(x3T-5-XJkPE}^fc+eHefzjy+$`mO} zl5dLTKdQK8?&k`d0h^bes^HH2%&P&-um+H0w8|uN{`0I3s042qH zq1Vb;%Jw5gQ~#tKb^=fh)(CC?Gf<)|+D#xlU9bgE8hAstS))Z$d$~*>;olZ<*(TlL zUr~PaYd)&6D~ek<)<~gU7Ss%;_lS5t5cu4ju7@O)l+?BB=GpwZ(u3li>|sz^(E#1X zS=6PdJn3!*%DsW@4qi~vG`{+x7Ot{L0X>Vjw!P4OeSl5A1kP}e-Re!-dl0sANhz3B z{N>{|MX@p;gnsxVb?`moVpPqdx{_&+nx~%^GpKYV>T`;Xo~9XKpTF4!O;w+96__@% zWo)-=-=OZ!4KhJeyR?~WZBMEtzsYNU8An$R*gb3YMQ)D;$l!;izY>7KOZhB-GW8Fn zoyuX?WouU=qYtg_D&E03>4=oTpBK0GMG@V>b)Z_deki9XJW^{4hw(Mm{At%A`Kt`% z6_w*k`b)>B+w`rV_Dd$g5|b3<%$a>0SzThbjQjcdxjgzZ6%;4oSLbdsoO^Gw5l&E zzGKEMB==fGuVT+SjYrqd@t6Yb0 zQ~Km8%4rZUshHII((23GdRz!6$+LAYMOeS4A$o7*a}oHHswrr4g;cb8sPqr<|Ey89 z>#SOkCRe#O%&gXQ*F(b>gz=-=b?Sw2kM_T4x-^4BZsKA)TpXRuAk?TPtNHM^jA*2O zqC9E6V9k6kySRN-b*L4h%{Cq#GSntSWxL$>7(~ zhcTY*xK6p$Sq7$zHrvn!- zCP%Tapg=+;(7``(N~vM~l3=#;XYIxs^~0$>Mh@+>>kI}Tj0ege81JMBgqHe) zPVPp^oi+B+X_$w@HyO>sHQ?687YsUKM0NN#UHISfBY1gxly*91e?+uhcOr}T<7`4% zJCCAg9|Gf|b3S<`6u?$fu9Yk5;1oT6XI4Nqh)9uk{utkobxE4txQI*lbr=|X7&vO* zRJ1({!lgzoE5ar$o3n&^uEuCH(YnQzuRuGr&#cx)qFZ&OLncL_raebw6^I@ z)0yw_D($-|b%PuoJ))@G@D7@Bq09x!5(R!W)MEhzrS&}crnf-PI1R9tT^EuxEkpw1 zRWV*W`BBr)HTsL~&X`e)P%Py>U!O?kYN|Q<^)eIN8%|!PKUIw=23X}N(=rSdsqXz7|m8H45JilhUOi zbBv+Lp*ftr$4Vc#Ak&EU%oDc9&Y#1To_WSR|D~5uhO!2%E&+2|41z>I74`3IbOtKj zt>JQYGBdqhs4^GNiDy)Py_OWWlTdE1ylyWgp0&ouh-Q#wVf$CQoome zIR^m^x(^Dqf2m^|vL<1KiNvjw#=eBVN(wSBIo{4mb2hw`9BOv3-}T$sNKo+@u%lsI1O;Q@aVLhrtTY+w&})fY zX+1#?<7a&si6VnLNP@kGtI2ZKeItj#p99+>q{zi_+{t+EVd+XqCnKaqu*^)dH?A!| z3i4%S!}+YU%`=uAF`h$dU2>Upeb_;_Oi)Gr^_MwcP$gm9M?kO#t7RGIx$V2M$C$V5 zId_7y z1}UW^>BLv=4v#LjPzI>sP5nBYkjOK#B3{8ZiiCs%f|HK4Yt%4elQw6ec8I4;rocvL z2st}}SnCAQLSurv8GoZvdKvR+yaxsYY(8$NFLt7e9WL!Di8?d6eKgne5qR9SYz`E6 z!`ffA*x%M#IL9VS^24{1EY^JJ-G@I+*FJ<7?KA*KX|)PP0?TJm+2qWNp{Sur_C*Wd z)N&u6N$MuEt|C;!zHZt`&6{K01#<#tRuy~Go8ThWLCX^>*4+@kAHtCLAxH3`=)C`z zXR=(B@66n{tF3{Lo$!Y(bXTU}L=PJvhB)ThmkDZs;vB@Km6f40S}v|M$n63$J1lP0 zGzup204JN@D|-TiQ#rT6EPW}&N&S$Eh^rJlDi*IW4D|N>FC#XCzdkB`;f$wY6l$5` zC^R*Q7jq%Uzqz6MnjrXW$~RVC{Uw5S>&L7lHj+?zfibWeVZmn;E$uqtQA|mZJJ~%0 z#k2*pn&c68bL3`_?5sKGq<})qF>U0M+LTh$S_~ z`zNSnOTw7|RdaN9&pQz=W%yODSUMd{ln_VJNWan>{QqU|8X}3->PZ+yPvcHCKD^{l_|r2a_F{Mx*&hv$LmZ zVFTLG7~4=#ywJCe|Ki$ToO8R(rM9-n^j%l#io-YswJk0#X&0CqHD=^;s^Ed%2>bdc z6m;KO=qwqMT9i|#GFsQRhDlqPGrpzS5IU~LR^>1Oa8A0ueSiNm4W4lp!(Vy{!h-6q#iy9hQQWe z@a^{%7mlL@`vXkjo@rM1N9H73YCYr`o^VR7aRxUSZ0$&9@MA?`n3{y;|IQ}}Vsvv9 zcQ)EoQk#dQk`?L|8!^TMqR^X>^rOzK~FXF2o7W{*X~)n0e? zWG9`P`jTdr%;KQ=JhaAD$-4Z4zXgx~uB`j%G$z4i8qrbgf6#E*SK>$vV$3xEBftEd z3E*rQx~qt>T+e;F?`(`6cwJW6U!k+0{9JBz6&zlo7+RwrsjiF@nKJeHa;`t{NW^PF z-Dd1ztPk)g94;u%ysAteGGxwt#35&q*(fTb-LXR~@3s+t1Mz8o>Z_LdZUMW4%JptY5)gX5ram;PS9E*q0NTG+Wki#uuKvKywb;P2s))ZCPQ3{y=oI0pzH>#IS}xfe zcqdAGZKooHZfrbQzv$ZMZl=n->Y}`*%Y+eU@PbumTu+;5n~lEt|E2IgQ8zsWzg9M_ z*IQ|dg89l{tiSw%?LAR`%B;O}GOPVY z8WJFv=(Is(y{F$$16`uW7=Z%D7;r`wpN1MSnrj1M6TVB&AE?-GA#65*c;tW2`91Lx zPh7e~8_u0Wiw+dkw`AX`n2%&VS#C%^$P0RZvbGZH{$%D+k9E)ykCU};xo(G(i!1*1 z%69XFqcnRekDjrc=jfH)JrcmdUCw#SZAF0A8-4K#M(g#|a*yUo_wTNFXrj6tt)m+` zhwd^fbuCKWG5gbee<9Sht8vJZG3qV6x2Hi*TJg?RZjgRo&!F53!XqZjX+T(rdO(6N zvsSQN#2H;>2ybA8edqrFc9BmU5&ycJfI0Vt{T18bxG)!7Z)rOkFF^!|1W4Y^-Ml5i z@m88|oQ0f!6uI+1!kpJ?Zs0}Fzyl~oL%Vv%mD3dobS=f{N(9H@YdYFRW!ckw;07~egU;s65kEi#zy+DuTLk`lYqTI(C8-0O4NkA`{ z0;IOxzvhfzNk&3O3^96`{|+W`iq=o*{&+{-jNUwf`tnRwLaMgABCOsy}+ z;&_=NrVJYYg@>(QK&bc|zfdtsJMZ-FaZl0|po->9f#AtslA6UCvX#g$2Vy~4f#_cm zB2+Cwr*$2rA+O%V@#Y6NPJI=4%bv;g?K7wGvK(95$a!cAfbKddJg?VNu=<$I(xZ!< zWJfE#O-!#3uzE(45-(iRi>Mxn>fCI)(C5J-yu4W)H<6cm3?a9|9WV<-pz4b zka=xu0`{QhK3!uyEc_@QGL3$AA%;B8$@$(%?I2a*@V|f9&l{tRo4rWE_bYmyqSoL{hTRjjRRc@5GRxGanr$!4iDtg zF8;_FF8EovU35xOsry2>Rzt?UPdx9mt5k2bK1}tb`Gaov_Kj5-+vC*_Jh6bl0>I0( zSKoXntDbbXs%A^RBD_!HJIru@u(aZT4F=}juPJNW z44Uf~RN1&)rw~P@T}3Z%!{@FJJ>fTU1AZhYOvvDp6zC>JAHLle;hjIliGU&)pp=9x zBdICe5E(CJuqUKKUaDr-b1iLNpT^V_De*R*?P%QXr{yg*;FNK@EJ(TK)8)ST?fPp! zQb2fQZ@*Q_2ZOfksHRWfcgSyfFGHTK6B`V5?iY52+z>aU5Be0aJkB1XrnBM&wpa(t z^hy3Bvh$}3mJWb7ZM-@^_#FuEJc{=re;1gkW7So0G3)g|uV!{sm!Nw>7X%Sz(Jg9d z`l++y@=ky%qHkFFO%B331{jn{31~c&Z_RJ=v_;5Oh(pjq?3KhnScNyXYV4KyXW3o| zVp$MxuEgKgcEb(vk|g35bcR1T=0yUhZU1g2FJevhG(>K=6JoQb;|2(9o_m_>ao_0q zO;we%^E&$u%EYVuP?@uBSB+Flt*t`M zg_-~9A_8}{z6gdX2dqUf9?x%LzG|{fm7l$=}zM{{OR6xN4cr@=o%r^zrq4 z{z*=EsV@we406_9xsnt})-+$1t_l_DIG>-^%dr#ByS#=`#7|?v zjQVDspG@MGRq+7cfY%iTU5((K>%H=wqbg2Z#^D9EUti_iy1wH%XqVy#YU6%9NGrlJ zLnME%bC`mTGypRjFK~ZlIFvs4b{A--x_M`Nrj`az+&$w3}D8ZZ;q;WYx8* zHdyT~(c^#PIST#(npSb7tnu!DB)iOZ@WL(IZ-fJyV0{Np2t%j%+2$C$F+ydGpstt! zS|&MyXr%$JiLwiXYs19|K+H&{+=iuAD!%@~ca=hau9spLjU7fjG#;v+T&oWo59fdS ze(&`>p=wrkjI{3$;gQifeYKolxaw2@62z$f8#7l?3cn*m6U?jus{=dSArfA~0`G&8 z(}@P-wi|{&e%X97rJhA1m(gkI!xV)2LVHwi*RUw4cy_m?%U5A0;;wZBT%K8i@6 zyqoqle7Y>0!I0uE+BO6mb;rRsJiX5&YSYq>a^5phTjn+pdz^p#ItcjvY^>%ydNBWq zeYsl+~>|3$UCf!pBqKrRlLG&iXw@O zfxp*H=Ws1gSjv7SKf&XVlCXx6##c8t$~O!Zzk<^Jsl0FtAcpEFiBtF4Y-Ltoe#%jL zYK8jr5=HXQjLI~M1QIs10|nYzQh<4r&iF9X_h;#F#hs zNT{51Np_9=#O9butA3Mt`Bh4cx=biSe4(NZ3nwRWqKk_IiG1yb7q8$%ATALu{~^vG zlP%GWupdoN*n?tWy#&)U?2%xfLiU809$HZBv3-C*-irF}Td_>9%2bUa*nRiOzN~=j zp9%QW9-09`Oc|;);W{0cp+#AY09zNpgdQa==LDX=DnsEhJHVJ096H7Irl_NqXp6Ao z=o&V3(+pjeUIl3jE8l)Io#w;-tOZ#q{(`P7BL%Uuu;mIFIzuyY7!0N7WV?fD$)EO~6dcgb^nQwgNF@ z#P6vZlMJVm0pfXs?YmVI>jGoXHZWEdVXOQ!RU!~W^q?h*S!yDbWa;=pNX4RRxr)Y&EAGlP$BF^uj+VMZt(x@0+@t2C~5_t zxsBteOH<$9K$%N%d>T>Wd{fDg&PofSzcV%Xw6|eKq7%P8+-}41-xjRGiZPm*F!pvy z8ixcXoE&ua$MDzFcK(4Ud;qgf{{t2L$>-bhWWQewbqO^oso-igQzUUyzrq?RjflG8Ncu}65{|F}1$DS>4%2B;)vnm;HBFs| zs9zIsJgQEv@#Jn2@JNd`kW%a!sg${vm>e~F7&b`VvRWEQ7)gH#tn+i|7lvgQo*1L! zR}|V?6LrMQE{~UIMburtb@rrw-JMjm+0*%sD-j%v7UcEGj>VZVa$V9QM$ zj6#_o?21QB+psatCjiYZJ+{Xm0H=Er^pmYNQt!Y|!7#>*x0KBIsjXeCm|0Vk6N{SKc#&jXg*Z?@(N3FTg%f|rwz>Q4Qx6EDmi#y|eWI52 zHM$DBgQRU)*!nkKXIX^j{P!;BC3=Xmns#Am7w9UTFvvs%FJ6f&#Vi1s*};P3%qu`9P8?1D$@lX73Rb65>ZvkPqCaY z=FCRCEEo4=^7eavN^;iRuEL1*YJ|NuKu_(GmUYDqDF&2G^PW8#aI6 ziPV!*r7cXF?fDPjtl*1Ri~3ofixt8_(<)9I_a!KOBSvuHkZ_fqt}xOK28cFsFFCAR zHD~K&B%H^EWhYCz?9?+KD2R8)+fO6h)#NZ<6(KD+d!E~bmMaDO+qPZJsnCPZWWh0o zU>bF8V&T zNkBy)BtOe&C?bYfu}G08secAY$yoYqm3~prc8@l`&OsK}?6cn$e#u$1kS6lsuhUnp z{Y$U&`sl2g_)kW()75^Yp=C@q|y@QML4Te@PD4t{}YR=l- z@XFt2077M2Efup)(`WM&{${kdpCR^d!lc0q-*+!Kq!J(=m_9~O8w7)@_62M%mE6~R zE>Y7h?`jzM)eGY&>L)dsjFMby2!NSQ9BLH5iA-+7uLPVN2?XZ)j*A1XLK`Lv6Rli} zQaNPx6$mapx8kO##wX%vo7Bcd7Zaz}5M^&&Cn#8>Jq(Flb0Y1nb%EIteL8SJJ^9k@{bKSERurCxC;pM33 zvRyBgH=Ff!VP30;;0Ft%VpO=O5vr?paTnrkhZiD~7n(gzJ z;GYa~C~)V|Kzr<$KZd&|Ws#`w(yH3toF`=mPLmNd4 z{`sCJqQ6FUr<)Tl0yUy7R%&$PXk&i*4K0ZH2Oe#Pvxt2tEniBn5gPX7{#vEyD;fnW!6i-kN2JYS@DrZW+JV9oBMttbsbiW3gChAwzIw=O3PgM7Y zef^djLomkW0+_XH;NmqU3wNl5(4DIsbV{@<$hvS z0aa>yt5c`IMz}J7|J!2iCoC8+s_7mt!J#78Y*TuHTHgWt zgE|IQ#K^b|vJd8f&y!lx?IiB|x*%adXfH=Sjko)8C|crac9rU-y_Q)xju@s6=3Qes zTOjeyolj2i@;TUQ54gs?3~z6~zmO8{!ugJ6><#;V=59kn*0PtGb=P-xwT)sHo6IqD z8N2|-%+RaqqXxChwuXOZ&dAIQdCv})oyL8S_c{M`#Fn@hj@tLJ#^y>7PG6?Jv7TQV%NE*_BS=X;?6b4d zE_ROaw{PplU-kHYD8RFqqda|b7$Cevr;;YCwYRHokq5balsHCsPE;5EY=CnBgu>#k zVF|*x0H=<1PEmEB3oNPunV&fcHrzPg-7rTnbc+nXYRwAdap*BfH!>cz)K;8yb*sQ9 zPyjB+zNObCt8gaqt4Z0C-5ss?S|S*3(RaTt4WhD4=bqA0#VtVE`9oXYQFBRCMx-y)w#jkJ)XtxTU06Ru82OXa6J%g}MBt4H z`0R$0y@y|n!pC2XFSlAP<(SN;R^uC74+eI3JbUBW&bv`+RT-!N^ExI9v;7#XGHk7jTpom!7AiN|VoWQAsw4|ASuzvRl9jv~#dV-571Ps%@GT;@7Xt_h;w95t& zM(9_xdpg_VIJ~Ap0fh90#MH?anUEaDu<1R31`=z|ra)fH+eK&w{eYNnHnwF@mY73y z*AScC6(;i-iy5uM>ZS`2>NhS%@$2!vmI{8ZiyUIsZ^V3Y!tdO}k&hccTGZn^hzE2| z31U#QL)Fn=&hULZIbio{1h{S;{se~GdvdBVjc`5p&AIrx6M&0JX(;{l_unkr8Cd-6 z`)>ZB%Vmyva*~fyNkZM;gTu<74GBI$xA+n(vqUdW8`4huoS>_xt$=fCz&lwz&{idd zJ=-!Ljoj^4mfWd!!WTB!8^3>Ly1P!80--RW@Fwm2B4X&5Pcy)J&kau}`EaA>L%l$Y z+KYj5PK5vtmV>7D-kmDtyd_ntWrq;f%`yXZql&R7TO{pOdHl$KJTp@zFdf-&P`FNw z+WOdu*(53Q@zlZ>v!QOM|yo{5T)d92Av zvgAlCDV6*zd{{*gr!dZ9k1@HKg{ zj(C4wjD{`y{*(zSBU5o9&RHU;`kW?X9_&rird4|v(d?KiPhhD=wQ>9A<$4n#yW0Bt zq|n|P2s;f5I^TifcA^m2W8WMys0L@sZ778Twh$)!%}wlqyx1F}|Ye0I$sW%i~l8n%^l=l0DZ!(k5WHxig{W_AFXO1K@U0!8qJG_qH_Hy#bHZ1;#PpbyaR`0vkXtV7Pwg zgJ5CY{=k{&AMbKl0}`wVTrr5fVESu>lZnNP41bxFV4jxPE*@sP173@|%FCBnkn@yU zmq1alObfsKorOS;02}a7+|5qwLU{*Yy11Ag=*F01gIz}Gj9OOh+;O;6K$~6V9JEaw zOp(mZ+}dsfEgXV4xaAk==5Vw@uxM4B>~E&lPsiR<{7*CYfRoiuEWtD!xbaP|FljFy z64yk}2nnygh5wT~;#5uStl!OK>Vi`H*t)|=NT`JLh%WdH5bovsrecFP**fFCnIxE-1>7Z zKpTDU?hY82%esVi48B^eE-1K8`?D4oTmno>F4|}0UHh)&l8J>m>p8%`NwT$03vR&T zU0hM~AdS}7X5)&tjfTnuZ;bdUEFD<7?H5w*LN0uZTmR9fo(re7;yCyJ)xozDz9SRTK#p}5~alcw;3o2c9!&yMHK5;zaM;3LX*1dljt(A7-^82)Lg&cYN-rplj11U(|0&wP)!NrV^h+RDT4VOk?u5`eIm)AZ8Y=77V~ay^0N*IEdRPoSC(o|i>Oc>R3U8sLRe7S>4@e1MjkF@ zTS<&lY9fRLV!_n-jtDZ*nesPyv4x97L4?vvDW!{J?n(B^V-m$WPhEh+ z7Uw5q)FrQC<%MTFh^SR;m-*np^>-S;uL6eL@BogZoHAMbrY`(rTZT@Y(rxpD{Dd4k zIII=g^d_*R5#5NI{GjI*q0V?C#r<2_Y#wqAHi9&6ClQUS#WC-|(}%si&RJ>p+(A59UwKrc!SSR@r-Cgf)~7)_$h&P!8lX74Zo&54 z{M8N(cZ*3qPm~ODWQ+NHY{57i@kx3>X8$X4xJuN+CY={jU!GW580TG3`NcW)BKZ^e zL*wz5^OM=&lT?!J@&xl&V~8WGzX;nH6~U|P<2!9eYKEuqvfY^YSA-2m`ZE8Jarlmo z%wit)BocT+qn<5^bwUJDZSEm1GODBD_+bm=qSLW4%}T}i{axdf)3^0My1<|kl@Gdf;VKCr zk6)BG;^zIfFkfP(?IwSWZOdDZDER67E#hFwHnG3NfL4IT$6LIncU3!97C=5os;X~f z+CuBrtFeZb9&_{-%kX(IFAv2?M|Lx#rK$YJrzZMx3&V)0U6rLfqrMK9>THbu-giH2 z{iz}e(4S*@N@GW3Hd&5ROi-rH# z;Mrcmza~PI=GZf2!m+Atw0IFdfKV}a_Xb;h3;>U=#G_@Qm;q?pZ5Ju0lEIR2P@TvH z7|`Y42V}y7f42gT5wOO5Ug?s7tk~uhMno6n{$q-uH2&G;a7H;kN-!2^M9rTWu(UnY~qvP55kU%3Iel=E<-n zb3&n>0^BTf^lVSQ?!Ke!;#+I%{Jz;f#rJcLE|G0Ow&?VJam5I~{WR91{u?kPbD9+X zTICH1MyGy?rmaBpT5Q+fEnBQlmpM-M5$=g%!xgf9f@-C@29KJ|7rOxdnTJqfWfcw* z#J?o~CZd9S>J7jP!$l<`UMbLwn|?p>xc`rf?~Z0WeBVxNHCweusx7M2t`)PYTC=70 zu3Bx0y(84BT2)1Ds%Y(1TVhj+p!NtN_6o5hdG+_6?|Hw!_y3&pIp@Bv`?~JydG0)% z;t_Vj)8D`FEzb(C$yKonu>Uqum3NwrtNA-kG_Hs@HSPWNpp)e6?3k!_Inevo@+Cqv zZ2%1Tq@2ook!9F3J`#5LF_?O3iIw~h!v}n zcO!sQ3*(Z6ejTmp7hIC2FW(en8+7s<7oJfL(aj*^S|(k#mjkL(=mvj70J057;{cFW zT@{I3b3cGynF&Dqj(W>l*6ve}Z+I_hT}Zeg{bDQ;uCI(f2!+erF}Z6 zjUi+~!V#2Hi3g~q28E#Mf8-xcPuuG_%XoA zx-mb}%dw%#3@_T;Nm9m@uEn(%ME4H&i0{?+G_U)Jr3zheE7Qow9Xh?=kLKMKj*N$R zlH6i_kI#5xQONo1x5kXm(UV?P znzkLSyE|a}fMg#MllC7oq-AwVt3#06YFeT#B|(-$hASX$5ut}1KQ1>@5wVBGAc8My zK_66k)CxOlJuML=_@=8$g*6q2#S_gYT&l%+bJH-VC50i&owpLANnAZCc0b04-eQ$+ zCner?WyVsr~GAPxl@Ma=3qeHNcKxT_Ac?!ZQsmIh>WBra}qmc&)w2u!iKYdCvBwNdc6N!FX%NJqh>N;u`MaV-b?9yE%t}T zsX8k*8lDPi-$7MAdzvgrLEhF*$1&wahlD(TVlysXZOiSxsr(K0b0!i^H-CamK(0+8 z9gA)(&1N(HFxuX$wg<`g8Z!Z{e%Etll%EfzB)~Z-zgHB3C`M1h(0`Or*Z4`k0&da1 z#^m@FAxgQ_&W}a_J87aBy%A5dFT-UP_K?ze}6;- zDv!SM$~0sQQ&8!!jvP>M=ynWna@lfNNYg0n%|TZqzGs9Rivt&9=L`GA(!fLSRqUE* zNh?fK73Z5yeHlPX_(w#l{XLv2h9|^I7WTvC-mRB&kL;Cc!`)b>n;_14GMbW2_B3(eX>n<+Nq51WYdOinWL`R z5!%fm5%;zk+x%@lph(rxbnTL|ub;Dl$-m_6;yzpf0kDo}@AO1m3v>i-NM7Yk529EP zZrvCYf08U;U^USEXuNU3N-CzLxISjHL~!07rN)GkR|reYkSR&NFi*A}(o|%MNwy6( zXNqOpUhsgK`zD6xS+nh16i01mv-KhENoI1;iG!!|a+GsFDb@r1vlXEnC8v61;8Ber6TozxU-{9ry|QeFYtr?o<%gaF2;kf#jXcux zJ8Kt@+*o3{aeUf?`{40_xe1Rd`5WV9!^kWZ<(Y;4FUy**)@80IzqN0qk4-HDF0j^1 zq1mRy0c)RC@R>^r&$)U@O(N_D9)!K6*zsCjbo1s8e7qht1R~8=y^Yf*W2?>&5d%|& ztPI45Lg`Kg{P@Yf0vP`~)GgN2-Gn%~VMDKr1e|{4-?UTLFymrnUw*IDt|D^Ldl|YU zOz5L<;X?}_@Lr9O1yYMm{OiZ&k2pscH`dNhmru&h3WvS=ZsNY#k%SOdQuqNKl@;d< zadNl+(BIh~;|DB4W3)6WeVyqVP@V{CvR*afHgmAi!9%6YxzU_q93>qSAT`CorjnZD zL$E42R`!#GJ(2%QRUPdnYT2Bwb|4r%^S;&&qi)`FI%)mmq$%16n%I<;Wak5Ls_F#; zSjN8&uy_t_B3*y_d?&Pm<4{hMn%@hmlUxPu5pBSGc=+9>>%|HAqXz z+Eg|={8fw|mK#0(aLq8ec`!)Of)%qPVU2j6I=f`QnWf50oYdwiJrx5u=Ejc@7Gx0Y zU{d(y4x~31pK;Bu>*7ZU<~O4fPaCJ16-CxDsg_!nU*?8M+#@A^vXJm-I{_dQWuPG* z=zD_-fk3cgFD~rl$7*UqysUm#)0*xUTo}UrAO`1=Mk^)(?MB0urF#QIRiXjb-f~*4 zSnSF=gV45g?filyZCF+?ptDt?&#t7E*7FweHi@EF8bga<(ttXb2)Y`Djnp;nR}5#J z1bEMZi(5KCOf8#|AlRihu3%AoU!0J4LKb>q-O-?~A>-28TX&FFa9O^xejD|J<_)@> zb$b14LD4{u?~8@#KW{)uK5a6sqwRO${KR;!&%$CZn(DM=W=Cpok`i?g%&3M!K19!EA*Yo_-~IV}{1Lb!8ofXGU4rv3WIRn3WCL287Uw{4_~6k)QoyJEP9ZM`2R67$%$?U0hQCRBbH%hoMRqA#vy&6LF|v?#ag$|N5`IRfoNop2 z^Dt@Vj|;P(9*Oi*#vioNFe2VB2l8vToKqfF^c9BPkMq|P;CVIqf^<)uDRkg_opT{D8jM$d+#I#Wzxmgm5JW4I(#`>GCsA zy#Mtg#X8%?$@2XYQ5<{l$wf(`&telbNABJtc+DMT-yht;xv`#cspA7|_ul@NH4xJC zp|Gr3G#A7_gnA#}o%vccZCN59ZmAY{^yv-_dg$#V_aU?`^Yv-SBXo$jo`HN(4*4Kr z#giV@bCo;12R;2b?v}?YkC3&M6Ik|pIl5q{agZQ%bMZYHN9W~l*z)j~eKGQ}Fp*dr z%C;j;hM_rfn{3yF)V`ilmZ*P*;f6hFZs@4`*)zB(akMFMChszP21XgC<^cI94pYyS z9S_d8lk`ZelJbVT+GG~|1!6-W!TYLgQt?>QE;{4{Q+ z9=K=5Bty@$9h;JK?pSQElk9DqW7g5#(T&BZ&auz;oAb`L#lQ%d6RXO zcIAoL_^#~=86;5%Lcja|dhx4P0p*CUC>-NF?*^8d= zcniML^Op!>Vd)&@AV=o-NN5bRw`9?f{?L60(w?BR6+(tV_DFZOV4kDk@7aPS5C}{NJ8N_-KepZJ?853omAfX82=l9n8)c zMs8{I)#gvj$wDBS8#q1`@^9_OmY{R1j?vfY!z}@d8$-x|42tMG50GBsDvY3$_Y3}F z)G;h;QrTU&wgu92+NF)k!jn5?qH5vF;A(9uW_@WyPH47Go|1=@%|rtjpC!~K$;+pQcdP4UV=@5vLE1>o9=^Y1L@L9Jsfg&E}O?67u--ssll#ahHR31tCjGDDvzzEn!KKz z95XxW;>}pz3jEYX9;2s!QT2h=>LKlG{itca^t^WI7tU5R<&-GS=UM<;(0w;R=O1 z7qwE|m#llpRMMVVe*J?ltU<)HSO_|Ow;$*mE7Ciuy(GrnALgXf_BnEEJvjwHnCh?P z2yqe&p|6D_&w7o_3{ zHkyK>bEJVP9Gey`Z$~H)#lil|{l2DGl4U)iwWgOjMw-V!7$*ZV(JCyw0Wpz$6Ia%g zRsXi+h<>O7?rlVPHB)MUNf^k66H8LZ$=ZaBj>l74KQnySsa_uL0)PQC@1pv^4J8FY zm|Bl@zx#9z%lG!*VsM+2OE5qWpS{jz&$(nuH`E5m-kDU05=dj==`~;+{j%}l#Pgt= z)tYPlRYmH|XDkz)rqU-O>K;KPjKkvSeFrKP1ih;vKju@~zHl6uA#*QgK=%5`j+X_3 zf89s1KWK`2adWDCv$+^CG+D4yd|CTIBMoGZ@^6t`tU(cj-#s^^U<2Kc&7*|0GWSv~ zOk@GsAPmeseF5fRaw-&T|JJ(nppA!r|M^m-t%0IPi!C>|drPXM6dm`gY0cs9;-2l) z@KC+mmgV)E$bkSO2Z=MrQ_|-*UM|VYZ{1%@yu6zwGxykdnX`)%$@vY)zbm$hY6<41 zxDe{>52@!J?pLBv6e4O<_`T1~6wAHDvMDULWZ56jXTW|EgK5^y!E^uJqfem8MC(nVRqI0q2s`8!pMwmhQA! zsY1+dZW(1jgdN2%M~h!1I_CB_08J*pl0kjNf{c`Rv!h999g2YG*w}jB+O3s)8b0>2 zy>tXs(lCmqkr2wBSQ$6A^~fYy=;U--61H?g5^4FG1hB9Q`V*O>O8VeUi0BQ}e2sNs z6~McJMg6US5d1e!3AlF|?Bd1xi4YkTOb-6g3w_RdApB|VDa|1Mf=iM`I`i4?(zi?d zDN{CvKf%3|s$(R*S%XN zq(w0ehvsAfRW7-0w{Ifpfs6BjC$|aNw{V0>z?yQ~zY0;%S-d1oN)ECt?EPLSqL;05 zE^f3RDvh(_v-ZNULphIu~#ss!RCN0Q)kSq8e%;3z85LV-%>^=mRnd(;I20}2c9P-s0|T>{J!}=%kZ{*CNGIf zTu~$x!OjINtq;D;5(jFC4vd!HNLomEqC(GJh*=a$mW^R5H|PdS1fkE2N5NUPNMCWF zjcO>BUFzi`RWm=BzAuK|*}O-Ku6S?5uC^#D>|6afl}?2Xj0>clX@kE5(2sv2qpG!R z@h7|Q-9wHhgP;8)OoFK*kwOW1lKY$-2z5J3Kjw#GXKsd|$}u9F>^Ma;37eNBP$jE< zQNS{DjbB(Hbri@RUPGe-`ug?x{tzIyS<lUBRzpj}OM5QeQh7WF|4?b)-! zQ)KXCd#~ja&X8+k%({+ycWaw?dINf5m=!a5E9BhJM=(3pv*H3M>_FUo_evvL7m(5}kA@^?`cgQ(%#T7{w{i#^i z1#E%uJ00Ej^L1(RSo9u0s4s6G?X*9@jOr13uJyKYSOvdphzhOn^Py$*J?uI9O4ZB@(4pfS~ef*gZ>hUdxXw~A^ zUFG8KVso+cm-QgQv!b@Ir6PlPLf-hR@+QaQdAp`2SxIcn?aa&XN8u{egB4xicMC%x zxUL-+@+<;&Rb9C;ei)1VMV`PrTThF{wB(U)+9)%*K>ns#4yybQ3*d)QBXvr3=O22r z0FypF_2MV`ZuM`}d?nU6)kM1yrO(TQBFjHDN?1uJ@LoE+X(N5p>0I#L-xA?2NgKFc zVGY`2YePC#GT`EYIss4EIzjn4Zrc1GsKc)7k+EsO7Zsn2OJ#NPW%BPa%KR&#DP2QW zV6Mrd<@S-G5T^6~eTPxT7PLupl*1-j$T@``)%xe9Deo>azy`f{8cIF$A~fbZ_b4rg zlNxDkuL6r_Fi*Z*%gA%+2l;DC+eO`f2nO%!m41I{&4-bXP@hj7SL~J%^Bs?J?i`gw zZH67KJw{v7=&vZecTC?N-U9~J8Dhf@lbl(P))b1)xa&u`0X5+{R4=YiM6~DKdJ_#<%GdryU+vkVIXx!i!kvPto* zI!=3~Z8A3BI!kYx^4rD_VKNR|?MrpIs{SD7+0rSjm|Iby;`#ZO7|W}G=UE|i$70)p zLEeu;PF>C*D7a!a+YcYAE?wXf#Q9qYb@*)4mrul?Np0u-<eyL&C9WYBu$GZg1omY_4dc&$V{H4e4O*DkNY?+>2jgn6ti8+<$qO8V5 z<41R1dbLu&mkgdWO^BA{CLDx9qCM093`_B~9zK-H@@JEWv(&Yc0(j@FYG9?+hX~|U zR{rGnpJ=O5EpA{#NmHFr2UJyL(vwGbmZnbyNcq}<8s>Uak@=!FtjcVD<0U4KD`VRt zjn?vdFT^uROX*A51G)xC+dV2rSv9~pEe|aNEtC)j_Mv^bZxo@7Bm+_?JBEd_og?Ke z<$#NDDznT^_GJxMn;x}2Po16^08Xw+g|g(h5V~IyRT*yb$2< zT97v$O=0BOFZ^hbQRC3{6*#4&Alo9{q%^b82>0xC-n!9{<@?4{QtUtlen+)5-}Jm} zY{}mJWs}IE1_8bTvGl?6Wcp4-xx4&EpDYH^&Gsv=X=JnAl&vGv&=Bn8W^WB^JK#u( zRCErr+W3Hu2i^kHgk;}pc(KFPVA#H;{uS{ke8DSx>1{*4>~bRtpb$iP)!n8XZHz^V zZc5pz0#BrE(P_6xmBr_T)S&N;1D0KoHFpG}ytl=!DqGsW#ZcBm(TooQ%@KxGM!G&! zev8o6?!mpk{*r5G<91~@sQ!en@=db(q=#=7?B_PTZ>)f8y4$kfsx{T!0i_)^{N%D2&7(!tkZ8DeuR8Lk}L@uIBJdsn$ zxNtq(=XjiQT=82BDg?*#-NvT@0K*Yc;q!$zsS?|k(rq3aVUf2G&Yn%$jxfs>v_yh@lLTBa3k!`1 zM(G?p;IJMYW-f<=kOy`7pfWh$uEAhYIJU}*BX<<*RLM>u&jbh;$4LgiS`Wko_mcrA zl#W1s;UUshuH0@LJg098f=3t9Z^|uF($R7>e4&_=iA7M^8BN0kSGPWWh45a!I?|y{ ztU-v{=Q5-Si?`oLgJvTMiEd-QH4NVlp>5*8#bns;@+4DRyyNvtQ<5kIZN|i@c9`Xx zmJJE12T196J>zH3tb19_nzvGMhJHL)ZeyNG#w=&*8t5-swFX0fsGc0hyOOTxhP<6* z{yN$$S6*CG^k)V)f)rAfEt!fi@4on2&-JBY0G~K^X1;1ap>>!^X?7KyMeS=9dg>n- zPo6c;#XK4q?B!VFCZC;o5#p)vX*c?qfmR{-9GJ^9S9=E_2W1q}(WZULMDiU|!L;NZ z7EQXOXv@e%D`+*3kzHV-HI(OPeL5Weq|YFtkrJrjPgc6W5*mi!#%AXN9D`M&xFHsg z|6s#{WJfDHY^l(AB~PAO>R7W|sah!_%XuT6XE;yY)6O^ciVFo)Q%ys+k^@wm#M&Zc zL^)SzslqA_G^A-g#s+GrF8 z9QOxs6Ghne7E0xq2?WoFpeNzwm_2!-WdGC9a?GYY?B(?b4V#|%F8r6Zn4OmlhB-Iw zldVDD>OMk6a-2;MUE}}0usc=uCsJl^1xStWn=75&c|bwG)EkwuJ`|%3AWjYB~8~Q zO1OKK4)8CF0HC?PM@~}J-sYkc3R+67RFWTZmwEYXbW{e(x>RadNu;T5e_hSJBv!yR zP5brR&5x|;9yI(VoqJ4&lv#SWU)mJQ#hy#-ycM4Xl=v$?>Z@?QO&7>MxM0f|sf;sJ zhzu@7#E0sf_b+M?7OmM&y_o=Ajsc@9cdpsDV8{=ImlafP8=lUX*0z8bGM3~5x@$gi zW7#IwsOtF5*VJ!%@jYXkd-Rz_DG8BPvObt2o2>u@=y9G`b@Om=62`+r=z@weYD=#Dn0m**(8~^MOnNRpfHo zo07xgh(gK~XD*FL7a8k8tZjDlRyIFYY3(5M#SW`=KFWg zMxR@~5eT6`%rZb)20t7AOz@m;2CdgZ1rJT+R2SpPuopG&XMz%5@E+Z^P)*Dm#9KP# z9wEI6DL;keG7))4%WktC-dTtN=nIp6>jTpirG(n$n9f^WawvW_sq;KyUX2vDABFZp zBIA-u){g!#GQ)|!smaT&jw{*_(mDbe@zqOon+SY>X1#XN+%A)U?yCLz=gPILZ^pf> z`xZ@t_S4}%ngZ&FFq=&ZmTM^7?)h!wRFB8jpLz^;X@-s$bvP{O;R6SHn$M3St``!M zbelX5Uza_bOEelrZzK4_nbUEbmeGpF@G0vA9>q~mP3sKn9MfNlddXR$;L|Y!M?Xii zNP76kPtbv0C0WVRzfw;FPluyjac^A&vV4;aN)^`FLFlgvIgnIS)9n_c2jg^ve3nJHkF zA=oMC!PgW&*F3xV+7V-YoUi+x+x-|sIya;aoYAwOcT{lpWSiN}EDuj}cmW%Nq!TUX zR=6&|$?4`n-jwukrbl%0;n;)sgP$g0<$`p24x>v~Q`5=(@a>c!d?W3~Y(PERs2O9w zy`#k7bo(oB*M*=T;U2YtE*f%jjlocjW)sW54rlkCXsJ81kjxuBs?8sFc!4}=k%z2$ zRI$A=S(#s=aX+IIHTpbI4P49n!DyK&TE*8 zYd0pYq5Gujtc=)Mm%^xOB5Sy00*?;HLu1$N=A8=-%+cL{G8qE8+2uu|c4YqG*i*55 zIsO}&y3&);+ufa@%XN($*uifBQ8^+ga_Vcy6A7xZEl1+PdyeUvH-&3zWaS-@*KbhF)t`3iUKqH90$CG(kd>y}Z` zF!Fnbo>rrCj+qHA)JYCPj#@2dX~-h)rkTtk&mxTa!Y*<+sWx&$pP}&+H1WGtMDnL-3U^|7I^NjLeeD@#6&@}4%-d1AB*a<|pPnC6&fn!$o}4DOUP5;) zc{q{llFfx~J+qLf0T*CN4AkMN-hKEhho3$;}2dHp;x99TW8Y}mMkY=+Pw+%|!e-`i`>@Do+{AgdsCm^43yQ00j{if~gStsnA zq5{dP^xP~CieT348;kf4FlZGPb>{`A`U`e9fu{Yv5RI6gs{QmEe=KT-ymvrj#?dDSH_-{viq;{LFy1;r!28}Uyw>3cKBLGYO+@;dzOs+7`Br^8NYuS z#Q-Ac-EKQY&`7!G@IEwfbdJZRzMKsx$L;|pk2IzawE+#N^}nk|tCmD?2&%%HKbd2k zuiYGd75ll4pIR~qFTSAro&svra%S~H-g`NR=G2NqE3k@tw3Z{oH3$ycM z>cpsgf#GcDvV*4?KwW-yrx$ZE4$S66Z^!nLVHn}CSN;Ul%=YH%3;ykXTI`{py5)jf zVW^UFVH6c2>oQ)(%dBEiCSowkZ!~>q=??=U3wzzwlcbGAJFFl#Ek#z3;pq@&{ zx4r1z1k_xk=UTFZk5&H%_+UiHn{CH+i9`6=e6G`LDOqmv4_Uyz=@{L&=^b-1Y)uBF zR=jKdZ`Qvx-?o0>(tso7`WLnT18ezbibD2t4BLk0dIYwrw@0M1_YeIERe*kWa`5&I zo>GIOG_fel{W% zaF7ie5ouZRLV}P0yWFx?o=77?c{*-4&K`$yW^T6NVYx4JH$HUsYAzGQwHPP7e!c$fK~2C)b5nB#0`wo!-`uc^FSekRikpC9y1`dw>TMK9&&8$mfHJY1d1;FZkrbOdYfl-3*u!%XXF> z=%Z9>vORnpx3{ty$N9zOdvr*n$iwf}8!Zs`BFfTC>-0_+)rDS(h2Qoo>NLD+YoZRq zQQo7!tdPzr1q=sY_8K7v$p^9){jEQ3Q+=%(eg?4h|N44`t!SZn?8Ui$a|_>g(_?5- z(O-=#bTbraA0-O*C>0!-(VB}saPa3Z8M|JIj3p{aE2)iJ0j~QM%55ifhIQs&UWTD7 zcodgMTkK+Cqq)PhcRuacbm->z|6S5lO5-oedeZx<_%&+Mx_j?(2SwPqvqO&>N3mAJ z&DJ#4vXlM#-?emFV4`yKl@Sfxsc=Bk@r-tGG7H6rdu6mmW{IVJ0RBZf*ZnqHj?V3WJ2tYM zLLpMHSLW%9sJF&V^d5F|TUD-UP&xkp;eypq^RvC@r30@ON5Adc9&uq$BA9CwR)z?L zEfPA%YI+T|YV8jW+9-MWKF~aK9xQS*_?{%yvZ!S8|aw;+CI!g>vnp!RFZ|wMQLVNq~I8K=< zU|W3T(KPkq`+}i;ebIKj2;h=HE84a^`s_#T3h3nSb8b;%KG6pZv`v3CEP=uaw%j_Y zvIF*01;TQW0fP>0Pv*-4C5MQLZ}AgVe`*%&*b(ve7KMKbl(?)Mcv6?_{b@xz-!eNt zNd4#FRKp8>Xl(2)iEfj%+vH8JIkr0YKW~d! zG$*w#TYapFFkWaK-K#WSbW|mj^T2f>PD=D@VPQOt+>o@)gaLa6XSYRvXW(oD*(dFc z%n07#bmbz^4K58;ML8P_mHU--(3S@8tqGm7T*Qb-$fW~gyrT=S6o+dtht~5D~srzjtufdSW=F^qV;h_a=nhj$NpNbc&``4DLP6?iRT3 zVnOL9a{X=z3nAf1#y?-f8U}6ihp`WjZfe`Q%c1j#FzTmJ@cspdXGV* z%0t(WRRgr6hEGl~(1?dUr)8N`CiY83`%k{4bv`p9$Q>f;VF!lZtJckc)7chkM_h)b zK?C((ZG4mrgDsjiZ;-&{+Tlo89w>lOmg0$nDEuJnzvbY!yFf-d@&mOSc4HR8J`8%MMDRcP_M z84?PTR80r?<=+9+zpTnBxiODy1wg2+3@qIVtFwVmfr=RzZXlcqur%;wbe5#iK>T}W zdyaheLO-0O-Qqf3R)mn$zlWjzt7&sXr60C6blD|JpgjKQ_dvX+?2HoZ1m7K>)b6t_ z%IRRkS!^LA9UV>e2+ z<2dk!jrpufyjO5GjQmvJ7l=Rk*4V8c5Y0l=pa0e)i#sf|%@}|aMjs_{^v+P{b^@+o z&sstOMGi^IV4VZ|O-?gE=87qy_|y=QBmE-10Xq|oF@a2sux-xkKhUtPHmhti)l>pkPq@7D#6&u{a4;LejIIUuGsg5a#^UqDy8hm)L$dsU_~Kat0>4=Tojjo=w1#fd2Lh(^Jw5PvmX)xb`)hH?EQxG_uRxTv-hLY}RWF_H51V zW!XoD;I+3-rdsz#-M%kj(ir*{_5hVgUSZ|ma`fw|s#3vvw;4s|snkq+0ofY|3cDnJ zW&K_T46G#CW%Ks^JW&Y`uc{=U#0^Xbe1K7^tW;Ow>ezNIXy>DiB)_yK2dcksl;Duh zu)xM~F8+T#&&*MI^gLdpuY|t-8hgO+By4eSF$>bpBr1?f9PfUo48P}$vGy5R>4EJ} z(IHFYE<=zWpVIqibJLK6{EnZv#fHIwTW{f_B zbLRT~_CjkQ=*KsdTj4-tiR)vjmppZim7I&KgBjWb@wL|%XB7O$td9zZzu6UsW3|kW zFHF9GOf?t?PkV7&N)L!b4m{t&T0mB$``gt0=7WZ4K?)kM5=%%@a53!XMwvcBT(h zUAs&;K0Z$Pbuet|pElTJVR2e&jrW;mC>NXy&*|xPrWNhC?jimb$O$@ldBWCmjuC~O z(V8=4RP<*UCzpr{t%qx=!Mnf90DOxFWIYQI*(0O6hW3l9;VPKx19Hd9 zW^Pb(ItIN<6+jxNlK1H82)n0by`Pz)q|5>>yU}~fkr62yD`1p<_c{GFBp+YAm)S1? zW=@>CH~l)ZFO1>nl;e@VbE&wPA|gw5=GnRF9Xhwfc3YWH%+;C#jp%69=a3g#5NW`g zOZ$ZUe7eW-%lQ|L{Myr!xmqo@W@qJn7CIo|04uB~ zatRv`4qq1rj{LnHE1FmHuEX&T$Sg`{4=0kbM;1b#PTeXE;8G62tqT&b?Jq@vg3aTW zO?pXoqkEhOw?-><&>1~t4-^FyWPD!U%!+C(M1a3)P@GrKgr=LRZU zwavU_6L>_EdP{3-JzlSm%>z;z8P2PO+0DwYgi7gL%b$BUztmg8bb;R_)uD}!V@irn zY8-~1ENDro4~M2AzDzE`f6s#(*HRd>5F|h8W z6GYgj*&$h+Aim2?h}MJoL+ur8v;BU(FD*)^UpFLr*uyQRPd-AQ<&3ot`vPAF4CJo& zp^$!^!VH8av0B;Q9PEYY!TWEN%2dwgq8D3_@4&r!fLT1`HaJelr!$EnQB&g)Au?X( zE0TuawtV*%pw^$L0eUx8>V4uVbkrOL0Q>1aB#Tb>6y7cg7v)9xIMTGdAA$@c@4H1H z(#@(^DE{}E&12G7A%&ECW2tuW62bYMv1*FP!Zc>_F9uF|!mCLAOwQKoh%E=!j!Y_V zqY&{uSWC6vMJ2SlPi97-xlvj30yCJn9bdB#-T3bV&VsM#Gkm$ToVjJ_9>tX8J4-C| z3icp$Q{4+d9L?L-1kHNSXKqG0iB|?v1>E&RbMr6AR!j0|O%h^dhTJ-GVC2v3{7Px= z!2R}GPvAw5C%tdBfhiZ=ohJ>^)00HMgLRRRk?7U4a#RBx4hHH+t)%*GuzIRev<#Oo zI15TVkbk`~qS1FQE^NQ6HFdy#tX^*FVX|;zNOQ9ORG(1jhFalSVV4S_NZ))LZ7t)H zd;o3{!Bnf>t$wDHXB5of2X~H=DI^PFRB05QOP7F#0nu7 zfIFJhnBiA8J<~u;+e1?cpUXuodMo0G8VVIwm%^~aMlcclg(wk&2s+-fCZ3b`Hn+zh ztW7uSfsl2-iYd-LH>*4udaP-?K$-EemdP%yTM*L%md{JB4P76klG#~EdcK6=VA$zG zh=2k7B#ujh(OmA0A!*W7#i0FP431CWg!8S`89qdQbjVt<7g+w98xy=(&C&eH>XfJQ z5ef|ax^pLJYbC`eUL%og;=5DI7&5?jjdN$NuY~fFg{~~L+LrAFf@V0oZ$POjO9NL@3RaOya@TQxFBz6|V*GV-Vg0FN6r$poq}sWJ$a~V{tbd1x zAN(2ops%4ATujd}xanr8FP#Zj!5LFS*ZB(mk>hb{Yjc)m$T<$Pp{L zhp=aB?!c{n>hdn8I_Oi)*|@@oWDJ(n?bxpgoi({7R;Vbp(pd_GF(K}Mr)o9n9i_H*k| zc$IM&>v!sB&x!Rn{NJm-|7ga$Itn(BpD^;}XrcIHrpqVG?A8JAHb?^gY`8Jx#X)Y- z1ovua+_9vB7f=IexphfSo^N)m>z|#TytkWn>3w&$HUaC85o*Bf3oBzWNz9STz2K7A z<{zjtS;dwtQ1HIbG=xj6Xi#ceId9K{q>UsQN_Q%sx$!_j@)pvceb%<~IBXQ3tf zkW`Vt?ZP`vJR61)rvI%gS%86r5)m?dPd)so4DnaClro4oK#@8v>c;X}T`{`|Yz);=T+rPy(-E+Ik!2=rznhZ=B|OK!~N{T&!g z_aq`NcOW@7_^Zg#TVao&_Ff@59|s%+UYQ8Tr2)S+$cIdY zp~+K;cM6qWYi^dhfG-^wvG+4PS*sOVq>3M_JNJGA+`43xGWka2J}Vj)H_)g^YBq0v z@rqjb#mOOT#(!FxWml<130TnuuoM1VtCqCcn)ziO(DP=~P(VRvV3(Y3{uwEyjI)Y; zfjXG5&hUogzt4xI%}EKXsc4|4Wz_6D7i~dq^s@v$omKI|PQ~!pExTCj`GaBQ5cgR` z?5pe#TZdCT(>wbzzl&veB|nVArr%sx5Cn?i=$X*4jvk(NgVm$pWgWYx9^1WKJ&=nV z2(-52oO+%~o33c|gU`4dq_1Lnrd3m{= zLyQV}{fd-rpKL$~tB_y<;bIR_UP5q z2Xh}I1+O?TgY!?lR@&`U?V5vzvfDYUGP9^XX76?n z-4a$dVpFYU9kJxn8o$h{N6sJzw`^rW$j^G7xm;iC^_vZ$@1*3o(hmXJGUH9U_Oz%= zl`$|hUQ*Hhm{Z=eP=;ZQ$#G3KiraXZ8}c+r^s08q7BMo`Dgj!LsbIe)nYd0PdRu(< z%@Ap2fhHOo>qu<551GPvzijFnRKt9=k!dmgzVj+g#V=21Ax2A!ZK_=C284u}%u`1l zQ?a{$($cWYOm`_g=dVE>*V2T9j3CqZIVt`R9v;eoqh<7Cuk|Vqr!Em*>9sS2NQbO|hv-07C_u)ZWr_ zD2-1q&s@B8t%pC_KR z7ta3%-p@G4qGx++HZk057k|Kqj_jG3jI)trtGMMe~DngM>-QoYgQjj zqR4893ao=8O!~D<%OZcFNrqgs`S)Hzgpe-eBU+*th8&L3%Wt{FxJ;d^h{{r~OoRCM zTL*#|5tR$U^@*Wvk?#ME! zI{pY9vT`hO@Q{=ce%0dBG%9}PdakB{(2TQb!V|6{1O!yX94bx7 z6c#jVQ6G7od;tO#>wMZDmgmmvTJ8c#op-H@su`VI$Y!Pg6^+Ob*V0i zlOJFMK6DVR!X{wvqdrs**AZcSi0G@d*|WdUv(|aXra}Sj>V2pI>maPZw(h;^!npGe zGPHg*YqV2RMEmD+19IpS=ZC%nr2eC5IDuFaV}T9QioS*?ZO{LHKq{#LOyqM_qJco#MdjI&>keWMIeYgUGdApTFJ0hr^{1CSpgC}^E?tWdVG;g&g4>i0QJ+c|2|KEl8pe(I zFmoBbb`Fsy?{!$RN~1$%cz>bp_GDoy&A9Fi#N_@4&nA~tv*Z&aWZzp=-*+?Udc~IR zh3J$B$zj5cFi;$mB0&_ko?M3U4krTZ`$FVZ!aN8u#e;UTQ7V)c%5pZV<+A*EO9uOf zOV8vQ(yFc{xzs_oke%j0^%=aQCb$rcz}jEf>PIssCpo8c;DgGV5+9z8W>C7b5#cdN zJ?EtoNl4`p%j#dTOS7M!(B3rbmayuzI6Wi4UT<9ASD-#FDR|mtB>MJuv)_T&bUO{JRDMs0HpW8gPQr_v5RG=^{*HGX%?Avj2q4^?Vm8_dM0|DVR)|$Mr?c; zJzetLvnKdWuJOu0iu^9+o~RWs)12yFKknULmnhAuO%iG1Y&pehVQ-{cJjY=wcOvq zQXN|>I`be1T#@^!%*I5pIzl*b|H5pz{44z2aU4mB)wF1Im6y21aOV==8|-?07=sPe zp`um=HS>gS%-T%{E80CTrQHFb0XVhi6Z2isY+W)Ef_})Tf%$v7sn)3j!w9ub3@{MOk5!8XDy$IU76p0mpmaU;W(Z^A59=o@El>tblF0j*By^ z_CMjR9x?5XetN%2HufWEb-to7hw$kIkrkauS~1#Q(CxJ{8g=Z&wEDM&^2FGM)%!W2 z@bY0V`2{TmEMpKdDmMB^3bl$EdZZs$?ubJeNsGD3Urt}xdFK*qMtxaRt#)=}YR+w( z39-J!?&=t6j4<~jop5-@2^O9-Es;i~YlNZ+{L&o4Myy-7tWkt6j* zua~g0Pgbc>mEQ|A!QQU}BuI6u$5)evc(Pyjr%*4*ScJv4?=n)4vW7@d2dp)@JDgP~ zjkXBU_X9g$k7KgotETpfCfrMfdQxW{Jg=5gzgpSfO=~$+Pj2yD_b`tz-aBKw7V~Rv z{jT2e<%dk4J0?qY78js%B#}p#9XpkEmSjgJ+K&FYD)oCoU1ZsR$2#R3+Yg{Nc1kQX zJUQ!ecT#<~M%i2CZ~tpN*J(ix3mJqcRSi;%auy`t-YTTkcwH}ykiCX$P}&$#d7S7| z#WEMcQa=hbdT7k}R7#bt!Q#`*#ShxYQh2{nNKefVi%LRgv9&bRwBCe8@(XD$0i66U zJ`k2X@atWl#XAhr*o^$DkNI#*QzEnsc!n4%;j6t10F8b32`3hbNI~JDDLXFd!U=0+ zDOwlcPDG2FuLjgPoq39OsTd|;(>!Has0lcCDFv~jhgd;;Ryck$>YRq^LSRaBT-)PlRp#k<<6!{$&$mwBNcu z`erJ5-ho5^pr(2r(Q7qo3!Md*;e+c}NI7AnpPc0V?cJS?{n;suyA~#4d=KYk{&bW6 z{p%?a9h;W%5M@tX1KRO-N$&mIg(*m2y`RZbQqoV2*)YT1GC__t6d|iCE8)*Fe5;8( zP$kuS&qE|Qes3H$KdKAX%0TJah|z1+H4PS~&(m*3#N@dbqISFHK^)hoWs`; zPr`}L6ecEN{24b#82Gz10+_WF8cTgHff-wxMm(c4XK&g>TprEeRM zPMCeKYn~0x9ox9=h((juD;BOD6abWNs3Fu6lI+SvFvlDeUL6Tf8v5T&E;SfS`n3`# zgSQ>)jQsQIgL-R}m;?b?9c;_0OTT?Fj?*!oL$e zL@2*HO<4Nc9~S_U*k1^i8$LmdecSHim<28+plyW~@Q%r|0q}=KAb27BkLLI7pdLEw z4BABjaOgloAoo1*(dj3a%P+5YN!qMg6W(G|pc)36XMt#0r-dlT50#7J&?x#ut74NF zSs1tQv0+4-LBA~|Roq#u`?R?DA|kyoK#i8Esj?(~oZw+w5!J!NkJ~}2f-n1WKi+jr zSUxxPxZ}22{gzPUwT}Z*F`0qT-%`=V_di4Bdd^RBLXh#f!x1^33{$AXUw}En&!3!( zonh5YZTV}T=UAr~x=I)PuCgtieHQ*HRzPxd<5)uW# z-cVbn5A|>;j`-bp=_D6Sjj4gdicCfSKwQ(&3xUrP&C~4XuoV)3|`> z(Ngnc8>-fJ)h%M?OC9|c45U@ajUQGB=u4kD$9=1&b+F_a?VsU{L+W+bQNEzbMZK}v zp&-$OlNUenzSWW*S)^xQJS=tk)`&vbI|`&p4Ha}DSSbko{$x-?l*>VZdV-xu)Cnyi zK6dl)qWG&tYuh7*5OKAw%zCmcaa-3dy}%y&Hg)&f%39YehoQlOj1MbSro zcWOx0SbNVX-pWp5`!Vf>141lRyxZ+R9_fOiM zg~9U!$W>0xAj3~mRP$pKC4L`fosezLV(Yd z_fnm;{}~W_`Q+v;fC^q=9Hjtf`ExCC1O5rmni1;hQ$KgN936)LSTFa7XgwP&QgpTL-FR7B_{Qb`7 zwb8{H#JRz^4BWToy6^|_&yAlPWj67Zen4W61SlBx_%%fv=l0ZujJ&OmT~%JQtuDs`FmRVixS}#9nS%Op-}LKysHBwTqZEAopwl zPr_us(qXUGy$For99f(@r-)&7ZYlP^gPiMZP!3bG`vhq3$D5YV`;x7xOgq`SqN{)I zO?NlXrEP8p&8>)ed5z5NIB)L~&@yci)iUVe?Lc*w*Pub&V<@$eBPRGxA5zhYN+&Hv zNrVw>gGR}d3!L7SK{{g(wwbu;03H76_GE9vQW0P~=Ivz9NUId$XhhFD@*O*QWMWjz z@ULC;dtn(>746Nj)HA&a%bq0VEA9s$de`|Kh!KBQTAPsuQc8w93-SO`5Erqkk>rCS zH?%_syvtwyXYA*GNlc(?8E^HYs}5c9zSi9TPT@?==*w+;hTCR&W4g?gvEo>$Y#Ms0$Kyj~-=}VaqeaDRR|BDvaMZr%R_wN*vtt$r<=pnE6M1^H zTqe>0?BafMRiD{?VC4|Al)z-Yu8quBE1rh%E{sgeCA|E0Mf_^A$F_6r^iTVvHnM&_ zO(o9mt^aJg0q%XDH&L16}WrQMUuReRstOe)lQq=JqKy z)QAyy^>@_=DY7(`3{vqg@2K5n zd8J4v*#6Kj^oX6aYGqzMb#~DA}I5YVSh0o`Y5(kJbhl{~|YBDQ80fvTvmB z*C|chq(9VBw}$M-`te_h^6z~+D=9pyD6u?na{>+LpQ?RLw6h9*$W(fI^2$K{M>ASL zU=f>2kw-GcfF&YU7uwA;w@)Rz<97Tvrw&qK0P5zN&&HN@-pjW)^3=fjYoumh=r;9G z#&noauY)Srs2~+mZ0D%p$i5cw(G^X|&0QqUwU{T(=cnN_hzwQX^t@oc zy7#ApBZIXx&m;3j?2|MQrI3+2Wm_+t%~D;|z)qi^d8jwKU{;?wwh>l&>^NoG6IB#QnQU!R|H+auQ^HL^Vq#^Y4F5dv| z*CeUogogDn(V~h}WZQlJKj>dQig(ms6WeTbPPQ3d*PN~#ELfjMM`sdOEWdvCEG52J zX#_58FPI=S{(z$6IFQqJwYp5CjVCo*)I%G^W&3(XhIcjPC;mD!$ zmID6MY(ao4s#tA%TkPtY4FT9Qxvyc5pJMEtcz1PDaI24ZjaodpS2*d4;0zMbi}XQP z(0I1ML{NhYhkrS!hA&p>w=8|#ld)tz7$-rpkaLidXN*?ay$^Rb;0+8Nn+Yh+X*b}{ z&_0bk>{+?C@6}+z7Vk_m*T7I2#Kd-Gq(d1dk=`zjEkB#eJ@hbrE=bTj8uc1*S}8yI zQ+66E1&EC`(;Cd%kbV2S!X={^gwU`9SP<7&X8H;|Aj@MnDG8X4q~Yx7DCm{46r*-c z+UtieCNT%SYV3T^TS?)(M3$9C5Bg#zyR&EI(_Wje;iat6XLg_yRdODN`X=o=|6kJW zn&S>e>uSSCCqZf0Ed_pdhxzi#wTgd^+vpllb9ZiB`|`l_Lgr1(!HT2%KIH*?_b;5K z#;0?sNCTYU_V^#AXz6LsgEDhQR|W6N#70j_H5n{jG!W*?V=t&I+&w3V4b0=~><7~Q zRMVqaJyC;{Cj}d~y=VM9`{hCX;AGtC+fNC9M@WPn!$G2e)01NXyX!#**0Qo+f>}iq z=0~Fqge~X@e@svSAl9#A)mnWZ`B?7tkh!Zp)&9Nq?*77x6nRa1Rw{;-iYQ^k^RJNk zrwr&LjqR^$8_ywMUbejwEy=7F$#^tG&Ixwne@M!& zAZBAb!r3>Jcev9nhZ)i3^ib5$x2Es+VgtT@h8^UWW;Ym;c4}CBl2gh1?K1pYHuXCi zIh{`T;PjjNriGCXNQ6D9ALjUvJ<j$4B@o_{iMfvwh1(xvZP4 z+D(vA^O+aXeES>ASAK~x&$ybx{9jp7rzw*6a`OKp<^~*hunK;!c{$7k zf#VI`%FJRfTtPOR%*-8ri%&kTQHV!vmwI4gz~^`@F#X|S6Cptr!WBT9F;;)azY( zi}*?1??^ydid2^Naq5nA%%#rUowh%fHE$|^Xt>?tjS%uTNxxkZxI&JPZ~^m6U1W25 z;!0VKTfIcej!t7wH?*|t0ZNE$Mq}aJY}$6~ba`>4RTM=HN9UQ@mqD6#=d2;!HFtY2 zOC&TbZ}&apyD;<7!|vx2G$rA{o~sNM$8DmAuM89rF7M~NuV=oYtc@O>`yy{PnDiQB zer&Wi%t2EpEax?W6Pm+WF?IhkmODI#y*Yp_mt^6*PB&~!yU_bcb@rI9wgo$!_-`cx z+>P@gdtenTb6Z?a9JfYGJ$FN2ZA;^hu`hr;xUcn$QUONImr=Ff(yr8+x4x!ED1q2_ z3g={%RjKFl%|h-w;8pV-U-}@eoReAEGpFoyH%xhd-{6ez&q3VdgN~NCuf>49(UM3yTQ(EBlZx_gdU2PYt<^!Un>4|1Jp^P_}b6^ zAHRjHF|K|mZQH+Bcduz-(KZNyBe*V``~iYsUQU}FC`lPoz+#r=5^*~H>hd7Iqyv| z@K@~-{}nTSrOBLgR;;po9p-IhUdwqEyz(7;78u!%8nkPB8?-$M|CM0GP4`gRs6pI) z-o>)rW&}F)%`Nys#FT|)-TB$<_`%u-uC}u)PurLcAE4E1xdV10y~9jHXL<{FxjAfs zB(i*04=a4*lpwVoaR_KJ8R6)!HUOpmHNu73-_5jqU-mBYMp@Tn(A;B#RrA)HGcWo6 zE$va?qK(pEr?=tk1VW-+gPv0r)!t9>nlIS=#8bu5oI|4rK5mon7YdE<4;1(q>Yv;% zEjzmXiDKr?Z3!KZu--+aIl-NXh>E5U0r&4T1+1L>Aiw5d%j~a zuh({lAn+r^33@db0L%LvWB6lAX3v;B=yw*LDbk4{5y!5;kHDb|2Q|h3x6*rzSE+)& zVV}ra;dGXBqU#CGnNKN5%nsROD;D8v<+%Lko}rBaFY-wTi>w>F-0$|-@@=bt)A#H} zj!*t;kK9)XpV}{U1n)|q3&Z4omF~QgPjez&_;V_Qy7!r}o}%DSX84N<8~07QHe1NV zWL%ksy(RO0nRoF|yUlf%wk9gOBLRMnrbmDxt6RJCD0x;umB`YUD8tS&ook`2qN7g- zn+HbpNf4(O@f%Cu)y*%IWQkIph9?^31{OXS9|AZO?(nx#Ahr@h%w-(SQj6)F`_VNcdsXK}~ zsH?c-%^S~>H82;fPomUS)>kCuq;;jZ15)6QGBqpNG&F^-| zI&^gIB7Y=>ITQXW{<`@`Xzg`}<~=ks8ugA1q-JlG=L7R2hJLJrEwg{DpN815U&`a$uXJF&DMtE;zJqngxaX}VVK+-%Bx zcSUH!O^yeLm{9*z14*HiUXAKh2>_&5JzB5xcSU7PwA{8DUWZ748|~EV>i(|nnn-xZ zGkmsYR;ss?`!TPAc?3tk5JH0pX!UmaI!#$_iJ zu1HnH4wT`%zBPo&|BN|Dd(%6mZ%7PAgcfJJuQ!5?RI72ItF05Jk;S=QFhZw!Xto>m zv3>=?XxuJ3VrIttk+}x#>o2sMlHWhD!^?ZBIW{DH&kHtt!dM|EAOK^nsgo~t|zo|(FAzamj)YHxK`_KX}WWWo>Q zGm3d64gp z{n!c_>1EOr+2Unu2@qrtr&XpMI~mIGXhiF>^H^Srfk_D49>ePTk#Va78@z3p@4W45SN#BfblE^S;wI3h4$Bmki87eg&-<4vDAX zxe8sM)l=9XbFA$Kb4nTm-Y{(2%6J|=tB*j@mr0#?SEL-wJkn+Bq#0aMIjD$wZX?O> ze)&1c`QE5I$aF@e)cYL_`e%0VgQk)iF~4o>d`~LMK36Vs4^p>R2B0%yuke;2>8jSH z9;c!gTps|$mul%*s>WYMo~>HDRvF;5KLg|b{F99phd}9Eq5NGR7Lq)|TGwarng||| zbuVG_?;d#}0WHahG2F?5B=%d)4mxiSJ2k%SBFRA)&(thZWAgDI1ICqSWn1z^hWEB@ zmG%*N=E*BZtT$65nijcj!m_xj`!o7t)J)-_HI(4*!c}z%3&Eia1$3Wq2Xm9VYEO5` z?0&aA5)@Gh?#TX=?rwY-n&MRq+4$JdG5y<)+u<*kQt=Cf7}nGPKl{Tpm5j%C#e_#S zp0xtMvo;MnYqIJf^j$LY0(^Jsa{AC!aQ^nj{(Vo%j!Tu%B@)wuWmZQ;irTTt9tfR# zXWPu%wJn%LfKLbYy)jjy10nMm0VTNq)?BGx&P$ENS9=7!G9TEoCQ8y)tf4T`LGgP- zi`_BUzrN=6VmDfmq<_d59pIC61l>w7?z8r_Jm@_n=f=!6`MIPc#HE0-HZy#<+(>Eo zrxyQW=8Il^e3sp#ukZX-<2{hlmhS?V|M6MuUdb~CJX5-HngEeQaA^^!;Ps6B4PMPiH(Z$EA`;Tuq=aZ`>EKEkTZ9#MRkWZQq zJEJTIwUUG&(z$M!Yc#`0DFL;*m#a;u?u-du=oCkG)f^cqTg2Uz<2>zhf2#d8WHd~U z(8kgKQoi-O(KyCGwp$T!_oU%tXZzF9p_G*ul$i$xHX|DMiW(2xo|F&ubUDOXe*&=X zIA!MO3XD%zER_MrKKEsH$}IY~e4<3J|F%M`yCc>yRE!u<(1YRiXg-3`<%JA+VI0p0 zTL8{XfVzhoUHh5J>o%>{PQc`-xhqv`!|;n+D2=&|wR=ACdhEnKxBo#dQdg`h%!{tk z*Wq&eKxP9gBaKij{CnZ0#r$(U5P!}Ulv=I7%JnB`H~g|0@(b2cTTomuepfRv=K(qm zv6$BjU%FBaS!1Rwl#c6(G4PVe7u9=)!{(77ct4SC9R{XKL|a7V#~iE)oZsk70S6E3 z);HA6juV-u)Xu)q`*T?{E_D9vb2@c%O`ms6t5K7I{mK}02lj+0fKofJSQ?3Ugo*O( zy#oo<%mStkb^{~cLdJ-9yiv3FXo!23$eTP(lK&F-X+dhVv#@pybe19a^WP~c)pJ|` zD?x#Ta+YVM=C?lGG2dC;dzn4ify&$?iTh zU*D-kEz2GVes?YJOaaxujhZ<6f#f;XZMDxj{%`S2>T*K^d~X>}(PG+G1}fyQ**_jJ zmmfcg%JM+Oig=-^u7=yg7d`iXRwC>!P=#?u#?iN4uwNI0abv94I|S#C|Ker91*<58 z8fFKh--p-f!+R*)z0+q0RL@>%9l!a$K$F65=j-ahh*Zt{KJ-5VHCvE+^}k}Kc32D^ z%5IB$k5;GsmVHKTeok-xtXZw`?o5Ova>?4q-+q#77HVz(N|!yQb&-{diY>K91iKYh(iT3)(n>(PiF;fNp^-3(a@<`{np8>~ zHMPPZDWaaVk=ZyI+!J4;-}n@=T@g?k6ME##?nCGxwVhnC$DmPfg{#p#{kpGn#Ewq# zNJ_j;3r7NdlHr*xvtl7r@4Oc%ZgME4#2voU%W`io%2n&SO=XiPtxc zlb43JEX+%UKgoNNvz3p>1ISQwU*8Ip?OQw+pCpnX+DQ810u$EHrR|E^Zmvb1=-k_>werD#jcG>8a1c$U~^l2EWIWL>IT zQrh1Xy#>dz91+FFSZ4DbO-qvGQ>zz(_06MmcU=O=Z_UWuM7q4*QFd|2R$hi5R#qad z3pi>STx( z<#6ZQp9%6pmiBGVpM!>|{i7;Xc+%T&b6P<@uD*-FG(&Wxrvj)CQ%RDn!|=@2>z*&U z&q>=^Hs@GFR-7Be+eL8P5~1sF_Ps^JMRphxr=NVi-k#zZ z35@^nQ0z{vvAkzxL}^Hb++o)_(S)wg&wL8D@ux~?N*me5z=^IhhU6+n^kPDWsGUyD z;%de7wfK_>P}cshjb;zu?k~cRRtv+JHgF`@>?vQ5NrCO#NAH?V!|VJ7f9%^Ti27cDYprf} zk9F{GpyfUa<;b;HrhDRLIDaJsZHrKK(@yyTHWv$hzN;}2mhu(&Cwa|IduaA#MG-pqa-8w)-Z?vy8_{Q_UQUs-0g;x@foG$SZc~x*ERPC8Y{e ziNB>?R&0XZu-MM0{XKR|W6Biy;KIasD13${hW+<7QsF_k3b_cxDFJbAdWw-NM0(}n z4S(H_Ri4$`Ay+scH~eqw9E(JZ^H^-iudLQbUFwHHDNh}?A6lfE3KfC#!deu6QDM6P z`hW!i&J%D7B0nlmym#H=8!=U>+M|`GYYoVggqV3fm6-U!-Jrd zj4Bimp?bWyrZ9uINWT_ayi!!ny!HJDqs-q2%?MlzHs6QdI9x$^7nMppZ-aWJ;4~$E z^yS8}pf9z-8`BTKJMXPAS1Uwk_slcK4}%mpN63@LC@AiS`^)$wOBbzAHS$b!|6>K= zL2|`Xb<7G8U`*-c`)^6->%jfkO`>;(j0j`QhZ42$-$DFWd(aWPbZR5)u$oSml#JOj z%Z==qj@1?tpbxC?inqC_4T}}u$XYb_t@2{jdUu(UM?QE74?gkP_`;~Nwyo*NzSY}a zGdpbkGG!df=^dT?d010c(_=YFc{soF&wBH|*r$qs3(ur0p8W6zM=$eiqC@o=7Yf$^b0m-^5Ki0ixp#DDXtl21oBXJ`o)!NVGXEs0N?lslC~UbOv(ZTBn9JW%M%i{V)uiTN z=`_~4rRAh1vy-0RG7a-!p4u9n(JD_9aq&6 zXv(>86-(~yO6LpKD5qKNM%Oe+wBwz{sTOiD+~d zu-ef|3=?vTFm3#Q)zB?i%iYl$iVui8Ewp{g0pdn|#~U+cPc0K}@bI6QS0>R!0)*E{ z6+V;!FsDB0B}Vj(O&w--eUY)P*XQo8@^TteE3mIW=-E6LuKbZxJ#B+BWPW)#)yu*mDYC6f$Jg} z{6!bRKK$~v*yZM8@dN^52l)djm{~1~68t?V&k7SEvHKzLU0Y?9MG!0!1lDc)7%cfW zomb|EsYRlwP^s<&&C<+avC}P{d44bP(|1VTkb`3G$KEQou3S((CCl zTlU{c7(N$2?z!IeNQF>yVG|M}Vaon)uU^rIa1oo$qE|Ez5_v&Z`G@N zvZ||ObAK>O^sWBH0$I61M<={ZUD%>nZ#!ED-PnC?jq>q_Ym|H9S!%~+crcUBOcv|P zgET21FPmX)dDe2~yYv~DA8m_5y*8I+O9pH|jeozDX7x|2o_w1c?o!b2=g+}^L%_WZTM zYAQ$)rbH5l9xL9)N+4}xF*6W6pV$NBq(J^Dfd1l;GCEEebYNr5YwAtRale*I9lU6*%mSs56u;}*eU zb6Z#G>M)(v%bbS^)FE(QiX~6Yr+BpkCt2*#xn5#fa+BcP+TCk&O=#o;7}0amRn_FM z$-m$;-KZ?J#d14A2jf5j6?|Yi6;OPL)&7#|k9w7^D0i*ciuD+OI$;)*rYG7~I*?Hy zr0I{w}QMSR302--rfDh-Lpt6ty;jS$cQ=E zHiI;9BJb(7h;E!MnU#~qL4=tUIE|zy^~qE({}Jy6C~}Xx3Mp$}p`Lh0CUWzPrz}+2 zRSc#fv}L41&B!us+jnmDCjQ@+yk3UjlnIsDAH(7oh~Qo1^=qCxgXxN|4}l2`|HXt#z~uz812!l)ek7 zp;u(s+kc}gK31979JZ^ufvO&<^|EkQ_cz?09qdz8ezE$e!Z%TsBKzk#+}T1CrwHW7&0!P1qc#2Z^P_joc_g@ zr&x)$rF|1l#?f0hL$`!a0f_S>1CCV?`TJx%rlD17=d$8T!o*UC8Cm@$x)65DoIV%x zKdo)U+_x{9sCbg6wi(IaFdsCxJ|#(!RL|goGS=BNAcdg5Rbx>24IOd?3{O%yH5c_` z)Kls3w7*^a{DFM*PrKzmEs-uj>pvAPKogl*dT)SQGqYti!>Fp4YElf&9tnnpjAp=x z9Msd9f&E*mf)}(FY2Lr5I5v_$Z-w`G)oMJFkq=9j%_h-(^RMC*qz82)SiKUd$QFdDOk*dZ<8cRBtl&&%$oY()U5m)1`U6z?Wtgu zrT73t6Ftsz0jxs5pMJm^@RPm$xF9UnlLJ>G@h;r&=*RNAkHeS7tB=uJmt@E2vQvcX z9KLrrhLAGBZTC;0ajENTWARW*jZ@ndtRa7bW;;6i)jrjLJEgmd?t`J{T}8=KuBch0^{n!y zGyshE^IptCgJEWXf>nfam`PojKtLA?7N8FIl^zKRlqU&Rf+<(Qlsp9^)uQYw4zp^r zWE|qqL-6w8+9SsHD{rREYQuL${lPLm$_Jy#vTE z%*gB|HvC*9y)j18vy2rRKCj#u%#D0W-9>k*Y{oosFzNK#V5A$$G`~DZ&dY*^i8=) z$^I^3?}4bu-n}}}w=P0U;sT28Hj3YlY_S889c^uAdHiZoWju%&?8$?j)M^S?6}6J% zCWiWfppg5!!p;!{Y?P2)@}8{;V@;-LI@SmoM3P{`j(HnfqA6gckfyD~wy*?C)s&yQ zb)uzw+8Wn9{F4G%6(E4+=&slr`g!V+(G0!FV_E-JKIn6jJm6Zq@Nmta%{B9ILt6XM zKEKMcau$2(Bf4E^xV(nw_Y$z8h1L2SR7tgau)V%<7S~a<(yCvj-Unk$hNt#bonsHl z4vGJ=u{hY$a)~~hS_%2i)Gh^2C$(?7w5WkG#A*;cd=2$kfnc{W!5$`|x40q&C*fDo z1bi3!@Vr}SU3#ctO>b6pZ{e8F=YZw;w$8b`DRF0X=GXwD-j0zE>SH*keD)QZ_{sr* zy@oH;B4p#obkf)LlVbO0t~SGaB|zx9v|usR3L)lJOIk(NGO=fATsTZ=5Y_0+62Z`R zq@@Bp0gtWvt*Gs4M#6K?gcf8s5svwt2JJrV%s{6I=BIp*3%#(W<|hVeTIv|OR&`qo z6(0>+BgAme zS=Wy#+Ns3m8Uym6uD|l5miAUR2?~`q%=Hv0BaOEn?y0^H-;K>g!239 zik{O!)y?@LAkZ<_jd4N+hi>P+I_&={qo)a|uAGV>TETtoLZ8K-NF*RepQ`#3>^ZX~ z)is@9b<$GCR-s`fb3PkAn|}axQFI^r=dqi~;ICDvZ|KUWp3|3y2Mk-LhwWx2o(y|$ z=?+J;Wae6rbqssG9|U)c`J@IJX7%ry19m(<;iEkMV<71Ix7zyGt{%@-hbj>3alaz~ zQ(N@Y`uP3+lT%5@Nl7yMiBaE`^dw=%+urv~pDHM&*zt*Wyzc?L`k|Pm#U|m0>pI9_hT340mY*Neqb*+N$Z9P?%|6DONB;b^wz?`T zuYY?8$AV|>N_wi`cNmE_ee5(yf}Ufm4zXdhC|)3ks7c<)DAf0uQFkUCUiY*#cP=8ZMae0WCUfueqg3&Vmq zt%{106Rf|t5Zb#+c7$zfQOuBN+2x}RHJCW{1V6O&-TafyKj6zDsFC-aOjpGE8mH^g z@AA91{zuEn6(=_JTeASUvPuiZd4JBsTQVL9*or=|ukJTd@jND;YET0!L#>BfEU#hq zSq||$5BVPJyVDc@>4ST5%)6L(Umjg9wgsA4f!})SEX3tQ$a4W>Cp|#1vmt*vf3K>? zLjQD5`oos8M3AYsi)}QTsE59eX}*P@qp6l|foWu`Yt=vTzhJ_tJ8U(3g#YDQ^Sepc zJJ;m$JB_pIQK|pPhfI{GfgfNuMH8A}E{C>Nw{--s^#=A3J&qd8| zm%FgH-Toboy^EfPr;F@Ntw8M$B9sgulpYBdm}mtFkk4MIiif8nG>Ie&-PO5IcnKSu zAtNhdio5PPaq5Vr<1fLWvUQ>?v_C=+^ItW!4cH=WbYN{C+D3RF>b~NzK2?o-b7DFp)#os||n4 zHaH)Vxu^duIF{`MpnSc#YR6*B9W$MJY+kLeHlZ>`k~BebgJ9m+UPmuGnjCbCIVxIX zo43erGjKoStCVI-`=dnG;jcu` zJ=+$KKrJK-g7c|6?jt{M2mMocCFyASf6aDE`V$tm4@)^78!aBoHb4GO^Mp6fxk8U9 zibC{k0tLrN^b#y$*KlSC`q??aqmHC5U1tI9_~LxUEIEs^(UunCzdVskn}c`hbfD)v;F#jv?8hD6u0KgljWa>wn^Qb#E zVt;XYG1zwK>e~&(PDODXX7`x=UI54*f)A70`9Kg1zC^y+GP&X-30;lHo)%5}@j~^Z zFh(*|U#zgql4H2@Uu!7;o^yt;D*M);)w7KtoA_GZSf8R}vi)}MH59zPh<9i$_ASbe ze@O_6)c`GT?&yS%t>axu_a5&r{S@LeubO-M6mptl>(_C4*w}Ba!e`n)4k=OPQdol| z-6D^yr!Qas>Wq@~@y_Nv_&=1rS5#Bo8a4`oprW9n)BvIaAJU`p@Fh~zA^n@OY^iBw&L+B)wpKqV>pRwKN>ST;07rB{h&F6i}eCL{} zi4cx>)CtMOO7Dfidq6lw#INPCOhzS1_@5*_K$W_%*c8IUgE2diamoK?03^r}IRMoC zXaVr`i2m69c!d3eEaWceb1d74$;BL)I+m zMSA?xVyfePXS(R}Q9Li8fvwSa6Ei}$OaoV9*J;ZG!WCK&RBKy1ok4ySGfuyKw-qa=fu~q^NS~_{=q3VZZQRaeZ~* zDU0}uLBKqI*Ch$@4+>R?u6}KnG4&{0UZEO z#@-pz`5K1jOkZbaQ6cBey_o>c!#=ExV%mpM^mr}6i!;Bp?b{_>DWD8p0tJ-wy{Xq> zr(sT$XO5Q;og3RXRp5J~ooI?aU#tZ0TXK8bmcg2hK?h|1nCWS>x~GCeHb*pvO~SYp zKTA)*+M!eK_A@dhJy=o^!k?3!HVk^yeioebT1h9(!v{Yf`rqIBK;v@?ejo08<=nr` zL)k6*m>|P*t<g8P3j;#3Ds8`_F+eVZ)1|*x)zL-_mhcB`#)UQZsJhpGEbzYW z5Gzi(q4-gj=C^#4GfH{6BpmghbprMT-OT5+Cwv5MIZxvm@Mpt8{x=)XiPzCLaQRB+ z=&S-;u_*|Y@oo<}2jT_59r-37qdmGp(F!UuN53QNUQ8#$h9eYpz7rHByjEpksEyWg z8$;@%P@iazwq*%p`ZvaYI^$-?BDDY7b4m2#*cs)ECBpe_0E(bm`w2+imk>Y3Y!S1T zTe3nTva*499`bto?hYT5X@`eTZ{Gsf2H^M<3GN8%K%7#t`bW#Wr#aE&J7v?ESF&*wK?&)_4CgT_2?yoRTX{Hzr5AT%9Jk z#~XQnNV_+2ZXP!bFJ0W|s7qC71uAk~mmK<-;}LT%!K!@{=I%RQahQLMmgZY|vficI zie;tpgIjn`_;Y6Xe3eRAkh?sR9ClIR5|EteabeOL*DC(|4tMLL(XKGP(naj zi9}daIu#!U@5=-T8Q>wu@{rcswk< zy(&VyCGys*3hK)f_=&MXmIX@CYE7z%vF5EA`+^;P@NC>wf=&+?c*!_@b^<09uG1Q# zxLZv_@Jb>jQGr8%Y#{VlL8aK2Fa49@e!((Al2{?IAlcA0uOm}%}Qcq=oHsyt9#8{eHAeM1-XiWJR6V-Lw@ zxj+BUVzb-CC`sgdFvRY3_1wc$Y|$>^>Iq5&k4;@NF=^|LecOPL8!A>jD0Fi0GVvn3 zK?F1d4?&pPC_-~}x@>63=*+La=w{!YI0VmRY163`y4a_+J2PJe`q?6apKpmKu-EMS07vND+qe)%iy!j|8j7z~A+RZn@aBaq=;0Wdj&+|IQ>}E= z2v9c(Bt&vUKOspurb}pS!8RydXCOvJ_Q*kFt(IoD+Z%>3h8^^VeQU_8KQ^CbK5bBw zLn!F5MuoDzW+Ys_&oxm+W|+Mch*e5HB1pSLc-;5%*G8OY*s31$!e*y$YsG*_?s~Y5 z+`a8wY(DHweZIfKl*>lZwmr;*b0h#Waq!LYk@o;Adi@x)e((TXnS{@n(%Gjf-~?nh zQmBYv9YA>T;^uJ{!7@;B-(vsRfK%38{Bl%zI-32)V;{*JnVU{FyT<)c6Eupy0Li5I zJzy(#^h^+w>@1KlV6Hcl-*Mf5nb%xUJE#M+-=nCYLWyW*%ph>x2|0fUeb-*lX~$W@p}Wv(R9Rpb6tc*aZod~-kx!Q;seSi&=TS#~ zKkLxBWY*CD1mWT(?1KD`!?MPGY{@IR^~Czr;#e$1d{GAfU~Y+VU7pg8L&7+W{krKW zHza2I7*wYYqNS}(4hI8h*+s8w^U^_g^(0V!1>v7c_Z!y!uj5L=H^59Gha4&7D12on zx?IO@24iDh$jE{o!0KHeE4Rch$D*Jl`J3u?P`JS{5l@x^i$k}f>8LkcmDZ@wah?E6 zt;3M+Sv`VIYf|M~DIqTZ^ng7^^l<`k&T?5uSUFAGV~a6;XAIC*(C_f-xAv&$hoq@d zEDHwh)6-gahb7!4Mf>s^mbMu$M90D2MJ9rH0(~hUGX#=wEj)QGst6&rvNQVe5tMiC zc{+sXfJ>&1C5`!h;bP)j+i@669K={;#pn|pkO|njLU@lGGnmV*r-=Ojc7OlVUPupC zV>iuh0c=HMN!UIFB6=6_I@{Z(f{5{E@^{gI_*y>SNLBZlS>CFJhN=4B zt#D>nUr^k4j}mgtENo`Q`%t!zW)y9C!%xJOyX>e>r#a@LH0kn$msnH6y3iwu^y7n3 zRgq8^*~kJX?iu=XMPjM*`b*;4vz(o9cr07ySb0Rzpja93%ZTWYU7-^IGmbo;Zx={` zWw#`#Ba_#l)d7eH00LgT7CWe(Z+cO?ws62Fdj#598$}9;PI1vYHYI408;82AdiJlL z)L2&jy|+Zz_A~1AXZC*k?v(hMe5d@o34N^t2wtnPt{AQS{nQR>>R=(Z?%z@XZtauC zy*CrxZoG#wO#^lb;08c|NkRPa{i%3FJ%9p>iW*ii;gZ*?>eJpS5^9amKK(pkmcazm zSKz!fC!7&l(tj?C^&*DdN_0Mg5v7gnR@~C}FCj}NDlYtZHt;DUtICCh$)@)z#zgzd746cH7 zsP5QuEem%K-B02=7oX{YTzk90_lLuuARUutqU< z?`$UoMc8GhlA}VqP8L0&qDRZMzX#?A@Q|f}Td1WH$*ZD;QSXnj#l@W9eAIodstav=UH#y2|JxE-~lc z77~3p5Vdl`;bWz|-v?6N6{xlbLrGB&uNfyrb_;&FYn0t`+jC*(>saEpLX2v zwGL-S=#~6B4U!(jYDFUEaZ0YXD%G*leF!|!WDLj>L>f42`V5g)yCS=Nw;Dbw`s4*7 zKqM=`e$Mh%IWE>}q8hng594RndVw^MvBtLM-W@^Nc9~{!ml}#rjsywazbb@H7cA0% z=WRw2RfNvJn~VzmcK<^Pld&h0CB=|s_Z*+H9FrEdFrdbl(%6VUTrD7ILjCvQV%a2% zD6N+6+Ll;ZNiXXPyG#{mT8U0;J_bs8Qwww@`~^YkFcR^`3nJh>9HD1Y{J`TL zB7m)W(NbfHUA#XiR?Ab_(}{M1dHEl0`4^zN$ssCRt!P(EeFZt2 z@|@}Xq1LveS(6NIm_8S$lG_~HZ^H!+v&`ZK97|30>(eSBn%{(!HFHX2fu9gqd7Lbr zUHh}9Xz+|~v4ebyTDs#y)J!-8ra{H_rjt7S|=9+(zJ!qNKvjPxe+QBcIlPjI^0b5WD#N_)4 zj=^=FcWcX0bje=#kE093b^bFFbalY;h{G~g&(g|lvFsB;z2JHm{LTB(2KzHHJFMR# zaaNia`&Ng3i3w;cKc7;2Z?aKX$!c#F8vl7%jIe3X){$+%hQ{hm|HddW~Sw=hI9L)Y} zX0n27GHHh_q1|3%;yvPE=RpuWrRDALyCk8*&wyfFaUhzO)dy#_d{o`JTE!nYmb(Ezdlah_jgz3Mksll|9 zW~KCjOGKWjLl@i=jpE#`+?3HCY%E18U9vY?a8mnZY~Or-7~fPqw>8@#$NO4!Zlgc? zPN0TR7-Tu?2$&@~TBg4L9+d@gz_R?KX^_sYzWa?b;A21Nd9#Mu-C$Q@C4Hp&1wQUO!7fC zRTvNHbujzku0*gGvdtcL|D-y~YzsJCX!Z{oNZgWJ1fopyO~C|PrTx64k~-@D&;_V7 zXr(j%AJky3yrn)@xR-=W2QyZICBJUV)Q5o>m2x<;`*gXe(3=p^+#Iu80*Qm}bsz`o z^or`15hQuTxg0)uts3-6k0B3=s>Y|Hl?{go>`lz;%x<L(4i8lK zXp7j(zLjS$i>oUNPJlgcAddXbO5d#(WB%}E1k6!_@#GnEHQQ4G$al#kO|-YoE^Cp8SP`ZCZ!&x%9UF-@rT%UiK*=OmzXn{yQkV?ami5lt_o^o( z921R$xWOj*Sq~3#26Ek3546aDamD4WKjrwzz9N5I3ivq1bB|V=hcC<4e_JY?q%^Dk zGM%8a%VRzfjI56r}#P~Z`skUZpF?Rl1FOsz!_~?Xcm|&5VmpSBifm+ZoAKVz0 z-zq3n88pKcczeM#$jN^pZ{3K&L{&ItZ=*!%Xe4(C7S*m0^KbjtsVPd4QNU!VPDb)z zud$rlE(PAhVxlU*VT`Dc%$w%Ah(ga{XCN_+idwzQFI2{XDlLAi$}3Kv>bi1jMZP3^9cuMzs8XNkX8{f zS%PlN;UUpznZq4vl&OQ#^o^FJ5r@ScVsKv>zWvIIeztECmKMoXv}L^Uw=YV?2RT)m z><~{9^Sr+Uc7YdJGMwa@ULP*Eb!C*WYVyM6%gu|>=Vd3liqJkI(M6{49&w!-f-epn z2dXThGlDJQHKUpmK0l$cg(l+j4I~-7@73M!}oGths-hsf52`sV2w2uQ>=2%Saq78t{!jJna+=pAXE=%2RP?cf z9FhHv1~?n;l^5v*N3|5>+Ez(dEdX!O*1>31a1=F;oCDUE_Zv7Ju1c$H^*MC0Pn=!B zQM5Ov9*S0imlhs()ukNg&k@W7=PoJM$%ZH8p*%0$NVn8*W~;W`)viP$iQOBm78-(%3CgYm9454lOGdJJb^TUn~ zI=ceMe0TfV-2ALtPMUuA*M;ceX;$(lerCJuq^zcG7+uLnzRs?eVlPZ1j1`Ry#`tP*HdBm|{=FHPJvJQe6*L{u)P)PP0Q6#0P z1lUeFNJT-bmXhBot=+mSluS6IA7>f&^nPD+j}lW}4lQkLqY`sS;z=9(YtSbVlKWam zxzeH7Um+^fh`|F=>9h`2I^)Ej%*U?`X$FwVML|XfaLwzC*MNMM{-&vOHCrO< zS?^$j(+8A%kgC@j_L>}~{`^%vo^6zZe>!2wPKnUmC@4Nnpoz)l5!Z5 z=dxqpUvyz}Q&;WtQ+T8;tK<9nA(EA^Jvl?D8m$Cj`t!;7)%N42%Iq0T?>#(Jy>rQ!I-0pcvo8SFH*&EjS-Gmcnwe{>RP&56({joMCao~#$$)y3}g zY?+%($^TwaM^dj<#Mv>>mUND&* zM-{ZFy3HACkBXto+lzeb)Z7{+SX3>4aO>1Nvy82}(Jq1rk=!vH<|-G>kAzJwW8nLx z8fzV^(wUWk_fXkDz2ov);pv=K(IJHQd*xy#&Y8PA(exqkZ&#Qz&I?H-I;KSKw7hlP zKED8LV#<)mn$9(PnS9&6ikG$IR2|=K?m-VSPBei7@2C6kY1UheEW-d~(OpRth074v zl81$e67oS+)+zPLLiIL((?jP&)1sd4zzEP;rY+1X>Bg*B?wU%C?-X3uo+^P~>8Xwn z<2w?Sb`@P)9t4}1CAEB85&{bcHor?^6!-aAgaizixBR-otYt`T9tm8KHuoTLx>Pwb z3@xCy{v`D@H?b*?i*~jCA#UGp6x5!L%C39`uF{j3Ry=UuBsT>?q_VAyKCcvMsqS*= zm}FCD{ac7)vt!W&e`)62x`jxH{g&KVfj{I>|1H(Xbjsz$KA{4;0r{_@kw3XS|8Le(Gk%L|AyhC0w}d zO&ya!(yaX4az`~7diY*PT6L#2ma|CKGecz=>LKRVbNT}kiOR{g)>%)w?Eac^uJQSQ zN`>3xaAvDTtT)9SY_J7ulzJ=nkqC2bm&QOnvy)QR_R{38Ov3x>*;b(k7h+F1#(mpm zsorj1pl+Cs5pMq0#GP4kaYQoFa(n>p5L!c|4s= zwG?BQnt5dKx%`lZ(;#D|Ll2~IbGPc8{5UyIozJ-3Pu;X8v<|IeMsqdAjbAl%C9?2j z%zM_u`%rOOH1A028CV6*RSmD2Yjf^I%T9fq;b70qoqnb`EP^tH>DNGvy#Hci5l(}9 zM?+&GMayi}g}9RL$NDeRAd=QtAOp5_fkE2>m3_QZ6u`DM=%#`Eq)OUIICpTL1qMBs z@a?I@LINj1YjYZ_V~c)(pYUb{)n^oa6S(%@<@E|>wz z-4pFB?y>NVWtvW$%VPIMfFXpK&tx3?57JnCeR1jCw>-GxQgb}eeHEaFDWOEHL}sPR zIAc@t03UYLCyxKzHQW8<1Tq_R&HNuvromv6)4hf_@*~5(J}6u#ZK{P%o6ICB_a2Pwp(}QFe?*|(YHjNyhoKC9YJ-_q zED}((;((6V#l|>-kaM4Y#XacZLM9LmqOw+*cUmN@gA#_>0SuJd-dN|V#v~STJbQz< zqAZiS*$5C5y2oWwuT&W|Y8N2mB``AKJ7S-vIsKL-kqtB-4D1lsabO-KOV(3FX6N6M ze|BaymshU-w~KTimW~}Zfq_NLysGA&%MO9|h_?)JeNueNgi%}7aiWu&qx#P~#5anP zzllvoDkWRxYfZBwjuQeCXv7^=f;rUtPy@yr9yRU;YrX7w4bn=_+W=pa{GsIaT~BwP zSytk%q&yqOlC5{qd5h>E$16EwQ&Swf6{m-W{4O4ppMyfAR42mgRe)0y@)RebXL~t1 z33krEC-B}b8dm?bmVQGTFT9sn8m;`_^hgRVtui;wFdi(A5NTYy163XO_>uwKE9Fu3 zba}pJ_`XJLR`0wbX70I_%>>LI3OJmVJ#x(a#I3G`qaEm}9x85#W_mUdqmt?mfsHsU zcU*agD8RQb%i(Ggd{t@Pg;-WbgteVz!4HFWVEp`}_J`+h@tI_M3&^G`#CN|>W%oq5 z@k{V7^eOGPWZ?lS>_~HjT~zmljQWFMX`FBkW=urglvlUa16-&fUj{sl-nD#rIF`HP zyb|!23iLD_QvR6y#&rH#9%+f)+to!T6sDAl?MK8h892i_y$SZi5}Gb;pIDukakh}wr1EjWD~g>iE)l3C~O8 z%gS)k_Ve-Q7-VBn_p0AfRY1IX%rkhuqRu+TJtCz+3x)_V)o=D+8zIWb&L zSv!k`QtLj{Nj_XYnp~bk@`a)UG+;ey`KD=B&0lO@PGLP<3#wJ-G(Es4uWJ*^jQz9b z=l%K%q#N4TnfQb#wZH$xyG>5R?$h`~OAYv7oHJV+g20q!Fv61}cZq@kk?%*_&WZo3|+Q0S=J?=pT<)3lYCq#iqw0CzXPPp))meF~NXZP{!X8da)v&Za zRYFEJ7ZB{IiAaN8sEWQqo+uBygVLHMCu1omxW;#v9hsna0yWv}m~M43{@X5eHM|Zs zSkp(2*#VzZxBfWsi)6zcksK6sUoaK#!y;3;yaA42+c{)p?*2LlOo-P6r zZNYU#(dAQ$3-?g#acT*-Nau2aicqwoU46>GNS9!t`dUyP3(}q8{LZKcA+-6K#Y*Hq z+pY6UZfCC42Qn=W&{TAj$2KHW7c~QtRM}GuWDdNz!`Ao+Ho!r=$`0{kfkX3*^0I(mnHFlH*v7J3RcU~IpkeO!puHnMMNn-upJ>pwHR`hN% z9F1}#+L=0kQpK_2`{iM^YNJ^hE6)iEx!zc|&h5|0am>-`__0$@5aVHb_|hh0R|5w3 z64RgGwKT;^3e5nr^-~Hu8Ig0H1ZEX)LdT#hz}=;VT2_)z%p0DLF z*=SP~Df3HsLpM@yg z7A1c@u-x!t4|!gMn15%~AaXR^$^@pLvZC;tZEQ5=L_B*!2}R&97m?Pv+`b@B0V!fE zcZhxXZeMe!gEJ+QI|DN13d?vi&n-GCZ%qdXG$b5M`ZXq&**cg+*%CD8L+$yKc>RbD z;W1gc-;ir#Wp~y(_2kTWB*vrj{^Z_JuU6f0@c21lfIoE{#vQh`c^7xKskK&hjja8> z|5l3A+|=`9G66mGbL5Y_$InD3{z9G!6{-DVeGFj8VW(h{iGJ&29Q90^L2qj?h;Z!z z@CiVt#)AM?ar62nnK){DhU}XlaQt9k#PnX_hgX~ND`GT!5q&z+YU?cnh<{a(|0ZrRWa;%wXX| zZcYYO%Z}B&`jV~I zX!XmoB#W0YeXgP{s1nd{Y#5J5ge%KIzE}K=#J9r_p^)_lQ#v#gE6suVS)@?h0#o*_ z9oXy{6L(7dF%8&CY>CyGKEO1}tAtjoz<9Sdzju>iA* zF4{YqJjV?RB;0Ei(J>L8Cg0=pbEOEE>zn#(uXiFN2YfrufEpX$H>qh8#4ecOc7gs2 zo|~@UR<%BMN$*vpjjH5#NUS*)3Yh-b5?#>Zc27nA7v+nh_g(?vGHKo|^WH4bjbIDj zS1a^rA$=osk8=NQAeK2teBg=uKCV0rxZNy&Tv~!&ZT8|JHyFd_i=*=iSC0rL)-c*RDWimO2w)W$p4=9!aN4j8dZSG{9h-376ns-P0DOF zTg5&|=k1vq-!7;>W8j-Yg7D!_bNcVX`Bbh@6T#77FL>!UH-FsuoTd6c*}!e|Siv;r z9YS@mh@2}QZ7NB3Oy6*mb!5HBI*467!_EEb>c&0WZKiC@`I6#~2^szZ6Py zX}SHBBah2f>mt?1`Ny`IuK(lE!|@Fh3Qo*;X?L;ID$Vo1O^9!lesigx#ln32p#whN z0HwU!xmSe=fQeHN@U1RKZBm=3w9T&%F)z|#EHr1Y7f-dHpdOpQcd(L_rlIk}MZD5h zzjBG|(%DZ}UOa#Dnudm!=HK@PhO;jdj~}))G?yMZsHwerp{920m4}0{&GRc+ z$v$uNM%lvgb#isR*Kgkv*x`D2@m9a$^PBX#5if#XoeTRQ=>6pUvm4ih-&((Epj8jQ zZT0j9Q^!9WiRTjaN?SRlPU$;#l7ywgB&FECT>M&io6u6^yqC8i zQq!KAA~^qr%dm&DRwg^D=G9cXz-&$w{TGu|ff>6Te@2_SH!sp@i2MHx3>;*>q`7}1 z4ctW%{NfxT_4Eekdc@W_nhS|7z*-OH$U~rgHjQtt-TWu|h!>W**M?Iah>LBu6mqHF zp{1lqVzbulN8gwDowCb_`|LqQZplw+1q@x%?Bmvc0oK+GhZgJ)>qn<~1N4rS+ul+2 z$%3o+>_<`AhSScgg%WjaG<$cAGI&ZcN%}NpE({NevxPbvw`jJ`)09nRZu5pYo5i=x zT^3@BTwQgB(juM2MHc0#$oFm?^0Q>Ib7LwDCz_1!u6it$y+OKq51`(JTI&={$ACnO3VlgTI7*_V%Q z>n-eEjPI0%toy&ik9?SfVyDIv59Auu3A^eL)DhC^u zlMrqgHEa3SV(5&{22-t4-@asDkhQ%k9enuyyX9bUu7OaYs`o|NYZ=jEEpN?Z%#DAi z6w{4ZtSSPSaSfbv0QUeihl!Ny0~CEF%Wzap&`R@_SwXUI?Y*fR8}C^fp_`We{FqCD zfI!~q7(Q&hQ0zq`dZ+g+{K>cOI^TVBmFoiO!tsY|O)|Q{KXg2Ahh2rWm{f{>EluDv*_^<_7JA_{q>a}u73=ErdDSe>C`=eYrrMc6QonNQwZ(95(?s+l2%gu z1auOIQ+!gu;z%Jdf1t)m?h*Q&ZsWs7z2KbRZ@)a7Eo)K^eepE?Ym((h%XG^K%aFMT zt-1oW@xk$x*LCtAF8%Ho_|DJuc;Gv%A6=$ZrZ8qRcArOG?i+O=VX1Y8Z?SIYqK$x! zG4UTFfXGEG?Rd@5bl?0y`ZeeGsD!ARsF-b;g(^G&KQ3G3V0Kz7oOF24^`3vy?IhbI z+N7ADFZJC9ZwzMrbor_C=f0z$qlM$BBXv-`AR3>C_sPa*fn`qTkHVLKS4X@Fw#nbdn6N%-)EpoZg6NL@GDCpOm)V^~P49G!ppb z7iuj}tWV|JWP$Q8s~@EXb~)OmJ*=kj;>L21DorZ&d>-p)N*G9hi*z@>sOTbpA-~nD z*Bb|RH1ysYGmm_f(fH%br!Vyu*%mpoR(F2J74+T9G0IWI%3D=HK9N-j#)7{ivR=f$ zh;G-?8~HQ!#=II|hQI8zU@s_B@Sq0w_4kCKVd1l(&%kD-Hvb%0#-Rw4a->0I7TY8n zSaeNPPi#e$&rsO#yn##E@^4D>SWCK-sTCX-=18zA?a7^7DF2l&Htk)%23Jfs{XNN6 zqg{jFW8S+>6{JcsDu?@q*D>#pczXX+^8r41ZDny5-9wHdFbeqz6%V|~o6a)q5$?It zlf!hGeUtr<7>7q6WEKtZh!eLKj{^I;fBB<7o$~tN0Yo;Tv{ealt+)zR1p?U zmN@lDTw-1Vi3`tU_A#Xh6bCA*jNiB1aNNjKQBa9ciB@67cw-9S5(gH?=Z?K8liS0a zB+4*VhhCn(@Z#d7Z}iXT-!iH&NnN~t-iyAuk+!ksJ)cBB_^!zxwsVh<)VxELE5Eqpx6kLU&^?d_;edxt3pFUA*sd*mL*59Tc8O^52=JTvwz4hs!y4F*_rzuZ0 zQw&56OXr_O8AaX7Rc7PkzUg<@+arI!e^N7?usU~%ur4lbl3cnv=21?}@5G~G!HtHH zd%WCyJkwWCBUqdAi7!z*;3^*?{cu7}I5nPx6(L2FO%H62;^F3m#Ms0fVd?z7{BDO2Wd~&gj|D6~{T4BmRu-z0XqL&juU6gR zFu1PI`t@O7+{=HUhPE}Sh?AGQ&!g^U2!t3il{%J8*{RzgJ9N9QE$qyv&%1W=v~Ral z10L8u@bR+4c%OtFH9lB(V*V96gfbbg()KZJte)M)ti-t7a(O%@ADnVnmmZ#u6{+Lr!u98bAz*0*cfIrnK!M_?j;W!ex9 zI-Km<`>nqFKWj07qM&W>sDnAXchC;!Wd%C;t*pU zQzrGJq)wyAQGGCP`P*_ZS4{ds-LhZhTGkkDe7JtvC2)W?ny!^mh4%)aHn45~mD8_B zF0B?5vxUw0m9NXy^>%^az$a_Z$#Hn~Q1$P)rWmh5#XrMxKjac+o1){f#RLe>?RaKa z&5yNU>6LU&)d9UcKaf0!JK)>MzN|C~`vm{FBy^12tLQcQWb|cdVkn~Go2-N1OTX!* zpn)u&9v(5(j8p%esJ*oG(hfUKd|c1X%nTsXCt&L_Hk2G0!5*#v@>HoVoA1v;{YFi7 z2B#F6k*hrN$Lpv^`Y8o=D4?k#JPTg87Iqvx#9Nb{3|99zr`8+-!FY>JH*|PdCb$rxe)a1M3oz~6H zsFVRhXua@B#ePy>0W!dcT4!h3-m>Y`x00r+G4%rjqzvu3kgONy2Yehha`Qe-6<}2N zf{(vYt#)v6ORG8uI!VZARUiM0M_ayvF18EP`yaNcQVNCvXP)w+`*TAm4Go*Xzi-+X zukUXD-_r zEHka~&Z>oydaMLgUnA6rBjDxEZ(@<(Y9AsP!W+UonjSE(4SV$m$+fcZ&OI-(u}@31 zf_Zpt-g247)OC|ft#F8;9@>YOzR_K##K{z%rw_2jXHUNFPJiI9aFzM4J9MFo^Ld7% zkE=1ml|zG`!AH#@9*>G(?Hi3RNbS8liCXVwe6M0N{j0SDww{kyK0ovduD|Ump>fCw zXkhPY-u@hr#vN{I$8LP(X`k2==6Psp9sR(~?ecW7)uyqy6V+lB2!4{Vhi) z(O*`_JD5dYSJ0m(GpxyaOfsz`?@K}aJ+kWZsqE8_=8So3canN|8YQkOGqs>@z79x8 z#0N!R=DL3M4E71gqhAey_q->IEt+&tzyH-N{|vTf+`U=bCs~Y3{z`70cQM z^*eyt+iO{PE&yYc`DteOww>=p`3_D0pru)BaiQU%@iGk~_|v$8N^@RXtnVs-$We#&G4!w-omrxI0K26xOA6hmlN#A2FCRU8h> z7-=9!3Eu`zbWz@*=OUygh;IYT;2EZpf#%5XGQBg4g;^F_*@d2_#{Ipt)-#$Q=M`%+ z9#41iwV=;dSsn)a*7NS^W}SoXThig$+%%UrnE9(_lR##Nc}}RR1qF0E`0tOh?k`a83OPsl?H7gRbXAKdq{|mEXEF;W zvnlCv5J?_oT9qti)jd-00STC`IHYG)wYu87Wt+)P>Q}OL%()QLRo1rl5uoNGV%iP~ zS2?VxtaF(v#jE(8Vxzg2<~`pn{Ngz5+kNZJr&@E`t@7=rwf|lzvhLN_X`Rs3z&I=2 z;EAWDR;Kf2=gM4u?=CrpvA@dgOinc8xuGYccST~?_>#nCC=;9L0Zs&ko?}Fw?Bq$@ zs3CO*>jK5QhA3h2d%zjEJvFUWT_!AuT=poh7?U8xE8?DN2z7EfA(6Q5Z z-N4(p8O{@TP|Ov(S?lTnue6;Hl9ld5aS35n;di?FKWlS9`)}`?voUj3T(s#-D4@$L z@=hLFe$3OJ!`(a|$T^c5GA3g6vGaqJL+th~x1Y@15d&4k0r})RGM}USbx|pmkLPjC zh8(-aIzd8R{esJ-MDjlD-xD2Ra}qwk%<3o)e!Nu|VOAwX$PXzJt=@WDib=nCqNw)+ zm85!f^=L`n{dMW(i=t2u9fDVyXFm{{ENZ6YR}7E}NEc%YwQYCaDXiLF=uVgRlg$lg z4C}rQkE0*$d!weAX6oBku#K6wPlKnx=3?$uMrLwl|FedIs3)_slh(yyQl*+bgQmt6 zu9M|nD??ev70#noOM@UE3}v}M@z#LfB^r5V9)L_&6EQHrY~lO~BkZ+%(axRZ5X%K_ z#_SuL4zwP*2Nz!=$o~E38o(z+u-QF@lcx$UlG-Nl-eL>7*~>IPSn)?e$NR{ddcYZ| zk&5a7_{5$yo0tEx&ZXBEM)WhPH(%lYsZA`-o0lK0$rZ2F&HjhSnUDVc6E$xQ^Vo>m zHSYOX(($-3H;`pzF^CnLS@v~dcXws@w3(!OdbsfdmI1hYt_$*bE7sEZnORcxsWj(< z|C|#o`RG)2u3_8#_IL>5%C#04?TnT>K=Sl69ZRO1TcdxKSKB{te*D;0=MmLVXLVds zC*C=oN+jIfh<7~vx}#hD`l@+`xAS<#cI)m+jL5yl9i^BGer?!$G z)KdnFczr1Gc`6y!Udlgl;rkIM(tlYxqVM#N)hy}gk{YFCD`Ur*tpJq?^eaX!_r zftJza?zWvzp=r(D;1lrJt=L=RS9pOmZGt>Z%&JG5MIYN(y(4)*QMm-U6D~oo7kywBVPNkFHg>J^xVpsv zai|qdL;g(UzE*m&zHzdCyK3W9C|Hx~a%&78?}YiTncNj;coOPcmN&~>^Q@9+Af!{K z#HzPN#}1pZ z{GlptX_{G%ifyI&Pw4z37(ASoG^B)!o{-AhLmXi6l>80i74ES)T1MWHHiGbbwWo#@?+WiGEHHLzdL!e;t^+yf z-9>^z9?@WA=iOfOX;_4QVp2P`nqNLWz2y9H+WYcK>THU|=~q}1D8SQt=$&McLLywP z^*V10yl+R$=?8!A_2SXrJ8zSyz0ans+=%Ug<44}ajyRi@I7S1V z?@ix(DVB96FopGMob5l~6`q70N7l%n#=7Ud_IQ5*!@`xy00bJgknA%BD{ggndVKP} zH{*M~!Luxi=IT{@cIaFV@Gv&-y2ESVraPXxTpc@GnZLeCL^F6oNgZnq{ER$hskOoW z>*v``HVwZFtYjbAHqm3?HUEgPcOIS%#7#45d-Ft2n)b+$i!tM}DvGZ5)UI!1Sm z?ic^>m;2N1bDitN@8oV>&k$PiYDQd=UFXWa6Sb&}F!cidw*r5b!iY^OVy~6S%2tS! zc8Y2@H$aL*^wFJ!2W_bV?BwM!3h3Eu*qiQGjLbZfrg6;I)#qDPJN+u@{j;ry7r9a2 zU*p}xxxtHzC+EWohQrJyn){2L@qeM{A(E+-nS*Qw)ybUlEETZUs|=C3T%dckUaW4j zf~P7}^LBLW%Y7yxlQH3(zHyq=FEzIHms`$lU0$~UaPf~ML;)`VezcgZM!Jh604`x@ z_G)Vu&8L?XASN+2szWPtVsT$gCGtaV8eCyU=0NK~+5QfZ`vs?7N@^5pm zQ=SF7N()4mXT@>1L75ZZ{#@T*rUM4}9#2;d-31+$3JqLw){l;`MICTM;v!MxMB~$M z6Z7w-x(GCRiniq)-X}}-JsSTd`V_zk7X^%I1AI$+uteWuptS1N%Nz=kuO_@&sra0R z+-3dOGVha_&reTZl}yGCb|`r-#`l;kWY-EF|4`s*cR1)jCboOJ|6pDt^q^h+Q#r_# z7I-GNi#BUkJAwuWkOlW_@$P1(9hT!aH&a7sM5&q7ol^Adgh=K9Jv z|4mU;p8p+WUeOfuY21U+aT&Hv(9Nk?7ZmL0x68p4tlT;I+ODu`IUYcOGEWhB@Lu*I z@26WqU~+IeRgGTfT+>r3KSr}vVa)N%M{Gjrour*I7++1NNq33ZgI}1BO1JTR03yJP5H0{Iu@=QWl7N269vqP4Lb!wr z^2a{+k6)s&DU}jemPyW%%d6y;Bo}@pRj)F!c}BV;y{v%LV+=CxTi2vLO(J0OQBh7M z=S1h!VAi_!83nsto!-3W-iz0?`prpjh_Qr*Cl2(5*tOK!x0lWEzI*Jh7=Y8o1q2Y= z@Z??ct62 z$65RqW1tTK`1PV-02JWO*gr$QxD1$>Yov&+y0>ECiqjMmyWf=5@mGGRO31XZByONO zdR)or6K^N|kI0M}VM79#F!y|HTpgGcY=FlkZt30Wi8sZuW@LZ zxuF=0_ryDzk*mpr?jcwe=S2JeYnj26bY~pUpqKpKM2zC2v+C_82=Yl9eEw57C4J$A zEX7T@*avJ;Sj;!d2>K=7g3O5BN;YV53_vCeFl)p{GHB~gM3m5zLe%71#quFvmdY7T;q`o%?nj9`PX5u!K%4e^wP_73*awBFmJe66ZIeGV2;r($WIDsK``XF+NHD zA=vq4k$y6t&1}tDA!&JP|01Vp40Q3SjiV|*xMoBhL6XTIEeX}n_=Ja?^2;^p9)Q?1 z*W-YM1C{6Qw5RuX&0-4`>V@AEqx#XtlWN zUq5*9leooR_j6mYkLbGk8~`@gl+r>!3#8ey)~a>aGQphI3OD?cS$uU(d)xkc=WS#T*eG=`w!JZd- z^Bkk*;#V*8BO%Ww%Q~7DYWzR3%E;jbQ9y;-_j9I*gM#Njh(y9(z6N~>2^&&(2r;GuMN1@q?Tz8WpysL`Ccz2naklQ z+j%)E9sFF6r}JxMY>3{ab%^~>i$rp*vt%oKaMALtdrp%@8~ldfJ5G}lpO_s50W5C; z#_0pN%2+8P)v_21>EH#o#>RM2PHt*WLNse!1Qp}Bu3N7AC3%JKtqr|mw+f%_IJi(Z z#k+#%D+*+c20gMm_DGUqQ~qf>%=3t>uj7q>qU+ehPgO+j-peE_K$^!lcd=MSE8Lmr z&HtiR@Z*C*#n=qq)OwWAG{#8BN`}OU(<6pWQ@S=k?0=F!!;<@4WDUCYK}Ij;SL8Wf z4)H82sOYZ6{rL#eOK(w^nU(JPvGSI;%#)me>Ku3O|C>U!jUFqO|%uVv$!Ihq}vY>dI{nR_lF z33je*qVsQK6fmj!=6HDIHi@{NU%A`RlDmGc@oNeQ9=K~H`B&)?{ z2PSOWtm`T5U!p*!t9e}jc~E7{CjNnh7Ppd=;ciE4S9aDSi9ECPx=_ccd&Sa{K%$iY z1uw?oXp+uHWqHV}@qGHNDLztOW#Dp7CTv8}2qhej1))U3gEdgLdM7_FHsd~~GSb+5 zP1Wj~lV7iyDr67?`zYU~In<$YdZ+6iM-$G@&(6&@8NI-(`53hedN50Fslz_=PZ*s<_s>yXjAm07n*fTt>qiF+ZlFJL1iNp6 zpB%#12=Pyf({xFcFIxJ|DKSYH%wk+04L&e$^z(I_t{9bk=D8^Kec(uOF;)(mlA(&T z6mda!cZB3vw}8@g-1&bkM<(7WJy1&Rg&7zZQ2|yh_--6onCy5l(@tn=1Y#JHWtT&3 zRvH0c=707{WC`Bwb$VA{oHs3m9ot2z?pt&^n8dOup*p!HgH2R2(hTQifPy^q_BYEm zk|RZ&Q}5-3MHXvLW^Gm4sM~L3!f>BP5ufk5WHOp{iQ3546QSz)#fkU=BxT_r8Nd87 zL<&t2uue8(vIy`m;J$UXg$$f3X^_%8lR-{xh|0~c{NnEU)+BQNCoDvME)sH8RDHJo zwtn*<2Rf9^{~B2FnHpqZz@Wd0ADC9ec(T z@idunbTyJ3m%;KBnn!`a-fQk^f;^<5OJs@jcyo}qKD)w(5bL*AYSUvpdp6=JM~vbu zDZ+2fUihCBfEyWcHTDSIvx+)&=N;rEHQ;ZJtN%;A`?3#W z!LoFTu}cHqqPwF_y5;3odPqImHQ{K;#mpu8SmWd~-JWDyl5fLVLXo)r(`Oe3pvmlE z1VHZA4vao0e8K7~oe{Y4&w4Y*)Q2|w-#6_V9qDqB#}?&Jxw~%STW3i%bOR^Iv94WX zG;!;aGzTc8>j8Maf~7arr76m)@EF76baojFMiUtefX5c`-&(RZ9~JegyQ)w_^Qvco z9EBI=r-Y;wi9CH>jhs};_dv*IaZ7R)2JzPIGuZcQBY^d9R2Hwbz~0U@vTSNcUcI05 zc=1-W4!~>!&>OsjZyI0h*tNj#?aw~2M+~@n06tPF2WNC(4=v&z^bIZG zzE9q8J28HjX^uCt_juC(`sh?{W(Ae)IVN&^QC)wTUzT8PrI7rzR~ma66)>$$QAEEW z$FV{ERfjO(FY$}9{)YSFe-VActP^5(BT-$2l-QVHm>M8p;C~usG=rgm!L)SSxU=sz zDLRBtu(d(BahQfirtcz_!|~pay!@;(k8XnbWvhtfMJcMaYE#L#cW~=k3n~$T#lPC8GM!7vFAd92cj+Mqrr1wsu^hj~l8GE1^s^yba3(HsF=Uvvm3j_mq~33bOYg}z9S zX5$liq4qZLa*)HDjrrR*a#dG7u!9(UmM0S-2V9h7{k7nq3u);u>{v*ny1!ZB?U3fE zhJ!FK^3SOx#nxXF0mc0vITMu3meU0n>AL>gc|<37xA@7{l6haEn7-8KH-I1X)GgQ! zY=x+C_-2D8zfS?knAcpW?l%*T!>#*C4+rvM;p8p2BSL2LVt~$nOwLt(e3NpZ9KOVe zV*0SS%mO;;aZXNgs{7#b_n7Tl4X6AK%F|)y%Uq0e%tVgu&O?-J5S2vaR}H#FL`_ef z#J(IIxQ8N+48IoW>I<ewmFmLv;)o^gwB9E}*O~~-bvlq^82^*-p;S}) zpmSwD4mrldW*lbu0;T0pw$lDR0uM3URlgdp_h`o6~7o!p_ATfv(`gOODkta3t$9``Qvcn0FR^CSM!!8 z<{fbocSZaBU?QvU=i<}mx%?_`)fYu4A!fYSb4f!96{Tb3&IgVY<@}08pLQ`AcS1+c zj=87d2mU9o_vb1sA^P#I)~nBbbm2##;jq0dd<}%Z9^^bLY7Z^n9;aW591JCXu4fQJ z8(G@X>@YnzIt(!cb;qp@AVhaZn%%(VWMX>;TaU+!%RIBMug-Uzolcge!c${i)vJ-l zg{R+54*OzN8%&xN=Od-2`Y$?fo#I-qcG+Y;AHyF=QNac#BoXeQZi3Rr>%@XG`8rP} z5BIE;azKExrz&WculNVe>+k}FDBuQh-<};?;^9>Xul#u3X*(AYZ4SrvDs=08t!VJW z-@0TD4Bq`Xpupi#d;f4uW>Tnkft~E_l8*;a)nkTdn@yeGo?d1P*RzIx}u_vd4~h zE4RPdTGJ2w?Ga!g{i>s-0d`OyDDDBtwd#{p-M|GaG_WL60-T9h$;S^AHmHDr9=*pcDk&MF zUA7y(9f2kST_XifrpI=W`v@Ir>TnXIBF{xsuE8{rc{cXkS61FbdEafRgZS+kWMxRq zuh!LOe`%wzt5={HB4a4Sf(Oqq!akc#_LSb>&R~KosxSDEJ11f@)@i;(X%`gJuaW}( znzgqM!T|T-Lfs1y&79QbAjpT;v#mRRUVAYqOSrP*!e0O0>J>?jv)yphoFVVxpxp`k z!gq}{O_Es?t(8l=nFF#0*~k%(?T#kk|Z>h9dL z6YL&&z(rMdW^jDsCJpv%nK(-hp=>NXzJOQKt%zhnZN~iTH<K7Wd30ZrqBZJ8b%-Ql>mdyDD_)Qe z6o?QnPpQ74qtXX{sW4$TlhgeZa9iTl5aN%~*8+RLL@x2nzyHrfJlhTK^R=|#*7jbT zJNedEtRPM`j-?uoBu0fdHWR&a>$-z0$a+^Y!&FMjkJZH{uWKCU={4;zqsqIept3VJ zSwY7nZ#6&F*CBm_W3uk>;@EBB6El+M=E~4TxHzcW^K({-N57A#0I$+bf{J?p?DF}b z6qW5LM&cg{4qnkfcFYw_2`qLtqBj2+t4aBuPxerY)b+>yUB7XEM=e}BU+6v_)NLOJ zwY99A`A;jp4=aFsD4A$&fV{y@D%3DnH^H zea}i)>Abr93GSiHW{oOZP*F#dvzRajycZ?wGO#${T^w6S0Soyvq&vUA3;I&(COxP~ zSO?suXUSTd_();?=}(JKvGzo4EL8g5v_;#L9r0|>{8yJ6J7PMN+fvMIX;Z&{7I??M zcf3F<=P3iln3F&bIXa_4tiGmg11IiGNjE_enb_f~FGo0zfxQE)4&o_b9OCFN4VEV@ z<=s{7e?aOYuZR9Gl>ybHr)Qj7U<7W3U2Njxqq^zv%q6>8H!-!q0I`BE@kqKE zDUHebjuCkLdB)nqQNoz_Z^@pg?})yS+`fNzZY?-F@79eAuF;Ozs7Gi@zGv_}*kf2E z?Cu6wj^f*YbAXNu63_ibw8I_4p~fs(a53;#URF_*BvqLhL{3w;IcIY~EJNIP*MPa& zI|mS*3~c8w{gt~p99zgRGhz1AhXB4WmrSpTl0O;?fqA~j<{gbgsJVy`A%9pI&wZ0$ zgt|kONk(25L2gj`@31;S4ZfPJmaI4VP%;xvDMB6~m&)FGCMx+5t9Wyk!{Lc?XhC$V z{6OYxb+{SKOYPn=%fqMF_%_;?68vw}oy74KR*x+zBhgwntOZNH#M*c-$yvi)b&KBs zRGuv3ObF;X?TysI2=eBcNz!P^ybfAz^=(?)AdxkBL;el3Hy zO_!AvYz#To{+h6|_b!of^C^?4KdUO#}1Y z06(;6pd_{Je0%Y`cGw$_ue&OCuZ+s7j5%10gWc=kob2QsBHR@)5L^bfACv3w=P2xF zZUQu&%$f+}H6!U=|8fN|U(yX#0g;D83nj%Y^%CW;>DJQ$uO=S7q~3n>iUZ8ed~@^u zgg*=W7B!exm9y<-@DX$5p3*(u#eUtv|0d?3)U9zrBvc$sk*i<&?do$&M=SZdI}c9Y zzstX6Tt%om)7yjB2njL*1c&$zRTj9!xmnf3)Zp!?{`gJKnYdPuqfFH$5$7>cA~FQJ zn=*tQki%37$Z7vb!_TT)MZ)jBp5MV$swU2j{Ehzjg+fzNU#Lfg$RZvQz@h-%N~Q4E zXF>Hf-(-fUTU#&e&094)uqG)RTQx$xc~%~8di;$`c|%d;L@0TxvaS7-ntlV2DQdO< z0GkLMkkSEsyl*B}7;@}x05U#?iwnM-P546?m)&aWXk3qh8uTn99hpjX2>F1)hyDcw zuJ2&#(PH@yMP?afGwQxTqCBoF{UIeg*Mo|S=>O*V=PDrB_!c5gSEbdK)wO8) z0p|2iSM=aDjQ82va#oVrDc@*i(0tOh1gh?>Aq{_qRZ=VSrvz|!kzmkAJvyk;pO?l7dOvd z;iVXo=hoK!t;LL}tw+YNJ$f^Bx&~qJu&tX?ItpgO0so0&mRZ+hS{*YRzXL;e-TZ;=SNh$&88Aiw1 z-H%Q;TvbRK`WY7`0ME3o-A^T--xR&lK1pH6xTk6>9)F!ZCS(_t3*Q$tUU|C>MN0AX zG_hP-*#Oh^g3Mr5+1D{h#Z2+JA8Q`WB8|7`H?i>L34h@#BxK|Q2NuqVGRzs zv;h8K!8&UTO6R4&Bj?ex;a^y4cjBNyC#3q1>xU46z=_D&eylSecU+Kym`eM>JoPk@UT;&$69K*v&zF!{in(N-fm3Zw~^xlXC&9@ z`p(`w63X@=gr~^!&!kgr!Ka2$=}eW!_b=yin=+IlO1dg(TU17Qh@!5o|hhzuPNN&nPEGkIdd-B6WtX`?7Z&LPzD6MclQEU(^E);8 zv*_8S$K=Kwr_(#h92a%ukp++|32cd2-r+BHCwo8-Vipu)Qa_)CYo^$lhgr{d|B|ETlpK>F6{ZLFCm%m35 zbqDEmzIK_+h}Cm~$Ed_)rQ`l@Cd)C7dDBw0=#Dasv`A6bsF+$EcU|J&EbutNa!7C0 zVlsj#AjC&kE`eRz@G=N0JK7#Dvq%3bg;sX;J9(G8F8`pP!_%X*9V9Pz28FzmoJ~}9 zfX?SJo!BSSCsHq2b$q3Th%A0S8(uFv)LU7^n_Ax_v|qgU{+kg&&QWil1w?QI;5!tH zi`v5bwM%!bysIpmxlTwD_$L^%~N0(;wvUJx~zyR#dOM z?;%$5`!Foo6DES1NPGq8=N-3pZ~qawI+<h^s$ovrcQM za&wEkfZD_dD!r6=*A|s_r`_J&yBy>Y6boT1ma4GK1c;7Tk>_5IdlnbuoSX$E9Yng$ z8pTH1o>X;my-fSJAGb`BWRmfsdfZak-NR^ecNincNME`yMoqkF;A}EJ%Ir)D{Puc+E1LayE z?%vGYtw8P6=`wLP0C<^s?BEa(wjoQw`FtV4baVb`r)p4JQ1xi6M+CN(#UA~Qfc{J3 z{0ku3+0kfwDN$RXTG2sHv^ncFZj-e2cJoy;AK*?@R><_1(L&RW*sM*DPsa>+))LLt zvlx;l_a2f>?v)_r44tiNq+0=_lfxoe21atmAfobOD=rLWv0^waaHhf_jvG0vI4 z8>~_s(sm&ac7`f%UgQ<``$OIc+`iaDr4;!d=577QJWK!UH~8*uHF8zbkS$)lYR1#P zOyfpFZ1Mv0g`nwc$u@*ppykv{Xb4TW)xJ9<(kJy4<^UvRyDGx6y2+3@h z_xGe5^0O=klMcQj=Z&A2uZ$|MucQ!x6h&udS1~BYH3;#h{A!l|5-a(rsm~VvbqjXf z*z%~Z_^>|R)YOUQCrQ)H*b|=?%xLSMVgC}>rOxH+^A&$5+F0&pXZBewlP(|zB0l5v zjGQfzav|%{rBF+}pOAevSO(JHIu21Ew1S|#MFQqBH}l+%;+H+&4c&o(2QFr>4;w3u zKSFk!NdD85+5WW&UcC@wBkmRBEHV zhFKJyFn;0O$%7S06mi~dhCQ02X(6#`q34m7=VAIc+N4Y#L{^d>JnfUs!%147C>r(B zGYJ~>`*rR1IQzM|CGO&%EG0|WCu6#})JcQCiI)K15;y&pTO$j)!$j>?jFAhQ?`owy z#y!w4$Wl*|ls1%dni|WGaXu<&-fQQoCj(3e{8)%vG2QWIig0{&wthG&b?kZZz5V&C zwq~)l9Qm^ad8e62w2=Zb4bHzTTf)eTM6uUL38OJ|ai6r1a?|Pt?1=m?_gyULe(%g6 z-S&s$VBVR|_wNUpt&s@Vl%x>@ibRe2^%4vGj4=+zVBeEe$wKtlBN8ptwD)EiJoOi< zL-dSS{#3Pgb~Zw!P86pHJ0g8ZrZSL&|LpN{kFO*R!`Tw9w)~GVAN@*e z+?Q|=ve=izb-Knwp~^Ep{K9UW@38x6pM#d%660}!%Gf8SY`II0D}{ZlQ|V5W-$@J0 z5O2SZ6n&-EiS^3;w8)>e_W|E6YIbsuNm(~BpZaXrc?GszJaFpLxqWsE=mW3DOUHfV zZ(q;TZYfm#jFh&Y_)kII*dFduQ8(WyU_4@r_N%Eri!gUEi=O`Y{*JqH98`UbwmQ~# z$7j}F$x=K)OF$nsB9foV5jon*Bn9sbG{$xqb+k)nI?Tl3TLJy8 z2JTIKFRb*gge6ztc64EBzh0u<2UWNJ<2|fn(}$T<@2b%9@|fGQXTcsGA;in4Ti%U@ z%U{fSE)vQxSLLT8RSgoB)MA>{-+bP9?}aj@_@9sZJG!t2n?~q*&dZ1Fb5Uq0sg2YB zg*^7x>dPbzNPS5cPOg>c20v31b)@SY3%@>i+G{8J4DIUOjx@Q~fgdua`@Fv);H!#h zwZCLZ3JT5+A<_JadeY&!{w_zt{PqfeJq0NwH@-Ow&o9Fb*8)wPC@{}2kO(B1`XO`} zC(iSyUgJF-e(LsK$vLQA_Mta>i2#v5v={kjbuG+{6NQXNdgU;Cl~omY;QYn|K)S9; zw_im_qp)6$`;~-F*qHT-*|T!3o!>2gZy&c^ulQf}wW_M1%)2mOT%ArZL*XJ1I5_u3 zS1|s5v77V9bvcIoRkk7p=9tX!A$sX+PK7Dd*~p@BmW0$zohX#eZQG0Zrqc&?S<@xt_f0P&*ZK#fUJ^ z$#FQV`E4$6z`l*93UWx}{yeVU>d|pjp_-1kt;+z&;9caq9QhCUHf(l(=2&tC?PW%w zkF_1$n!Ub?-uL=TH(pUke?}2GDoh=ms-rewK}xpr=MIDPd7#c83VZWhD+uX5dCMPH zTnO>-9gfUJyGw4Ch_)@}N^qM{TGH+{<{^siUndmgP2AmydEQTA@z}k;JBAo=tDt^R z=Yyk@$GCR^c+h|GvjBNt17*bGFU^yh{OFdlOFhCe(fiHbfn}F+pty?Z6&FG0_2V|e z)aCJP36|Ec;0QMhT+}7dnn@TZH}w-(~8t~ zx~`{wn;F5QF16WuKDsWe9dqUE--kLj^wmk)LP@L+9}+yd7vSvYG=fPDB)!6(gWh#w zujTyCvsqJCMj{*1e^iVQ0mo`=(eEA+XZCl<1U*t9d-&cewB1a#h3LC}L6oR_yw@Aq z%(UmP<<<={%U&E+wmfbSrWDCYJ&y7!2Nu2;X%AtMTmDo~G1`^jh|OD%~ed7bXMSj^M3y=Gf@VY$CNnpN~CY(0QW)q@Xrw|dz(adkd{ z>3G~ALE1b@Ml6p82xWJP&k>lr0try<;pheZ|0H0x(jKR*I?On#43l?$92^}?SiCv* zOz^Uu2tdBfHvKrY+ndQ)va^f(`l!ghjmn}{bCA_EcV@B?s1HA zt+k5MP@A9jjyI>boejCT+aVRXl^x9aDo%|TL4^q#U5)A&PP!(q1uikn3?JysN*)RI zl~v!nsgG48ZtechP9lkX3%ZTG3rO@;*-$9w1e?iDS$7L!IxMBeaD~mcA1Ok2JSZ4c z;MeT%%yi1<-LyYL=&925lFXTM!r~f*X<<3~4skk)wXXc3FkG$Kr7po@a-5K%ON*Lo z^b66E8oX;mrZMZ&NG>Z^QB{}ngX-U8&lfue-lt|&d1yXkwz*jG zT}}O^VK0`?o;2Qy7Y5f*nx1fhL6M>3HI-e7Zsjs;ivaVClCzo-u9|;6nGqg!`;G6% z7V-N|h)EPmi*iV2S=`U#`jSPQZ)K78qgd#_CXEcCm-DT;k~wShN0J&U{p=7*;aVJ^MuWGA1@IShp>`R3C3hFpx^x)odNk%wEQ7(Buih6243f zSQHU8ZN%%esGof7He#kfD-=)c8yQCrt70;M94<0o8~X{q`sJ-=^HL!jXgLSikB)q@ zq9Oo+v%L`tx^`<=kU7N+k#5y za7=~VfyV`LPd9NLp3Y`Y#0Cw8F@km(HHS~#N+E2LCr#T;xglg4Cw0u2%*;(Le@a-5 z2`Gj>y<=_lCNu;X;p|klRL6lZRb@()r)Smxax8=8SszHE;bf6~++x3EFa2#&11YRf zKD}0Ph~eNwAx$QRKABDa13Hy)(**Hkqd7wO;YJ8soS%8k**b3MD=d_u539YmlfVdj#c1CMFMSrfXK~J6?7yF`EyP*1ccYooOT1@! zFa!5nOYiw{7xy+(*iEu+QUNg})}vgv7{R*sImy^(Qcso+fB3jEB)4<_g7?!6Mq zdqcQbc~8_7+f9<&8~+_AA29|VBEzov%4FvTR+Oq|FJYSz#xE@S(-R?d|7 zsk4OAD8C^}XU8`i&JKH9+;x%g4WX)X;|gOlEaR2$|iF?9T5GdFc4ez zUdsgLsi^!ZJ0#o;^(WYpD@I+oe)wrc=$vUF%zyc5kEYbjSi_wnN(kcjN)W)VDMI|c zf91=^e!FHF@U4#kz?1v`z6C(S#(pOi*mX?%!JYZXm}--0<&N$LOsN=#wR6gzqY ziU)9Z_^@$Qube$Oog@WLYR%ooeFc+Z9eCiB;PvH z+OH0ng*%JCXmIx|4DLmcup-`I8g0f#=}zIyDG952GBzX2(Z{pC>$+QiFjdIo+O0~v z*I||4r0JB|Tm^Se>yH~7WzJLG#D584<@fF`aCAuGS9P;vHTo0m)J{Hr;iFk~|GYYK zP_g6!2zYR``18LJCFPJDIu@8RWf;ou&5uta8^b)C+yLnBK>TgZUq!9$MBy_Zp!+S_ zoE*pPS*R4dhK8;jjGwn#sRR1V4Z+}goaR;OL-QS0uNSnC=4W!a1vQvEIVq+rJUgP6 zM{Ce!Z}HE)m}H6^VNH0}`FCbr0rIa^qHVd_F8qSKSwCZM7bG~UJm$BR)G&25=D~&><1QKZ z9`>1f%}&<|R_U^=bP#vnpg?kC2(06;n9kD2O-5()-53%83+{(?+Ba#(d|)2V-pEwA zgUm5Rxl3cuPUb$^3WjsD{yQ5=;OndvsSvw{43|jZjK1v{aPNciI1rha1eBxH*^>m( zAl$-#(v91?-C0v!v%Wn=i%ktc6*L=N;-C^dg!iQaLRMrC0jJBhSr_wt)$(xZC*YOU zD@7SQ&LMKzF4puCF#R}sN?3I4TKBYb^U{^hJ1n)_wlnZkxj&53INvJr(u&|qx`{q0 zpJK6uM_nE0P|+IYXaBaHfZK<1Mwt3+CkebBohzh_> z(*Gm&X_7U7BW7U$E}GXjIOeU_2n}cVzD=d}?h&jjSe81Xy%KB&;cg2iAzx_E1KK{E#K;V%zJ^*y+dOOJvd)kZ4fT z{%N(_N_)GdwmN2Fo$8uqgnBa`xpCy!GtxsKdzfq0d@HH}fpl)3@|VVazXiCAQP&19 zQ#(A~d+&{sB#!8}g9v&pamnxhU{fD$9naNW`AhWt>9L_Q30ELjhMTN4Le^5?T6$7) z9xR

    =V9i4Yfhd8uf}c&egQ$LF`nOe*tPJf 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

    5Bk-iOq5xNe<#DL|G#~S^CX3 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

    Il9=4CyjPkVn3H#RUB(qrv{0^4~I( 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 z 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 + + + + LSMinimumSystemVersion + 10.9 + CFBundleDevelopmentRegion + English + CFBundleAllowMixedLocalizations + + CFBundleExecutable + DEPLOY_LAUNCHER_NAME + CFBundleIconFile + DEPLOY_ICON_FILE + CFBundleIdentifier + DEPLOY_BUNDLE_IDENTIFIER + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + DEPLOY_BUNDLE_NAME + CFBundlePackageType + APPL + CFBundleShortVersionString + DEPLOY_BUNDLE_SHORT_VERSION + CFBundleSignature + ???? + + LSApplicationCategoryType + DEPLOY_BUNDLE_CATEGORY + CFBundleVersion + DEPLOY_BUNDLE_CFBUNDLE_VERSION + NSHumanReadableCopyright + DEPLOY_BUNDLE_COPYRIGHTDEPLOY_FILE_ASSOCIATIONS + NSHighResolutionCapable + true + + diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/Info.plist.template b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/Info.plist.template new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/Info.plist.template @@ -0,0 +1,54 @@ + + + + + LSMinimumSystemVersion + 10.7.4 + CFBundleDevelopmentRegion + English + CFBundleAllowMixedLocalizations + + CFBundleExecutable + DEPLOY_LAUNCHER_NAME + CFBundleIconFile + DEPLOY_ICON_FILE + CFBundleIdentifier + DEPLOY_BUNDLE_IDENTIFIER + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + DEPLOY_BUNDLE_NAME + CFBundlePackageType + APPL + CFBundleShortVersionString + DEPLOY_BUNDLE_SHORT_VERSION + CFBundleSignature + ???? + + LSApplicationCategoryType + DEPLOY_BUNDLE_CATEGORY + CFBundleVersion + DEPLOY_BUNDLE_CFBUNDLE_VERSION + NSHumanReadableCopyright + DEPLOY_BUNDLE_COPYRIGHT + JavaRuntime + DEPLOY_JAVA_RUNTIME_NAME + JavaMainClassName + DEPLOY_LAUNCHER_CLASS + JavaAppClasspath + DEPLOY_APP_CLASSPATH + JavaMainJarName + DEPLOY_MAIN_JAR_NAME + JavaOptions + +DEPLOY_JAVA_OPTIONS + + ArgOptions + +DEPLOY_ARGUMENTS + DEPLOY_FILE_ASSOCIATIONS + NSHighResolutionCapable + true + + diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacAppStore.entitlements b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacAppStore.entitlements new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacAppStore.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacAppStore_Inherit.entitlements b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacAppStore_Inherit.entitlements new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacAppStore_Inherit.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.inherit + + + diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties @@ -0,0 +1,104 @@ +# +# 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 +app.bundler.description=A Directory based image of a mac Application with an optionally co-bundled JRE. Used as a base for the Installer bundlers +store.bundler.name=Mac App Store Ready Bundler +store.bundler.description=Creates a binary bundle ready for deployment into the Mac App Store. +dmg.bundler.name=DMG Installer +dmg.bundler.description=Mac DMG Installer Bundle +pkg.bundler.name=PKG Installer +pkg.bundler.description=Mac PKG Installer Bundle. + +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.non-existent-runtime=The file for the Runtime/JRE directory does not exist. +error.non-existent-runtime.advice=Point the runtime parameter to a directory that containes the JRE. +error.cannot-detect-runtime-in-directory=Cannot determine which JRE/JDK exists in the specified runtime directory. +error.cannot-detect-runtime-in-directory.advice=Point the runtime directory to one of the JDK/JRE root, the Contents/Home directory of that root, or the Contents/Home/jre directory of the JDK. +error.parameters-null=Parameters map is null. +error.parameters-null.advice=Pass in a non-null parameters map. +error.cannot-create-output-dir=Output directory {0} cannot be created. +error.cannot-write-to-output-dir=Output directory {0} is not writable. +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.dmg-does-not-do-daemons=DMG bundler doesn't support services. +error.dmg-does-not-do-daemons.advice=Make sure that the service hint is set to false. + +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 Bundle for {0}. +mesasge.intermediate-bundle-location=Intermediate application bundle image: {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.could-not-retrieve-name=Could not retrieve gecos name. +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=When using an external app image you must specify the identifier. +message.app-image-requires-identifier.advice=Set the identifier via the -appId CLI flag, the fx:application/@id ANT attribute, or via the 'identifier' bundler argument. +message.building-dmg=Building DMG package for {0}. +message.running-script=Running shell script on application image [{0}]. +message.intermediate-image-location=[DEBUG] Intermediate application bundle 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. + diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_ja.properties b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_ja.properties new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_ja.properties @@ -0,0 +1,104 @@ +# +# 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 +app.bundler.description=A Directory based image of a mac Application with an optionally co-bundled JRE. Used as a base for the Installer bundlers +store.bundler.name=Mac App Store Ready Bundler +store.bundler.description=Creates a binary bundle ready for deployment into the Mac App Store. +dmg.bundler.name=DMG Installer +dmg.bundler.description=Mac DMG Installer Bundle +pkg.bundler.name=PKG Installer +pkg.bundler.description=Mac PKG Installer Bundle. + +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.non-existent-runtime=The file for the Runtime/JRE directory does not exist. +error.non-existent-runtime.advice=Point the runtime parameter to a directory that containes the JRE. +error.cannot-detect-runtime-in-directory=Cannot determine which JRE/JDK exists in the specified runtime directory. +error.cannot-detect-runtime-in-directory.advice=Point the runtime directory to one of the JDK/JRE root, the Contents/Home directory of that root, or the Contents/Home/jre directory of the JDK. +error.parameters-null=Parameters map is null. +error.parameters-null.advice=Pass in a non-null parameters map. +error.cannot-create-output-dir=Output directory {0} cannot be created. +error.cannot-write-to-output-dir=Output directory {0} is not writable. +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.dmg-does-not-do-daemons=DMG bundler doesn't support services. +error.dmg-does-not-do-daemons.advice=Make sure that the service hint is set to false. + +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 Bundle for {0}. +mesasge.intermediate-bundle-location=Intermediate application bundle image: {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.could-not-retrieve-name=Could not retrieve gecos name. +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=When using an external app image you must specify the identifier. +message.app-image-requires-identifier.advice=Set the identifier via the -appId CLI flag, the fx:application/@id ANT attribute, or via the 'identifier' bundler argument. +message.building-dmg=Building DMG package for {0}. +message.running-script=Running shell script on application image [{0}]. +message.intermediate-image-location=[DEBUG] Intermediate application bundle 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. + diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_zh_CN.properties b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_zh_CN.properties new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_zh_CN.properties @@ -0,0 +1,104 @@ +# +# 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 +app.bundler.description=A Directory based image of a mac Application with an optionally co-bundled JRE. Used as a base for the Installer bundlers +store.bundler.name=Mac App Store Ready Bundler +store.bundler.description=Creates a binary bundle ready for deployment into the Mac App Store. +dmg.bundler.name=DMG Installer +dmg.bundler.description=Mac DMG Installer Bundle +pkg.bundler.name=PKG Installer +pkg.bundler.description=Mac PKG Installer Bundle. + +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.non-existent-runtime=The file for the Runtime/JRE directory does not exist. +error.non-existent-runtime.advice=Point the runtime parameter to a directory that containes the JRE. +error.cannot-detect-runtime-in-directory=Cannot determine which JRE/JDK exists in the specified runtime directory. +error.cannot-detect-runtime-in-directory.advice=Point the runtime directory to one of the JDK/JRE root, the Contents/Home directory of that root, or the Contents/Home/jre directory of the JDK. +error.parameters-null=Parameters map is null. +error.parameters-null.advice=Pass in a non-null parameters map. +error.cannot-create-output-dir=Output directory {0} cannot be created. +error.cannot-write-to-output-dir=Output directory {0} is not writable. +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.dmg-does-not-do-daemons=DMG bundler doesn't support services. +error.dmg-does-not-do-daemons.advice=Make sure that the service hint is set to false. + +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 Bundle for {0}. +mesasge.intermediate-bundle-location=Intermediate application bundle image: {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.could-not-retrieve-name=Could not retrieve gecos name. +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=When using an external app image you must specify the identifier. +message.app-image-requires-identifier.advice=Set the identifier via the -appId CLI flag, the fx:application/@id ANT attribute, or via the 'identifier' bundler argument. +message.building-dmg=Building DMG package for {0}. +message.running-script=Running shell script on application image [{0}]. +message.intermediate-image-location=[DEBUG] Intermediate application bundle 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. + diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/Runtime-Info.plist.template b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/Runtime-Info.plist.template new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/Runtime-Info.plist.template @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + libjli.dylib + CFBundleIdentifier + CF_BUNDLE_IDENTIFIER + CFBundleInfoDictionaryVersion + 7.0 + CFBundleName + CF_BUNDLE_NAME + CFBundlePackageType + BNDL + CFBundleShortVersionString + CF_BUNDLE_SHORT_VERSION_STRING + CFBundleSignature + ???? + CFBundleVersion + CF_BUNDLE_VERSION + + diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/background_dmg.png b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/background_dmg.png new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2cfefac72e0ab4d13ab3d6475d653d90d40f01fa GIT binary patch literal 3407 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 U?VGJj%YSiP$sVMuP5~$W355rqyZ`_I diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/background_pkg.png b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/background_pkg.png new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..d496c513cd5db320268973666dbf95e5bb1822d1 GIT binary patch literal 14559 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 + + + + Label + DEPLOY_DAEMON_IDENTIFIER + ProgramArguments + + DEPLOY_DAEMON_LAUNCHER_PATH + + RunAtLoad + KeepAlive + + diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/lic_template.plist b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/lic_template.plist new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/lic_template.plist @@ -0,0 +1,244 @@ + + + + + LPic + + + Attributes + 0x0000 + Data + AAAAAgAAAAAAAAAAAAQAAA== + ID + 5000 + Name + + + + STR# + + + Attributes + 0x0000 + Data + AAYPRW5nbGlzaCBkZWZhdWx0BUFncmVlCERpc2FncmVlBVByaW50B1NhdmUuLi56SWYgeW91IGFncmVlIHdpdGggdGhlIHRlcm1zIG9mIHRoaXMgbGljZW5zZSwgY2xpY2sgIkFncmVlIiB0byBhY2Nlc3MgdGhlIHNvZnR3YXJlLiAgSWYgeW91IGRvIG5vdCBhZ3JlZSwgcHJlc3MgIkRpc2FncmVlLiI= + ID + 5000 + Name + English buttons + + + Attributes + 0x0000 + Data + AAYHRGV1dHNjaAtBa3plcHRpZXJlbghBYmxlaG5lbgdEcnVja2VuClNpY2hlcm4uLi7nS2xpY2tlbiBTaWUgaW4g0kFremVwdGllcmVu0ywgd2VubiBTaWUgbWl0IGRlbiBCZXN0aW1tdW5nZW4gZGVzIFNvZnR3YXJlLUxpemVuenZlcnRyYWdzIGVpbnZlcnN0YW5kZW4gc2luZC4gRmFsbHMgbmljaHQsIGJpdHRlINJBYmxlaG5lbtMgYW5rbGlja2VuLiBTaWUga5pubmVuIGRpZSBTb2Z0d2FyZSBudXIgaW5zdGFsbGllcmVuLCB3ZW5uIFNpZSDSQWt6ZXB0aWVyZW7TIGFuZ2VrbGlja3QgaGFiZW4u + ID + 5001 + Name + German + + + Attributes + 0x0000 + Data + AAYHRW5nbGlzaAVBZ3JlZQhEaXNhZ3JlZQVQcmludAdTYXZlLi4ue0lmIHlvdSBhZ3JlZSB3aXRoIHRoZSB0ZXJtcyBvZiB0aGlzIGxpY2Vuc2UsIHByZXNzICJBZ3JlZSIgdG8gaW5zdGFsbCB0aGUgc29mdHdhcmUuICBJZiB5b3UgZG8gbm90IGFncmVlLCBwcmVzcyAiRGlzYWdyZWUiLg== + ID + 5002 + Name + English + + + Attributes + 0x0000 + Data + AAYHRXNwYZZvbAdBY2VwdGFyCk5vIGFjZXB0YXIISW1wcmltaXIKR3VhcmRhci4uLsBTaSBlc3SHIGRlIGFjdWVyZG8gY29uIGxvcyB0jnJtaW5vcyBkZSBlc3RhIGxpY2VuY2lhLCBwdWxzZSAiQWNlcHRhciIgcGFyYSBpbnN0YWxhciBlbCBzb2Z0d2FyZS4gRW4gZWwgc3VwdWVzdG8gZGUgcXVlIG5vIGVzdI4gZGUgYWN1ZXJkbyBjb24gbG9zIHSOcm1pbm9zIGRlIGVzdGEgbGljZW5jaWEsIHB1bHNlICJObyBhY2VwdGFyLiI= + ID + 5003 + Name + Spanish + + + Attributes + 0x0000 + Data + AAYIRnJhbo1haXMIQWNjZXB0ZXIHUmVmdXNlcghJbXByaW1lcg5FbnJlZ2lzdHJlci4uLrpTaSB2b3VzIGFjY2VwdGV6IGxlcyB0ZXJtZXMgZGUgbGEgcHKOc2VudGUgbGljZW5jZSwgY2xpcXVleiBzdXIgIkFjY2VwdGVyIiBhZmluIGQnaW5zdGFsbGVyIGxlIGxvZ2ljaWVsLiBTaSB2b3VzIG4nkHRlcyBwYXMgZCdhY2NvcmQgYXZlYyBsZXMgdGVybWVzIGRlIGxhIGxpY2VuY2UsIGNsaXF1ZXogc3VyICJSZWZ1c2VyIi4= + ID + 5004 + Name + French + + + Attributes + 0x0000 + Data + AAYISXRhbGlhbm8HQWNjZXR0bwdSaWZpdXRvBlN0YW1wYQtSZWdpc3RyYS4uLn9TZSBhY2NldHRpIGxlIGNvbmRpemlvbmkgZGkgcXVlc3RhIGxpY2VuemEsIGZhaSBjbGljIHN1ICJBY2NldHRvIiBwZXIgaW5zdGFsbGFyZSBpbCBzb2Z0d2FyZS4gQWx0cmltZW50aSBmYWkgY2xpYyBzdSAiUmlmaXV0byIu + ID + 5005 + Name + Italian + + + Attributes + 0x0000 + Data + AAYISmFwYW5lc2UKk6+I04K1gtyCtwyTr4jTgrWC3IK5gvEIiPON/IK3gukHlduRti4uLrSWe4Ncg3SDZ4NFg0eDQY5nl3CLlpH4jF+W8YLMj/CMj4LJk6+I04KzguqC6Y/qjYeCyYLNgUGDXIN0g2eDRYNHg0GC8INDg5ODWINngVuDi4K3gumCvYLfgsmBdZOviNOCtYLcgreBdoLwiZ+CtYLEgq2CvoKzgqKBQoFAk6+I04KzguqCyIKij+qNh4LJgs2BQYF1k6+I04K1gtyCuYLxgXaC8ImfgrWCxIKtgr6Cs4KigUI= + ID + 5006 + Name + Japanese + + + Attributes + 0x0000 + Data + AAYKTmVkZXJsYW5kcwJKYQNOZWUFUHJpbnQJQmV3YWFyLi4upEluZGllbiB1IGFra29vcmQgZ2FhdCBtZXQgZGUgdm9vcndhYXJkZW4gdmFuIGRlemUgbGljZW50aWUsIGt1bnQgdSBvcCAnSmEnIGtsaWtrZW4gb20gZGUgcHJvZ3JhbW1hdHV1ciB0ZSBpbnN0YWxsZXJlbi4gSW5kaWVuIHUgbmlldCBha2tvb3JkIGdhYXQsIGtsaWt0IHUgb3AgJ05lZScu + ID + 5007 + Name + Dutch + + + Attributes + 0x0000 + Data + AAYGU3ZlbnNrCEdvZGuKbm5zBkF2YppqcwhTa3JpdiB1dAhTcGFyYS4uLpNPbSBEdSBnb2Rrim5uZXIgbGljZW5zdmlsbGtvcmVuIGtsaWNrYSBwjCAiR29ka4pubnMiIGaaciBhdHQgaW5zdGFsbGVyYSBwcm9ncmFtcHJvZHVrdGVuLiBPbSBEdSBpbnRlIGdvZGuKbm5lciBsaWNlbnN2aWxsa29yZW4sIGtsaWNrYSBwjCAiQXZimmpzIi4= + ID + 5008 + Name + Swedish + + + Attributes + 0x0000 + Data + AAYRUG9ydHVndZBzLCBCcmFzaWwJQ29uY29yZGFyCURpc2NvcmRhcghJbXByaW1pcglTYWx2YXIuLi6MU2UgZXN0hyBkZSBhY29yZG8gY29tIG9zIHRlcm1vcyBkZXN0YSBsaWNlbo1hLCBwcmVzc2lvbmUgIkNvbmNvcmRhciIgcGFyYSBpbnN0YWxhciBvIHNvZnR3YXJlLiBTZSBui28gZXN0hyBkZSBhY29yZG8sIHByZXNzaW9uZSAiRGlzY29yZGFyIi4= + ID + 5009 + Name + Brazilian Portuguese + + + Attributes + 0x0000 + Data + AAYSU2ltcGxpZmllZCBDaGluZXNlBM2s0uIGsrvNrNLiBLTy06EGtOa0oqGtVMjnufvE+s2s0uKxvtDtv8nQrdLptcTM9b/uo6zH67C0obDNrNLiobHAtLCy17C0y8jtvP6ho8jnufvE+rK7zazS4qOsx+uwtKGwsrvNrNLiobGhow== + ID + 5010 + Name + Simplified Chinese + + + Attributes + 0x0000 + Data + AAYTVHJhZGl0aW9uYWwgQ2hpbmVzZQSmULdOBqSjplC3TgSmQ6ZMBsB4pnOhS1CmcKpHsXqmULdOpbuzXKVpw9K4zKq6sfi02qFBvdCr9qGnplC3TqGopUimd7jLs27F6aFDpnCqR6SjplC3TqFBvdCr9qGnpKOmULdOoaihQw== + ID + 5011 + Name + Traditional Chinese + + + Attributes + 0x0000 + Data + AAYFRGFuc2sERW5pZwVVZW5pZwdVZHNrcml2CkFya2l2ZXIuLi6YSHZpcyBkdSBhY2NlcHRlcmVyIGJldGluZ2Vsc2VybmUgaSBsaWNlbnNhZnRhbGVuLCBza2FsIGR1IGtsaWtrZSBwjCDSRW5pZ9MgZm9yIGF0IGluc3RhbGxlcmUgc29mdHdhcmVuLiBLbGlrIHCMINJVZW5pZ9MgZm9yIGF0IGFubnVsbGVyZSBpbnN0YWxsZXJpbmdlbi4= + ID + 5012 + Name + Danish + + + Attributes + 0x0000 + Data + AAYFU3VvbWkISHl2imtzeW4KRW4gaHl2imtzeQdUdWxvc3RhCVRhbGxlbm5hyW9IeXaKa3N5IGxpc2Vuc3Npc29waW11a3NlbiBlaGRvdCBvc29pdHRhbWFsbGEg1Uh5doprc3nVLiBKb3MgZXQgaHl2imtzeSBzb3BpbXVrc2VuIGVodG9qYSwgb3NvaXRhINVFbiBoeXaKa3N51S4= + ID + 5013 + Name + Finnish + + + Attributes + 0x0000 + Data + AAYRRnJhbo1haXMgY2FuYWRpZW4IQWNjZXB0ZXIHUmVmdXNlcghJbXByaW1lcg5FbnJlZ2lzdHJlci4uLrpTaSB2b3VzIGFjY2VwdGV6IGxlcyB0ZXJtZXMgZGUgbGEgcHKOc2VudGUgbGljZW5jZSwgY2xpcXVleiBzdXIgIkFjY2VwdGVyIiBhZmluIGQnaW5zdGFsbGVyIGxlIGxvZ2ljaWVsLiBTaSB2b3VzIG4nkHRlcyBwYXMgZCdhY2NvcmQgYXZlYyBsZXMgdGVybWVzIGRlIGxhIGxpY2VuY2UsIGNsaXF1ZXogc3VyICJSZWZ1c2VyIi4= + ID + 5014 + Name + French Canadian + + + Attributes + 0x0000 + Data + AAYGS29yZWFuBLW/wMcJtb/AxyC+yMfUBsfBuLDGrgfA+sDlLi4ufrvnv+sgsOi+4LytwMcgs7u/67+hILW/wMfHz7jpLCAitb/AxyIgtNzD37imILStt68gvNLHwcauv/6+7rimILyzxKHHz73KvcO/wC4gtb/Ax8fPwfYgvsq0wrTZuOksICK1v8DHIL7Ix9QiILTcw9+4piC0qbijvcq9w7/ALg== + ID + 5015 + Name + Korean + + + Attributes + 0x0000 + Data + AAYFTm9yc2sERW5pZwlJa2tlIGVuaWcIU2tyaXYgdXQKQXJraXZlci4uLqNIdmlzIERlIGVyIGVuaWcgaSBiZXN0ZW1tZWxzZW5lIGkgZGVubmUgbGlzZW5zYXZ0YWxlbiwga2xpa2tlciBEZSBwjCAiRW5pZyIta25hcHBlbiBmb3IgjCBpbnN0YWxsZXJlIHByb2dyYW12YXJlbi4gSHZpcyBEZSBpa2tlIGVyIGVuaWcsIGtsaWtrZXIgRGUgcIwgIklra2UgZW5pZyIu + ID + 5016 + Name + Norwegian + + + TEXT + + + Attributes + 0x0000 + Data + APPLICATION_LICENSE_TEXT + ID + 5000 + Name + English SLA + + + TMPL + + + Attributes + 0x0000 + Data + E0RlZmF1bHQgTGFuZ3VhZ2UgSUREV1JEBUNvdW50T0NOVAQqKioqTFNUQwtzeXMgbGFuZyBJRERXUkQebG9jYWwgcmVzIElEIChvZmZzZXQgZnJvbSA1MDAwRFdSRBAyLWJ5dGUgbGFuZ3VhZ2U/RFdSRAQqKioqTFNURQ== + ID + 128 + Name + LPic + + + plst + + + Attributes + 0x0050 + Data + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + ID + 0 + Name + + + + styl + + + Attributes + 0x0000 + Data + AAMAAAAAAAwACQAUAAAAAAAAAAAAAAAAACcADAAJABQBAAAAAAAAAAAAAAAAKgAMAAkAFAAAAAAAAAAAAAA= + ID + 5000 + Name + English SLA + + + + diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/postinstall.template b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/postinstall.template new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/postinstall.template @@ -0,0 +1,6 @@ +#!/usr/bin/env sh + +chown root:wheel "INSTALL_LOCATION" +chmod a+rX "INSTALL_LOCATION" + +exit 0 diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/preinstall.template b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/preinstall.template new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/preinstall.template @@ -0,0 +1,8 @@ +#!/usr/bin/env sh + +if [ ! -d "INSTALL_LOCATION" ] +then + mkdir -p "INSTALL_LOCATION" +fi + +exit 0 diff --git a/src/jdk.jpackage/macosx/classes/module-info.java.extra b/src/jdk.jpackage/macosx/classes/module-info.java.extra new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/macosx/classes/module-info.java.extra @@ -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.jpackage.internal.Bundler with + jdk.jpackage.internal.MacAppBundler, + jdk.jpackage.internal.MacAppStoreBundler, + jdk.jpackage.internal.MacDmgBundler, + jdk.jpackage.internal.MacPkgBundler; + diff --git a/src/jdk.jpackage/macosx/native/jpackageapplauncher/main.m b/src/jdk.jpackage/macosx/native/jpackageapplauncher/main.m new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/macosx/native/jpackageapplauncher/main.m @@ -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 +#include +#include + +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; +} diff --git a/src/jdk.jpackage/macosx/native/libapplauncher/MacPlatform.h b/src/jdk.jpackage/macosx/native/libapplauncher/MacPlatform.h new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/macosx/native/libapplauncher/MacPlatform.h @@ -0,0 +1,72 @@ +/* + * 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 void SetCurrentDirectory(TString Value); + 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 GetKeys(); +}; + + +#endif // MACPLATFORM_H diff --git a/src/jdk.jpackage/macosx/native/libapplauncher/MacPlatform.mm b/src/jdk.jpackage/macosx/native/libapplauncher/MacPlatform.mm new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/macosx/native/libapplauncher/MacPlatform.mm @@ -0,0 +1,507 @@ +/* + * 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 +#include +#include +#include +#include + +#import +#import + +#include +#include + +#ifdef __OBJC__ +#import +#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("Java"); +} + +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; +} + +void MacPlatform::SetCurrentDirectory(TString Value) { + chdir(PlatformString(Value).toPlatformString()); +} + +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 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::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 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 (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 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 MacPlatform::GetKeys() { + std::map keys; + + if (UsePListForConfigFile() == false) { + return Platform::GetKeys(); + } else { + keys.insert(std::map::value_type(CONFIG_VERSION, + _T("app.version"))); + keys.insert(std::map::value_type(CONFIG_MAINJAR_KEY, + _T("JavaMainJarName"))); + keys.insert(std::map::value_type(CONFIG_MAINMODULE_KEY, + _T("JavaMainModuleName"))); + keys.insert(std::map::value_type( + CONFIG_MAINCLASSNAME_KEY, _T("JavaMainClassName"))); + keys.insert(std::map::value_type( + CONFIG_CLASSPATH_KEY, _T("JavaAppClasspath"))); + keys.insert(std::map::value_type(APP_NAME_KEY, + _T("CFBundleName"))); + keys.insert(std::map::value_type(JAVA_RUNTIME_KEY, + _T("JavaRuntime"))); + keys.insert(std::map::value_type(JPACKAGE_APP_DATA_DIR, + _T("CFBundleIdentifier"))); + + keys.insert(std::map::value_type(CONFIG_SPLASH_KEY, + _T("app.splash"))); + keys.insert(std::map::value_type(CONFIG_APP_MEMORY, + _T("app.memory"))); + keys.insert(std::map::value_type(CONFIG_APP_DEBUG, + _T("app.debug"))); + keys.insert(std::map::value_type( + CONFIG_APPLICATION_INSTANCE, _T("app.application.instance"))); + + keys.insert(std::map::value_type( + CONFIG_SECTION_APPLICATION, _T("Application"))); + keys.insert(std::map::value_type( + CONFIG_SECTION_JAVAOPTIONS, _T("JavaOptions"))); + keys.insert(std::map::value_type( + CONFIG_SECTION_APPCDSJAVAOPTIONS, _T("AppCDSJavaOptions"))); + keys.insert(std::map::value_type( + CONFIG_SECTION_APPCDSGENERATECACHEJAVAOPTIONS, + _T("AppCDSGenerateCacheJavaOptions"))); + keys.insert(std::map::value_type( + CONFIG_SECTION_ARGOPTIONS, _T("ArgOptions"))); + } + + return keys; +} diff --git a/src/jdk.jpackage/macosx/native/libapplauncher/PlatformDefs.h b/src/jdk.jpackage/macosx/native/libapplauncher/PlatformDefs.h new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/macosx/native/libapplauncher/PlatformDefs.h @@ -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 +#include +#include +#include +#include +#include + +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 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 diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AbstractAppImageBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AbstractAppImageBuilder.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AbstractAppImageBuilder.java @@ -0,0 +1,263 @@ +/* + * 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.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.jpackage.internal.resources.ResourceLocator; + +import static jdk.jpackage.internal.StandardBundlerParam.*; +import static jdk.jpackage.internal.StandardBundlerParam.ARGUMENTS; + +public abstract class AbstractAppImageBuilder { + + private static final ResourceBundle I18N = ResourceBundle.getBundle( + "jdk.jpackage.internal.resources.MainResources"); + + private final Map properties; + private final Path root; + protected List excludeFileList = new ArrayList<>(); + + public AbstractAppImageBuilder(Map properties, + Path root) throws IOException { + this.properties = properties; + this.root = root; + excludeFileList.add(".*\\.diz"); + } + + public InputStream getResourceAsStream(String name) { + return ResourceLocator.class.getResourceAsStream(name); + } + + public abstract void prepareApplicationFiles() throws IOException; + public abstract void prepareJreFiles() throws IOException; + public abstract Path getAppDir(); + public abstract Path getAppModsDir(); + + public Map getProperties() { + return this.properties; + } + + public Path getRoot() { + return this.root; + } + + public String getExcludeFileList() { + return String.join(",", excludeFileList); + } + + 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); + } + } + + protected InputStream locateResource(String publicName, String category, + String defaultName, File customFile, + boolean verbose, File publicRoot) throws IOException { + InputStream is = null; + boolean customFromClasspath = false; + boolean customFromFile = false; + if (publicName != null) { + if (publicRoot != null) { + File publicResource = new File(publicRoot, publicName); + if (publicResource.exists() && publicResource.isFile()) { + is = new FileInputStream(publicResource); + } + } else { + is = getResourceAsStream(publicName); + } + customFromClasspath = (is != null); + } + if (is == null && customFile != null) { + is = new FileInputStream(customFile); + customFromFile = (is != null); + } + if (is == null && defaultName != null) { + is = getResourceAsStream(defaultName); + } + if (verbose) { + String msg = null; + if (customFromClasspath) { + msg = MessageFormat.format(I18N.getString( + "message.using-custom-resource"), + category == null ? "" : "[" + category + "] ", publicName); + } else if (customFromFile) { + msg = MessageFormat.format(I18N.getString( + "message.using-custom-resource-from-file"), + category == null ? "" : "[" + category + "] ", + customFile.getAbsoluteFile()); + } else if (is != null) { + msg = MessageFormat.format(I18N.getString( + "message.using-default-resource"), + defaultName, + category == null ? "" : "[" + category + "] ", + publicName); + } else { + msg = MessageFormat.format(I18N.getString( + "message.no-default-resource"), + defaultName == null ? "" : defaultName, + category == null ? "" : "[" + category + "] ", + publicName); + } + if (msg != null) { + Log.verbose(msg); + } + } + return is; + } + + + protected String preprocessTextResource(String publicName, String category, + String defaultName, Map pairs, + boolean verbose, File publicRoot) throws IOException { + InputStream inp = locateResource(publicName, category, + defaultName, null, verbose, publicRoot); + if (inp == null) { + throw new RuntimeException( + "Module corrupt? No "+defaultName+" resource!"); + } + + try (InputStream is = inp) { + //read fully into memory + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int length; + while ((length = is.read(buffer)) != -1) { + baos.write(buffer, 0, length); + } + + //substitute + String result = new String(baos.toByteArray()); + for (Map.Entry e : pairs.entrySet()) { + if (e.getValue() != null) { + result = result.replace(e.getKey(), e.getValue()); + } + } + return result; + } + } + + public void writeCfgFile(Map params, + File cfgFileName, String runtimeLocation) throws IOException { + 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); + + 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=" + runtimeLocation); + out.println("app.identifier=" + IDENTIFIER.fetchFrom(params)); + out.println("app.classpath=" + String.join(File.pathSeparator, + CLASSPATH.fetchFrom(params).split("[ :;]"))); + + // 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 = JLinkBundlerHelper.getMainClass(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=" + + mainJar.toPath().getFileName().toString()); + } + if (mainClass != null) { + out.println("app.mainclass=" + + mainClass.replaceAll("\\.", "/")); + } + } + + Integer port = JLinkBundlerHelper.DEBUG.fetchFrom(params); + + if (port != null) { + out.println( + "app.debug=-agentlib:jdwp=transport=dt_socket," + + "server=y,suspend=y,address=localhost:" + + port); + } + + out.println(); + out.println("[JavaOptions]"); + List 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(getAppDir().relativize(modsDir)); + } + + out.println(); + out.println("[ArgOptions]"); + List 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); + } + } + + + out.close(); + } + +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AbstractBundler.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AbstractBundler.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AbstractBundler.java @@ -0,0 +1,194 @@ +/* + * 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.jpackage.internal; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.StandardCopyOption; +import java.nio.file.Files; +import java.text.MessageFormat; +import java.util.Map; +import java.util.ResourceBundle; + +import jdk.jpackage.internal.resources.ResourceLocator; + +/** + * 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. + */ +public abstract class AbstractBundler implements Bundler { + + private static final ResourceBundle I18N = ResourceBundle.getBundle( + "jdk.jpackage.internal.resources.MainResources"); + + public static final BundlerParamInfo IMAGES_ROOT = + new StandardBundlerParam<>( + "imagesRoot", + File.class, + params -> new File( + StandardBundlerParam.TEMP_ROOT.fetchFrom(params), "images"), + (s, p) -> null); + + public InputStream getResourceAsStream(String name) { + return ResourceLocator.class.getResourceAsStream(name); + } + + protected void fetchResource(String publicName, String category, + String defaultName, File result, boolean verbose, File publicRoot) + throws IOException { + + InputStream is = streamResource(publicName, category, + defaultName, verbose, publicRoot); + if (is != null) { + try { + Files.copy(is, result.toPath(), + StandardCopyOption.REPLACE_EXISTING); + } finally { + is.close(); + } + } else { + if (verbose) { + Log.verbose(MessageFormat.format(I18N.getString( + "message.no-default-resource"), + defaultName == null ? "" : defaultName, + category == null ? "" : "[" + category + "] ", + publicName)); + } + } + } + + protected void fetchResource(String publicName, String category, + File defaultFile, File result, boolean verbose, File publicRoot) + throws IOException { + + InputStream is = streamResource(publicName, category, + null, verbose, publicRoot); + if (is != null) { + try { + Files.copy(is, result.toPath()); + } finally { + is.close(); + } + } else { + IOUtils.copyFile(defaultFile, result); + if (verbose) { + Log.verbose(MessageFormat.format(I18N.getString( + "message.using-custom-resource-from-file"), + category == null ? "" : "[" + category + "] ", + defaultFile.getAbsoluteFile())); + } + } + } + + private InputStream streamResource(String publicName, String category, + String defaultName, boolean verbose, File publicRoot) + throws IOException { + boolean custom = false; + InputStream is = null; + if (publicName != null) { + if (publicRoot != null) { + File publicResource = new File(publicRoot, publicName); + if (publicResource.exists() && publicResource.isFile()) { + is = new BufferedInputStream( + new FileInputStream(publicResource)); + } + } else { + is = getResourceAsStream(publicName); + } + custom = (is != null); + } + if (is == null && defaultName != null) { + is = getResourceAsStream(defaultName); + } + if (verbose && is != null) { + String msg = null; + if (custom) { + msg = MessageFormat.format(I18N.getString( + "message.using-custom-resource"), + category == null ? + "" : "[" + category + "] ", publicName); + } else { + msg = MessageFormat.format(I18N.getString( + "message.using-default-resource"), + defaultName == null ? "" : defaultName, + category == null ? "" : "[" + category + "] ", + publicName); + } + Log.verbose(msg); + } + return is; + } + + protected String preprocessTextResource(String publicName, String category, + String defaultName, Map pairs, + boolean verbose, File publicRoot) throws IOException { + InputStream inp = streamResource( + publicName, category, defaultName, verbose, publicRoot); + if (inp == null) { + throw new RuntimeException( + "Jar corrupt? No " + defaultName + " resource!"); + } + + // read fully into memory + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int length; + while ((length = inp.read(buffer)) != -1) { + baos.write(buffer, 0, length); + } + + // substitute + String result = new String(baos.toByteArray()); + for (Map.Entry e : pairs.entrySet()) { + if (e.getValue() != null) { + result = result.replace(e.getKey(), e.getValue()); + } + } + return result; + } + + @Override + public String toString() { + return getName(); + } + + @Override + public void cleanup(Map params) { + try { + IOUtils.deleteRecursive( + StandardBundlerParam.TEMP_ROOT.fetchFrom(params)); + } catch (IOException e) { + Log.debug(e.getMessage()); + } + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AbstractImageBundler.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AbstractImageBundler.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AbstractImageBundler.java @@ -0,0 +1,144 @@ +/* + * 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.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.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 intermeadiate step in "create-installer" mode. + * + * The concrete implementations are in the platform specific Bundlers. + */ +public abstract class AbstractImageBundler extends AbstractBundler { + + private final static String JAVA_VERSION_SPEC = + "java version \"((\\d+).(\\d+).(\\d+).(\\d+))(-(.*))?(\\+[^\"]*)?\""; + + private static final ResourceBundle I18N = ResourceBundle.getBundle( + "jdk.jpackage.internal.resources.MainResources"); + + public void imageBundleValidation(Map p) + throws ConfigException { + StandardBundlerParam.validateMainClassInfoFromAppResources(p); + + } + + public static void extractFlagsFromVersion( + Map params, String versionOutput) { + Pattern bitArchPattern = Pattern.compile("(\\d*)[- ]?[bB]it"); + Matcher matcher = bitArchPattern.matcher(versionOutput); + if (matcher.find()) { + params.put(".runtime.bit-arch", matcher.group(1)); + } else { + // presume 32 bit on no match + params.put(".runtime.bit-arch", "32"); + } + + Pattern oldVersionMatcher = Pattern.compile( + "java version \"((\\d+.(\\d+).\\d+)(_(\\d+)))?(-(.*))?\""); + matcher = oldVersionMatcher.matcher(versionOutput); + if (matcher.find()) { + params.put(".runtime.version", matcher.group(1)); + params.put(".runtime.version.release", matcher.group(2)); + params.put(".runtime.version.major", matcher.group(3)); + params.put(".runtime.version.update", matcher.group(5)); + params.put(".runtime.version.minor", matcher.group(5)); + params.put(".runtime.version.security", matcher.group(5)); + params.put(".runtime.version.patch", "0"); + params.put(".runtime.version.modifiers", matcher.group(7)); + } else { + Pattern newVersionMatcher = Pattern.compile(JAVA_VERSION_SPEC); + matcher = newVersionMatcher.matcher(versionOutput); + if (matcher.find()) { + params.put(".runtime.version", matcher.group(1)); + params.put(".runtime.version.release", matcher.group(1)); + params.put(".runtime.version.major", matcher.group(2)); + params.put(".runtime.version.update", matcher.group(3)); + params.put(".runtime.version.minor", matcher.group(3)); + params.put(".runtime.version.security", matcher.group(4)); + params.put(".runtime.version.patch", matcher.group(5)); + params.put(".runtime.version.modifiers", matcher.group(7)); + } else { + params.put(".runtime.version", ""); + params.put(".runtime.version.release", ""); + params.put(".runtime.version.major", ""); + params.put(".runtime.version.update", ""); + params.put(".runtime.version.minor", ""); + params.put(".runtime.version.security", ""); + params.put(".runtime.version.patch", ""); + params.put(".runtime.version.modifiers", ""); + } + } + } + + protected File createRoot(Map p, + File outputDirectory, boolean dependentTask, String name) + throws PackagerException { + if (!outputDirectory.isDirectory() && !outputDirectory.mkdirs()) { + throw new RuntimeException(MessageFormat.format( + I18N.getString("error.cannot-create-output-dir"), + outputDirectory.getAbsolutePath())); + } + if (!outputDirectory.canWrite()) { + throw new RuntimeException(MessageFormat.format( + I18N.getString("error.cannot-write-to-output-dir"), + outputDirectory.getAbsolutePath())); + } + if (!dependentTask) { + Log.verbose(MessageFormat.format( + I18N.getString("message.creating-app-bundle"), + name, outputDirectory.getAbsolutePath())); + } + + // Create directory structure + File rootDirectory = new File(outputDirectory, name); + + if (rootDirectory.exists()) { + throw new PackagerException("error.root-exists", + rootDirectory.getAbsolutePath()); + } + + rootDirectory.mkdirs(); + + return rootDirectory; + } + +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AddLauncherArguments.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AddLauncherArguments.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AddLauncherArguments.java @@ -0,0 +1,187 @@ +/* + * 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.jpackage.internal; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.io.File; +import jdk.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 + * + * The add-launcher properties file may have any of: + * + * appVersion + * module + * add-modules + * main-jar + * main-class + * icon + * arguments + * java-options + * win-console + * + */ +class AddLauncherArguments { + + private final String name; + private final String filename; + private Map allArgs; + private Map 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.ADD_MODULES.getId(), + getOptionValue(CLIOptions.ADD_MODULES)); + + 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)); + + String argumentStr = getOptionValue(CLIOptions.ARGUMENTS); + putUnlessNullOrEmpty(bundleParams, + CLIOptions.ARGUMENTS.getId(), + Arguments.getArgumentList(argumentStr)); + + String jvmargsStr = getOptionValue(CLIOptions.JAVA_OPTIONS); + putUnlessNullOrEmpty(bundleParams, + 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 getLauncherMap() { + initLauncherMap(); + return bundleParams; + } + + private void putUnlessNull(Map params, + String param, Object value) { + if (value != null) { + params.put(param, value); + } + } + + private void putUnlessNullOrEmpty(Map params, + String param, Collection value) { + if (value != null && !value.isEmpty()) { + params.put(param, value); + } + } + + private void putUnlessNullOrEmpty(Map params, + String param, Map value) { + if (value != null && !value.isEmpty()) { + params.put(param, value); + } + } + + static Map merge( + Map original, + Map additional) { + Map tmp = new HashMap<>(original); + if (additional.containsKey("module")) { + tmp.remove("main-jar"); + tmp.remove("main-class"); + } else if (additional.containsKey("main-jar")) { + tmp.remove("module"); + // should we only remove add-modules when it wasn't actually passed + // but was inferred or empty ? + tmp.remove("add-modules"); + } + tmp.putAll(additional); + return tmp; + } + +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ArgAction.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ArgAction.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ArgAction.java @@ -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.jpackage.internal; + +@FunctionalInterface +interface ArgAction { + void execute(); +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Arguments.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Arguments.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Arguments.java @@ -0,0 +1,886 @@ +/* + * 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.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.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.jpackage.internal.resources.MainResources"); + + private static final String APPIMAGE_MODE = "create-app-image"; + private static final String INSTALLER_MODE = "create-installer"; + + private static final String FA_EXTENSIONS = "extension"; + private static final String FA_CONTENT_TYPE = "mime-type"; + private static final String FA_DESCRIPTION = "description"; + private static final String FA_ICON = "icon"; + + public static final BundlerParamInfo CREATE_APP_IMAGE = + new StandardBundlerParam<>( + APPIMAGE_MODE, + Boolean.class, + p -> Boolean.FALSE, + (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ? + true : Boolean.valueOf(s)); + + public static final BundlerParamInfo CREATE_INSTALLER = + new StandardBundlerParam<>( + INSTALLER_MODE, + Boolean.class, + p -> Boolean.FALSE, + (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ? + true : Boolean.valueOf(s)); + + // regexp for parsing args (for example, for additional launchers) + private static Pattern pattern = Pattern.compile( + "(?:(?:([\"'])(?:\\\\\\1|.)*?(?:\\1|$))|(?:\\\\[\"'\\s]|[^\\s]))++"); + + private DeployParams deployParams = null; + private BundlerType bundleType = null; + + private int pos = 0; + private List argList = null; + + private List allOptions = null; + + private String input = null; + private String output = null; + + private boolean hasMainJar = false; + private boolean hasMainClass = false; + private boolean hasMainModule = false; + private boolean hasTargetFormat = false; + private boolean hasAppImage = false; + public boolean userProvidedBuildRoot = false; + + private String buildRoot = null; + private String mainJarPath = null; + + private static boolean runtimeInstaller = false; + + private List platformBundlers = null; + + private List addLaunchers = null; + + private static Map argIds = new HashMap<>(); + private static Map 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) throws PackagerException { + argList = new ArrayList(args.length); + for (String arg : args) { + argList.add(arg); + } + Log.debug ("\njpackage argument list: \n" + argList + "\n"); + pos = 0; + + deployParams = new DeployParams(); + bundleType = BundlerType.NONE; + + allOptions = new ArrayList<>(); + + addLaunchers = new ArrayList<>(); + } + + // CLIOptions is public for DeployParamsTest + public enum CLIOptions { + CREATE_APP_IMAGE(APPIMAGE_MODE, OptionCategories.MODE, () -> { + context().bundleType = BundlerType.IMAGE; + context().deployParams.setTargetFormat("image"); + setOptionValue(APPIMAGE_MODE, true); + }), + + CREATE_INSTALLER(INSTALLER_MODE, OptionCategories.MODE, () -> { + setOptionValue(INSTALLER_MODE, true); + context().bundleType = BundlerType.INSTALLER; + String format = "installer"; + context().deployParams.setTargetFormat(format); + }), + + INSTALLER_TYPE("installer-type", OptionCategories.PROPERTY, () -> { + String type = popArg(); + if (BundlerType.INSTALLER.equals(context().bundleType)) { + context().deployParams.setTargetFormat(type); + context().hasTargetFormat = true; + } + setOptionValue("installer-type", type); + }), + + INPUT ("input", "i", OptionCategories.PROPERTY, () -> { + context().input = popArg(); + setOptionValue("input", context().input); + }), + + OUTPUT ("output", "o", OptionCategories.PROPERTY, () -> { + context().output = popArg(); + context().deployParams.setOutput(new File(context().output)); + }), + + DESCRIPTION ("description", "d", OptionCategories.PROPERTY), + + VENDOR ("vendor", OptionCategories.PROPERTY), + + APPCLASS ("main-class", OptionCategories.PROPERTY, () -> { + context().hasMainClass = true; + setOptionValue("main-class", popArg()); + }), + + NAME ("name", "n", OptionCategories.PROPERTY), + + IDENTIFIER ("identifier", OptionCategories.PROPERTY), + + VERBOSE ("verbose", OptionCategories.PROPERTY, () -> { + setOptionValue("verbose", true); + Log.setVerbose(true); + }), + + RESOURCE_DIR("resource-dir", + OptionCategories.PROPERTY, () -> { + String resourceDir = popArg(); + setOptionValue("resource-dir", resourceDir); + }), + + ARGUMENTS ("arguments", OptionCategories.PROPERTY, () -> { + List 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), + + JAVA_OPTIONS ("java-options", OptionCategories.PROPERTY, () -> { + List args = getArgumentList(popArg()); + args.forEach(a -> setOptionValue("java-options", a)); + }), + + FILE_ASSOCIATIONS ("file-associations", + OptionCategories.PROPERTY, () -> { + Map args = new HashMap<>(); + + // load .properties file + Map 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> associationList = + new ArrayList>(); + + 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-root", OptionCategories.PROPERTY, () -> { + context().buildRoot = popArg(); + context().userProvidedBuildRoot = true; + setOptionValue("temp-root", context().buildRoot); + }), + + INSTALL_DIR ("install-dir", OptionCategories.PROPERTY), + + PREDEFINED_APP_IMAGE ("app-image", OptionCategories.PROPERTY, ()-> { + setOptionValue("app-image", popArg()); + context().hasAppImage = true; + }), + + 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), + + MAC_SIGN ("mac-sign", "s", OptionCategories.PLATFORM_MAC, () -> { + setOptionValue("mac-sign", true); + }), + + MAC_BUNDLE_NAME ("mac-bundle-name", OptionCategories.PLATFORM_MAC), + + MAC_BUNDLE_IDENTIFIER("mac-bundle-identifier", + OptionCategories.PLATFORM_MAC), + + MAC_APP_STORE_CATEGORY ("mac-app-store-category", + OptionCategories.PLATFORM_MAC), + + MAC_BUNDLE_SIGNING_PREFIX ("mac-bundle-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_REGISTRY_NAME ("win-registry-name", OptionCategories.PLATFORM_WIN), + + 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-bundle-name", + OptionCategories.PLATFORM_LINUX), + + LINUX_DEB_MAINTAINER ("linux-deb-maintainer", + OptionCategories.PLATFORM_LINUX), + + LINUX_RPM_LICENSE_TYPE ("linux-rpm-license-type", + OptionCategories.PLATFORM_LINUX), + + LINUX_PACKAGE_DEPENDENCIES ("linux-package-deps", + OptionCategories.PLATFORM_LINUX), + + 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() { + String prefix = isMode() ? "" : "--"; + return prefix + this.id; + } + + String getShortIdWithPrefix() { + return this.shortId == null ? null : "-" + this.shortId; + } + + void execute() { + if (action != null) { + action.execute(); + } else { + defaultAction(); + } + } + + boolean isMode() { + return category == OptionCategories.MODE; + } + + OptionCategories getCategory() { + return category; + } + + 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 void prevArg() { + context().pos--; + } + + private static boolean hasNextArg() { + return context().pos < context().argList.size(); + } + } + + enum OptionCategories { + MODE, + MODULAR, + PROPERTY, + PLATFORM_MAC, + PLATFORM_WIN, + PLATFORM_LINUX; + } + + public boolean processArguments() throws Exception { + 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 (allOptions.isEmpty() || !allOptions.get(0).isMode()) { + // first argument should always be a mode. + throw new PackagerException("ERR_MissingMode"); + } + + if (hasMainJar && !hasMainClass) { + // try to get main-class from manifest + String mainClass = getMainClassFromManifest(); + if (mainClass != null) { + CLIOptions.setOptionValue( + CLIOptions.APPCLASS.getId(), mainClass); + } + } + + // display warning for arguments that are not supported + // for current configuration. + + validateArguments(); + + addResources(deployParams, input); + + deployParams.setBundleType(bundleType); + + List> 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 usedNames = new ArrayList(); + usedNames.add(bp.getName()); // add main app name + + for (AddLauncherArguments sl : addLaunchers) { + Map 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"); + } + + return generateBundle(bp.getBundleParamsAsMap()); + } catch (Exception e) { + if (Log.isVerbose()) { + throw e; + } else { + String msg1 = e.getMessage(); + Log.error(msg1); + if (e.getCause() != null && e.getCause() != e) { + String msg2 = e.getCause().getMessage(); + if (!msg1.contains(msg2)) { + Log.error(msg2); + } + } + return false; + } + } + } + + private void validateArguments() throws PackagerException { + CLIOptions mode = allOptions.get(0); + boolean imageOnly = (mode == CLIOptions.CREATE_APP_IMAGE); + boolean hasAppImage = allOptions.contains( + CLIOptions.PREDEFINED_APP_IMAGE); + boolean hasRuntime = allOptions.contains( + CLIOptions.PREDEFINED_RUNTIME_IMAGE); + boolean installerOnly = !imageOnly && hasAppImage; + boolean runtimeInstall = !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_NotImageOption", + option.getIdWithPrefix()); + } + } else if (installerOnly || runtimeInstall) { + if (!ValidOptions.checkIfInstallerSupported(option)) { + String key = runtimeInstaller ? + "ERR_NoInstallerEntryPoint" : "ERR_NotInstallerOption"; + throw new PackagerException(key, option.getIdWithPrefix()); + } + } + } + if (installerOnly && hasRuntime) { + // note --runtime-image is only for image or runtime installer. + throw new PackagerException("ERR_NotInstallerOption", + CLIOptions.PREDEFINED_RUNTIME_IMAGE.getIdWithPrefix()); + } + if (hasMainJar && hasMainModule) { + throw new PackagerException("ERR_BothMainJarAndModule"); + } + if (imageOnly && !hasMainJar && !hasMainModule) { + throw new PackagerException("ERR_NoEntryPoint"); + } + } + + private List getPlatformBundlers() { + + if (platformBundlers != null) { + return platformBundlers; + } + + platformBundlers = new ArrayList<>(); + for (jdk.jpackage.internal.Bundler bundler : + Bundlers.createBundlersInstance().getBundlers( + bundleType.toString())) { + if (hasTargetFormat && deployParams.getTargetFormat() != null && + !deployParams.getTargetFormat().equalsIgnoreCase( + bundler.getID())) { + continue; + } + if (bundler.supported(runtimeInstaller)) { + platformBundlers.add(bundler); + } + } + + return platformBundlers; + } + + private boolean generateBundle(Map params) + throws PackagerException { + + boolean bundleCreated = false; + + // the temp-root 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-root 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); + List bundlers = getPlatformBundlers(); + if (bundlers.isEmpty()) { + throw new PackagerException("ERR_InvalidInstallerType", + deployParams.getTargetFormat()); + } + PackagerException pe = null; + for (jdk.jpackage.internal.Bundler bundler : bundlers) { + Map localParams = new HashMap<>(params); + try { + if (bundler.validate(localParams)) { + File result = + bundler.execute(localParams, deployParams.outdir); + if (!userProvidedBuildRoot) { + bundler.cleanup(localParams); + } + if (result == null) { + throw new PackagerException("MSG_BundlerFailed", + bundler.getID(), bundler.getName()); + } + bundleCreated = true; // at least one bundle was created + } + Log.verbose(MessageFormat.format( + I18N.getString("message.bundle-created"), + bundler.getName())); + } catch (UnsupportedPlatformException upe) { + Log.debug(upe); + if (pe == null) { + pe = new PackagerException(upe, + "MSG_BundlerPlatformException", bundler.getName()); + } + } catch (ConfigException e) { + Log.debug(e); + if (pe == null) { + pe = (e.getAdvice() != null) ? + new PackagerException(e, + "MSG_BundlerConfigException", + bundler.getName(), e.getMessage(), e.getAdvice()) : + new PackagerException(e, + "MSG_BundlerConfigExceptionNoAdvice", + bundler.getName(), e.getMessage()); + } + } catch (RuntimeException re) { + Log.debug(re); + if (pe == null) { + pe = 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())); + } + } + } + if (pe != null) { + // throw packager exception only after trying all bundlers + throw pe; + } + return bundleCreated; + } + + private void addResources(DeployParams deployParams, + String inputdir) 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 fileNames; + fileNames = new ArrayList<>(); + try (Stream 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(); + } + + static boolean isCLIOption(String arg) { + return toCLIOption(arg) != null; + } + + static CLIOptions toCLIOption(String arg) { + CLIOptions option; + if ((option = argIds.get(arg)) == null) { + option = argShortIds.get(arg); + } + return option; + } + + static Map getArgumentMap(String inputString) { + Map map = new HashMap<>(); + List list = getArgumentList(inputString); + for (String pair : list) { + int equals = pair.indexOf("="); + if (equals != -1) { + String key = pair.substring(0, equals); + String value = pair.substring(equals+1, pair.length()); + map.put(key, value); + } + } + return map; + } + + static Map getPropertiesFromFile(String filename) { + Map 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 getArgumentList(String inputString) { + List 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; + } + +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BasicBundlers.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BasicBundlers.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BasicBundlers.java @@ -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.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. + *
      + *
    • Windows file image
    • + *
    • Mac .app
    • + *
    • Linux file image
    • + *
    • Windows MSI
    • + *
    • Windows EXE
    • + *
    • Mac DMG
    • + *
    • Mac PKG
    • + *
    • Linux DEB
    • + *
    • Linux RPM
    • + * + *
    + */ +public class BasicBundlers implements Bundlers { + + boolean defaultsLoaded = false; + + private final Collection bundlers = new CopyOnWriteArrayList<>(); + + @Override + public Collection getBundlers() { + return Collections.unmodifiableCollection(bundlers); + } + + @Override + public Collection 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)); + } + } + + @Override + public void loadDefaultBundlers() { + // no-op. We now load all bundlers from module system. + } + + // Loads bundlers from the META-INF/services direct + @Override + public void loadBundlersFromServices(ClassLoader cl) { + ServiceLoader loader = ServiceLoader.load(Bundler.class, cl); + for (Bundler aLoader : loader) { + bundlers.add(aLoader); + } + } + + @Override + public void loadBundler(Bundler bundler) { + bundlers.add(bundler); + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundleParams.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundleParams.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundleParams.java @@ -0,0 +1,373 @@ +/* + * 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.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.jpackage.internal.StandardBundlerParam.*; + +public class BundleParams { + + final protected Map params; + + // RelativeFileSet + public static final String PARAM_APP_RESOURCES = "appResources"; + + // BundlerType + public static final String PARAM_TYPE = "type"; + + // String + public static final String PARAM_BUNDLE_FORMAT = "bundleFormat"; + // String + 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 , 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 - 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 params) { + this.params = new HashMap<>(params); + } + + public void addAllBundleParams(Map p) { + params.putAll(p); + } + + public C fetchParam(BundlerParamInfo paramInfo) { + return paramInfo.fetchFrom(params); + } + + @SuppressWarnings("unchecked") + public C fetchParamWithDefault( + Class klass, C defaultValue, String... keys) { + for (String key : keys) { + Object o = params.get(key); + if (klass.isInstance(o)) { + return (C) o; + } else if (params.containsKey(key) && o == null) { + return null; + } else if (o != null) { + Log.debug("Bundle param " + key + " is not type " + klass); + } + } + return defaultValue; + } + + public C fetchParam(Class klass, String... keys) { + return fetchParamWithDefault(klass, null, keys); + } + + // 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 getBundleParamsAsMap() { + return new HashMap<>(params); + } + + public void setJvmargs(List jvmargs) { + putUnlessNullOrEmpty(JAVA_OPTIONS.getID(), jvmargs); + } + + public void setArguments(List arguments) { + putUnlessNullOrEmpty(ARGUMENTS.getID(), arguments); + } + + public void setAddModules(String value) { + putUnlessNull(StandardBundlerParam.ADD_MODULES.getID(), value); + } + + public void setLimitModules(String value) { + putUnlessNull(StandardBundlerParam.LIMIT_MODULES.getID(), value); + } + + public void setModulePath(String value) { + putUnlessNull(StandardBundlerParam.MODULE_PATH.getID(), value); + } + + public void setMainModule(String value) { + putUnlessNull(StandardBundlerParam.MODULE.getID(), value); + } + + public void setDebug(String value) { + putUnlessNull(JLinkBundlerHelper.DEBUG.getID(), value); + } + + public String getApplicationID() { + return fetchParam(IDENTIFIER); + } + + public String getApplicationClass() { + return fetchParam(MAIN_CLASS); + } + + public void setApplicationClass(String applicationClass) { + putUnlessNull(PARAM_APPLICATION_CLASS, applicationClass); + } + + public String getAppVersion() { + return fetchParam(VERSION); + } + + public void setAppVersion(String version) { + putUnlessNull(PARAM_VERSION, version); + } + + public String getDescription() { + return fetchParam(DESCRIPTION); + } + + public void setDescription(String s) { + putUnlessNull(PARAM_DESCRIPTION, s); + } + + public void setInstalldirChooser(Boolean b) { + putUnlessNull(PARAM_INSTALLDIR_CHOOSER, b); + } + + public String getName() { + return fetchParam(APP_NAME); + } + + public void setName(String name) { + putUnlessNull(PARAM_NAME, name); + } + + @SuppressWarnings("deprecation") + public BundlerType getType() { + return fetchParam(BundlerType.class, PARAM_TYPE); + } + + @SuppressWarnings("deprecation") + public void setType(BundlerType type) { + putUnlessNull(PARAM_TYPE, type); + } + + public String getBundleFormat() { + return fetchParam(String.class, PARAM_BUNDLE_FORMAT); + } + + public void setBundleFormat(String t) { + putUnlessNull(PARAM_BUNDLE_FORMAT, t); + } + + public boolean getVerbose() { + return fetchParam(VERBOSE); + } + + public List getJvmargs() { + return JAVA_OPTIONS.fetchFrom(params); + } + + public List getArguments() { + return ARGUMENTS.fetchFrom(params); + } + + public jdk.jpackage.internal.RelativeFileSet getAppResource() { + return fetchParam(APP_RESOURCES); + } + + public void setAppResource(jdk.jpackage.internal.RelativeFileSet fs) { + putUnlessNull(PARAM_APP_RESOURCES, fs); + } + + public void setAppResourcesList( + List rfs) { + putUnlessNull(APP_RESOURCES_LIST.getID(), rfs); + } + + public String getMainClassName() { + String applicationClass = getApplicationClass(); + + if (applicationClass == null) { + return null; + } + + int idx = applicationClass.lastIndexOf("."); + if (idx >= 0) { + return applicationClass.substring(idx+1); + } + return applicationClass; + } + + public String getCopyright() { + return fetchParam(COPYRIGHT); + } + + public void setCopyright(String c) { + putUnlessNull(PARAM_COPYRIGHT, c); + } + + private String mainJar = null; + + // assuming that application was packaged according to the rules + // we must have application jar, i.e. jar where we embed launcher + // and have main application class listed as main class! + // If there are more than one, or none - it will be treated as + // deployment error + // + // Note we look for both JavaFX executable jars and regular executable jars + // As long as main "application" entry point is the same it is main class + // (i.e. for FX jar we will use JavaFX manifest entry ...) + public String getMainApplicationJar() { + jdk.jpackage.internal.RelativeFileSet appResources = getAppResource(); + if (mainJar != null) { + if (getApplicationClass() == null) try { + if (appResources != null) { + File srcdir = appResources.getBaseDirectory(); + JarFile jf = new JarFile(new File(srcdir, mainJar)); + Manifest m = jf.getManifest(); + Attributes attrs = (m != null) ? + m.getMainAttributes() : null; + if (attrs != null) { + setApplicationClass( + attrs.getValue(Attributes.Name.MAIN_CLASS)); + } + } + } catch (IOException ignore) { + } + return mainJar; + } + + String applicationClass = getApplicationClass(); + + if (appResources == null || applicationClass == null) { + return null; + } + File srcdir = appResources.getBaseDirectory(); + for (String fname : appResources.getIncludedFiles()) { + JarFile jf; + try { + jf = new JarFile(new File(srcdir, fname)); + Manifest m = jf.getManifest(); + Attributes attrs = (m != null) ? m.getMainAttributes() : null; + if (attrs != null) { + boolean javaMain = applicationClass.equals( + attrs.getValue(Attributes.Name.MAIN_CLASS)); + + if (javaMain) { + mainJar = fname; + return mainJar; + } + } + } catch (IOException ignore) { + } + } + return null; + } + + public String getVendor() { + return fetchParam(VENDOR); + } + + public void setVendor(String vendor) { + putUnlessNull(PARAM_VENDOR, vendor); + } + + public String getEmail() { + return fetchParam(String.class, PARAM_EMAIL); + } + + public void setEmail(String email) { + putUnlessNull(PARAM_EMAIL, email); + } + + public void putUnlessNull(String param, Object value) { + if (value != null) { + params.put(param, value); + } + } + + public void putUnlessNullOrEmpty(String param, Collection value) { + if (value != null && !value.isEmpty()) { + params.put(param, value); + } + } + + public void putUnlessNullOrEmpty(String param, Map value) { + if (value != null && !value.isEmpty()) { + params.put(param, value); + } + } + +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundler.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundler.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundler.java @@ -0,0 +1,133 @@ +/* + * 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.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 A more verbose description of the bundler. + */ + String getDescription(); + + /** + * @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(); + + /** + * The parameters that this bundler uses to generate it's bundle. + * @return immutable collection + */ + Collection> getBundleParameters(); + + /** + * 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 UnsupportedPlatformException If the bundler cannot run on this + * platform (i.e. creating mac apps on windows) + * @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 params) + throws UnsupportedPlatformException, 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 parameters as specified by getBundleParameters. + * 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: + *
      + *
    • A required parameter is not found in the params list, for + * example missing the main class.
    • + *
    • A parameter has the wrong type of an object, for example a + * String where a File is required
    • + *
    • Bundler specific incompatibilities with the parameters, for + * example a bad version number format or an application id with + * forward slashes.
    • + *
    + */ + public File execute(Map params, + File outputParentDir) throws PackagerException; + + /** + * Removes temporary files that are used for bundling. + */ + public void cleanup(Map params); + + /** + * Returns "true" if this bundler is supported on current platform. + */ + public boolean supported(boolean runtimeInstaller); +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundlerParamInfo.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundlerParamInfo.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundlerParamInfo.java @@ -0,0 +1,139 @@ +/* + * 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.jpackage.internal; + +import java.util.Map; +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * BundlerParamInfo + * + * A BundlerParamInfo encapsulates an individual bundler parameter of type . + */ +class BundlerParamInfo { + + /** + * The command line and hashmap name of the parameter + */ + String id; + + /** + * Type of the parameter + */ + Class 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, T> defaultValueFunction; + + /** + * An optional string converter for command line arguments. + */ + BiFunction, T> stringConverter; + + String getID() { + return id; + } + + Class getValueType() { + return valueType; + } + + void setValueType(Class valueType) { + this.valueType = valueType; + } + + boolean getIsDefaultValue() { + return isDefaultValue; + } + + Function, T> getDefaultValueFunction() { + return defaultValueFunction; + } + + void setDefaultValueFunction( + Function, T> defaultValueFunction) { + this.defaultValueFunction = defaultValueFunction; + } + + BiFunction,T> + getStringConverter() { + return stringConverter; + } + + void setStringConverter(BiFunction, T> stringConverter) { + this.stringConverter = stringConverter; + } + + @SuppressWarnings("unchecked") + final T fetchFrom(Map params) { + return fetchFrom(params, true); + } + + @SuppressWarnings("unchecked") + final T fetchFrom(Map params, + boolean invokeDefault) { + Object o = params.get(getID()); + if (o instanceof String && getStringConverter() != null) { + return getStringConverter().apply((String)o, params); + } + + Class 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; + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundlerType.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundlerType.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundlerType.java @@ -0,0 +1,32 @@ +/* + * 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.jpackage.internal; + +enum BundlerType { + NONE, + IMAGE, // Generates app image only + INSTALLER // Generates installers +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundlers.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundlers.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundlers.java @@ -0,0 +1,143 @@ +/* + * 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.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 bundlersLoader = + ServiceLoader.load(Bundlers.class, servicesClassLoader); + Bundlers bundlers = null; + Iterator 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 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 getBundlers(String type); + + /** + * Loads the bundlers common to the JDK. A typical implementation + * would load: + *
      + *
    • Windows file image
    • + *
    • Mac .app
    • + *
    • Linux file image
    • + + *
    • Windows MSI
    • + *
    • Windows EXE
    • + *
    • Mac DMG
    • + *
    • Mac PKG
    • + *
    • Linux DEB
    • + *
    • Linux RPM
    • + * + *
    + * + * This method is called from the + * {@link #createBundlersInstance(ClassLoader)} + * and {@link #createBundlersInstance()} methods. + * NOTE: Because of the module system this method is now not used. + */ + void loadDefaultBundlers(); + + /** + * 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); + + /** + * Loads a specific bundler into the set of bundlers. + * Useful for a manually configured bundler. + * + * @param bundler the specific bundler to add + */ + void loadBundler(Bundler bundler); +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/CLIHelp.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/CLIHelp.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/CLIHelp.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.jpackage.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.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.isDebug()) ? + Platform.UNKNOWN : Platform.getPlatform(); + String types; + String pLaunchOptions; + String pInstallOptions; + String pInstallDir; + switch (platform) { + case MAC: + types = "{\"pkg\", \"dmg\"}"; + pLaunchOptions = I18N.getString("MSG_Help_mac_launcher"); + pInstallOptions = ""; + pInstallDir + = I18N.getString("MSG_Help_mac_linux_install_dir"); + break; + case LINUX: + types = "{\"rpm\", \"deb\"}"; + pLaunchOptions = ""; + pInstallOptions = I18N.getString("MSG_Help_linux_install"); + pInstallDir + = I18N.getString("MSG_Help_mac_linux_install_dir"); + break; + case WINDOWS: + types = "{\"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 = + "{\"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)); + } + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ConfigException.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ConfigException.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ConfigException.java @@ -0,0 +1,45 @@ +/* + * 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.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(Exception cause) { + super(cause); + this.advice = null; + } + + public String getAdvice() { + return advice; + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DeployParams.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DeployParams.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DeployParams.java @@ -0,0 +1,581 @@ +/* + * 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.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 resources = new ArrayList<>(); + + String id; + String vendor; + String email; + String description; + String licenseType; + String copyright; + String version; + Boolean systemWide; + Boolean serviceHint; + Boolean signBundle; + Boolean installdirChooser; + + String applicationClass; + + List params; + List arguments; //unnamed arguments + + // Java 9 modules support + String addModules = null; + String limitModules = null; + String modulePath = null; + String module = null; + String debugPort = null; + + File outdir = null; + + String appId = null; + + // list of jvm args + // (in theory string can contain spaces and need to be escaped + List jvmargs = new LinkedList<>(); + + // raw arguments to the bundler + Map bundlerArguments = new LinkedHashMap<>(); + + void setLicenseType(String licenseType) { + this.licenseType = licenseType; + } + + void setCopyright(String copyright) { + this.copyright = copyright; + } + + void setVersion(String version) { + this.version = version; + } + + void setSystemWide(Boolean systemWide) { + this.systemWide = systemWide; + } + + void setInstalldirChooser(Boolean installdirChooser) { + this.installdirChooser = installdirChooser; + } + + void setSignBundle(Boolean signBundle) { + this.signBundle = signBundle; + } + + void addJvmArg(String v) { + jvmargs.add(v); + } + + void setArguments(List args) { + this.arguments = args; + } + + List getArguments() { + return this.arguments; + } + + void addArgument(String arg) { + this.arguments.add(arg); + } + + void addAddModule(String value) { + if (addModules == null) { + addModules = value; + } + else { + addModules += "," + value; + } + } + + void addLimitModule(String value) { + if (limitModules == null) { + limitModules = value; + } + else { + limitModules += "," + value; + } + } + + String getModulePath() { + return this.modulePath; + } + + void setModulePath(String value) { + this.modulePath = value; + } + + void setModule(String value) { + this.module = value; + } + + void setDebug(String value) { + this.debugPort = value; + } + + void setDescription(String description) { + this.description = description; + } + + public void setAppId(String id) { + appId = id; + } + + void setParams(List params) { + this.params = params; + } + + void setVendor(String vendor) { + this.vendor = vendor; + } + + void setEmail(String email) { + this.email = email; + } + + void setApplicationClass(String applicationClass) { + this.applicationClass = applicationClass; + } + + File getOutput() { + return outdir; + } + + 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 expandFileset(File root) { + List 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 classpath = ""; + for (RelativeFileSet resource : resources) { + for (String file : resource.getIncludedFiles()) { + if (file.endsWith(".jar")) { + classpath += file + File.pathSeparator; + } + } + } + addBundleArgument( + StandardBundlerParam.CLASSPATH.getID(), classpath); + } + + private static File createFile(final File baseDir, final String path) { + final File testFile = new File(path); + return testFile.isAbsolute() ? + testFile : new File(baseDir == null ? + null : baseDir.getAbsolutePath(), path); + } + + 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 { + if (outdir == null) { + throw new PackagerException("ERR_MissingArgument", "--output"); + } + + 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 = (BundlerType.INSTALLER == getBundleType()) && + !hasAppImage && !hasModule && !hasMain && hasRuntimeImage; + + if (getBundleType() == BundlerType.IMAGE) { + // 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 (getBundleType() == BundlerType.INSTALLER) { + 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-root + 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"); + } + } + } + + boolean validateForBundle() { + boolean result = false; + + // Success + if (((applicationClass != null && !applicationClass.isEmpty()) || + (module != null && !module.isEmpty()))) { + result = true; + } + + return result; + } + + BundlerType bundleType = BundlerType.NONE; + String targetFormat = null; //means any + + void setBundleType(BundlerType type) { + bundleType = type; + } + + BundlerType getBundleType() { + return bundleType; + } + + void setTargetFormat(String t) { + targetFormat = t; + } + + String getTargetFormat() { + return targetFormat; + } + + private String getArch() { + String arch = System.getProperty("os.arch").toLowerCase(); + + if ("x86".equals(arch) || "i386".equals(arch) || "i486".equals(arch) + || "i586".equals(arch) || "i686".equals(arch)) { + arch = "x86"; + } else if ("x86_64".equals(arch) || "amd64".equals("arch")) { + arch = "x86_64"; + } + + return arch; + } + + static final Set 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 output folder! + bundleParams.setAppResourcesList(resources); + + bundleParams.setApplicationClass(applicationClass); + bundleParams.setAppVersion(version); + bundleParams.setType(bundleType); + bundleParams.setBundleFormat(targetFormat); + bundleParams.setVendor(vendor); + bundleParams.setEmail(email); + bundleParams.setInstalldirChooser(installdirChooser); + bundleParams.setCopyright(copyright); + bundleParams.setDescription(description); + + bundleParams.setJvmargs(jvmargs); + bundleParams.setArguments(arguments); + + if (addModules != null && !addModules.isEmpty()) { + bundleParams.setAddModules(addModules); + } + + if (limitModules != null && !limitModules.isEmpty()) { + bundleParams.setLimitModules(limitModules); + } + + if (modulePath != null && !modulePath.isEmpty()) { + bundleParams.setModulePath(modulePath); + } + + if (module != null && !module.isEmpty()) { + bundleParams.setMainModule(module); + } + + if (debugPort != null && !debugPort.isEmpty()) { + bundleParams.setDebug(debugPort); + } + + Map paramsMap = new TreeMap<>(); + if (params != null) { + for (Param p : params) { + paramsMap.put(p.name, p.value); + } + } + + Map unescapedHtmlParams = new TreeMap<>(); + Map escapedHtmlParams = new TreeMap<>(); + + // check for collisions + TreeSet 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; + } + + Map getBundlerArguments() { + return this.bundlerArguments; + } + + void putUnlessNull(String param, Object value) { + if (value != null) { + bundlerArguments.put(param, value); + } + } + + void putUnlessNullOrEmpty(String param, Map value) { + if (value != null && !value.isEmpty()) { + bundlerArguments.put(param, value); + } + } + + void putUnlessNullOrEmpty(String param, Collection value) { + if (value != null && !value.isEmpty()) { + bundlerArguments.put(param, value); + } + } + + @Override + public String toString() { + return "DeployParams {" + "output: " + outdir + + " resources: {" + resources + "}}"; + } + +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/EnumeratedBundlerParam.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/EnumeratedBundlerParam.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/EnumeratedBundlerParam.java @@ -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.jpackage.internal; + +import java.util.*; +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * EnumeratedBundlerParams + * + * 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: + * + *
    {@code
    + *     Set 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);
    + * }
    + * + */ +class EnumeratedBundlerParam extends BundlerParamInfo { + // 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 elements; + private final boolean strict; + + EnumeratedBundlerParam(String id, Class valueType, + Function, T> defaultValueFunction, + BiFunction, T> stringConverter, + Map 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 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(); + } + +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java @@ -0,0 +1,312 @@ +/* + * 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.jpackage.internal; + +import java.io.*; +import java.net.URL; +import java.util.Arrays; +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.ArrayList; +import java.util.List; + +/** + * 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() { + @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 { + Files.walkFileTree(src, new SimpleFileVisitor() { + @Override + public FileVisitResult preVisitDirectory(final Path dir, + final BasicFileAttributes attrs) throws IOException { + Files.createDirectories(dest.resolve(src.relativize(dir))); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(final Path file, + final BasicFileAttributes attrs) throws IOException { + Files.copy(file, dest.resolve(src.relativize(file))); + return FileVisitResult.CONTINUE; + } + }); + } + + public static void copyRecursive(Path src, Path dest, + final List excludes) throws IOException { + Files.walkFileTree(src, new SimpleFileVisitor() { + @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 copyFromURL(URL location, File file) throws IOException { + copyFromURL(location, file, false); + } + + public static void copyFromURL(URL location, File file, boolean append) + throws IOException { + if (location == null) { + throw new IOException("Missing input resource!"); + } + if (file.exists() && !append) { + file.delete(); + } + InputStream in = location.openStream(); + FileOutputStream out = new FileOutputStream(file, append); + byte[] buffer = new byte[1024]; + int len; + while ((len = in.read(buffer)) != -1) { + out.write(buffer, 0, len); + } + out.close(); + in.close(); + file.setReadOnly(); + file.setReadable(true, false); + } + + 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(); + + FileChannel source = null; + FileChannel destination = null; + source = new FileInputStream(sourceFile).getChannel(); + destination = new FileOutputStream(destFile).getChannel(); + if (destination != null && source != null) { + destination.transferFrom(source, 0, source.size()); + } + if (source != null) { + source.close(); + } + if (destination != null) { + destination.close(); + } + + //preserve executable bit! + if (sourceFile.canExecute()) { + destFile.setExecutable(true, false); + } + if (!sourceFile.canWrite()) { + destFile.setReadOnly(); + } + destFile.setReadable(true, false); + } + + public static long getFolderSize(File folder) { + long foldersize = 0; + + File[] children = folder.listFiles(); + if (children != null) { + for (File f : children) { + if (f.isDirectory()) { + foldersize += getFolderSize(f); + } else { + foldersize += f.length(); + } + } + } + + return foldersize; + } + + // run "launcher paramfile" in the directory where paramfile is kept + public static void run(String launcher, File paramFile, boolean verbose) + throws IOException { + if (paramFile != null && paramFile.exists()) { + ProcessBuilder pb = + new ProcessBuilder(launcher, paramFile.getName()); + pb = pb.directory(paramFile.getParentFile()); + exec(pb, verbose); + } + } + + public static void exec(ProcessBuilder pb, boolean verbose) + throws IOException { + exec(pb, verbose, false); + } + + public static void exec(ProcessBuilder pb, boolean verbose, + boolean testForPresenseOnly) throws IOException { + exec(pb, verbose, testForPresenseOnly, null); + } + + public static void exec(ProcessBuilder pb, boolean verbose, + boolean testForPresenseOnly, PrintStream consumer) + throws IOException { + pb.redirectErrorStream(true); + Log.verbose("Running " + + Arrays.toString(pb.command().toArray(new String[0])) + + (pb.directory() != null ? (" in " + pb.directory()) : "")); + Process p = pb.start(); + InputStreamReader isr = new InputStreamReader(p.getInputStream()); + BufferedReader br = new BufferedReader(isr); + String lineRead; + while ((lineRead = br.readLine()) != null) { + if (consumer != null) { + consumer.print(lineRead + '\n'); + } else { + Log.verbose(lineRead); + } + } + try { + int ret = p.waitFor(); + if (ret != 0 && !(testForPresenseOnly && ret != 127)) { + throw new IOException("Exec failed with code " + + ret + " command [" + + Arrays.toString(pb.command().toArray(new String[0])) + + " in " + (pb.directory() != null ? + pb.directory().getAbsolutePath() : + "unspecified directory")); + } + } catch (InterruptedException ex) { + } + } + + @SuppressWarnings("unchecked") + private static Process startProcess(Object... args) throws IOException { + final ArrayList argsList = new ArrayList<>(); + for (Object a : args) { + if (a instanceof List) { + argsList.addAll((List)a); + } else if (a instanceof String) { + argsList.add((String)a); + } + } + + return Runtime.getRuntime().exec( + argsList.toArray(new String[argsList.size()])); + } + + private static void logErrorStream(Process p) { + final BufferedReader err = + new BufferedReader(new InputStreamReader(p.getErrorStream())); + Thread t = new Thread(() -> { + try { + String line; + while ((line = err.readLine()) != null) { + Log.error(line); + } + } catch (IOException ioe) { + Log.verbose(ioe); + } + }); + t.setDaemon(true); + t.start(); + } + + public static int getProcessOutput(List result, Object... args) + throws IOException, InterruptedException { + final Process p = startProcess(args); + + List list = new ArrayList<>(); + final BufferedReader in = + new BufferedReader(new InputStreamReader(p.getInputStream())); + Thread t = new Thread(() -> { + try { + String line; + while ((line = in.readLine()) != null) { + list.add(line); + } + } catch (IOException ioe) { + jdk.jpackage.internal.Log.verbose(ioe); + } + }); + t.setDaemon(true); + t.start(); + + logErrorStream(p); + + int ret = p.waitFor(); + + result.clear(); + result.addAll(list); + + return ret; + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/InvalidBundlerParamException.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/InvalidBundlerParamException.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/InvalidBundlerParamException.java @@ -0,0 +1,33 @@ +/* + * 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.jpackage.internal; + +public class InvalidBundlerParamException extends RuntimeException { + private static final long serialVersionUID = 1L; + public InvalidBundlerParamException(String message) { + super(message); + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkBundlerHelper.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkBundlerHelper.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkBundlerHelper.java @@ -0,0 +1,464 @@ +/* + * 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.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.lang.module.Configuration; +import java.lang.module.ResolvedModule; +import java.lang.module.ModuleDescriptor; +import java.lang.module.ModuleFinder; +import java.lang.module.ModuleReference; + +final class JLinkBundlerHelper { + + private static final ResourceBundle I18N = ResourceBundle.getBundle( + "jdk.jpackage.internal.resources.MainResources"); + private static final String JRE_MODULES_FILENAME = + "jdk/jpackage/internal/resources/jre.list"; + private static final String SERVER_JRE_MODULES_FILENAME = + "jdk/jpackage/internal/resources/jre.module.list"; + + static final ToolProvider JLINK_TOOL = + ToolProvider.findFirst("jlink").orElseThrow(); + + private JLinkBundlerHelper() {} + + @SuppressWarnings("unchecked") + static final BundlerParamInfo DEBUG = + new StandardBundlerParam<>( + "-J-Xdebug", + Integer.class, + p -> null, + (s, p) -> { + return Integer.valueOf(s); + }); + + static String listOfPathToString(List value) { + String result = ""; + + for (Path path : value) { + if (result.length() > 0) { + result += File.pathSeparator; + } + + result += path.toString(); + } + + return result; + } + + static String setOfStringToString(Set value) { + String result = ""; + + for (String element : value) { + if (result.length() > 0) { + result += ","; + } + + result += element; + } + + return result; + } + + static File getMainJar(Map 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 getMainClass(Map params) { + String result = ""; + String mainModule = StandardBundlerParam.MODULE.fetchFrom(params); + if (mainModule != null) { + int index = mainModule.indexOf("/"); + if (index > 0) { + result = mainModule.substring(index + 1); + } + } else { + RelativeFileSet fileset = + StandardBundlerParam.MAIN_JAR.fetchFrom(params); + if (fileset != null) { + result = StandardBundlerParam.MAIN_CLASS.fetchFrom(params); + } else { + // possibly app-image + } + } + + return result; + } + + static String getMainModule(Map 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; + } + + private static Set getValidModules(List modulePath, + Set addModules, Set limitModules) { + ModuleHelper moduleHelper = new ModuleHelper( + modulePath, addModules, limitModules); + return removeInvalidModules(modulePath, moduleHelper.modules()); + } + + static void execute(Map params, + AbstractAppImageBuilder imageBuilder) + throws IOException, Exception { + List modulePath = + StandardBundlerParam.MODULE_PATH.fetchFrom(params); + Set addModules = + StandardBundlerParam.ADD_MODULES.fetchFrom(params); + Set limitModules = + StandardBundlerParam.LIMIT_MODULES.fetchFrom(params); + Path outputDir = imageBuilder.getRoot(); + String excludeFileList = imageBuilder.getExcludeFileList(); + 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 = addModules.isEmpty(); + + // 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 validModules = + getValidModules(modulePath, addModules, limitModules); + + if (mainModule != null) { + validModules.add(mainModule); + } + + Log.verbose(MessageFormat.format( + I18N.getString("message.modules"), validModules.toString())); + + runJLink(outputDir, modulePath, validModules, limitModules, + excludeFileList, new HashMap(), bindServices); + + imageBuilder.prepareApplicationFiles(); + } + + + // Returns the path to the JDK modules in the user defined module path. + static Path findPathOfModule( List modulePath, String moduleName) { + + for (Path path : modulePath) { + Path moduleNamePath = path.resolve(moduleName); + + if (Files.exists(moduleNamePath)) { + return path; + } + } + + 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 getDefaultModules( + Path[] paths, String[] addModules) { + + // the modules in the run-time image that export an API + Stream systemRoots = ModuleFinder.ofSystem().findAll().stream() + .map(ModuleReference::descriptor) + .filter(descriptor -> exportsAPI(descriptor)) + .map(ModuleDescriptor::name); + + Set roots; + if (addModules == null || addModules.length == 0) { + roots = systemRoots.collect(Collectors.toSet()); + } else { + var extraRoots = Stream.of(addModules); + roots = Stream.concat(systemRoots, + extraRoots).collect(Collectors.toSet()); + } + + ModuleFinder finder = ModuleFinder.ofSystem(); + if (paths != null && paths.length > 0) { + finder = ModuleFinder.compose(finder, ModuleFinder.of(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() + .filter(e -> !e.isQualified()) + .findAny() + .isPresent(); + } + + private static Set removeInvalidModules( + List modulePath, Set modules) { + Set result = new LinkedHashSet(); + ModuleManager mm = new ModuleManager(modulePath); + List lmodfiles = + mm.getModules(EnumSet.of(ModuleManager.SearchType.ModularJar, + ModuleManager.SearchType.Jmod, + ModuleManager.SearchType.ExplodedModule)); + + HashMap validModules = new HashMap<>(); + + for (ModFile modFile : lmodfiles) { + validModules.put(modFile.getModName(), modFile); + } + + for (String name : modules) { + if (validModules.containsKey(name)) { + result.add(name); + } else { + Log.error(MessageFormat.format( + I18N.getString("warning.module.does.not.exist"), name)); + } + } + + return result; + } + + 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 modules = new HashSet<>(); + private enum Macros {None, AllModulePath, AllRuntime} + + ModuleHelper(List paths, Set addModules, + Set limitModules) { + boolean addAllModulePath = false; + boolean addDefaultMods = false; + + for (Iterator 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.toArray(new Path[0]), + addModules.toArray(new String[0]))); + } + } + + Set modules() { + return modules; + } + + private static Set getModuleNamesFromPath(List Value) { + Set result = new LinkedHashSet(); + ModuleManager mm = new ModuleManager(Value); + List modFiles = mm.getModules( + EnumSet.of(ModuleManager.SearchType.ModularJar, + ModuleManager.SearchType.Jmod, + ModuleManager.SearchType.ExplodedModule)); + + for (ModFile modFile : modFiles) { + result.add(modFile.getModName()); + } + return result; + } + } + + private static void runJLink(Path output, List modulePath, + Set modules, Set limitModules, String excludes, + HashMap user, boolean bindServices) + throws IOException { + + // This is just to ensure jlink is given a non-existant directory + // The passed in output path should be non-existant or empty directory + IOUtils.deleteRecursive(output.toFile()); + + ArrayList args = new ArrayList(); + 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 (excludes != null) { + args.add("--exclude-files"); + args.add(excludes); + } + if (user != null && !user.isEmpty()) { + for (Map.Entry 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 IOException("jlink failed with: " + jlinkOut); + } else if (jlinkOut.length() > 0) { + Log.verbose("jlink output: " + jlinkOut); + } + } + + private static String getPathList(List 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 strings) { + String ret = null; + for (String s : strings) { + if (ret == null) { + ret = s; + } else { + ret += "," + s; + } + } + return (ret == null) ? null : Matcher.quoteReplacement(ret); + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JPackageToolProvider.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JPackageToolProvider.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JPackageToolProvider.java @@ -0,0 +1,51 @@ +/* + * 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.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 jdk.jpackage.main.Main.run(out, err, args); + } catch (Exception ignored) { + return -1; + } + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Log.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Log.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Log.java @@ -0,0 +1,195 @@ +/* + * 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.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; + + public Logger(boolean v) { + verbose = v; + } + + public void setVerbose(boolean v) { + verbose = v; + } + + 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 && (Log.debug || verbose)) { + t.printStackTrace(out); + } else if (Log.debug || verbose) { + t.printStackTrace(System.out); + } + } + + public void verbose(String msg) { + if (out != null && (Log.debug || verbose)) { + out.println(msg); + } else if (Log.debug || verbose) { + System.out.println(msg); + } + } + + public void debug(String msg) { + if (out != null && Log.debug) { + out.println(msg); + } else if (Log.debug) { + System.out.println(msg); + } + } + } + + private static Logger delegate = null; + private static boolean debug = + "true".equals(System.getenv("JPACKAGE_DEBUG")); + + public static void setLogger(Logger l) { + delegate = l; + if (l == null) { + delegate = new Logger(false); + } + } + + public static Logger getLogger() { + return delegate; + } + + 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(boolean v) { + if (delegate != null) { + delegate.setVerbose(v); + } + } + + public static boolean isVerbose() { + if (delegate != null) { + return delegate.isVerbose(); + } + + return false; // Off by default + } + + public static void verbose(String msg) { + if (delegate != null) { + delegate.verbose(msg); + } + } + + public static void verbose(Throwable t) { + if (delegate != null) { + delegate.verbose(t); + } + } + + public static void debug(String msg) { + if (delegate != null) { + delegate.debug(msg); + } + } + + public static void debug(Throwable t) { + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + try (PrintStream ps = new PrintStream(baos)) { + t.printStackTrace(ps); + } + debug(baos.toString()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static boolean isDebug() { + return debug; + } + + public static void setDebug(boolean debug) { + Log.debug = debug; + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ModFile.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ModFile.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ModFile.java @@ -0,0 +1,126 @@ +/* + * 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.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; + + try { + for (ZipEntry entry = zip.getNextEntry(); entry != null; + entry = zip.getNextEntry()) { + if (entry.getName().matches("module-info.class")) { + result = JarType.ModularJar; + break; + } + } + + zip.close(); + } catch (IOException ex) { + } + } catch (FileNotFoundException e) { + } + + return result; + } + + private static String getFileWithoutExtension(String FileName) { + return FileName.replaceFirst("[.][^.]+$", ""); + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ModuleManager.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ModuleManager.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ModuleManager.java @@ -0,0 +1,122 @@ +/* + * 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.jpackage.internal; + +import java.io.File; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; + +final class ModuleManager { + private final List folders = new ArrayList(); + + enum SearchType {UnnamedJar, ModularJar, Jmod, ExplodedModule} + + ModuleManager(String folders) { + super(); + String lfolders = folders.replaceAll("^\"|\"$", ""); + List paths = new ArrayList(); + + for (String folder : + Arrays.asList(lfolders.split(File.pathSeparator))) { + File file = new File(folder); + paths.add(file.toPath()); + } + + initialize(paths); + } + + ModuleManager(List Paths) { + super(); + initialize(Paths); + } + + private void initialize(List Paths) { + for (Path path : Paths) { + folders.add(path.toString().replaceAll("^\"|\"$", "")); + } + } + + List getModules() { + return getModules(EnumSet.of(SearchType.UnnamedJar, + SearchType.ModularJar, SearchType.Jmod, + SearchType.ExplodedModule)); + } + + List getModules(EnumSet Search) { + List result = new ArrayList(); + + for (String folder : folders) { + result.addAll(getAllModulesInDirectory(folder, Search)); + } + + return result; + } + + private static List getAllModulesInDirectory(String folder, + EnumSet Search) { + List result = new ArrayList(); + File lfolder = new File(folder); + File[] files = { lfolder }; + if (lfolder.isDirectory()) { + files = lfolder.listFiles(); + } + + if (files != null) { + for (File file : files) { + ModFile modFile = new ModFile(file); + + switch (modFile.getModType()) { + case Unknown: + break; + case UnnamedJar: + if (Search.contains(SearchType.UnnamedJar)) { + result.add(modFile); + } + break; + case ModularJar: + if (Search.contains(SearchType.ModularJar)) { + result.add(modFile); + } + break; + case Jmod: + if (Search.contains(SearchType.Jmod)) { + result.add(modFile); + } + break; + case ExplodedModule: + if (Search.contains(SearchType.ExplodedModule)) { + result.add(modFile); + } + break; + } + } + } + return result; + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagerException.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagerException.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagerException.java @@ -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.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.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); + } + +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Param.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Param.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Param.java @@ -0,0 +1,40 @@ +/* + * 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.jpackage.internal; + +class Param { + String name; + String value; + + void setName(String name) { + this.name = name; + } + + void setValue(String value) { + this.value = value; + } + +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Platform.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Platform.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Platform.java @@ -0,0 +1,105 @@ +/* + * 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.jpackage.internal; + +import java.util.regex.Pattern; + +/** + * Platform + * + * Use Platform 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; + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/RelativeFileSet.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/RelativeFileSet.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/RelativeFileSet.java @@ -0,0 +1,117 @@ +/* + * 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.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 files = new LinkedHashSet<>(); + + RelativeFileSet(RelativeFileSet copy) { + basedir = copy.basedir; + files = new LinkedHashSet<>(copy.files); + } + + RelativeFileSet(File base, Collection 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)); + } + } + } + + void upshift() { + String root = basedir.getName(); + basedir = basedir.getParentFile(); + Set newFiles = new LinkedHashSet<>(); + for (String s : files) { + newFiles.add(root + File.separator + s); + } + files = newFiles; + } + + RelativeFileSet(File base, Set files) { + this(base, (Collection) files); + } + + boolean contains(String[] requiredFiles) { + boolean result = true; + + for(String fname: requiredFiles) { + if (!files.contains(fname)) { + Log.debug(" RelativeFileSet does not contain [" + fname + "]"); + result = false; + } + } + + return result; + } + + boolean contains(String requiredFile) { + if (files.contains(requiredFile)) { + return true; + } else { + Log.debug("RelativeFileSet does not contain [" +requiredFile+ "]"); + return false; + } + } + + File getBaseDirectory() { + return basedir; + } + + Set getIncludedFiles() { + return files; + } + + @Override + public String toString() { + if (files.size() == 1) { + return "" + basedir + File.pathSeparator + files; + } + return "RelativeFileSet {basedir:" + basedir + + ", files: {" + files + "}"; + } + +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/StandardBundlerParam.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/StandardBundlerParam.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/StandardBundlerParam.java @@ -0,0 +1,771 @@ +/* + * 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.jpackage.internal; + +import jdk.jpackage.internal.BundleParams; +import jdk.jpackage.internal.AbstractAppImageBuilder; + +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +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.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Properties; +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.regex.Pattern; +import java.util.stream.Collectors; + +/** + * 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 extends BundlerParamInfo { + + private static final ResourceBundle I18N = ResourceBundle.getBundle( + "jdk.jpackage.internal.resources.MainResources"); + private static final String JAVABASEJMOD = "java.base.jmod"; + + StandardBundlerParam(String id, Class valueType, + Function, T> defaultValueFunction, + BiFunction, T> stringConverter) + { + this.id = id; + this.valueType = valueType; + this.defaultValueFunction = defaultValueFunction; + this.stringConverter = stringConverter; + } + + static final StandardBundlerParam 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> APP_RESOURCES_LIST = + new StandardBundlerParam<>( + BundleParams.PARAM_APP_RESOURCES + "List", + (Class>) (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 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 MAIN_JAR = + new StandardBundlerParam<>( + Arguments.CLIOptions.MAIN_JAR.getId(), + RelativeFileSet.class, + params -> { + extractMainClassInfoFromAppResources(params); + return (RelativeFileSet) params.get("mainJar"); + }, + (s, p) -> getMainJar(s, p) + ); + + // TODO: test CLASSPATH jar manifest Attributet + static final StandardBundlerParam CLASSPATH = + new StandardBundlerParam<>( + "classpath", + String.class, + params -> { + extractMainClassInfoFromAppResources(params); + String cp = (String) params.get("classpath"); + return cp == null ? "" : cp; + }, + (s, p) -> s.replace(File.pathSeparator, " ") + ); + + static final StandardBundlerParam 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.getMainClass(params); + } + return s; + }, + (s, p) -> s + ); + + static final StandardBundlerParam PREDEFINED_RUNTIME_IMAGE = + new StandardBundlerParam<>( + Arguments.CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId(), + File.class, + params -> null, + (s, p) -> new File(s) + ); + + static final StandardBundlerParam 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 ICON = + new StandardBundlerParam<>( + Arguments.CLIOptions.ICON.getId(), + File.class, + params -> null, + (s, p) -> new File(s) + ); + + static final StandardBundlerParam VENDOR = + new StandardBundlerParam<>( + Arguments.CLIOptions.VENDOR.getId(), + String.class, + params -> I18N.getString("param.vendor.default"), + (s, p) -> s + ); + + static final StandardBundlerParam 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 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> ARGUMENTS = + new StandardBundlerParam<>( + Arguments.CLIOptions.ARGUMENTS.getId(), + (Class>) (Object) List.class, + params -> Collections.emptyList(), + (s, p) -> splitStringWithEscapes(s) + ); + + @SuppressWarnings("unchecked") + static final StandardBundlerParam> JAVA_OPTIONS = + new StandardBundlerParam<>( + Arguments.CLIOptions.JAVA_OPTIONS.getId(), + (Class>) (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 VERSION = + new StandardBundlerParam<>( + Arguments.CLIOptions.VERSION.getId(), + String.class, + params -> I18N.getString("param.version.default"), + (s, p) -> s + ); + + @SuppressWarnings("unchecked") + public static final StandardBundlerParam LICENSE_FILE = + new StandardBundlerParam<>( + Arguments.CLIOptions.LICENSE_FILE.getId(), + String.class, + params -> null, + (s, p) -> s + ); + + static final StandardBundlerParam TEMP_ROOT = + new StandardBundlerParam<>( + Arguments.CLIOptions.TEMP_ROOT.getId(), + File.class, + params -> { + try { + return Files.createTempDirectory( + "jdk.jpackage").toFile(); + } catch (IOException ioe) { + return null; + } + }, + (s, p) -> new File(s) + ); + + public static final StandardBundlerParam 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 IDENTIFIER = + new StandardBundlerParam<>( + Arguments.CLIOptions.IDENTIFIER.getId(), + 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 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 RESOURCE_DIR = + new StandardBundlerParam<>( + Arguments.CLIOptions.RESOURCE_DIR.getId(), + File.class, + params -> null, + (s, p) -> new File(s) + ); + + static final BundlerParamInfo INSTALL_DIR = + new StandardBundlerParam<>( + Arguments.CLIOptions.INSTALL_DIR.getId(), + String.class, + params -> null, + (s, p) -> s + ); + + static final StandardBundlerParam 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>> ADD_LAUNCHERS = + new StandardBundlerParam<>( + Arguments.CLIOptions.ADD_LAUNCHER.getId(), + (Class>>) (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 + >> FILE_ASSOCIATIONS = + new StandardBundlerParam<>( + Arguments.CLIOptions.FILE_ASSOCIATIONS.getId(), + (Class>>) (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> FA_EXTENSIONS = + new StandardBundlerParam<>( + "fileAssociation.extension", + (Class>) (Object) List.class, + params -> null, // null means not matched to an extension + (s, p) -> Arrays.asList(s.split("(,|\\s)+")) + ); + + @SuppressWarnings("unchecked") + static final StandardBundlerParam> FA_CONTENT_TYPE = + new StandardBundlerParam<>( + "fileAssociation.contentType", + (Class>) (Object) List.class, + params -> null, + // null means not matched to a content/mime type + (s, p) -> Arrays.asList(s.split("(,|\\s)+")) + ); + + static final StandardBundlerParam FA_DESCRIPTION = + new StandardBundlerParam<>( + "fileAssociation.description", + String.class, + params -> APP_NAME.fetchFrom(params) + " File", + null + ); + + static final StandardBundlerParam FA_ICON = + new StandardBundlerParam<>( + "fileAssociation.icon", + File.class, + ICON::fetchFrom, + (s, p) -> new File(s) + ); + + @SuppressWarnings("unchecked") + static final BundlerParamInfo> MODULE_PATH = + new StandardBundlerParam<>( + Arguments.CLIOptions.MODULE_PATH.getId(), + (Class>) (Object)List.class, + p -> { return getDefaultModulePath(); }, + (s, p) -> { + List 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(); + } + + // Add the default JDK module path to the module path. + if (javaBasePath == null) { + List 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 MODULE = + new StandardBundlerParam<>( + Arguments.CLIOptions.MODULE.getId(), + String.class, + p -> null, + (s, p) -> { + return String.valueOf(s); + }); + + @SuppressWarnings("unchecked") + static final BundlerParamInfo> ADD_MODULES = + new StandardBundlerParam<>( + Arguments.CLIOptions.ADD_MODULES.getId(), + (Class>) (Object) Set.class, + p -> new LinkedHashSet(), + (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(","))) + ); + + @SuppressWarnings("unchecked") + static final BundlerParamInfo> LIMIT_MODULES = + new StandardBundlerParam<>( + "limit-modules", + (Class>) (Object) Set.class, + p -> new LinkedHashSet(), + (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(","))) + ); + + static boolean isRuntimeInstaller(Map p) { + if (p.containsKey(MODULE.getID()) || + p.containsKey(MAIN_JAR.getID()) || + p.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 p.containsKey(PREDEFINED_RUNTIME_IMAGE.getID()); + } + + static File getPredefinedAppImage(Map p) { + File applicationImage = null; + if (PREDEFINED_APP_IMAGE.fetchFrom(p) != null) { + applicationImage = PREDEFINED_APP_IMAGE.fetchFrom(p); + Log.debug("Using App Image from " + applicationImage); + 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 p, + AbstractAppImageBuilder appBuilder) + throws IOException , ConfigException { + File image = PREDEFINED_RUNTIME_IMAGE.fetchFrom(p); + if (!image.exists()) { + throw new ConfigException( + MessageFormat.format(I18N.getString( + "message.runtime-image-dir-does-not-exist"), + PREDEFINED_RUNTIME_IMAGE.getID(), + image.toString()), + MessageFormat.format(I18N.getString( + "message.runtime-image-dir-does-not-exist.advice"), + PREDEFINED_RUNTIME_IMAGE.getID())); + } + // copy whole runtime, need to skip jmods and src.zip + final List excludes = Arrays.asList("jmods", "src.zip"); + IOUtils.copyRecursive(image.toPath(), appBuilder.getRoot(), excludes); + + // if module-path given - copy modules to appDir/mods + List modulePath = + StandardBundlerParam.MODULE_PATH.fetchFrom(p); + List 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(); + } + + static void extractMainClassInfoFromAppResources( + Map 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 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 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 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 || + hasModule || hasAppImage || isRuntimeInstaller(params)) { + return; + } + + 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 splitStringWithEscapes(String s) { + List l = new ArrayList<>(); + StringBuilder current = new StringBuilder(); + boolean quoted = false; + boolean escaped = false; + for (char c : s.toCharArray()) { + if (escaped) { + current.append(c); + } else if ('"' == c) { + quoted = !quoted; + } else if (!quoted && Character.isWhitespace(c)) { + l.add(current.toString()); + current = new StringBuilder(); + } else { + current.append(c); + } + } + l.add(current.toString()); + return l; + } + + private static List + createAppResourcesListFromString(String s, + Map objectObjectMap) { + List 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 theFiles = new HashSet<>(); + try { + Files.walk(f.toPath()) + .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 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 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 getDefaultModulePath() { + List result = new ArrayList(); + 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 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; + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/UnsupportedPlatformException.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/UnsupportedPlatformException.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/UnsupportedPlatformException.java @@ -0,0 +1,30 @@ +/* + * 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.jpackage.internal; + +public class UnsupportedPlatformException extends Exception { + private static final long serialVersionUID = 1L; +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ValidOptions.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ValidOptions.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ValidOptions.java @@ -0,0 +1,143 @@ +/* + * 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.jpackage.internal; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import jdk.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 mode, CLIOptions arg) + * Determine if the given arg is valid in the given mode. + * + * checkIfOtherSupported(CLIOptions mode, CLIOptions arg) + * Determine if the given arg is valid in the a different mode. + */ +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 options = new HashMap<>(); + + + // initializing list of mandatory arguments + static { + options.put(CLIOptions.CREATE_APP_IMAGE.getId(), USE.ALL); + options.put(CLIOptions.CREATE_INSTALLER.getId(), USE.ALL); + 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.IDENTIFIER.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.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.INSTALLER_TYPE.getId(), USE.INSTALL); + 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_REGISTRY_NAME.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_CATEGORY.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_RPM_LICENSE_TYPE.getId(), USE.INSTALL); + options.put(CLIOptions.LINUX_PACKAGE_DEPENDENCIES.getId(), + USE.INSTALL); + options.put(CLIOptions.LINUX_MENU_GROUP.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; + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/VersionExtractor.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/VersionExtractor.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/VersionExtractor.java @@ -0,0 +1,91 @@ +/* + * 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.jpackage.internal; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.text.MessageFormat; +import java.util.ResourceBundle; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class VersionExtractor extends PrintStream { + + private static final ResourceBundle I18N = ResourceBundle.getBundle( + "jdk.jpackage.internal.resources.MainResources"); + + private final String pattern; + private String version = null; + + public VersionExtractor(String pattern) { + super(new ByteArrayOutputStream()); + + this.pattern = pattern; + } + + public String getVersion() { + if (version == null) { + String content + = new String(((ByteArrayOutputStream) out).toByteArray()); + Pattern p = Pattern.compile(pattern); + Matcher matcher = p.matcher(content); + if (matcher.find()) { + version = matcher.group(1); + } + } + return version; + } + + public static boolean isLessThan(String version, String compareTo) + throws RuntimeException { + if (version == null || version.isEmpty()) { + throw new RuntimeException(MessageFormat.format( + I18N.getString("ERR_VersionComparison"), + version, compareTo)); + } + + if (compareTo == null || compareTo.isEmpty()) { + throw new RuntimeException(MessageFormat.format( + I18N.getString("ERR_VersionComparison"), + version, compareTo)); + } + + String [] versionArray = version.trim().split(Pattern.quote(".")); + String [] compareToArray = compareTo.trim().split(Pattern.quote(".")); + + for (int i = 0; i < versionArray.length; i++) { + int v1 = Integer.parseInt(versionArray[i]); + int v2 = Integer.parseInt(compareToArray[i]); + if (v1 < v2) { + return true; + } else if (v1 > v2) { + return false; + } + } + + return false; + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/HelpResources.properties b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/HelpResources.properties new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/HelpResources.properties @@ -0,0 +1,279 @@ +# +# 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 \n\ +\n\ +where mode is one of: \n\ +\ create-app-image\n\ +\ Generates a platform-specific application image.\n\ +\ create-installer\n\ +\ Generates a platform-specific installer for the application.\n\ +\ \n\ +Sample usages:\n\ +--------------\n\ +\ Generate a non-modular application image:\n\ +\ jpackage create-app-image -o outputdir -i inputdir -n name \\\n\ +\ --main-class className --main-jar MyJar.jar\n\ +\ Generate a modular application image:\n\ +\ jpackage create-app-image -o outputdir -n name \\\n\ +\ -p modulePath -m moduleName/className\n\ +\ To provide your own options to jlink, run jlink separately:\n\ +\ jlink --output appRuntimeImage -p ModulePath -m moduleName\\\n\ +\ --no-header-files [...]\n\ +\ jpackage create-app-image -o outputdir -n name\\\n\ +\ -m moduleName/className --runtime-image appRuntimeIMage\n\ +\ Generate an application installer:\n\ +\ jpackage create-installer -o outputdir -n name \\\n\ +\ -p modulePath -m moduleName/className\n\ +\ jpackage create-installer -i inputdir -o outputdir -n name \\\n\ +\ --main-class package.ClassName --main-jar MyJar.jar\n\ +\ jpackage create-installer -o outputdir -n \\\n\ +\ --app-image [--installer-type ]\n\ +\ Generate a Java runtime installer:\n\ +\ jpackage create-installer -o outputdir -n name \\\n\ +\ --runtime-image \n\ +\n\ +Generic Options:\n\ +\ @ \n\ +\ Read options and/or mode from a file \n\ +\ This option can be used multiple times.\n\ +\ --app-version \n\ +\ Version of the application and/or installer\n\ +\ --copyright \n\ +\ Copyright for the application\n\ +\ --description \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 \n\ +\ Name of the application and/or installer\n\ +\ --output -o \n\ +\ Path where generated output file is placed\n\ +\ (absolute path or relative to the current directory)\n\ +\ --temp-root \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-root 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 \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 [,...]\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 ...\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\ +\ --runtime-image \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. --bind-services will also be added if\n\ +\ --add-modules is not specified.\n\ +\n\ +\Options for creating the application image:\n\ +\ --icon \n\ +\ Path of the icon of the application bundle\n\ +\ (absolute path or relative to the current directory)\n\ +\ --input -i \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 =\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", "add-modules", "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
    \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 \n\ +\ Options to pass to the Java runtime\n\ +\ This option can be used multiple times.\n\ +\ --main-class \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
    \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 [/
    ]\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 installer(s):\n\ +\ --app-image \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\ +\ See create-app-image mode options to create the application image.\n\ +\ --file-associations \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\ +\ --identifier \n\ +\ An identifier that uniquely identifies the application\n\ +\ Defaults to the main class name.\n\ +\ The value should be a valid DNS name.\n\ +\ --install-dir \n\ +\ {4}\ +\ --installer-type \n\ +\ The type of the installer to create\n\ +\ Valid values are: {1} \n\ +\ If this option is not specified (in create-installer mode) all\n\ +\ supported types of installable packages for the current\n\ +\ platform will be created.\n\ +\ --license-file \n\ +\ Path to the license file\n\ +\ (absolute path or relative to the current directory)\n\ +\ --resource-dir \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 \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 installer.\n\ +\n\ +\Platform dependent options for creating the application installer(s):\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 \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-registry-name \n\ +\ Name of the application for registry references.\n\ +\ The default is the Application Name with only\n\ +\ alphanumerics, dots, and dashes (no whitespace)\n\ +\ --win-shortcut\n\ +\ Creates a desktop shortcut for the application\n\ +\ --win-upgrade-uuid \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-bundle-identifier \n\ +\ An identifier that uniquely identifies the application for MacOSX\n\ +\ Defaults to the value of --identifier option.\n\ +\ May only use alphanumeric (A-Z,a-z,0-9), hyphen (-),\n\ +\ and period (.) characters.\n\ +\ --mac-bundle-name \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-bundle-signing-prefix \n\ +\ When signing the application bundle, this value is prefixed to all\n\ +\ components that need to be signed that don't have\n\ +\ an existing bundle identifier.\n\ +\ --mac-sign\n\ +\ Request that the bundle be signed\n\ +\ --mac-signing-keychain \n\ +\ Path of the keychain to use\n\ +\ (absolute path or relative to the current directory)\n\ +\ If not specified, the standard keychains are used.\n\ +\ --mac-signing-key-user-name \n\ +\ User name portion of the typical\n\ +\ "Mac Developer ID Application: " signing key\n\ + +MSG_Help_linux_install=\ +\ --linux-bundle-name \n\ +\ Name for Linux bundle, defaults to the application name\n\ +\ --linux-deb-maintainer \n\ +\ Maintainer for .deb bundle\n\ +\ --linux-menu-group \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 \n\ +\ Type of the license ("License: " of the RPM .spec)\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 the application\n\ +\ such as "Program Files" or "AppData" on Windows.\n\ + +MSG_Help_no_args=Usage: jpackage \n\ +\Use jpackage --help (or -h) for a list of possible options\ + diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/HelpResources_ja.properties b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/HelpResources_ja.properties new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/HelpResources_ja.properties @@ -0,0 +1,279 @@ +# +# 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 \n\ +\n\ +where mode is one of: \n\ +\ create-app-image\n\ +\ Generates a platform-specific application image.\n\ +\ create-installer\n\ +\ Generates a platform-specific installer for the application.\n\ +\ \n\ +Sample usages:\n\ +--------------\n\ +\ Generate a non-modular application image:\n\ +\ jpackage create-app-image -o outputdir -i inputdir -n name \\\n\ +\ --main-class className --main-jar MyJar.jar\n\ +\ Generate a modular application image:\n\ +\ jpackage create-app-image -o outputdir -n name \\\n\ +\ -p modulePath -m moduleName/className\n\ +\ To provide your own options to jlink, run jlink separately:\n\ +\ jlink --output appRuntimeImage -p ModulePath -m moduleName\\\n\ +\ --no-header-files [...]\n\ +\ jpackage create-app-image -o outputdir -n name\\\n\ +\ -m moduleName/className --runtime-image appRuntimeIMage\n\ +\ Generate an application installer:\n\ +\ jpackage create-installer -o outputdir -n name \\\n\ +\ -p modulePath -m moduleName/className\n\ +\ jpackage create-installer -i inputdir -o outputdir -n name \\\n\ +\ --main-class package.ClassName --main-jar MyJar.jar\n\ +\ jpackage create-installer -o outputdir -n \\\n\ +\ --app-image [--installer-type ]\n\ +\ Generate a Java runtime installer:\n\ +\ jpackage create-installer -o outputdir -n name \\\n\ +\ --runtime-image \n\ +\n\ +Generic Options:\n\ +\ @ \n\ +\ Read options and/or mode from a file \n\ +\ This option can be used multiple times.\n\ +\ --app-version \n\ +\ Version of the application and/or installer\n\ +\ --copyright \n\ +\ Copyright for the application\n\ +\ --description \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 \n\ +\ Name of the application and/or installer\n\ +\ --output -o \n\ +\ Path where generated output file is placed\n\ +\ (absolute path or relative to the current directory)\n\ +\ --temp-root \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-root 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 \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 [,...]\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 ...\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\ +\ --runtime-image \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. --bind-services will also be added if\n\ +\ --add-modules is not specified.\n\ +\n\ +\Options for creating the application image:\n\ +\ --icon \n\ +\ Path of the icon of the application bundle\n\ +\ (absolute path or relative to the current directory)\n\ +\ --input -i \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 =\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", "add-modules", "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
    \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 \n\ +\ Options to pass to the Java runtime\n\ +\ This option can be used multiple times.\n\ +\ --main-class \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
    \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 [/
    ]\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 installer(s):\n\ +\ --app-image \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\ +\ See create-app-image mode options to create the application image.\n\ +\ --file-associations \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\ +\ --identifier \n\ +\ An identifier that uniquely identifies the application\n\ +\ Defaults to the main class name.\n\ +\ The value should be a valid DNS name.\n\ +\ --install-dir \n\ +\ {4}\ +\ --installer-type \n\ +\ The type of the installer to create\n\ +\ Valid values are: {1} \n\ +\ If this option is not specified (in create-installer mode) all\n\ +\ supported types of installable packages for the current\n\ +\ platform will be created.\n\ +\ --license-file \n\ +\ Path to the license file\n\ +\ (absolute path or relative to the current directory)\n\ +\ --resource-dir \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 \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 installer.\n\ +\n\ +\Platform dependent options for creating the application installer(s):\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 \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-registry-name \n\ +\ Name of the application for registry references.\n\ +\ The default is the Application Name with only\n\ +\ alphanumerics, dots, and dashes (no whitespace)\n\ +\ --win-shortcut\n\ +\ Creates a desktop shortcut for the application\n\ +\ --win-upgrade-uuid \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-bundle-identifier \n\ +\ An identifier that uniquely identifies the application for MacOSX\n\ +\ Defaults to the value of --identifier option.\n\ +\ May only use alphanumeric (A-Z,a-z,0-9), hyphen (-),\n\ +\ and period (.) characters.\n\ +\ --mac-bundle-name \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-bundle-signing-prefix \n\ +\ When signing the application bundle, this value is prefixed to all\n\ +\ components that need to be signed that don't have\n\ +\ an existing bundle identifier.\n\ +\ --mac-sign\n\ +\ Request that the bundle be signed\n\ +\ --mac-signing-keychain \n\ +\ Path of the keychain to use\n\ +\ (absolute path or relative to the current directory)\n\ +\ If not specified, the standard keychains are used.\n\ +\ --mac-signing-key-user-name \n\ +\ User name portion of the typical\n\ +\ "Mac Developer ID Application: " signing key\n\ + +MSG_Help_linux_install=\ +\ --linux-bundle-name \n\ +\ Name for Linux bundle, defaults to the application name\n\ +\ --linux-deb-maintainer \n\ +\ Maintainer for .deb bundle\n\ +\ --linux-menu-group \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 \n\ +\ Type of the license ("License: " of the RPM .spec)\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 the application\n\ +\ such as "Program Files" or "AppData" on Windows.\n\ + +MSG_Help_no_args=Usage: jpackage \n\ +\Use jpackage --help (or -h) for a list of possible options\ + diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/HelpResources_zh_CN.properties b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/HelpResources_zh_CN.properties new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/HelpResources_zh_CN.properties @@ -0,0 +1,279 @@ +# +# 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 \n\ +\n\ +where mode is one of: \n\ +\ create-app-image\n\ +\ Generates a platform-specific application image.\n\ +\ create-installer\n\ +\ Generates a platform-specific installer for the application.\n\ +\ \n\ +Sample usages:\n\ +--------------\n\ +\ Generate a non-modular application image:\n\ +\ jpackage create-app-image -o outputdir -i inputdir -n name \\\n\ +\ --main-class className --main-jar MyJar.jar\n\ +\ Generate a modular application image:\n\ +\ jpackage create-app-image -o outputdir -n name \\\n\ +\ -p modulePath -m moduleName/className\n\ +\ To provide your own options to jlink, run jlink separately:\n\ +\ jlink --output appRuntimeImage -p ModulePath -m moduleName\\\n\ +\ --no-header-files [...]\n\ +\ jpackage create-app-image -o outputdir -n name\\\n\ +\ -m moduleName/className --runtime-image appRuntimeIMage\n\ +\ Generate an application installer:\n\ +\ jpackage create-installer -o outputdir -n name \\\n\ +\ -p modulePath -m moduleName/className\n\ +\ jpackage create-installer -i inputdir -o outputdir -n name \\\n\ +\ --main-class package.ClassName --main-jar MyJar.jar\n\ +\ jpackage create-installer -o outputdir -n \\\n\ +\ --app-image [--installer-type ]\n\ +\ Generate a Java runtime installer:\n\ +\ jpackage create-installer -o outputdir -n name \\\n\ +\ --runtime-image \n\ +\n\ +Generic Options:\n\ +\ @ \n\ +\ Read options and/or mode from a file \n\ +\ This option can be used multiple times.\n\ +\ --app-version \n\ +\ Version of the application and/or installer\n\ +\ --copyright \n\ +\ Copyright for the application\n\ +\ --description \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 \n\ +\ Name of the application and/or installer\n\ +\ --output -o \n\ +\ Path where generated output file is placed\n\ +\ (absolute path or relative to the current directory)\n\ +\ --temp-root \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-root 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 \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 [,...]\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 ...\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\ +\ --runtime-image \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. --bind-services will also be added if\n\ +\ --add-modules is not specified.\n\ +\n\ +\Options for creating the application image:\n\ +\ --icon \n\ +\ Path of the icon of the application bundle\n\ +\ (absolute path or relative to the current directory)\n\ +\ --input -i \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 =\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", "add-modules", "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
    \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 \n\ +\ Options to pass to the Java runtime\n\ +\ This option can be used multiple times.\n\ +\ --main-class \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
    \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 [/
    ]\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 installer(s):\n\ +\ --app-image \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\ +\ See create-app-image mode options to create the application image.\n\ +\ --file-associations \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\ +\ --identifier \n\ +\ An identifier that uniquely identifies the application\n\ +\ Defaults to the main class name.\n\ +\ The value should be a valid DNS name.\n\ +\ --install-dir \n\ +\ {4}\ +\ --installer-type \n\ +\ The type of the installer to create\n\ +\ Valid values are: {1} \n\ +\ If this option is not specified (in create-installer mode) all\n\ +\ supported types of installable packages for the current\n\ +\ platform will be created.\n\ +\ --license-file \n\ +\ Path to the license file\n\ +\ (absolute path or relative to the current directory)\n\ +\ --resource-dir \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 \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 installer.\n\ +\n\ +\Platform dependent options for creating the application installer(s):\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 \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-registry-name \n\ +\ Name of the application for registry references.\n\ +\ The default is the Application Name with only\n\ +\ alphanumerics, dots, and dashes (no whitespace)\n\ +\ --win-shortcut\n\ +\ Creates a desktop shortcut for the application\n\ +\ --win-upgrade-uuid \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-bundle-identifier \n\ +\ An identifier that uniquely identifies the application for MacOSX\n\ +\ Defaults to the value of --identifier option.\n\ +\ May only use alphanumeric (A-Z,a-z,0-9), hyphen (-),\n\ +\ and period (.) characters.\n\ +\ --mac-bundle-name \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-bundle-signing-prefix \n\ +\ When signing the application bundle, this value is prefixed to all\n\ +\ components that need to be signed that don't have\n\ +\ an existing bundle identifier.\n\ +\ --mac-sign\n\ +\ Request that the bundle be signed\n\ +\ --mac-signing-keychain \n\ +\ Path of the keychain to use\n\ +\ (absolute path or relative to the current directory)\n\ +\ If not specified, the standard keychains are used.\n\ +\ --mac-signing-key-user-name \n\ +\ User name portion of the typical\n\ +\ "Mac Developer ID Application: " signing key\n\ + +MSG_Help_linux_install=\ +\ --linux-bundle-name \n\ +\ Name for Linux bundle, defaults to the application name\n\ +\ --linux-deb-maintainer \n\ +\ Maintainer for .deb bundle\n\ +\ --linux-menu-group \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 \n\ +\ Type of the license ("License: " of the RPM .spec)\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 the application\n\ +\ such as "Program Files" or "AppData" on Windows.\n\ + +MSG_Help_no_args=Usage: jpackage \n\ +\Use jpackage --help (or -h) for a list of possible options\ + diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/MainResources.properties b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/MainResources.properties new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/MainResources.properties @@ -0,0 +1,97 @@ +# +# 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.category.default=Unknown +param.copyright.default=Copyright (C) {0,date,YYYY} +param.description.default=none +param.vendor.default=Unknown +param.version.default=1.0 + +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 bundle: {0} in {1}. +message.detected.modules=Automatically adding detected modules: {0}. +message.modules=Adding modules: {0} to runtime image. +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} bundle. + +error.cannot-create-output-dir=Output directory {0} cannot be created. +error.cannot-write-to-output-dir=Output directory {0} is not writable. +error.root-exists=Error: Application output directory {0} already exists. +error.no-application-class=Main application class is missing. +error.no-application-class.advice=Please specify main application class. +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. + +warning.module.does.not.exist=Module [{0}] does not exist. +warning.no.jdk.modules.found=Warning: No JDK Modules found. +warning.missing.arg.file=Warning: Missing argument file: {0}. + +MSG_BundlerFailed=Error: Bundler "{1}" ({0}) failed to produce a bundle. +MSG_BundlerPlatformException=Bundler {0} skipped because the bundler does not support bundling on this platform. +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_Version=jpackage version +MSG_BundlerFailed=Error: Bundler "{1}" ({0}) failed to produce a bundle. + + + +ERR_UnsupportedOption=Error: Option [{0}] is not valid on this platform. +ERR_NotImageOption=Error: Option [{0}] is not valid in create-app-image mode. +ERR_NotInstallerOption=Error: Option [{0}] is not valid with --app-image option. +ERR_NoInstallerEntryPoint=Error: Option [{0}] is not valid without --module or --main-jar entry point option. + +ERR_MissingMode=Error: Mode is not specified. +ERR_MissingArgument=Error: Missing argument: {0}. +ERR_MissingAppResources=Error: No application jars found. +ERR_AppImageNotExist=Error: App image directory "{0}" does not exist. +ERR_AppImageInvalid=Error: App image directory "{0}" does not contain "app" sub-directory. +ERR_NoAddLauncherName=Error: --add-launcher option requires a name and a file path (--add-launcher =). +ERR_NoUniqueName=Error: --add-launcher = 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-root ({0}) must be non-existant directory. +ERR_InvalidOption=Error: Invalid Option: [{0}]. +ERR_VersionComparison=Error: Failed to compare version {0} with {1}. +ERR_InvalidInstallerType=Error: Invalid or Unsupported Installer type: [{0}]. +ERR_BothMainJarAndModule=Error: Cannot have both --main-jar and --module Options. +ERR_NoEntryPoint=Error: create-app-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}. diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/MainResources_ja.properties b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/MainResources_ja.properties new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/MainResources_ja.properties @@ -0,0 +1,97 @@ +# +# 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.category.default=Unknown +param.copyright.default=Copyright (C) {0,date,YYYY} +param.description.default=none +param.vendor.default=Unknown +param.version.default=1.0 + +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 bundle: {0} in {1}. +message.detected.modules=Automatically adding detected modules: {0}. +message.modules=Adding modules: {0} to runtime image. +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} bundle. + +error.cannot-create-output-dir=Output directory {0} cannot be created. +error.cannot-write-to-output-dir=Output directory {0} is not writable. +error.root-exists=Error: Application output directory {0} already exists. +error.no-application-class=Main application class is missing. +error.no-application-class.advice=Please specify main application class. +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. + +warning.module.does.not.exist=Module [{0}] does not exist. +warning.no.jdk.modules.found=Warning: No JDK Modules found. +warning.missing.arg.file=Warning: Missing argument file: {0}. + +MSG_BundlerFailed=Error: Bundler "{1}" ({0}) failed to produce a bundle. +MSG_BundlerPlatformException=Bundler {0} skipped because the bundler does not support bundling on this platform. +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_Version=jpackage version +MSG_BundlerFailed=Error: Bundler "{1}" ({0}) failed to produce a bundle. + + + +ERR_UnsupportedOption=Error: Option [{0}] is not valid on this platform. +ERR_NotImageOption=Error: Option [{0}] is not valid in create-app-image mode. +ERR_NotInstallerOption=Error: Option [{0}] is not valid with --app-image option. +ERR_NoInstallerEntryPoint=Error: Option [{0}] is not valid without --module or --main-jar entry point option. + +ERR_MissingMode=Error: Mode is not specified. +ERR_MissingArgument=Error: Missing argument: {0}. +ERR_MissingAppResources=Error: No application jars found. +ERR_AppImageNotExist=Error: App image directory "{0}" does not exist. +ERR_AppImageInvalid=Error: App image directory "{0}" does not contain "app" sub-directory. +ERR_NoAddLauncherName=Error: --add-launcher option requires a name and a file path (--add-launcher =). +ERR_NoUniqueName=Error: --add-launcher = 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-root ({0}) must be non-existant directory. +ERR_InvalidOption=Error: Invalid Option: [{0}]. +ERR_VersionComparison=Error: Failed to compare version {0} with {1}. +ERR_InvalidInstallerType=Error: Invalid or Unsupported Installer type: [{0}]. +ERR_BothMainJarAndModule=Error: Cannot have both --main-jar and --module Options. +ERR_NoEntryPoint=Error: create-app-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}. diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/MainResources_zh_CN.properties b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/MainResources_zh_CN.properties new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/MainResources_zh_CN.properties @@ -0,0 +1,97 @@ +# +# 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.category.default=Unknown +param.copyright.default=Copyright (C) {0,date,YYYY} +param.description.default=none +param.vendor.default=Unknown +param.version.default=1.0 + +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 bundle: {0} in {1}. +message.detected.modules=Automatically adding detected modules: {0}. +message.modules=Adding modules: {0} to runtime image. +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} bundle. + +error.cannot-create-output-dir=Output directory {0} cannot be created. +error.cannot-write-to-output-dir=Output directory {0} is not writable. +error.root-exists=Error: Application output directory {0} already exists. +error.no-application-class=Main application class is missing. +error.no-application-class.advice=Please specify main application class. +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. + +warning.module.does.not.exist=Module [{0}] does not exist. +warning.no.jdk.modules.found=Warning: No JDK Modules found. +warning.missing.arg.file=Warning: Missing argument file: {0}. + +MSG_BundlerFailed=Error: Bundler "{1}" ({0}) failed to produce a bundle. +MSG_BundlerPlatformException=Bundler {0} skipped because the bundler does not support bundling on this platform. +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_Version=jpackage version +MSG_BundlerFailed=Error: Bundler "{1}" ({0}) failed to produce a bundle. + + + +ERR_UnsupportedOption=Error: Option [{0}] is not valid on this platform. +ERR_NotImageOption=Error: Option [{0}] is not valid in create-app-image mode. +ERR_NotInstallerOption=Error: Option [{0}] is not valid with --app-image option. +ERR_NoInstallerEntryPoint=Error: Option [{0}] is not valid without --module or --main-jar entry point option. + +ERR_MissingMode=Error: Mode is not specified. +ERR_MissingArgument=Error: Missing argument: {0}. +ERR_MissingAppResources=Error: No application jars found. +ERR_AppImageNotExist=Error: App image directory "{0}" does not exist. +ERR_AppImageInvalid=Error: App image directory "{0}" does not contain "app" sub-directory. +ERR_NoAddLauncherName=Error: --add-launcher option requires a name and a file path (--add-launcher =). +ERR_NoUniqueName=Error: --add-launcher = 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-root ({0}) must be non-existant directory. +ERR_InvalidOption=Error: Invalid Option: [{0}]. +ERR_VersionComparison=Error: Failed to compare version {0} with {1}. +ERR_InvalidInstallerType=Error: Invalid or Unsupported Installer type: [{0}]. +ERR_BothMainJarAndModule=Error: Cannot have both --main-jar and --module Options. +ERR_NoEntryPoint=Error: create-app-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}. diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/ResourceLocator.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/ResourceLocator.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/ResourceLocator.java @@ -0,0 +1,30 @@ +/* + * 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.jpackage.internal.resources; + +public class ResourceLocator { + +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/main/CommandLine.java b/src/jdk.jpackage/share/classes/jdk/jpackage/main/CommandLine.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/main/CommandLine.java @@ -0,0 +1,316 @@ +/* + * Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.jpackage.main; + +import java.io.IOException; +import java.io.Reader; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Various utility methods for processing Java tool command line arguments. + * + *

    This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class CommandLine { + /** + * Process Win32-style command files for the specified command line + * arguments and return the resulting arguments. A command file argument + * is of the form '@file' where 'file' is the name of the file whose + * contents are to be parsed for additional arguments. The contents of + * the command file are parsed using StreamTokenizer and the original + * '@file' argument replaced with the resulting tokens. Recursive command + * files are not supported. The '@' character itself can be quoted with + * the sequence '@@'. + * @param args the arguments that may contain @files + * @return the arguments, with @files expanded + * @throws IOException if there is a problem reading any of the @files + */ + public static String[] parse(String[] args) throws IOException { + List newArgs = new ArrayList<>(); + appendParsedCommandArgs(newArgs, Arrays.asList(args)); + return newArgs.toArray(new String[newArgs.size()]); + } + + private static void appendParsedCommandArgs(List newArgs, List args) throws IOException { + for (String arg : args) { + if (arg.length() > 1 && arg.charAt(0) == '@') { + arg = arg.substring(1); + if (arg.charAt(0) == '@') { + newArgs.add(arg); + } else { + loadCmdFile(arg, newArgs); + } + } else { + newArgs.add(arg); + } + } + } + + /** + * Process the given environment variable and appends any Win32-style + * command files for the specified command line arguments and return + * the resulting arguments. A command file argument + * is of the form '@file' where 'file' is the name of the file whose + * contents are to be parsed for additional arguments. The contents of + * the command file are parsed using StreamTokenizer and the original + * '@file' argument replaced with the resulting tokens. Recursive command + * files are not supported. The '@' character itself can be quoted with + * the sequence '@@'. + * @param envVariable the env variable to process + * @param args the arguments that may contain @files + * @return the arguments, with environment variable's content and expansion of @files + * @throws IOException if there is a problem reading any of the @files + * @throws com.sun.tools.javac.main.CommandLine.UnmatchedQuote + */ + public static List parse(String envVariable, List args) + throws IOException, UnmatchedQuote { + + List inArgs = new ArrayList<>(); + appendParsedEnvVariables(inArgs, envVariable); + inArgs.addAll(args); + List newArgs = new ArrayList<>(); + appendParsedCommandArgs(newArgs, inArgs); + return newArgs; + } + + /** + * Process the given environment variable and appends any Win32-style + * command files for the specified command line arguments and return + * the resulting arguments. A command file argument + * is of the form '@file' where 'file' is the name of the file whose + * contents are to be parsed for additional arguments. The contents of + * the command file are parsed using StreamTokenizer and the original + * '@file' argument replaced with the resulting tokens. Recursive command + * files are not supported. The '@' character itself can be quoted with + * the sequence '@@'. + * @param envVariable the env variable to process + * @param args the arguments that may contain @files + * @return the arguments, with environment variable's content and expansion of @files + * @throws IOException if there is a problem reading any of the @files + * @throws com.sun.tools.javac.main.CommandLine.UnmatchedQuote + */ + public static String[] parse(String envVariable, String[] args) throws IOException, UnmatchedQuote { + List out = parse(envVariable, Arrays.asList(args)); + return out.toArray(new String[out.size()]); + } + + private static void loadCmdFile(String name, List args) throws IOException { + try (Reader r = Files.newBufferedReader(Paths.get(name), Charset.defaultCharset())) { + Tokenizer t = new Tokenizer(r); + String s; + while ((s = t.nextToken()) != null) { + args.add(s); + } + } + } + + public static class Tokenizer { + private final Reader in; + private int ch; + + public Tokenizer(Reader in) throws IOException { + this.in = in; + ch = in.read(); + } + + public String nextToken() throws IOException { + skipWhite(); + if (ch == -1) { + return null; + } + + StringBuilder sb = new StringBuilder(); + char quoteChar = 0; + + while (ch != -1) { + switch (ch) { + case ' ': + case '\t': + case '\f': + if (quoteChar == 0) { + return sb.toString(); + } + sb.append((char) ch); + break; + + case '\n': + case '\r': + return sb.toString(); + + case '\'': + case '"': + if (quoteChar == 0) { + quoteChar = (char) ch; + } else if (quoteChar == ch) { + quoteChar = 0; + } else { + sb.append((char) ch); + } + break; + + case '\\': + if (quoteChar != 0) { + ch = in.read(); + switch (ch) { + case '\n': + case '\r': + while (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t' || ch == '\f') { + ch = in.read(); + } + continue; + + case 'n': + ch = '\n'; + break; + case 'r': + ch = '\r'; + break; + case 't': + ch = '\t'; + break; + case 'f': + ch = '\f'; + break; + } + } + sb.append((char) ch); + break; + + default: + sb.append((char) ch); + } + + ch = in.read(); + } + + return sb.toString(); + } + + void skipWhite() throws IOException { + while (ch != -1) { + switch (ch) { + case ' ': + case '\t': + case '\n': + case '\r': + case '\f': + break; + + case '#': + ch = in.read(); + while (ch != '\n' && ch != '\r' && ch != -1) { + ch = in.read(); + } + break; + + default: + return; + } + + ch = in.read(); + } + } + } + + @SuppressWarnings("fallthrough") + private static void appendParsedEnvVariables(List newArgs, String envVariable) + throws UnmatchedQuote { + + if (envVariable == null) { + return; + } + String in = System.getenv(envVariable); + if (in == null || in.trim().isEmpty()) { + return; + } + + final char NUL = (char)0; + final int len = in.length(); + + int pos = 0; + StringBuilder sb = new StringBuilder(); + char quote = NUL; + char ch; + + loop: + while (pos < len) { + ch = in.charAt(pos); + switch (ch) { + case '\"': case '\'': + if (quote == NUL) { + quote = ch; + } else if (quote == ch) { + quote = NUL; + } else { + sb.append(ch); + } + pos++; + break; + case '\f': case '\n': case '\r': case '\t': case ' ': + if (quote == NUL) { + newArgs.add(sb.toString()); + sb.setLength(0); + while (ch == '\f' || ch == '\n' || ch == '\r' || ch == '\t' || ch == ' ') { + pos++; + if (pos >= len) { + break loop; + } + ch = in.charAt(pos); + } + break; + } + // fall through + default: + sb.append(ch); + pos++; + } + } + if (sb.length() != 0) { + newArgs.add(sb.toString()); + } + if (quote != NUL) { + throw new UnmatchedQuote(envVariable); + } + } + + public static class UnmatchedQuote extends Exception { + private static final long serialVersionUID = 0; + + public final String variableName; + + UnmatchedQuote(String variable) { + this.variableName = variable; + } + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/main/Main.java b/src/jdk.jpackage/share/classes/jdk/jpackage/main/Main.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/main/Main.java @@ -0,0 +1,129 @@ +/* + * 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.jpackage.main; + +import jdk.jpackage.internal.Arguments; +import jdk.jpackage.internal.Log; +import jdk.jpackage.internal.CLIHelp; +import java.io.PrintWriter; +import java.util.ResourceBundle; + +public class Main { + + private static final ResourceBundle bundle = ResourceBundle.getBundle( + "jdk.jpackage.internal.resources.MainResources"); + + private static final String version = bundle.getString("MSG_Version") + + " " + System.getProperty("java.version"); + + /** + * main(String... args) + * This is the entry point for the jpackage tool. + * + * @param args command line arguments + */ + public static void main(String... args) throws Exception { + // Create logger with default system.out and system.err + Log.Logger logger = new Log.Logger(false); + Log.setLogger(logger); + + int status = run(args); + System.exit(status); + } + + /** + * run() - this is the entry point for the ToolProvider API. + * + * @param out output stream + * @param err error output stream + * @param args command line arguments + * @return an exit code. 0 means success, non-zero means an error occurred. + */ + public static int run(PrintWriter out, PrintWriter err, String... args) + throws Exception { + // Create logger with provided streams + Log.Logger logger = new Log.Logger(false); + logger.setPrintWriter(out, err); + Log.setLogger(logger); + + int status = run(args); + Log.flush(); + return status; + } + + private static int run(String... args) throws Exception { + String[] newArgs = CommandLine.parse(args); + if (newArgs.length == 0) { + CLIHelp.showHelp(true); + } else if (hasHelp(newArgs)){ + if (hasVersion(newArgs)) { + Log.info(version + "\n"); + } + CLIHelp.showHelp(false); + } else if (hasVersion(newArgs)) { + Log.info(version); + } else { + try { + Arguments arguments = new Arguments(newArgs); + if (!arguments.processArguments()) { + // processArguments() should log error message if failed. + return -1; + } + } catch (Exception e) { + if (Log.isVerbose()) { + Log.verbose(e); + } else { + Log.error(e.getMessage()); + if (e.getCause() != null && e.getCause() != e) { + Log.error(e.getCause().getMessage()); + } + } + return -1; + } + } + + return 0; + } + + private static boolean hasHelp(String[] args) { + for (String a : args) { + if ("--help".equals(a) || "-h".equals(a)) { + return true; + } + } + return false; + } + + private static boolean hasVersion(String[] args) { + for (String a : args) { + if ("--version".equals(a)) { + return true; + } + } + return false; + } + +} diff --git a/src/jdk.jpackage/share/classes/module-info.java b/src/jdk.jpackage/share/classes/module-info.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/classes/module-info.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * Defines the Java Packaging tool, jpackage. + * + *

    jpackage is a tool for generating self-contained application bundles. + * + *

    This module provides the equivalent of command-line access to jpackage + * via the {@link java.util.spi.ToolProvider ToolProvider} SPI. + * Instances of the tool can be obtained by calling + * {@link java.util.spi.ToolProvider#findFirst ToolProvider.findFirst} + * or the {@link java.util.ServiceLoader service loader} with the name + * {@code "jpackage"}. + * + * @implNote The {@code jpackage} tool is not thread-safe. An application + * should not call either of the + * {@link java.util.spi.ToolProvider ToolProvider} {@code run} methods + * concurrently, even with separate {@code "jpackage"} {@code ToolProvider} + * instances, or undefined behavior may result. + *

    + * + * @moduleGraph + * @since 13 + */ + +module jdk.jpackage { + requires jdk.jlink; + + requires java.xml; + requires java.logging; + requires java.desktop; + + uses jdk.jpackage.internal.Bundler; + uses jdk.jpackage.internal.Bundlers; + + provides jdk.jpackage.internal.Bundlers with + jdk.jpackage.internal.BasicBundlers; + + provides java.util.spi.ToolProvider + with jdk.jpackage.internal.JPackageToolProvider; +} diff --git a/src/jdk.jpackage/share/native/libapplauncher/FileAttributes.h b/src/jdk.jpackage/share/native/libapplauncher/FileAttributes.h new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/native/libapplauncher/FileAttributes.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef FILEATTRIBUTES_H +#define FILEATTRIBUTES_H + +#include "Platform.h" +#include "PlatformString.h" +#include "FileAttribute.h" + +#include + +class FileAttributes { +private: + TString FFileName; + bool FFollowLink; + std::vector FAttributes; + + bool WriteAttributes(); + bool ReadAttributes(); + bool Valid(const FileAttribute Value); + +public: + FileAttributes(const TString FileName, bool FollowLink = true); + + void Append(const FileAttribute Value); + bool Contains(const FileAttribute Value); + void Remove(const FileAttribute Value); +}; + +#endif // FILEATTRIBUTES_H + diff --git a/src/jdk.jpackage/share/native/libapplauncher/FilePath.h b/src/jdk.jpackage/share/native/libapplauncher/FilePath.h new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/native/libapplauncher/FilePath.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef FILEPATH_H +#define FILEPATH_H + +#include "Platform.h" +#include "PlatformString.h" +#include "FileAttribute.h" + +#include + +class FileAttributes { +private: + TString FFileName; + bool FFollowLink; + std::vector FAttributes; + + bool WriteAttributes(); + bool ReadAttributes(); + bool Valid(const FileAttribute Value); + +public: + FileAttributes(const TString FileName, bool FollowLink = true); + + void Append(const FileAttribute Value); + bool Contains(const FileAttribute Value); + void Remove(const FileAttribute Value); +}; + +class FilePath { +private: + FilePath(void) {} + ~FilePath(void) {} + +public: + static bool FileExists(const TString FileName); + static bool DirectoryExists(const TString DirectoryName); + + static bool DeleteFile(const TString FileName); + static bool DeleteDirectory(const TString DirectoryName); + + static TString ExtractFilePath(TString Path); + static TString ExtractFileExt(TString Path); + static TString ExtractFileName(TString Path); + static TString ChangeFileExt(TString Path, TString Extension); + + static TString IncludeTrailingSeparator(const TString value); + static TString IncludeTrailingSeparator(const char* value); + static TString IncludeTrailingSeparator(const wchar_t* value); + static TString FixPathForPlatform(TString Path); + static TString FixPathSeparatorForPlatform(TString Path); + static TString PathSeparator(); + + static bool CreateDirectory(TString Path, bool ownerOnly); + static void ChangePermissions(TString FileName, bool ownerOnly); +}; + +#endif //FILEPATH_H diff --git a/src/jdk.jpackage/share/native/libapplauncher/Helpers.cpp b/src/jdk.jpackage/share/native/libapplauncher/Helpers.cpp new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/native/libapplauncher/Helpers.cpp @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "Helpers.h" +#include "PlatformString.h" +#include "PropertyFile.h" + + +bool Helpers::SplitOptionIntoNameValue( + TString option, TString& Name, TString& Value) { + bool hasValue = false; + Name = _T(""); + Value = _T(""); + unsigned int index = 0; + + for (; index < option.length(); index++) { + TCHAR c = option[index]; + + switch (c) { + case '=': { + index++; + hasValue = true; + break; + } + + case '\\': { + if (index + 1 < option.length()) { + c = option[index + 1]; + + switch (c) { + case '\\': { + index++; + Name += '\\'; + break; + } + + case '=': { + index++; + Name += '='; + break; + } + } + + } + + continue; + } + + default: { + Name += c; + continue; + } + } + + break; + } + + if (hasValue) { + Value = option.substr(index, index - option.length()); + } + + return (option.length() > 0); +} + + +TString Helpers::ReplaceString(TString subject, const TString& search, + const TString& replace) { + size_t pos = 0; + while((pos = subject.find(search, pos)) != TString::npos) { + subject.replace(pos, search.length(), replace); + pos += replace.length(); + } + return subject; +} + +TString Helpers::ConvertIdToFilePath(TString Value) { + TString search; + search = '.'; + TString replace; + replace = '/'; + TString result = ReplaceString(Value, search, replace); + return result; +} + +TString Helpers::ConvertIdToJavaPath(TString Value) { + TString search; + search = '.'; + TString replace; + replace = '/'; + TString result = ReplaceString(Value, search, replace); + search = '\\'; + result = ReplaceString(result, search, replace); + return result; +} + +TString Helpers::ConvertJavaPathToId(TString Value) { + TString search; + search = '/'; + TString replace; + replace = '.'; + TString result = ReplaceString(Value, search, replace); + return result; +} + +OrderedMap + Helpers::GetJavaOptionsFromConfig(IPropertyContainer* config) { + OrderedMap result; + + for (unsigned int index = 0; index < config->GetCount(); index++) { + TString argname = + TString(_T("jvmarg.")) + PlatformString(index + 1).toString(); + TString argvalue; + + if (config->GetValue(argname, argvalue) == false) { + break; + } + else if (argvalue.empty() == false) { + TString name; + TString value; + if (Helpers::SplitOptionIntoNameValue(argvalue, name, value)) { + result.Append(name, value); + } + } + } + + return result; +} + +std::list Helpers::GetArgsFromConfig(IPropertyContainer* config) { + std::list result; + + for (unsigned int index = 0; index < config->GetCount(); index++) { + TString argname = TString(_T("arg.")) + + PlatformString(index + 1).toString(); + TString argvalue; + + if (config->GetValue(argname, argvalue) == false) { + break; + } + else if (argvalue.empty() == false) { + result.push_back((argvalue)); + } + } + + return result; +} + +std::list + Helpers::MapToNameValueList(OrderedMap Map) { + std::list result; + std::vector keys = Map.GetKeys(); + + for (OrderedMap::const_iterator iterator = Map.begin(); + iterator != Map.end(); iterator++) { + JPPair *item = *iterator; + TString key = item->first; + TString value = item->second; + + if (value.length() == 0) { + result.push_back(key); + } else { + result.push_back(key + _T('=') + value); + } + } + + return result; +} + +TString Helpers::NameValueToString(TString name, TString value) { + TString result; + + if (value.empty() == true) { + result = name; + } + else { + result = name + TString(_T("=")) + value; + } + + return result; +} + +std::list Helpers::StringToArray(TString Value) { + std::list result; + TString line; + + for (unsigned int index = 0; index < Value.length(); index++) { + TCHAR c = Value[index]; + + switch (c) { + case '\n': { + result.push_back(line); + line = _T(""); + break; + } + + case '\r': { + result.push_back(line); + line = _T(""); + + if (Value[index + 1] == '\n') + index++; + + break; + } + + default: { + line += c; + } + } + } + + // The buffer may not have ended with a Carriage Return/Line Feed. + if (line.length() > 0) { + result.push_back(line); + } + + return result; +} diff --git a/src/jdk.jpackage/share/native/libapplauncher/Helpers.h b/src/jdk.jpackage/share/native/libapplauncher/Helpers.h new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/native/libapplauncher/Helpers.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef HELPERS_H +#define HELPERS_H + +#include "Platform.h" +#include "OrderedMap.h" +#include "IniFile.h" + + +class Helpers { +private: + Helpers(void) {} + ~Helpers(void) {} + +public: + // Supports two formats for option: + // Example 1: + // foo=bar + // + // Example 2: + // + static bool SplitOptionIntoNameValue(TString option, + TString& Name, TString& Value); + static TString ReplaceString(TString subject, const TString& search, + const TString& replace); + static TString ConvertIdToFilePath(TString Value); + static TString ConvertIdToJavaPath(TString Value); + static TString ConvertJavaPathToId(TString Value); + + static OrderedMap + GetJavaOptionsFromConfig(IPropertyContainer* config); + static std::list GetArgsFromConfig(IPropertyContainer* config); + + static std::list + MapToNameValueList(OrderedMap Map); + + static TString NameValueToString(TString name, TString value); + + static std::list StringToArray(TString Value); +}; + +#endif // HELPERS_H diff --git a/src/jdk.jpackage/share/native/libapplauncher/IniFile.cpp b/src/jdk.jpackage/share/native/libapplauncher/IniFile.cpp new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/native/libapplauncher/IniFile.cpp @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "IniFile.h" +#include "Helpers.h" + +#include + + +IniFile::IniFile() : ISectionalPropertyContainer() { +} + +IniFile::~IniFile() { + for (OrderedMap::iterator iterator = + FMap.begin(); iterator != FMap.end(); iterator++) { + JPPair *item = *iterator; + delete item->second; + } +} + +bool IniFile::LoadFromFile(const TString FileName) { + bool result = false; + Platform& platform = Platform::GetInstance(); + + std::list contents = platform.LoadFromFile(FileName); + + if (contents.empty() == false) { + bool found = false; + + // Determine the if file is an INI file or property file. + // Assign FDefaultSection if it is + // an INI file. Otherwise FDefaultSection is NULL. + for (std::list::const_iterator iterator = contents.begin(); + iterator != contents.end(); iterator++) { + TString line = *iterator; + + if (line[0] == ';') { + // Semicolon is a comment so ignore the line. + continue; + } + else { + if (line[0] == '[') { + found = true; + } + + break; + } + } + + if (found == true) { + TString sectionName; + + for (std::list::const_iterator iterator = contents.begin(); + iterator != contents.end(); iterator++) { + TString line = *iterator; + + if (line[0] == ';') { + // Semicolon is a comment so ignore the line. + continue; + } + else if (line[0] == '[' && line[line.length() - 1] == ']') { + sectionName = line.substr(1, line.size() - 2); + } + else if (sectionName.empty() == false) { + TString name; + TString value; + + if (Helpers::SplitOptionIntoNameValue( + line, name, value) == true) { + Append(sectionName, name, value); + } + } + } + + result = true; + } + } + + return result; +} + +bool IniFile::SaveToFile(const TString FileName, bool ownerOnly) { + bool result = false; + + std::list contents; + std::vector keys = FMap.GetKeys(); + + for (unsigned int index = 0; index < keys.size(); index++) { + TString name = keys[index]; + IniSectionData *section; + + if (FMap.GetValue(name, section) == true) { + contents.push_back(_T("[") + name + _T("]")); + std::list lines = section->GetLines(); + contents.insert(contents.end(), lines.begin(), lines.end()); + contents.push_back(_T("")); + } + } + + Platform& platform = Platform::GetInstance(); + platform.SaveToFile(FileName, contents, ownerOnly); + result = true; + return result; +} + +void IniFile::Append(const TString SectionName, + const TString Key, TString Value) { + if (FMap.ContainsKey(SectionName) == true) { + IniSectionData* section; + + if (FMap.GetValue(SectionName, section) == true && section != NULL) { + section->SetValue(Key, Value); + } + } + else { + IniSectionData *section = new IniSectionData(); + section->SetValue(Key, Value); + FMap.Append(SectionName, section); + } +} + +void IniFile::AppendSection(const TString SectionName, + OrderedMap Values) { + if (FMap.ContainsKey(SectionName) == true) { + IniSectionData* section; + + if (FMap.GetValue(SectionName, section) == true && section != NULL) { + section->Append(Values); + } + } + else { + IniSectionData *section = new IniSectionData(Values); + FMap.Append(SectionName, section); + } +} + +bool IniFile::GetValue(const TString SectionName, + const TString Key, TString& Value) { + bool result = false; + IniSectionData* section; + + if (FMap.GetValue(SectionName, section) == true && section != NULL) { + result = section->GetValue(Key, Value); + } + + return result; +} + +bool IniFile::SetValue(const TString SectionName, + const TString Key, TString Value) { + bool result = false; + IniSectionData* section; + + if (FMap.GetValue(SectionName, section) && section != NULL) { + result = section->SetValue(Key, Value); + } + else { + Append(SectionName, Key, Value); + } + + + return result; +} + +bool IniFile::GetSection(const TString SectionName, + OrderedMap &Data) { + bool result = false; + + if (FMap.ContainsKey(SectionName) == true) { + IniSectionData* section; + + if (FMap.GetValue(SectionName, section) == true && section != NULL) { + OrderedMap data = section->GetData(); + Data.Append(data); + result = true; + } + } + + return result; +} + +bool IniFile::ContainsSection(const TString SectionName) { + return FMap.ContainsKey(SectionName); +} + +//---------------------------------------------------------------------------- + +IniSectionData::IniSectionData() { + FMap.SetAllowDuplicates(true); +} + +IniSectionData::IniSectionData(OrderedMap Values) { + FMap = Values; +} + +std::vector IniSectionData::GetKeys() { + return FMap.GetKeys(); +} + +std::list IniSectionData::GetLines() { + std::list result; + std::vector keys = FMap.GetKeys(); + + for (unsigned int index = 0; index < keys.size(); index++) { + TString name = keys[index]; + TString value; + + if (FMap.GetValue(name, value) == true) { + name = Helpers::ReplaceString(name, _T("="), _T("\\=")); + value = Helpers::ReplaceString(value, _T("="), _T("\\=")); + + TString line = name + _T('=') + value; + result.push_back(line); + } + } + + return result; +} + +OrderedMap IniSectionData::GetData() { + OrderedMap result = FMap; + return result; +} + +bool IniSectionData::GetValue(const TString Key, TString& Value) { + return FMap.GetValue(Key, Value); +} + +bool IniSectionData::SetValue(const TString Key, TString Value) { + return FMap.SetValue(Key, Value); +} + +void IniSectionData::Append(OrderedMap Values) { + FMap.Append(Values); +} + +size_t IniSectionData::GetCount() { + return FMap.Count(); +} diff --git a/src/jdk.jpackage/share/native/libapplauncher/IniFile.h b/src/jdk.jpackage/share/native/libapplauncher/IniFile.h new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/native/libapplauncher/IniFile.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef INIFILE_H +#define INIFILE_H + +#include "Platform.h" +#include "OrderedMap.h" + +#include + + +class IniSectionData : public IPropertyContainer { +private: + OrderedMap FMap; + +public: + IniSectionData(); + IniSectionData(OrderedMap Values); + + std::vector GetKeys(); + std::list GetLines(); + OrderedMap GetData(); + + bool SetValue(const TString Key, TString Value); + void Append(OrderedMap Values); + + virtual bool GetValue(const TString Key, TString& Value); + virtual size_t GetCount(); +}; + + +class IniFile : public ISectionalPropertyContainer { +private: + OrderedMap FMap; + +public: + IniFile(); + virtual ~IniFile(); + + void internalTest(); + + bool LoadFromFile(const TString FileName); + bool SaveToFile(const TString FileName, bool ownerOnly = true); + + void Append(const TString SectionName, const TString Key, TString Value); + void AppendSection(const TString SectionName, + OrderedMap Values); + bool SetValue(const TString SectionName, + const TString Key, TString Value); + + // ISectionalPropertyContainer + virtual bool GetSection(const TString SectionName, + OrderedMap &Data); + virtual bool ContainsSection(const TString SectionName); + virtual bool GetValue(const TString SectionName, + const TString Key, TString& Value); +}; + +#endif // INIFILE_H diff --git a/src/jdk.jpackage/share/native/libapplauncher/JavaVirtualMachine.cpp b/src/jdk.jpackage/share/native/libapplauncher/JavaVirtualMachine.cpp new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/native/libapplauncher/JavaVirtualMachine.cpp @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "JavaVirtualMachine.h" +#include "Platform.h" +#include "PlatformString.h" +#include "FilePath.h" +#include "Package.h" +#include "Helpers.h" +#include "Messages.h" +#include "Macros.h" + +#include "jni.h" + +#include +#include +#include + + +bool RunVM() { + JavaVirtualMachine javavm; + + bool result = javavm.StartJVM(); + + if (!result) { + Platform& platform = Platform::GetInstance(); + platform.ShowMessage(_T("Failed to launch JVM\n")); + } + + return result; +} + +//---------------------------------------------------------------------------- + +JavaOptions::JavaOptions(): FOptions(NULL) { +} + +JavaOptions::~JavaOptions() { + if (FOptions != NULL) { + for (unsigned int index = 0; index < GetCount(); index++) { + delete[] FOptions[index].optionString; + } + + delete[] FOptions; + } +} + +void JavaOptions::AppendValue(const TString Key, TString Value, void* Extra) { + JavaOptionItem item; + item.name = Key; + item.value = Value; + item.extraInfo = Extra; + FItems.push_back(item); +} + +void JavaOptions::AppendValue(const TString Key, TString Value) { + AppendValue(Key, Value, NULL); +} + +void JavaOptions::AppendValue(const TString Key) { + AppendValue(Key, _T(""), NULL); +} + +void JavaOptions::AppendValues(OrderedMap Values) { + std::vector orderedKeys = Values.GetKeys(); + + for (std::vector::const_iterator iterator = orderedKeys.begin(); + iterator != orderedKeys.end(); iterator++) { + TString name = *iterator; + TString value; + + if (Values.GetValue(name, value) == true) { + AppendValue(name, value); + } + } +} + +void JavaOptions::ReplaceValue(const TString Key, TString Value) { + for (std::list::iterator iterator = FItems.begin(); + iterator != FItems.end(); iterator++) { + + TString lkey = iterator->name; + + if (lkey == Key) { + JavaOptionItem item = *iterator; + item.value = Value; + iterator = FItems.erase(iterator); + FItems.insert(iterator, item); + break; + } + } +} + +std::list JavaOptions::ToList() { + std::list result; + Macros& macros = Macros::GetInstance(); + + for (std::list::const_iterator iterator = FItems.begin(); + iterator != FItems.end(); iterator++) { + TString key = iterator->name; + TString value = iterator->value; + TString option = Helpers::NameValueToString(key, value); + option = macros.ExpandMacros(option); + result.push_back(option); + } + + return result; +} + +size_t JavaOptions::GetCount() { + return FItems.size(); +} + +//---------------------------------------------------------------------------- + +JavaVirtualMachine::JavaVirtualMachine() { +} + +JavaVirtualMachine::~JavaVirtualMachine(void) { +} + +bool JavaVirtualMachine::StartJVM() { + Platform& platform = Platform::GetInstance(); + Package& package = Package::GetInstance(); + + TString classpath = package.GetClassPath(); + TString modulepath = package.GetModulePath(); + JavaOptions options; + + if (modulepath.empty() == false) { + options.AppendValue(_T("-Djava.module.path"), modulepath); + } + + options.AppendValue(_T("-Djava.library.path"), + package.GetPackageAppDirectory() + FilePath::PathSeparator() + + package.GetPackageLauncherDirectory()); + options.AppendValue( + _T("-Djava.launcher.path"), package.GetPackageLauncherDirectory()); + options.AppendValues(package.GetJavaOptions()); + +#ifdef DEBUG + if (package.Debugging() == dsJava) { + options.AppendValue(_T("-Xdebug"), _T("")); + options.AppendValue( + _T("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=localhost:5005"), + _T("")); + platform.ShowMessage(_T("localhost:5005")); + } +#endif // DEBUG + + TString maxHeapSizeOption; + TString minHeapSizeOption; + + + if (package.GetMemoryState() == PackageBootFields::msAuto) { + TPlatformNumber memorySize = package.GetMemorySize(); + TString memory = + PlatformString((size_t)memorySize).toString() + _T("m"); + maxHeapSizeOption = TString(_T("-Xmx")) + memory; + options.AppendValue(maxHeapSizeOption, _T("")); + + if (memorySize > 256) + minHeapSizeOption = _T("-Xms256m"); + else + minHeapSizeOption = _T("-Xms") + memory; + + options.AppendValue(minHeapSizeOption, _T("")); + } + + TString mainClassName = package.GetMainClassName(); + TString mainModule = package.GetMainModule(); + + if (mainClassName.empty() == true && mainModule.empty() == true) { + Messages& messages = Messages::GetInstance(); + platform.ShowMessage(messages.GetMessage(NO_MAIN_CLASS_SPECIFIED)); + return false; + } + + configureLibrary(); + + // Initialize the arguments to JLI_Launch() + // + // On Mac OS X JLI_Launch spawns a new thread that actually starts the JVM. + // This new thread simply re-runs main(argc, argv). Therefore we do not + // want to add new args if we are still in the original main thread so we + // will treat them as command line args provided by the user ... + // Only propagate original set of args first time. + + options.AppendValue(_T("-classpath")); + options.AppendValue(classpath); + + std::list vmargs; + vmargs.push_back(package.GetCommandName()); + + if (package.HasSplashScreen() == true) { + options.AppendValue(TString(_T("-splash:")) + + package.GetSplashScreenFileName(), _T("")); + } + + if (mainModule.empty() == true) { + options.AppendValue(Helpers::ConvertJavaPathToId(mainClassName), + _T("")); + } else { + options.AppendValue(_T("-m")); + options.AppendValue(mainModule); + } + + return launchVM(options, vmargs); +} + +void JavaVirtualMachine::configureLibrary() { + Platform& platform = Platform::GetInstance(); + Package& package = Package::GetInstance(); + TString libName = package.GetJavaLibraryFileName(); + platform.addPlatformDependencies(&javaLibrary); + javaLibrary.Load(libName); +} + +bool JavaVirtualMachine::launchVM(JavaOptions& options, + std::list& vmargs) { + Platform& platform = Platform::GetInstance(); + Package& package = Package::GetInstance(); + +#ifdef MAC + // Mac adds a ProcessSerialNumber to args when launched from .app + // filter out the psn since they it's not expected in the app + if (platform.IsMainThread() == false) { + std::list loptions = options.ToList(); + vmargs.splice(vmargs.end(), loptions, + loptions.begin(), loptions.end()); + } +#else + std::list loptions = options.ToList(); + vmargs.splice(vmargs.end(), loptions, loptions.begin(), loptions.end()); +#endif + + std::list largs = package.GetArgs(); + vmargs.splice(vmargs.end(), largs, largs.begin(), largs.end()); + + size_t argc = vmargs.size(); + DynamicBuffer argv(argc + 1); + if (argv.GetData() == NULL) { + return false; + } + + unsigned int index = 0; + for (std::list::const_iterator iterator = vmargs.begin(); + iterator != vmargs.end(); iterator++) { + TString item = *iterator; + std::string arg = PlatformString(item).toStdString(); +#ifdef DEBUG + printf("%i %s\n", index, arg.c_str()); +#endif // DEBUG + argv[index] = PlatformString::duplicate(arg.c_str()); + index++; + } + + argv[argc] = NULL; + +// On Mac we can only free the boot fields if the calling thread is +// not the main thread. +#ifdef MAC + if (platform.IsMainThread() == false) { + package.FreeBootFields(); + } +#else + package.FreeBootFields(); +#endif // MAC + + if (javaLibrary.JavaVMCreate(argc, argv.GetData()) == true) { + return true; + } + + for (index = 0; index < argc; index++) { + if (argv[index] != NULL) { + delete[] argv[index]; + } + } + + return false; +} diff --git a/src/jdk.jpackage/share/native/libapplauncher/JavaVirtualMachine.h b/src/jdk.jpackage/share/native/libapplauncher/JavaVirtualMachine.h new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/native/libapplauncher/JavaVirtualMachine.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef JAVAVIRTUALMACHINE_H +#define JAVAVIRTUALMACHINE_H + + +#include "jni.h" +#include "Platform.h" +#include "Library.h" + +struct JavaOptionItem { + TString name; + TString value; + void* extraInfo; +}; + +class JavaOptions { +private: + std::list FItems; + JavaVMOption* FOptions; + +public: + JavaOptions(); + ~JavaOptions(); + + void AppendValue(const TString Key, TString Value, void* Extra); + void AppendValue(const TString Key, TString Value); + void AppendValue(const TString Key); + void AppendValues(OrderedMap Values); + void ReplaceValue(const TString Key, TString Value); + std::list ToList(); + size_t GetCount(); +}; + +class JavaVirtualMachine { +private: + JavaLibrary javaLibrary; + + void configureLibrary(); + bool launchVM(JavaOptions& options, std::list& vmargs); +public: + JavaVirtualMachine(); + ~JavaVirtualMachine(void); + + bool StartJVM(); +}; + +bool RunVM(); + +#endif // JAVAVIRTUALMACHINE_H diff --git a/src/jdk.jpackage/share/native/libapplauncher/Library.cpp b/src/jdk.jpackage/share/native/libapplauncher/Library.cpp new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/native/libapplauncher/Library.cpp @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "Library.h" +#include "Platform.h" +#include "Messages.h" +#include "PlatformString.h" + +#include +#include + +Library::Library() { + Initialize(); +} + +Library::Library(const TString &FileName) { + Initialize(); + Load(FileName); +} + +Library::~Library() { + Unload(); +} + +void Library::Initialize() { + FModule = NULL; + FDependentLibraryNames = NULL; + FDependenciesLibraries = NULL; +} + +void Library::InitializeDependencies() { + if (FDependentLibraryNames == NULL) { + FDependentLibraryNames = new std::vector(); + } + + if (FDependenciesLibraries == NULL) { + FDependenciesLibraries = new std::vector(); + } +} + +void Library::LoadDependencies() { + if (FDependentLibraryNames != NULL && FDependenciesLibraries != NULL) { + for (std::vector::const_iterator iterator = + FDependentLibraryNames->begin(); + iterator != FDependentLibraryNames->end(); iterator++) { + Library* library = new Library(); + + if (library->Load(*iterator) == true) { + FDependenciesLibraries->push_back(library); + } + } + + delete FDependentLibraryNames; + FDependentLibraryNames = NULL; + } +} + +void Library::UnloadDependencies() { + if (FDependenciesLibraries != NULL) { + for (std::vector::const_iterator iterator = + FDependenciesLibraries->begin(); + iterator != FDependenciesLibraries->end(); iterator++) { + Library* library = *iterator; + + if (library != NULL) { + library->Unload(); + delete library; + } + } + + delete FDependenciesLibraries; + FDependenciesLibraries = NULL; + } +} + +Procedure Library::GetProcAddress(const std::string& MethodName) const { + Platform& platform = Platform::GetInstance(); + return platform.GetProcAddress(FModule, MethodName); +} + +bool Library::Load(const TString &FileName) { + bool result = true; + + if (FModule == NULL) { + LoadDependencies(); + Platform& platform = Platform::GetInstance(); + FModule = platform.LoadLibrary(FileName); + + if (FModule == NULL) { + Messages& messages = Messages::GetInstance(); + platform.ShowMessage(messages.GetMessage(LIBRARY_NOT_FOUND), + FileName); + result = false; + } else { + fname = PlatformString(FileName).toStdString(); + } + } + + return result; +} + +bool Library::Unload() { + bool result = false; + + if (FModule != NULL) { + Platform& platform = Platform::GetInstance(); + platform.FreeLibrary(FModule); + FModule = NULL; + UnloadDependencies(); + result = true; + } + + return result; +} + +void Library::AddDependency(const TString &FileName) { + InitializeDependencies(); + + if (FDependentLibraryNames != NULL) { + FDependentLibraryNames->push_back(FileName); + } +} + +void Library::AddDependencies(const std::vector &Dependencies) { + if (Dependencies.size() > 0) { + InitializeDependencies(); + + if (FDependentLibraryNames != NULL) { + for (std::vector::const_iterator iterator = + FDependentLibraryNames->begin(); + iterator != FDependentLibraryNames->end(); iterator++) { + TString fileName = *iterator; + AddDependency(fileName); + } + } + } +} + +JavaLibrary::JavaLibrary() : Library(), FCreateProc(NULL) { +} + +bool JavaLibrary::JavaVMCreate(size_t argc, char *argv[]) { + if (FCreateProc == NULL) { + FCreateProc = (JAVA_CREATE) GetProcAddress(LAUNCH_FUNC); + } + + if (FCreateProc == NULL) { + Platform& platform = Platform::GetInstance(); + Messages& messages = Messages::GetInstance(); + platform.ShowMessage( + messages.GetMessage(FAILED_LOCATING_JVM_ENTRY_POINT)); + return false; + } + + return FCreateProc((int) argc, argv, + 0, NULL, + 0, NULL, + "", + "", + "java", + "java", + false, + false, + false, + 0) == 0; +} diff --git a/src/jdk.jpackage/share/native/libapplauncher/Library.h b/src/jdk.jpackage/share/native/libapplauncher/Library.h new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/native/libapplauncher/Library.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef LIBRARY_H +#define LIBRARY_H + +#include "PlatformDefs.h" +//#include "Platform.h" +#include "OrderedMap.h" + +#include "jni.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +// Private typedef for function pointer casting +#define LAUNCH_FUNC "JLI_Launch" + +typedef int (JNICALL *JAVA_CREATE)(int argc, char ** argv, + int jargc, const char** jargv, + int appclassc, const char** appclassv, + const char* fullversion, + const char* dotversion, + const char* pname, + const char* lname, + jboolean javaargs, + jboolean cpwildcard, + jboolean javaw, + jint ergo); + +class Library { +private: + std::vector *FDependentLibraryNames; + std::vector *FDependenciesLibraries; + Module FModule; + std::string fname; + + void Initialize(); + void InitializeDependencies(); + void LoadDependencies(); + void UnloadDependencies(); + +public: + void* GetProcAddress(const std::string& MethodName) const; + +public: + Library(); + Library(const TString &FileName); + ~Library(); + + bool Load(const TString &FileName); + bool Unload(); + + const std::string& GetName() const { + return fname; + } + + void AddDependency(const TString &FileName); + void AddDependencies(const std::vector &Dependencies); +}; + +class JavaLibrary : public Library { + JAVA_CREATE FCreateProc; + JavaLibrary(const TString &FileName); +public: + JavaLibrary(); + bool JavaVMCreate(size_t argc, char *argv[]); +}; + +#endif // LIBRARY_H + diff --git a/src/jdk.jpackage/share/native/libapplauncher/Macros.cpp b/src/jdk.jpackage/share/native/libapplauncher/Macros.cpp new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/native/libapplauncher/Macros.cpp @@ -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. + */ + +#include "Macros.h" +#include "Package.h" +#include "Helpers.h" + + +Macros::Macros(void) { +} + +Macros::~Macros(void) { +} + +void Macros::Initialize() { + Package& package = Package::GetInstance(); + Macros& macros = Macros::GetInstance(); + + // Public macros. + macros.AddMacro(_T("$APPDIR"), package.GetPackageRootDirectory()); + macros.AddMacro(_T("$PACKAGEDIR"), package.GetPackageAppDirectory()); + macros.AddMacro(_T("$LAUNCHERDIR"), package.GetPackageLauncherDirectory()); + macros.AddMacro(_T("$APPDATADIR"), package.GetAppDataDirectory()); + + TString javaHome = + FilePath::ExtractFilePath(package.GetJavaLibraryFileName()); + macros.AddMacro(_T("$JREHOME"), javaHome); + + // App CDS Macros + macros.AddMacro(_T("$CACHEDIR"), package.GetAppCDSCacheDirectory()); + + // Private macros. + TString javaVMLibraryName = FilePath::ExtractFileName(javaHome); + macros.AddMacro(_T("$JAVAVMLIBRARYNAME"), javaVMLibraryName); +} + +Macros& Macros::GetInstance() { + static Macros instance; + return instance; +} + +TString Macros::ExpandMacros(TString Value) { + TString result = Value; + + for (std::map::iterator iterator = FData.begin(); + iterator != FData.end(); + iterator++) { + + TString name = iterator->first; + + if (Value.find(name) != TString::npos) { + TString lvalue = iterator->second; + result = Helpers::ReplaceString(Value, name, lvalue); + result = ExpandMacros(result); + break; + } + } + + return result; +} + +void Macros::AddMacro(TString Key, TString Value) { + FData.insert(std::map::value_type(Key, Value)); +} diff --git a/src/jdk.jpackage/share/native/libapplauncher/Macros.h b/src/jdk.jpackage/share/native/libapplauncher/Macros.h new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/native/libapplauncher/Macros.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef MACROS_H +#define MACROS_H + +#include "Platform.h" + +#include + + +class Macros { +private: + std::map FData; + + Macros(void); + +public: + static Macros& GetInstance(); + static void Initialize(); + ~Macros(void); + + TString ExpandMacros(TString Value); + void AddMacro(TString Key, TString Value); +}; + +#endif // MACROS_H diff --git a/src/jdk.jpackage/share/native/libapplauncher/Messages.cpp b/src/jdk.jpackage/share/native/libapplauncher/Messages.cpp new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/native/libapplauncher/Messages.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "Messages.h" +#include "Platform.h" +#include "FilePath.h" +#include "Helpers.h" +#include "Macros.h" +#include "JavaVirtualMachine.h" + +Messages::Messages(void) { + FMessages.SetReadOnly(false); + FMessages.SetValue(LIBRARY_NOT_FOUND, _T("Failed to find library.")); + FMessages.SetValue(FAILED_CREATING_JVM, _T("Failed to create JVM")); + FMessages.SetValue(FAILED_LOCATING_JVM_ENTRY_POINT, + _T("Failed to locate JLI_Launch")); + FMessages.SetValue(NO_MAIN_CLASS_SPECIFIED, _T("No main class specified")); + FMessages.SetValue(METHOD_NOT_FOUND, _T("No method %s in class %s.")); + FMessages.SetValue(CLASS_NOT_FOUND, _T("Class %s not found.")); + FMessages.SetValue(ERROR_INVOKING_METHOD, _T("Error invoking method.")); + FMessages.SetValue(APPCDS_CACHE_FILE_NOT_FOUND, + _T("Error: AppCDS cache does not exists:\n%s\n")); +} + +Messages& Messages::GetInstance() { + static Messages instance; + // Guaranteed to be destroyed. Instantiated on first use. + return instance; +} + +Messages::~Messages(void) { +} + +TString Messages::GetMessage(const TString Key) { + TString result; + FMessages.GetValue(Key, result); + Macros& macros = Macros::GetInstance(); + result = macros.ExpandMacros(result); + return result; +} diff --git a/src/jdk.jpackage/share/native/libapplauncher/Messages.h b/src/jdk.jpackage/share/native/libapplauncher/Messages.h new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/native/libapplauncher/Messages.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef MESSAGES_H +#define MESSAGES_H + +#include "PropertyFile.h" + +#define LIBRARY_NOT_FOUND _T("library.not.found") +#define FAILED_CREATING_JVM _T("failed.creating.jvm") +#define FAILED_LOCATING_JVM_ENTRY_POINT _T("failed.locating.jvm.entry.point") +#define NO_MAIN_CLASS_SPECIFIED _T("no.main.class.specified") + +#define METHOD_NOT_FOUND _T("method.not.found") +#define CLASS_NOT_FOUND _T("class.not.found") +#define ERROR_INVOKING_METHOD _T("error.invoking.method") + +#define CONFIG_FILE_NOT_FOUND _T("config.file.not.found") + +#define BUNDLED_JVM_NOT_FOUND _T("bundled.jvm.not.found") + +#define APPCDS_CACHE_FILE_NOT_FOUND _T("appcds.cache.file.not.found") + +class Messages { +private: + PropertyFile FMessages; + + Messages(void); +public: + static Messages& GetInstance(); + ~Messages(void); + + TString GetMessage(const TString Key); +}; + +#endif // MESSAGES_H diff --git a/src/jdk.jpackage/share/native/libapplauncher/OrderedMap.h b/src/jdk.jpackage/share/native/libapplauncher/OrderedMap.h new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/native/libapplauncher/OrderedMap.h @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef ORDEREDMAP_H +#define ORDEREDMAP_H + +#include +#include +#include +#include + +#include + +template +struct JPPair +{ + typedef _T1 first_type; + typedef _T2 second_type; + + first_type first; + second_type second; + + JPPair(first_type Value1, second_type Value2) { + first = Value1; + second = Value2; + } +}; + + +template +class OrderedMap { +public: + typedef TKey key_type; + typedef TValue mapped_type; + typedef JPPair container_type; + typedef typename std::vector::iterator iterator; + typedef typename std::vector::const_iterator const_iterator; + +private: + typedef std::map map_type; + typedef std::vector list_type; + + map_type FMap; + list_type FList; + bool FAllowDuplicates; + + typename list_type::iterator FindListItem(const key_type Key) { + typename list_type::iterator result = FList.end(); + + for (typename list_type::iterator iterator = + FList.begin(); iterator != FList.end(); iterator++) { + container_type *item = *iterator; + + if (item->first == Key) { + result = iterator; + break; + } + } + + return result; + } + +public: + OrderedMap() { + FAllowDuplicates = false; + } + + OrderedMap(const OrderedMap &Value) { + Append(Value); + } + + ~OrderedMap() { + Clear(); + } + + void SetAllowDuplicates(bool Value) { + FAllowDuplicates = Value; + } + + iterator begin() { + return FList.begin(); + } + + const_iterator begin() const { + return FList.begin(); + } + + iterator end() { + return FList.end(); + } + + const_iterator end() const { + return FList.end(); + } + + void Clear() { + for (typename list_type::iterator iterator = + FList.begin(); iterator != FList.end(); iterator++) { + container_type *item = *iterator; + + if (item != NULL) { + delete item; + item = NULL; + } + } + + FMap.clear(); + FList.clear(); + } + + bool ContainsKey(key_type Key) { + bool result = false; + + if (FMap.find(Key) != FMap.end()) { + result = true; + } + + return result; + } + + std::vector GetKeys() { + std::vector result; + + for (typename list_type::const_iterator iterator = FList.begin(); + iterator != FList.end(); iterator++) { + container_type *item = *iterator; + result.push_back(item->first); + } + + return result; + } + + void Assign(const OrderedMap &Value) { + Clear(); + Append(Value); + } + + void Append(const OrderedMap &Value) { + for (size_t index = 0; index < Value.FList.size(); index++) { + container_type *item = Value.FList[index]; + Append(item->first, item->second); + } + } + + void Append(key_type Key, mapped_type Value) { + container_type *item = new container_type(Key, Value); + FMap.insert(std::pair(Key, item)); + FList.push_back(item); + } + + bool RemoveByKey(key_type Key) { + bool result = false; + typename list_type::iterator iterator = FindListItem(Key); + + if (iterator != FList.end()) { + FMap.erase(Key); + FList.erase(iterator); + result = true; + } + + return result; + } + + bool GetValue(key_type Key, mapped_type &Value) { + bool result = false; + container_type* item = FMap[Key]; + + if (item != NULL) { + Value = item->second; + result = true; + } + + return result; + } + + bool SetValue(key_type Key, mapped_type &Value) { + bool result = false; + + if ((FAllowDuplicates == false) && (ContainsKey(Key) == true)) { + container_type *item = FMap[Key]; + + if (item != NULL) { + item->second = Value; + result = true; + } + } + else { + Append(Key, Value); + result = true; + } + + return result; + } + + mapped_type &operator[](key_type Key) { + container_type* item = FMap[Key]; + assert(item != NULL); + + if (item != NULL) { + return item->second; + } + + throw std::invalid_argument("Key not found"); + } + + OrderedMap& operator= (OrderedMap &Value) { + Clear(); + Append(Value); + return *this; + } + + size_t Count() { + return FList.size(); + } +}; + +#endif // ORDEREDMAP_H diff --git a/src/jdk.jpackage/share/native/libapplauncher/Package.cpp b/src/jdk.jpackage/share/native/libapplauncher/Package.cpp new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/native/libapplauncher/Package.cpp @@ -0,0 +1,556 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "Package.h" +#include "Helpers.h" +#include "Macros.h" +#include "IniFile.h" + +#include + + +Package::Package(void) { + FInitialized = false; + Initialize(); +} + +TPlatformNumber StringToPercentageOfNumber(TString Value, + TPlatformNumber Number) { + TPlatformNumber result = 0; + size_t percentage = atoi(PlatformString(Value.c_str())); + + if (percentage > 0 && Number > 0) { + result = Number * percentage / 100; + } + + return result; +} + +void Package::Initialize() { + if (FInitialized == true) { + return; + } + + Platform& platform = Platform::GetInstance(); + + FBootFields = new PackageBootFields(); + FDebugging = dsNone; + + FBootFields->FPackageRootDirectory = platform.GetPackageRootDirectory(); + FBootFields->FPackageAppDirectory = platform.GetPackageAppDirectory(); + FBootFields->FPackageLauncherDirectory = + platform.GetPackageLauncherDirectory(); + FBootFields->FAppDataDirectory = platform.GetAppDataDirectory(); + + std::map keys = platform.GetKeys(); + + // Read from configure.cfg/Info.plist + AutoFreePtr config = + platform.GetConfigFile(platform.GetConfigFileName()); + + config->GetValue(keys[CONFIG_SECTION_APPLICATION], + keys[JPACKAGE_APP_DATA_DIR], FBootFields->FPackageAppDataDirectory); + FBootFields->FPackageAppDataDirectory = + FilePath::FixPathForPlatform(FBootFields->FPackageAppDataDirectory); + + // Main JAR. + config->GetValue(keys[CONFIG_SECTION_APPLICATION], + keys[CONFIG_MAINJAR_KEY], FBootFields->FMainJar); + FBootFields->FMainJar = + FilePath::IncludeTrailingSeparator(GetPackageAppDirectory()) + + FilePath::FixPathForPlatform(FBootFields->FMainJar); + + // Main Module. + config->GetValue(keys[CONFIG_SECTION_APPLICATION], + keys[CONFIG_MAINMODULE_KEY], FBootFields->FMainModule); + + // Classpath. + // 1. If the provided class path contains main jar then only use + // provided class path. + // 2. If class path provided by config file is empty then add main jar. + // 3. If main jar is not in provided class path then add it. + config->GetValue(keys[CONFIG_SECTION_APPLICATION], + keys[CONFIG_CLASSPATH_KEY], FBootFields->FClassPath); + FBootFields->FClassPath = + FilePath::FixPathSeparatorForPlatform(FBootFields->FClassPath); + + if (FBootFields->FClassPath.empty() == true) { + FBootFields->FClassPath = GetMainJar(); + } else if (FBootFields->FClassPath.find(GetMainJar()) == TString::npos) { + FBootFields->FClassPath = GetMainJar() + + FilePath::PathSeparator() + FBootFields->FClassPath; + } + + // Modulepath. + config->GetValue(keys[CONFIG_SECTION_APPLICATION], + keys[CONFIG_MODULEPATH_KEY], FBootFields->FModulePath); + FBootFields->FModulePath = + FilePath::FixPathSeparatorForPlatform(FBootFields->FModulePath); + + // Main Class. + config->GetValue(keys[CONFIG_SECTION_APPLICATION], + keys[CONFIG_MAINCLASSNAME_KEY], FBootFields->FMainClassName); + + // Splash Screen. + if (config->GetValue(keys[CONFIG_SECTION_APPLICATION], + keys[CONFIG_SPLASH_KEY], + FBootFields->FSplashScreenFileName) == true) { + FBootFields->FSplashScreenFileName = + FilePath::IncludeTrailingSeparator(GetPackageAppDirectory()) + + FilePath::FixPathForPlatform(FBootFields->FSplashScreenFileName); + + if (FilePath::FileExists(FBootFields->FSplashScreenFileName) == false) { + FBootFields->FSplashScreenFileName = _T(""); + } + } + + // Runtime. + config->GetValue(keys[CONFIG_SECTION_APPLICATION], + keys[JAVA_RUNTIME_KEY], FBootFields->FJavaRuntimeDirectory); + + // Read jvmargs. + PromoteAppCDSState(config); + ReadJavaOptions(config); + + // Read args if none were passed in. + if (FBootFields->FArgs.size() == 0) { + OrderedMap args; + + if (config->GetSection(keys[CONFIG_SECTION_ARGOPTIONS], args) == true) { + FBootFields->FArgs = Helpers::MapToNameValueList(args); + } + } + + // Auto Memory. + TString autoMemory; + + if (config->GetValue(keys[CONFIG_SECTION_APPLICATION], + keys[CONFIG_APP_MEMORY], autoMemory) == true) { + if (autoMemory == _T("auto") || autoMemory == _T("100%")) { + FBootFields->FMemoryState = PackageBootFields::msAuto; + FBootFields->FMemorySize = platform.GetMemorySize(); + } else if (autoMemory.length() == 2 && isdigit(autoMemory[0]) && + autoMemory[1] == '%') { + FBootFields->FMemoryState = PackageBootFields::msAuto; + FBootFields->FMemorySize = + StringToPercentageOfNumber(autoMemory.substr(0, 1), + platform.GetMemorySize()); + } else if (autoMemory.length() == 3 && isdigit(autoMemory[0]) && + isdigit(autoMemory[1]) && autoMemory[2] == '%') { + FBootFields->FMemoryState = PackageBootFields::msAuto; + FBootFields->FMemorySize = + StringToPercentageOfNumber(autoMemory.substr(0, 2), + platform.GetMemorySize()); + } else { + FBootFields->FMemoryState = PackageBootFields::msManual; + FBootFields->FMemorySize = 0; + } + } + + // Debug + TString debug; + if (config->GetValue(keys[CONFIG_SECTION_APPLICATION], + keys[CONFIG_APP_DEBUG], debug) == true) { + FBootFields->FArgs.push_back(debug); + } +} + +void Package::Clear() { + FreeBootFields(); + FInitialized = false; +} + +// This is the only location that the AppCDS state should be modified except +// by command line arguments provided by the user. +// +// The state of AppCDS is as follows: +// +// -> cdsUninitialized +// -> cdsGenCache If -Xappcds:generatecache +// -> cdsDisabled If -Xappcds:off +// -> cdsEnabled If "AppCDSJavaOptions" section is present +// -> cdsAuto If "AppCDSJavaOptions" section is present and +// app.appcds.cache=auto +// -> cdsDisabled Default +// +void Package::PromoteAppCDSState(ISectionalPropertyContainer* Config) { + Platform& platform = Platform::GetInstance(); + std::map keys = platform.GetKeys(); + + // The AppCDS state can change at this point. + switch (platform.GetAppCDSState()) { + case cdsEnabled: + case cdsAuto: + case cdsDisabled: + case cdsGenCache: { + // Do nothing. + break; + } + + case cdsUninitialized: { + if (Config->ContainsSection( + keys[CONFIG_SECTION_APPCDSJAVAOPTIONS]) == true) { + // If the AppCDS section is present then enable AppCDS. + TString appCDSCacheValue; + + // If running with AppCDS enabled, and the configuration has + // been setup so "auto" is enabled, then + // the launcher will attempt to generate the cache file + // automatically and run the application. + if (Config->GetValue(keys[CONFIG_SECTION_APPLICATION], + _T("app.appcds.cache"), appCDSCacheValue) == true && + appCDSCacheValue == _T("auto")) { + platform.SetAppCDSState(cdsAuto); + } + else { + platform.SetAppCDSState(cdsEnabled); + } + } else { + + platform.SetAppCDSState(cdsDisabled); + } + } + } +} + +void Package::ReadJavaOptions(ISectionalPropertyContainer* Config) { + Platform& platform = Platform::GetInstance(); + std::map keys = platform.GetKeys(); + + // Evaluate based on the current AppCDS state. + switch (platform.GetAppCDSState()) { + case cdsUninitialized: { + throw Exception(_T("Internal Error")); + } + + case cdsDisabled: { + Config->GetSection(keys[CONFIG_SECTION_JAVAOPTIONS], + FBootFields->FJavaOptions); + break; + } + + case cdsGenCache: { + Config->GetSection(keys[ + CONFIG_SECTION_APPCDSGENERATECACHEJAVAOPTIONS], + FBootFields->FJavaOptions); + break; + } + + case cdsAuto: + case cdsEnabled: { + if (Config->GetValue(keys[CONFIG_SECTION_APPCDSJAVAOPTIONS], + _T( "-XX:SharedArchiveFile"), + FBootFields->FAppCDSCacheFileName) == true) { + // File names may contain the incorrect path separators. + // The cache file name must be corrected at this point. + if (FBootFields->FAppCDSCacheFileName.empty() == false) { + IniFile* iniConfig = dynamic_cast(Config); + + if (iniConfig != NULL) { + FBootFields->FAppCDSCacheFileName = + FilePath::FixPathForPlatform( + FBootFields->FAppCDSCacheFileName); + iniConfig->SetValue(keys[ + CONFIG_SECTION_APPCDSJAVAOPTIONS], + _T( "-XX:SharedArchiveFile"), + FBootFields->FAppCDSCacheFileName); + } + } + + Config->GetSection(keys[CONFIG_SECTION_APPCDSJAVAOPTIONS], + FBootFields->FJavaOptions); + } + + break; + } + } +} + +void Package::SetCommandLineArguments(int argc, TCHAR* argv[]) { + if (argc > 0) { + std::list args; + + // Prepare app arguments. Skip value at index 0 - + // this is path to executable. + FBootFields->FCommandName = argv[0]; + + // Path to executable is at 0 index so start at index 1. + for (int index = 1; index < argc; index++) { + TString arg = argv[index]; + +#ifdef DEBUG + if (arg == _T("-debug")) { + FDebugging = dsNative; + } + + if (arg == _T("-javadebug")) { + FDebugging = dsJava; + } +#endif //DEBUG +#ifdef MAC + if (arg.find(_T("-psn_"), 0) != TString::npos) { + Platform& platform = Platform::GetInstance(); + + if (platform.IsMainThread() == true) { +#ifdef DEBUG + printf("%s\n", arg.c_str()); +#endif //DEBUG + continue; + } + } + + if (arg == _T("-NSDocumentRevisionsDebugMode")) { + // Ignore -NSDocumentRevisionsDebugMode and + // the following YES/NO + index++; + continue; + } +#endif //MAC + + args.push_back(arg); + } + + if (args.size() > 0) { + FBootFields->FArgs = args; + } + } +} + +Package& Package::GetInstance() { + static Package instance; + // Guaranteed to be destroyed. Instantiated on first use. + return instance; +} + +Package::~Package(void) { + FreeBootFields(); +} + +void Package::FreeBootFields() { + if (FBootFields != NULL) { + delete FBootFields; + FBootFields = NULL; + } +} + +OrderedMap Package::GetJavaOptions() { + return FBootFields->FJavaOptions; +} + +std::vector GetKeysThatAreNotDuplicates(OrderedMap &Defaults, OrderedMap &Overrides) { + std::vector result; + std::vector overrideKeys = Overrides.GetKeys(); + + for (size_t index = 0; index < overrideKeys.size(); index++) { + TString overridesKey = overrideKeys[index]; + TString overridesValue; + TString defaultValue; + + if ((Defaults.ContainsKey(overridesKey) == false) || + (Defaults.GetValue(overridesKey, defaultValue) == true && + Overrides.GetValue(overridesKey, overridesValue) == true && + defaultValue != overridesValue)) { + result.push_back(overridesKey); + } + } + + return result; +} + +OrderedMap CreateOrderedMapFromKeyList(OrderedMap &Map, std::vector &Keys) { + OrderedMap result; + + for (size_t index = 0; index < Keys.size(); index++) { + TString key = Keys[index]; + TString value; + + if (Map.GetValue(key, value) == true) { + result.Append(key, value); + } + } + + return result; +} + +std::vector GetKeysThatAreNotOverridesOfDefaultValues( + OrderedMap &Defaults, OrderedMap &Overrides) { + std::vector result; + std::vector keys = Overrides.GetKeys(); + + for (unsigned int index = 0; index< keys.size(); index++) { + TString key = keys[index]; + + if (Defaults.ContainsKey(key) == true) { + try { + TString value = Overrides[key]; + Defaults[key] = value; + } + catch (std::out_of_range &) { + } + } + else { + result.push_back(key); + } + } + + return result; +} + +std::list Package::GetArgs() { + assert(FBootFields != NULL); + return FBootFields->FArgs; +} + +TString Package::GetPackageRootDirectory() { + assert(FBootFields != NULL); + return FBootFields->FPackageRootDirectory; +} + +TString Package::GetPackageAppDirectory() { + assert(FBootFields != NULL); + return FBootFields->FPackageAppDirectory; +} + +TString Package::GetPackageLauncherDirectory() { + assert(FBootFields != NULL); + return FBootFields->FPackageLauncherDirectory; +} + +TString Package::GetAppDataDirectory() { + assert(FBootFields != NULL); + return FBootFields->FAppDataDirectory; +} + +TString Package::GetAppCDSCacheDirectory() { + if (FAppCDSCacheDirectory.empty()) { + Platform& platform = Platform::GetInstance(); + FAppCDSCacheDirectory = FilePath::IncludeTrailingSeparator( + platform.GetAppDataDirectory()) + + FilePath::IncludeTrailingSeparator( + GetPackageAppDataDirectory()) + _T("cache"); + + Macros& macros = Macros::GetInstance(); + FAppCDSCacheDirectory = macros.ExpandMacros(FAppCDSCacheDirectory); + FAppCDSCacheDirectory = + FilePath::FixPathForPlatform(FAppCDSCacheDirectory); + } + + return FAppCDSCacheDirectory; +} + +TString Package::GetAppCDSCacheFileName() { + assert(FBootFields != NULL); + + if (FBootFields->FAppCDSCacheFileName.empty() == false) { + Macros& macros = Macros::GetInstance(); + FBootFields->FAppCDSCacheFileName = + macros.ExpandMacros(FBootFields->FAppCDSCacheFileName); + FBootFields->FAppCDSCacheFileName = + FilePath::FixPathForPlatform(FBootFields->FAppCDSCacheFileName); + } + + return FBootFields->FAppCDSCacheFileName; +} + +TString Package::GetPackageAppDataDirectory() { + assert(FBootFields != NULL); + return FBootFields->FPackageAppDataDirectory; +} + +TString Package::GetClassPath() { + assert(FBootFields != NULL); + return FBootFields->FClassPath; +} + +TString Package::GetModulePath() { + assert(FBootFields != NULL); + return FBootFields->FModulePath; +} + +TString Package::GetMainJar() { + assert(FBootFields != NULL); + return FBootFields->FMainJar; +} + +TString Package::GetMainModule() { + assert(FBootFields != NULL); + return FBootFields->FMainModule; +} + +TString Package::GetMainClassName() { + assert(FBootFields != NULL); + return FBootFields->FMainClassName; +} + +TString Package::GetJavaLibraryFileName() { + assert(FBootFields != NULL); + + if (FBootFields->FJavaLibraryFileName.empty() == true) { + Platform& platform = Platform::GetInstance(); + Macros& macros = Macros::GetInstance(); + TString jvmRuntimePath = macros.ExpandMacros(GetJavaRuntimeDirectory()); + FBootFields->FJavaLibraryFileName = + platform.GetBundledJavaLibraryFileName(jvmRuntimePath); + } + + return FBootFields->FJavaLibraryFileName; +} + +TString Package::GetJavaRuntimeDirectory() { + assert(FBootFields != NULL); + return FBootFields->FJavaRuntimeDirectory; +} + +TString Package::GetSplashScreenFileName() { + assert(FBootFields != NULL); + return FBootFields->FSplashScreenFileName; +} + +bool Package::HasSplashScreen() { + assert(FBootFields != NULL); + return FilePath::FileExists(FBootFields->FSplashScreenFileName); +} + +TString Package::GetCommandName() { + assert(FBootFields != NULL); + return FBootFields->FCommandName; +} + +TPlatformNumber Package::GetMemorySize() { + assert(FBootFields != NULL); + return FBootFields->FMemorySize; +} + +PackageBootFields::MemoryState Package::GetMemoryState() { + assert(FBootFields != NULL); + return FBootFields->FMemoryState; +} + +DebugState Package::Debugging() { + return FDebugging; +} diff --git a/src/jdk.jpackage/share/native/libapplauncher/Package.h b/src/jdk.jpackage/share/native/libapplauncher/Package.h new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/native/libapplauncher/Package.h @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef PACKAGE_H +#define PACKAGE_H + + +#include "Platform.h" +#include "PlatformString.h" +#include "FilePath.h" +#include "PropertyFile.h" + +#include +#include + +class PackageBootFields { +public: + enum MemoryState {msManual, msAuto}; + +public: + OrderedMap FJavaOptions; + std::list FArgs; + + TString FPackageRootDirectory; + TString FPackageAppDirectory; + TString FPackageLauncherDirectory; + TString FAppDataDirectory; + TString FPackageAppDataDirectory; + TString FClassPath; + TString FModulePath; + TString FMainJar; + TString FMainModule; + TString FMainClassName; + TString FJavaRuntimeDirectory; + TString FJavaLibraryFileName; + TString FSplashScreenFileName; + bool FUseJavaPreferences; + TString FCommandName; + + TString FAppCDSCacheFileName; + + TPlatformNumber FMemorySize; + MemoryState FMemoryState; +}; + + +class Package { +private: + Package(Package const&); // Don't Implement. + void operator=(Package const&); // Don't implement + +private: + bool FInitialized; + PackageBootFields* FBootFields; + TString FAppCDSCacheDirectory; + + DebugState FDebugging; + + Package(void); + + TString GetMainJar(); + void ReadJavaOptions(ISectionalPropertyContainer* Config); + void PromoteAppCDSState(ISectionalPropertyContainer* Config); + +public: + static Package& GetInstance(); + ~Package(void); + + void Initialize(); + void Clear(); + void FreeBootFields(); + + void SetCommandLineArguments(int argc, TCHAR* argv[]); + + OrderedMap GetJavaOptions(); + TString GetMainModule(); + + std::list GetArgs(); + + TString GetPackageRootDirectory(); + TString GetPackageAppDirectory(); + TString GetPackageLauncherDirectory(); + TString GetAppDataDirectory(); + + TString GetAppCDSCacheDirectory(); + TString GetAppCDSCacheFileName(); + + TString GetPackageAppDataDirectory(); + TString GetClassPath(); + TString GetModulePath(); + TString GetMainClassName(); + TString GetJavaLibraryFileName(); + TString GetJavaRuntimeDirectory(); + TString GetSplashScreenFileName(); + bool HasSplashScreen(); + TString GetCommandName(); + + TPlatformNumber GetMemorySize(); + PackageBootFields::MemoryState GetMemoryState(); + + DebugState Debugging(); +}; + +#endif // PACKAGE_H diff --git a/src/jdk.jpackage/share/native/libapplauncher/Platform.cpp b/src/jdk.jpackage/share/native/libapplauncher/Platform.cpp new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/native/libapplauncher/Platform.cpp @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "Platform.h" +#include "Messages.h" +#include "PlatformString.h" +#include "FilePath.h" + +#include +#include + +#ifdef WINDOWS +#include "WindowsPlatform.h" +#endif // WINDOWS +#ifdef LINUX +#include "LinuxPlatform.h" +#endif // LINUX +#ifdef MAC +#include "MacPlatform.h" +#endif // MAC + +Platform& Platform::GetInstance() { +#ifdef WINDOWS + static WindowsPlatform instance; +#endif // WINDOWS + +#ifdef LINUX + static LinuxPlatform instance; +#endif // LINUX + +#ifdef MAC + static MacPlatform instance; +#endif // MAC + + return instance; +} + +TString Platform::GetConfigFileName() { + TString result; + TString basedir = GetPackageAppDirectory(); + + if (basedir.empty() == false) { + basedir = FilePath::IncludeTrailingSeparator(basedir); + TString appConfig = basedir + GetAppName() + _T(".cfg"); + + if (FilePath::FileExists(appConfig) == true) { + result = appConfig; + } + else { + result = basedir + _T("package.cfg"); + + if (FilePath::FileExists(result) == false) { + result = _T(""); + } + } + } + + return result; +} + +std::list Platform::LoadFromFile(TString FileName) { + std::list result; + + if (FilePath::FileExists(FileName) == true) { + std::wifstream stream(FileName.data()); + InitStreamLocale(&stream); + + if (stream.is_open() == true) { + while (stream.eof() == false) { + std::wstring line; + std::getline(stream, line); + + // # at the first character will comment out the line. + if (line.empty() == false && line[0] != '#') { + result.push_back(PlatformString(line).toString()); + } + } + } + } + + return result; +} + +void Platform::SaveToFile(TString FileName, std::list Contents, bool ownerOnly) { + TString path = FilePath::ExtractFilePath(FileName); + + if (FilePath::DirectoryExists(path) == false) { + FilePath::CreateDirectory(path, ownerOnly); + } + + std::wofstream stream(FileName.data()); + InitStreamLocale(&stream); + + FilePath::ChangePermissions(FileName.data(), ownerOnly); + + if (stream.is_open() == true) { + for (std::list::const_iterator iterator = + Contents.begin(); iterator != Contents.end(); iterator++) { + TString line = *iterator; + stream << PlatformString(line).toUnicodeString() << std::endl; + } + } +} + +std::map Platform::GetKeys() { + std::map keys; + keys.insert(std::map::value_type(CONFIG_VERSION, + _T("app.version"))); + keys.insert(std::map::value_type(CONFIG_MAINJAR_KEY, + _T("app.mainjar"))); + keys.insert(std::map::value_type(CONFIG_MAINMODULE_KEY, + _T("app.mainmodule"))); + keys.insert(std::map::value_type(CONFIG_MAINCLASSNAME_KEY, + _T("app.mainclass"))); + keys.insert(std::map::value_type(CONFIG_CLASSPATH_KEY, + _T("app.classpath"))); + keys.insert(std::map::value_type(CONFIG_MODULEPATH_KEY, + _T("app.modulepath"))); + keys.insert(std::map::value_type(APP_NAME_KEY, + _T("app.name"))); + keys.insert(std::map::value_type(JAVA_RUNTIME_KEY, + _T("app.runtime"))); + keys.insert(std::map::value_type(JPACKAGE_APP_DATA_DIR, + _T("app.identifier"))); + keys.insert(std::map::value_type(CONFIG_SPLASH_KEY, + _T("app.splash"))); + keys.insert(std::map::value_type(CONFIG_APP_MEMORY, + _T("app.memory"))); + keys.insert(std::map::value_type(CONFIG_APP_DEBUG, + _T("app.debug"))); + keys.insert(std::map::value_type(CONFIG_APPLICATION_INSTANCE, + _T("app.application.instance"))); + keys.insert(std::map::value_type(CONFIG_SECTION_APPLICATION, + _T("Application"))); + keys.insert(std::map::value_type(CONFIG_SECTION_JAVAOPTIONS, + _T("JavaOptions"))); + keys.insert(std::map::value_type(CONFIG_SECTION_APPCDSJAVAOPTIONS, + _T("AppCDSJavaOptions"))); + keys.insert(std::map::value_type(CONFIG_SECTION_APPCDSGENERATECACHEJAVAOPTIONS, + _T("AppCDSGenerateCacheJavaOptions"))); + keys.insert(std::map::value_type(CONFIG_SECTION_ARGOPTIONS, + _T("ArgOptions"))); + + return keys; +} diff --git a/src/jdk.jpackage/share/native/libapplauncher/Platform.h b/src/jdk.jpackage/share/native/libapplauncher/Platform.h new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/native/libapplauncher/Platform.h @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef PLATFORM_H +#define PLATFORM_H + +#include "PlatformDefs.h" +#include "Properties.h" +#include "OrderedMap.h" +#include "Library.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +// Config file sections +#define CONFIG_SECTION_APPLICATION _T("CONFIG_SECTION_APPLICATION") +#define CONFIG_SECTION_JAVAOPTIONS _T("CONFIG_SECTION_JAVAOPTIONS") +#define CONFIG_SECTION_APPCDSJAVAOPTIONS _T("CONFIG_SECTION_APPCDSJAVAOPTIONS") +#define CONFIG_SECTION_ARGOPTIONS _T("CONFIG_SECTION_ARGOPTIONS") +#define CONFIG_SECTION_APPCDSGENERATECACHEJAVAOPTIONS \ + _T("CONFIG_SECTION_APPCDSGENERATECACHEJAVAOPTIONS") + +// Config file keys. +#define CONFIG_VERSION _T("CONFIG_VERSION") +#define CONFIG_MAINJAR_KEY _T("CONFIG_MAINJAR_KEY") +#define CONFIG_MAINMODULE_KEY _T("CONFIG_MAINMODULE_KEY") +#define CONFIG_MAINCLASSNAME_KEY _T("CONFIG_MAINCLASSNAME_KEY") +#define CONFIG_CLASSPATH_KEY _T("CONFIG_CLASSPATH_KEY") +#define CONFIG_MODULEPATH_KEY _T("CONFIG_MODULEPATH_KEY") +#define APP_NAME_KEY _T("APP_NAME_KEY") +#define CONFIG_SPLASH_KEY _T("CONFIG_SPLASH_KEY") +#define CONFIG_APP_MEMORY _T("CONFIG_APP_MEMORY") +#define CONFIG_APP_DEBUG _T("CONFIG_APP_DEBUG") +#define CONFIG_APPLICATION_INSTANCE _T("CONFIG_APPLICATION_INSTANCE") + +#define JAVA_RUNTIME_KEY _T("JAVA_RUNTIME_KEY") +#define JPACKAGE_APP_DATA_DIR _T("CONFIG_APP_IDENTIFIER") + +struct WideString { + size_t length; + wchar_t* data; + + WideString() { length = 0; data = NULL; } +}; + +struct MultibyteString { + size_t length; + char* data; + + MultibyteString() { length = 0; data = NULL; } +}; + +class Process { +protected: + std::list FOutput; + +public: + Process() { + Output.SetInstance(this); + Input.SetInstance(this); + } + + virtual ~Process() {} + + virtual bool IsRunning() = 0; + virtual bool Terminate() = 0; + virtual bool Execute(const TString Application, + const std::vector Arguments, bool AWait = false) = 0; + virtual bool Wait() = 0; + virtual TProcessID GetProcessID() = 0; + + virtual std::list GetOutput() { return FOutput; } + virtual void SetInput(TString Value) = 0; + + ReadProperty, &Process::GetOutput> Output; + WriteProperty Input; +}; + + +template +class AutoFreePtr { +private: + T* FObject; + +public: + AutoFreePtr() { + FObject = NULL; + } + + AutoFreePtr(T* Value) { + FObject = Value; + } + + ~AutoFreePtr() { + if (FObject != NULL) { + delete FObject; + } + } + + operator T* () const { + return FObject; + } + + T& operator* () const { + return *FObject; + } + + T* operator->() const { + return FObject; + } + + T** operator&() { + return &FObject; + } + + T* operator=(const T * rhs) { + FObject = rhs; + return FObject; + } +}; + +enum DebugState {dsNone, dsNative, dsJava}; +enum MessageResponse {mrOK, mrCancel}; +enum AppCDSState {cdsUninitialized, cdsDisabled, + cdsEnabled, cdsAuto, cdsGenCache}; + +class Platform { +private: + AppCDSState FAppCDSState; + +protected: + Platform(void): FAppCDSState(cdsUninitialized) { + } + +public: + AppCDSState GetAppCDSState() { return FAppCDSState; } + void SetAppCDSState(AppCDSState Value) { FAppCDSState = Value; } + + static Platform& GetInstance(); + + virtual ~Platform(void) {} + +public: + virtual void ShowMessage(TString title, TString description) = 0; + virtual void ShowMessage(TString description) = 0; + virtual MessageResponse ShowResponseMessage(TString title, + TString description) = 0; + + virtual void SetCurrentDirectory(TString Value) = 0; + + // Caller must free result using delete[]. + virtual TCHAR* ConvertStringToFileSystemString(TCHAR* Source, + bool &release) = 0; + + // Caller must free result using delete[]. + virtual TCHAR* ConvertFileSystemStringToString(TCHAR* Source, + bool &release) = 0; + + // Returns: + // Windows=C:\Users\\AppData\Local + // Linux=~/.local + // Mac=~/Library/Application Support + virtual TString GetAppDataDirectory() = 0; + + virtual TString GetPackageAppDirectory() = 0; + virtual TString GetPackageLauncherDirectory() = 0; + virtual TString GetPackageRuntimeBinDirectory() = 0; + virtual TString GetAppName() = 0; + + virtual TString GetConfigFileName(); + + virtual TString GetBundledJavaLibraryFileName(TString RuntimePath) = 0; + + // Caller must free result. + virtual ISectionalPropertyContainer* GetConfigFile(TString FileName) = 0; + + virtual TString GetModuleFileName() = 0; + virtual TString GetPackageRootDirectory() = 0; + + virtual Module LoadLibrary(TString FileName) = 0; + virtual void FreeLibrary(Module Module) = 0; + virtual Procedure GetProcAddress(Module Module, std::string MethodName) = 0; + + // Caller must free result. + virtual Process* CreateProcess() = 0; + + virtual bool IsMainThread() = 0; + + // Returns megabytes. + virtual TPlatformNumber GetMemorySize() = 0; + + virtual std::map GetKeys(); + + virtual void InitStreamLocale(wios *stream) = 0; + virtual std::list LoadFromFile(TString FileName); + virtual void SaveToFile(TString FileName, + std::list Contents, bool ownerOnly); + + virtual TString GetTempDirectory() = 0; + + virtual void addPlatformDependencies(JavaLibrary *pJavaLibrary) = 0; + +public: + // String helpers + // Caller must free result using delete[]. + static void CopyString(char *Destination, + size_t NumberOfElements, const char *Source); + + // Caller must free result using delete[]. + static void CopyString(wchar_t *Destination, + size_t NumberOfElements, const wchar_t *Source); + + static WideString MultibyteStringToWideString(const char* value); + static MultibyteString WideStringToMultibyteString(const wchar_t* value); +}; + +class Exception: public std::exception { +private: + TString FMessage; + +protected: + void SetMessage(const TString Message) { + FMessage = Message; + } + +public: + explicit Exception() : exception() {} + explicit Exception(const TString Message) : exception() { + SetMessage(Message); + } + virtual ~Exception() throw() {} + + TString GetMessage() { return FMessage; } +}; + +#endif // PLATFORM_H diff --git a/src/jdk.jpackage/share/native/libapplauncher/PlatformString.cpp b/src/jdk.jpackage/share/native/libapplauncher/PlatformString.cpp new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/native/libapplauncher/PlatformString.cpp @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "PlatformString.h" + +#include "Helpers.h" + +#include +#include +#include +#include +#include +#include + +#include "jni.h" + +void PlatformString::initialize() { + FWideTStringToFree = NULL; + FLength = 0; + FData = NULL; +} + +PlatformString::PlatformString(void) { + initialize(); +} + +PlatformString::~PlatformString(void) { + if (FData != NULL) { + delete[] FData; + } + + if (FWideTStringToFree != NULL) { + delete[] FWideTStringToFree; + } +} + +PlatformString::PlatformString(const PlatformString &value) { + initialize(); + FLength = value.FLength; + FData = new char[FLength + 1]; + Platform::CopyString(FData, FLength + 1, value.FData); +} + +PlatformString::PlatformString(const char* value) { + initialize(); + FLength = strlen(value); + FData = new char[FLength + 1]; + Platform::CopyString(FData, FLength + 1, value); +} + +PlatformString::PlatformString(size_t Value) { + initialize(); + + std::stringstream ss; + std::string s; + ss << Value; + s = ss.str(); + + FLength = strlen(s.c_str()); + FData = new char[FLength + 1]; + Platform::CopyString(FData, FLength + 1, s.c_str()); +} + +PlatformString::PlatformString(const wchar_t* value) { + initialize(); + MultibyteString temp = Platform::WideStringToMultibyteString(value); + FLength = temp.length; + FData = temp.data; +} + +PlatformString::PlatformString(const std::string &value) { + initialize(); + const char* lvalue = value.data(); + FLength = value.size(); + FData = new char[FLength + 1]; + Platform::CopyString(FData, FLength + 1, lvalue); +} + +PlatformString::PlatformString(const std::wstring &value) { + initialize(); + const wchar_t* lvalue = value.data(); + MultibyteString temp = Platform::WideStringToMultibyteString(lvalue); + FLength = temp.length; + FData = temp.data; +} + +TString PlatformString::Format(const TString value, ...) { + TString result = value; + + va_list arglist; + va_start(arglist, value); + + while (1) { + size_t pos = result.find(_T("%s"), 0); + + if (pos == TString::npos) { + break; + } + else { + TCHAR* arg = va_arg(arglist, TCHAR*); + + if (arg == NULL) { + break; + } + else { + result.replace(pos, StringLength(_T("%s")), arg); + } + } + } + + va_end(arglist); + + return result; +} + +size_t PlatformString::length() { + return FLength; +} + +char* PlatformString::c_str() { + return FData; +} + +char* PlatformString::toMultibyte() { + return FData; +} + +wchar_t* PlatformString::toWideString() { + WideString result = Platform::MultibyteStringToWideString(FData); + + if (result.data != NULL) { + if (FWideTStringToFree != NULL) { + delete [] FWideTStringToFree; + } + + FWideTStringToFree = result.data; + } + + return result.data; +} + +std::wstring PlatformString::toUnicodeString() { + std::wstring result; + wchar_t* data = toWideString(); + + if (FLength != 0 && data != NULL) { + // NOTE: Cleanup of result is handled by PlatformString destructor. + result = data; + } + + return result; +} + +std::string PlatformString::toStdString() { + std::string result; + char* data = toMultibyte(); + + if (FLength > 0 && data != NULL) { + result = data; + } + + return result; +} + +TCHAR* PlatformString::toPlatformString() { +#ifdef _UNICODE + return toWideString(); +#else + return c_str(); +#endif //_UNICODE +} + +TString PlatformString::toString() { +#ifdef _UNICODE + return toUnicodeString(); +#else + return toStdString(); +#endif //_UNICODE +} + +PlatformString::operator char* () { + return c_str(); +} + +PlatformString::operator wchar_t* () { + return toWideString(); +} + +PlatformString::operator std::wstring () { + return toUnicodeString(); +} + +char* PlatformString::duplicate(const char* Value) { + size_t length = strlen(Value); + char* result = new char[length + 1]; + Platform::CopyString(result, length + 1, Value); + return result; +} + +wchar_t* PlatformString::duplicate(const wchar_t* Value) { + size_t length = wcslen(Value); + wchar_t* result = new wchar_t[length + 1]; + Platform::CopyString(result, length + 1, Value); + return result; +} diff --git a/src/jdk.jpackage/share/native/libapplauncher/PlatformString.h b/src/jdk.jpackage/share/native/libapplauncher/PlatformString.h new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/native/libapplauncher/PlatformString.h @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef PLATFORMSTRING_H +#define PLATFORMSTRING_H + + +#include +#include +#include +#include + +#include "jni.h" +#include "Platform.h" + + +template +class DynamicBuffer { +private: + T* FData; + size_t FSize; + +public: + DynamicBuffer(size_t Size) { + FSize = 0; + FData = NULL; + Resize(Size); + } + + ~DynamicBuffer() { + delete[] FData; + } + + T* GetData() { return FData; } + size_t GetSize() { return FSize; } + + bool Resize(size_t Size) { + FSize = Size; + + if (FData != NULL) { + delete[] FData; + FData = NULL; + } + + if (FSize != 0) { + FData = new T[FSize]; + if (FData != NULL) { + Zero(); + } else { + return false; + } + } + + return true; + } + + void Zero() { + memset(FData, 0, FSize * sizeof(T)); + } + + T& operator[](size_t index) { + return FData[index]; + } +}; + +class PlatformString { +private: + char* FData; // Stored as UTF-8 + size_t FLength; + wchar_t* FWideTStringToFree; + + void initialize(); + +// Prohibit Heap-Based PlatformStrings +private: + static void *operator new(size_t size); + static void operator delete(void *ptr); + +public: + PlatformString(void); + PlatformString(const PlatformString &value); + PlatformString(const char* value); + PlatformString(const wchar_t* value); + PlatformString(const std::string &value); + PlatformString(const std::wstring &value); + PlatformString(size_t Value); + + static TString Format(const TString value, ...); + + ~PlatformString(void); + + size_t length(); + + char* c_str(); + char* toMultibyte(); + wchar_t* toWideString(); + std::wstring toUnicodeString(); + std::string toStdString(); + TCHAR* toPlatformString(); + TString toString(); + + operator char* (); + operator wchar_t* (); + operator std::wstring (); + + // Caller must free result using delete[]. + static char* duplicate(const char* Value); + + // Caller must free result using delete[]. + static wchar_t* duplicate(const wchar_t* Value); +}; + + +#endif // PLATFORMSTRING_H diff --git a/src/jdk.jpackage/share/native/libapplauncher/Properties.h b/src/jdk.jpackage/share/native/libapplauncher/Properties.h new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/native/libapplauncher/Properties.h @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef PROPERTIES_H +#define PROPERTIES_H + +#include "PlatformDefs.h" +#include "OrderedMap.h" + +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include + +//using namespace std; + +template +class Property { +private: + ObjectType* FObject; + +public: + Property() { + FObject = NULL; + } + + void SetInstance(ObjectType* Value) { + FObject = Value; + } + + // To set the value using the set method. + ValueType operator =(const ValueType& Value) { + assert(FObject != NULL); + (FObject->*setter)(Value); + return Value; + } + + // The Property class is treated as the internal type. + operator ValueType() { + assert(FObject != NULL); + return (FObject->*getter)(); + } +}; + +template +class ReadProperty { +private: + ObjectType* FObject; + +public: + ReadProperty() { + FObject = NULL; + } + + void SetInstance(ObjectType* Value) { + FObject = Value; + } + + // The Property class is treated as the internal type. + operator ValueType() { + assert(FObject != NULL); + return (FObject->*getter)(); + } +}; + +template +class WriteProperty { +private: + ObjectType* FObject; + +public: + WriteProperty() { + FObject = NULL; + } + + void SetInstance(ObjectType* Value) { + FObject = Value; + } + + // To set the value using the set method. + ValueType operator =(const ValueType& Value) { + assert(FObject != NULL); + (FObject->*setter)(Value); + return Value; + } +}; + +template +class StaticProperty { +public: + StaticProperty() { + } + + // To set the value using the set method. + ValueType operator =(const ValueType& Value) { + (*getter)(Value); + return Value; + } + + // The Property class is treated as the internal type which is the getter. + operator ValueType() { + return (*setter)(); + } +}; + +template +class StaticReadProperty { +public: + StaticReadProperty() { + } + + // The Property class is treated as the internal type which is the getter. + operator ValueType() { + return (*getter)(); + } +}; + +template +class StaticWriteProperty { +public: + StaticWriteProperty() { + } + + // To set the value using the set method. + ValueType operator =(const ValueType& Value) { + (*setter)(Value); + return Value; + } +}; + +class IPropertyContainer { +public: + IPropertyContainer(void) {} + virtual ~IPropertyContainer(void) {} + + virtual bool GetValue(const TString Key, TString& Value) = 0; + virtual size_t GetCount() = 0; +}; + +class ISectionalPropertyContainer { +public: + ISectionalPropertyContainer(void) {} + virtual ~ISectionalPropertyContainer(void) {} + + virtual bool GetValue(const TString SectionName, + const TString Key, TString& Value) = 0; + virtual bool ContainsSection(const TString SectionName) = 0; + virtual bool GetSection(const TString SectionName, + OrderedMap &Data) = 0; +}; + +#endif // PROPERTIES_H + diff --git a/src/jdk.jpackage/share/native/libapplauncher/PropertyFile.cpp b/src/jdk.jpackage/share/native/libapplauncher/PropertyFile.cpp new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/native/libapplauncher/PropertyFile.cpp @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "PropertyFile.h" + +#include "Helpers.h" +#include "FilePath.h" + +#include + + +PropertyFile::PropertyFile(void) : IPropertyContainer() { + FReadOnly = false; + FModified = false; +} + +PropertyFile::PropertyFile(const TString FileName) : IPropertyContainer() { + FReadOnly = true; + FModified = false; + LoadFromFile(FileName); +} + +PropertyFile::PropertyFile(OrderedMap Value) { + FData.Append(Value); +} + +PropertyFile::PropertyFile(PropertyFile &Value) { + FData = Value.FData; + FReadOnly = Value.FReadOnly; + FModified = Value.FModified; +} + +PropertyFile::~PropertyFile(void) { + FData.Clear(); +} + +void PropertyFile::SetModified(bool Value) { + FModified = Value; +} + +bool PropertyFile::IsModified() { + return FModified; +} + +bool PropertyFile::GetReadOnly() { + return FReadOnly; +} + +void PropertyFile::SetReadOnly(bool Value) { + FReadOnly = Value; +} + +bool PropertyFile::LoadFromFile(const TString FileName) { + bool result = false; + Platform& platform = Platform::GetInstance(); + + std::list contents = platform.LoadFromFile(FileName); + + if (contents.empty() == false) { + for (std::list::const_iterator iterator = contents.begin(); + iterator != contents.end(); iterator++) { + TString line = *iterator; + TString name; + TString value; + + if (Helpers::SplitOptionIntoNameValue(line, name, value) == true) { + FData.Append(name, value); + } + } + + SetModified(false); + result = true; + } + + return result; +} + +bool PropertyFile::SaveToFile(const TString FileName, bool ownerOnly) { + bool result = false; + + if (GetReadOnly() == false && IsModified()) { + std::list contents; + std::vector keys = FData.GetKeys(); + + for (size_t index = 0; index < keys.size(); index++) { + TString name = keys[index]; + + try { + TString value;// = FData[index]; + + if (FData.GetValue(name, value) == true) { + TString line = name + _T('=') + value; + contents.push_back(line); + } + } + catch (std::out_of_range &) { + } + } + + Platform& platform = Platform::GetInstance(); + platform.SaveToFile(FileName, contents, ownerOnly); + + SetModified(false); + result = true; + } + + return result; +} + +bool PropertyFile::GetValue(const TString Key, TString& Value) { + return FData.GetValue(Key, Value); +} + +bool PropertyFile::SetValue(const TString Key, TString Value) { + bool result = false; + + if (GetReadOnly() == false) { + FData.SetValue(Key, Value); + SetModified(true); + result = true; + } + + return result; +} + +bool PropertyFile::RemoveKey(const TString Key) { + bool result = false; + + if (GetReadOnly() == false) { + result = FData.RemoveByKey(Key); + + if (result == true) { + SetModified(true); + } + } + + return result; +} + +size_t PropertyFile::GetCount() { + return FData.Count(); +} + +OrderedMap PropertyFile::GetData() { + return FData; +} diff --git a/src/jdk.jpackage/share/native/libapplauncher/PropertyFile.h b/src/jdk.jpackage/share/native/libapplauncher/PropertyFile.h new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/native/libapplauncher/PropertyFile.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef PROPERTYFILE_H +#define PROPERTYFILE_H + +#include "Platform.h" +#include "Helpers.h" + + +class PropertyFile : public IPropertyContainer { +private: + bool FReadOnly; + bool FModified; + OrderedMap FData; + + void SetModified(bool Value); + +public: + PropertyFile(void); + PropertyFile(const TString FileName); + PropertyFile(OrderedMap Value); + PropertyFile(PropertyFile &Value); + virtual ~PropertyFile(void); + + bool IsModified(); + bool GetReadOnly(); + void SetReadOnly(bool Value); + + bool LoadFromFile(const TString FileName); + bool SaveToFile(const TString FileName, bool ownerOnly = true); + + bool SetValue(const TString Key, TString Value); + bool RemoveKey(const TString Key); + + OrderedMap GetData(); + + // IPropertyContainer + virtual bool GetValue(const TString Key, TString& Value); + virtual size_t GetCount(); +}; + +#endif // PROPERTYFILE_H diff --git a/src/jdk.jpackage/share/native/libapplauncher/main.cpp b/src/jdk.jpackage/share/native/libapplauncher/main.cpp new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/share/native/libapplauncher/main.cpp @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "Platform.h" +#include "PlatformString.h" +#include "FilePath.h" +#include "PropertyFile.h" +#include "JavaVirtualMachine.h" +#include "Package.h" +#include "Macros.h" +#include "Messages.h" + +#include +#include +#include + +/* +This is the app launcher program for application packaging on Windows, Mac, + and Linux. + +Basic approach: + - Launcher (jpackageapplauncher) is executable that loads + applauncher.dll/libapplauncher.dylib/libapplauncher.so + and calls start_launcher below. + - Reads app/package.cfg or Info.plist or app/.cfg for application + launch configuration (package.cfg is property file). + - Load Java with requested Java settings (bundled client Java if availble, + server or installed Java otherwise). + - Wait for Java to exit and then exit from Main + - To debug application by passing command line argument. + - Application folder is added to the library path (so LoadLibrary()) works. + +Limitations and future work: + - Running Java code in primordial thread may cause problems + (example: can not use custom stack size). + Solution used by java launcher is to create a new thread to invoke Java. + See CR 6316197 for more information. +*/ + +extern "C" { + + JNIEXPORT bool start_launcher(int argc, TCHAR* argv[]) { + bool result = false; + bool parentProcess = true; + + // Platform must be initialize first. + Platform& platform = Platform::GetInstance(); + + try { + for (int index = 0; index < argc; index++) { + TString argument = argv[index]; + + if (argument == _T("-Xappcds:generatecache")) { + platform.SetAppCDSState(cdsGenCache); + } + else if (argument == _T("-Xappcds:off")) { + platform.SetAppCDSState(cdsDisabled); + } + else if (argument == _T("-Xapp:child")) { + parentProcess = false; + } + } + + // Package must be initialized after Platform is fully initialized. + Package& package = Package::GetInstance(); + Macros::Initialize(); + package.SetCommandLineArguments(argc, argv); + platform.SetCurrentDirectory(package.GetPackageAppDirectory()); + + switch (platform.GetAppCDSState()) { + case cdsDisabled: + case cdsUninitialized: + case cdsEnabled: { + break; + } + + case cdsGenCache: { + TString cacheDirectory = package.GetAppCDSCacheDirectory(); + + if (FilePath::DirectoryExists(cacheDirectory) == false) { + FilePath::CreateDirectory(cacheDirectory, true); + } else { + TString cacheFileName = + package.GetAppCDSCacheFileName(); + if (FilePath::FileExists(cacheFileName) == true) { + FilePath::DeleteFile(cacheFileName); + } + } + + break; + } + + case cdsAuto: { + TString cacheFileName = package.GetAppCDSCacheFileName(); + + if (parentProcess == true && + FilePath::FileExists(cacheFileName) == false) { + AutoFreePtr process = platform.CreateProcess(); + std::vector args; + args.push_back(_T("-Xappcds:generatecache")); + args.push_back(_T("-Xapp:child")); + process->Execute( + platform.GetModuleFileName(), args, true); + + if (FilePath::FileExists(cacheFileName) == false) { + // Cache does not exist after trying to generate it, + // so run without cache. + platform.SetAppCDSState(cdsDisabled); + package.Clear(); + package.Initialize(); + } + } + + break; + } + } + + // Validation + switch (platform.GetAppCDSState()) { + case cdsDisabled: + case cdsGenCache: { + // Do nothing. + break; + } + + case cdsEnabled: + case cdsAuto: { + TString cacheFileName = + package.GetAppCDSCacheFileName(); + + if (FilePath::FileExists(cacheFileName) == false) { + Messages& messages = Messages::GetInstance(); + TString message = PlatformString::Format( + messages.GetMessage( + APPCDS_CACHE_FILE_NOT_FOUND), + cacheFileName.data()); + throw Exception(message); + } + break; + } + + case cdsUninitialized: { + platform.ShowMessage(_T("Internal Error")); + break; + } + } + + // Run App + result = RunVM(); + } catch (Exception &e) { + platform.ShowMessage(e.GetMessage()); + } + + return result; + } + + JNIEXPORT void stop_launcher() { + } +} diff --git a/src/jdk.jpackage/unix/native/libapplauncher/FileAttribute.h b/src/jdk.jpackage/unix/native/libapplauncher/FileAttribute.h new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/unix/native/libapplauncher/FileAttribute.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef FILEATTRIBUTE_H +#define FILEATTRIBUTE_H + +enum FileAttribute { + faBlockSpecial, + faCharacterSpecial, + faFIFOSpecial, + faNormal, + faDirectory, + faSymbolicLink, + faSocket, + + // Owner + faReadOnly, + faWriteOnly, + faReadWrite, + faExecute, + + // Group + faGroupReadOnly, + faGroupWriteOnly, + faGroupReadWrite, + faGroupExecute, + + // Others + faOthersReadOnly, + faOthersWriteOnly, + faOthersReadWrite, + faOthersExecute, + + faHidden +}; + +#endif // FILEATTRIBUTE_H diff --git a/src/jdk.jpackage/unix/native/libapplauncher/FileAttributes.cpp b/src/jdk.jpackage/unix/native/libapplauncher/FileAttributes.cpp new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/unix/native/libapplauncher/FileAttributes.cpp @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "FileAttributes.h" + +#include +#include +#include + +FileAttributes::FileAttributes(const TString FileName, bool FollowLink) { + FFileName = FileName; + FFollowLink = FollowLink; + ReadAttributes(); +} + +bool FileAttributes::WriteAttributes() { + bool result = false; + + mode_t attributes = 0; + + for (std::vector::const_iterator iterator = + FAttributes.begin(); + iterator != FAttributes.end(); iterator++) { + switch (*iterator) { + case faBlockSpecial: + { + attributes |= S_IFBLK; + break; + } + case faCharacterSpecial: + { + attributes |= S_IFCHR; + break; + } + case faFIFOSpecial: + { + attributes |= S_IFIFO; + break; + } + case faNormal: + { + attributes |= S_IFREG; + break; + } + case faDirectory: + { + attributes |= S_IFDIR; + break; + } + case faSymbolicLink: + { + attributes |= S_IFLNK; + break; + } + case faSocket: + { + attributes |= S_IFSOCK; + break; + } + + // Owner + case faReadOnly: + { + attributes |= S_IRUSR; + break; + } + case faWriteOnly: + { + attributes |= S_IWUSR; + break; + } + case faReadWrite: + { + attributes |= S_IRUSR; + attributes |= S_IWUSR; + break; + } + case faExecute: + { + attributes |= S_IXUSR; + break; + } + + // Group + case faGroupReadOnly: + { + attributes |= S_IRGRP; + break; + } + case faGroupWriteOnly: + { + attributes |= S_IWGRP; + break; + } + case faGroupReadWrite: + { + attributes |= S_IRGRP; + attributes |= S_IWGRP; + break; + } + case faGroupExecute: + { + attributes |= S_IXGRP; + break; + } + + // Others + case faOthersReadOnly: + { + attributes |= S_IROTH; + break; + } + case faOthersWriteOnly: + { + attributes |= S_IWOTH; + break; + } + case faOthersReadWrite: + { + attributes |= S_IROTH; + attributes |= S_IWOTH; + break; + } + case faOthersExecute: + { + attributes |= S_IXOTH; + break; + } + default: + break; + } + } + + if (chmod(FFileName.data(), attributes) == 0) { + result = true; + } + + return result; +} + +#define S_ISRUSR(m) (((m) & S_IRWXU) == S_IRUSR) +#define S_ISWUSR(m) (((m) & S_IRWXU) == S_IWUSR) +#define S_ISXUSR(m) (((m) & S_IRWXU) == S_IXUSR) + +#define S_ISRGRP(m) (((m) & S_IRWXG) == S_IRGRP) +#define S_ISWGRP(m) (((m) & S_IRWXG) == S_IWGRP) +#define S_ISXGRP(m) (((m) & S_IRWXG) == S_IXGRP) + +#define S_ISROTH(m) (((m) & S_IRWXO) == S_IROTH) +#define S_ISWOTH(m) (((m) & S_IRWXO) == S_IWOTH) +#define S_ISXOTH(m) (((m) & S_IRWXO) == S_IXOTH) + +bool FileAttributes::ReadAttributes() { + bool result = false; + + struct stat status; + + if (stat(StringToFileSystemString(FFileName), &status) == 0) { + result = true; + + if (S_ISBLK(status.st_mode) != 0) { + FAttributes.push_back(faBlockSpecial); + } + if (S_ISCHR(status.st_mode) != 0) { + FAttributes.push_back(faCharacterSpecial); + } + if (S_ISFIFO(status.st_mode) != 0) { + FAttributes.push_back(faFIFOSpecial); + } + if (S_ISREG(status.st_mode) != 0) { + FAttributes.push_back(faNormal); + } + if (S_ISDIR(status.st_mode) != 0) { + FAttributes.push_back(faDirectory); + } + if (S_ISLNK(status.st_mode) != 0) { + FAttributes.push_back(faSymbolicLink); + } + if (S_ISSOCK(status.st_mode) != 0) { + FAttributes.push_back(faSocket); + } + + // Owner + if (S_ISRUSR(status.st_mode) != 0) { + if (S_ISWUSR(status.st_mode) != 0) { + FAttributes.push_back(faReadWrite); + } else { + FAttributes.push_back(faReadOnly); + } + } else if (S_ISWUSR(status.st_mode) != 0) { + FAttributes.push_back(faWriteOnly); + } + + if (S_ISXUSR(status.st_mode) != 0) { + FAttributes.push_back(faExecute); + } + + // Group + if (S_ISRGRP(status.st_mode) != 0) { + if (S_ISWGRP(status.st_mode) != 0) { + FAttributes.push_back(faGroupReadWrite); + } else { + FAttributes.push_back(faGroupReadOnly); + } + } else if (S_ISWGRP(status.st_mode) != 0) { + FAttributes.push_back(faGroupWriteOnly); + } + + if (S_ISXGRP(status.st_mode) != 0) { + FAttributes.push_back(faGroupExecute); + } + + + // Others + if (S_ISROTH(status.st_mode) != 0) { + if (S_ISWOTH(status.st_mode) != 0) { + FAttributes.push_back(faOthersReadWrite); + } else { + FAttributes.push_back(faOthersReadOnly); + } + } else if (S_ISWOTH(status.st_mode) != 0) { + FAttributes.push_back(faOthersWriteOnly); + } + + if (S_ISXOTH(status.st_mode) != 0) { + FAttributes.push_back(faOthersExecute); + } + + if (FFileName.size() > 0 && FFileName[0] == '.') { + FAttributes.push_back(faHidden); + } + } + + return result; +} + +bool FileAttributes::Valid(const FileAttribute Value) { + bool result = false; + + switch (Value) { + case faReadWrite: + case faWriteOnly: + case faExecute: + + case faGroupReadWrite: + case faGroupWriteOnly: + case faGroupReadOnly: + case faGroupExecute: + + case faOthersReadWrite: + case faOthersWriteOnly: + case faOthersReadOnly: + case faOthersExecute: + + case faReadOnly: + result = true; + break; + + default: + break; + } + + return result; +} + +void FileAttributes::Append(FileAttribute Value) { + if (Valid(Value) == true) { + if ((Value == faReadOnly && Contains(faWriteOnly) == true) || + (Value == faWriteOnly && Contains(faReadOnly) == true)) { + Value = faReadWrite; + } + + FAttributes.push_back(Value); + WriteAttributes(); + } +} + +bool FileAttributes::Contains(FileAttribute Value) { + bool result = false; + + std::vector::const_iterator iterator = + std::find(FAttributes.begin(), FAttributes.end(), Value); + + if (iterator != FAttributes.end()) { + result = true; + } + + return result; +} + +void FileAttributes::Remove(FileAttribute Value) { + if (Valid(Value) == true) { + if (Value == faReadOnly && Contains(faReadWrite) == true) { + Append(faWriteOnly); + Remove(faReadWrite); + } else if (Value == faWriteOnly && Contains(faReadWrite) == true) { + Append(faReadOnly); + Remove(faReadWrite); + } + + std::vector::iterator iterator = + std::find(FAttributes.begin(), FAttributes.end(), Value); + + if (iterator != FAttributes.end()) { + FAttributes.erase(iterator); + WriteAttributes(); + } + } +} diff --git a/src/jdk.jpackage/unix/native/libapplauncher/FilePath.cpp b/src/jdk.jpackage/unix/native/libapplauncher/FilePath.cpp new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/unix/native/libapplauncher/FilePath.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "PlatformDefs.h" +#include "FilePath.h" + +#include +#include +#include + +bool FilePath::FileExists(const TString FileName) { + bool result = false; + struct stat buf; + + if ((stat(StringToFileSystemString(FileName), &buf) == 0) && + (S_ISREG(buf.st_mode) != 0)) { + result = true; + } + + return result; +} + +bool FilePath::DirectoryExists(const TString DirectoryName) { + bool result = false; + + struct stat buf; + + if ((stat(StringToFileSystemString(DirectoryName), &buf) == 0) && + (S_ISDIR(buf.st_mode) != 0)) { + result = true; + } + + return result; +} + +bool FilePath::DeleteFile(const TString FileName) { + bool result = false; + + if (FileExists(FileName) == true) { + if (unlink(StringToFileSystemString(FileName)) == 0) { + result = true; + } + } + + return result; +} + +bool FilePath::DeleteDirectory(const TString DirectoryName) { + bool result = false; + + if (DirectoryExists(DirectoryName) == true) { + if (unlink(StringToFileSystemString(DirectoryName)) == 0) { + result = true; + } + } + + return result; +} + +TString FilePath::IncludeTrailingSeparator(const TString value) { + TString result = value; + + if (value.size() > 0) { + TString::iterator i = result.end(); + i--; + + if (*i != TRAILING_PATHSEPARATOR) { + result += TRAILING_PATHSEPARATOR; + } + } + + return result; +} + +TString FilePath::IncludeTrailingSeparator(const char* value) { + TString lvalue = PlatformString(value).toString(); + return IncludeTrailingSeparator(lvalue); +} + +TString FilePath::IncludeTrailingSeparator(const wchar_t* value) { + TString lvalue = PlatformString(value).toString(); + return IncludeTrailingSeparator(lvalue); +} + +TString FilePath::ExtractFilePath(TString Path) { + return dirname(StringToFileSystemString(Path)); +} + +TString FilePath::ExtractFileExt(TString Path) { + TString result; + size_t dot = Path.find_last_of('.'); + + if (dot != TString::npos) { + result = Path.substr(dot, Path.size() - dot); + } + + return result; +} + +TString FilePath::ExtractFileName(TString Path) { + return basename(StringToFileSystemString(Path)); +} + +TString FilePath::ChangeFileExt(TString Path, TString Extension) { + TString result; + size_t dot = Path.find_last_of('.'); + + if (dot != TString::npos) { + result = Path.substr(0, dot) + Extension; + } + + if (result.empty() == true) { + result = Path; + } + + return result; +} + +TString FilePath::FixPathForPlatform(TString Path) { + TString result = Path; + std::replace(result.begin(), result.end(), + BAD_TRAILING_PATHSEPARATOR, TRAILING_PATHSEPARATOR); + return result; +} + +TString FilePath::FixPathSeparatorForPlatform(TString Path) { + TString result = Path; + std::replace(result.begin(), result.end(), + BAD_PATH_SEPARATOR, PATH_SEPARATOR); + return result; +} + +TString FilePath::PathSeparator() { + TString result; + result = PATH_SEPARATOR; + return result; +} + +bool FilePath::CreateDirectory(TString Path, bool ownerOnly) { + bool result = false; + + std::list paths; + TString lpath = Path; + + while (lpath.empty() == false && DirectoryExists(lpath) == false) { + paths.push_front(lpath); + lpath = ExtractFilePath(lpath); + } + + for (std::list::iterator iterator = paths.begin(); + iterator != paths.end(); iterator++) { + lpath = *iterator; + + mode_t mode = S_IRWXU; + if (!ownerOnly) { + mode |= S_IRWXG | S_IROTH | S_IXOTH; + } + if (mkdir(StringToFileSystemString(lpath), mode) == 0) { + result = true; + } else { + result = false; + break; + } + } + + return result; +} + +void FilePath::ChangePermissions(TString FileName, bool ownerOnly) { + mode_t mode = S_IRWXU; + if (!ownerOnly) { + mode |= S_IRWXG | S_IROTH | S_IXOTH; + } + chmod(FileName.data(), mode); +} diff --git a/src/jdk.jpackage/unix/native/libapplauncher/PosixPlatform.cpp b/src/jdk.jpackage/unix/native/libapplauncher/PosixPlatform.cpp new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/unix/native/libapplauncher/PosixPlatform.cpp @@ -0,0 +1,320 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "PosixPlatform.h" + +#include "PlatformString.h" +#include "FilePath.h" +#include "Helpers.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +PosixPlatform::PosixPlatform(void) { +} + +PosixPlatform::~PosixPlatform(void) { +} + +TString PosixPlatform::GetTempDirectory() { + struct passwd* pw = getpwuid(getuid()); + TString homedir(pw->pw_dir); + homedir += getTmpDirString(); + if (!FilePath::DirectoryExists(homedir)) { + if (!FilePath::CreateDirectory(homedir, false)) { + homedir.clear(); + } + } + + return homedir; +} + +TString PosixPlatform::fixName(const TString& name) { + TString fixedName(name); + const TString chars("?:*<>/\\"); + for (TString::const_iterator it = chars.begin(); it != chars.end(); it++) { + fixedName.erase(std::remove(fixedName.begin(), + fixedName.end(), *it), fixedName.end()); + } + return fixedName; +} + +MessageResponse PosixPlatform::ShowResponseMessage(TString title, + TString description) { + MessageResponse result = mrCancel; + + printf("%s %s (Y/N)\n", PlatformString(title).toPlatformString(), + PlatformString(description).toPlatformString()); + fflush(stdout); + + std::string input; + std::cin >> input; + + if (input == "Y") { + result = mrOK; + } + + return result; +} + +void PosixPlatform::SetCurrentDirectory(TString Value) { + chdir(StringToFileSystemString(Value)); +} + +Module PosixPlatform::LoadLibrary(TString FileName) { + return dlopen(StringToFileSystemString(FileName), RTLD_LAZY); +} + +void PosixPlatform::FreeLibrary(Module AModule) { + dlclose(AModule); +} + +Procedure PosixPlatform::GetProcAddress(Module AModule, + std::string MethodName) { + return dlsym(AModule, PlatformString(MethodName)); +} + +Process* PosixPlatform::CreateProcess() { + return new PosixProcess(); +} + +void PosixPlatform::addPlatformDependencies(JavaLibrary *pJavaLibrary) { +} + +void Platform::CopyString(char *Destination, + size_t NumberOfElements, const char *Source) { + strncpy(Destination, Source, NumberOfElements); + + if (NumberOfElements > 0) { + Destination[NumberOfElements - 1] = '\0'; + } +} + +void Platform::CopyString(wchar_t *Destination, + size_t NumberOfElements, const wchar_t *Source) { + wcsncpy(Destination, Source, NumberOfElements); + + if (NumberOfElements > 0) { + Destination[NumberOfElements - 1] = '\0'; + } +} + +// Owner must free the return value. + +MultibyteString Platform::WideStringToMultibyteString( + const wchar_t* value) { + MultibyteString result; + size_t count = 0; + + if (value == NULL) { + return result; + } + + count = wcstombs(NULL, value, 0); + if (count > 0) { + result.data = new char[count + 1]; + result.data[count] = '\0'; + result.length = count; + wcstombs(result.data, value, count); + } + + return result; +} + +// Owner must free the return value. + +WideString Platform::MultibyteStringToWideString(const char* value) { + WideString result; + size_t count = 0; + + if (value == NULL) { + return result; + } + + count = mbstowcs(NULL, value, 0); + if (count > 0) { + result.data = new wchar_t[count + 1]; + result.data[count] = '\0'; + result.length = count; + mbstowcs(result.data, value, count); + } + + return result; +} + +void PosixPlatform::InitStreamLocale(wios *stream) { + // Nothing to do for POSIX platforms. +} + +PosixProcess::PosixProcess() : Process() { + FChildPID = 0; + FRunning = false; + FOutputHandle = 0; + FInputHandle = 0; +} + +PosixProcess::~PosixProcess() { + Terminate(); +} + +bool PosixProcess::ReadOutput() { + bool result = false; + + if (FOutputHandle != 0 && IsRunning() == true) { + char buffer[4096] = {0}; + + ssize_t count = read(FOutputHandle, buffer, sizeof (buffer)); + + if (count == -1) { + if (errno == EINTR) { + // continue; + } else { + perror("read"); + exit(1); + } + } else if (count == 0) { + // break; + } else { + if (buffer[count - 1] == EOF) { + buffer[count - 1] = '\0'; + } + + std::list output = Helpers::StringToArray(buffer); + FOutput.splice(FOutput.end(), output, output.begin(), output.end()); + result = true; + } + } + + return false; +} + +bool PosixProcess::IsRunning() { + bool result = false; + + if (kill(FChildPID, 0) == 0) { + result = true; + } + + return result; +} + +bool PosixProcess::Terminate() { + bool result = false; + + if (IsRunning() == true && FRunning == true) { + FRunning = false; + Cleanup(); + int status = kill(FChildPID, SIGTERM); + + if (status == 0) { + result = true; + } else { +#ifdef DEBUG + if (errno == EINVAL) { + printf("Kill error: The value of the sig argument is an invalid or unsupported signal number."); + } else if (errno == EPERM) { + printf("Kill error: The process does not have permission to send the signal to any receiving process."); + } else if (errno == ESRCH) { + printf("Kill error: No process or process group can be found corresponding to that specified by pid."); + } +#endif // DEBUG + if (IsRunning() == true) { + status = kill(FChildPID, SIGKILL); + + if (status == 0) { + result = true; + } + } + } + } + + return result; +} + +bool PosixProcess::Wait() { + bool result = false; + + int status = 0; + pid_t wpid = 0; + + wpid = wait(&status); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + if (errno != EINTR) { + status = -1; + } + } + +#ifdef DEBUG + if (WIFEXITED(status)) { + printf("child exited, status=%d\n", WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + printf("child killed (signal %d)\n", WTERMSIG(status)); + } else if (WIFSTOPPED(status)) { + printf("child stopped (signal %d)\n", WSTOPSIG(status)); +#ifdef WIFCONTINUED // Not all implementations support this + } else if (WIFCONTINUED(status)) { + printf("child continued\n"); +#endif // WIFCONTINUED + } else { // Non-standard case -- may never happen + printf("Unexpected status (0x%x)\n", status); + } +#endif // DEBUG + + if (wpid != -1) { + result = true; + } + + return result; +} + +TProcessID PosixProcess::GetProcessID() { + return FChildPID; +} + +void PosixProcess::SetInput(TString Value) { + if (FInputHandle != 0) { + write(FInputHandle, Value.data(), Value.size()); + } +} + +std::list PosixProcess::GetOutput() { + ReadOutput(); + return Process::GetOutput(); +} diff --git a/src/jdk.jpackage/unix/native/libapplauncher/PosixPlatform.h b/src/jdk.jpackage/unix/native/libapplauncher/PosixPlatform.h new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/unix/native/libapplauncher/PosixPlatform.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef POSIXPLATFORM_H +#define POSIXPLATFORM_H + +#include "Platform.h" +#include + +class PosixPlatform : virtual public Platform { +protected: + + TString fixName(const TString& name); + + virtual TString getTmpDirString() = 0; + +public: + PosixPlatform(void); + virtual ~PosixPlatform(void); + +public: + virtual MessageResponse ShowResponseMessage(TString title, + TString description); + + virtual void SetCurrentDirectory(TString Value); + + virtual Module LoadLibrary(TString FileName); + virtual void FreeLibrary(Module AModule); + virtual Procedure GetProcAddress(Module AModule, std::string MethodName); + + virtual Process* CreateProcess(); + virtual TString GetTempDirectory(); + void InitStreamLocale(wios *stream); + void addPlatformDependencies(JavaLibrary *pJavaLibrary); +}; + +class PosixProcess : public Process { +private: + pid_t FChildPID; + sigset_t saveblock; + int FOutputHandle; + int FInputHandle; + struct sigaction savintr, savequit; + bool FRunning; + + void Cleanup(); + bool ReadOutput(); + +public: + PosixProcess(); + virtual ~PosixProcess(); + + virtual bool IsRunning(); + virtual bool Terminate(); + virtual bool Execute(const TString Application, + const std::vector Arguments, bool AWait = false); + virtual bool Wait(); + virtual TProcessID GetProcessID(); + virtual void SetInput(TString Value); + virtual std::list GetOutput(); +}; + +#endif // POSIXPLATFORM_H diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinAppBundler.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinAppBundler.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinAppBundler.java @@ -0,0 +1,253 @@ +/* + * 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.jpackage.internal; + +import java.io.File; +import java.nio.file.Path; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.ResourceBundle; + +import static jdk.jpackage.internal.WindowsBundlerParam.*; +import static jdk.jpackage.internal.WinMsiBundler.WIN_APP_IMAGE; + +public class WinAppBundler extends AbstractImageBundler { + + private static final ResourceBundle I18N = ResourceBundle.getBundle( + "jdk.jpackage.internal.resources.WinResources"); + + static final BundlerParamInfo ICON_ICO = + new StandardBundlerParam<>( + "icon.ico", + File.class, + params -> { + File f = ICON.fetchFrom(params); + if (f != null && !f.getName().toLowerCase().endsWith(".ico")) { + Log.error(MessageFormat.format( + I18N.getString("message.icon-not-ico"), f)); + return null; + } + return f; + }, + (s, p) -> new File(s)); + + @Override + public boolean validate(Map params) + throws UnsupportedPlatformException, ConfigException { + try { + if (params == null) throw new ConfigException( + I18N.getString("error.parameters-null"), + I18N.getString("error.parameters-null.advice")); + + return doValidate(params); + } catch (RuntimeException re) { + if (re.getCause() instanceof ConfigException) { + throw (ConfigException) re.getCause(); + } else { + throw new ConfigException(re); + } + } + } + + // to be used by chained bundlers, e.g. by EXE bundler to avoid + // skipping validation if p.type does not include "image" + private boolean doValidate(Map p) + throws UnsupportedPlatformException, ConfigException { + if (Platform.getPlatform() != Platform.WINDOWS) { + throw new UnsupportedPlatformException(); + } + + imageBundleValidation(p); + + if (StandardBundlerParam.getPredefinedAppImage(p) != null) { + return true; + } + + // Make sure that jpackage.exe exists. + File tool = new File( + System.getProperty("java.home") + "\\bin\\jpackage.exe"); + + if (!tool.exists()) { + throw new ConfigException( + I18N.getString("error.no-windows-resources"), + I18N.getString("error.no-windows-resources.advice")); + } + + return true; + } + + private static boolean usePredefineAppName(Map p) { + return (PREDEFINED_APP_IMAGE.fetchFrom(p) != null); + } + + private static String appName; + synchronized static String getAppName( + Map p) { + // If we building from predefined app image, then we should use names + // from image and not from CLI. + if (usePredefineAppName(p)) { + if (appName == null) { + // Use WIN_APP_IMAGE here, since we already copy pre-defined + // image to WIN_APP_IMAGE + File appImageDir = WIN_APP_IMAGE.fetchFrom(p); + + File appDir = new File(appImageDir.toString() + "\\app"); + File [] files = appDir.listFiles( + (File dir, String name) -> name.endsWith(".cfg")); + if (files == null || files.length == 0) { + String name = APP_NAME.fetchFrom(p); + Path exePath = appImageDir.toPath().resolve(name + ".exe"); + Path icoPath = appImageDir.toPath().resolve(name + ".ico"); + if (exePath.toFile().exists() && + icoPath.toFile().exists()) { + return name; + } else { + throw new RuntimeException(MessageFormat.format( + I18N.getString("error.cannot-find-launcher"), + appImageDir)); + } + } else { + appName = files[0].getName(); + int index = appName.indexOf("."); + if (index != -1) { + appName = appName.substring(0, index); + } + if (files.length > 1) { + Log.error(MessageFormat.format(I18N.getString( + "message.multiple-launchers"), appName)); + } + } + return appName; + } else { + return appName; + } + } + + return APP_NAME.fetchFrom(p); + } + + public static String getLauncherName(Map p) { + return getAppName(p) + ".exe"; + } + + public static String getLauncherCfgName(Map p) { + return "app\\" + getAppName(p) +".cfg"; + } + + public boolean bundle(Map p, File outputDirectory) + throws PackagerException { + return doBundle(p, outputDirectory, false) != null; + } + + File doBundle(Map p, File outputDirectory, + boolean dependentTask) throws PackagerException { + if (StandardBundlerParam.isRuntimeInstaller(p)) { + return PREDEFINED_RUNTIME_IMAGE.fetchFrom(p); + } else { + return doAppBundle(p, outputDirectory, dependentTask); + } + } + + File doAppBundle(Map p, File outputDirectory, + boolean dependentTask) throws PackagerException { + try { + File rootDirectory = createRoot(p, outputDirectory, dependentTask, + APP_NAME.fetchFrom(p)); + AbstractAppImageBuilder appBuilder = + new WindowsAppImageBuilder(p, outputDirectory.toPath()); + if (PREDEFINED_RUNTIME_IMAGE.fetchFrom(p) == null ) { + JLinkBundlerHelper.execute(p, appBuilder); + } else { + StandardBundlerParam.copyPredefinedRuntimeImage(p, appBuilder); + } + if (!dependentTask) { + Log.verbose(MessageFormat.format( + I18N.getString("message.result-dir"), + outputDirectory.getAbsolutePath())); + } + return rootDirectory; + } catch (PackagerException pe) { + throw pe; + } catch (Exception e) { + Log.verbose(e); + throw new PackagerException(e); + } + } + + @Override + public String getName() { + return I18N.getString("app.bundler.name"); + } + + @Override + public String getDescription() { + return I18N.getString("app.bundler.description"); + } + + @Override + public String getID() { + return "windows.app"; + } + + @Override + public String getBundleType() { + return "IMAGE"; + } + + @Override + public Collection> getBundleParameters() { + return getAppBundleParameters(); + } + + public static Collection> getAppBundleParameters() { + return Arrays.asList( + APP_NAME, + APP_RESOURCES, + ARGUMENTS, + CLASSPATH, + ICON_ICO, + JAVA_OPTIONS, + MAIN_CLASS, + MAIN_JAR, + VERSION, + VERBOSE + ); + } + + @Override + public File execute(Map params, + File outputParentDir) throws PackagerException { + return doBundle(params, outputParentDir, false); + } + + @Override + public boolean supported(boolean platformInstaller) { + return (Platform.getPlatform() == Platform.WINDOWS); + } + +} diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinExeBundler.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinExeBundler.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinExeBundler.java @@ -0,0 +1,869 @@ +/* + * 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.jpackage.internal; + +import java.io.*; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.text.MessageFormat; +import java.util.*; + +import static jdk.jpackage.internal.WindowsBundlerParam.*; + +public class WinExeBundler extends AbstractBundler { + + private static final ResourceBundle I18N = ResourceBundle.getBundle( + "jdk.jpackage.internal.resources.WinResources"); + + public static final BundlerParamInfo APP_BUNDLER = + new WindowsBundlerParam<>( + "win.app.bundler", + WinAppBundler.class, + params -> new WinAppBundler(), + null); + + public static final BundlerParamInfo EXE_IMAGE_DIR = + new WindowsBundlerParam<>( + "win.exe.imageDir", + File.class, + params -> { + File imagesRoot = IMAGES_ROOT.fetchFrom(params); + if (!imagesRoot.exists()) imagesRoot.mkdirs(); + return new File(imagesRoot, "win-exe.image"); + }, + (s, p) -> null); + + public static final BundlerParamInfo WIN_APP_IMAGE = + new WindowsBundlerParam<>( + "win.app.image", + File.class, + null, + (s, p) -> null); + + public static final BundlerParamInfo UPGRADE_UUID = + new WindowsBundlerParam<>( + Arguments.CLIOptions.WIN_UPGRADE_UUID.getId(), + UUID.class, + params -> UUID.randomUUID(), + (s, p) -> UUID.fromString(s)); + + public static final StandardBundlerParam EXE_SYSTEM_WIDE = + new StandardBundlerParam<>( + Arguments.CLIOptions.WIN_PER_USER_INSTALLATION.getId(), + Boolean.class, + params -> true, // default to system wide + (s, p) -> (s == null || "null".equalsIgnoreCase(s))? null + : Boolean.valueOf(s) + ); + public static final StandardBundlerParam PRODUCT_VERSION = + new StandardBundlerParam<>( + "win.msi.productVersion", + String.class, + VERSION::fetchFrom, + (s, p) -> s + ); + + public static final StandardBundlerParam MENU_HINT = + new WindowsBundlerParam<>( + Arguments.CLIOptions.WIN_MENU_HINT.getId(), + Boolean.class, + params -> false, + (s, p) -> (s == null || + "null".equalsIgnoreCase(s))? true : Boolean.valueOf(s) + ); + + public static final StandardBundlerParam SHORTCUT_HINT = + new WindowsBundlerParam<>( + Arguments.CLIOptions.WIN_SHORTCUT_HINT.getId(), + Boolean.class, + params -> false, + (s, p) -> (s == null || + "null".equalsIgnoreCase(s))? false : Boolean.valueOf(s) + ); + + private final static String DEFAULT_EXE_PROJECT_TEMPLATE = "template.iss"; + private final static String DEFAULT_JRE_EXE_TEMPLATE = "template.jre.iss"; + private static final String TOOL_INNO_SETUP_COMPILER = "iscc.exe"; + + public static final BundlerParamInfo + TOOL_INNO_SETUP_COMPILER_EXECUTABLE = new WindowsBundlerParam<>( + "win.exe.iscc.exe", + String.class, + params -> { + for (String dirString : (System.getenv("PATH") + + ";C:\\Program Files (x86)\\Inno Setup 5;" + + "C:\\Program Files\\Inno Setup 5").split(";")) { + File f = new File(dirString.replace("\"", ""), + TOOL_INNO_SETUP_COMPILER); + if (f.isFile()) { + return f.toString(); + } + } + return null; + }, + null); + + @Override + public String getName() { + return getString("exe.bundler.name"); + } + + @Override + public String getDescription() { + return getString("exe.bundler.description"); + } + + @Override + public String getID() { + return "exe"; + } + + @Override + public String getBundleType() { + return "INSTALLER"; + } + + @Override + public Collection> getBundleParameters() { + Collection> results = new LinkedHashSet<>(); + results.addAll(WinAppBundler.getAppBundleParameters()); + results.addAll(getExeBundleParameters()); + return results; + } + + public static Collection> getExeBundleParameters() { + return Arrays.asList( + DESCRIPTION, + COPYRIGHT, + LICENSE_FILE, + MENU_GROUP, + MENU_HINT, + SHORTCUT_HINT, + EXE_SYSTEM_WIDE, + VENDOR, + INSTALLDIR_CHOOSER + ); + } + + @Override + public File execute(Map p, + File outputParentDir) throws PackagerException { + return bundle(p, outputParentDir); + } + + @Override + public boolean supported(boolean platformInstaller) { + return (Platform.getPlatform() == Platform.WINDOWS); + } + + private static String findToolVersion(String toolName) { + try { + if (toolName == null || "".equals(toolName)) return null; + + ProcessBuilder pb = new ProcessBuilder( + toolName, + "/?"); + VersionExtractor ve = + new VersionExtractor("Inno Setup (\\d+.?\\d*)"); + IOUtils.exec(pb, Log.isDebug(), true, ve); + // not interested in the output + String version = ve.getVersion(); + Log.verbose(MessageFormat.format( + getString("message.tool-version"), toolName, version)); + return version; + } catch (Exception e) { + if (Log.isDebug()) { + Log.verbose(e); + } + return null; + } + } + + @Override + public boolean validate(Map p) + throws UnsupportedPlatformException, ConfigException { + try { + if (p == null) throw new ConfigException( + getString("error.parameters-null"), + getString("error.parameters-null.advice")); + + // run basic validation to ensure requirements are met + // we are not interested in return code, only possible exception + APP_BUNDLER.fetchFrom(p).validate(p); + + // make sure some key values don't have newlines + for (BundlerParamInfo pi : Arrays.asList( + APP_NAME, + COPYRIGHT, + DESCRIPTION, + MENU_GROUP, + VENDOR, + VERSION) + ) { + String v = pi.fetchFrom(p); + if (v.contains("\n") | v.contains("\r")) { + throw new ConfigException("Parmeter '" + pi.getID() + + "' cannot contain a newline.", + " Change the value of '" + pi.getID() + + " so that it does not contain any newlines"); + } + } + + // exe bundlers trim the copyright to 100 characters, + // tell them this will happen + if (COPYRIGHT.fetchFrom(p).length() > 100) { + throw new ConfigException( + getString("error.copyright-is-too-long"), + getString("error.copyright-is-too-long.advice")); + } + + String innoVersion = findToolVersion( + TOOL_INNO_SETUP_COMPILER_EXECUTABLE.fetchFrom(p)); + + //Inno Setup 5+ is required + String minVersion = "5.0"; + + if (VersionExtractor.isLessThan(innoVersion, minVersion)) { + Log.error(MessageFormat.format( + getString("message.tool-wrong-version"), + TOOL_INNO_SETUP_COMPILER, innoVersion, minVersion)); + throw new ConfigException( + getString("error.iscc-not-found"), + getString("error.iscc-not-found.advice")); + } + + /********* validate bundle parameters *************/ + + // only one mime type per association, at least one file extension + List> associations = + FILE_ASSOCIATIONS.fetchFrom(p); + if (associations != null) { + for (int i = 0; i < associations.size(); i++) { + Map assoc = associations.get(i); + List mimes = FA_CONTENT_TYPE.fetchFrom(assoc); + if (mimes.size() > 1) { + throw new ConfigException(MessageFormat.format( + getString("error.too-many-content-" + + "types-for-file-association"), i), + getString("error.too-many-content-" + + "types-for-file-association.advice")); + } + } + } + + return true; + } catch (RuntimeException re) { + if (re.getCause() instanceof ConfigException) { + throw (ConfigException) re.getCause(); + } else { + throw new ConfigException(re); + } + } + } + + private boolean prepareProto(Map p) + throws PackagerException, IOException { + File appImage = StandardBundlerParam.getPredefinedAppImage(p); + File appDir = null; + + // we either have an application image or need to build one + if (appImage != null) { + appDir = new File( + EXE_IMAGE_DIR.fetchFrom(p), APP_NAME.fetchFrom(p)); + // copy everything from appImage dir into appDir/name + IOUtils.copyRecursive(appImage.toPath(), appDir.toPath()); + } else { + appDir = APP_BUNDLER.fetchFrom(p).doBundle(p, + EXE_IMAGE_DIR.fetchFrom(p), true); + } + + if (appDir == null) { + return false; + } + + p.put(WIN_APP_IMAGE.getID(), appDir); + + String licenseFile = LICENSE_FILE.fetchFrom(p); + if (licenseFile != null) { + // need to copy license file to the working directory and convert to rtf if needed + File lfile = new File(licenseFile); + File destFile = new File(CONFIG_ROOT.fetchFrom(p), lfile.getName()); + IOUtils.copyFile(lfile, destFile); + ensureByMutationFileIsRTF(destFile); + } + + // copy file association icons + List> fileAssociations = + FILE_ASSOCIATIONS.fetchFrom(p); + + for (Map fa : fileAssociations) { + File icon = FA_ICON.fetchFrom(fa); // TODO FA_ICON_ICO + if (icon == null) { + continue; + } + + File faIconFile = new File(appDir, icon.getName()); + + if (icon.exists()) { + try { + IOUtils.copyFile(icon, faIconFile); + } catch (IOException e) { + Log.verbose(e); + } + } + } + + return true; + } + + public File bundle(Map p, File outdir) + throws PackagerException { + if (!outdir.isDirectory() && !outdir.mkdirs()) { + throw new PackagerException("error.cannot-create-output-dir", + outdir.getAbsolutePath()); + } + if (!outdir.canWrite()) { + throw new PackagerException("error.cannot-write-to-output-dir", + outdir.getAbsolutePath()); + } + + String tempDirectory = WindowsDefender.getUserTempDirectory(); + if (Arguments.CLIOptions.context().userProvidedBuildRoot) { + tempDirectory = TEMP_ROOT.fetchFrom(p).getAbsolutePath(); + } + if (WindowsDefender.isThereAPotentialWindowsDefenderIssue( + tempDirectory)) { + Log.error(MessageFormat.format( + getString("message.potential.windows.defender.issue"), + tempDirectory)); + } + + // validate we have valid tools before continuing + String iscc = TOOL_INNO_SETUP_COMPILER_EXECUTABLE.fetchFrom(p); + if (iscc == null || !new File(iscc).isFile()) { + Log.verbose(getString("error.iscc-not-found")); + Log.verbose(MessageFormat.format( + getString("message.iscc-file-string"), iscc)); + throw new PackagerException("error.iscc-not-found"); + } + + File imageDir = EXE_IMAGE_DIR.fetchFrom(p); + try { + imageDir.mkdirs(); + + boolean menuShortcut = MENU_HINT.fetchFrom(p); + boolean desktopShortcut = SHORTCUT_HINT.fetchFrom(p); + if (!menuShortcut && !desktopShortcut) { + // both can not be false - user will not find the app + Log.verbose(getString("message.one-shortcut-required")); + p.put(MENU_HINT.getID(), true); + } + + if (prepareProto(p) && prepareProjectConfig(p)) { + File configScript = getConfig_Script(p); + if (configScript.exists()) { + Log.verbose(MessageFormat.format( + getString("message.running-wsh-script"), + configScript.getAbsolutePath())); + IOUtils.run("wscript", configScript, VERBOSE.fetchFrom(p)); + } + return buildEXE(p, outdir); + } + return null; + } catch (IOException ex) { + Log.verbose(ex); + throw new PackagerException(ex); + } + } + + // name of post-image script + private File getConfig_Script(Map p) { + return new File(EXE_IMAGE_DIR.fetchFrom(p), + APP_NAME.fetchFrom(p) + "-post-image.wsf"); + } + + private String getAppIdentifier(Map p) { + String nm = UPGRADE_UUID.fetchFrom(p).toString(); + + // limitation of innosetup + if (nm.length() > 126) { + Log.error(getString("message-truncating-id")); + nm = nm.substring(0, 126); + } + + return nm; + } + + private String getLicenseFile(Map p) { + String licenseFile = LICENSE_FILE.fetchFrom(p); + if (licenseFile != null) { + File lfile = new File(licenseFile); + File destFile = new File(CONFIG_ROOT.fetchFrom(p), lfile.getName()); + String filePath = destFile.getAbsolutePath(); + if (filePath.contains(" ")) { + return "\"" + filePath + "\""; + } else { + return filePath; + } + } + + return null; + } + + void validateValueAndPut(Map data, String key, + BundlerParamInfo param, + Map p) throws IOException { + String value = param.fetchFrom(p); + if (value.contains("\r") || value.contains("\n")) { + throw new IOException("Configuration Parameter " + + param.getID() + " cannot contain multiple lines of text"); + } + data.put(key, innosetupEscape(value)); + } + + private String innosetupEscape(String value) { + if (value == null) { + return ""; + } + + if (value.contains("\"") || !value.trim().equals(value)) { + value = "\"" + value.replace("\"", "\"\"") + "\""; + } + return value; + } + + boolean prepareMainProjectFile(Map p) + throws IOException { + Map data = new HashMap<>(); + data.put("PRODUCT_APP_IDENTIFIER", + innosetupEscape(getAppIdentifier(p))); + + validateValueAndPut(data, "INSTALL_DIR", WINDOWS_INSTALL_DIR, p); + validateValueAndPut(data, "INSTALLER_NAME", APP_NAME, p); + validateValueAndPut(data, "APPLICATION_VENDOR", VENDOR, p); + validateValueAndPut(data, "APPLICATION_VERSION", VERSION, p); + validateValueAndPut(data, "INSTALLER_FILE_NAME", + INSTALLER_FILE_NAME, p); + + data.put("LAUNCHER_NAME", + innosetupEscape(WinAppBundler.getAppName(p))); + + data.put("APPLICATION_LAUNCHER_FILENAME", + innosetupEscape(WinAppBundler.getLauncherName(p))); + + data.put("APPLICATION_DESKTOP_SHORTCUT", + SHORTCUT_HINT.fetchFrom(p) ? "returnTrue" : "returnFalse"); + data.put("APPLICATION_MENU_SHORTCUT", + MENU_HINT.fetchFrom(p) ? "returnTrue" : "returnFalse"); + validateValueAndPut(data, "APPLICATION_GROUP", MENU_GROUP, p); + validateValueAndPut(data, "APPLICATION_COPYRIGHT", COPYRIGHT, p); + + data.put("APPLICATION_LICENSE_FILE", + innosetupEscape(getLicenseFile(p))); + data.put("DISABLE_DIR_PAGE", + INSTALLDIR_CHOOSER.fetchFrom(p) ? "No" : "Yes"); + + Boolean isSystemWide = EXE_SYSTEM_WIDE.fetchFrom(p); + + if (isSystemWide) { + data.put("APPLICATION_INSTALL_ROOT", "{pf}"); + data.put("APPLICATION_INSTALL_PRIVILEGE", "admin"); + } else { + data.put("APPLICATION_INSTALL_ROOT", "{localappdata}"); + data.put("APPLICATION_INSTALL_PRIVILEGE", "lowest"); + } + + data.put("ARCHITECTURE_BIT_MODE", "x64"); + + validateValueAndPut(data, "RUN_FILENAME", APP_NAME, p); + + validateValueAndPut(data, "APPLICATION_DESCRIPTION", + DESCRIPTION, p); + + data.put("APPLICATION_SERVICE", "returnFalse"); + data.put("APPLICATION_NOT_SERVICE", "returnFalse"); + data.put("APPLICATION_APP_CDS_INSTALL", "returnFalse"); + data.put("START_ON_INSTALL", ""); + data.put("STOP_ON_UNINSTALL", ""); + data.put("RUN_AT_STARTUP", ""); + + String imagePathString = + WIN_APP_IMAGE.fetchFrom(p).toPath().toAbsolutePath().toString(); + data.put("APPLICATION_IMAGE", innosetupEscape(imagePathString)); + Log.verbose("setting APPLICATION_IMAGE to " + + innosetupEscape(imagePathString) + " for InnoSetup"); + + StringBuilder addLaunchersCfg = new StringBuilder(); + for (Map + launcher : ADD_LAUNCHERS.fetchFrom(p)) { + String application_name = APP_NAME.fetchFrom(launcher); + if (MENU_HINT.fetchFrom(launcher)) { + // Name: "{group}\APPLICATION_NAME"; + // Filename: "{app}\APPLICATION_NAME.exe"; + // IconFilename: "{app}\APPLICATION_NAME.ico" + addLaunchersCfg.append("Name: \"{group}\\"); + addLaunchersCfg.append(application_name); + addLaunchersCfg.append("\"; Filename: \"{app}\\"); + addLaunchersCfg.append(application_name); + addLaunchersCfg.append(".exe\"; IconFilename: \"{app}\\"); + addLaunchersCfg.append(application_name); + addLaunchersCfg.append(".ico\"\r\n"); + } + if (SHORTCUT_HINT.fetchFrom(launcher)) { + // Name: "{commondesktop}\APPLICATION_NAME"; + // Filename: "{app}\APPLICATION_NAME.exe"; + // IconFilename: "{app}\APPLICATION_NAME.ico" + addLaunchersCfg.append("Name: \"{commondesktop}\\"); + addLaunchersCfg.append(application_name); + addLaunchersCfg.append("\"; Filename: \"{app}\\"); + addLaunchersCfg.append(application_name); + addLaunchersCfg.append(".exe\"; IconFilename: \"{app}\\"); + addLaunchersCfg.append(application_name); + addLaunchersCfg.append(".ico\"\r\n"); + } + } + data.put("ADD_LAUNCHERS", addLaunchersCfg.toString()); + + StringBuilder registryEntries = new StringBuilder(); + String regName = APP_REGISTRY_NAME.fetchFrom(p); + List> fetchFrom = + FILE_ASSOCIATIONS.fetchFrom(p); + for (int i = 0; i < fetchFrom.size(); i++) { + Map fileAssociation = fetchFrom.get(i); + String description = FA_DESCRIPTION.fetchFrom(fileAssociation); + File icon = FA_ICON.fetchFrom(fileAssociation); //TODO FA_ICON_ICO + + List extensions = FA_EXTENSIONS.fetchFrom(fileAssociation); + String entryName = regName + "File"; + if (i > 0) { + entryName += "." + i; + } + + if (extensions == null) { + Log.verbose(getString( + "message.creating-association-with-null-extension")); + } else { + for (String ext : extensions) { + if (isSystemWide) { + // "Root: HKCR; Subkey: \".myp\"; + // ValueType: string; ValueName: \"\"; + // ValueData: \"MyProgramFile\"; + // Flags: uninsdeletevalue" + registryEntries.append("Root: HKCR; Subkey: \".") + .append(ext) + .append("\"; ValueType: string;" + + " ValueName: \"\"; ValueData: \"") + .append(entryName) + .append("\"; Flags: uninsdeletevalue uninsdeletekeyifempty\r\n"); + } else { + registryEntries.append( + "Root: HKCU; Subkey: \"Software\\Classes\\.") + .append(ext) + .append("\"; ValueType: string;" + + " ValueName: \"\"; ValueData: \"") + .append(entryName) + .append("\"; Flags: uninsdeletevalue uninsdeletekeyifempty\r\n"); + } + } + } + + if (extensions != null && !extensions.isEmpty()) { + String ext = extensions.get(0); + List mimeTypes = + FA_CONTENT_TYPE.fetchFrom(fileAssociation); + for (String mime : mimeTypes) { + if (isSystemWide) { + // "Root: HKCR; + // Subkey: HKCR\\Mime\\Database\\ + // Content Type\\application/chaos; + // ValueType: string; + // ValueName: Extension; + // ValueData: .chaos; + // Flags: uninsdeletevalue" + registryEntries.append("Root: HKCR; Subkey: " + + "\"Mime\\Database\\Content Type\\") + .append(mime) + .append("\"; ValueType: string; ValueName: " + + "\"Extension\"; ValueData: \".") + .append(ext) + .append("\"; Flags: uninsdeletevalue uninsdeletekeyifempty\r\n"); + } else { + registryEntries.append( + "Root: HKCU; Subkey: \"Software\\" + + "Classes\\Mime\\Database\\Content Type\\") + .append(mime) + .append("\"; ValueType: string; " + + "ValueName: \"Extension\"; ValueData: \".") + .append(ext) + .append("\"; Flags: uninsdeletevalue uninsdeletekeyifempty\r\n"); + } + } + } + + if (isSystemWide) { + // "Root: HKCR; + // Subkey: \"MyProgramFile\"; + // ValueType: string; + // ValueName: \"\"; + // ValueData: \"My Program File\"; + // Flags: uninsdeletekey" + registryEntries.append("Root: HKCR; Subkey: \"") + .append(entryName) + .append( + "\"; ValueType: string; ValueName: \"\"; ValueData: \"") + .append(removeQuotes(description)) + .append("\"; Flags: uninsdeletekey\r\n"); + } else { + registryEntries.append( + "Root: HKCU; Subkey: \"Software\\Classes\\") + .append(entryName) + .append( + "\"; ValueType: string; ValueName: \"\"; ValueData: \"") + .append(removeQuotes(description)) + .append("\"; Flags: uninsdeletekey\r\n"); + } + + if (icon != null && icon.exists()) { + if (isSystemWide) { + // "Root: HKCR; + // Subkey: \"MyProgramFile\\DefaultIcon\"; + // ValueType: string; + // ValueName: \"\"; + // ValueData: \"{app}\\MYPROG.EXE,0\"\n" + + registryEntries.append("Root: HKCR; Subkey: \"") + .append(entryName) + .append("\\DefaultIcon\"; ValueType: string; " + + "ValueName: \"\"; ValueData: \"{app}\\") + .append(icon.getName()) + .append("\"\r\n"); + } else { + registryEntries.append( + "Root: HKCU; Subkey: \"Software\\Classes\\") + .append(entryName) + .append("\\DefaultIcon\"; ValueType: string; " + + "ValueName: \"\"; ValueData: \"{app}\\") + .append(icon.getName()) + .append("\"\r\n"); + } + } + + if (isSystemWide) { + // "Root: HKCR; + // Subkey: \"MyProgramFile\\shell\\open\\command\"; + // ValueType: string; + // ValueName: \"\"; + // ValueData: \"\"\"{app}\\MYPROG.EXE\"\" \"\"%1\"\"\"\n" + registryEntries.append("Root: HKCR; Subkey: \"") + .append(entryName) + .append("\\shell\\open\\command\"; ValueType: " + + "string; ValueName: \"\"; ValueData: \"\"\"{app}\\") + .append(APP_NAME.fetchFrom(p)) + .append("\"\" \"\"%1\"\"\"\r\n"); + } else { + registryEntries.append( + "Root: HKCU; Subkey: \"Software\\Classes\\") + .append(entryName) + .append("\\shell\\open\\command\"; ValueType: " + + "string; ValueName: \"\"; ValueData: \"\"\"{app}\\") + .append(APP_NAME.fetchFrom(p)) + .append("\"\" \"\"%1\"\"\"\r\n"); + } + } + if (registryEntries.length() > 0) { + data.put("FILE_ASSOCIATIONS", + "ChangesAssociations=yes\r\n\r\n[Registry]\r\n" + + registryEntries.toString()); + } else { + data.put("FILE_ASSOCIATIONS", ""); + } + + String iss = StandardBundlerParam.isRuntimeInstaller(p) ? + DEFAULT_JRE_EXE_TEMPLATE : DEFAULT_EXE_PROJECT_TEMPLATE; + + Writer w = new BufferedWriter(new FileWriter( + getConfig_ExeProjectFile(p))); + + String content = preprocessTextResource( + getConfig_ExeProjectFile(p).getName(), + getString("resource.inno-setup-project-file"), + iss, data, VERBOSE.fetchFrom(p), + RESOURCE_DIR.fetchFrom(p)); + w.write(content); + w.close(); + return true; + } + + private final static String removeQuotes(String s) { + if (s.length() > 2 && s.startsWith("\"") && s.endsWith("\"")) { + // special case for '"XXX"' return 'XXX' not '-XXX-' + // note '"' and '""' are excluded from this special case + s = s.substring(1, s.length() - 1); + } + // if there interior double quotes replace them with '-' + return s.replaceAll("\"", "-"); + } + + private final static String DEFAULT_INNO_SETUP_ICON = + "icon_inno_setup.bmp"; + + private boolean prepareProjectConfig(Map p) + throws IOException { + prepareMainProjectFile(p); + + // prepare installer icon + File iconTarget = getConfig_SmallInnoSetupIcon(p); + fetchResource(iconTarget.getName(), + getString("resource.setup-icon"), + DEFAULT_INNO_SETUP_ICON, + iconTarget, + VERBOSE.fetchFrom(p), + RESOURCE_DIR.fetchFrom(p)); + + fetchResource(getConfig_Script(p).getName(), + getString("resource.post-install-script"), + (String) null, + getConfig_Script(p), + VERBOSE.fetchFrom(p), + RESOURCE_DIR.fetchFrom(p)); + return true; + } + + private File getConfig_SmallInnoSetupIcon( + Map p) { + return new File(EXE_IMAGE_DIR.fetchFrom(p), + APP_NAME.fetchFrom(p) + "-setup-icon.bmp"); + } + + private File getConfig_ExeProjectFile(Map p) { + return new File(EXE_IMAGE_DIR.fetchFrom(p), + APP_NAME.fetchFrom(p) + ".iss"); + } + + + private File buildEXE(Map p, File outdir) + throws IOException { + Log.verbose(MessageFormat.format( + getString("message.outputting-to-location"), + outdir.getAbsolutePath())); + + outdir.mkdirs(); + + // run Inno Setup + ProcessBuilder pb = new ProcessBuilder( + TOOL_INNO_SETUP_COMPILER_EXECUTABLE.fetchFrom(p), + "/q", // turn off inno setup output + "/o"+outdir.getAbsolutePath(), + getConfig_ExeProjectFile(p).getAbsolutePath()); + pb = pb.directory(EXE_IMAGE_DIR.fetchFrom(p)); + IOUtils.exec(pb, VERBOSE.fetchFrom(p)); + + Log.verbose(MessageFormat.format( + getString("message.output-location"), + outdir.getAbsolutePath())); + + // presume the result is the ".exe" file with the newest modified time + // not the best solution, but it is the most reliable + File result = null; + long lastModified = 0; + File[] list = outdir.listFiles(); + if (list != null) { + for (File f : list) { + if (f.getName().endsWith(".exe") && + f.lastModified() > lastModified) { + result = f; + lastModified = f.lastModified(); + } + } + } + + return result; + } + + public static void ensureByMutationFileIsRTF(File f) { + if (f == null || !f.isFile()) return; + + try { + boolean existingLicenseIsRTF = false; + + try (FileInputStream fin = new FileInputStream(f)) { + byte[] firstBits = new byte[7]; + + if (fin.read(firstBits) == firstBits.length) { + String header = new String(firstBits); + existingLicenseIsRTF = "{\\rtf1\\".equals(header); + } + } + + if (!existingLicenseIsRTF) { + List oldLicense = Files.readAllLines(f.toPath()); + try (Writer w = Files.newBufferedWriter( + f.toPath(), Charset.forName("Windows-1252"))) { + w.write("{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033" + + "{\\fonttbl{\\f0\\fnil\\fcharset0 Arial;}}\n" + + "\\viewkind4\\uc1\\pard\\sa200\\sl276" + + "\\slmult1\\lang9\\fs20 "); + oldLicense.forEach(l -> { + try { + for (char c : l.toCharArray()) { + if (c < 0x10) { + w.write("\\'0"); + w.write(Integer.toHexString(c)); + } else if (c > 0xff) { + w.write("\\ud"); + w.write(Integer.toString(c)); + w.write("?"); + } else if ((c < 0x20) || (c >= 0x80) || + (c == 0x5C) || (c == 0x7B) || + (c == 0x7D)) { + w.write("\\'"); + w.write(Integer.toHexString(c)); + } else { + w.write(c); + } + } + if (l.length() < 1) { + w.write("\\par"); + } else { + w.write(" "); + } + w.write("\r\n"); + } catch (IOException e) { + Log.verbose(e); + } + }); + w.write("}\r\n"); + } + } + } catch (IOException e) { + Log.verbose(e); + } + } + + private static String getString(String key) + throws MissingResourceException { + return I18N.getString(key); + } +} diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java @@ -0,0 +1,1204 @@ +/* + * 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.jpackage.internal; + +import java.io.*; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.text.MessageFormat; +import java.util.*; +import java.util.regex.Pattern; + +import static jdk.jpackage.internal.WindowsBundlerParam.*; + +public class WinMsiBundler extends AbstractBundler { + + private static final ResourceBundle I18N = ResourceBundle.getBundle( + "jdk.jpackage.internal.resources.WinResources"); + + public static final BundlerParamInfo APP_BUNDLER = + new WindowsBundlerParam<>( + "win.app.bundler", + WinAppBundler.class, + params -> new WinAppBundler(), + null); + + public static final BundlerParamInfo CAN_USE_WIX36 = + new WindowsBundlerParam<>( + "win.msi.canUseWix36", + Boolean.class, + params -> false, + (s, p) -> Boolean.valueOf(s)); + + public static final BundlerParamInfo MSI_IMAGE_DIR = + new WindowsBundlerParam<>( + "win.msi.imageDir", + File.class, + params -> { + File imagesRoot = IMAGES_ROOT.fetchFrom(params); + if (!imagesRoot.exists()) imagesRoot.mkdirs(); + return new File(imagesRoot, "win-msi.image"); + }, + (s, p) -> null); + + public static final BundlerParamInfo WIN_APP_IMAGE = + new WindowsBundlerParam<>( + "win.app.image", + File.class, + null, + (s, p) -> null); + + public static final StandardBundlerParam MSI_SYSTEM_WIDE = + new StandardBundlerParam<>( + Arguments.CLIOptions.WIN_PER_USER_INSTALLATION.getId(), + Boolean.class, + params -> true, // MSIs default to system wide + // valueOf(null) is false, + // and we actually do want null + (s, p) -> (s == null || "null".equalsIgnoreCase(s))? null + : Boolean.valueOf(s) + ); + + + public static final StandardBundlerParam PRODUCT_VERSION = + new StandardBundlerParam<>( + "win.msi.productVersion", + String.class, + VERSION::fetchFrom, + (s, p) -> s + ); + + public static final BundlerParamInfo UPGRADE_UUID = + new WindowsBundlerParam<>( + Arguments.CLIOptions.WIN_UPGRADE_UUID.getId(), + UUID.class, + params -> UUID.randomUUID(), + (s, p) -> UUID.fromString(s)); + + private static final String TOOL_CANDLE = "candle.exe"; + private static final String TOOL_LIGHT = "light.exe"; + // autodetect just v3.7, v3.8, 3.9, 3.10 and 3.11 + private static final String AUTODETECT_DIRS = + ";C:\\Program Files (x86)\\WiX Toolset v3.11\\bin;" + + "C:\\Program Files\\WiX Toolset v3.11\\bin;" + + "C:\\Program Files (x86)\\WiX Toolset v3.10\\bin;" + + "C:\\Program Files\\WiX Toolset v3.10\\bin;" + + "C:\\Program Files (x86)\\WiX Toolset v3.9\\bin;" + + "C:\\Program Files\\WiX Toolset v3.9\\bin;" + + "C:\\Program Files (x86)\\WiX Toolset v3.8\\bin;" + + "C:\\Program Files\\WiX Toolset v3.8\\bin;" + + "C:\\Program Files (x86)\\WiX Toolset v3.7\\bin;" + + "C:\\Program Files\\WiX Toolset v3.7\\bin"; + + public static final BundlerParamInfo TOOL_CANDLE_EXECUTABLE = + new WindowsBundlerParam<>( + "win.msi.candle.exe", + String.class, + params -> { + for (String dirString : (System.getenv("PATH") + + AUTODETECT_DIRS).split(";")) { + File f = new File(dirString.replace("\"", ""), TOOL_CANDLE); + if (f.isFile()) { + return f.toString(); + } + } + return null; + }, + null); + + public static final BundlerParamInfo TOOL_LIGHT_EXECUTABLE = + new WindowsBundlerParam<>( + "win.msi.light.exe", + String.class, + params -> { + for (String dirString : (System.getenv("PATH") + + AUTODETECT_DIRS).split(";")) { + File f = new File(dirString.replace("\"", ""), TOOL_LIGHT); + if (f.isFile()) { + return f.toString(); + } + } + return null; + }, + null); + + public static final StandardBundlerParam MENU_HINT = + new WindowsBundlerParam<>( + Arguments.CLIOptions.WIN_MENU_HINT.getId(), + Boolean.class, + params -> false, + // valueOf(null) is false, + // and we actually do want null in some cases + (s, p) -> (s == null || + "null".equalsIgnoreCase(s))? true : Boolean.valueOf(s) + ); + + public static final StandardBundlerParam SHORTCUT_HINT = + new WindowsBundlerParam<>( + Arguments.CLIOptions.WIN_SHORTCUT_HINT.getId(), + Boolean.class, + params -> false, + // valueOf(null) is false, + // and we actually do want null in some cases + (s, p) -> (s == null || + "null".equalsIgnoreCase(s))? false : Boolean.valueOf(s) + ); + + @Override + public String getName() { + return I18N.getString("msi.bundler.name"); + } + + @Override + public String getDescription() { + return I18N.getString("msi.bundler.description"); + } + + @Override + public String getID() { + return "msi"; + } + + @Override + public String getBundleType() { + return "INSTALLER"; + } + + @Override + public Collection> getBundleParameters() { + Collection> results = new LinkedHashSet<>(); + results.addAll(WinAppBundler.getAppBundleParameters()); + results.addAll(getMsiBundleParameters()); + return results; + } + + public static Collection> getMsiBundleParameters() { + return Arrays.asList( + DESCRIPTION, + MENU_GROUP, + MENU_HINT, + PRODUCT_VERSION, + SHORTCUT_HINT, + MSI_SYSTEM_WIDE, + VENDOR, + LICENSE_FILE, + INSTALLDIR_CHOOSER + ); + } + + @Override + public File execute(Map params, + File outputParentDir) throws PackagerException { + return bundle(params, outputParentDir); + } + + @Override + public boolean supported(boolean platformInstaller) { + return (Platform.getPlatform() == Platform.WINDOWS); + } + + private static String findToolVersion(String toolName) { + try { + if (toolName == null || "".equals(toolName)) return null; + + ProcessBuilder pb = new ProcessBuilder( + toolName, + "/?"); + VersionExtractor ve = new VersionExtractor("version (\\d+.\\d+)"); + // not interested in the output + IOUtils.exec(pb, Log.isDebug(), true, ve); + String version = ve.getVersion(); + Log.verbose(MessageFormat.format( + I18N.getString("message.tool-version"), + toolName, version)); + return version; + } catch (Exception e) { + if (Log.isDebug()) { + Log.verbose(e); + } + return null; + } + } + + @Override + public boolean validate(Map p) + throws UnsupportedPlatformException, ConfigException { + try { + if (p == null) throw new ConfigException( + I18N.getString("error.parameters-null"), + I18N.getString("error.parameters-null.advice")); + + // run basic validation to ensure requirements are met + // we are not interested in return code, only possible exception + APP_BUNDLER.fetchFrom(p).validate(p); + + String candleVersion = + findToolVersion(TOOL_CANDLE_EXECUTABLE.fetchFrom(p)); + String lightVersion = + findToolVersion(TOOL_LIGHT_EXECUTABLE.fetchFrom(p)); + + // WiX 3.0+ is required + String minVersion = "3.0"; + boolean bad = false; + + if (VersionExtractor.isLessThan(candleVersion, minVersion)) { + Log.verbose(MessageFormat.format( + I18N.getString("message.wrong-tool-version"), + TOOL_CANDLE, candleVersion, minVersion)); + bad = true; + } + if (VersionExtractor.isLessThan(lightVersion, minVersion)) { + Log.verbose(MessageFormat.format( + I18N.getString("message.wrong-tool-version"), + TOOL_LIGHT, lightVersion, minVersion)); + bad = true; + } + + if (bad){ + throw new ConfigException( + I18N.getString("error.no-wix-tools"), + I18N.getString("error.no-wix-tools.advice")); + } + + if (!VersionExtractor.isLessThan(lightVersion, "3.6")) { + Log.verbose(I18N.getString("message.use-wix36-features")); + p.put(CAN_USE_WIX36.getID(), Boolean.TRUE); + } + + /********* validate bundle parameters *************/ + + String version = PRODUCT_VERSION.fetchFrom(p); + if (!isVersionStringValid(version)) { + throw new ConfigException( + MessageFormat.format(I18N.getString( + "error.version-string-wrong-format"), version), + MessageFormat.format(I18N.getString( + "error.version-string-wrong-format.advice"), + PRODUCT_VERSION.getID())); + } + + // only one mime type per association, at least one file extension + List> associations = + FILE_ASSOCIATIONS.fetchFrom(p); + if (associations != null) { + for (int i = 0; i < associations.size(); i++) { + Map assoc = associations.get(i); + List mimes = FA_CONTENT_TYPE.fetchFrom(assoc); + if (mimes.size() > 1) { + throw new ConfigException(MessageFormat.format( + I18N.getString("error.too-many-content-" + + "types-for-file-association"), i), + I18N.getString("error.too-many-content-" + + "types-for-file-association.advice")); + } + } + } + + return true; + } catch (RuntimeException re) { + if (re.getCause() instanceof ConfigException) { + throw (ConfigException) re.getCause(); + } else { + throw new ConfigException(re); + } + } + } + + // http://msdn.microsoft.com/en-us/library/aa370859%28v=VS.85%29.aspx + // The format of the string is as follows: + // major.minor.build + // The first field is the major version and has a maximum value of 255. + // The second field is the minor version and has a maximum value of 255. + // The third field is called the build version or the update version and + // has a maximum value of 65,535. + static boolean isVersionStringValid(String v) { + if (v == null) { + return true; + } + + String p[] = v.split("\\."); + if (p.length > 3) { + Log.verbose(I18N.getString( + "message.version-string-too-many-components")); + return false; + } + + try { + int val = Integer.parseInt(p[0]); + if (val < 0 || val > 255) { + Log.verbose(I18N.getString( + "error.version-string-major-out-of-range")); + return false; + } + if (p.length > 1) { + val = Integer.parseInt(p[1]); + if (val < 0 || val > 255) { + Log.verbose(I18N.getString( + "error.version-string-minor-out-of-range")); + return false; + } + } + if (p.length > 2) { + val = Integer.parseInt(p[2]); + if (val < 0 || val > 65535) { + Log.verbose(I18N.getString( + "error.version-string-build-out-of-range")); + return false; + } + } + } catch (NumberFormatException ne) { + Log.verbose(I18N.getString("error.version-string-part-not-number")); + Log.verbose(ne); + return false; + } + + return true; + } + + private boolean prepareProto(Map p) + throws PackagerException, IOException { + File appImage = StandardBundlerParam.getPredefinedAppImage(p); + File appDir = null; + + // we either have an application image or need to build one + if (appImage != null) { + appDir = new File( + MSI_IMAGE_DIR.fetchFrom(p), APP_NAME.fetchFrom(p)); + // copy everything from appImage dir into appDir/name + IOUtils.copyRecursive(appImage.toPath(), appDir.toPath()); + } else { + appDir = APP_BUNDLER.fetchFrom(p).doBundle(p, + MSI_IMAGE_DIR.fetchFrom(p), true); + } + + p.put(WIN_APP_IMAGE.getID(), appDir); + + String licenseFile = LICENSE_FILE.fetchFrom(p); + if (licenseFile != null) { + // need to copy license file to the working directory and convert to rtf if needed + File lfile = new File(licenseFile); + File destFile = new File(CONFIG_ROOT.fetchFrom(p), lfile.getName()); + IOUtils.copyFile(lfile, destFile); + ensureByMutationFileIsRTF(destFile); + } + + // copy file association icons + List> fileAssociations = + FILE_ASSOCIATIONS.fetchFrom(p); + for (Map fa : fileAssociations) { + File icon = FA_ICON.fetchFrom(fa); // TODO FA_ICON_ICO + if (icon == null) { + continue; + } + + File faIconFile = new File(appDir, icon.getName()); + + if (icon.exists()) { + try { + IOUtils.copyFile(icon, faIconFile); + } catch (IOException e) { + Log.verbose(e); + } + } + } + + return appDir != null; + } + + public File bundle(Map p, File outdir) + throws PackagerException { + if (!outdir.isDirectory() && !outdir.mkdirs()) { + throw new PackagerException("error.cannot-create-output-dir", + outdir.getAbsolutePath()); + } + if (!outdir.canWrite()) { + throw new PackagerException("error.cannot-write-to-output-dir", + outdir.getAbsolutePath()); + } + + // validate we have valid tools before continuing + String light = TOOL_LIGHT_EXECUTABLE.fetchFrom(p); + String candle = TOOL_CANDLE_EXECUTABLE.fetchFrom(p); + if (light == null || !new File(light).isFile() || + candle == null || !new File(candle).isFile()) { + Log.verbose(MessageFormat.format( + I18N.getString("message.light-file-string"), light)); + Log.verbose(MessageFormat.format( + I18N.getString("message.candle-file-string"), candle)); + throw new PackagerException("error.no-wix-tools"); + } + + File imageDir = MSI_IMAGE_DIR.fetchFrom(p); + try { + imageDir.mkdirs(); + + boolean menuShortcut = MENU_HINT.fetchFrom(p); + boolean desktopShortcut = SHORTCUT_HINT.fetchFrom(p); + if (!menuShortcut && !desktopShortcut) { + // both can not be false - user will not find the app + Log.verbose(I18N.getString("message.one-shortcut-required")); + p.put(MENU_HINT.getID(), true); + } + + if (prepareProto(p) && prepareWiXConfig(p) + && prepareBasicProjectConfig(p)) { + File configScriptSrc = getConfig_Script(p); + if (configScriptSrc.exists()) { + // we need to be running post script in the image folder + + // NOTE: Would it be better to generate it to the image + // folder and save only if "verbose" is requested? + + // for now we replicate it + File configScript = + new File(imageDir, configScriptSrc.getName()); + IOUtils.copyFile(configScriptSrc, configScript); + Log.verbose(MessageFormat.format( + I18N.getString("message.running-wsh-script"), + configScript.getAbsolutePath())); + IOUtils.run("wscript", + configScript, false); + } + return buildMSI(p, outdir); + } + return null; + } catch (IOException ex) { + Log.verbose(ex); + throw new PackagerException(ex); + } + } + + // name of post-image script + private File getConfig_Script(Map params) { + return new File(CONFIG_ROOT.fetchFrom(params), + APP_NAME.fetchFrom(params) + "-post-image.wsf"); + } + + private boolean prepareBasicProjectConfig( + Map params) throws IOException { + fetchResource(getConfig_Script(params).getName(), + I18N.getString("resource.post-install-script"), + (String) null, + getConfig_Script(params), + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + return true; + } + + private String relativePath(File basedir, File file) { + return file.getAbsolutePath().substring( + basedir.getAbsolutePath().length() + 1); + } + + boolean prepareMainProjectFile( + Map params) throws IOException { + Map data = new HashMap<>(); + + UUID productGUID = UUID.randomUUID(); + + Log.verbose(MessageFormat.format( + I18N.getString("message.generated-product-guid"), + productGUID.toString())); + + // we use random GUID for product itself but + // user provided for upgrade guid + // Upgrade guid is important to decide whether it is an upgrade of + // installed app. I.e. we need it to be the same for + // 2 different versions of app if possible + data.put("PRODUCT_GUID", productGUID.toString()); + data.put("PRODUCT_UPGRADE_GUID", + UPGRADE_UUID.fetchFrom(params).toString()); + data.put("UPGRADE_BLOCK", getUpgradeBlock(params)); + + data.put("APPLICATION_NAME", APP_NAME.fetchFrom(params)); + data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params)); + data.put("APPLICATION_VENDOR", VENDOR.fetchFrom(params)); + data.put("APPLICATION_VERSION", PRODUCT_VERSION.fetchFrom(params)); + + // WinAppBundler will add application folder again => step out + File imageRootDir = WIN_APP_IMAGE.fetchFrom(params); + File launcher = new File(imageRootDir, + WinAppBundler.getLauncherName(params)); + + String launcherPath = relativePath(imageRootDir, launcher); + data.put("APPLICATION_LAUNCHER", launcherPath); + + String iconPath = launcherPath.replace(".exe", ".ico"); + + data.put("APPLICATION_ICON", iconPath); + + data.put("REGISTRY_ROOT", getRegistryRoot(params)); + + boolean canUseWix36Features = CAN_USE_WIX36.fetchFrom(params); + data.put("WIX36_ONLY_START", + canUseWix36Features ? "" : ""); + + if (MSI_SYSTEM_WIDE.fetchFrom(params)) { + data.put("INSTALL_SCOPE", "perMachine"); + } else { + data.put("INSTALL_SCOPE", "perUser"); + } + + data.put("PLATFORM", "x64"); + data.put("WIN64", "yes"); + + data.put("UI_BLOCK", getUIBlock(params)); + + // Add CA to check install dir + if (INSTALLDIR_CHOOSER.fetchFrom(params)) { + data.put("CA_BLOCK", CA_BLOCK); + data.put("INVALID_INSTALL_DIR_DLG_BLOCK", INVALID_INSTALL_DIR_DLG_BLOCK); + } else { + data.put("CA_BLOCK", ""); + data.put("INVALID_INSTALL_DIR_DLG_BLOCK", ""); + } + + List> addLaunchers = + ADD_LAUNCHERS.fetchFrom(params); + + StringBuilder addLauncherIcons = new StringBuilder(); + for (int i = 0; i < addLaunchers.size(); i++) { + Map sl = addLaunchers.get(i); + // + if (SHORTCUT_HINT.fetchFrom(sl) || MENU_HINT.fetchFrom(sl)) { + File addLauncher = new File(imageRootDir, + WinAppBundler.getLauncherName(sl)); + String addLauncherPath = + relativePath(imageRootDir, addLauncher); + String addLauncherIconPath = + addLauncherPath.replace(".exe", ".ico"); + + addLauncherIcons.append(" \r\n"); + } + } + data.put("ADD_LAUNCHER_ICONS", addLauncherIcons.toString()); + + String wxs = StandardBundlerParam.isRuntimeInstaller(params) ? + MSI_PROJECT_TEMPLATE_SERVER_JRE : MSI_PROJECT_TEMPLATE; + + Writer w = new BufferedWriter( + new FileWriter(getConfig_ProjectFile(params))); + + String content = preprocessTextResource( + getConfig_ProjectFile(params).getName(), + I18N.getString("resource.wix-config-file"), + wxs, data, VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + w.write(content); + w.close(); + return true; + } + private int id; + private int compId; + private final static String LAUNCHER_ID = "LauncherId"; + + private static final String CA_BLOCK = + "\n" + + ""; + + private static final String INVALID_INSTALL_DIR_DLG_BLOCK = + "\n" + + "\n" + + "1\n" + + "\n" + + "\n" + + "1\n" + + "\n" + + "\n" + + "" + I18N.getString("message.install.dir.exist") + "\n" + + "\n" + + ""; + + /** + * Overrides the dialog sequence in built-in dialog set "WixUI_InstallDir" + * to exclude license dialog + */ + private static final String TWEAK_FOR_EXCLUDING_LICENSE = + " 1\n" + + " 1\n"; + + private static final String CHECK_INSTALL_DLG_CTRL = + " 1\n" + + " INSTALLDIR_VALID=\"0\"\n" + + " INSTALLDIR_VALID=\"1\"\n"; + + // Required upgrade element for installers which support major upgrade (when user + // specifies --win-upgrade-uuid). We will allow downgrades. + private static final String UPGRADE_BLOCK = + ""; + + private String getUpgradeBlock(Map params) { + if (UPGRADE_UUID.getIsDefaultValue()) { + return ""; + } else { + return UPGRADE_BLOCK; + } + } + + /** + * Creates UI element using WiX built-in dialog sets + * - WixUI_InstallDir/WixUI_Minimal. + * The dialog sets are the closest to what we want to implement. + * + * WixUI_Minimal for license dialog only + * WixUI_InstallDir for installdir dialog only or for both + * installdir/license dialogs + */ + private String getUIBlock(Map params) throws IOException { + String uiBlock = ""; // UI-less element + + // Copy CA dll to include with installer + if (INSTALLDIR_CHOOSER.fetchFrom(params)) { + File helper = new File(CONFIG_ROOT.fetchFrom(params), "wixhelper.dll"); + try (InputStream is_lib = getResourceAsStream("wixhelper.dll")) { + Files.copy(is_lib, helper.toPath()); + } + } + + if (INSTALLDIR_CHOOSER.fetchFrom(params)) { + boolean enableTweakForExcludingLicense = + (getLicenseFile(params) == null); + uiBlock = " \n" + + " \n" + + (enableTweakForExcludingLicense ? + TWEAK_FOR_EXCLUDING_LICENSE : "") + + CHECK_INSTALL_DLG_CTRL; + } else if (getLicenseFile(params) != null) { + uiBlock = " \n"; + } + + return uiBlock; + } + + private void walkFileTree(Map params, + File root, PrintStream out, String prefix) { + List dirs = new ArrayList<>(); + List files = new ArrayList<>(); + + if (!root.isDirectory()) { + throw new RuntimeException( + MessageFormat.format( + I18N.getString("error.cannot-walk-directory"), + root.getAbsolutePath())); + } + + // sort to files and dirs + File[] children = root.listFiles(); + if (children != null) { + for (File f : children) { + if (f.isDirectory()) { + dirs.add(f); + } else { + files.add(f); + } + } + } + + // have files => need to output component + out.println(prefix + " "); + out.println(prefix + " "); + out.println(prefix + " "); + + boolean needRegistryKey = !MSI_SYSTEM_WIDE.fetchFrom(params); + File imageRootDir = WIN_APP_IMAGE.fetchFrom(params); + File launcherFile = + new File(imageRootDir, WinAppBundler.getLauncherName(params)); + + // Find out if we need to use registry. We need it if + // - we doing user level install as file can not serve as KeyPath + // - if we adding shortcut in this component + + for (File f: files) { + boolean isLauncher = f.equals(launcherFile); + if (isLauncher) { + needRegistryKey = true; + } + } + + if (needRegistryKey) { + // has to be under HKCU to make WiX happy + out.println(prefix + " " : " Action=\"createAndRemoveOnUninstall\">")); + out.println(prefix + + " "); + out.println(prefix + " "); + } + + boolean menuShortcut = MENU_HINT.fetchFrom(params); + boolean desktopShortcut = SHORTCUT_HINT.fetchFrom(params); + + Map idToFileMap = new TreeMap<>(); + boolean launcherSet = false; + + for (File f : files) { + boolean isLauncher = f.equals(launcherFile); + + launcherSet = launcherSet || isLauncher; + + boolean doShortcuts = + isLauncher && (menuShortcut || desktopShortcut); + + String thisFileId = isLauncher ? LAUNCHER_ID : ("FileId" + (id++)); + idToFileMap.put(f.getName(), thisFileId); + + out.println(prefix + " "); + if (doShortcuts && desktopShortcut) { + out.println(prefix + + " "); + } + if (doShortcuts && menuShortcut) { + out.println(prefix + + " "); + } + + List> addLaunchers = + ADD_LAUNCHERS.fetchFrom(params); + for (int i = 0; i < addLaunchers.size(); i++) { + Map sl = addLaunchers.get(i); + File addLauncherFile = new File(imageRootDir, + WinAppBundler.getLauncherName(sl)); + if (f.equals(addLauncherFile)) { + if (SHORTCUT_HINT.fetchFrom(sl)) { + out.println(prefix + + " "); + } + if (MENU_HINT.fetchFrom(sl)) { + out.println(prefix + + " "); + // Should we allow different menu groups? Not for now. + } + } + } + out.println(prefix + " "); + } + + if (launcherSet) { + List> fileAssociations = + FILE_ASSOCIATIONS.fetchFrom(params); + String regName = APP_REGISTRY_NAME.fetchFrom(params); + Set defaultedMimes = new TreeSet<>(); + int count = 0; + for (Map fa : fileAssociations) { + String description = FA_DESCRIPTION.fetchFrom(fa); + List extensions = FA_EXTENSIONS.fetchFrom(fa); + List mimeTypes = FA_CONTENT_TYPE.fetchFrom(fa); + File icon = FA_ICON.fetchFrom(fa); // TODO FA_ICON_ICO + + String mime = (mimeTypes == null || + mimeTypes.isEmpty()) ? null : mimeTypes.get(0); + + if (extensions == null) { + Log.verbose(I18N.getString( + "message.creating-association-with-null-extension")); + + String entryName = regName + "File"; + if (count > 0) { + entryName += "." + count; + } + count++; + out.print(prefix + " "); + } else { + for (String ext : extensions) { + String entryName = regName + "File"; + if (count > 0) { + entryName += "." + count; + } + count++; + + out.print(prefix + " "); + + if (extensions == null) { + Log.verbose(I18N.getString( + "message.creating-association-with-null-extension")); + } else { + out.print(prefix + " "); + } else { + out.println(" ContentType='" + mime + "'>"); + if (!defaultedMimes.contains(mime)) { + out.println(prefix + + " "); + defaultedMimes.add(mime); + } + } + out.println(prefix + + " "); + out.println(prefix + " "); + } + out.println(prefix + " "); + } + } + } + } + + out.println(prefix + " "); + + for (File d : dirs) { + out.println(prefix + " "); + walkFileTree(params, d, out, prefix + " "); + out.println(prefix + " "); + } + } + + String getRegistryRoot(Map params) { + if (MSI_SYSTEM_WIDE.fetchFrom(params)) { + return "HKLM"; + } else { + return "HKCU"; + } + } + + boolean prepareContentList(Map params) + throws FileNotFoundException { + File f = new File( + CONFIG_ROOT.fetchFrom(params), MSI_PROJECT_CONTENT_FILE); + PrintStream out = new PrintStream(f); + + // opening + out.println(""); + out.println(""); + + out.println(" "); + if (MSI_SYSTEM_WIDE.fetchFrom(params)) { + // install to programfiles + out.println(" "); + } else { + // install to user folder + out.println( + " "); + } + + // We should get valid folder or subfolders + String installDir = WINDOWS_INSTALL_DIR.fetchFrom(params); + String [] installDirs = installDir.split(Pattern.quote("\\")); + for (int i = 0; i < (installDirs.length - 1); i++) { + out.println(" "); + } + + out.println(" "); + + // dynamic part + id = 0; + compId = 0; // reset counters + walkFileTree(params, WIN_APP_IMAGE.fetchFrom(params), out, " "); + + // closing + for (int i = 0; i < installDirs.length; i++) { + out.println(" "); + } + out.println(" "); + + // for shortcuts + if (SHORTCUT_HINT.fetchFrom(params)) { + out.println(" "); + } + if (MENU_HINT.fetchFrom(params)) { + out.println(" "); + out.println(" "); + out.println(" "); + out.println(" "); + // This has to be under HKCU to make WiX happy. + // There are numberous discussions on this amoung WiX users + // (if user A installs and user B uninstalls key is left behind) + // there are suggested workarounds but none of them are appealing. + // Leave it for now + out.println( + " "); + out.println(" "); + out.println(" "); + out.println(" "); + } + + out.println(" "); + + out.println(" "); + for (int j = 0; j < compId; j++) { + out.println(" "); + } + // component is defined in the template.wsx + out.println(" "); + out.println(" "); + out.println(""); + + out.close(); + return true; + } + + private File getConfig_ProjectFile(Map params) { + return new File(CONFIG_ROOT.fetchFrom(params), + APP_NAME.fetchFrom(params) + ".wxs"); + } + + private String getLicenseFile(Map p) { + String licenseFile = LICENSE_FILE.fetchFrom(p); + if (licenseFile != null) { + File lfile = new File(licenseFile); + File destFile = new File(CONFIG_ROOT.fetchFrom(p), lfile.getName()); + String filePath = destFile.getAbsolutePath(); + if (filePath.contains(" ")) { + return "\"" + filePath + "\""; + } else { + return filePath; + } + } + + return null; + } + + private boolean prepareWiXConfig( + Map params) throws IOException { + return prepareMainProjectFile(params) && prepareContentList(params); + + } + private final static String MSI_PROJECT_TEMPLATE = "template.wxs"; + private final static String MSI_PROJECT_TEMPLATE_SERVER_JRE = + "template.jre.wxs"; + private final static String MSI_PROJECT_CONTENT_FILE = "bundle.wxi"; + + private File buildMSI(Map params, File outdir) + throws IOException { + File tmpDir = new File(TEMP_ROOT.fetchFrom(params), "tmp"); + File candleOut = new File( + tmpDir, APP_NAME.fetchFrom(params) +".wixobj"); + File msiOut = new File( + outdir, INSTALLER_FILE_NAME.fetchFrom(params) + ".msi"); + + Log.verbose(MessageFormat.format(I18N.getString( + "message.preparing-msi-config"), msiOut.getAbsolutePath())); + + msiOut.getParentFile().mkdirs(); + + // run candle + ProcessBuilder pb = new ProcessBuilder( + TOOL_CANDLE_EXECUTABLE.fetchFrom(params), + "-nologo", + getConfig_ProjectFile(params).getAbsolutePath(), + "-ext", "WixUtilExtension", + "-out", candleOut.getAbsolutePath()); + pb = pb.directory(WIN_APP_IMAGE.fetchFrom(params)); + IOUtils.exec(pb, false); + + Log.verbose(MessageFormat.format(I18N.getString( + "message.generating-msi"), msiOut.getAbsolutePath())); + + boolean enableLicenseUI = (getLicenseFile(params) != null); + boolean enableInstalldirUI = INSTALLDIR_CHOOSER.fetchFrom(params); + + List commandLine = new ArrayList<>(); + + commandLine.add(TOOL_LIGHT_EXECUTABLE.fetchFrom(params)); + if (enableLicenseUI) { + commandLine.add("-dWixUILicenseRtf="+getLicenseFile(params)); + } + commandLine.add("-nologo"); + commandLine.add("-spdb"); + commandLine.add("-sice:60"); + // ignore warnings due to "missing launcguage info" (ICE60) + commandLine.add(candleOut.getAbsolutePath()); + commandLine.add("-ext"); + commandLine.add("WixUtilExtension"); + if (enableLicenseUI || enableInstalldirUI) { + commandLine.add("-ext"); + commandLine.add("WixUIExtension.dll"); + } + + // Only needed if we using CA dll, so Wix can find it + if (enableInstalldirUI) { + commandLine.add("-b"); + commandLine.add(CONFIG_ROOT.fetchFrom(params).getAbsolutePath()); + } + + commandLine.add("-out"); + commandLine.add(msiOut.getAbsolutePath()); + + // create .msi + pb = new ProcessBuilder(commandLine); + + pb = pb.directory(WIN_APP_IMAGE.fetchFrom(params)); + IOUtils.exec(pb, false); + + candleOut.delete(); + IOUtils.deleteRecursive(tmpDir); + + return msiOut; + } + + public static void ensureByMutationFileIsRTF(File f) { + if (f == null || !f.isFile()) return; + + try { + boolean existingLicenseIsRTF = false; + + try (FileInputStream fin = new FileInputStream(f)) { + byte[] firstBits = new byte[7]; + + if (fin.read(firstBits) == firstBits.length) { + String header = new String(firstBits); + existingLicenseIsRTF = "{\\rtf1\\".equals(header); + } + } + + if (!existingLicenseIsRTF) { + List oldLicense = Files.readAllLines(f.toPath()); + try (Writer w = Files.newBufferedWriter( + f.toPath(), Charset.forName("Windows-1252"))) { + w.write("{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033" + + "{\\fonttbl{\\f0\\fnil\\fcharset0 Arial;}}\n" + + "\\viewkind4\\uc1\\pard\\sa200\\sl276" + + "\\slmult1\\lang9\\fs20 "); + oldLicense.forEach(l -> { + try { + for (char c : l.toCharArray()) { + // 0x00 <= ch < 0x20 Escaped (\'hh) + // 0x20 <= ch < 0x80 Raw(non - escaped) char + // 0x80 <= ch <= 0xFF Escaped(\ 'hh) + // 0x5C, 0x7B, 0x7D (special RTF characters + // \,{,})Escaped(\'hh) + // ch > 0xff Escaped (\\ud###?) + if (c < 0x10) { + w.write("\\'0"); + w.write(Integer.toHexString(c)); + } else if (c > 0xff) { + w.write("\\ud"); + w.write(Integer.toString(c)); + // \\uc1 is in the header and in effect + // so we trail with a replacement char if + // the font lacks that character - '?' + w.write("?"); + } else if ((c < 0x20) || (c >= 0x80) || + (c == 0x5C) || (c == 0x7B) || + (c == 0x7D)) { + w.write("\\'"); + w.write(Integer.toHexString(c)); + } else { + w.write(c); + } + } + // blank lines are interpreted as paragraph breaks + if (l.length() < 1) { + w.write("\\par"); + } else { + w.write(" "); + } + w.write("\r\n"); + } catch (IOException e) { + Log.verbose(e); + } + }); + w.write("}\r\n"); + } + } + } catch (IOException e) { + Log.verbose(e); + } + + } +} diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsAppImageBuilder.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsAppImageBuilder.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsAppImageBuilder.java @@ -0,0 +1,444 @@ +/* + * 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.jpackage.internal; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.UncheckedIOException; +import java.io.Writer; +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.PosixFilePermission; +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.ResourceBundle; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import static jdk.jpackage.internal.StandardBundlerParam.*; + +public class WindowsAppImageBuilder extends AbstractAppImageBuilder { + + static { + System.loadLibrary("jpackage"); + } + + private static final ResourceBundle I18N = ResourceBundle.getBundle( + "jdk.jpackage.internal.resources.WinResources"); + + private final static String LIBRARY_NAME = "applauncher.dll"; + private final static String REDIST_MSVCR = "vcruntimeVS_VER.dll"; + private final static String REDIST_MSVCP = "msvcpVS_VER.dll"; + + private final static String TEMPLATE_APP_ICON ="javalogo_white_48.ico"; + + private static final String EXECUTABLE_PROPERTIES_TEMPLATE = + "WinLauncher.template"; + + private final Path root; + private final Path appDir; + private final Path appModsDir; + private final Path runtimeDir; + private final Path mdir; + + private final Map params; + + public static final BundlerParamInfo REBRAND_EXECUTABLE = + new WindowsBundlerParam<>( + "win.launcher.rebrand", + Boolean.class, + params -> Boolean.TRUE, + (s, p) -> Boolean.valueOf(s)); + + public static final BundlerParamInfo ICON_ICO = + new StandardBundlerParam<>( + "icon.ico", + File.class, + params -> { + File f = ICON.fetchFrom(params); + if (f != null && !f.getName().toLowerCase().endsWith(".ico")) { + Log.error(MessageFormat.format( + I18N.getString("message.icon-not-ico"), f)); + return null; + } + return f; + }, + (s, p) -> new File(s)); + + public static final StandardBundlerParam CONSOLE_HINT = + new WindowsBundlerParam<>( + Arguments.CLIOptions.WIN_CONSOLE_HINT.getId(), + Boolean.class, + params -> false, + // valueOf(null) is false, + // and we actually do want null in some cases + (s, p) -> (s == null + || "null".equalsIgnoreCase(s)) ? true : Boolean.valueOf(s)); + + public WindowsAppImageBuilder(Map config, Path imageOutDir) + throws IOException { + super(config, + imageOutDir.resolve(APP_NAME.fetchFrom(config) + "/runtime")); + + Objects.requireNonNull(imageOutDir); + + this.params = config; + + this.root = imageOutDir.resolve(APP_NAME.fetchFrom(params)); + this.appDir = root.resolve("app"); + this.appModsDir = appDir.resolve("mods"); + this.runtimeDir = root.resolve("runtime"); + this.mdir = runtimeDir.resolve("lib"); + Files.createDirectories(appDir); + Files.createDirectories(runtimeDir); + } + + public WindowsAppImageBuilder(String jreName, Path imageOutDir) + throws IOException { + super(null, imageOutDir.resolve(jreName)); + + Objects.requireNonNull(imageOutDir); + + this.params = null; + this.root = imageOutDir.resolve(jreName); + this.appDir = null; + this.appModsDir = null; + this.runtimeDir = root; + this.mdir = runtimeDir.resolve("lib"); + Files.createDirectories(runtimeDir); + } + + private Path destFile(String dir, String filename) { + return runtimeDir.resolve(dir).resolve(filename); + } + + private void writeEntry(InputStream in, Path dstFile) throws IOException { + Files.createDirectories(dstFile.getParent()); + Files.copy(in, dstFile); + } + + private void writeSymEntry(Path dstFile, Path target) throws IOException { + Files.createDirectories(dstFile.getParent()); + Files.createLink(dstFile, target); + } + + /** + * chmod ugo+x file + */ + private void setExecutable(Path file) { + try { + Set perms = + Files.getPosixFilePermissions(file); + perms.add(PosixFilePermission.OWNER_EXECUTE); + perms.add(PosixFilePermission.GROUP_EXECUTE); + perms.add(PosixFilePermission.OTHERS_EXECUTE); + Files.setPosixFilePermissions(file, perms); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + } + + private static void createUtf8File(File file, String content) + throws IOException { + try (OutputStream fout = new FileOutputStream(file); + Writer output = new OutputStreamWriter(fout, "UTF-8")) { + output.write(content); + } + } + + public static String getLauncherName(Map p) { + return APP_NAME.fetchFrom(p) + ".exe"; + } + + // Returns launcher resource name for launcher we need to use. + public static String getLauncherResourceName( + Map p) { + if (CONSOLE_HINT.fetchFrom(p)) { + return "jpackageapplauncher.exe"; + } else { + return "jpackageapplauncherw.exe"; + } + } + + public static String getLauncherCfgName(Map p) { + return "app/" + APP_NAME.fetchFrom(p) +".cfg"; + } + + private File getConfig_AppIcon(Map params) { + return new File(getConfigRoot(params), + APP_NAME.fetchFrom(params) + ".ico"); + } + + private File getConfig_ExecutableProperties( + Map params) { + return new File(getConfigRoot(params), + APP_NAME.fetchFrom(params) + ".properties"); + } + + File getConfigRoot(Map params) { + return CONFIG_ROOT.fetchFrom(params); + } + + @Override + public Path getAppDir() { + return appDir; + } + + @Override + public Path getAppModsDir() { + return appModsDir; + } + + @Override + public void prepareApplicationFiles() throws IOException { + Map originalParams = new HashMap<>(params); + File rootFile = root.toFile(); + if (!rootFile.isDirectory() && !rootFile.mkdirs()) { + throw new RuntimeException(MessageFormat.format(I18N.getString( + "error.cannot-create-output-dir"), rootFile.getAbsolutePath())); + } + if (!rootFile.canWrite()) { + throw new RuntimeException(MessageFormat.format( + I18N.getString("error.cannot-write-to-output-dir"), + rootFile.getAbsolutePath())); + } + // create the .exe launchers + createLauncherForEntryPoint(params); + + // copy the jars + copyApplication(params); + + // copy in the needed libraries + try (InputStream is_lib = getResourceAsStream(LIBRARY_NAME)) { + Files.copy(is_lib, root.resolve(LIBRARY_NAME)); + } + + copyMSVCDLLs(); + + // create the additional launcher(s), if any + List> entryPoints = + StandardBundlerParam.ADD_LAUNCHERS.fetchFrom(params); + for (Map entryPoint : entryPoints) { + createLauncherForEntryPoint( + AddLauncherArguments.merge(originalParams, entryPoint)); + } + } + + @Override + public void prepareJreFiles() throws IOException {} + + private void copyMSVCDLLs() throws IOException { + AtomicReference ioe = new AtomicReference<>(); + try (Stream files = Files.list(runtimeDir.resolve("bin"))) { + files.filter(p -> Pattern.matches( + "^(vcruntime|msvcp|msvcr|ucrtbase|api-ms-win-).*\\.dll$", + p.toFile().getName().toLowerCase())) + .forEach(p -> { + try { + Files.copy(p, root.resolve((p.toFile().getName()))); + } catch (IOException e) { + ioe.set(e); + } + }); + } + + IOException e = ioe.get(); + if (e != null) { + throw e; + } + } + + // TODO: do we still need this? + private boolean copyMSVCDLLs(String VS_VER) throws IOException { + final InputStream REDIST_MSVCR_URL = getResourceAsStream( + REDIST_MSVCR.replaceAll("VS_VER", VS_VER)); + final InputStream REDIST_MSVCP_URL = getResourceAsStream( + REDIST_MSVCP.replaceAll("VS_VER", VS_VER)); + + if (REDIST_MSVCR_URL != null && REDIST_MSVCP_URL != null) { + Files.copy( + REDIST_MSVCR_URL, + root.resolve(REDIST_MSVCR.replaceAll("VS_VER", VS_VER))); + Files.copy( + REDIST_MSVCP_URL, + root.resolve(REDIST_MSVCP.replaceAll("VS_VER", VS_VER))); + return true; + } + + return false; + } + + private void validateValueAndPut( + Map data, String key, + BundlerParamInfo param, + Map params) { + String value = param.fetchFrom(params); + if (value.contains("\r") || value.contains("\n")) { + Log.error("Configuration Parameter " + param.getID() + + " contains multiple lines of text, ignore it"); + data.put(key, ""); + return; + } + data.put(key, value); + } + + protected void prepareExecutableProperties( + Map params) throws IOException { + Map data = new HashMap<>(); + + // mapping Java parameters in strings for version resource + validateValueAndPut(data, "COMPANY_NAME", VENDOR, params); + validateValueAndPut(data, "FILE_DESCRIPTION", DESCRIPTION, params); + validateValueAndPut(data, "FILE_VERSION", VERSION, params); + data.put("INTERNAL_NAME", getLauncherName(params)); + validateValueAndPut(data, "LEGAL_COPYRIGHT", COPYRIGHT, params); + data.put("ORIGINAL_FILENAME", getLauncherName(params)); + validateValueAndPut(data, "PRODUCT_NAME", APP_NAME, params); + validateValueAndPut(data, "PRODUCT_VERSION", VERSION, params); + + try (Writer w = Files.newBufferedWriter( + getConfig_ExecutableProperties(params).toPath(), + StandardCharsets.UTF_8)) { + String content = preprocessTextResource( + getConfig_ExecutableProperties(params).getName(), + I18N.getString("resource.executable-properties-template"), + EXECUTABLE_PROPERTIES_TEMPLATE, data, + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + w.write(content); + } + } + + private void createLauncherForEntryPoint( + Map p) throws IOException { + + File launcherIcon = ICON_ICO.fetchFrom(p); + File icon = launcherIcon != null ? + launcherIcon : ICON_ICO.fetchFrom(params); + File iconTarget = getConfig_AppIcon(p); + + InputStream in = locateResource( + APP_NAME.fetchFrom(params) + ".ico", + "icon", + TEMPLATE_APP_ICON, + icon, + VERBOSE.fetchFrom(params), + RESOURCE_DIR.fetchFrom(params)); + + Files.copy(in, iconTarget.toPath(), + StandardCopyOption.REPLACE_EXISTING); + + writeCfgFile(p, root.resolve( + getLauncherCfgName(p)).toFile(), "$APPDIR\\runtime"); + + prepareExecutableProperties(p); + + // Copy executable root folder + Path executableFile = root.resolve(getLauncherName(p)); + try (InputStream is_launcher = + getResourceAsStream(getLauncherResourceName(p))) { + writeEntry(is_launcher, executableFile); + } + + File launcher = executableFile.toFile(); + launcher.setWritable(true, true); + + // Update branding of EXE file + if (REBRAND_EXECUTABLE.fetchFrom(p)) { + try { + String tempDirectory = WindowsDefender.getUserTempDirectory(); + if (Arguments.CLIOptions.context().userProvidedBuildRoot) { + tempDirectory = TEMP_ROOT.fetchFrom(p).getAbsolutePath(); + } + if (WindowsDefender.isThereAPotentialWindowsDefenderIssue( + tempDirectory)) { + Log.error(MessageFormat.format(I18N.getString( + "message.potential.windows.defender.issue"), + tempDirectory)); + } + + launcher.setWritable(true); + + if (iconTarget.exists()) { + iconSwap(iconTarget.getAbsolutePath(), + launcher.getAbsolutePath()); + } + + File executableProperties = getConfig_ExecutableProperties(p); + + if (executableProperties.exists()) { + if (versionSwap(executableProperties.getAbsolutePath(), + launcher.getAbsolutePath()) != 0) { + throw new RuntimeException(MessageFormat.format( + I18N.getString("error.version-swap"), + executableProperties.getAbsolutePath())); + } + } + } finally { + executableFile.toFile().setReadOnly(); + } + } + + Files.copy(iconTarget.toPath(), + root.resolve(APP_NAME.fetchFrom(p) + ".ico")); + } + + private void copyApplication(Map params) + throws IOException { + List appResourcesList = + APP_RESOURCES_LIST.fetchFrom(params); + if (appResourcesList == null) { + throw new RuntimeException("Null app resources?"); + } + for (RelativeFileSet appResources : appResourcesList) { + if (appResources == null) { + throw new RuntimeException("Null app resources?"); + } + File srcdir = appResources.getBaseDirectory(); + for (String fname : appResources.getIncludedFiles()) { + copyEntry(appDir, srcdir, fname); + } + } + } + + private static native int iconSwap(String iconTarget, String launcher); + + private static native int versionSwap(String executableProperties, String launcher); + +} diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsBundlerParam.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsBundlerParam.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsBundlerParam.java @@ -0,0 +1,116 @@ +/* + * 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.jpackage.internal; + +import java.text.MessageFormat; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.function.BiFunction; +import java.util.function.Function; + +class WindowsBundlerParam extends StandardBundlerParam { + + private static final ResourceBundle I18N = ResourceBundle.getBundle( + "jdk.jpackage.internal.resources.WinResources"); + + WindowsBundlerParam(String id, Class valueType, + Function, T> defaultValueFunction, + BiFunction, T> stringConverter) { + super(id, valueType, defaultValueFunction, stringConverter); + } + + static final BundlerParamInfo INSTALLER_FILE_NAME = + new StandardBundlerParam<> ( + "win.installerName", + String.class, + params -> { + String nm = APP_NAME.fetchFrom(params); + if (nm == null) return null; + + String version = VERSION.fetchFrom(params); + if (version == null) { + return nm; + } else { + return nm + "-" + version; + } + }, + (s, p) -> s); + + static final BundlerParamInfo APP_REGISTRY_NAME = + new StandardBundlerParam<> ( + Arguments.CLIOptions.WIN_REGISTRY_NAME.getId(), + String.class, + params -> { + String nm = APP_NAME.fetchFrom(params); + if (nm == null) return null; + + return nm.replaceAll("[^-a-zA-Z\\.0-9]", ""); + }, + (s, p) -> s); + + static final StandardBundlerParam MENU_GROUP = + new StandardBundlerParam<>( + Arguments.CLIOptions.WIN_MENU_GROUP.getId(), + String.class, + params -> I18N.getString("param.menu-group.default"), + (s, p) -> s + ); + + static final BundlerParamInfo INSTALLDIR_CHOOSER = + new StandardBundlerParam<> ( + Arguments.CLIOptions.WIN_DIR_CHOOSER.getId(), + Boolean.class, + params -> Boolean.FALSE, + (s, p) -> Boolean.valueOf(s) + ); + + static final BundlerParamInfo WINDOWS_INSTALL_DIR = + new StandardBundlerParam<>( + "windows-install-dir", + String.class, + params -> { + String dir = INSTALL_DIR.fetchFrom(params); + if (dir != null) { + if (dir.contains(":") || dir.contains("..")) { + Log.error(MessageFormat.format(I18N.getString( + "message.invalid.install.dir"), dir, + APP_NAME.fetchFrom(params))); + } else { + if (dir.startsWith("\\")) { + dir = dir.substring(1); + } + if (dir.endsWith("\\")) { + dir = dir.substring(0, dir.length() - 1); + } + return dir; + } + } + return APP_NAME.fetchFrom(params); // Default to app name + }, + (s, p) -> s + ); +} diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsDefender.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsDefender.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsDefender.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.jpackage.internal; + +import java.util.List; + +final class WindowsDefender { + + private WindowsDefender() {} + + static final boolean isThereAPotentialWindowsDefenderIssue(String dir) { + boolean result = false; + + if (Platform.getPlatform() == Platform.WINDOWS && + Platform.getMajorVersion() == 10) { + + // If DisableRealtimeMonitoring is not enabled then there + // may be a problem. + if (!WindowsRegistry.readDisableRealtimeMonitoring() && + !isDirectoryInExclusionPath(dir)) { + result = true; + } + } + + return result; + } + + private static boolean isDirectoryInExclusionPath(String dir) { + boolean result = false; + // If the user temp directory is not found in the exclusion + // list then there may be a problem. + List paths = WindowsRegistry.readExclusionsPaths(); + for (String s : paths) { + if (WindowsRegistry.comparePaths(s, dir)) { + result = true; + break; + } + } + + return result; + } + + static final String getUserTempDirectory() { + String tempDirectory = System.getProperty("java.io.tmpdir"); + return tempDirectory; + } +} diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsRegistry.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsRegistry.java new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsRegistry.java @@ -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.jpackage.internal; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; + +import static jdk.jpackage.internal.IOUtils.exec; + +final class WindowsRegistry { + + // Currently we only support HKEY_LOCAL_MACHINE. Native implementation will + // require support for additinal HKEY if needed. + private static final int HKEY_LOCAL_MACHINE = 1; + + static { + System.loadLibrary("jpackage"); + } + + private WindowsRegistry() {} + + /** + * Reads the registry value for DisableRealtimeMonitoring. + * @return true if DisableRealtimeMonitoring is set to 0x1, + * false otherwise. + */ + static final boolean readDisableRealtimeMonitoring() { + final String subKey = "Software\\Microsoft\\" + + "Windows Defender\\Real-Time Protection"; + final String value = "DisableRealtimeMonitoring"; + int result = readDwordValue(HKEY_LOCAL_MACHINE, subKey, value, 0); + return (result == 1); + } + + static final List readExclusionsPaths() { + List result = new ArrayList<>(); + final String subKey = "Software\\Microsoft\\" + + "Windows Defender\\Exclusions\\Paths"; + long lKey = openRegistryKey(HKEY_LOCAL_MACHINE, subKey); + if (lKey == 0) { + return result; + } + + String valueName; + int index = 0; + do { + valueName = enumRegistryValue(lKey, index); + if (valueName != null) { + result.add(valueName); + index++; + } + } while (valueName != null); + + closeRegistryKey(lKey); + + return result; + } + + /** + * Reads DWORD registry value. + * + * @param key one of HKEY predefine value + * @param subKey registry sub key + * @param value value to read + * @param defaultValue default value in case if subKey or value not found + * or any other errors occurred + * @return value's data only if it was read successfully, otherwise + * defaultValue + */ + private static native int readDwordValue(int key, String subKey, + String value, int defaultValue); + + /** + * Open registry key. + * + * @param key one of HKEY predefine value + * @param subKey registry sub key + * @return native handle to open key + */ + private static native long openRegistryKey(int key, String subKey); + + /** + * Enumerates the values for registry key. + * + * @param lKey native handle to open key returned by openRegistryKey + * @param index index of value starting from 0. Increment until this + * function returns NULL which means no more values. + * @return returns value or NULL if error or no more data + */ + private static native String enumRegistryValue(long lKey, int index); + + /** + * Close registry key. + * + * @param lKey native handle to open key returned by openRegistryKey + */ + private static native void closeRegistryKey(long lKey); + + /** + * Compares two Windows paths regardless case and if paths are short or long. + * + * @param path1 path to compare + * @param path2 path to compare + * @return true if paths point to same location + */ + public static native boolean comparePaths(String path1, String path2); +} diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinLauncher.template b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinLauncher.template new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinLauncher.template @@ -0,0 +1,34 @@ +# +# Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# +# + +CompanyName=COMPANY_NAME +FileDescription=FILE_DESCRIPTION +FileVersion=FILE_VERSION +InternalName=INTERNAL_NAME +LegalCopyright=LEGAL_COPYRIGHT +OriginalFilename=ORIGINAL_FILENAME +ProductName=PRODUCT_NAME +ProductVersion=PRODUCT_VERSION diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties @@ -0,0 +1,90 @@ +# +# Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# +# + +app.bundler.name=Windows Application Image +app.bundler.description=A Directory based image of a windows Application with an optionally co-bundled JRE. Used as a base for the Installer bundlers +exe.bundler.name=EXE Installer +exe.bundler.description=Microsoft Windows EXE Installer, via InnoIDE. +msi.bundler.name=MSI Installer +msi.bundler.description=Microsoft Windows MSI Installer, via WiX. + +param.menu-group.default=Unknown + +resource.application-icon=application icon +resource.executable-properties-template=Template for creating executable properties file. +resource.inno-setup-project-file=Inno Setup project file +resource.setup-icon=setup dialog icon +resource.post-install-script=script to run after application image is populated +resource.wix-config-file=WiX config file + +error.parameters-null=Parameters map is null. +error.parameters-null.advice=Pass in a non-null parameters map. +error.no-windows-resources=This copy of the JDK does not support Windows. +error.no-windows-resources.advice=Please use the Oracle JDK for Windows. +error.cannot-find-launcher=Cannot find cfg file in predefined app image directory {0}. +error.cannot-create-output-dir=Output directory {0} cannot be created. +error.cannot-write-to-output-dir=Output directory {0} is not writable. +error.iscc-not-found=Can not find Inno Setup Compiler (iscc.exe). +error.iscc-not-found.advice=Download Inno Setup 5 or later from http://www.jrsoftware.org and add it to the PATH. +error.copyright-is-too-long=The copyright string is too long for InnoSetup. +error.copyright-is-too-long.advice=Provide a copyright string shorter than 100 characters. +error.too-many-content-types-for-file-association=More than one MIME types was specified for File Association number {0}. +error.too-many-content-types-for-file-association.advice=Specify one and only one MIME type for each file association. +error.no-wix-tools=Can not find WiX tools (light.exe, candle.exe). +error.no-wix-tools.advice=Download WiX 3.0 or later from http://wix.sf.net and add it to the PATH. +error.version-string-wrong-format=Version string is not compatible with MSI rules [{0}]. +error.version-string-wrong-format.advice=Set the bundler argument "{0}" according to these rules: http://msdn.microsoft.com/en-us/library/aa370859%28v\=VS.85%29.aspx . +error.version-string-major-out-of-range=Major version must be in the range [0, 255]. +error.version-string-build-out-of-range=Build part of version must be in the range [0, 65535]. +error.version-string-minor-out-of-range=Minor version must be in the range [0, 255]. +error.version-string-part-not-number=Failed to convert version component to int. +error.cannot-walk-directory=Can not walk [{0}] - it is not a valid directory. +error.version-swap=Failed to update version information for {0}. + +message.result-dir=Result application bundle: {0}. +message.icon-not-ico=The specified icon "{0}" is not an ICO file and will not be used. The default icon will be used in it's place. +message.multiple-launchers=Multiple launchers found in predefined app-image. {0} will be used as primary launcher. +message.potential.windows.defender.issue=Warning: Windows Defender may prevent jpackage from functioning. If there is an issue, it can be addressed by either disabling realtime monitoring, or adding an exclusion for the directory "{0}". +message.tool-wrong-version=Detected [{0}] version {1} but version {2} is required. +message.outputting-to-location=Generating EXE for installer to: {0}. +message.output-location=Installer (.exe) saved to: {0} +message.tool-version=Detected [{0}] version [{1}]. +message.one-shortcut-required=At least one type of shortcut is required. Enabling menu shortcut. +message.running-wsh-script=Running WSH script on application image [{0}]. +message.iscc-file-string=InnoSetup compiler set to {0}. +message.creating-association-with-null-extension=Creating association with null extension. +message.wrong-tool-version=Detected [{0}] version {1} but version {2} is required. +message.version-string-too-many-components=Version sting may have up to 3 components - major.minor.build . +message.truncating.id=Truncating Application ID to 126 chars for Inno Setup. +message.use-wix36-features=WiX 3.6 detected. Enabling advanced cleanup action. +message.generated-product-guid=Generated product GUID: {0}. +message.preparing-msi-config=Preparing MSI config: {0}. +message.generating-msi=Generating MSI: {0}. +message.light-file-string=WiX light tool set to {0}. +message.candle-file-string=WiX candle tool set to {0}. +message.install.dir.exist=The folder [APPLICATIONFOLDER] already exist. Whould you like to install to that folder anyway? +message.invalid.install.dir=Warning: Invalid install directory {0}. Install directory should be a relative sub-path under the default installation location such as "Program Files". Defaulting to application name "{1}". + diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_ja.properties b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_ja.properties new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_ja.properties @@ -0,0 +1,90 @@ +# +# Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# +# + +app.bundler.name=Windows Application Image +app.bundler.description=A Directory based image of a windows Application with an optionally co-bundled JRE. Used as a base for the Installer bundlers +exe.bundler.name=EXE Installer +exe.bundler.description=Microsoft Windows EXE Installer, via InnoIDE. +msi.bundler.name=MSI Installer +msi.bundler.description=Microsoft Windows MSI Installer, via WiX. + +param.menu-group.default=Unknown + +resource.application-icon=application icon +resource.executable-properties-template=Template for creating executable properties file. +resource.inno-setup-project-file=Inno Setup project file +resource.setup-icon=setup dialog icon +resource.post-install-script=script to run after application image is populated +resource.wix-config-file=WiX config file + +error.parameters-null=Parameters map is null. +error.parameters-null.advice=Pass in a non-null parameters map. +error.no-windows-resources=This copy of the JDK does not support Windows. +error.no-windows-resources.advice=Please use the Oracle JDK for Windows. +error.cannot-find-launcher=Cannot find cfg file in predefined app image directory {0}. +error.cannot-create-output-dir=Output directory {0} cannot be created. +error.cannot-write-to-output-dir=Output directory {0} is not writable. +error.iscc-not-found=Can not find Inno Setup Compiler (iscc.exe). +error.iscc-not-found.advice=Download Inno Setup 5 or later from http://www.jrsoftware.org and add it to the PATH. +error.copyright-is-too-long=The copyright string is too long for InnoSetup. +error.copyright-is-too-long.advice=Provide a copyright string shorter than 100 characters. +error.too-many-content-types-for-file-association=More than one MIME types was specified for File Association number {0}. +error.too-many-content-types-for-file-association.advice=Specify one and only one MIME type for each file association. +error.no-wix-tools=Can not find WiX tools (light.exe, candle.exe). +error.no-wix-tools.advice=Download WiX 3.0 or later from http://wix.sf.net and add it to the PATH. +error.version-string-wrong-format=Version string is not compatible with MSI rules [{0}]. +error.version-string-wrong-format.advice=Set the bundler argument "{0}" according to these rules: http://msdn.microsoft.com/en-us/library/aa370859%28v\=VS.85%29.aspx . +error.version-string-major-out-of-range=Major version must be in the range [0, 255]. +error.version-string-build-out-of-range=Build part of version must be in the range [0, 65535]. +error.version-string-minor-out-of-range=Minor version must be in the range [0, 255]. +error.version-string-part-not-number=Failed to convert version component to int. +error.cannot-walk-directory=Can not walk [{0}] - it is not a valid directory. +error.version-swap=Failed to update version information for {0}. + +message.result-dir=Result application bundle: {0}. +message.icon-not-ico=The specified icon "{0}" is not an ICO file and will not be used. The default icon will be used in it's place. +message.multiple-launchers=Multiple launchers found in predefined app-image. {0} will be used as primary launcher. +message.potential.windows.defender.issue=Warning: Windows Defender may prevent jpackage from functioning. If there is an issue, it can be addressed by either disabling realtime monitoring, or adding an exclusion for the directory "{0}". +message.tool-wrong-version=Detected [{0}] version {1} but version {2} is required. +message.outputting-to-location=Generating EXE for installer to: {0}. +message.output-location=Installer (.exe) saved to: {0} +message.tool-version=Detected [{0}] version [{1}]. +message.one-shortcut-required=At least one type of shortcut is required. Enabling menu shortcut. +message.running-wsh-script=Running WSH script on application image [{0}]. +message.iscc-file-string=InnoSetup compiler set to {0}. +message.creating-association-with-null-extension=Creating association with null extension. +message.wrong-tool-version=Detected [{0}] version {1} but version {2} is required. +message.version-string-too-many-components=Version sting may have up to 3 components - major.minor.build . +message.truncating.id=Truncating Application ID to 126 chars for Inno Setup. +message.use-wix36-features=WiX 3.6 detected. Enabling advanced cleanup action. +message.generated-product-guid=Generated product GUID: {0}. +message.preparing-msi-config=Preparing MSI config: {0}. +message.generating-msi=Generating MSI: {0}. +message.light-file-string=WiX light tool set to {0}. +message.candle-file-string=WiX candle tool set to {0}. +message.install.dir.exist=The folder [APPLICATIONFOLDER] already exist. Whould you like to install to that folder anyway? +message.invalid.install.dir=Warning: Invalid install directory {0}. Install directory should be a relative sub-path under the default installation location such as "Program Files". Defaulting to application name "{1}". + diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_zh_CN.properties b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_zh_CN.properties new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_zh_CN.properties @@ -0,0 +1,90 @@ +# +# Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# +# + +app.bundler.name=Windows Application Image +app.bundler.description=A Directory based image of a windows Application with an optionally co-bundled JRE. Used as a base for the Installer bundlers +exe.bundler.name=EXE Installer +exe.bundler.description=Microsoft Windows EXE Installer, via InnoIDE. +msi.bundler.name=MSI Installer +msi.bundler.description=Microsoft Windows MSI Installer, via WiX. + +param.menu-group.default=Unknown + +resource.application-icon=application icon +resource.executable-properties-template=Template for creating executable properties file. +resource.inno-setup-project-file=Inno Setup project file +resource.setup-icon=setup dialog icon +resource.post-install-script=script to run after application image is populated +resource.wix-config-file=WiX config file + +error.parameters-null=Parameters map is null. +error.parameters-null.advice=Pass in a non-null parameters map. +error.no-windows-resources=This copy of the JDK does not support Windows. +error.no-windows-resources.advice=Please use the Oracle JDK for Windows. +error.cannot-find-launcher=Cannot find cfg file in predefined app image directory {0}. +error.cannot-create-output-dir=Output directory {0} cannot be created. +error.cannot-write-to-output-dir=Output directory {0} is not writable. +error.iscc-not-found=Can not find Inno Setup Compiler (iscc.exe). +error.iscc-not-found.advice=Download Inno Setup 5 or later from http://www.jrsoftware.org and add it to the PATH. +error.copyright-is-too-long=The copyright string is too long for InnoSetup. +error.copyright-is-too-long.advice=Provide a copyright string shorter than 100 characters. +error.too-many-content-types-for-file-association=More than one MIME types was specified for File Association number {0}. +error.too-many-content-types-for-file-association.advice=Specify one and only one MIME type for each file association. +error.no-wix-tools=Can not find WiX tools (light.exe, candle.exe). +error.no-wix-tools.advice=Download WiX 3.0 or later from http://wix.sf.net and add it to the PATH. +error.version-string-wrong-format=Version string is not compatible with MSI rules [{0}]. +error.version-string-wrong-format.advice=Set the bundler argument "{0}" according to these rules: http://msdn.microsoft.com/en-us/library/aa370859%28v\=VS.85%29.aspx . +error.version-string-major-out-of-range=Major version must be in the range [0, 255]. +error.version-string-build-out-of-range=Build part of version must be in the range [0, 65535]. +error.version-string-minor-out-of-range=Minor version must be in the range [0, 255]. +error.version-string-part-not-number=Failed to convert version component to int. +error.cannot-walk-directory=Can not walk [{0}] - it is not a valid directory. +error.version-swap=Failed to update version information for {0}. + +message.result-dir=Result application bundle: {0}. +message.icon-not-ico=The specified icon "{0}" is not an ICO file and will not be used. The default icon will be used in it's place. +message.multiple-launchers=Multiple launchers found in predefined app-image. {0} will be used as primary launcher. +message.potential.windows.defender.issue=Warning: Windows Defender may prevent jpackage from functioning. If there is an issue, it can be addressed by either disabling realtime monitoring, or adding an exclusion for the directory "{0}". +message.tool-wrong-version=Detected [{0}] version {1} but version {2} is required. +message.outputting-to-location=Generating EXE for installer to: {0}. +message.output-location=Installer (.exe) saved to: {0} +message.tool-version=Detected [{0}] version [{1}]. +message.one-shortcut-required=At least one type of shortcut is required. Enabling menu shortcut. +message.running-wsh-script=Running WSH script on application image [{0}]. +message.iscc-file-string=InnoSetup compiler set to {0}. +message.creating-association-with-null-extension=Creating association with null extension. +message.wrong-tool-version=Detected [{0}] version {1} but version {2} is required. +message.version-string-too-many-components=Version sting may have up to 3 components - major.minor.build . +message.truncating.id=Truncating Application ID to 126 chars for Inno Setup. +message.use-wix36-features=WiX 3.6 detected. Enabling advanced cleanup action. +message.generated-product-guid=Generated product GUID: {0}. +message.preparing-msi-config=Preparing MSI config: {0}. +message.generating-msi=Generating MSI: {0}. +message.light-file-string=WiX light tool set to {0}. +message.candle-file-string=WiX candle tool set to {0}. +message.install.dir.exist=The folder [APPLICATIONFOLDER] already exist. Whould you like to install to that folder anyway? +message.invalid.install.dir=Warning: Invalid install directory {0}. Install directory should be a relative sub-path under the default installation location such as "Program Files". Defaulting to application name "{1}". + diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/icon_inno_setup.bmp b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/icon_inno_setup.bmp new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..d1a545d6f983f00f2b5ae556ee980a61335932d3 GIT binary patch literal 4326 zc%1E)eN0nl6o(HjuK01=(IADlvn{1yovpR&1VI9=@-1isTy2rGd?;ek>Hx80jI1(a z9C6MXvzTnUiNvX6>ikG_Tbwh-n3*|aoG~VD#yDe)F~&G!{KK91_V)G`u)1W~Uwe|$ z(A=lzoZoxXd*)XE`8sgqBmx-mJr`g3_)35z?(;*F0F)EMFZ>XO@y+3w2OBqTglpHX zL2GL(96fpr>grh7wd)J$?(T*sPo6+=aV2;>Ubt}K0<2l{4&1wU4|;pwhb>z^fy0Nt zh0~{hg1dL`a(;dN5~!)Eg_SE;!OfdDVf*&&uz&vnc>er31Oh!UckVo>s;Yv^moLME z2M?gDYb`W3HbY-uADlRG0?wa54?A}30H3cN1_m~R%jJSww{F4Sy+{T+GcY)~2kzXt1MAiWpscJ67A;x~-+%uDl$V#ojT<+>>urP$8#cnGO`EXK zZpRgc-1VqaI_>C>l}ZzDW@{1@!rJqY{u?SqF8A7bvsm}dhVI53R4ci??K7 zGaJPFWfPvqKA1n>iT!ynk07+PwBk7~#rxnM_HY@`{ub7+!~5zwW_%aV_B+h}Am;Y~ z@0T-}=>XmXS1_Lwn8^a1`2fyaiv6C(bMD8P58{kBakdSZ%M+ZTn7fB*cK`G5@wEso z!zkmzFk1f7sHat`aym7dt~h8kojEF{HpXN88be^FD=`>{MAyuKLE~pco?T+dOz%xo zte+Z(^@_CK^h|@qE_n9N|Z{6mb^DWJ!TpHKYnpaN{YwhG4fyx<0x|| zlf!(ufL5Gh(xuI$o`1mWeY>qKB}}hDBC%^J<|ITrZEaCD@44L#D3%x}DO}Wh4Xc^j zcu}pUe`^sVEtiq7B$pvgxh$u-^;mJxXkDL3y*jUVj){ua*8&p8Y#{;`vpu&!HfPSz z+N7d&Zb~htI?p;aNIk4=qG<8TCOnh=v~s;(FVj~jmL?YFD_fdWS(A;$Vb6^hkM^3l zCL_@Zu_R1XPT;k%JX4~%YDH=|(~w&4<2{;4ro$b_GnrDC2Gt(hL^D4g6G-I`!9BL; zbeaSbh=?S0tTxJvntF@BLQ3bWQ)IpwF6WNtMN8^ELN4RP8m6p|^(IUZnbx9!CLdXt z{*DoI@g<0~QQibjMQgfQJYUP&l$1uMAYzT_g^MmE_Wr4|)kddm{W8OQN*f;BDvMU3 zAJc1Yj+(($v{_+5+F_5yq8QUFqws1-KU7~|?^cHd&F%h}RK3_k@}?z;hO?Menq}d^ zECl~lBV&6KLQ2asGU%M1GFy33u1c%w`Wl*?+Doaj%Go`Jq;w>S?P9l-Z zU*z#5WENQQ8>TzQX$_oIl;4^(QT|ddOj>S+TtoWhBN^q*iXknjovqOlrCP0C6ho{h zTwfO^fuytS>x5|4lo^y4c)U;-qSROzmUZJv6gnJgb<{;-J&{8xCH2|acqZog6t!C9 zj`Sjm6j2kxvMm0tDRhWKs7Z1nk1E+5O9)|fK3~Ax>Cow9QIUH5b1dqy0;^Xm zEQIkPA8nW+7U_>W|J)S3sFsSz&7{l6p^JzJy(#(UH1CCt>D4@Cxp)jc1%xAvN$hV5U*Vv*|S#Q zIw6hKc+c9DXNwUlarJOV_Bu0e7adYwtL2C+fLgvuU6{%(UhlvjVUH}trr&moG{H-z u#~_H@4%aW*TfcbqJ*z)vPcCeDHET=e$h~|;=;c;uS=HAgy?@Am?)?onEZgw_ diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/javalogo_white_48.ico b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/javalogo_white_48.ico new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1893f0bc033113aa50c12749b03143a7f6fad121 GIT binary patch literal 3774 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 diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/template.iss b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/template.iss new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/template.iss @@ -0,0 +1,74 @@ +;This file will be executed next to the application bundle image +;I.e. current directory will contain folder INSTALLER_NAME with application files +[Setup] +AppId=PRODUCT_APP_IDENTIFIER +AppName=INSTALLER_NAME +AppVersion=APPLICATION_VERSION +AppVerName=INSTALLER_NAME APPLICATION_VERSION +AppPublisher=APPLICATION_VENDOR +AppComments=APPLICATION_DESCRIPTION +AppCopyright=APPLICATION_COPYRIGHT +VersionInfoVersion=APPLICATION_VERSION +VersionInfoDescription=APPLICATION_DESCRIPTION +DefaultDirName=APPLICATION_INSTALL_ROOT\INSTALL_DIR +DisableStartupPrompt=Yes +DisableDirPage=DISABLE_DIR_PAGE +DisableProgramGroupPage=Yes +DisableReadyPage=Yes +DisableFinishedPage=Yes +DisableWelcomePage=Yes +DefaultGroupName=APPLICATION_GROUP +;Optional License +LicenseFile=APPLICATION_LICENSE_FILE +;WinXP or above +MinVersion=0,5.1 +OutputBaseFilename=INSTALLER_FILE_NAME +Compression=lzma +SolidCompression=yes +PrivilegesRequired=APPLICATION_INSTALL_PRIVILEGE +SetupIconFile=INSTALLER_NAME\LAUNCHER_NAME.ico +UninstallDisplayIcon={app}\LAUNCHER_NAME.ico +UninstallDisplayName=INSTALLER_NAME +WizardImageStretch=No +WizardSmallImageFile=INSTALLER_NAME-setup-icon.bmp +ArchitecturesInstallIn64BitMode=ARCHITECTURE_BIT_MODE +FILE_ASSOCIATIONS + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Files] +Source: "INSTALLER_NAME\LAUNCHER_NAME.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "INSTALLER_NAME\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs + +[Icons] +Name: "{group}\INSTALLER_NAME"; Filename: "{app}\LAUNCHER_NAME.exe"; IconFilename: "{app}\LAUNCHER_NAME.ico"; Check: APPLICATION_MENU_SHORTCUT() +Name: "{commondesktop}\INSTALLER_NAME"; Filename: "{app}\LAUNCHER_NAME.exe"; IconFilename: "{app}\LAUNCHER_NAME.ico"; Check: APPLICATION_DESKTOP_SHORTCUT() +ADD_LAUNCHERS + +[Run] +Filename: "{app}\RUN_FILENAME.exe"; Parameters: "-Xappcds:generatecache"; Check: APPLICATION_APP_CDS_INSTALL() +Filename: "{app}\RUN_FILENAME.exe"; Description: "{cm:LaunchProgram,INSTALLER_NAME}"; Flags: nowait postinstall skipifsilent; Check: APPLICATION_NOT_SERVICE() +Filename: "{app}\RUN_FILENAME.exe"; Parameters: "-install -svcName ""INSTALLER_NAME"" -svcDesc ""APPLICATION_DESCRIPTION"" -mainExe ""APPLICATION_LAUNCHER_FILENAME"" START_ON_INSTALL RUN_AT_STARTUP"; Check: APPLICATION_SERVICE() + +[UninstallRun] +Filename: "{app}\RUN_FILENAME.exe "; Parameters: "-uninstall -svcName INSTALLER_NAME STOP_ON_UNINSTALL"; Check: APPLICATION_SERVICE() + +[Code] +function returnTrue(): Boolean; +begin + Result := True; +end; + +function returnFalse(): Boolean; +begin + Result := False; +end; + +function InitializeSetup(): Boolean; +begin +// Possible future improvements: +// if version less or same => just launch app +// if upgrade => check if same app is running and wait for it to exit + Result := True; +end; diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/template.jre.iss b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/template.jre.iss new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/template.jre.iss @@ -0,0 +1,57 @@ +;This file will be executed next to the application bundle image +;I.e. current directory will contain folder INSTALLER_NAME with application files +[Setup] +AppId=PRODUCT_APP_IDENTIFIER +AppName=INSTALLER_NAME +AppVersion=APPLICATION_VERSION +AppVerName=INSTALLER_NAME APPLICATION_VERSION +AppPublisher=APPLICATION_VENDOR +AppComments=APPLICATION_DESCRIPTION +AppCopyright=APPLICATION_COPYRIGHT +VersionInfoVersion=APPLICATION_VERSION +VersionInfoDescription=APPLICATION_DESCRIPTION +DefaultDirName=APPLICATION_INSTALL_ROOT\INSTALLER_NAME +DisableStartupPrompt=Yes +DisableDirPage=DISABLE_DIR_PAGE +DisableProgramGroupPage=Yes +DisableReadyPage=Yes +DisableFinishedPage=Yes +DisableWelcomePage=Yes +DefaultGroupName=APPLICATION_GROUP +;Optional License +LicenseFile=APPLICATION_LICENSE_FILE +;WinXP or above +MinVersion=0,5.1 +OutputBaseFilename=INSTALLER_FILE_NAME +Compression=lzma +SolidCompression=yes +PrivilegesRequired=APPLICATION_INSTALL_PRIVILEGE +SetupIconFile= +UninstallDisplayIcon= +UninstallDisplayName=INSTALLER_NAME +WizardImageStretch=No +WizardSmallImageFile=INSTALLER_NAME-setup-icon.bmp +ArchitecturesInstallIn64BitMode=ARCHITECTURE_BIT_MODE +FILE_ASSOCIATIONS + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Files] +Source: "APPLICATION_IMAGE\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs + +[Code] +function returnTrue(): Boolean; +begin + Result := True; +end; + +function returnFalse(): Boolean; +begin + Result := False; +end; + +function InitializeSetup(): Boolean; +begin + Result := True; +end; diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/template.jre.wxs b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/template.jre.wxs new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/template.jre.wxs @@ -0,0 +1,46 @@ + + + + + +UPGRADE_BLOCK + + + + + + + + + + WIX36_ONLY_START + + WIX36_ONLY_END + + + +UI_BLOCK + + diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/template.wxs b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/template.wxs new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/template.wxs @@ -0,0 +1,53 @@ + + + + + + UPGRADE_BLOCK + + + + + + + + + + WIX36_ONLY_START + + WIX36_ONLY_END + + + + CA_BLOCK + + INVALID_INSTALL_DIR_DLG_BLOCK + UI_BLOCK + + + + ADD_LAUNCHER_ICONS + + diff --git a/src/jdk.jpackage/windows/classes/module-info.java.extra b/src/jdk.jpackage/windows/classes/module-info.java.extra new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/classes/module-info.java.extra @@ -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.jpackage.internal.Bundler with + jdk.jpackage.internal.WinAppBundler, + jdk.jpackage.internal.WinExeBundler, + jdk.jpackage.internal.WinMsiBundler; + diff --git a/src/jdk.jpackage/windows/native/jpackageapplauncher/WinLauncher.cpp b/src/jdk.jpackage/windows/native/jpackageapplauncher/WinLauncher.cpp new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/native/jpackageapplauncher/WinLauncher.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include +#include +#include +#include + +#define JPACKAGE_LIBRARY TEXT("applauncher.dll") + +typedef bool (*start_launcher)(int argc, TCHAR* argv[]); +typedef void (*stop_launcher)(); + +std::wstring GetTitle() { + std::wstring result; + wchar_t buffer[MAX_PATH]; + GetModuleFileName(NULL, buffer, MAX_PATH - 1); + buffer[MAX_PATH - 1] = '\0'; + result = buffer; + size_t slash = result.find_last_of('\\'); + + if (slash != std::wstring::npos) + result = result.substr(slash + 1, result.size() - slash - 1); + + return result; +} + +#ifdef LAUNCHERC +int main(int argc0, char *argv0[]) { +#else // LAUNCHERC +int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, + LPTSTR lpCmdLine, int nCmdShow) { +#endif // LAUNCHERC + int result = 1; + TCHAR **argv; + int argc; + + // [RT-31061] otherwise UI can be left in back of other windows. + ::AllowSetForegroundWindow(ASFW_ANY); + + ::setlocale(LC_ALL, "en_US.utf8"); + argv = CommandLineToArgvW(GetCommandLine(), &argc); + + HMODULE library = ::LoadLibrary(JPACKAGE_LIBRARY); + + if (library == NULL) { + std::wstring title = GetTitle(); + std::wstring description = std::wstring(JPACKAGE_LIBRARY) + + std::wstring(TEXT(" not found.")); + MessageBox(NULL, description.data(), + title.data(), MB_ICONERROR | MB_OK); + } + else { + start_launcher start = + (start_launcher)GetProcAddress(library, "start_launcher"); + stop_launcher stop = + (stop_launcher)GetProcAddress(library, "stop_launcher"); + + if (start != NULL && stop != NULL) { + if (start(argc, argv) == true) { + result = 0; + stop(); + } + } + + ::FreeLibrary(library); + } + + if (argv != NULL) { + LocalFree(argv); + } + + return result; +} + diff --git a/src/jdk.jpackage/windows/native/libapplauncher/DllMain.cpp b/src/jdk.jpackage/windows/native/libapplauncher/DllMain.cpp new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/native/libapplauncher/DllMain.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include + +extern "C" { + + BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, + LPVOID lpvReserved) { + return true; + } +} \ No newline at end of file diff --git a/src/jdk.jpackage/windows/native/libapplauncher/FileAttribute.h b/src/jdk.jpackage/windows/native/libapplauncher/FileAttribute.h new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/native/libapplauncher/FileAttribute.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef FILEATTRIBUTE_H +#define FILEATTRIBUTE_H + +enum FileAttribute { + faArchive = FILE_ATTRIBUTE_ARCHIVE, + faCompressed = FILE_ATTRIBUTE_COMPRESSED, + faDevice = FILE_ATTRIBUTE_DEVICE, + faDirectory = FILE_ATTRIBUTE_DIRECTORY, + faEncrypted = FILE_ATTRIBUTE_ENCRYPTED, + faHidden = FILE_ATTRIBUTE_HIDDEN, + //faIntegrityStream = FILE_ATTRIBUTE_INTEGRITY_STREAM, + faNormal = FILE_ATTRIBUTE_NORMAL, + faNotContentIndexed = FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, + //faNoScrubData = FILE_ATTRIBUTE_NO_SCRUB_DATA, + faOffline = FILE_ATTRIBUTE_OFFLINE, + faSystem = FILE_ATTRIBUTE_SYSTEM, + faSymbolicLink = FILE_ATTRIBUTE_REPARSE_POINT, + faSparceFile = FILE_ATTRIBUTE_SPARSE_FILE, + faReadOnly = FILE_ATTRIBUTE_READONLY, + faTemporary = FILE_ATTRIBUTE_TEMPORARY, + faVirtual = FILE_ATTRIBUTE_VIRTUAL +}; + +#endif // FILEATTRIBUTE_H + diff --git a/src/jdk.jpackage/windows/native/libapplauncher/FilePath.cpp b/src/jdk.jpackage/windows/native/libapplauncher/FilePath.cpp new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/native/libapplauncher/FilePath.cpp @@ -0,0 +1,473 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "FilePath.h" + +#include +#include +#include + +bool FilePath::FileExists(const TString FileName) { + bool result = false; + WIN32_FIND_DATA FindFileData; + TString fileName = FixPathForPlatform(FileName); + HANDLE handle = FindFirstFile(fileName.data(), &FindFileData); + + if (handle != INVALID_HANDLE_VALUE) { + if (FILE_ATTRIBUTE_DIRECTORY & FindFileData.dwFileAttributes) { + result = true; + } + else { + result = true; + } + + FindClose(handle); + } + return result; +} + +bool FilePath::DirectoryExists(const TString DirectoryName) { + bool result = false; + WIN32_FIND_DATA FindFileData; + TString directoryName = FixPathForPlatform(DirectoryName); + HANDLE handle = FindFirstFile(directoryName.data(), &FindFileData); + + if (handle != INVALID_HANDLE_VALUE) { + if (FILE_ATTRIBUTE_DIRECTORY & FindFileData.dwFileAttributes) { + result = true; + } + + FindClose(handle); + } + return result; +} + +std::string GetLastErrorAsString() { + // Get the error message, if any. + DWORD errorMessageID = ::GetLastError(); + + if (errorMessageID == 0) { + return "No error message has been recorded"; + } + + LPSTR messageBuffer = NULL; + size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, + SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL); + + std::string message(messageBuffer, size); + + // Free the buffer. + LocalFree(messageBuffer); + + return message; +} + +bool FilePath::DeleteFile(const TString FileName) { + bool result = false; + + if (FileExists(FileName) == true) { + TString lFileName = FixPathForPlatform(FileName); + FileAttributes attributes(lFileName); + + if (attributes.Contains(faReadOnly) == true) { + attributes.Remove(faReadOnly); + } + + result = ::DeleteFile(lFileName.data()) == TRUE; + } + + return result; +} + +bool FilePath::DeleteDirectory(const TString DirectoryName) { + bool result = false; + + if (DirectoryExists(DirectoryName) == true) { + SHFILEOPSTRUCTW fos = {0}; + TString directoryName = FixPathForPlatform(DirectoryName); + DynamicBuffer lDirectoryName(directoryName.size() + 2); + if (lDirectoryName.GetData() == NULL) { + return false; + } + memcpy(lDirectoryName.GetData(), directoryName.data(), (directoryName.size() + 2) * sizeof(TCHAR)); + lDirectoryName[directoryName.size() + 1] = NULL; + // Double null terminate for SHFileOperation. + + // Delete the folder and everything inside. + fos.wFunc = FO_DELETE; + fos.pFrom = lDirectoryName.GetData(); + fos.fFlags = FOF_NO_UI; + result = SHFileOperation(&fos) == 0; + } + + return result; +} + +TString FilePath::IncludeTrailingSeparator(const TString value) { + TString result = value; + + if (value.size() > 0) { + TString::iterator i = result.end(); + i--; + + if (*i != TRAILING_PATHSEPARATOR) { + result += TRAILING_PATHSEPARATOR; + } + } + + return result; +} + +TString FilePath::IncludeTrailingSeparator(const char* value) { + TString lvalue = PlatformString(value).toString(); + return IncludeTrailingSeparator(lvalue); +} + +TString FilePath::IncludeTrailingSeparator(const wchar_t* value) { + TString lvalue = PlatformString(value).toString(); + return IncludeTrailingSeparator(lvalue); +} + +TString FilePath::ExtractFilePath(TString Path) { + TString result; + size_t slash = Path.find_last_of(TRAILING_PATHSEPARATOR); + if (slash != TString::npos) + result = Path.substr(0, slash); + return result; +} + +TString FilePath::ExtractFileExt(TString Path) { + TString result; + size_t dot = Path.find_last_of('.'); + + if (dot != TString::npos) { + result = Path.substr(dot, Path.size() - dot); + } + + return result; +} + +TString FilePath::ExtractFileName(TString Path) { + TString result; + + size_t slash = Path.find_last_of(TRAILING_PATHSEPARATOR); + if (slash != TString::npos) + result = Path.substr(slash + 1, Path.size() - slash - 1); + + return result; +} + +TString FilePath::ChangeFileExt(TString Path, TString Extension) { + TString result; + size_t dot = Path.find_last_of('.'); + + if (dot != TString::npos) { + result = Path.substr(0, dot) + Extension; + } + + if (result.empty() == true) { + result = Path; + } + + return result; +} + +TString FilePath::FixPathForPlatform(TString Path) { + TString result = Path; + std::replace(result.begin(), result.end(), + BAD_TRAILING_PATHSEPARATOR, TRAILING_PATHSEPARATOR); + // The maximum path that does not require long path prefix. On Windows the + // maximum path is 260 minus 1 (NUL) but for directories it is 260 minus + // 12 minus 1 (to allow for the creation of a 8.3 file in the directory). + const int maxPath = 247; + if (result.length() > maxPath && + result.find(_T("\\\\?\\")) == TString::npos && + result.find(_T("\\\\?\\UNC")) == TString::npos) { + const TString prefix(_T("\\\\")); + if (!result.compare(0, prefix.size(), prefix)) { + // UNC path, converting to UNC path in long notation + result = _T("\\\\?\\UNC") + result.substr(1, result.length()); + } else { + // converting to non-UNC path in long notation + result = _T("\\\\?\\") + result; + } + } + return result; +} + +TString FilePath::FixPathSeparatorForPlatform(TString Path) { + TString result = Path; + std::replace(result.begin(), result.end(), + BAD_PATH_SEPARATOR, PATH_SEPARATOR); + return result; +} + +TString FilePath::PathSeparator() { + TString result; + result = PATH_SEPARATOR; + return result; +} + +bool FilePath::CreateDirectory(TString Path, bool ownerOnly) { + bool result = false; + + std::list paths; + TString lpath = Path; + + while (lpath.empty() == false && DirectoryExists(lpath) == false) { + paths.push_front(lpath); + lpath = ExtractFilePath(lpath); + } + + for (std::list::iterator iterator = paths.begin(); + iterator != paths.end(); iterator++) { + lpath = *iterator; + + if (_wmkdir(lpath.data()) == 0) { + result = true; + } else { + result = false; + break; + } + } + + return result; +} + +void FilePath::ChangePermissions(TString FileName, bool ownerOnly) { +} + +#include + +FileAttributes::FileAttributes(const TString FileName, bool FollowLink) { + FFileName = FileName; + FFollowLink = FollowLink; + ReadAttributes(); +} + +bool FileAttributes::WriteAttributes() { + bool result = false; + + DWORD attributes = 0; + + for (std::vector::const_iterator iterator = + FAttributes.begin(); + iterator != FAttributes.end(); iterator++) { + switch (*iterator) { + case faArchive: { + attributes = attributes & FILE_ATTRIBUTE_ARCHIVE; + break; + } + case faCompressed: { + attributes = attributes & FILE_ATTRIBUTE_COMPRESSED; + break; + } + case faDevice: { + attributes = attributes & FILE_ATTRIBUTE_DEVICE; + break; + } + case faDirectory: { + attributes = attributes & FILE_ATTRIBUTE_DIRECTORY; + break; + } + case faEncrypted: { + attributes = attributes & FILE_ATTRIBUTE_ENCRYPTED; + break; + } + case faHidden: { + attributes = attributes & FILE_ATTRIBUTE_HIDDEN; + break; + } + case faNormal: { + attributes = attributes & FILE_ATTRIBUTE_NORMAL; + break; + } + case faNotContentIndexed: { + attributes = attributes & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; + break; + } + case faOffline: { + attributes = attributes & FILE_ATTRIBUTE_OFFLINE; + break; + } + case faSystem: { + attributes = attributes & FILE_ATTRIBUTE_SYSTEM; + break; + } + case faSymbolicLink: { + attributes = attributes & FILE_ATTRIBUTE_REPARSE_POINT; + break; + } + case faSparceFile: { + attributes = attributes & FILE_ATTRIBUTE_SPARSE_FILE; + break; + } + case faReadOnly: { + attributes = attributes & FILE_ATTRIBUTE_READONLY; + break; + } + case faTemporary: { + attributes = attributes & FILE_ATTRIBUTE_TEMPORARY; + break; + } + case faVirtual: { + attributes = attributes & FILE_ATTRIBUTE_VIRTUAL; + break; + } + } + } + + if (::SetFileAttributes(FFileName.data(), attributes) != 0) { + result = true; + } + + return result; +} + +#define S_ISRUSR(m) (((m) & S_IRWXU) == S_IRUSR) +#define S_ISWUSR(m) (((m) & S_IRWXU) == S_IWUSR) +#define S_ISXUSR(m) (((m) & S_IRWXU) == S_IXUSR) + +#define S_ISRGRP(m) (((m) & S_IRWXG) == S_IRGRP) +#define S_ISWGRP(m) (((m) & S_IRWXG) == S_IWGRP) +#define S_ISXGRP(m) (((m) & S_IRWXG) == S_IXGRP) + +#define S_ISROTH(m) (((m) & S_IRWXO) == S_IROTH) +#define S_ISWOTH(m) (((m) & S_IRWXO) == S_IWOTH) +#define S_ISXOTH(m) (((m) & S_IRWXO) == S_IXOTH) + +bool FileAttributes::ReadAttributes() { + bool result = false; + + DWORD attributes = ::GetFileAttributes(FFileName.data()); + + if (attributes != INVALID_FILE_ATTRIBUTES) { + result = true; + + if (attributes | FILE_ATTRIBUTE_ARCHIVE) { + FAttributes.push_back(faArchive); + } + if (attributes | FILE_ATTRIBUTE_COMPRESSED) { + FAttributes.push_back(faCompressed); + } + if (attributes | FILE_ATTRIBUTE_DEVICE) { + FAttributes.push_back(faDevice); + } + if (attributes | FILE_ATTRIBUTE_DIRECTORY) { + FAttributes.push_back(faDirectory); + } + if (attributes | FILE_ATTRIBUTE_ENCRYPTED) { + FAttributes.push_back(faEncrypted); + } + if (attributes | FILE_ATTRIBUTE_HIDDEN) { + FAttributes.push_back(faHidden); + } + // if (attributes | FILE_ATTRIBUTE_INTEGRITY_STREAM) { + // FAttributes.push_back(faIntegrityStream); + // } + if (attributes | FILE_ATTRIBUTE_NORMAL) { + FAttributes.push_back(faNormal); + } + if (attributes | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED) { + FAttributes.push_back(faNotContentIndexed); + } + // if (attributes | FILE_ATTRIBUTE_NO_SCRUB_DATA) { + // FAttributes.push_back(faNoScrubData); + // } + if (attributes | FILE_ATTRIBUTE_SYSTEM) { + FAttributes.push_back(faSystem); + } + if (attributes | FILE_ATTRIBUTE_OFFLINE) { + FAttributes.push_back(faOffline); + } + if (attributes | FILE_ATTRIBUTE_REPARSE_POINT) { + FAttributes.push_back(faSymbolicLink); + } + if (attributes | FILE_ATTRIBUTE_SPARSE_FILE) { + FAttributes.push_back(faSparceFile); + } + if (attributes | FILE_ATTRIBUTE_READONLY ) { + FAttributes.push_back(faReadOnly); + } + if (attributes | FILE_ATTRIBUTE_TEMPORARY) { + FAttributes.push_back(faTemporary); + } + if (attributes | FILE_ATTRIBUTE_VIRTUAL) { + FAttributes.push_back(faVirtual); + } + } + + return result; +} + +bool FileAttributes::Valid(const FileAttribute Value) { + bool result = false; + + switch (Value) { + case faHidden: + case faReadOnly: { + result = true; + break; + } + default: + break; + } + + return result; +} + +void FileAttributes::Append(FileAttribute Value) { + if (Valid(Value) == true) { + FAttributes.push_back(Value); + WriteAttributes(); + } +} + +bool FileAttributes::Contains(FileAttribute Value) { + bool result = false; + + std::vector::const_iterator iterator = + std::find(FAttributes.begin(), FAttributes.end(), Value); + + if (iterator != FAttributes.end()) { + result = true; + } + + return result; +} + +void FileAttributes::Remove(FileAttribute Value) { + if (Valid(Value) == true) { + std::vector::iterator iterator = + std::find(FAttributes.begin(), FAttributes.end(), Value); + + if (iterator != FAttributes.end()) { + FAttributes.erase(iterator); + WriteAttributes(); + } + } +} diff --git a/src/jdk.jpackage/windows/native/libapplauncher/PlatformDefs.h b/src/jdk.jpackage/windows/native/libapplauncher/PlatformDefs.h new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/native/libapplauncher/PlatformDefs.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef PLATFORM_DEFS_H +#define PLATFORM_DEFS_H + +// Define Windows compatibility requirements XP or later +#define WINVER 0x0600 +#define _WIN32_WINNT 0x0600 + +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +#ifndef WINDOWS +#define WINDOWS +#endif + +typedef std::wstring TString; +#define StringLength wcslen + +#define TRAILING_PATHSEPARATOR '\\' +#define BAD_TRAILING_PATHSEPARATOR '/' +#define PATH_SEPARATOR ';' +#define BAD_PATH_SEPARATOR ':' + +typedef ULONGLONG TPlatformNumber; +typedef DWORD TProcessID; + +typedef void* Module; +typedef void* Procedure; + +#endif // PLATFORM_DEFS_H diff --git a/src/jdk.jpackage/windows/native/libapplauncher/WindowsPlatform.cpp b/src/jdk.jpackage/windows/native/libapplauncher/WindowsPlatform.cpp new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/native/libapplauncher/WindowsPlatform.cpp @@ -0,0 +1,761 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "Platform.h" + +#include "JavaVirtualMachine.h" +#include "WindowsPlatform.h" +#include "Package.h" +#include "Helpers.h" +#include "PlatformString.h" +#include "Macros.h" + +#include +#include +#include +#include +#include +#include + +using namespace std; + +#define WINDOWS_JPACKAGE_TMP_DIR \ + L"\\AppData\\Local\\Java\\JPackage\\tmp" + +class Registry { +private: + HKEY FKey; + HKEY FOpenKey; + bool FOpen; + +public: + + Registry(HKEY Key) { + FOpen = false; + FKey = Key; + } + + ~Registry() { + Close(); + } + + void Close() { + if (FOpen == true) { + RegCloseKey(FOpenKey); + } + } + + bool Open(TString SubKey) { + bool result = false; + Close(); + + if (RegOpenKeyEx(FKey, SubKey.data(), 0, KEY_READ, &FOpenKey) == + ERROR_SUCCESS) { + result = true; + } + + return result; + } + + std::list GetKeys() { + std::list result; + DWORD count; + + if (RegQueryInfoKey(FOpenKey, NULL, NULL, NULL, NULL, NULL, NULL, + &count, NULL, NULL, NULL, NULL) == ERROR_SUCCESS) { + + DWORD length = 255; + DynamicBuffer buffer(length); + if (buffer.GetData() == NULL) { + return result; + } + + for (unsigned int index = 0; index < count; index++) { + buffer.Zero(); + DWORD status = RegEnumValue(FOpenKey, index, buffer.GetData(), + &length, NULL, NULL, NULL, NULL); + + while (status == ERROR_MORE_DATA) { + length = length * 2; + if (!buffer.Resize(length)) { + return result; + } + status = RegEnumValue(FOpenKey, index, buffer.GetData(), + &length, NULL, NULL, NULL, NULL); + } + + if (status == ERROR_SUCCESS) { + TString value = buffer.GetData(); + result.push_back(value); + } + } + } + + return result; + } + + TString ReadString(TString Name) { + TString result; + DWORD length; + DWORD dwRet; + DynamicBuffer buffer(0); + length = 0; + + dwRet = RegQueryValueEx(FOpenKey, Name.data(), NULL, NULL, NULL, + &length); + if (dwRet == ERROR_MORE_DATA || dwRet == 0) { + if (!buffer.Resize(length + 1)) { + return result; + } + dwRet = RegQueryValueEx(FOpenKey, Name.data(), NULL, NULL, + (LPBYTE) buffer.GetData(), &length); + result = buffer.GetData(); + } + + return result; + } +}; + +WindowsPlatform::WindowsPlatform(void) : Platform() { + FMainThread = ::GetCurrentThreadId(); +} + +WindowsPlatform::~WindowsPlatform(void) { +} + +TString WindowsPlatform::GetPackageAppDirectory() { + return FilePath::IncludeTrailingSeparator( + GetPackageRootDirectory()) + _T("app"); +} + +TString WindowsPlatform::GetPackageLauncherDirectory() { + return GetPackageRootDirectory(); +} + +TString WindowsPlatform::GetPackageRuntimeBinDirectory() { + return FilePath::IncludeTrailingSeparator(GetPackageRootDirectory()) + _T("runtime\\bin"); +} + +TCHAR* WindowsPlatform::ConvertStringToFileSystemString(TCHAR* Source, + bool &release) { + // Not Implemented. + return NULL; +} + +TCHAR* WindowsPlatform::ConvertFileSystemStringToString(TCHAR* Source, + bool &release) { + // Not Implemented. + return NULL; +} + +void WindowsPlatform::SetCurrentDirectory(TString Value) { + _wchdir(Value.data()); +} + +TString WindowsPlatform::GetPackageRootDirectory() { + TString filename = GetModuleFileName(); + return FilePath::ExtractFilePath(filename); +} + +TString WindowsPlatform::GetAppDataDirectory() { + TString result; + TCHAR path[MAX_PATH]; + + if (SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, path) == S_OK) { + result = path; + } + + return result; +} + +TString WindowsPlatform::GetAppName() { + TString result = GetModuleFileName(); + result = FilePath::ExtractFileName(result); + result = FilePath::ChangeFileExt(result, _T("")); + return result; +} + +void WindowsPlatform::ShowMessage(TString title, TString description) { + MessageBox(NULL, description.data(), + !title.empty() ? title.data() : description.data(), + MB_ICONERROR | MB_OK); +} + +void WindowsPlatform::ShowMessage(TString description) { + TString appname = GetModuleFileName(); + appname = FilePath::ExtractFileName(appname); + MessageBox(NULL, description.data(), appname.data(), MB_ICONERROR | MB_OK); +} + +MessageResponse WindowsPlatform::ShowResponseMessage(TString title, + TString description) { + MessageResponse result = mrCancel; + + if (::MessageBox(NULL, description.data(), title.data(), MB_OKCANCEL) == + IDOK) { + result = mrOK; + } + + return result; +} + +TString WindowsPlatform::GetBundledJavaLibraryFileName(TString RuntimePath) { + TString result = FilePath::IncludeTrailingSeparator(RuntimePath) + + _T("jre\\bin\\jli.dll"); + + if (FilePath::FileExists(result) == false) { + result = FilePath::IncludeTrailingSeparator(RuntimePath) + + _T("bin\\jli.dll"); + } + + return result; +} + +ISectionalPropertyContainer* WindowsPlatform::GetConfigFile(TString FileName) { + IniFile *result = new IniFile(); + if (result == NULL) { + return NULL; + } + + result->LoadFromFile(FileName); + + return result; +} + +TString WindowsPlatform::GetModuleFileName() { + TString result; + DynamicBuffer buffer(MAX_PATH); + if (buffer.GetData() == NULL) { + return result; + } + + ::GetModuleFileName(NULL, buffer.GetData(), + static_cast (buffer.GetSize())); + + while (ERROR_INSUFFICIENT_BUFFER == GetLastError()) { + if (!buffer.Resize(buffer.GetSize() * 2)) { + return result; + } + ::GetModuleFileName(NULL, buffer.GetData(), + static_cast (buffer.GetSize())); + } + + result = buffer.GetData(); + return result; +} + +Module WindowsPlatform::LoadLibrary(TString FileName) { + return ::LoadLibrary(FileName.data()); +} + +void WindowsPlatform::FreeLibrary(Module AModule) { + ::FreeLibrary((HMODULE) AModule); +} + +Procedure WindowsPlatform::GetProcAddress(Module AModule, + std::string MethodName) { + return ::GetProcAddress((HMODULE) AModule, MethodName.c_str()); +} + +bool WindowsPlatform::IsMainThread() { + bool result = (FMainThread == ::GetCurrentThreadId()); + return result; +} + +TString WindowsPlatform::GetTempDirectory() { + TString result; + PWSTR userDir = 0; + + if (SUCCEEDED(SHGetKnownFolderPath( + FOLDERID_Profile, + 0, + NULL, + &userDir))) { + result = userDir; + result += WINDOWS_JPACKAGE_TMP_DIR; + CoTaskMemFree(userDir); + } + + return result; +} + +static BOOL CALLBACK enumWindows(HWND winHandle, LPARAM lParam) { + DWORD pid = (DWORD) lParam, wPid = 0; + GetWindowThreadProcessId(winHandle, &wPid); + if (pid == wPid) { + SetForegroundWindow(winHandle); + return FALSE; + } + return TRUE; +} + +TPlatformNumber WindowsPlatform::GetMemorySize() { + SYSTEM_INFO si; + GetSystemInfo(&si); + size_t result = (size_t) si.lpMaximumApplicationAddress; + result = result / 1048576; // Convert from bytes to megabytes. + return result; +} + +std::vector FilterList(std::vector &Items, + std::wregex Pattern) { + std::vector result; + + for (std::vector::iterator it = Items.begin(); + it != Items.end(); ++it) { + TString item = *it; + std::wsmatch match; + + if (std::regex_search(item, match, Pattern)) { + result.push_back(item); + } + } + return result; +} + +Process* WindowsPlatform::CreateProcess() { + return new WindowsProcess(); +} + +void WindowsPlatform::InitStreamLocale(wios *stream) { + const std::locale empty_locale = std::locale::empty(); + const std::locale utf8_locale = + std::locale(empty_locale, new std::codecvt_utf8()); + stream->imbue(utf8_locale); +} + +void WindowsPlatform::addPlatformDependencies(JavaLibrary *pJavaLibrary) { + if (pJavaLibrary == NULL) { + return; + } + + if (FilePath::FileExists(_T("msvcr100.dll")) == true) { + pJavaLibrary->AddDependency(_T("msvcr100.dll")); + } + + TString runtimeBin = GetPackageRuntimeBinDirectory(); + SetDllDirectory(runtimeBin.c_str()); +} + +void Platform::CopyString(char *Destination, + size_t NumberOfElements, const char *Source) { + strcpy_s(Destination, NumberOfElements, Source); + + if (NumberOfElements > 0) { + Destination[NumberOfElements - 1] = '\0'; + } +} + +void Platform::CopyString(wchar_t *Destination, + size_t NumberOfElements, const wchar_t *Source) { + wcscpy_s(Destination, NumberOfElements, Source); + + if (NumberOfElements > 0) { + Destination[NumberOfElements - 1] = '\0'; + } +} + +// Owner must free the return value. +MultibyteString Platform::WideStringToMultibyteString( + const wchar_t* value) { + MultibyteString result; + size_t count = 0; + + if (value == NULL) { + return result; + } + + count = WideCharToMultiByte(CP_UTF8, 0, value, -1, NULL, 0, NULL, NULL); + + if (count > 0) { + result.data = new char[count + 1]; + result.length = WideCharToMultiByte(CP_UTF8, 0, value, -1, + result.data, (int)count, NULL, NULL); + } + + return result; +} + +// Owner must free the return value. +WideString Platform::MultibyteStringToWideString(const char* value) { + WideString result; + size_t count = 0; + + if (value == NULL) { + return result; + } + + mbstowcs_s(&count, NULL, 0, value, _TRUNCATE); + + if (count > 0) { + result.data = new wchar_t[count + 1]; + mbstowcs_s(&result.length, result.data, count, value, count); + } + + return result; +} + +FileHandle::FileHandle(std::wstring FileName) { + FHandle = ::CreateFile(FileName.data(), GENERIC_READ, FILE_SHARE_READ, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); +} + +FileHandle::~FileHandle() { + if (IsValid() == true) { + ::CloseHandle(FHandle); + } +} + +bool FileHandle::IsValid() { + return FHandle != INVALID_HANDLE_VALUE; +} + +HANDLE FileHandle::GetHandle() { + return FHandle; +} + +FileMappingHandle::FileMappingHandle(HANDLE FileHandle) { + FHandle = ::CreateFileMapping(FileHandle, NULL, PAGE_READONLY, 0, 0, NULL); +} + +bool FileMappingHandle::IsValid() { + return FHandle != NULL; +} + +FileMappingHandle::~FileMappingHandle() { + if (IsValid() == true) { + ::CloseHandle(FHandle); + } +} + +HANDLE FileMappingHandle::GetHandle() { + return FHandle; +} + +FileData::FileData(HANDLE Handle) { + FBaseAddress = ::MapViewOfFile(Handle, FILE_MAP_READ, 0, 0, 0); +} + +FileData::~FileData() { + if (IsValid() == true) { + ::UnmapViewOfFile(FBaseAddress); + } +} + +bool FileData::IsValid() { + return FBaseAddress != NULL; +} + +LPVOID FileData::GetBaseAddress() { + return FBaseAddress; +} + +WindowsLibrary::WindowsLibrary(std::wstring FileName) { + FFileName = FileName; +} + +std::vector WindowsLibrary::GetImports() { + std::vector result; + FileHandle library(FFileName); + + if (library.IsValid() == true) { + FileMappingHandle mapping(library.GetHandle()); + + if (mapping.IsValid() == true) { + FileData fileData(mapping.GetHandle()); + + if (fileData.IsValid() == true) { + PIMAGE_DOS_HEADER dosHeader = + (PIMAGE_DOS_HEADER) fileData.GetBaseAddress(); + PIMAGE_FILE_HEADER pImgFileHdr = + (PIMAGE_FILE_HEADER) fileData.GetBaseAddress(); + if (dosHeader->e_magic == IMAGE_DOS_SIGNATURE) { + result = DumpPEFile(dosHeader); + } + } + } + } + + return result; +} + +// Given an RVA, look up the section header that encloses it and return a +// pointer to its IMAGE_SECTION_HEADER + +PIMAGE_SECTION_HEADER WindowsLibrary::GetEnclosingSectionHeader(DWORD rva, + PIMAGE_NT_HEADERS pNTHeader) { + PIMAGE_SECTION_HEADER result = 0; + PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(pNTHeader); + + for (unsigned index = 0; index < pNTHeader->FileHeader.NumberOfSections; + index++, section++) { + // Is the RVA is within this section? + if ((rva >= section->VirtualAddress) && + (rva < (section->VirtualAddress + section->Misc.VirtualSize))) { + result = section; + } + } + + return result; +} + +LPVOID WindowsLibrary::GetPtrFromRVA(DWORD rva, PIMAGE_NT_HEADERS pNTHeader, + DWORD imageBase) { + LPVOID result = 0; + PIMAGE_SECTION_HEADER pSectionHdr = GetEnclosingSectionHeader(rva, + pNTHeader); + + if (pSectionHdr != NULL) { + INT delta = (INT) ( + pSectionHdr->VirtualAddress - pSectionHdr->PointerToRawData); + DWORD_PTR dwp = (DWORD_PTR) (imageBase + rva - delta); + result = reinterpret_cast (dwp); // VS2017 - FIXME + } + + return result; +} + +std::vector WindowsLibrary::GetImportsSection(DWORD base, + PIMAGE_NT_HEADERS pNTHeader) { + std::vector result; + + // Look up where the imports section is located. Normally in + // the .idata section, + // but not necessarily so. Therefore, grab the RVA from the data dir. + DWORD importsStartRVA = pNTHeader->OptionalHeader.DataDirectory[ + IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; + + if (importsStartRVA != NULL) { + // Get the IMAGE_SECTION_HEADER that contains the imports. This is + // usually the .idata section, but doesn't have to be. + PIMAGE_SECTION_HEADER pSection = + GetEnclosingSectionHeader(importsStartRVA, pNTHeader); + + if (pSection != NULL) { + PIMAGE_IMPORT_DESCRIPTOR importDesc = + (PIMAGE_IMPORT_DESCRIPTOR) GetPtrFromRVA( + importsStartRVA, pNTHeader, base); + + if (importDesc != NULL) { + while (true) { + // See if we've reached an empty IMAGE_IMPORT_DESCRIPTOR + if ((importDesc->TimeDateStamp == 0) && + (importDesc->Name == 0)) { + break; + } + + std::string filename = (char*) GetPtrFromRVA( + importDesc->Name, pNTHeader, base); + result.push_back(PlatformString(filename)); + importDesc++; // advance to next IMAGE_IMPORT_DESCRIPTOR + } + } + } + } + + return result; +} + +std::vector WindowsLibrary::DumpPEFile(PIMAGE_DOS_HEADER dosHeader) { + std::vector result; + // all of this is VS2017 - FIXME + DWORD_PTR dwDosHeaders = reinterpret_cast (dosHeader); + DWORD_PTR dwPIHeaders = dwDosHeaders + (DWORD) (dosHeader->e_lfanew); + + PIMAGE_NT_HEADERS pNTHeader = + reinterpret_cast (dwPIHeaders); + + // Verify that the e_lfanew field gave us a reasonable + // pointer and the PE signature. + // TODO: To really fix JDK-8131321 this condition needs to be changed. + // There is a matching change + // in JavaVirtualMachine.cpp that also needs to be changed. + if (pNTHeader->Signature == IMAGE_NT_SIGNATURE) { + DWORD base = (DWORD) (dwDosHeaders); + result = GetImportsSection(base, pNTHeader); + } + + return result; +} + +#include + +WindowsJob::WindowsJob() { + FHandle = NULL; +} + +WindowsJob::~WindowsJob() { + if (FHandle != NULL) { + CloseHandle(FHandle); + } +} + +HANDLE WindowsJob::GetHandle() { + if (FHandle == NULL) { + FHandle = CreateJobObject(NULL, NULL); // GLOBAL + + if (FHandle == NULL) { + ::MessageBox(0, _T("Could not create job object"), + _T("TEST"), MB_OK); + } else { + JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {0}; + + // Configure all child processes associated with + // the job to terminate when the + jeli.BasicLimitInformation.LimitFlags = + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + if (0 == SetInformationJobObject(FHandle, + JobObjectExtendedLimitInformation, &jeli, sizeof (jeli))) { + ::MessageBox(0, _T("Could not SetInformationJobObject"), + _T("TEST"), MB_OK); + } + } + } + + return FHandle; +} + +// Initialize static member of WindowsProcess +WindowsJob WindowsProcess::FJob; + +WindowsProcess::WindowsProcess() : Process() { + FRunning = false; +} + +WindowsProcess::~WindowsProcess() { + Terminate(); +} + +void WindowsProcess::Cleanup() { + CloseHandle(FProcessInfo.hProcess); + CloseHandle(FProcessInfo.hThread); +} + +bool WindowsProcess::IsRunning() { + bool result = false; + + HANDLE handle = ::CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0); + if (handle == INVALID_HANDLE_VALUE) { + return false; + } + + PROCESSENTRY32 process = {0}; + process.dwSize = sizeof (process); + + if (::Process32First(handle, &process)) { + do { + if (process.th32ProcessID == FProcessInfo.dwProcessId) { + result = true; + break; + } + } while (::Process32Next(handle, &process)); + } + + CloseHandle(handle); + + return result; +} + +bool WindowsProcess::Terminate() { + bool result = false; + + if (IsRunning() == true && FRunning == true) { + FRunning = false; + } + + return result; +} + +bool WindowsProcess::Execute(const TString Application, + const std::vector Arguments, bool AWait) { + bool result = false; + + if (FRunning == false) { + FRunning = true; + + STARTUPINFO startupInfo; + ZeroMemory(&startupInfo, sizeof (startupInfo)); + startupInfo.cb = sizeof (startupInfo); + ZeroMemory(&FProcessInfo, sizeof (FProcessInfo)); + + TString command = Application; + + for (std::vector::const_iterator iterator = Arguments.begin(); + iterator != Arguments.end(); iterator++) { + command += TString(_T(" ")) + *iterator; + } + + if (::CreateProcess(Application.data(), (wchar_t*)command.data(), NULL, + NULL, FALSE, 0, NULL, NULL, &startupInfo, &FProcessInfo) == FALSE) { + TString message = PlatformString::Format( + _T("Error: Unable to create process %s"), + Application.data()); + throw Exception(message); + } else { + if (FJob.GetHandle() != NULL) { + if (::AssignProcessToJobObject(FJob.GetHandle(), + FProcessInfo.hProcess) == 0) { + // Failed to assign process to job. It doesn't prevent + // anything from continuing so continue. + } + } + + // Wait until child process exits. + if (AWait == true) { + Wait(); + // Close process and thread handles. + Cleanup(); + } + } + } + + return result; +} + +bool WindowsProcess::Wait() { + bool result = false; + + WaitForSingleObject(FProcessInfo.hProcess, INFINITE); + return result; +} + +TProcessID WindowsProcess::GetProcessID() { + return FProcessInfo.dwProcessId; +} + +bool WindowsProcess::ReadOutput() { + bool result = false; + // TODO implement + return result; +} + +void WindowsProcess::SetInput(TString Value) { + // TODO implement +} + +std::list WindowsProcess::GetOutput() { + ReadOutput(); + return Process::GetOutput(); +} diff --git a/src/jdk.jpackage/windows/native/libapplauncher/WindowsPlatform.h b/src/jdk.jpackage/windows/native/libapplauncher/WindowsPlatform.h new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/native/libapplauncher/WindowsPlatform.h @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef WINDOWSPLATFORM_H +#define WINDOWSPLATFORM_H + +#include +#include "Platform.h" + +class WindowsPlatform : virtual public Platform { +private: + DWORD FMainThread; + +public: + WindowsPlatform(void); + virtual ~WindowsPlatform(void); + + virtual TCHAR* ConvertStringToFileSystemString(TCHAR* Source, + bool &release); + virtual TCHAR* ConvertFileSystemStringToString(TCHAR* Source, + bool &release); + + virtual void ShowMessage(TString title, TString description); + virtual void ShowMessage(TString description); + virtual MessageResponse ShowResponseMessage(TString title, + TString description); + + virtual void SetCurrentDirectory(TString Value); + virtual TString GetPackageRootDirectory(); + virtual TString GetAppDataDirectory(); + virtual TString GetAppName(); + virtual TString GetBundledJavaLibraryFileName(TString RuntimePath); + TString GetPackageAppDirectory(); + TString GetPackageLauncherDirectory(); + TString GetPackageRuntimeBinDirectory(); + + virtual ISectionalPropertyContainer* GetConfigFile(TString FileName); + + virtual TString GetModuleFileName(); + virtual Module LoadLibrary(TString FileName); + virtual void FreeLibrary(Module AModule); + virtual Procedure GetProcAddress(Module AModule, std::string MethodName); + + virtual Process* CreateProcess(); + + virtual bool IsMainThread(); + virtual TPlatformNumber GetMemorySize(); + + virtual TString GetTempDirectory(); + void InitStreamLocale(wios *stream); + void addPlatformDependencies(JavaLibrary *pJavaLibrary); +}; + +class FileHandle { +private: + HANDLE FHandle; + +public: + FileHandle(std::wstring FileName); + ~FileHandle(); + + bool IsValid(); + HANDLE GetHandle(); +}; + + +class FileMappingHandle { +private: + HANDLE FHandle; + +public: + FileMappingHandle(HANDLE FileHandle); + ~FileMappingHandle(); + + bool IsValid(); + HANDLE GetHandle(); +}; + + +class FileData { +private: + LPVOID FBaseAddress; + +public: + FileData(HANDLE Handle); + ~FileData(); + + bool IsValid(); + LPVOID GetBaseAddress(); +}; + + +class WindowsLibrary { +private: + TString FFileName; + + // Given an RVA, look up the section header that encloses it and return a + // pointer to its IMAGE_SECTION_HEADER + static PIMAGE_SECTION_HEADER GetEnclosingSectionHeader(DWORD rva, + PIMAGE_NT_HEADERS pNTHeader); + static LPVOID GetPtrFromRVA(DWORD rva, PIMAGE_NT_HEADERS pNTHeader, + DWORD imageBase); + static std::vector GetImportsSection(DWORD base, + PIMAGE_NT_HEADERS pNTHeader); + static std::vector DumpPEFile(PIMAGE_DOS_HEADER dosHeader); + +public: + WindowsLibrary(const TString FileName); + + std::vector GetImports(); +}; + + +class WindowsJob { +private: + HANDLE FHandle; + +public: + WindowsJob(); + ~WindowsJob(); + + HANDLE GetHandle(); +}; + + +class WindowsProcess : public Process { +private: + bool FRunning; + + PROCESS_INFORMATION FProcessInfo; + static WindowsJob FJob; + + void Cleanup(); + bool ReadOutput(); + +public: + WindowsProcess(); + virtual ~WindowsProcess(); + + virtual bool IsRunning(); + virtual bool Terminate(); + virtual bool Execute(const TString Application, + const std::vector Arguments, bool AWait = false); + virtual bool Wait(); + virtual TProcessID GetProcessID(); + virtual void SetInput(TString Value); + virtual std::list GetOutput(); +}; + +#endif // WINDOWSPLATFORM_H diff --git a/src/jdk.jpackage/windows/native/libjpackage/ByteBuffer.cpp b/src/jdk.jpackage/windows/native/libjpackage/ByteBuffer.cpp new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/native/libjpackage/ByteBuffer.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "ByteBuffer.h" + +#include + +ByteBuffer::ByteBuffer() { + buffer.reserve(1024); +} + +ByteBuffer::~ByteBuffer() { +} + +LPBYTE ByteBuffer::getPtr() { + return &buffer[0]; +} + +size_t ByteBuffer::getPos() { + return buffer.size(); +} + +void ByteBuffer::AppendString(wstring str) { + size_t len = (str.size() + 1) * sizeof (WCHAR); + AppendBytes((BYTE*) str.c_str(), len); +} + +void ByteBuffer::AppendWORD(WORD word) { + AppendBytes((BYTE*) & word, sizeof (WORD)); +} + +void ByteBuffer::Align(size_t bytesNumber) { + size_t pos = getPos(); + if (pos % bytesNumber) { + DWORD dwNull = 0; + size_t len = bytesNumber - pos % bytesNumber; + AppendBytes((BYTE*) & dwNull, len); + } +} + +void ByteBuffer::AppendBytes(BYTE* ptr, size_t len) { + buffer.insert(buffer.end(), ptr, ptr + len); +} + +void ByteBuffer::ReplaceWORD(size_t offset, WORD word) { + ReplaceBytes(offset, (BYTE*) & word, sizeof (WORD)); +} + +void ByteBuffer::ReplaceBytes(size_t offset, BYTE* ptr, size_t len) { + for (size_t i = 0; i < len; i++) { + buffer[offset + i] = *(ptr + i); + } +} diff --git a/src/jdk.jpackage/windows/native/libjpackage/ByteBuffer.h b/src/jdk.jpackage/windows/native/libjpackage/ByteBuffer.h new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/native/libjpackage/ByteBuffer.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef BYTEBUFFER_H +#define BYTEBUFFER_H + +#include +#include +#include + +using namespace std; + +class ByteBuffer { +public: + ByteBuffer(); + ~ByteBuffer(); + + LPBYTE getPtr(); + size_t getPos(); + + void AppendString(wstring str); + void AppendWORD(WORD word); + void AppendBytes(BYTE* ptr, size_t len); + + void ReplaceWORD(size_t offset, WORD word); + void ReplaceBytes(size_t offset, BYTE* ptr, size_t len); + + void Align(size_t bytesNumber); + +private: + vector buffer; +}; + +#endif // BYTEBUFFER_H \ No newline at end of file diff --git a/src/jdk.jpackage/windows/native/libjpackage/IconSwap.cpp b/src/jdk.jpackage/windows/native/libjpackage/IconSwap.cpp new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/native/libjpackage/IconSwap.cpp @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include +#include +#include +#include + +using namespace std; + +// http://msdn.microsoft.com/en-us/library/ms997538.aspx + +typedef struct _ICONDIRENTRY { + BYTE bWidth; + BYTE bHeight; + BYTE bColorCount; + BYTE bReserved; + WORD wPlanes; + WORD wBitCount; + DWORD dwBytesInRes; + DWORD dwImageOffset; +} ICONDIRENTRY, * LPICONDIRENTRY; + +typedef struct _ICONDIR { + WORD idReserved; + WORD idType; + WORD idCount; + ICONDIRENTRY idEntries[1]; +} ICONDIR, * LPICONDIR; + +// #pragmas are used here to insure that the structure's +// packing in memory matches the packing of the EXE or DLL. +#pragma pack(push) +#pragma pack(2) + +typedef struct _GRPICONDIRENTRY { + BYTE bWidth; + BYTE bHeight; + BYTE bColorCount; + BYTE bReserved; + WORD wPlanes; + WORD wBitCount; + DWORD dwBytesInRes; + WORD nID; +} GRPICONDIRENTRY, * LPGRPICONDIRENTRY; +#pragma pack(pop) + +#pragma pack(push) +#pragma pack(2) + +typedef struct _GRPICONDIR { + WORD idReserved; + WORD idType; + WORD idCount; + GRPICONDIRENTRY idEntries[1]; +} GRPICONDIR, * LPGRPICONDIR; +#pragma pack(pop) + +void PrintError() { + LPVOID message = NULL; + DWORD error = GetLastError(); + + if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, NULL, error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) & message, 0, NULL) != 0) { + printf("%S", (LPTSTR) message); + LocalFree(message); + } +} + +// Note: We do not check here that iconTarget is valid icon. +// Java code will already do this for us. + +bool ChangeIcon(wstring iconTarget, wstring launcher) { + WORD language = MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT); + + HANDLE icon = CreateFile(iconTarget.c_str(), GENERIC_READ, 0, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (icon == INVALID_HANDLE_VALUE) { + PrintError(); + return false; + } + + // Reading .ICO file + WORD idReserved, idType, idCount; + + DWORD dwBytesRead; + ReadFile(icon, &idReserved, sizeof (WORD), &dwBytesRead, NULL); + ReadFile(icon, &idType, sizeof (WORD), &dwBytesRead, NULL); + ReadFile(icon, &idCount, sizeof (WORD), &dwBytesRead, NULL); + + LPICONDIR lpid = (LPICONDIR) malloc( + sizeof (ICONDIR) + (sizeof (ICONDIRENTRY) * (idCount - 1))); + if (lpid == NULL) { + CloseHandle(icon); + printf("Error: Failed to allocate memory\n"); + return false; + } + + lpid->idReserved = idReserved; + lpid->idType = idType; + lpid->idCount = idCount; + + ReadFile(icon, &lpid->idEntries[0], sizeof (ICONDIRENTRY) * lpid->idCount, + &dwBytesRead, NULL); + + LPGRPICONDIR lpgid = (LPGRPICONDIR) malloc( + sizeof (GRPICONDIR) + (sizeof (GRPICONDIRENTRY) * (idCount - 1))); + if (lpid == NULL) { + CloseHandle(icon); + free(lpid); + printf("Error: Failed to allocate memory\n"); + return false; + } + + lpgid->idReserved = idReserved; + lpgid->idType = idType; + lpgid->idCount = idCount; + + for (int i = 0; i < lpgid->idCount; i++) { + lpgid->idEntries[i].bWidth = lpid->idEntries[i].bWidth; + lpgid->idEntries[i].bHeight = lpid->idEntries[i].bHeight; + lpgid->idEntries[i].bColorCount = lpid->idEntries[i].bColorCount; + lpgid->idEntries[i].bReserved = lpid->idEntries[i].bReserved; + lpgid->idEntries[i].wPlanes = lpid->idEntries[i].wPlanes; + lpgid->idEntries[i].wBitCount = lpid->idEntries[i].wBitCount; + lpgid->idEntries[i].dwBytesInRes = lpid->idEntries[i].dwBytesInRes; + lpgid->idEntries[i].nID = i + 1; + } + + // Store images in .EXE + HANDLE update = BeginUpdateResource(launcher.c_str(), FALSE); + if (update == NULL) { + free(lpid); + free(lpgid); + CloseHandle(icon); + PrintError(); + return false; + } + + for (int i = 0; i < lpid->idCount; i++) { + LPBYTE lpBuffer = (LPBYTE) malloc(lpid->idEntries[i].dwBytesInRes); + SetFilePointer(icon, lpid->idEntries[i].dwImageOffset, + NULL, FILE_BEGIN); + ReadFile(icon, lpBuffer, lpid->idEntries[i].dwBytesInRes, + &dwBytesRead, NULL); + if (!UpdateResource(update, RT_ICON, + MAKEINTRESOURCE(lpgid->idEntries[i].nID), + language, &lpBuffer[0], lpid->idEntries[i].dwBytesInRes)) { + free(lpBuffer); + free(lpid); + free(lpgid); + CloseHandle(icon); + PrintError(); + return false; + } + free(lpBuffer); + } + + free(lpid); + CloseHandle(icon); + + if (!UpdateResource(update, RT_GROUP_ICON, + MAKEINTRESOURCE(1), language, &lpgid[0], + (sizeof (WORD) * 3) + (sizeof (GRPICONDIRENTRY) * lpgid->idCount))) { + free(lpgid); + PrintError(); + return false; + } + + free(lpgid); + + if (EndUpdateResource(update, FALSE) == FALSE) { + PrintError(); + return false; + } + + return true; +} diff --git a/src/jdk.jpackage/windows/native/libjpackage/IconSwap.h b/src/jdk.jpackage/windows/native/libjpackage/IconSwap.h new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/native/libjpackage/IconSwap.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef ICONSWAP_H +#define ICONSWAP_H + +#include + +using namespace std; + +bool ChangeIcon(wstring iconTarget, wstring launcher); + +#endif // ICONSWAP_H \ No newline at end of file diff --git a/src/jdk.jpackage/windows/native/libjpackage/Utils.cpp b/src/jdk.jpackage/windows/native/libjpackage/Utils.cpp new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/native/libjpackage/Utils.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "Windows.h" +#include "Utils.h" + +#define BUFFER_SIZE 4096 + +wstring GetStringFromJString(JNIEnv *pEnv, jstring jstr) { + const jchar *pJChars = pEnv->GetStringChars(jstr, NULL); + if (pJChars == NULL) { + return wstring(L""); + } + + wstring wstr(pJChars); + + pEnv->ReleaseStringChars(jstr, pJChars); + + return wstr; +} + +jstring GetJStringFromString(JNIEnv *pEnv, const jchar *unicodeChars, jsize len) { + return pEnv->NewString(unicodeChars, len); +} + +wstring GetLongPath(wstring path) { + wstring result(L""); + + size_t len = path.length(); + if (len > 1) { + if (path.at(len - 1) == '\\') { + path.erase(len - 1); + } + } + + TCHAR *pBuffer = new TCHAR[BUFFER_SIZE]; + if (pBuffer != NULL) { + DWORD dwResult = GetLongPathName(path.c_str(), pBuffer, BUFFER_SIZE); + if (dwResult > 0 && dwResult < BUFFER_SIZE) { + result = wstring(pBuffer); + } else { + delete [] pBuffer; + pBuffer = new TCHAR[dwResult]; + if (pBuffer != NULL) { + DWORD dwResult2 = GetLongPathName(path.c_str(), pBuffer, dwResult); + if (dwResult2 == (dwResult - 1)) { + result = wstring(pBuffer); + } + } + } + + if (pBuffer != NULL) { + delete [] pBuffer; + } + } + + return result; +} diff --git a/src/jdk.jpackage/windows/native/libjpackage/Utils.h b/src/jdk.jpackage/windows/native/libjpackage/Utils.h new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/native/libjpackage/Utils.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef UTILS_H +#define UTILS_H + +#include +#include "jni.h" + +using namespace std; + +wstring GetStringFromJString(JNIEnv *pEnv, jstring jstr); +jstring GetJStringFromString(JNIEnv *pEnv, const jchar *unicodeChars, jsize len); + +wstring GetLongPath(wstring path); + +#endif // UTILS_H diff --git a/src/jdk.jpackage/windows/native/libjpackage/VersionInfoSwap.cpp b/src/jdk.jpackage/windows/native/libjpackage/VersionInfoSwap.cpp new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/native/libjpackage/VersionInfoSwap.cpp @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "VersionInfoSwap.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace std; + +/* + * [Property file] contains key/value pairs + * The swap tool uses these pairs to create new version resource + * + * See MSDN docs for VS_VERSIONINFO structure that + * depicts organization of data in this version resource + * https://msdn.microsoft.com/en-us/library/ms647001(v=vs.85).aspx + * + * The swap tool makes changes in [Executable file] + * The tool assumes that the executable file has no version resource + * and it adds new resource in the executable file. + * If the executable file has an existing version resource, then + * the existing version resource will be replaced with new one. + */ + +VersionInfoSwap::VersionInfoSwap(wstring executableProperties, wstring launcher) { + m_executableProperties = executableProperties; + m_launcher = launcher; +} + +bool VersionInfoSwap::PatchExecutable() { + bool b = LoadFromPropertyFile(); + if (!b) { + return false; + } + + ByteBuffer buf; + b = CreateNewResource(&buf); + if (!b) { + return false; + } + + b = this->UpdateResource(buf.getPtr(), static_cast (buf.getPos())); + if (!b) { + return false; + } + + return true; +} + +bool VersionInfoSwap::LoadFromPropertyFile() { + wifstream stream(m_executableProperties.c_str()); + + const locale empty_locale = locale::empty(); + const locale utf8_locale = + locale(empty_locale, new codecvt_utf8()); + stream.imbue(utf8_locale); + + if (stream.is_open() == true) { + int lineNumber = 1; + while (stream.eof() == false) { + wstring line; + getline(stream, line); + + // # at the first character will comment out the line. + if (line.empty() == false && line[0] != '#') { + wstring::size_type pos = line.find('='); + if (pos != wstring::npos) { + wstring name = line.substr(0, pos); + wstring value = line.substr(pos + 1); + m_props[name] = value; + } + } + lineNumber++; + } + return true; + } + + return false; +} + +/* + * Creates new version resource + * + * MSND docs for VS_VERSION_INFO structure + * https://msdn.microsoft.com/en-us/library/ms647001(v=vs.85).aspx + */ +bool VersionInfoSwap::CreateNewResource(ByteBuffer *buf) { + size_t versionInfoStart = buf->getPos(); + buf->AppendWORD(0); + buf->AppendWORD(sizeof VS_FIXEDFILEINFO); + buf->AppendWORD(0); + buf->AppendString(TEXT("VS_VERSION_INFO")); + buf->Align(4); + + VS_FIXEDFILEINFO fxi; + if (!FillFixedFileInfo(&fxi)) { + return false; + } + buf->AppendBytes((BYTE*) & fxi, sizeof (VS_FIXEDFILEINFO)); + buf->Align(4); + + // String File Info + size_t stringFileInfoStart = buf->getPos(); + buf->AppendWORD(0); + buf->AppendWORD(0); + buf->AppendWORD(1); + buf->AppendString(TEXT("StringFileInfo")); + buf->Align(4); + + // String Table + size_t stringTableStart = buf->getPos(); + buf->AppendWORD(0); + buf->AppendWORD(0); + buf->AppendWORD(1); + + // "040904B0" = LANG_ENGLISH/SUBLANG_ENGLISH_US, Unicode CP + buf->AppendString(TEXT("040904B0")); + buf->Align(4); + + // Strings + vector keys; + for (map::const_iterator it = + m_props.begin(); it != m_props.end(); ++it) { + keys.push_back(it->first); + } + + for (size_t index = 0; index < keys.size(); index++) { + wstring name = keys[index]; + wstring value = m_props[name]; + + size_t stringStart = buf->getPos(); + buf->AppendWORD(0); + buf->AppendWORD(static_cast (value.length())); + buf->AppendWORD(1); + buf->AppendString(name); + buf->Align(4); + buf->AppendString(value); + buf->ReplaceWORD(stringStart, + static_cast (buf->getPos() - stringStart)); + buf->Align(4); + } + + buf->ReplaceWORD(stringTableStart, + static_cast (buf->getPos() - stringTableStart)); + buf->ReplaceWORD(stringFileInfoStart, + static_cast (buf->getPos() - stringFileInfoStart)); + + // VarFileInfo + size_t varFileInfoStart = buf->getPos(); + buf->AppendWORD(1); + buf->AppendWORD(0); + buf->AppendWORD(1); + buf->AppendString(TEXT("VarFileInfo")); + buf->Align(4); + + buf->AppendWORD(0x24); + buf->AppendWORD(0x04); + buf->AppendWORD(0x00); + buf->AppendString(TEXT("Translation")); + buf->Align(4); + // "000004B0" = LANG_NEUTRAL/SUBLANG_ENGLISH_US, Unicode CP + buf->AppendWORD(0x0000); + buf->AppendWORD(0x04B0); + + buf->ReplaceWORD(varFileInfoStart, + static_cast (buf->getPos() - varFileInfoStart)); + buf->ReplaceWORD(versionInfoStart, + static_cast (buf->getPos() - versionInfoStart)); + + return true; +} + +bool VersionInfoSwap::FillFixedFileInfo(VS_FIXEDFILEINFO *fxi) { + wstring fileVersion; + wstring productVersion; + int ret; + + fileVersion = m_props[TEXT("FileVersion")]; + productVersion = m_props[TEXT("ProductVersion")]; + + unsigned fv_1 = 0, fv_2 = 0, fv_3 = 0, fv_4 = 0; + unsigned pv_1 = 0, pv_2 = 0, pv_3 = 0, pv_4 = 0; + + ret = _stscanf_s(fileVersion.c_str(), + TEXT("%d.%d.%d.%d"), &fv_1, &fv_2, &fv_3, &fv_4); + if (ret <= 0 || ret > 4) { + return false; + } + + ret = _stscanf_s(productVersion.c_str(), + TEXT("%d.%d.%d.%d"), &pv_1, &pv_2, &pv_3, &pv_4); + if (ret <= 0 || ret > 4) { + return false; + } + + fxi->dwSignature = 0xFEEF04BD; + fxi->dwStrucVersion = 0x00010000; + + fxi->dwFileVersionMS = MAKELONG(fv_2, fv_1); + fxi->dwFileVersionLS = MAKELONG(fv_4, fv_3); + fxi->dwProductVersionMS = MAKELONG(pv_2, pv_1); + fxi->dwProductVersionLS = MAKELONG(pv_4, pv_3); + + fxi->dwFileFlagsMask = 0; + fxi->dwFileFlags = 0; + fxi->dwFileOS = VOS_NT_WINDOWS32; + + wstring exeExt = + m_launcher.substr(m_launcher.find_last_of(TEXT("."))); + if (exeExt == TEXT(".exe")) { + fxi->dwFileType = VFT_APP; + } else if (exeExt == TEXT(".dll")) { + fxi->dwFileType = VFT_DLL; + } else { + fxi->dwFileType = VFT_UNKNOWN; + } + fxi->dwFileSubtype = 0; + + fxi->dwFileDateLS = 0; + fxi->dwFileDateMS = 0; + + return true; +} + +/* + * Adds new resource in the executable + */ +bool VersionInfoSwap::UpdateResource(LPVOID lpResLock, DWORD size) { + + HANDLE hUpdateRes; + BOOL r; + + hUpdateRes = ::BeginUpdateResource(m_launcher.c_str(), FALSE); + if (hUpdateRes == NULL) { + return false; + } + + r = ::UpdateResource(hUpdateRes, + RT_VERSION, + MAKEINTRESOURCE(VS_VERSION_INFO), + MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), + lpResLock, + size); + + if (!r) { + return false; + } + + if (!::EndUpdateResource(hUpdateRes, FALSE)) { + return false; + } + + return true; +} diff --git a/src/jdk.jpackage/windows/native/libjpackage/VersionInfoSwap.h b/src/jdk.jpackage/windows/native/libjpackage/VersionInfoSwap.h new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/native/libjpackage/VersionInfoSwap.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef VERSIONINFOSWAP_H +#define VERSIONINFOSWAP_H + +#include "ByteBuffer.h" +#include + +using namespace std; + +class VersionInfoSwap { +public: + VersionInfoSwap(wstring executableProperties, wstring launcher); + + bool PatchExecutable(); + +private: + wstring m_executableProperties; + wstring m_launcher; + + map m_props; + + bool LoadFromPropertyFile(); + bool CreateNewResource(ByteBuffer *buf); + bool UpdateResource(LPVOID lpResLock, DWORD size); + bool FillFixedFileInfo(VS_FIXEDFILEINFO *fxi); +}; + +#endif // VERSIONINFOSWAP_H \ No newline at end of file diff --git a/src/jdk.jpackage/windows/native/libjpackage/WindowsRegistry.cpp b/src/jdk.jpackage/windows/native/libjpackage/WindowsRegistry.cpp new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/native/libjpackage/WindowsRegistry.cpp @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include +#include +#include + +#include "Utils.h" + +// Max value name size per MSDN plus NULL +#define VALUE_NAME_SIZE 16384 + +#ifdef __cplusplus +extern "C" { +#endif +#undef jdk_jpackage_internal_WindowsRegistry_HKEY_LOCAL_MACHINE +#define jdk_jpackage_internal_WindowsRegistry_HKEY_LOCAL_MACHINE 1L + + /* + * Class: jdk_jpackage_internal_WindowsRegistry + * Method: readDwordValue + * Signature: (ILjava/lang/String;Ljava/lang/String;I)I + */ + JNIEXPORT jint JNICALL Java_jdk_jpackage_internal_WindowsRegistry_readDwordValue( + JNIEnv *pEnv, jclass c, jint key, jstring jSubKey, jstring jValue, jint defaultValue) { + jint jResult = defaultValue; + + if (key != jdk_jpackage_internal_WindowsRegistry_HKEY_LOCAL_MACHINE) { + return jResult; + } + + wstring subKey = GetStringFromJString(pEnv, jSubKey); + wstring value = GetStringFromJString(pEnv, jValue); + + HKEY hSubKey = NULL; + LSTATUS status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, subKey.c_str(), 0, + KEY_QUERY_VALUE, &hSubKey); + if (status == ERROR_SUCCESS) { + DWORD dwValue = 0; + DWORD cbData = sizeof (DWORD); + status = RegQueryValueEx(hSubKey, value.c_str(), NULL, NULL, + (LPBYTE) & dwValue, &cbData); + if (status == ERROR_SUCCESS) { + jResult = (jint) dwValue; + } + + RegCloseKey(hSubKey); + } + + return jResult; + } + + /* + * Class: jdk_jpackage_internal_WindowsRegistry + * Method: openRegistryKey + * Signature: (ILjava/lang/String;)J + */ + JNIEXPORT jlong JNICALL Java_jdk_jpackage_internal_WindowsRegistry_openRegistryKey( + JNIEnv *pEnv, jclass c, jint key, jstring jSubKey) { + if (key != jdk_jpackage_internal_WindowsRegistry_HKEY_LOCAL_MACHINE) { + return 0; + } + + wstring subKey = GetStringFromJString(pEnv, jSubKey); + HKEY hSubKey = NULL; + LSTATUS status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, subKey.c_str(), 0, + KEY_QUERY_VALUE, &hSubKey); + if (status == ERROR_SUCCESS) { + return (jlong)hSubKey; + } + + return 0; + } + + /* + * Class: jdk_jpackage_internal_WindowsRegistry + * Method: enumRegistryValue + * Signature: (JI)Ljava/lang/String; + */ + JNIEXPORT jstring JNICALL Java_jdk_jpackage_internal_WindowsRegistry_enumRegistryValue( + JNIEnv *pEnv, jclass c, jlong lKey, jint jIndex) { + HKEY hKey = (HKEY)lKey; + TCHAR valueName[VALUE_NAME_SIZE] = {0}; // Max value name size per MSDN plus NULL + DWORD cchValueName = VALUE_NAME_SIZE; + LSTATUS status = RegEnumValue(hKey, (DWORD)jIndex, valueName, &cchValueName, + NULL, NULL, NULL, NULL); + if (status == ERROR_SUCCESS) { + size_t chLength = 0; + if (StringCchLength(valueName, VALUE_NAME_SIZE, &chLength) == S_OK) { + return GetJStringFromString(pEnv, valueName, (jsize)chLength); + } + } + + return NULL; + } + + /* + * Class: jdk_jpackage_internal_WindowsRegistry + * Method: closeRegistryKey + * Signature: (J)V + */ + JNIEXPORT void JNICALL Java_jdk_jpackage_internal_WindowsRegistry_closeRegistryKey( + JNIEnv *pEnc, jclass c, jlong lKey) { + HKEY hKey = (HKEY)lKey; + RegCloseKey(hKey); + } + + /* + * Class: jdk_jpackage_internal_WindowsRegistry + * Method: comparePaths + * Signature: (Ljava/lang/String;Ljava/lang/String;)Z + */ + JNIEXPORT jboolean JNICALL Java_jdk_jpackage_internal_WindowsRegistry_comparePaths( + JNIEnv *pEnv, jclass c, jstring jPath1, jstring jPath2) { + wstring path1 = GetStringFromJString(pEnv, jPath1); + wstring path2 = GetStringFromJString(pEnv, jPath2); + + path1 = GetLongPath(path1); + path2 = GetLongPath(path2); + + if (path1.length() == 0 || path2.length() == 0) { + return JNI_FALSE; + } + + if (path1.length() != path2.length()) { + return JNI_FALSE; + } + + if (_tcsnicmp(path1.c_str(), path2.c_str(), path1.length()) == 0) { + return JNI_TRUE; + } + + return JNI_FALSE; + } + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/src/jdk.jpackage/windows/native/libjpackage/jpackage.cpp b/src/jdk.jpackage/windows/native/libjpackage/jpackage.cpp new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/native/libjpackage/jpackage.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include +#include +#include + +#include "IconSwap.h" +#include "VersionInfoSwap.h" +#include "Utils.h" + +using namespace std; + +#ifdef __cplusplus +extern "C" { +#endif + + /* + * Class: jdk_jpackage_internal_WindowsAppImageBuilder + * Method: iconSwap + * Signature: (Ljava/lang/String;Ljava/lang/String;)I + */ + JNIEXPORT jint JNICALL Java_jdk_jpackage_internal_WindowsAppImageBuilder_iconSwap( + JNIEnv *pEnv, jclass c, jstring jIconTarget, jstring jLauncher) { + wstring iconTarget = GetStringFromJString(pEnv, jIconTarget); + wstring launcher = GetStringFromJString(pEnv, jLauncher); + + if (ChangeIcon(iconTarget, launcher)) { + return 0; + } + + return 1; + } + + /* + * Class: jdk_jpackage_internal_WindowsAppImageBuilder + * Method: versionSwap + * Signature: (Ljava/lang/String;Ljava/lang/String;)I + */ + JNIEXPORT jint JNICALL Java_jdk_jpackage_internal_WindowsAppImageBuilder_versionSwap( + JNIEnv *pEnv, jclass c, jstring jExecutableProperties, jstring jLauncher) { + + wstring executableProperties = GetStringFromJString(pEnv, jExecutableProperties); + wstring launcher = GetStringFromJString(pEnv, jLauncher); + + VersionInfoSwap vs(executableProperties, launcher); + if (vs.PatchExecutable()) { + return 0; + } + + return 1; + } + + BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { + return TRUE; + } + +#ifdef __cplusplus +} +#endif diff --git a/src/jdk.jpackage/windows/native/libwixhelper/libwixhelper.cpp b/src/jdk.jpackage/windows/native/libwixhelper/libwixhelper.cpp new file mode 100644 --- /dev/null +++ b/src/jdk.jpackage/windows/native/libwixhelper/libwixhelper.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include +#include + +extern "C" { + +#ifdef JP_EXPORT_FUNCTION +#error Unexpected JP_EXPORT_FUNCTION define +#endif +#define JP_EXPORT_FUNCTION comment(linker, "/EXPORT:" __FUNCTION__ "=" __FUNCDNAME__) + + BOOL WINAPI DllMain(HINSTANCE hInst, ULONG ulReason, + LPVOID lpvReserved) { + return TRUE; + } + + BOOL DirectoryExist(TCHAR *szValue) { + DWORD attr = GetFileAttributes(szValue); + if (attr == INVALID_FILE_ATTRIBUTES) { + return FALSE; + } + + if (attr & FILE_ATTRIBUTE_DIRECTORY) { + return TRUE; + } + + return FALSE; + } + + UINT __stdcall CheckInstallDir(MSIHANDLE hInstall) { + #pragma JP_EXPORT_FUNCTION + + TCHAR *szValue = NULL; + DWORD cchSize = 0; + + UINT result = MsiGetProperty(hInstall, TEXT("APPLICATIONFOLDER"), TEXT(""), &cchSize); + if (result == ERROR_MORE_DATA) { + cchSize = cchSize + 1; // NULL termination + szValue = new TCHAR[cchSize]; + if (szValue) { + result = MsiGetProperty(hInstall, TEXT("APPLICATIONFOLDER"), szValue, &cchSize); + } else { + return ERROR_INSTALL_FAILURE; + } + } + + if (result != ERROR_SUCCESS) { + delete [] szValue; + return ERROR_INSTALL_FAILURE; + } + + if (DirectoryExist(szValue)) { + if (PathIsDirectoryEmpty(szValue)) { + MsiSetProperty(hInstall, TEXT("INSTALLDIR_VALID"), TEXT("1")); + } else { + MsiSetProperty(hInstall, TEXT("INSTALLDIR_VALID"), TEXT("0")); + } + } else { + MsiSetProperty(hInstall, TEXT("INSTALLDIR_VALID"), TEXT("1")); + } + + delete [] szValue; + + return ERROR_SUCCESS; + } +} diff --git a/test/jdk/tools/jpackage/JPackageHelpTest.java b/test/jdk/tools/jpackage/JPackageHelpTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/JPackageHelpTest.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + /* + * @test + * @summary jpackage help test + * @library helpers + * @build JPackageHelper + * @build JPackagePath + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageHelpTest + */ +public class JPackageHelpTest { + + // Platform specific help messages. + private static final String WINDOWS_HELP = + "--win-dir-chooser"; + private static final String OSX_HELP = + "--mac-bundle-identifier"; + private static final String LINUX_HELP = + "--linux-bundle-name"; + + private static void validate(String output1, String output2) + throws Exception { + if (output1.split("\n").length < 25) { + throw new AssertionError("jpacakger --help failed"); + } + + if (output2.split("\n").length < 25) { + throw new AssertionError("jpacakger -h failed"); + } + + // Make sure output matches for --help and -h + if (!output1.equals(output2)) { + System.err.println("================= --help ================="); + System.err.println(output1); + + System.err.println("=================== -h ==================="); + System.err.println(output2); + + throw new AssertionError( + "jpacakger help text does not match between --help and -h"); + } + + if (JPackageHelper.isWindows()) { + if (!output1.contains(WINDOWS_HELP)) { + throw new AssertionError( + "jpacakger help text missing Windows specific help"); + } + + if (output1.contains(OSX_HELP) || output1.contains(LINUX_HELP)) { + throw new AssertionError( + "jpacakger help text contains other platforms specific help"); + + } + } else if (JPackageHelper.isOSX()) { + if (!output1.contains(OSX_HELP)) { + throw new AssertionError( + "jpacakger help text missing OS X specific help"); + } + + if (output1.contains(WINDOWS_HELP) || + output1.contains(LINUX_HELP)) { + throw new AssertionError( + "jpacakger help text contains other platforms specific help"); + } + } else if (JPackageHelper.isLinux()) { + if (!output1.contains(LINUX_HELP)) { + throw new AssertionError( + "jpacakger help text missing Linux specific help"); + } + + if (output1.contains(OSX_HELP) || output1.contains(WINDOWS_HELP)) { + throw new AssertionError( + "jpacakger help text contains other platforms specific help"); + } + } + } + + private static void testHelp() throws Exception { + String output1 = JPackageHelper.executeCLI(true, "--help"); + String output2 = JPackageHelper.executeCLI(true, "-h"); + validate(output1, output2); + } + + private static void testHelpToolProvider() throws Exception { + String output1 = JPackageHelper.executeToolProvider(true, "--help"); + String output2 = JPackageHelper.executeToolProvider(true, "-h"); + validate(output1, output2); + } + + public static void main(String[] args) throws Exception { + testHelp(); + testHelpToolProvider(); + } + +} diff --git a/test/jdk/tools/jpackage/JPackageInvalidArgTest.java b/test/jdk/tools/jpackage/JPackageInvalidArgTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/JPackageInvalidArgTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + /* + * @test + * @summary jpackage invalid argument test + * @library helpers + * @build JPackageHelper + * @build JPackagePath + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageInvalidArgTest + */ +public class JPackageInvalidArgTest { + + private static final String ARG1 = "--no-such-argument"; + private static final String ARG2 = "--output"; + private static final String RESULT1 = + "Invalid Option: [--no-such-argument]"; + private static final String RESULT2 = "Mode is not specified"; + + private static void validate(String arg, String output) throws Exception { + String[] result = output.split("\n"); + if (result.length != 1) { + System.err.println(output); + throw new AssertionError("Invalid number of lines in output: " + + result.length); + } + + if (arg.equals(ARG1)) { + if (!result[0].trim().contains(RESULT1)) { + System.err.println("Expected: " + RESULT1); + System.err.println("Actual: " + result[0]); + throw new AssertionError("Unexpected output: " + result[0]); + } + } else if (arg.equals(ARG2)) { + if (!result[0].trim().contains(RESULT2)) { + System.err.println("Expected: " + RESULT2); + System.err.println("Actual: " + result[0]); + throw new AssertionError("Unexpected output: " + result[0]); + } + } + } + + private static void testInvalidArg() throws Exception { + String output = JPackageHelper.executeCLI(false, ARG1); + validate(ARG1, output); + output = JPackageHelper.executeCLI(false, ARG2); + validate(ARG2, output); + } + + private static void testInvalidArgToolProvider() throws Exception { + String output = JPackageHelper.executeToolProvider(false, ARG1); + validate(ARG1, output); + output = JPackageHelper.executeToolProvider(false, ARG2); + validate(ARG2, output); + } + + public static void main(String[] args) throws Exception { + testInvalidArg(); + testInvalidArgToolProvider(); + } + +} diff --git a/test/jdk/tools/jpackage/JPackageMissingArgumentsTest.java b/test/jdk/tools/jpackage/JPackageMissingArgumentsTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/JPackageMissingArgumentsTest.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + /* + * @test + * @summary jpackage create image missing arguments test + * @library helpers + * @build JPackageHelper + * @build JPackagePath + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageMissingArgumentsTest + */ + +public class JPackageMissingArgumentsTest { + private static final String [] RESULT_1 = {"--output"}; + private static final String [] CMD_1 = { + "create-app-image", + "--input", "input", + "--name", "test", + "--main-jar", "hello.jar", + "--main-class", "Hello", + }; + + private static final String [] RESULT_2 = {"--input"}; + private static final String [] CMD_2 = { + "create-app-image", + "--output", "output", + "--name", "test", + "--main-jar", "hello.jar", + "--main-class", "Hello", + }; + + private static final String [] RESULT_3 = {"--input", "--app-image"}; + private static final String [] CMD_3 = { + "create-installer", + "--output", "output", + "--name", "test", + "--main-jar", "hello.jar", + "--main-class", "Hello", + }; + + private static final String [] RESULT_4 = {"main class was not specified"}; + private static final String [] CMD_4 = { + "create-app-image", + "--input", "input", + "--output", "output", + "--name", "test", + "--main-jar", "hello.jar", + }; + + private static final String [] RESULT_5 = {"--main-jar"}; + private static final String [] CMD_5 = { + "create-app-image", + "--input", "input", + "--output", "output", + "--name", "test", + "--main-class", "Hello", + }; + + private static final String [] RESULT_6 = {"--module-path", "--runtime-image"}; + private static final String [] CMD_6 = { + "create-app-image", + "--output", "output", + "--name", "test", + "--module", "com.hello/com.hello.Hello", + }; + + private static final String [] RESULT_7 = {"--module-path", "--runtime-image", + "--app-image"}; + private static final String [] CMD_7 = { + "create-installer", + "--output", "output", + "--name", "test", + "--module", "com.hello/com.hello.Hello", + }; + + private static void validate(String output, String [] expected, + boolean single) throws Exception { + String[] result = output.split("\n"); + if (single && result.length != 1) { + System.err.println(output); + throw new AssertionError("Invalid number of lines in output: " + + result.length); + } + + for (String s : expected) { + if (!result[0].contains(s)) { + System.err.println("Expected to contain: " + s); + System.err.println("Actual: " + result[0]); + throw new AssertionError("Unexpected error message"); + } + } + } + + private static void testMissingArg() throws Exception { + String output = JPackageHelper.executeCLI(false, CMD_1); + validate(output, RESULT_1, true); + + output = JPackageHelper.executeCLI(false, CMD_2); + validate(output, RESULT_2, true); + + output = JPackageHelper.executeCLI(false, CMD_3); + validate(output, RESULT_3, true); + + output = JPackageHelper.executeCLI(false, CMD_4); + validate(output, RESULT_4, false); + + output = JPackageHelper.executeCLI(false, CMD_5); + validate(output, RESULT_5, true); + + output = JPackageHelper.executeCLI(false, CMD_6); + validate(output, RESULT_6, true); + + output = JPackageHelper.executeCLI(false, CMD_7); + validate(output, RESULT_7, true); + } + + private static void testMissingArgToolProvider() throws Exception { + String output = JPackageHelper.executeToolProvider(false, CMD_1); + validate(output, RESULT_1, true); + + output = JPackageHelper.executeToolProvider(false, CMD_2); + validate(output, RESULT_2, true); + + output = JPackageHelper.executeToolProvider(false, CMD_3); + validate(output, RESULT_3, true); + + output = JPackageHelper.executeToolProvider(false, CMD_4); + validate(output, RESULT_4, false); + + output = JPackageHelper.executeToolProvider(false, CMD_5); + validate(output, RESULT_5, true); + + output = JPackageHelper.executeToolProvider(false, CMD_6); + validate(output, RESULT_6, true); + + output = JPackageHelper.executeToolProvider(false, CMD_7); + validate(output, RESULT_7, true); + } + + public static void main(String[] args) throws Exception { + JPackageHelper.createHelloImageJar(); + testMissingArg(); + testMissingArgToolProvider(); + } + +} diff --git a/test/jdk/tools/jpackage/JPackageNoArgTest.java b/test/jdk/tools/jpackage/JPackageNoArgTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/JPackageNoArgTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + /* + * @test + * @summary jpackage no argument test + * @library helpers + * @build JPackageHelper + * @build JPackagePath + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageNoArgTest + */ +public class JPackageNoArgTest { + + private static final String RESULT1 = "Usage: jpackage "; + private static final String[] EXPECTED = + {"--help", "list of possible options"}; + + private static void validate(String output) throws Exception { + String[] result = output.split("\n"); + if (result.length != 2) { + System.err.println(output); + throw new AssertionError( + "Invalid number of lines in output: " + result.length); + } + + if (!result[0].trim().equals(RESULT1)) { + System.err.println("Expected: " + RESULT1); + System.err.println("Actual: " + result[0]); + throw new AssertionError("Unexpected line 1"); + } + + for (String expected : EXPECTED) { + if (!result[1].contains(expected)) { + System.err.println("Expected to contain: " + expected); + System.err.println("Actual: " + result[1]); + throw new AssertionError("Unexpected line 2"); + } + } + } + + private static void testNoArg() throws Exception { + String output = JPackageHelper.executeCLI(true, new String[0]); + validate(output); + } + + private static void testNoArgToolProvider() throws Exception { + String output = + JPackageHelper.executeToolProvider(true, new String[0]); + validate(output); + } + + public static void main(String[] args) throws Exception { + testNoArg(); + testNoArgToolProvider(); + } + +} diff --git a/test/jdk/tools/jpackage/JPackageVersionTest.java b/test/jdk/tools/jpackage/JPackageVersionTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/JPackageVersionTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + /* + * @test + * @summary jpackage version test + * @library helpers + * @build JPackageHelper + * @build JPackagePath + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageVersionTest + */ +public class JPackageVersionTest { + + private static final String ARG = "--version"; + private static final String RESULT = "jpackage version" + + " " + System.getProperty("java.version"); + + private static void validate(String output) throws Exception { + String[] result = output.split("\n"); + if (result.length != 1) { + System.err.println(output); + throw new AssertionError("Invalid number of lines in output: " + result.length); + } + + if (!result[0].trim().equals(RESULT)) { + System.err.println("Expected: " + RESULT); + System.err.println("Actual: " + result[0]); + throw new AssertionError("Unexpected line 1"); + } + } + + private static void testVersion() throws Exception { + String output = JPackageHelper.executeCLI(true, ARG); + validate(output); + } + + private static void testVersionToolProvider() throws Exception { + String output = JPackageHelper.executeToolProvider(true, ARG); + validate(output); + } + + public static void main(String[] args) throws Exception { + testVersion(); + testVersionToolProvider(); + } + +} diff --git a/test/jdk/tools/jpackage/apps/com.hello/com/hello/Hello.java b/test/jdk/tools/jpackage/apps/com.hello/com/hello/Hello.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/apps/com.hello/com/hello/Hello.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.hello; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.PrintWriter; + +public class Hello { + + private static final String MSG = "jpackage test application"; + private static final int EXPECTED_NUM_OF_PARAMS = 3; // Starts at 1 + + public static void main(String[] args) { + String outputFile = "appOutput.txt"; + File file = new File(outputFile); + + try (PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)))) { + System.out.println(MSG); + out.println(MSG); + + System.out.println("args.length: " + args.length); + out.println("args.length: " + args.length); + + for (String arg : args) { + System.out.println(arg); + out.println(arg); + } + + for (int index = 1; index <= EXPECTED_NUM_OF_PARAMS; index++) { + String value = System.getProperty("param" + index); + if (value != null) { + System.out.println("-Dparam" + index + "=" + value); + out.println("-Dparam" + index + "=" + value); + } + } + } catch (Exception ex) { + System.err.println(ex.toString()); + } + } + +} diff --git a/test/jdk/tools/jpackage/apps/com.hello/module-info.java b/test/jdk/tools/jpackage/apps/com.hello/module-info.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/apps/com.hello/module-info.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +module com.hello { + exports com.hello; +} diff --git a/test/jdk/tools/jpackage/apps/com.other/com/other/Other.java b/test/jdk/tools/jpackage/apps/com.other/com/other/Other.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/apps/com.other/com/other/Other.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.other; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.PrintWriter; + +public class Other { + + private static final String MSG = "other jpackage test application"; + private static final int EXPECTED_NUM_OF_PARAMS = 3; // Starts at 1 + + public static void main(String[] args) { + String outputFile = "appOutput.txt"; + File file = new File(outputFile); + + try (PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)))) { + System.out.println(MSG); + out.println(MSG); + + System.out.println("args.length: " + args.length); + out.println("args.length: " + args.length); + + for (String arg : args) { + System.out.println(arg); + out.println(arg); + } + + for (int index = 1; index <= EXPECTED_NUM_OF_PARAMS; index++) { + String value = System.getProperty("param" + index); + if (value != null) { + System.out.println("-Dparam" + index + "=" + value); + out.println("-Dparam" + index + "=" + value); + } + } + } catch (Exception ex) { + System.err.println(ex.toString()); + } + } + +} diff --git a/test/jdk/tools/jpackage/apps/com.other/module-info.java b/test/jdk/tools/jpackage/apps/com.other/module-info.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/apps/com.other/module-info.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +module com.other { + exports com.other; +} diff --git a/test/jdk/tools/jpackage/apps/image/Hello.java b/test/jdk/tools/jpackage/apps/image/Hello.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/apps/image/Hello.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.PrintWriter; + +public class Hello { + + private static final String MSG = "jpackage test application"; + private static final int EXPECTED_NUM_OF_PARAMS = 3; // Starts at 1 + + public static void main(String[] args) { + printToStdout(args); + printToFile(args); + } + + private static void printToStdout(String[] args) { + System.out.println(MSG); + + System.out.println("args.length: " + args.length); + + for (String arg : args) { + System.out.println(arg); + } + + for (int index = 1; index <= EXPECTED_NUM_OF_PARAMS; index++) { + String value = System.getProperty("param" + index); + if (value != null) { + System.out.println("-Dparam" + index + "=" + value); + } + } + } + + private static void printToFile(String[] args) { + String outputFile = "appOutput.txt"; + File file = new File(outputFile); + + try (PrintWriter out + = new PrintWriter(new BufferedWriter(new FileWriter(file)))) { + out.println(MSG); + + out.println("args.length: " + args.length); + + for (String arg : args) { + out.println(arg); + } + + for (int index = 1; index <= EXPECTED_NUM_OF_PARAMS; index++) { + String value = System.getProperty("param" + index); + if (value != null) { + out.println("-Dparam" + index + "=" + value); + } + } + } catch (Exception ex) { + System.err.println(ex.getMessage()); + } + } +} diff --git a/test/jdk/tools/jpackage/apps/installer/Hello.java b/test/jdk/tools/jpackage/apps/installer/Hello.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/apps/installer/Hello.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.PrintWriter; +import java.awt.Desktop; +import java.awt.desktop.OpenFilesEvent; +import java.awt.desktop.OpenFilesHandler; +import java.util.List; + +public class Hello implements OpenFilesHandler { + + private static final String MSG = "jpackage test application"; + private static final int EXPECTED_NUM_OF_PARAMS = 3; // Starts at 1 + private static List files; + + public static void main(String[] args) { + if(Desktop.getDesktop().isSupported(Desktop.Action.APP_OPEN_FILE)) { + Desktop.getDesktop().setOpenFileHandler(new Hello()); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + printToStdout(args); + if (args.length == 1 || (files != null && files.size() == 1)) { // Called via file association + printToFile(args); + } + } + + private static void printToStdout(String[] args) { + System.out.println(MSG); + + System.out.println("args.length: " + (files == null ? args.length : args.length + files.size())); + + for (String arg : args) { + System.out.println(arg); + } + + if (files != null) { + for (File file : files) { + System.out.println(file.getAbsolutePath()); + } + } + + for (int index = 1; index <= EXPECTED_NUM_OF_PARAMS; index++) { + String value = System.getProperty("param" + index); + if (value != null) { + System.out.println("-Dparam" + index + "=" + value); + } + } + } + + private static void printToFile(String[] args) { + File inputFile = files == null ? new File(args[0]) : files.get(0); + String outputFile = inputFile.getParent() + File.separator + "appOutput.txt"; + File file = new File(outputFile); + + try (PrintWriter out + = new PrintWriter(new BufferedWriter(new FileWriter(file)))) { + out.println(MSG); + + out.println("args.length: " + (files == null ? args.length : args.length + files.size())); + + for (String arg : args) { + out.println(arg); + } + + if (files != null) { + for (File f : files) { + out.println(f.getAbsolutePath()); + } + } + + for (int index = 1; index <= EXPECTED_NUM_OF_PARAMS; index++) { + String value = System.getProperty("param" + index); + if (value != null) { + out.println("-Dparam" + index + "=" + value); + } + } + } catch (Exception ex) { + System.err.println(ex.getMessage()); + } + } + + @Override + public void openFiles(OpenFilesEvent e) { + files = e.getFiles(); + } +} diff --git a/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageAddLauncherBase.java b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageAddLauncherBase.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageAddLauncherBase.java @@ -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. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; + +public class JPackageCreateAppImageAddLauncherBase { + private static final String app = JPackagePath.getApp(); + private static final String app2 = JPackagePath.getAppSL(); + private static final String appOutput = JPackagePath.getAppOutputFile(); + private static final String appWorkingDir = JPackagePath.getAppWorkingDir(); + + // Note: quotes in argument for add launcher is not support by test + private static final String ARGUMENT1 = "argument 1"; + private static final String ARGUMENT2 = "argument 2"; + private static final String ARGUMENT3 = "argument 3"; + + private static final List arguments = new ArrayList<>(); + + private static final String PARAM1 = "-Dparam1=Some Param 1"; + private static final String PARAM2 = "-Dparam2=Some Param 2"; + private static final String PARAM3 = "-Dparam3=Some Param 3"; + + private static final List vmArguments = new ArrayList<>(); + + private static void validateResult(List args, List vmArgs) + throws Exception { + File outfile = new File(appWorkingDir + File.separator + appOutput); + if (!outfile.exists()) { + throw new AssertionError(appOutput + " was not created"); + } + + String output = Files.readString(outfile.toPath()); + String[] result = output.split("\n"); + + if (result.length != (args.size() + vmArgs.size() + 2)) { + throw new AssertionError("Unexpected number of lines: " + + result.length); + } + + if (!result[0].trim().equals("jpackage test application")) { + throw new AssertionError("Unexpected result[0]: " + result[0]); + } + + if (!result[1].trim().equals("args.length: " + args.size())) { + throw new AssertionError("Unexpected result[1]: " + result[1]); + } + + int index = 2; + for (String arg : args) { + if (!result[index].trim().equals(arg)) { + throw new AssertionError("Unexpected result[" + index + "]: " + + result[index]); + } + index++; + } + + for (String vmArg : vmArgs) { + if (!result[index].trim().equals(vmArg)) { + throw new AssertionError("Unexpected result[" + index + "]: " + + result[index]); + } + index++; + } + } + + private static void validate() throws Exception { + int retVal = JPackageHelper.execute(null, app); + if (retVal != 0) { + throw new AssertionError("Test application exited with error: " + + retVal); + } + validateResult(new ArrayList<>(), new ArrayList<>()); + + retVal = JPackageHelper.execute(null, app2); + if (retVal != 0) { + throw new AssertionError("Test application exited with error: " + + retVal); + } + validateResult(arguments, vmArguments); + } + + public static void testCreateAppImage(String [] cmd) throws Exception { + JPackageHelper.executeCLI(true, cmd); + validate(); + } + + public static void testCreateAppImageToolProvider(String [] cmd) throws Exception { + JPackageHelper.executeToolProvider(true, cmd); + validate(); + } + + public static void createSLProperties() throws Exception { + arguments.add(ARGUMENT1); + arguments.add(ARGUMENT2); + arguments.add(ARGUMENT3); + + String argumentsMap = + JPackageHelper.listToArgumentsMap(arguments, true); + + vmArguments.add(PARAM1); + vmArguments.add(PARAM2); + vmArguments.add(PARAM3); + + String vmArgumentsMap = + JPackageHelper.listToArgumentsMap(vmArguments, true); + + try (PrintWriter out = new PrintWriter(new BufferedWriter( + new FileWriter("sl.properties")))) { + out.println("arguments=" + argumentsMap); + out.println("java-options=" + vmArgumentsMap); + } + } + +} diff --git a/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageAddLauncherModuleTest.java b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageAddLauncherModuleTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageAddLauncherModuleTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + /* + * @test + * @summary jpackage create image with additional launcher test + * @library ../helpers + * @build JPackageHelper + * @build JPackagePath + * @build JPackageCreateAppImageAddLauncherBase + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateAppImageAddLauncherModuleTest + */ +public class JPackageCreateAppImageAddLauncherModuleTest { + private static final String OUTPUT = "output"; + private static final String [] CMD = { + "create-app-image", + "--output", OUTPUT, + "--name", "test", + "--module", "com.hello/com.hello.Hello", + "--module-path", "input", + "--add-launcher", "test2=sl.properties"}; + + public static void main(String[] args) throws Exception { + JPackageHelper.createHelloModule(); + JPackageCreateAppImageAddLauncherBase.createSLProperties(); + JPackageCreateAppImageAddLauncherBase.testCreateAppImage(CMD); + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageCreateAppImageAddLauncherBase.testCreateAppImageToolProvider(CMD); + } + +} diff --git a/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageAddLauncherTest.java b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageAddLauncherTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageAddLauncherTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + /* + * @test + * @summary jpackage create image with additional launcher test + * @library ../helpers + * @build JPackageHelper + * @build JPackagePath + * @build JPackageCreateAppImageAddLauncherBase + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateAppImageAddLauncherTest + */ +public class JPackageCreateAppImageAddLauncherTest { + private static final String OUTPUT = "output"; + private static final String [] CMD = { + "create-app-image", + "--input", "input", + "--output", OUTPUT, + "--name", "test", + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--add-launcher", "test2=sl.properties"}; + + public static void main(String[] args) throws Exception { + JPackageHelper.createHelloImageJar(); + JPackageCreateAppImageAddLauncherBase.createSLProperties(); + JPackageCreateAppImageAddLauncherBase.testCreateAppImage(CMD); + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageCreateAppImageAddLauncherBase.testCreateAppImageToolProvider(CMD); + } + +} diff --git a/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageAddModulesTest.java b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageAddModulesTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageAddModulesTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + /* + * @test + * @summary jpackage create image module test + * @library ../helpers + * @build JPackageHelper + * @build JPackagePath + * @build JPackageCreateAppImageBase + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateAppImageAddModulesTest + */ +public class JPackageCreateAppImageAddModulesTest { + private static final String OUTPUT = "output"; + + private static final String [] CMD1 = { + "create-app-image", + "--output", OUTPUT, + "--name", "test", + "--module", "com.hello/com.hello.Hello", + "--module-path", "input", + "--add-modules", "java.desktop", + }; + + private static final String [] CMD2 = { + "create-app-image", + "--output", OUTPUT, + "--name", "test", + "--module", "com.hello/com.hello.Hello", + "--module-path", "input", + "--add-modules", "java.desktop,java.xml", + }; + + private static final String [] CMD3 = { + "create-app-image", + "--output", OUTPUT, + "--name", "test", + "--module", "com.hello/com.hello.Hello", + "--module-path", "input", + "--add-modules", "java.desktop", + "--add-modules", "java.xml", + }; + + public static void main(String[] args) throws Exception { + JPackageHelper.createHelloModule(); + JPackageCreateAppImageBase.testCreateAppImage(CMD1); + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageCreateAppImageBase.testCreateAppImageToolProvider(CMD1); + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageCreateAppImageBase.testCreateAppImageToolProvider(CMD2); + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageCreateAppImageBase.testCreateAppImageToolProvider(CMD3); + } + +} diff --git a/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageArgumentsBase.java b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageArgumentsBase.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageArgumentsBase.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; + +public class JPackageCreateAppImageArgumentsBase { + + private static final String app = JPackagePath.getApp(); + private static final String appOutput = JPackagePath.getAppOutputFile(); + private static final String appWorkingDir = JPackagePath.getAppWorkingDir(); + + private static final String ARGUMENT1 = "argument"; + private static final String ARGUMENT2 = "Some Arguments"; + private static final String ARGUMENT3 = "Value \"with\" quotes"; + + private static final String ARGUMENT_CMD1 = "test"; + + private static final List arguments = new ArrayList<>(); + private static final List argumentsCmd = new ArrayList<>(); + + public static void initArguments(boolean toolProvider, String[] cmd) { + if (arguments.isEmpty()) { + arguments.add(ARGUMENT1); + arguments.add(ARGUMENT2); + arguments.add(ARGUMENT3); + } + + if (argumentsCmd.isEmpty()) { + argumentsCmd.add(ARGUMENT_CMD1); + } + + String argumentsMap + = JPackageHelper.listToArgumentsMap(arguments, toolProvider); + cmd[cmd.length - 1] = argumentsMap; + } + + private static void validateResult(String[] result, List args) + throws Exception { + if (result.length != (args.size() + 2)) { + throw new AssertionError( + "Unexpected number of lines: " + result.length); + } + + if (!result[0].trim().equals("jpackage test application")) { + throw new AssertionError("Unexpected result[0]: " + result[0]); + } + + if (!result[1].trim().equals("args.length: " + args.size())) { + throw new AssertionError("Unexpected result[1]: " + result[1]); + } + + int index = 2; + for (String arg : args) { + if (!result[index].trim().equals(arg)) { + throw new AssertionError( + "Unexpected result[" + index + "]: " + result[index]); + } + index++; + } + } + + private static void validate(String arg, List expectedArgs) + throws Exception { + int retVal; + + if (arg == null) { + retVal = JPackageHelper.execute(null, app); + } else { + retVal = JPackageHelper.execute(null, app, arg); + } + if (retVal != 0) { + throw new AssertionError("Test application exited with error: " + + retVal); + } + + File outfile = new File(appWorkingDir + File.separator + appOutput); + if (!outfile.exists()) { + throw new AssertionError(appOutput + " was not created"); + } + + String output = Files.readString(outfile.toPath()); + String[] result = output.split("\n"); + validateResult(result, expectedArgs); + } + + public static void testCreateAppImage(String[] cmd) throws Exception { + initArguments(false, cmd); + JPackageHelper.executeCLI(true, cmd); + validate(null, arguments); + validate(ARGUMENT_CMD1, argumentsCmd); + } + + public static void testCreateAppImageToolProvider(String[] cmd) throws Exception { + initArguments(true, cmd); + JPackageHelper.executeToolProvider(true, cmd); + validate(null, arguments); + validate(ARGUMENT_CMD1, argumentsCmd); + } +} diff --git a/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageArgumentsModuleTest.java b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageArgumentsModuleTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageArgumentsModuleTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create image with --arguments test + * @library ../helpers + * @build JPackageHelper + * @build JPackagePath + * @build JPackageCreateAppImageArgumentsBase + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateAppImageArgumentsModuleTest + */ +public class JPackageCreateAppImageArgumentsModuleTest { + private static final String OUTPUT = "output"; + + private static final String[] CMD = { + "create-app-image", + "--output", OUTPUT, + "--name", "test", + "--module", "com.hello/com.hello.Hello", + "--module-path", "input", + "--arguments", "TBD"}; + + public static void main(String[] args) throws Exception { + JPackageHelper.createHelloModule(); + JPackageCreateAppImageArgumentsBase.testCreateAppImage(CMD); + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageCreateAppImageArgumentsBase.testCreateAppImageToolProvider(CMD); + } + +} diff --git a/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageArgumentsTest.java b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageArgumentsTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageArgumentsTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create image with --arguments test + * @library ../helpers + * @build JPackageHelper + * @build JPackagePath + * @build JPackageCreateAppImageArgumentsBase + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateAppImageArgumentsTest + */ +public class JPackageCreateAppImageArgumentsTest { + private static final String OUTPUT = "output"; + + private static final String[] CMD = { + "create-app-image", + "--input", "input", + "--output", OUTPUT, + "--name", "test", + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--arguments", "TBD"}; + + public static void main(String[] args) throws Exception { + JPackageHelper.createHelloImageJar(); + JPackageCreateAppImageArgumentsBase.testCreateAppImage(CMD); + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageCreateAppImageArgumentsBase.testCreateAppImageToolProvider(CMD); + } + +} diff --git a/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageBase.java b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageBase.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageBase.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.nio.file.Files; + +public abstract class JPackageCreateAppImageBase { + private static final String app = JPackagePath.getApp(); + private static final String appOutput = JPackagePath.getAppOutputFile(); + private static final String appWorkingDir = JPackagePath.getAppWorkingDir(); + + private static void validateResult(String[] result) throws Exception { + if (result.length != 2) { + throw new AssertionError( + "Unexpected number of lines: " + result.length); + } + + if (!result[0].trim().equals("jpackage test application")) { + throw new AssertionError("Unexpected result[0]: " + result[0]); + } + + if (!result[1].trim().equals("args.length: 0")) { + throw new AssertionError("Unexpected result[1]: " + result[1]); + } + } + + private static void validate() throws Exception { + int retVal = JPackageHelper.execute(null, app); + if (retVal != 0) { + throw new AssertionError( + "Test application exited with error: " + retVal); + } + + File outfile = new File(appWorkingDir + File.separator + appOutput); + if (!outfile.exists()) { + throw new AssertionError(appOutput + " was not created"); + } + + String output = Files.readString(outfile.toPath()); + String[] result = output.split("\n"); + validateResult(result); + } + + public static void testCreateAppImage(String [] cmd) throws Exception { + JPackageHelper.executeCLI(true, cmd); + validate(); + } + + public static void testCreateAppImageToolProvider(String [] cmd) throws Exception { + JPackageHelper.executeToolProvider(true, cmd); + validate(); + } +} diff --git a/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageErrorTest.java b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageErrorTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageErrorTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + /* + * @test + * @summary jpackage create app image error test + * @library ../helpers + * @build JPackageHelper + * @build JPackagePath + * @build JPackageCreateAppImageBase + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateAppImageErrorTest + */ +import java.util.*; +import java.io.*; +import java.nio.*; +import java.nio.file.*; +import java.nio.file.attribute.*; + +public class JPackageCreateAppImageErrorTest { + + private static final String OUTPUT = "output"; + + private static final String ARG1 = "--no-such-argument"; + private static final String EXPECTED1 = + "Invalid Option: [--no-such-argument]"; + private static final String ARG2 = "--output"; + private static final String EXPECTED2 = "Mode is not specified"; + + private static final String [] CMD1 = { + "create-app-image", + "--input", "input", + "--output", OUTPUT, + "--name", "test", + "--main-jar", "non-existant.jar", + }; + private static final String EXP1 = "main jar does not exist"; + + private static final String [] CMD2 = { + "create-app-image", + "--input", "input", + "--output", OUTPUT, + "--name", "test", + "--main-jar", "hello.jar", + }; + private static final String EXP2 = "class was not specified nor was"; + + private static void validate(String output, String expected, boolean single) + throws Exception { + String[] result = output.split("\n"); + if (single && result.length != 1) { + System.err.println(output); + throw new AssertionError("Unexpected multiple lines of output: " + + output); + } + + if (!result[0].trim().contains(expected)) { + throw new AssertionError("Unexpected output: " + result[0] + + " - expected output to contain: " + expected); + } + } + + + public static void main(String[] args) throws Exception { + JPackageHelper.createHelloImageJar(); + + validate(JPackageHelper.executeToolProvider(false, ARG1), EXPECTED1, true); + validate(JPackageHelper.executeToolProvider(false, ARG2), EXPECTED2, true); + + JPackageHelper.deleteOutputFolder(OUTPUT); + validate(JPackageHelper.executeToolProvider(false, CMD1), EXP1, false); + + JPackageHelper.deleteOutputFolder(OUTPUT); + validate(JPackageHelper.executeToolProvider(false, CMD2), EXP2, false); + + } + +} diff --git a/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageIconTest.java b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageIconTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageIconTest.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.nio.file.Files; + +/* + * @test + * @summary jpackage create image to verify --icon + * @library ../helpers + * @build JPackageHelper + * @build JPackagePath + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateAppImageIconTest + */ +public class JPackageCreateAppImageIconTest { + private static final String OUTPUT = "output"; + private static final String app = JPackagePath.getApp(); + private static final String appOutput = JPackagePath.getAppOutputFile(); + private static final String appWorkingDir = JPackagePath.getAppWorkingDir(); + + private static final String[] CMD = { + "create-app-image", + "--input", "input", + "--name", "test", + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--icon", getIconPath(), + "--output", OUTPUT}; + + private static void validateResult(String[] result) throws Exception { + if (result.length != 2) { + throw new AssertionError( + "Unexpected number of lines: " + result.length); + } + + if (!result[0].trim().equals("jpackage test application")) { + throw new AssertionError("Unexpected result[0]: " + result[0]); + } + + if (!result[1].trim().equals("args.length: 0")) { + throw new AssertionError("Unexpected result[1]: " + result[1]); + } + } + + private static void validate() throws Exception { + int retVal = JPackageHelper.execute(null, app); + if (retVal != 0) { + throw new AssertionError( + "Test application exited with error: " + retVal); + } + + File outfile = new File(appWorkingDir + File.separator + appOutput); + if (!outfile.exists()) { + throw new AssertionError(appOutput + " was not created"); + } + + String output = Files.readString(outfile.toPath()); + String[] result = output.split("\n"); + validateResult(result); + } + + private static void validateIcon() throws Exception { + File origIcon = new File(getIconPath()); + File icon = new File(JPackagePath.getAppIcon()); + if (origIcon.length() != icon.length()) { + System.err.println("origIcon.length(): " + origIcon.length()); + System.err.println("icon.length(): " + icon.length()); + throw new AssertionError("Icons size does not match"); + } + } + + private static void testIcon() throws Exception { + JPackageHelper.executeCLI(true, CMD); + validate(); + validateIcon(); + } + + private static void testIconToolProvider() throws Exception { + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageHelper.executeToolProvider(true, CMD); + validate(); + validateIcon(); + } + + private static String getIconPath() { + String ext = ".ico"; + if (JPackageHelper.isOSX()) { + ext = ".icns"; + } else if (JPackageHelper.isLinux()) { + ext = ".png"; + } + + String path = JPackagePath.getTestSrcRoot() + File.separator + "resources" + + File.separator + "icon" + ext; + + return path; + } + + public static void main(String[] args) throws Exception { + JPackageHelper.createHelloImageJar(); + testIcon(); + testIconToolProvider(); + } + +} diff --git a/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageJavaOptionsBase.java b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageJavaOptionsBase.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageJavaOptionsBase.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; + +public class JPackageCreateAppImageJavaOptionsBase { + + private static final String app = JPackagePath.getApp(); + private static final String appOutput = JPackagePath.getAppOutputFile(); + private static final String appWorkingDir = JPackagePath.getAppWorkingDir(); + + private static final String ARGUMENT1 = "-Dparam1=Some Param 1"; + private static final String ARGUMENT2 = "-Dparam2=Some \"Param\" 2"; + private static final String ARGUMENT3 = + "-Dparam3=Some \"Param\" with \" 3"; + + private static final List arguments = new ArrayList<>(); + + private static void initArguments(boolean toolProvider, String [] cmd) { + if (arguments.isEmpty()) { + arguments.add(ARGUMENT1); + arguments.add(ARGUMENT2); + arguments.add(ARGUMENT3); + } + + String argumentsMap = JPackageHelper.listToArgumentsMap(arguments, + toolProvider); + cmd[cmd.length - 1] = argumentsMap; + } + + private static void initArguments2(boolean toolProvider, String [] cmd) { + int index = cmd.length - 6; + + cmd[index++] = "--java-options"; + arguments.clear(); + arguments.add(ARGUMENT1); + cmd[index++] = JPackageHelper.listToArgumentsMap(arguments, + toolProvider); + + cmd[index++] = "--java-options"; + arguments.clear(); + arguments.add(ARGUMENT2); + cmd[index++] = JPackageHelper.listToArgumentsMap(arguments, + toolProvider); + + cmd[index++] = "--java-options"; + arguments.clear(); + arguments.add(ARGUMENT3); + cmd[index++] = JPackageHelper.listToArgumentsMap(arguments, + toolProvider); + + arguments.clear(); + arguments.add(ARGUMENT1); + arguments.add(ARGUMENT2); + arguments.add(ARGUMENT3); + } + + private static void validateResult(String[] result, List args) + throws Exception { + if (result.length != (args.size() + 2)) { + for (String r : result) { + System.err.println(r.trim()); + } + throw new AssertionError("Unexpected number of lines: " + + result.length); + } + + if (!result[0].trim().equals("jpackage test application")) { + throw new AssertionError("Unexpected result[0]: " + result[0]); + } + + if (!result[1].trim().equals("args.length: 0")) { + throw new AssertionError("Unexpected result[1]: " + result[1]); + } + + int index = 2; + for (String arg : args) { + if (!result[index].trim().equals(arg)) { + throw new AssertionError("Unexpected result[" + index + "]: " + + result[index]); + } + index++; + } + } + + private static void validate(List expectedArgs) throws Exception { + int retVal = JPackageHelper.execute(null, app); + if (retVal != 0) { + throw new AssertionError("Test application exited with error: " + + retVal); + } + + File outfile = new File(appWorkingDir + File.separator + appOutput); + if (!outfile.exists()) { + throw new AssertionError(appOutput + " was not created"); + } + + String output = Files.readString(outfile.toPath()); + String[] result = output.split("\n"); + validateResult(result, expectedArgs); + } + + public static void testCreateAppImageJavaOptions(String [] cmd) throws Exception { + initArguments(false, cmd); + JPackageHelper.executeCLI(true, cmd); + validate(arguments); + } + + public static void testCreateAppImageJavaOptionsToolProvider(String [] cmd) throws Exception { + initArguments(true, cmd); + JPackageHelper.executeToolProvider(true, cmd); + validate(arguments); + } + + public static void testCreateAppImageJavaOptions2(String [] cmd) throws Exception { + initArguments2(false, cmd); + JPackageHelper.executeCLI(true, cmd); + validate(arguments); + } + + public static void testCreateAppImageJavaOptions2ToolProvider(String [] cmd) throws Exception { + initArguments2(true, cmd); + JPackageHelper.executeToolProvider(true, cmd); + validate(arguments); + } +} diff --git a/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageJavaOptionsModuleTest.java b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageJavaOptionsModuleTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageJavaOptionsModuleTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create image with --java-options test + * @library ../helpers + * @build JPackageHelper + * @build JPackagePath + * @build JPackageCreateAppImageJavaOptionsBase + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateAppImageJavaOptionsModuleTest + */ +public class JPackageCreateAppImageJavaOptionsModuleTest { + private static final String OUTPUT = "output"; + + private static final String[] CMD = { + "create-app-image", + "--output", OUTPUT, + "--name", "test", + "--module", "com.hello/com.hello.Hello", + "--module-path", "input", + "--java-options", "TBD"}; + + private static final String[] CMD2 = { + "create-app-image", + "--output", OUTPUT, + "--name", "test", + "--module", "com.hello/com.hello.Hello", + "--module-path", "input", + "--java-options", "TBD", + "--java-options", "TBD", + "--java-options", "TBD"}; + + public static void main(String[] args) throws Exception { + JPackageHelper.createHelloModule(); + + JPackageCreateAppImageJavaOptionsBase.testCreateAppImageJavaOptions(CMD); + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageCreateAppImageJavaOptionsBase.testCreateAppImageJavaOptionsToolProvider(CMD); + + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageCreateAppImageJavaOptionsBase.testCreateAppImageJavaOptions2(CMD2); + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageCreateAppImageJavaOptionsBase.testCreateAppImageJavaOptions2ToolProvider(CMD2); + } + +} diff --git a/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageJavaOptionsTest.java b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageJavaOptionsTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageJavaOptionsTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create image with --java-options test + * @library ../helpers + * @build JPackageHelper + * @build JPackagePath + * @build JPackageCreateAppImageJavaOptionsBase + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateAppImageJavaOptionsTest + */ +public class JPackageCreateAppImageJavaOptionsTest { + private static final String OUTPUT = "output"; + + private static final String[] CMD = { + "create-app-image", + "--input", "input", + "--output", OUTPUT, + "--name", "test", + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--java-options", "TBD"}; + + private static final String[] CMD2 = { + "create-app-image", + "--input", "input", + "--output", OUTPUT, + "--name", "test", + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--java-options", "TBD", + "--java-options", "TBD", + "--java-options", "TBD"}; + + public static void main(String[] args) throws Exception { + JPackageHelper.createHelloImageJar(); + JPackageCreateAppImageJavaOptionsBase.testCreateAppImageJavaOptions(CMD); + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageCreateAppImageJavaOptionsBase.testCreateAppImageJavaOptionsToolProvider(CMD); + + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageCreateAppImageJavaOptionsBase.testCreateAppImageJavaOptions2(CMD2); + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageCreateAppImageJavaOptionsBase.testCreateAppImageJavaOptions2ToolProvider(CMD2); + } + +} diff --git a/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageMainClassAttributeTest.java b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageMainClassAttributeTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageMainClassAttributeTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.nio.file.Files; + +/* + * @test + * @summary jpackage create image with no main class arguments and with main-class attribute + * @library ../helpers + * @build JPackageHelper + * @build JPackagePath + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateAppImageMainClassAttributeTest + */ +public class JPackageCreateAppImageMainClassAttributeTest { + private static final String OUTPUT = "output"; + private static final String app = JPackagePath.getApp(); + private static final String appOutput = JPackagePath.getAppOutputFile(); + private static final String appWorkingDir = JPackagePath.getAppWorkingDir(); + + private static final String[] CMD = { + "create-app-image", + "--input", "input", + "--output", OUTPUT, + "--name", "test", + "--main-jar", "hello.jar"}; + + private static void validateResult(String[] result) throws Exception { + if (result.length != 2) { + throw new AssertionError( + "Unexpected number of lines: " + result.length); + } + + if (!result[0].trim().equals("jpackage test application")) { + throw new AssertionError("Unexpected result[0]: " + result[0]); + } + + if (!result[1].trim().equals("args.length: 0")) { + throw new AssertionError("Unexpected result[1]: " + result[1]); + } + } + + private static void validate() throws Exception { + int retVal = JPackageHelper.execute(null, app); + if (retVal != 0) { + throw new AssertionError( + "Test application exited with error: " + retVal); + } + + File outfile = new File(appWorkingDir + File.separator + appOutput); + if (!outfile.exists()) { + throw new AssertionError(appOutput + " was not created"); + } + + String output = Files.readString(outfile.toPath()); + String[] result = output.split("\n"); + validateResult(result); + } + + private static void testMainClassAttribute() throws Exception { + JPackageHelper.executeCLI(true, CMD); + validate(); + } + + private static void testMainClassAttributeToolProvider() throws Exception { + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageHelper.executeToolProvider(true, CMD); + validate(); + } + + public static void main(String[] args) throws Exception { + JPackageHelper.createHelloImageJarWithMainClass(); + testMainClassAttribute(); + testMainClassAttributeToolProvider(); + } + +} diff --git a/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageMainClassErrorTest.java b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageMainClassErrorTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageMainClassErrorTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.nio.file.Files; + +/* + * @test + * @summary jpackage create image with no main class arguments and with main-class attribute + * @library ../helpers + * @build JPackageHelper + * @build JPackagePath + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateAppImageMainClassErrorTest + */ +public class JPackageCreateAppImageMainClassErrorTest { + private static final String OUTPUT = "output"; + private static final String app = JPackagePath.getApp(); + private static final String appOutput = JPackagePath.getAppOutputFile(); + private static final String appWorkingDir = JPackagePath.getAppWorkingDir(); + + private static final String[] CMD = { + "create-app-image", + "--input", "input", + "--output", OUTPUT, + "--name", "test", + "--main-jar", "hello.jar"}; + + private static void validate(String output) throws Exception { + String[] result = output.split("\n"); + if (result.length != 2) { + throw new AssertionError( + "Unexpected number of lines: " + result.length); + } + + if (!result[0].trim().contains("main class was not specified")) { + throw new AssertionError("Unexpected result[0]: " + result[0]); + } + + if (!result[1].trim().startsWith("Advice to fix: ")) { + throw new AssertionError("Unexpected result[1]: " + result[1]); + } + } + + public static void main(String[] args) throws Exception { + JPackageHelper.createHelloImageJar(); + + JPackageHelper.deleteOutputFolder(OUTPUT); + validate(JPackageHelper.executeCLI(false, CMD)); + + JPackageHelper.deleteOutputFolder(OUTPUT); + validate(JPackageHelper.executeToolProvider(false, CMD)); + } + +} diff --git a/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageModularJarTest.java b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageModularJarTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageModularJarTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + /* + * @test + * @summary jpackage create image modular jar test + * @library ../helpers + * @build JPackageHelper + * @build JPackagePath + * @build JPackageCreateAppImageBase + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateAppImageModularJarTest + */ +public class JPackageCreateAppImageModularJarTest { + private static final String OUTPUT = "output"; + + private static final String [] CMD1 = { + "create-app-image", + "--input", "input", + "--output", OUTPUT, + "--name", "test", + "--main-jar", "com.hello.jar", + "--main-class", "com.hello.Hello", + }; + + private static final String [] CMD2 = { + "create-app-image", + "--output", OUTPUT, + "--name", "test", + "--module", "com.hello/com.hello.Hello", + "--module-path", "input/com.hello.jar", + }; + + public static void main(String[] args) throws Exception { + JPackageHelper.createHelloModule(); + + JPackageCreateAppImageBase.testCreateAppImage(CMD1); + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageCreateAppImageBase.testCreateAppImageToolProvider(CMD1); + + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageCreateAppImageBase.testCreateAppImage(CMD2); + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageCreateAppImageBase.testCreateAppImageToolProvider(CMD2); + } + +} diff --git a/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageModuleMainClassErrorTest.java b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageModuleMainClassErrorTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageModuleMainClassErrorTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.nio.file.Files; + +/* + * @test + * @summary jpackage create image with no main class arguments and with main-class attribute + * @library ../helpers + * @build JPackageHelper + * @build JPackagePath + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateAppImageModuleMainClassErrorTest + */ +public class JPackageCreateAppImageModuleMainClassErrorTest { + private static final String OUTPUT = "output"; + private static final String app = JPackagePath.getApp(); + private static final String appOutput = JPackagePath.getAppOutputFile(); + private static final String appWorkingDir = JPackagePath.getAppWorkingDir(); + + private static final String [] CMD = { + "create-app-image", + "--output", OUTPUT, + "--name", "test", + "--module", "com.hello", + "--module-path", "input"}; + + private static void validate(String buildOutput) throws Exception { + + File outfile = new File(appWorkingDir + File.separator + appOutput); + int retVal = JPackageHelper.execute(outfile, app); + if (retVal == 0) { + throw new AssertionError( + "Test application exited without error: "); + } + + if (!outfile.exists()) { + throw new AssertionError(appOutput + " was not created"); + } + String output = Files.readString(outfile.toPath()); + String[] result = output.split("\n"); + + if (result.length != 1) { + System.out.println("outfile (" + outfile + ") content: " + output); + throw new AssertionError( + "Unexpected number of lines: " + result.length); + } + + if (!result[0].trim().contains( + "does not have a ModuleMainClass attribute")) { + throw new AssertionError("Unexpected result[0]: " + result[0]); + } + } + + public static void main(String[] args) throws Exception { + JPackageHelper.createHelloModule(); + + JPackageHelper.deleteOutputFolder(OUTPUT); + validate(JPackageHelper.executeCLI(true, CMD)); + + JPackageHelper.deleteOutputFolder(OUTPUT); + validate(JPackageHelper.executeToolProvider(true, CMD)); + } + +} diff --git a/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageModulePathTest.java b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageModulePathTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageModulePathTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + /* + * @test + * @summary jpackage create image module test + * @library ../helpers + * @build JPackageHelper + * @build JPackagePath + * @build JPackageCreateAppImageBase + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateAppImageModulePathTest + */ + +import java.io.File; + +public class JPackageCreateAppImageModulePathTest { + private static final String OUTPUT = "output"; + + private static final String [] CMD1 = { + "create-app-image", + "--output", OUTPUT, + "--name", "test", + "--module", "com.hello/com.hello.Hello", + "--module-path", "input", + }; + + private static final String [] CMD2 = { + "create-app-image", + "--output", OUTPUT, + "--name", "test", + "--module", "com.hello/com.hello.Hello", + "--module-path", "input" + File.pathSeparator + "input-other", + }; + + private static final String [] CMD3 = { + "create-app-image", + "--output", OUTPUT, + "--name", "test", + "--module", "com.hello/com.hello.Hello", + "--module-path", "input", + "--module-path", "input-other", + }; + + public static void main(String[] args) throws Exception { + JPackageHelper.createHelloModule(); + JPackageCreateAppImageBase.testCreateAppImageToolProvider(CMD1); + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageCreateAppImageBase.testCreateAppImageToolProvider(CMD2); + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageCreateAppImageBase.testCreateAppImageToolProvider(CMD3); + } + +} diff --git a/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageModuleTest.java b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageModuleTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageModuleTest.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + /* + * @test + * @summary jpackage create image module test + * @library ../helpers + * @build JPackageHelper + * @build JPackagePath + * @build JPackageCreateAppImageBase + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateAppImageModuleTest + */ +public class JPackageCreateAppImageModuleTest { + private static final String OUTPUT = "output"; + + private static final String [] CMD = { + "create-app-image", + "--output", OUTPUT, + "--name", "test", + "--module", "com.hello/com.hello.Hello", + "--module-path", "input"}; + + public static void main(String[] args) throws Exception { + JPackageHelper.createHelloModule(); + JPackageCreateAppImageBase.testCreateAppImage(CMD); + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageCreateAppImageBase.testCreateAppImageToolProvider(CMD); + } + +} diff --git a/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageNoNameTest.java b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageNoNameTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageNoNameTest.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.nio.file.Files; + +/* + * @test + * @summary jpackage create image with no --name + * @library ../helpers + * @build JPackageHelper + * @build JPackagePath + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateAppImageNoNameTest + */ +public class JPackageCreateAppImageNoNameTest { + private static final String OUTPUT = "output"; + private static final String app = JPackagePath.getAppNoName(); + private static final String appOutput = JPackagePath.getAppOutputFile(); + private static final String appWorkingDir = JPackagePath.getAppWorkingDirNoName(); + + private static final String[] CMD = { + "create-app-image", + "--input", "input", + "--output", OUTPUT, + "--main-jar", "hello.jar", + "--main-class", "Hello", + }; + + private static void validateResult(String[] result) throws Exception { + if (result.length != 2) { + throw new AssertionError( + "Unexpected number of lines: " + result.length); + } + + if (!result[0].trim().equals("jpackage test application")) { + throw new AssertionError("Unexpected result[0]: " + result[0]); + } + + if (!result[1].trim().equals("args.length: 0")) { + throw new AssertionError("Unexpected result[1]: " + result[1]); + } + } + + private static void validate() throws Exception { + int retVal = JPackageHelper.execute(null, app); + if (retVal != 0) { + throw new AssertionError( + "Test application exited with error: " + retVal); + } + + File outfile = new File(appWorkingDir + File.separator + appOutput); + if (!outfile.exists()) { + throw new AssertionError(appOutput + " was not created"); + } + + String output = Files.readString(outfile.toPath()); + String[] result = output.split("\n"); + validateResult(result); + } + + private static void testMainClassAttribute() throws Exception { + JPackageHelper.executeCLI(true, CMD); + validate(); + } + + private static void testMainClassAttributeToolProvider() throws Exception { + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageHelper.executeToolProvider(true, CMD); + validate(); + } + + public static void main(String[] args) throws Exception { + JPackageHelper.createHelloImageJar(); + testMainClassAttribute(); + testMainClassAttributeToolProvider(); + } + +} diff --git a/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageRuntimeBase.java b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageRuntimeBase.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageRuntimeBase.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.nio.file.Files; + + public class JPackageCreateAppImageRuntimeBase { + private static final String app = JPackagePath.getApp(); + private static final String appWorkingDir = JPackagePath.getAppWorkingDir(); + private static final String runtimeJava = JPackagePath.getRuntimeJava(); + private static final String runtimeJavaOutput = "javaOutput.txt"; + private static final String appOutput = JPackagePath.getAppOutputFile(); + + private static void validateResult(String[] result) throws Exception { + if (result.length != 2) { + throw new AssertionError("Unexpected number of lines: " + result.length); + } + + if (!result[0].trim().equals("jpackage test application")) { + throw new AssertionError("Unexpected result[0]: " + result[0]); + } + + if (!result[1].trim().equals("args.length: 0")) { + throw new AssertionError("Unexpected result[1]: " + result[1]); + } + } + + private static void validate() throws Exception { + int retVal = JPackageHelper.execute(null, app); + if (retVal != 0) { + throw new AssertionError("Test application exited with error: " + retVal); + } + + File outfile = new File(appWorkingDir + File.separator + appOutput); + if (!outfile.exists()) { + throw new AssertionError(appOutput + " was not created"); + } + + String output = Files.readString(outfile.toPath()); + String[] result = output.split("\n"); + validateResult(result); + } + + private static void validateRuntime() throws Exception { + int retVal = JPackageHelper.execute(new File(runtimeJavaOutput), runtimeJava, "--list-modules"); + if (retVal != 0) { + throw new AssertionError("Test application exited with error: " + retVal); + } + + File outfile = new File(runtimeJavaOutput); + if (!outfile.exists()) { + throw new AssertionError(runtimeJavaOutput + " was not created"); + } + + String output = Files.readString(outfile.toPath()); + String[] result = output.split("\n"); + if (result.length != 1) { + throw new AssertionError("Unexpected number of lines: " + result.length); + } + + if (!result[0].startsWith("java.base")) { + throw new AssertionError("Unexpected result: " + result[0]); + } + } + + public static void testCreateAppImage(String [] cmd) throws Exception { + JPackageHelper.executeCLI(true, cmd); + validate(); + validateRuntime(); + } + + public static void testCreateAppImageToolProvider(String [] cmd) throws Exception { + JPackageHelper.executeToolProvider(true, cmd); + validate(); + validateRuntime(); + } + +} diff --git a/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageRuntimeModuleTest.java b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageRuntimeModuleTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageRuntimeModuleTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + /* + * @test + * @summary jpackage create image runtime test + * @library ../helpers + * @build JPackageHelper + * @build JPackagePath + * @build JPackageCreateAppImageRuntimeBase + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateAppImageRuntimeModuleTest + */ +public class JPackageCreateAppImageRuntimeModuleTest { + private static final String OUTPUT = "output"; + private static final String [] CMD = { + "create-app-image", + "--runtime-image", "runtime", + "--output", OUTPUT, + "--name", "test", + "--module", "com.hello/com.hello.Hello", + "--module-path", "input"}; + + public static void main(String[] args) throws Exception { + JPackageHelper.createHelloModule(); + JPackageHelper.createRuntime(); + JPackageCreateAppImageRuntimeBase.testCreateAppImage(CMD); + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageCreateAppImageRuntimeBase.testCreateAppImageToolProvider(CMD); + } + +} diff --git a/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageRuntimeTest.java b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageRuntimeTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageRuntimeTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + /* + * @test + * @summary jpackage create image runtime test + * @library ../helpers + * @build JPackageHelper + * @build JPackagePath + * @build JPackageCreateAppImageRuntimeBase + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateAppImageRuntimeTest + */ +public class JPackageCreateAppImageRuntimeTest { + private static final String OUTPUT = "output"; + private static final String [] CMD = { + "create-app-image", + "--runtime-image", "runtime", + "--input", "input", + "--output", OUTPUT, + "--name", "test", + "--main-jar", "hello.jar", + "--main-class", "Hello", + }; + + public static void main(String[] args) throws Exception { + JPackageHelper.createHelloImageJar(); + JPackageHelper.createRuntime(); + JPackageCreateAppImageRuntimeBase.testCreateAppImage(CMD); + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageCreateAppImageRuntimeBase.testCreateAppImageToolProvider(CMD); + } + +} diff --git a/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageTempRootTest.java b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageTempRootTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageTempRootTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; + + /* + * @test + * @requires (os.family == "windows") + * @summary jpackage create image to test --temp-root + * @library ../helpers + * @build JPackageHelper + * @build JPackagePath + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateAppImageTempRootTest + */ +public class JPackageCreateAppImageTempRootTest { + private static final String OUTPUT = "output"; + private static String buildRoot = null; + private static final String BUILD_ROOT = "buildRoot"; + private static final String BUILD_ROOT_TB = "buildRootToolProvider"; + + private static final String [] CMD = { + "create-app-image", + "--input", "input", + "--output", OUTPUT, + "--name", "test", + "--main-jar", "hello.jar", + "--main-class", "Hello", + }; + + private static final String [] CMD_BUILD_ROOT = { + "create-app-image", + "--input", "input", + "--output", OUTPUT, + "--name", "test", + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--temp-root", "TBD"}; + + private static void validate(boolean retain) throws Exception { + File br = new File(buildRoot); + if (retain) { + if (!br.exists()) { + throw new AssertionError(br.getAbsolutePath() + " does not exist"); + } + } else { + if (br.exists()) { + throw new AssertionError(br.getAbsolutePath() + " exist"); + } + } + } + + private static void init(boolean toolProvider) { + if (toolProvider) { + buildRoot = BUILD_ROOT_TB; + } else { + buildRoot = BUILD_ROOT; + } + + CMD_BUILD_ROOT[CMD_BUILD_ROOT.length - 1] = buildRoot; + } + + private static void testTempRoot() throws Exception { + init(false); + JPackageHelper.executeCLI(true, CMD); + validate(false); + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageHelper.executeCLI(true, CMD_BUILD_ROOT); + validate(true); + } + + private static void testTempRootToolProvider() throws Exception { + init(true); + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageHelper.executeToolProvider(true, CMD); + validate(false); + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageHelper.executeToolProvider(true, CMD_BUILD_ROOT); + validate(true); + } + + public static void main(String[] args) throws Exception { + JPackageHelper.createHelloImageJar(); + testTempRoot(); + testTempRootToolProvider(); + } + +} diff --git a/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageTest.java b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + /* + * @test + * @summary jpackage create image test + * @library ../helpers + * @build JPackageHelper + * @build JPackagePath + * @build JPackageCreateAppImageBase + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateAppImageTest + */ +public class JPackageCreateAppImageTest { + private static final String OUTPUT = "output"; + + private static final String [] CMD = { + "create-app-image", + "--input", "input", + "--output", OUTPUT, + "--name", "test", + "--main-jar", "hello.jar", + "--main-class", "Hello", + }; + + public static void main(String[] args) throws Exception { + JPackageHelper.createHelloImageJar(); + JPackageCreateAppImageBase.testCreateAppImage(CMD); + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageCreateAppImageBase.testCreateAppImageToolProvider(CMD); + } +} diff --git a/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageVerboseTest.java b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageVerboseTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageVerboseTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create image verbose test + * @library ../helpers + * @build JPackageHelper + * @build JPackagePath + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateAppImageVerboseTest + */ +public class JPackageCreateAppImageVerboseTest { + private static final String OUTPUT = "output"; + private static final String[] CMD = { + "create-app-image", + "--input", "input", + "--output", OUTPUT, + "--name", "test", + "--main-jar", "hello.jar", + "--main-class", "Hello", + }; + + private static final String[] CMD_VERBOSE = { + "create-app-image", + "--input", "input", + "--output", OUTPUT, + "--name", "test", + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--verbose"}; + + private static void validate(String result, String resultVerbose) + throws Exception { + String[] r = result.split("\n"); + String[] rv = resultVerbose.split("\n"); + + if (r.length >= rv.length) { + System.err.println("r.length: " + r.length); + System.err.println(result); + System.err.println("rv.length: " + rv.length); + System.err.println(resultVerbose); + throw new AssertionError( + "non-verbose output is less or equal to verbose output"); + } + } + + private static void testCreateAppImage() throws Exception { + String result = JPackageHelper.executeCLI(true, CMD); + JPackageHelper.deleteOutputFolder(OUTPUT); + String resultVerbose = JPackageHelper.executeCLI(true, CMD_VERBOSE); + validate(result, resultVerbose); + } + + private static void testCreateAppImageToolProvider() throws Exception { + JPackageHelper.deleteOutputFolder(OUTPUT); + String result = JPackageHelper.executeToolProvider(true, CMD); + JPackageHelper.deleteOutputFolder(OUTPUT); + String resultVerbose = + JPackageHelper.executeToolProvider(true, CMD_VERBOSE); + validate(result, resultVerbose); + } + + public static void main(String[] args) throws Exception { + JPackageHelper.createHelloImageJar(); + testCreateAppImage(); + testCreateAppImageToolProvider(); + } + +} diff --git a/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageVersionTest.java b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageVersionTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createappimage/JPackageCreateAppImageVersionTest.java @@ -0,0 +1,103 @@ + +import java.io.File; +import java.nio.file.Files; + +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create image --app-version test + * @library ../helpers + * @build JPackageHelper + * @build JPackagePath + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateAppImageVersionTest + */ +public class JPackageCreateAppImageVersionTest { + private static final String OUTPUT = "output"; + private static final String appCfg = JPackagePath.getAppCfg(); + private static final String VERSION = "2.3"; + private static final String VERSION_DEFAULT = "1.0"; + + private static final String[] CMD = { + "create-app-image", + "--input", "input", + "--output", OUTPUT, + "--name", "test", + "--main-jar", "hello.jar", + "--main-class", "Hello", + }; + + private static final String[] CMD_VERSION = { + "create-app-image", + "--input", "input", + "--output", OUTPUT, + "--name", "test", + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--app-version", VERSION}; + + private static void validate(String version) + throws Exception { + File outfile = new File(appCfg); + if (!outfile.exists()) { + throw new AssertionError(appCfg + " was not created"); + } + + String output = Files.readString(outfile.toPath()); + if (version == null) { + version = VERSION_DEFAULT; + } + + String expected = "app.version=" + version; + if (!output.contains(expected)) { + System.err.println("Expected: " + expected); + throw new AssertionError("Cannot find expected entry in config file"); + } + } + + private static void testVersion() throws Exception { + JPackageHelper.executeCLI(true, CMD); + validate(null); + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageHelper.executeCLI(true, CMD_VERSION); + validate(VERSION); + } + + private static void testVersionToolProvider() throws Exception { + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageHelper.executeToolProvider(true, CMD); + validate(null); + JPackageHelper.deleteOutputFolder(OUTPUT); + JPackageHelper.executeToolProvider(true, CMD_VERSION); + validate(VERSION); + } + + public static void main(String[] args) throws Exception { + JPackageHelper.createHelloImageJar(); + testVersion(); + testVersionToolProvider(); + } + +} diff --git a/test/jdk/tools/jpackage/createinstaller/linux/base/JPackageCreateInstallerBase.java b/test/jdk/tools/jpackage/createinstaller/linux/base/JPackageCreateInstallerBase.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/linux/base/JPackageCreateInstallerBase.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class JPackageCreateInstallerBase { + + private static String TEST_NAME; + private static String EXT; + private static String OUTPUT; + private static String[] CMD; + + private static void copyResults() throws Exception { + List files = new ArrayList<>(); + files.add(OUTPUT.toLowerCase()); + JPackageInstallerHelper.copyTestResults(files); + } + + private static void testCreateInstaller() throws Exception { + JPackageHelper.executeCLI(true, CMD); + JPackageInstallerHelper.validateOutput(OUTPUT); + copyResults(); + } + + private static void verifyInstall() throws Exception { + String app = JPackagePath.getLinuxInstalledApp(TEST_NAME); + JPackageInstallerHelper.validateApp(app); + } + + private static void verifyUnInstall() throws Exception { + String folderPath = JPackagePath.getLinuxInstallFolder(TEST_NAME); + File folder = new File(folderPath); + if (folder.exists()) { + throw new AssertionError("Error: " + folder.getAbsolutePath() + " exist"); + } + } + + private static void init(String name, String ext) { + TEST_NAME = name; + EXT = ext; + if (EXT.equals("rpm")) { + OUTPUT = "output" + File.separator + TEST_NAME + "-1.0-1.x86_64." + EXT; + } else { + OUTPUT = "output" + File.separator + TEST_NAME + "-1.0." + EXT; + } + CMD = new String[]{ + "create-installer", + "--installer-type", EXT, + "--input", "input", + "--output", "output", + "--name", TEST_NAME, + "--main-jar", "hello.jar", + "--main-class", "Hello" }; + } + + public static void run(String name, String ext) throws Exception { + init(name, ext); + + if (JPackageInstallerHelper.isVerifyInstall()) { + verifyInstall(); + } else if (JPackageInstallerHelper.isVerifyUnInstall()) { + verifyUnInstall(); + } else { + JPackageHelper.createHelloInstallerJar(); + testCreateInstaller(); + } + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/linux/base/JPackageCreateInstallerBundleNameBase.java b/test/jdk/tools/jpackage/createinstaller/linux/base/JPackageCreateInstallerBundleNameBase.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/linux/base/JPackageCreateInstallerBundleNameBase.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class JPackageCreateInstallerBundleNameBase { + + private static String TEST_NAME; + private static String BUNDLE_NAME; + private static String EXT; + private static String OUTPUT; + private static String[] CMD; + + private static void copyResults() throws Exception { + List files = new ArrayList<>(); + files.add(OUTPUT.toLowerCase()); + JPackageInstallerHelper.copyTestResults(files); + } + + private static void testCreateInstaller() throws Exception { + JPackageHelper.executeCLI(true, CMD); + JPackageInstallerHelper.validateOutput(OUTPUT); + copyResults(); + } + + private static void verifyInstall() throws Exception { + String app = JPackagePath.getLinuxInstalledApp(TEST_NAME); + JPackageInstallerHelper.validateApp(app); + } + + private static void verifyUnInstall() throws Exception { + String folderPath = JPackagePath.getLinuxInstallFolder(TEST_NAME); + File folder = new File(folderPath); + if (folder.exists()) { + throw new AssertionError("Error: " + folder.getAbsolutePath() + " exist"); + } + } + + private static void init(String name, String ext) { + TEST_NAME = name; + BUNDLE_NAME = "jpackage-test-bundle-name"; + EXT = ext; + if (EXT.equals("rpm")) { + OUTPUT = "output" + File.separator + BUNDLE_NAME + "-1.0-1.x86_64." + EXT; + } else { + OUTPUT = "output" + File.separator + BUNDLE_NAME + "-1.0." + EXT; + } + CMD = new String[]{ + "create-installer", + "--installer-type", EXT, + "--input", "input", + "--output", "output", + "--name", TEST_NAME, + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--linux-bundle-name", BUNDLE_NAME}; + } + + public static void run(String name, String ext) throws Exception { + init(name, ext); + + if (JPackageInstallerHelper.isVerifyInstall()) { + verifyInstall(); + } else if (JPackageInstallerHelper.isVerifyUnInstall()) { + verifyUnInstall(); + } else { + JPackageHelper.createHelloInstallerJar(); + testCreateInstaller(); + } + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/linux/base/JPackageCreateInstallerFileAssociationsBase.java b/test/jdk/tools/jpackage/createinstaller/linux/base/JPackageCreateInstallerFileAssociationsBase.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/linux/base/JPackageCreateInstallerFileAssociationsBase.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.awt.Desktop; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; + +public class JPackageCreateInstallerFileAssociationsBase { + + private static String TEST_NAME; + private static String EXT; + private static String TEST_EXT; + private static String OUTPUT; + private static String[] CMD; + + private static void copyResults() throws Exception { + List files = new ArrayList<>(); + files.add(OUTPUT.toLowerCase()); + JPackageInstallerHelper.copyTestResults(files); + } + + private static void testCreateInstaller() throws Exception { + JPackageHelper.executeCLI(true, CMD); + JPackageInstallerHelper.validateOutput(OUTPUT); + copyResults(); + } + + private static void validateAppOutput() throws Exception { + File outFile = new File("appOutput.txt"); + int count = 10; + boolean bOutputCreated = false; + while (count > 0) { + if (!outFile.exists()) { + Thread.sleep(3000); + count--; + } else { + bOutputCreated = true; + break; + } + } + + if (!bOutputCreated) { + throw new AssertionError(outFile.getAbsolutePath() + " was not created"); + } + + String output = Files.readString(outFile.toPath()); + String[] result = output.split("\n"); + if (result.length != 3) { + System.err.println(output); + throw new AssertionError( + "Unexpected number of lines: " + result.length); + } + + if (!result[0].trim().equals("jpackage test application")) { + throw new AssertionError("Unexpected result[0]: " + result[0]); + } + + if (!result[1].trim().equals("args.length: 1")) { + throw new AssertionError("Unexpected result[1]: " + result[1]); + } + + File faFile = new File(TEST_NAME + "." + TEST_EXT); + if (!result[2].trim().equals(faFile.getAbsolutePath())) { + throw new AssertionError("Unexpected result[2]: " + result[2]); + } + } + + private static void verifyInstall() throws Exception { + createFileAssociationsTestFile(); + Desktop.getDesktop().open(new File(TEST_NAME + "." + TEST_EXT)); + validateAppOutput(); + } + + private static void verifyUnInstall() throws Exception { + String folderPath = JPackagePath.getLinuxInstallFolder(TEST_NAME); + File folder = new File(folderPath); + if (folder.exists()) { + throw new AssertionError("Error: " + folder.getAbsolutePath() + " exist"); + } + } + + private static void createFileAssociationsTestFile() throws Exception { + try (PrintWriter out = new PrintWriter(new BufferedWriter( + new FileWriter(TEST_NAME + "." + TEST_EXT)))) { + out.println(TEST_NAME); + } + } + + private static void createFileAssociationsProperties() throws Exception { + try (PrintWriter out = new PrintWriter(new BufferedWriter( + new FileWriter("fa.properties")))) { + out.println("extension=" + TEST_EXT); + out.println("mime-type=application/" + TEST_EXT); + out.println("description=jpackage test extention"); + } + } + + private static void init(String name, String ext) { + TEST_NAME = name; + EXT = ext; + TEST_EXT = "jptest1"; + if (EXT.equals("rpm")) { + OUTPUT = "output" + File.separator + TEST_NAME + "-1.0-1.x86_64." + EXT; + } else { + OUTPUT = "output" + File.separator + TEST_NAME + "-1.0." + EXT; + } + CMD = new String[]{ + "create-installer", + "--installer-type", EXT, + "--input", "input", + "--output", "output", + "--name", TEST_NAME, + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--file-associations", "fa.properties"}; + } + + public static void run(String name, String ext) throws Exception { + init(name, ext); + + if (JPackageInstallerHelper.isVerifyInstall()) { + verifyInstall(); + } else if (JPackageInstallerHelper.isVerifyUnInstall()) { + verifyUnInstall(); + } else { + JPackageHelper.createHelloInstallerJar(); + createFileAssociationsProperties(); + testCreateInstaller(); + } + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/linux/base/JPackageCreateInstallerInstallDirBase.java b/test/jdk/tools/jpackage/createinstaller/linux/base/JPackageCreateInstallerInstallDirBase.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/linux/base/JPackageCreateInstallerInstallDirBase.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class JPackageCreateInstallerInstallDirBase { + + private static String TEST_NAME; + private static String EXT; + private static String OUTPUT; + private static String[] CMD; + + private static void copyResults() throws Exception { + List files = new ArrayList<>(); + files.add(OUTPUT.toLowerCase()); + JPackageInstallerHelper.copyTestResults(files); + } + + private static void testCreateInstaller() throws Exception { + JPackageHelper.executeCLI(true, CMD); + JPackageInstallerHelper.validateOutput(OUTPUT); + copyResults(); + } + + private static void verifyInstall() throws Exception { + String app = JPackagePath.getLinuxInstalledApp("jpackage", TEST_NAME); + JPackageInstallerHelper.validateApp(app); + } + + private static void verifyUnInstall() throws Exception { + String folderPath = JPackagePath.getLinuxInstallFolder("jpackage", TEST_NAME); + File folder = new File(folderPath); + if (folder.exists()) { + throw new AssertionError("Error: " + folder.getAbsolutePath() + " exist"); + } + + folderPath = JPackagePath.getLinuxInstallFolder("jpackage", null); + folder = new File(folderPath); + if (folder.exists()) { + throw new AssertionError("Error: " + folder.getAbsolutePath() + " exist"); + } + } + + private static void init(String name, String ext) { + TEST_NAME = name; + EXT = ext; + if (EXT.equals("rpm")) { + OUTPUT = "output" + File.separator + TEST_NAME + "-1.0-1.x86_64." + EXT; + } else { + OUTPUT = "output" + File.separator + TEST_NAME + "-1.0." + EXT; + } + CMD = new String[]{ + "create-installer", + "--installer-type", EXT, + "--input", "input", + "--output", "output", + "--name", TEST_NAME, + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--install-dir", "/opt/jpackage"}; + } + + public static void run(String name, String ext) throws Exception { + init(name, ext); + + if (JPackageInstallerHelper.isVerifyInstall()) { + verifyInstall(); + } else if (JPackageInstallerHelper.isVerifyUnInstall()) { + verifyUnInstall(); + } else { + JPackageHelper.createHelloInstallerJar(); + testCreateInstaller(); + } + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/linux/base/JPackageCreateInstallerLicenseBase.java b/test/jdk/tools/jpackage/createinstaller/linux/base/JPackageCreateInstallerLicenseBase.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/linux/base/JPackageCreateInstallerLicenseBase.java @@ -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. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class JPackageCreateInstallerLicenseBase { + + private static String TEST_NAME; + private static String EXT; + private static String OUTPUT; + private static String[] CMD; + + private static void copyResults() throws Exception { + List files = new ArrayList<>(); + files.add(OUTPUT.toLowerCase()); + JPackageInstallerHelper.copyTestResults(files); + } + + private static void testCreateInstaller() throws Exception { + JPackageHelper.executeCLI(true, CMD); + JPackageInstallerHelper.validateOutput(OUTPUT); + copyResults(); + } + + private static void verifyInstall() throws Exception { + String app = JPackagePath.getLinuxInstalledApp(TEST_NAME); + JPackageInstallerHelper.validateApp(app); + + } + + private static void verifyUnInstall() throws Exception { + String folderPath = JPackagePath.getLinuxInstallFolder(TEST_NAME); + File folder = new File(folderPath); + if (folder.exists()) { + throw new AssertionError("Error: " + folder.getAbsolutePath() + " exist"); + } + } + + private static void init(String name, String ext) { + TEST_NAME = name; + EXT = ext; + if (EXT.equals("rpm")) { + OUTPUT = "output" + File.separator + TEST_NAME + "-1.0-1.x86_64." + EXT; + } else { + OUTPUT = "output" + File.separator + TEST_NAME + "-1.0." + EXT; + } + CMD = new String [] { + "create-installer", + "--installer-type", EXT, + "--input", "input", + "--output", "output", + "--name", TEST_NAME, + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--license-file", JPackagePath.getLicenseFilePath()}; + } + + public static void run(String name, String ext) throws Exception { + init(name, ext); + + if (JPackageInstallerHelper.isVerifyInstall()) { + verifyInstall(); + } else if (JPackageInstallerHelper.isVerifyUnInstall()) { + verifyUnInstall(); + } else { + JPackageHelper.createHelloInstallerJar(); + testCreateInstaller(); + } + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/linux/base/JPackageCreateInstallerLicenseTypeBase.java b/test/jdk/tools/jpackage/createinstaller/linux/base/JPackageCreateInstallerLicenseTypeBase.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/linux/base/JPackageCreateInstallerLicenseTypeBase.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; + +public class JPackageCreateInstallerLicenseTypeBase { + + private static String TEST_NAME; + private static String EXT; + private static String JP_LICENSE_TYPE; + private static String OUTPUT; + private static String[] CMD; + + private static void copyResults() throws Exception { + List files = new ArrayList<>(); + files.add(OUTPUT.toLowerCase()); + JPackageInstallerHelper.copyTestResults(files); + } + + private static final String infoResult = "infoResult.txt"; + private static void validatePackage() throws Exception { + int retVal = JPackageHelper.execute(new File(infoResult),"rpm", + "--query", "--package", "--info", OUTPUT.toLowerCase()); + if (retVal != 0) { + throw new AssertionError("rpm exited with error: " + retVal); + } + + File outfile = new File(infoResult); + if (!outfile.exists()) { + throw new AssertionError(infoResult + " was not created"); + } + + String output = Files.readString(outfile.toPath()); + if (!output.contains(JP_LICENSE_TYPE)) { + throw new AssertionError("Unexpected result: " + output); + } + } + + private static void testCreateInstaller() throws Exception { + JPackageHelper.executeCLI(true, CMD); + JPackageInstallerHelper.validateOutput(OUTPUT); + validatePackage(); + copyResults(); + } + + private static void verifyInstall() throws Exception { + String app = JPackagePath.getLinuxInstalledApp(TEST_NAME); + JPackageInstallerHelper.validateApp(app); + } + + private static void verifyUnInstall() throws Exception { + String folderPath = JPackagePath.getLinuxInstallFolder(TEST_NAME); + File folder = new File(folderPath); + if (folder.exists()) { + throw new AssertionError("Error: " + folder.getAbsolutePath() + " exist"); + } + } + + private static void init(String name, String ext) { + TEST_NAME = name; + EXT = ext; + JP_LICENSE_TYPE = "JP_LICENSE_TYPE"; + if (EXT.equals("rpm")) { + OUTPUT = "output" + File.separator + TEST_NAME + "-1.0-1.x86_64." + EXT; + } else { + OUTPUT = "output" + File.separator + TEST_NAME + "-1.0." + EXT; + } + CMD = new String[]{ + "create-installer", + "--installer-type", EXT, + "--input", "input", + "--output", "output", + "--name", TEST_NAME, + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--linux-rpm-license-type", JP_LICENSE_TYPE}; + } + + public static void run(String name, String ext) throws Exception { + init(name, ext); + + if (JPackageInstallerHelper.isVerifyInstall()) { + verifyInstall(); + } else if (JPackageInstallerHelper.isVerifyUnInstall()) { + verifyUnInstall(); + } else { + JPackageHelper.createHelloInstallerJar(); + testCreateInstaller(); + } + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/linux/base/JPackageCreateInstallerMaintainerBase.java b/test/jdk/tools/jpackage/createinstaller/linux/base/JPackageCreateInstallerMaintainerBase.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/linux/base/JPackageCreateInstallerMaintainerBase.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; + +public class JPackageCreateInstallerMaintainerBase { + + private static String TEST_NAME; + private static String EMAIL; + private static String EXT; + private static String OUTPUT; + private static String[] CMD; + + private static void copyResults() throws Exception { + List files = new ArrayList<>(); + files.add(OUTPUT.toLowerCase()); + JPackageInstallerHelper.copyTestResults(files); + } + + private static final String infoResult = "infoResult.txt"; + private static void validatePackage() throws Exception { + int retVal = JPackageHelper.execute(new File(infoResult), "dpkg", + "--info", OUTPUT.toLowerCase()); + if (retVal != 0) { + throw new AssertionError("dpkg exited with error: " + retVal); + } + + File outfile = new File(infoResult); + if (!outfile.exists()) { + throw new AssertionError(infoResult + " was not created"); + } + + String output = Files.readString(outfile.toPath()); + if (!output.contains("Maintainer: " + EMAIL)) { + throw new AssertionError("Unexpected result: " + output); + } + } + + private static void testCreateInstaller() throws Exception { + JPackageHelper.executeCLI(true, CMD); + JPackageInstallerHelper.validateOutput(OUTPUT); + validatePackage(); + copyResults(); + } + + private static void verifyInstall() throws Exception { + String app = JPackagePath.getLinuxInstalledApp(TEST_NAME); + JPackageInstallerHelper.validateApp(app); + } + + private static void verifyUnInstall() throws Exception { + String folderPath = JPackagePath.getLinuxInstallFolder(TEST_NAME); + File folder = new File(folderPath); + if (folder.exists()) { + throw new AssertionError("Error: " + folder.getAbsolutePath() + " exist"); + } + } + + private static void init(String name, String ext) { + TEST_NAME = name; + EMAIL = "jpackage-test@java.com"; + EXT = ext; + OUTPUT = "output" + File.separator + TEST_NAME + "-1.0." + EXT; + CMD = new String[]{ + "create-installer", + "--installer-type", EXT, + "--input", "input", + "--output", "output", + "--name", TEST_NAME, + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--linux-deb-maintainer", EMAIL}; + } + + public static void run(String name, String ext) throws Exception { + init(name, ext); + + if (JPackageInstallerHelper.isVerifyInstall()) { + verifyInstall(); + } else if (JPackageInstallerHelper.isVerifyUnInstall()) { + verifyUnInstall(); + } else { + JPackageHelper.createHelloInstallerJar(); + testCreateInstaller(); + } + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/linux/base/JPackageCreateInstallerPackageDepsBase.java b/test/jdk/tools/jpackage/createinstaller/linux/base/JPackageCreateInstallerPackageDepsBase.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/linux/base/JPackageCreateInstallerPackageDepsBase.java @@ -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. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; + +public class JPackageCreateInstallerPackageDepsBase { + + private static String TEST_NAME; + private static String DEP_NAME; + private static String EXT; + private static String OUTPUT; + private static String OUTPUT_DEP; + private static String[] CMD; + private static String[] CMD_DEP; + + private static void copyResults() throws Exception { + List files = new ArrayList<>(); + files.add(OUTPUT.toLowerCase()); + files.add(OUTPUT_DEP.toLowerCase()); + JPackageInstallerHelper.copyTestResults(files); + } + + private static final String infoResult = "infoResult.txt"; + private static void validatePackage() throws Exception { + if (EXT.equals("rpm")) { + int retVal = JPackageHelper.execute(new File(infoResult),"rpm", + "--query", "--package", "--requires", OUTPUT.toLowerCase()); + if (retVal != 0) { + throw new AssertionError("rpm exited with error: " + retVal); + } + } else { + int retVal = JPackageHelper.execute(new File(infoResult), "dpkg", + "--info", OUTPUT.toLowerCase()); + if (retVal != 0) { + throw new AssertionError("dpkg exited with error: " + retVal); + } + } + + File outfile = new File(infoResult); + if (!outfile.exists()) { + throw new AssertionError(infoResult + " was not created"); + } + + String output = Files.readString(outfile.toPath()); + if (!output.contains(DEP_NAME.toLowerCase())) { + throw new AssertionError("Unexpected result: " + output); + } + } + + private static void testCreateInstaller() throws Exception { + JPackageHelper.executeCLI(true, CMD); + JPackageHelper.executeCLI(true, CMD_DEP); + JPackageInstallerHelper.validateOutput(OUTPUT); + JPackageInstallerHelper.validateOutput(OUTPUT_DEP); + validatePackage(); + copyResults(); + } + + private static void verifyInstall() throws Exception { + String app = JPackagePath.getLinuxInstalledApp(TEST_NAME); + JPackageInstallerHelper.validateApp(app); + + app = JPackagePath.getLinuxInstalledApp(DEP_NAME); + JPackageInstallerHelper.validateApp(app); + } + + private static void verifyUnInstall() throws Exception { + String folderPath = JPackagePath.getLinuxInstallFolder(TEST_NAME); + File folder = new File(folderPath); + if (folder.exists()) { + throw new AssertionError("Error: " + folder.getAbsolutePath() + " exist"); + } + + folderPath = JPackagePath.getLinuxInstallFolder(DEP_NAME); + folder = new File(folderPath); + if (folder.exists()) { + throw new AssertionError("Error: " + folder.getAbsolutePath() + " exist"); + } + } + + private static void init(String name, String ext) { + TEST_NAME = name; + DEP_NAME = name + "Dep"; + EXT = ext; + if (EXT.equals("rpm")) { + OUTPUT = "output" + File.separator + TEST_NAME + "-1.0-1.x86_64." + EXT; + OUTPUT_DEP = "output" + File.separator + DEP_NAME + "-1.0-1.x86_64." + EXT; + } else { + OUTPUT = "output" + File.separator + TEST_NAME + "-1.0." + EXT; + OUTPUT_DEP = "output" + File.separator + DEP_NAME + "-1.0." + EXT; + } + CMD = new String[]{ + "create-installer", + "--installer-type", EXT, + "--input", "input", + "--output", "output", + "--name", TEST_NAME, + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--linux-package-deps", DEP_NAME.toLowerCase()}; + CMD_DEP = new String[]{ + "create-installer", + "--installer-type", EXT, + "--input", "input", + "--output", "output", + "--name", DEP_NAME, + "--main-jar", "hello.jar", + "--main-class", "Hello"}; + } + + public static void run(String name, String ext) throws Exception { + init(name, ext); + + if (JPackageInstallerHelper.isVerifyInstall()) { + verifyInstall(); + } else if (JPackageInstallerHelper.isVerifyUnInstall()) { + verifyUnInstall(); + } else { + JPackageHelper.createHelloInstallerJar(); + testCreateInstaller(); + } + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/linux/deb/JPackageCreateInstallerBundleNameTest.java b/test/jdk/tools/jpackage/createinstaller/linux/deb/JPackageCreateInstallerBundleNameTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/linux/deb/JPackageCreateInstallerBundleNameTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerBundleNameBase + * @requires (os.family == "linux") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerBundleNameTest + */ +public class JPackageCreateInstallerBundleNameTest { + private static final String TEST_NAME = "JPackageCreateInstallerBundleNameTest"; + private static final String EXT = "deb"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerBundleNameBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/linux/deb/JPackageCreateInstallerFileAssociationsTest.java b/test/jdk/tools/jpackage/createinstaller/linux/deb/JPackageCreateInstallerFileAssociationsTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/linux/deb/JPackageCreateInstallerFileAssociationsTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerFileAssociationsBase + * @requires (os.family == "linux") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerFileAssociationsTest + */ +public class JPackageCreateInstallerFileAssociationsTest { + private static final String TEST_NAME = "JPackageCreateInstallerFileAssociationsTest"; + private static final String EXT = "deb"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerFileAssociationsBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/linux/deb/JPackageCreateInstallerInstallDirTest.java b/test/jdk/tools/jpackage/createinstaller/linux/deb/JPackageCreateInstallerInstallDirTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/linux/deb/JPackageCreateInstallerInstallDirTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerInstallDirBase + * @requires (os.family == "linux") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerInstallDirTest + */ +public class JPackageCreateInstallerInstallDirTest { + private static final String TEST_NAME = "JPackageCreateInstallerInstallDirTest"; + private static final String EXT = "deb"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerInstallDirBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/linux/deb/JPackageCreateInstallerLicenseTest.java b/test/jdk/tools/jpackage/createinstaller/linux/deb/JPackageCreateInstallerLicenseTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/linux/deb/JPackageCreateInstallerLicenseTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerLicenseBase + * @requires (os.family == "linux") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerLicenseTest + */ +public class JPackageCreateInstallerLicenseTest { + private static final String TEST_NAME = "JPackageCreateInstallerLicenseTest"; + private static final String EXT = "deb"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerLicenseBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/linux/deb/JPackageCreateInstallerMaintainerTest.java b/test/jdk/tools/jpackage/createinstaller/linux/deb/JPackageCreateInstallerMaintainerTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/linux/deb/JPackageCreateInstallerMaintainerTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerMaintainerBase + * @requires (os.family == "linux") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerMaintainerTest + */ +public class JPackageCreateInstallerMaintainerTest { + private static final String TEST_NAME = "JPackageCreateInstallerMaintainerTest"; + private static final String EXT = "deb"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerMaintainerBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/linux/deb/JPackageCreateInstallerPackageDepsTest.java b/test/jdk/tools/jpackage/createinstaller/linux/deb/JPackageCreateInstallerPackageDepsTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/linux/deb/JPackageCreateInstallerPackageDepsTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerPackageDepsBase + * @requires (os.family == "linux") + * @modules jdk.jpackage + * @ignore + * @run main/othervm/timeout=240 -Xmx512m JPackageCreateInstallerPackageDepsTest + */ +public class JPackageCreateInstallerPackageDepsTest { + private static final String TEST_NAME = "JPackageCreateInstallerPackageDepsTest"; + private static final String EXT = "deb"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerPackageDepsBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/linux/deb/JPackageCreateInstallerTest.java b/test/jdk/tools/jpackage/createinstaller/linux/deb/JPackageCreateInstallerTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/linux/deb/JPackageCreateInstallerTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerBase + * @requires (os.family == "linux") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerTest + */ +public class JPackageCreateInstallerTest { + private static final String TEST_NAME = "JPackageCreateInstallerTest"; + private static final String EXT = "deb"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/linux/deb/install.sh b/test/jdk/tools/jpackage/createinstaller/linux/deb/install.sh new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/linux/deb/install.sh @@ -0,0 +1,8 @@ +sudo dpkg -i jpackagecreateinstallertest-1.0.deb +sudo dpkg -i jpackagecreateinstallerfileassociationstest-1.0.deb +sudo dpkg -i jpackagecreateinstallerlicensetest-1.0.deb +sudo dpkg -i jpackagecreateinstallerinstalldirtest-1.0.deb +sudo dpkg -i jpackage-test-bundle-name-1.0.deb +sudo dpkg -i jpackagecreateinstallermaintainertest-1.0.deb +sudo dpkg -i jpackagecreateinstallerpackagedepstestdep-1.0.deb +sudo dpkg -i jpackagecreateinstallerpackagedepstest-1.0.deb \ No newline at end of file diff --git a/test/jdk/tools/jpackage/createinstaller/linux/deb/uninstall.sh b/test/jdk/tools/jpackage/createinstaller/linux/deb/uninstall.sh new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/linux/deb/uninstall.sh @@ -0,0 +1,8 @@ +sudo dpkg -r jpackagecreateinstallertest +sudo dpkg -r jpackagecreateinstallerfileassociationstest +sudo dpkg -r jpackagecreateinstallerlicensetest +sudo dpkg -r jpackagecreateinstallerinstalldirtest +sudo dpkg -r jpackage-test-bundle-name +sudo dpkg -r jpackagecreateinstallermaintainertest +sudo dpkg -r jpackagecreateinstallerpackagedepstest +sudo dpkg -r jpackagecreateinstallerpackagedepstestdep diff --git a/test/jdk/tools/jpackage/createinstaller/linux/rpm/JPackageCreateInstallerBundleNameTest.java b/test/jdk/tools/jpackage/createinstaller/linux/rpm/JPackageCreateInstallerBundleNameTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/linux/rpm/JPackageCreateInstallerBundleNameTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerBundleNameBase + * @requires (os.family == "linux") + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateInstallerBundleNameTest + */ +public class JPackageCreateInstallerBundleNameTest { + private static final String TEST_NAME = "JPackageCreateInstallerBundleNameTest"; + private static final String EXT = "rpm"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerBundleNameBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/linux/rpm/JPackageCreateInstallerFileAssociationsTest.java b/test/jdk/tools/jpackage/createinstaller/linux/rpm/JPackageCreateInstallerFileAssociationsTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/linux/rpm/JPackageCreateInstallerFileAssociationsTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerFileAssociationsBase + * @requires (os.family == "linux") + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateInstallerFileAssociationsTest + */ +public class JPackageCreateInstallerFileAssociationsTest { + private static final String TEST_NAME = "JPackageCreateInstallerFileAssociationsTest"; + private static final String EXT = "rpm"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerFileAssociationsBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/linux/rpm/JPackageCreateInstallerInstallDirTest.java b/test/jdk/tools/jpackage/createinstaller/linux/rpm/JPackageCreateInstallerInstallDirTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/linux/rpm/JPackageCreateInstallerInstallDirTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerInstallDirBase + * @requires (os.family == "linux") + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateInstallerInstallDirTest + */ +public class JPackageCreateInstallerInstallDirTest { + private static final String TEST_NAME = "JPackageCreateInstallerInstallDirTest"; + private static final String EXT = "rpm"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerInstallDirBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/linux/rpm/JPackageCreateInstallerLicenseTest.java b/test/jdk/tools/jpackage/createinstaller/linux/rpm/JPackageCreateInstallerLicenseTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/linux/rpm/JPackageCreateInstallerLicenseTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerLicenseBase + * @requires (os.family == "linux") + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateInstallerLicenseTest + */ +public class JPackageCreateInstallerLicenseTest { + private static final String TEST_NAME = "JPackageCreateInstallerLicenseTest"; + private static final String EXT = "rpm"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerLicenseBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/linux/rpm/JPackageCreateInstallerLicenseTypeTest.java b/test/jdk/tools/jpackage/createinstaller/linux/rpm/JPackageCreateInstallerLicenseTypeTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/linux/rpm/JPackageCreateInstallerLicenseTypeTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerLicenseTypeBase + * @requires (os.family == "linux") + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateInstallerLicenseTypeTest + */ +public class JPackageCreateInstallerLicenseTypeTest { + private static final String TEST_NAME = "JPackageCreateInstallerLicenseTypeTest"; + private static final String EXT = "rpm"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerLicenseTypeBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/linux/rpm/JPackageCreateInstallerPackageDepsTest.java b/test/jdk/tools/jpackage/createinstaller/linux/rpm/JPackageCreateInstallerPackageDepsTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/linux/rpm/JPackageCreateInstallerPackageDepsTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerPackageDepsBase + * @requires (os.family == "linux") + * @modules jdk.jpackage + * @run main/othervm/timeout=240 -Xmx512m JPackageCreateInstallerPackageDepsTest + */ +public class JPackageCreateInstallerPackageDepsTest { + private static final String TEST_NAME = "JPackageCreateInstallerPackageDepsTest"; + private static final String EXT = "rpm"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerPackageDepsBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/linux/rpm/JPackageCreateInstallerTest.java b/test/jdk/tools/jpackage/createinstaller/linux/rpm/JPackageCreateInstallerTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/linux/rpm/JPackageCreateInstallerTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerBase + * @requires (os.family == "linux") + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateInstallerTest + */ +public class JPackageCreateInstallerTest { + private static final String TEST_NAME = "JPackageCreateInstallerTest"; + private static final String EXT = "rpm"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/linux/rpm/install.sh b/test/jdk/tools/jpackage/createinstaller/linux/rpm/install.sh new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/linux/rpm/install.sh @@ -0,0 +1,8 @@ +sudo rpm --install jpackagecreateinstallerfileassociationstest-1.0-1.x86_64.rpm +sudo rpm --install jpackagecreateinstallerinstalldirtest-1.0-1.x86_64.rpm +sudo rpm --install jpackagecreateinstallerlicensetest-1.0-1.x86_64.rpm +sudo rpm --install jpackagecreateinstallerlicensetypetest-1.0-1.x86_64.rpm +sudo rpm --install jpackagecreateinstallerpackagedepstestdep-1.0-1.x86_64.rpm +sudo rpm --install jpackagecreateinstallerpackagedepstest-1.0-1.x86_64.rpm +sudo rpm --install jpackagecreateinstallertest-1.0-1.x86_64.rpm +sudo rpm --install jpackage-test-bundle-name-1.0-1.x86_64.rpm diff --git a/test/jdk/tools/jpackage/createinstaller/linux/rpm/uninstall.sh b/test/jdk/tools/jpackage/createinstaller/linux/rpm/uninstall.sh new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/linux/rpm/uninstall.sh @@ -0,0 +1,8 @@ +sudo rpm -e jpackagecreateinstallerfileassociationstest +sudo rpm -e jpackagecreateinstallerinstalldirtest +sudo rpm -e jpackagecreateinstallerlicensetest +sudo rpm -e jpackagecreateinstallerlicensetypetest +sudo rpm -e jpackagecreateinstallerpackagedepstest +sudo rpm -e jpackagecreateinstallerpackagedepstestdep +sudo rpm -e jpackagecreateinstallertest +sudo rpm -e jpackage-test-bundle-name diff --git a/test/jdk/tools/jpackage/createinstaller/macosx/base/JPackageCreateInstallerBase.java b/test/jdk/tools/jpackage/createinstaller/macosx/base/JPackageCreateInstallerBase.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/macosx/base/JPackageCreateInstallerBase.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class JPackageCreateInstallerBase { + + private static String TEST_NAME; + private static String EXT; + private static String OUTPUT; + private static String[] CMD; + + private static void copyResults() throws Exception { + List files = new ArrayList<>(); + files.add(OUTPUT); + JPackageInstallerHelper.copyTestResults(files); + } + + private static void testCreateInstaller() throws Exception { + JPackageHelper.executeCLI(true, CMD); + JPackageInstallerHelper.validateOutput(OUTPUT); + copyResults(); + } + + private static void verifyInstall() throws Exception { + String app = JPackagePath.getOSXInstalledApp(TEST_NAME); + JPackageInstallerHelper.validateApp(app); + } + + private static void verifyUnInstall() throws Exception { + // Not needed on OS X, since we just deleting installed application + // without using generated installer. We keeping this for consistnency + // between platforms. + } + + private static void init(String name, String ext) { + TEST_NAME = name; + EXT = ext; + OUTPUT = "output" + File.separator + TEST_NAME + "-1.0." + EXT; + CMD = new String[]{ + "create-installer", + "--installer-type", EXT, + "--input", "input", + "--output", "output", + "--name", TEST_NAME, + "--main-jar", "hello.jar", + "--main-class", "Hello"}; + } + + public static void run(String name, String ext) throws Exception { + init(name, ext); + + if (JPackageInstallerHelper.isVerifyInstall()) { + verifyInstall(); + } else if (JPackageInstallerHelper.isVerifyUnInstall()) { + verifyUnInstall(); + } else { + JPackageHelper.createHelloInstallerJar(); + testCreateInstaller(); + } + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/macosx/base/JPackageCreateInstallerFileAssociationsBase.java b/test/jdk/tools/jpackage/createinstaller/macosx/base/JPackageCreateInstallerFileAssociationsBase.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/macosx/base/JPackageCreateInstallerFileAssociationsBase.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.awt.Desktop; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; + +public class JPackageCreateInstallerFileAssociationsBase { + + private static String TEST_NAME; + private static String EXT; + private static String TEST_EXT; + private static String OUTPUT; + private static String[] CMD; + + private static void copyResults() throws Exception { + List files = new ArrayList<>(); + files.add(OUTPUT); + JPackageInstallerHelper.copyTestResults(files); + } + + private static void testCreateInstaller() throws Exception { + JPackageHelper.executeCLI(true, CMD); + JPackageInstallerHelper.validateOutput(OUTPUT); + copyResults(); + } + + private static void validateAppOutput() throws Exception { + File outFile = new File("appOutput.txt"); + int count = 10; + boolean bOutputCreated = false; + while (count > 0) { + if (!outFile.exists()) { + Thread.sleep(3000); + count--; + } else { + bOutputCreated = true; + break; + } + } + + if (!bOutputCreated) { + throw new AssertionError(outFile.getAbsolutePath() + " was not created"); + } + + String output = Files.readString(outFile.toPath()); + String[] result = output.split("\n"); + if (result.length != 3) { + System.err.println(output); + throw new AssertionError( + "Unexpected number of lines: " + result.length); + } + + if (!result[0].trim().equals("jpackage test application")) { + throw new AssertionError("Unexpected result[0]: " + result[0]); + } + + if (!result[1].trim().equals("args.length: 1")) { + throw new AssertionError("Unexpected result[1]: " + result[1]); + } + + File faFile = new File(TEST_NAME + "." + TEST_EXT); + if (!result[2].trim().equals(faFile.getAbsolutePath())) { + throw new AssertionError("Unexpected result[2]: " + result[2]); + } + } + + private static void verifyInstall() throws Exception { + createFileAssociationsTestFile(); + Desktop.getDesktop().open(new File(TEST_NAME + "." + TEST_EXT)); + validateAppOutput(); + } + + private static void verifyUnInstall() throws Exception { + // Not needed on OS X, since we just deleting installed application + // without using generated installer. We keeping this for consistnency + // between platforms. + } + + private static void createFileAssociationsTestFile() throws Exception { + try (PrintWriter out = new PrintWriter(new BufferedWriter( + new FileWriter(TEST_NAME + "." + TEST_EXT)))) { + out.println(TEST_NAME); + } + } + + private static void createFileAssociationsProperties() throws Exception { + try (PrintWriter out = new PrintWriter(new BufferedWriter( + new FileWriter("fa.properties")))) { + out.println("extension=" + TEST_EXT); + out.println("mime-type=application/" + TEST_EXT); + out.println("description=jpackage test extention"); + } + } + + private static void init(String name, String ext) { + TEST_NAME = name; + EXT = ext; + TEST_EXT = "jptest1"; + OUTPUT = "output" + File.separator + TEST_NAME + "-1.0." + EXT; + CMD = new String[]{ + "create-installer", + "--installer-type", EXT, + "--input", "input", + "--output", "output", + "--name", TEST_NAME, + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--file-associations", "fa.properties"}; + } + + public static void run(String name, String ext) throws Exception { + init(name, ext); + + if (JPackageInstallerHelper.isVerifyInstall()) { + verifyInstall(); + } else if (JPackageInstallerHelper.isVerifyUnInstall()) { + verifyUnInstall(); + } else { + JPackageHelper.createHelloInstallerJar(); + createFileAssociationsProperties(); + testCreateInstaller(); + } + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/macosx/base/JPackageCreateInstallerInstallDirBase.java b/test/jdk/tools/jpackage/createinstaller/macosx/base/JPackageCreateInstallerInstallDirBase.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/macosx/base/JPackageCreateInstallerInstallDirBase.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class JPackageCreateInstallerInstallDirBase { + + private static String TEST_NAME; + private static String EXT; + private static String OUTPUT; + private static String[] CMD; + + private static void copyResults() throws Exception { + List files = new ArrayList<>(); + files.add(OUTPUT); + JPackageInstallerHelper.copyTestResults(files); + } + + private static void testCreateInstaller() throws Exception { + JPackageHelper.executeCLI(true, CMD); + JPackageInstallerHelper.validateOutput(OUTPUT); + copyResults(); + } + + private static void verifyInstall() throws Exception { + String app = JPackagePath.getOSXInstalledApp("jpackage", TEST_NAME); + JPackageInstallerHelper.validateApp(app); + } + + private static void verifyUnInstall() throws Exception { + // Not needed on OS X, since we just deleting installed application + // without using generated installer. We keeping this for consistnency + // between platforms. + } + + private static void init(String name, String ext) { + TEST_NAME = name; + EXT = ext; + OUTPUT = "output" + File.separator + TEST_NAME + "-1.0." + EXT; + CMD = new String[]{ + "create-installer", + "--installer-type", EXT, + "--input", "input", + "--output", "output", + "--name", TEST_NAME, + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--install-dir", "/Applications/jpackage"}; + } + + public static void run(String name, String ext) throws Exception { + init(name, ext); + + if (JPackageInstallerHelper.isVerifyInstall()) { + verifyInstall(); + } else if (JPackageInstallerHelper.isVerifyUnInstall()) { + verifyUnInstall(); + } else { + JPackageHelper.createHelloInstallerJar(); + testCreateInstaller(); + } + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/macosx/base/JPackageCreateInstallerLicenseBase.java b/test/jdk/tools/jpackage/createinstaller/macosx/base/JPackageCreateInstallerLicenseBase.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/macosx/base/JPackageCreateInstallerLicenseBase.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class JPackageCreateInstallerLicenseBase { + + private static String TEST_NAME; + private static String EXT; + private static String OUTPUT; + private static String[] CMD; + + private static void copyResults() throws Exception { + List files = new ArrayList<>(); + files.add(OUTPUT); + JPackageInstallerHelper.copyTestResults(files); + } + + private static void testCreateInstaller() throws Exception { + JPackageHelper.executeCLI(true, CMD); + JPackageInstallerHelper.validateOutput(OUTPUT); + copyResults(); + } + + private static void verifyInstall() throws Exception { + String app = JPackagePath.getOSXInstalledApp(TEST_NAME); + JPackageInstallerHelper.validateApp(app); + + } + + private static void verifyUnInstall() throws Exception { + // Not needed on OS X, since we just deleting installed application + // without using generated installer. We keeping this for consistnency + // between platforms. + } + + private static void init(String name, String ext) { + TEST_NAME = name; + EXT = ext; + OUTPUT = "output" + File.separator + TEST_NAME + "-1.0." + EXT; + CMD = new String [] { + "create-installer", + "--installer-type", EXT, + "--input", "input", + "--output", "output", + "--name", TEST_NAME, + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--license-file", JPackagePath.getLicenseFilePath()}; + } + + public static void run(String name, String ext) throws Exception { + init(name, ext); + + if (JPackageInstallerHelper.isVerifyInstall()) { + verifyInstall(); + } else if (JPackageInstallerHelper.isVerifyUnInstall()) { + verifyUnInstall(); + } else { + JPackageHelper.createHelloInstallerJar(); + testCreateInstaller(); + } + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/macosx/dmg/JPackageCreateInstallerFileAssociationsTest.java b/test/jdk/tools/jpackage/createinstaller/macosx/dmg/JPackageCreateInstallerFileAssociationsTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/macosx/dmg/JPackageCreateInstallerFileAssociationsTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerFileAssociationsBase + * @requires (os.family == "mac") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerFileAssociationsTest + */ +public class JPackageCreateInstallerFileAssociationsTest { + private static final String TEST_NAME = "JPackageCreateInstallerFileAssociationsTest"; + private static final String EXT = "dmg"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerFileAssociationsBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/macosx/dmg/JPackageCreateInstallerInstallDirTest.java b/test/jdk/tools/jpackage/createinstaller/macosx/dmg/JPackageCreateInstallerInstallDirTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/macosx/dmg/JPackageCreateInstallerInstallDirTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerInstallDirBase + * @requires (os.family == "mac") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerInstallDirTest + */ +public class JPackageCreateInstallerInstallDirTest { + private static final String TEST_NAME = "JPackageCreateInstallerInstallDirTest"; + private static final String EXT = "dmg"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerInstallDirBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/macosx/dmg/JPackageCreateInstallerLicenseTest.java b/test/jdk/tools/jpackage/createinstaller/macosx/dmg/JPackageCreateInstallerLicenseTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/macosx/dmg/JPackageCreateInstallerLicenseTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerLicenseBase + * @requires (os.family == "mac") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerLicenseTest + */ +public class JPackageCreateInstallerLicenseTest { + private static final String TEST_NAME = "JPackageCreateInstallerLicenseTest"; + private static final String EXT = "dmg"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerLicenseBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/macosx/dmg/JPackageCreateInstallerTest.java b/test/jdk/tools/jpackage/createinstaller/macosx/dmg/JPackageCreateInstallerTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/macosx/dmg/JPackageCreateInstallerTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerBase + * @requires (os.family == "mac") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerTest + */ +public class JPackageCreateInstallerTest { + private static final String TEST_NAME = "JPackageCreateInstallerTest"; + private static final String EXT = "dmg"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/macosx/dmg/install.sh b/test/jdk/tools/jpackage/createinstaller/macosx/dmg/install.sh new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/macosx/dmg/install.sh @@ -0,0 +1,21 @@ +echo "Note: This script will install DMG files silently. In order to verify UI, each .dmg needs to launched manually via Finder." + +# JPackageCreateInstallerTest +hdiutil attach JPackageCreateInstallerTest-1.0.dmg +sudo /usr/sbin/installer -pkg /Volumes/JPackageCreateInstallerTest/JPackageCreateInstallerTest-1.0.pkg -target / +hdiutil detach /Volumes/JPackageCreateInstallerTest/ + +# JPackageCreateInstallerLicenseTest +hdiutil attach JPackageCreateInstallerLicenseTest-1.0.dmg +sudo /usr/sbin/installer -pkg /Volumes/JPackageCreateInstallerLicenseTest/JPackageCreateInstallerLicenseTest-1.0.pkg -target / +hdiutil detach /Volumes/JPackageCreateInstallerLicenseTest/ + +# JPackageCreateInstallerFileAssociationsTest +hdiutil attach JPackageCreateInstallerFileAssociationsTest-1.0.dmg +sudo /usr/sbin/installer -pkg /Volumes/JPackageCreateInstallerFileAssociationsTest/JPackageCreateInstallerFileAssociationsTest-1.0.pkg -target / +hdiutil detach /Volumes/JPackageCreateInstallerFileAssociationsTest/ + +# JPackageCreateInstallerInstallDirTest +hdiutil attach JPackageCreateInstallerInstallDirTest-1.0.dmg +sudo /usr/sbin/installer -pkg /Volumes/JPackageCreateInstallerInstallDirTest/JPackageCreateInstallerInstallDirTest-1.0.pkg -target / +hdiutil detach /Volumes/JPackageCreateInstallerInstallDirTest/ diff --git a/test/jdk/tools/jpackage/createinstaller/macosx/dmg/uninstall.sh b/test/jdk/tools/jpackage/createinstaller/macosx/dmg/uninstall.sh new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/macosx/dmg/uninstall.sh @@ -0,0 +1,4 @@ +sudo rm -rf /Applications/JPackageCreateInstallerTest.app +sudo rm -rf /Applications/JPackageCreateInstallerLicenseTest.app +sudo rm -rf /Applications/JPackageCreateInstallerFileAssociationsTest.app +sudo rm -rf /Applications/jpackage/JPackageCreateInstallerInstallDirTest.app diff --git a/test/jdk/tools/jpackage/createinstaller/macosx/pkg/JPackageCreateInstallerFileAssociationsTest.java b/test/jdk/tools/jpackage/createinstaller/macosx/pkg/JPackageCreateInstallerFileAssociationsTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/macosx/pkg/JPackageCreateInstallerFileAssociationsTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerFileAssociationsBase + * @requires (os.family == "mac") + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateInstallerFileAssociationsTest + */ +public class JPackageCreateInstallerFileAssociationsTest { + private static final String TEST_NAME = "JPackageCreateInstallerFileAssociationsTest"; + private static final String EXT = "pkg"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerFileAssociationsBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/macosx/pkg/JPackageCreateInstallerInstallDirTest.java b/test/jdk/tools/jpackage/createinstaller/macosx/pkg/JPackageCreateInstallerInstallDirTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/macosx/pkg/JPackageCreateInstallerInstallDirTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerInstallDirBase + * @requires (os.family == "mac") + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateInstallerInstallDirTest + */ +public class JPackageCreateInstallerInstallDirTest { + private static final String TEST_NAME = "JPackageCreateInstallerInstallDirTest"; + private static final String EXT = "pkg"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerInstallDirBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/macosx/pkg/JPackageCreateInstallerLicenseTest.java b/test/jdk/tools/jpackage/createinstaller/macosx/pkg/JPackageCreateInstallerLicenseTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/macosx/pkg/JPackageCreateInstallerLicenseTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerLicenseBase + * @requires (os.family == "mac") + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateInstallerLicenseTest + */ +public class JPackageCreateInstallerLicenseTest { + private static final String TEST_NAME = "JPackageCreateInstallerLicenseTest"; + private static final String EXT = "pkg"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerLicenseBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/macosx/pkg/JPackageCreateInstallerTest.java b/test/jdk/tools/jpackage/createinstaller/macosx/pkg/JPackageCreateInstallerTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/macosx/pkg/JPackageCreateInstallerTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerBase + * @requires (os.family == "mac") + * @modules jdk.jpackage + * @run main/othervm -Xmx512m JPackageCreateInstallerTest + */ +public class JPackageCreateInstallerTest { + private static final String TEST_NAME = "JPackageCreateInstallerTest"; + private static final String EXT = "pkg"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/macosx/pkg/install.sh b/test/jdk/tools/jpackage/createinstaller/macosx/pkg/install.sh new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/macosx/pkg/install.sh @@ -0,0 +1,5 @@ +echo Note: This script will install packages silently. In order to verify UI, each .pkg needs to launched manually via Finder. +sudo /usr/sbin/installer -pkg JPackageCreateInstallerTest-1.0.pkg -target / +sudo /usr/sbin/installer -pkg JPackageCreateInstallerLicenseTest-1.0.pkg -target / +sudo /usr/sbin/installer -pkg JPackageCreateInstallerFileAssociationsTest-1.0.pkg -target / +sudo /usr/sbin/installer -pkg JPackageCreateInstallerInstallDirTest-1.0.pkg -target / diff --git a/test/jdk/tools/jpackage/createinstaller/macosx/pkg/uninstall.sh b/test/jdk/tools/jpackage/createinstaller/macosx/pkg/uninstall.sh new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/macosx/pkg/uninstall.sh @@ -0,0 +1,4 @@ +sudo rm -rf /Applications/JPackageCreateInstallerTest.app +sudo rm -rf /Applications/JPackageCreateInstallerLicenseTest.app +sudo rm -rf /Applications/JPackageCreateInstallerFileAssociationsTest.app +sudo rm -rf /Applications/jpackage/JPackageCreateInstallerInstallDirTest.app diff --git a/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerBase.java b/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerBase.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerBase.java @@ -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. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class JPackageCreateInstallerBase { + + private static String TEST_NAME; + private static String EXT; + private static String OUTPUT; + private static String[] CMD; + + private static void copyResults() throws Exception { + List files = new ArrayList<>(); + files.add(OUTPUT); + JPackageInstallerHelper.copyTestResults(files); + } + + private static void testCreateInstaller() throws Exception { + JPackageHelper.executeCLI(true, CMD); + JPackageInstallerHelper.validateOutput(OUTPUT); + copyResults(); + } + + private static void verifyInstall() throws Exception { + String app = JPackagePath.getWinInstalledApp(TEST_NAME); + JPackageInstallerHelper.validateApp(app); + + // Validate start menu + JPackageInstallerHelper.validateStartMenu("Unknown", TEST_NAME, true); + } + + private static void verifyUnInstall() throws Exception { + String folderPath = JPackagePath.getWinInstallFolder(TEST_NAME); + File folder = new File(folderPath); + if (folder.exists()) { + throw new AssertionError("Error: " + folder.getAbsolutePath() + " exist"); + } + + // Validate start menu + JPackageInstallerHelper.validateStartMenu("Unknown", TEST_NAME, false); + } + + private static void init(String name, String ext) { + TEST_NAME = name; + EXT = ext; + OUTPUT = "output" + File.separator + TEST_NAME + "-1.0." + EXT; + CMD = new String[]{ + "create-installer", + "--installer-type", EXT, + "--input", "input", + "--output", "output", + "--name", TEST_NAME, + "--main-jar", "hello.jar", + "--main-class", "Hello"}; + } + + public static void run(String name, String ext) throws Exception { + init(name, ext); + + if (JPackageInstallerHelper.isVerifyInstall()) { + verifyInstall(); + } else if (JPackageInstallerHelper.isVerifyUnInstall()) { + verifyUnInstall(); + } else { + JPackageHelper.createHelloInstallerJar(); + testCreateInstaller(); + } + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerFileAssociationsBase.java b/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerFileAssociationsBase.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerFileAssociationsBase.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.awt.Desktop; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; + +public class JPackageCreateInstallerFileAssociationsBase { + + private static String TEST_NAME; + private static String EXT; + private static String TEST_EXT; + private static String OUTPUT; + private static String[] CMD; + + private static void copyResults() throws Exception { + List files = new ArrayList<>(); + files.add(OUTPUT); + JPackageInstallerHelper.copyTestResults(files); + } + + private static void testCreateInstaller() throws Exception { + JPackageHelper.executeCLI(true, CMD); + JPackageInstallerHelper.validateOutput(OUTPUT); + copyResults(); + } + + private static void validateAppOutput() throws Exception { + File outFile = new File("appOutput.txt"); + int count = 10; + boolean bOutputCreated = false; + while (count > 0) { + if (!outFile.exists()) { + Thread.sleep(3000); + count--; + } else { + bOutputCreated = true; + break; + } + } + + if (!bOutputCreated) { + throw new AssertionError(outFile.getAbsolutePath() + " was not created"); + } + + String output = Files.readString(outFile.toPath()); + String[] result = output.split("\n"); + if (result.length != 3) { + System.err.println(output); + throw new AssertionError( + "Unexpected number of lines: " + result.length); + } + + if (!result[0].trim().equals("jpackage test application")) { + throw new AssertionError("Unexpected result[0]: " + result[0]); + } + + if (!result[1].trim().equals("args.length: 1")) { + throw new AssertionError("Unexpected result[1]: " + result[1]); + } + + File faFile = new File(TEST_NAME + "." + TEST_EXT); + if (!result[2].trim().equals(faFile.getAbsolutePath())) { + throw new AssertionError("Unexpected result[2]: " + result[2]); + } + } + + private static void verifyInstall() throws Exception { + createFileAssociationsTestFile(); + Desktop.getDesktop().open(new File(TEST_NAME + "." + TEST_EXT)); + validateAppOutput(); + + // Validate start menu + JPackageInstallerHelper.validateStartMenu("Unknown", TEST_NAME, true); + + // Validate registry + String[] values1 = {TEST_NAME}; + JPackageInstallerHelper.validateWinRegistry("HKLM\\Software\\Classes\\." + TEST_EXT, values1, true); + String[] values2 = {TEST_EXT}; + JPackageInstallerHelper.validateWinRegistry("HKLM\\Software\\Classes\\MIME\\Database\\Content Type\\application/" + TEST_EXT, values2, true); + } + + private static void verifyUnInstall() throws Exception { + String folderPath = JPackagePath.getWinInstallFolder(TEST_NAME); + File folder = new File(folderPath); + if (folder.exists()) { + throw new AssertionError("Error: " + folder.getAbsolutePath() + " exist"); + } + + // Validate start menu + JPackageInstallerHelper.validateStartMenu("Unknown", TEST_NAME, false); + + // Validate registry + JPackageInstallerHelper.validateWinRegistry("HKLM\\Software\\Classes\\." + TEST_EXT, null, false); + JPackageInstallerHelper.validateWinRegistry("HKLM\\Software\\Classes\\MIME\\Database\\Content Type\\application/" + TEST_EXT, null, false); + } + + private static void createFileAssociationsTestFile() throws Exception { + try (PrintWriter out = new PrintWriter(new BufferedWriter( + new FileWriter(TEST_NAME + "." + TEST_EXT)))) { + out.println(TEST_NAME); + } + } + + private static void createFileAssociationsProperties() throws Exception { + try (PrintWriter out = new PrintWriter(new BufferedWriter( + new FileWriter("fa.properties")))) { + out.println("extension=" + TEST_EXT); + out.println("mime-type=application/" + TEST_EXT); + out.println("description=jpackage test extention"); + } + } + + private static void init(String name, String ext, String installDir, String testExt) { + TEST_NAME = name; + EXT = ext; + TEST_EXT = testExt; + OUTPUT = "output" + File.separator + TEST_NAME + "-1.0." + EXT; + if (installDir == null) { + CMD = new String[]{ + "create-installer", + "--installer-type", EXT, + "--input", "input", + "--output", "output", + "--name", TEST_NAME, + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--file-associations", "fa.properties"}; + } else { + CMD = new String[]{ + "create-installer", + "--installer-type", EXT, + "--input", "input", + "--output", "output", + "--name", TEST_NAME, + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--file-associations", "fa.properties", + "--install-dir", installDir}; + } + } + + public static void run(String name, String ext, String installDir, String testExt) throws Exception { + init(name, ext, installDir, testExt); + + if (JPackageInstallerHelper.isVerifyInstall()) { + verifyInstall(); + } else if (JPackageInstallerHelper.isVerifyUnInstall()) { + verifyUnInstall(); + } else { + JPackageHelper.createHelloInstallerJar(); + createFileAssociationsProperties(); + testCreateInstaller(); + } + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerInstallDirBase.java b/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerInstallDirBase.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerInstallDirBase.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class JPackageCreateInstallerInstallDirBase { + + private static String TEST_NAME; + private static String EXT; + private static String OUTPUT; + private static String[] CMD; + private static String INSTALL_DIR; + + private static void copyResults() throws Exception { + List files = new ArrayList<>(); + files.add(OUTPUT); + JPackageInstallerHelper.copyTestResults(files); + } + + private static void testCreateInstaller() throws Exception { + JPackageHelper.executeCLI(true, CMD); + JPackageInstallerHelper.validateOutput(OUTPUT); + copyResults(); + } + + private static void verifyInstall() throws Exception { + String app = JPackagePath.getWinInstalledApp(INSTALL_DIR, TEST_NAME); + JPackageInstallerHelper.validateApp(app); + + // Validate start menu + JPackageInstallerHelper.validateStartMenu("Unknown", TEST_NAME, false); + + // Validate desktop shortcut + JPackageInstallerHelper.validateDesktopShortcut(TEST_NAME, true); + } + + private static void verifyUnInstall() throws Exception { + String folderPath = JPackagePath.getWinInstallFolder(INSTALL_DIR); + File folder = new File(folderPath); + if (folder.exists()) { + throw new AssertionError("Error: " + folder.getAbsolutePath() + " exist"); + } + + // Validate start menu + JPackageInstallerHelper.validateStartMenu("Unknown", TEST_NAME, false); + + // Validate desktop shortcut + JPackageInstallerHelper.validateDesktopShortcut(TEST_NAME, false); + } + + private static void init(String name, String ext) { + TEST_NAME = name; + EXT = ext; + OUTPUT = "output" + File.separator + TEST_NAME + "-1.0." + EXT; + INSTALL_DIR = "TestVendor\\JPackageCreateInstallerInstallDirTestDir"; + CMD = new String[]{ + "create-installer", + "--installer-type", EXT, + "--input", "input", + "--output", "output", + "--name", TEST_NAME, + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--install-dir", INSTALL_DIR, + "--win-shortcut"}; + } + + public static void run(String name, String ext) throws Exception { + init(name, ext); + + if (JPackageInstallerHelper.isVerifyInstall()) { + verifyInstall(); + } else if (JPackageInstallerHelper.isVerifyUnInstall()) { + verifyUnInstall(); + } else { + JPackageHelper.createHelloInstallerJar(); + testCreateInstaller(); + } + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerLicenseBase.java b/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerLicenseBase.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerLicenseBase.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class JPackageCreateInstallerLicenseBase { + + private static String TEST_NAME; + private static String EXT; + private static String OUTPUT; + private static String[] CMD; + + private static void copyResults() throws Exception { + List files = new ArrayList<>(); + files.add(OUTPUT); + JPackageInstallerHelper.copyTestResults(files); + } + + private static void testCreateInstaller() throws Exception { + JPackageHelper.executeCLI(true, CMD); + JPackageInstallerHelper.validateOutput(OUTPUT); + copyResults(); + } + + private static void verifyInstall() throws Exception { + String app = JPackagePath.getWinInstalledApp(TEST_NAME); + JPackageInstallerHelper.validateApp(app); + + // Validate start menu + JPackageInstallerHelper.validateStartMenu("Unknown", TEST_NAME, true); + } + + private static void verifyUnInstall() throws Exception { + String folderPath = JPackagePath.getWinInstallFolder(TEST_NAME); + File folder = new File(folderPath); + if (folder.exists()) { + throw new AssertionError("Error: " + folder.getAbsolutePath() + " exist"); + } + + // Validate start menu + JPackageInstallerHelper.validateStartMenu("Unknown", TEST_NAME, false); + } + + private static void init(String name, String ext) { + TEST_NAME = name; + EXT = ext; + OUTPUT = "output" + File.separator + TEST_NAME + "-1.0." + EXT; + CMD = new String [] { + "create-installer", + "--installer-type", EXT, + "--input", "input", + "--output", "output", + "--name", TEST_NAME, + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--license-file", JPackagePath.getLicenseFilePath()}; + } + + public static void run(String name, String ext) throws Exception { + init(name, ext); + + if (JPackageInstallerHelper.isVerifyInstall()) { + verifyInstall(); + } else if (JPackageInstallerHelper.isVerifyUnInstall()) { + verifyUnInstall(); + } else { + JPackageHelper.createHelloInstallerJar(); + testCreateInstaller(); + } + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerWinDirChooserBase.java b/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerWinDirChooserBase.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerWinDirChooserBase.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class JPackageCreateInstallerWinDirChooserBase { + + private static String TEST_NAME; + private static String EXT; + private static String OUTPUT; + private static String[] CMD; + + private static void copyResults() throws Exception { + List files = new ArrayList<>(); + files.add(OUTPUT); + JPackageInstallerHelper.copyTestResults(files); + } + + private static void testCreateInstaller() throws Exception { + JPackageHelper.executeCLI(true, CMD); + JPackageInstallerHelper.validateOutput(OUTPUT); + copyResults(); + } + + private static void verifyInstall() throws Exception { + String app = JPackagePath.getWinInstalledApp(TEST_NAME); + JPackageInstallerHelper.validateApp(app); + + // Validate start menu + JPackageInstallerHelper.validateStartMenu("Unknown", TEST_NAME, true); + } + + private static void verifyUnInstall() throws Exception { + String folderPath = JPackagePath.getWinInstallFolder(TEST_NAME); + File folder = new File(folderPath); + if (folder.exists()) { + throw new AssertionError("Error: " + folder.getAbsolutePath() + " exist"); + } + + // Validate start menu + JPackageInstallerHelper.validateStartMenu("Unknown", TEST_NAME, false); + } + + private static void init(String name, String ext) { + TEST_NAME = name; + EXT = ext; + OUTPUT = "output" + File.separator + TEST_NAME + "-1.0." + EXT; + CMD = new String[]{ + "create-installer", + "--installer-type", EXT, + "--input", "input", + "--output", "output", + "--name", TEST_NAME, + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--win-dir-chooser"}; + } + + public static void run(String name, String ext) throws Exception { + init(name, ext); + + if (JPackageInstallerHelper.isVerifyInstall()) { + verifyInstall(); + } else if (JPackageInstallerHelper.isVerifyUnInstall()) { + verifyUnInstall(); + } else { + JPackageHelper.createHelloInstallerJar(); + testCreateInstaller(); + } + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerWinMenuBase.java b/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerWinMenuBase.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerWinMenuBase.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class JPackageCreateInstallerWinMenuBase { + + private static String TEST_NAME; + private static String EXT; + private static String OUTPUT; + private static String[] CMD; + + private static void copyResults() throws Exception { + List files = new ArrayList<>(); + files.add(OUTPUT); + JPackageInstallerHelper.copyTestResults(files); + } + + private static void testCreateInstaller() throws Exception { + JPackageHelper.executeCLI(true, CMD); + JPackageInstallerHelper.validateOutput(OUTPUT); + copyResults(); + } + + private static void verifyInstall() throws Exception { + String app = JPackagePath.getWinInstalledApp(TEST_NAME); + JPackageInstallerHelper.validateApp(app); + + // Validate start menu + JPackageInstallerHelper.validateStartMenu("Unknown", TEST_NAME, true); + } + + private static void verifyUnInstall() throws Exception { + String folderPath = JPackagePath.getWinInstallFolder(TEST_NAME); + File folder = new File(folderPath); + if (folder.exists()) { + throw new AssertionError("Error: " + folder.getAbsolutePath() + " exist"); + } + + // Validate start menu + JPackageInstallerHelper.validateStartMenu("Unknown", TEST_NAME, false); + } + + private static void init(String name, String ext) { + TEST_NAME = name; + EXT = ext; + OUTPUT = "output" + File.separator + TEST_NAME + "-1.0." + EXT; + CMD = new String[]{ + "create-installer", + "--installer-type", EXT, + "--input", "input", + "--output", "output", + "--name", TEST_NAME, + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--win-menu"}; + } + + public static void run(String name, String ext) throws Exception { + init(name, ext); + + if (JPackageInstallerHelper.isVerifyInstall()) { + verifyInstall(); + } else if (JPackageInstallerHelper.isVerifyUnInstall()) { + verifyUnInstall(); + } else { + JPackageHelper.createHelloInstallerJar(); + testCreateInstaller(); + } + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerWinMenuGroupBase.java b/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerWinMenuGroupBase.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerWinMenuGroupBase.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class JPackageCreateInstallerWinMenuGroupBase { + + private static String TEST_NAME; + private static String EXT; + private static String OUTPUT; + private static String[] CMD; + + private static void copyResults() throws Exception { + List files = new ArrayList<>(); + files.add(OUTPUT); + JPackageInstallerHelper.copyTestResults(files); + } + + private static void testCreateInstaller() throws Exception { + JPackageHelper.executeCLI(true, CMD); + JPackageInstallerHelper.validateOutput(OUTPUT); + copyResults(); + } + + private static void verifyInstall() throws Exception { + String app = JPackagePath.getWinInstalledApp(TEST_NAME); + JPackageInstallerHelper.validateApp(app); + + // Validate start menu + JPackageInstallerHelper.validateStartMenu(TEST_NAME, TEST_NAME, true); + } + + private static void verifyUnInstall() throws Exception { + String folderPath = JPackagePath.getWinInstallFolder(TEST_NAME); + File folder = new File(folderPath); + if (folder.exists()) { + throw new AssertionError("Error: " + folder.getAbsolutePath() + " exist"); + } + + // Validate start menu + JPackageInstallerHelper.validateStartMenu(TEST_NAME, TEST_NAME, false); + } + + private static void init(String name, String ext) { + TEST_NAME = name; + EXT = ext; + OUTPUT = "output" + File.separator + TEST_NAME + "-1.0." + EXT; + CMD = new String[]{ + "create-installer", + "--installer-type", EXT, + "--input", "input", + "--output", "output", + "--name", TEST_NAME, + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--win-menu", + "--win-menu-group", TEST_NAME}; + } + + public static void run(String name, String ext) throws Exception { + init(name, ext); + + if (JPackageInstallerHelper.isVerifyInstall()) { + verifyInstall(); + } else if (JPackageInstallerHelper.isVerifyUnInstall()) { + verifyUnInstall(); + } else { + JPackageHelper.createHelloInstallerJar(); + testCreateInstaller(); + } + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerWinPerUserInstallBase.java b/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerWinPerUserInstallBase.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerWinPerUserInstallBase.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class JPackageCreateInstallerWinPerUserInstallBase { + + private static String TEST_NAME; + private static String EXT; + private static String OUTPUT; + private static String[] CMD; + + private static void copyResults() throws Exception { + List files = new ArrayList<>(); + files.add(OUTPUT); + JPackageInstallerHelper.copyTestResults(files); + } + + private static void testCreateInstaller() throws Exception { + JPackageHelper.executeCLI(true, CMD); + JPackageInstallerHelper.validateOutput(OUTPUT); + copyResults(); + } + + private static void verifyInstall() throws Exception { + String app = JPackagePath.getWinUserLocalInstalledApp(TEST_NAME); + JPackageInstallerHelper.validateApp(app); + + // Validate start menu + JPackageInstallerHelper.validateUserLocalStartMenu(TEST_NAME, TEST_NAME, true); + } + + private static void verifyUnInstall() throws Exception { + String folderPath = JPackagePath.getWinUserLocalInstallFolder(TEST_NAME); + File folder = new File(folderPath); + if (folder.exists()) { + throw new AssertionError("Error: " + folder.getAbsolutePath() + " exist"); + } + + // Validate start menu + JPackageInstallerHelper.validateUserLocalStartMenu(TEST_NAME, TEST_NAME, false); + } + + private static void init(String name, String ext) { + TEST_NAME = name; + EXT = ext; + OUTPUT = "output" + File.separator + TEST_NAME + "-1.0." + EXT; + CMD = new String[]{ + "create-installer", + "--installer-type", EXT, + "--input", "input", + "--output", "output", + "--name", TEST_NAME, + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--win-per-user-install", + "--win-menu", + "--win-menu-group", TEST_NAME}; + } + + public static void run(String name, String ext) throws Exception { + init(name, ext); + + if (JPackageInstallerHelper.isVerifyInstall()) { + verifyInstall(); + } else if (JPackageInstallerHelper.isVerifyUnInstall()) { + verifyUnInstall(); + } else { + JPackageHelper.createHelloInstallerJar(); + testCreateInstaller(); + } + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerWinRegistryNameBase.java b/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerWinRegistryNameBase.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerWinRegistryNameBase.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.awt.Desktop; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; + +public class JPackageCreateInstallerWinRegistryNameBase { + + private static String TEST_NAME; + private static String EXT; + private static String TEST_EXT; + private static String WIN_REGISTRY_NAME; + private static String OUTPUT; + private static String[] CMD; + + private static void copyResults() throws Exception { + List files = new ArrayList<>(); + files.add(OUTPUT); + JPackageInstallerHelper.copyTestResults(files); + } + + private static void testCreateInstaller() throws Exception { + JPackageHelper.executeCLI(true, CMD); + JPackageInstallerHelper.validateOutput(OUTPUT); + copyResults(); + } + + private static void validateAppOutput() throws Exception { + File outFile = new File("appOutput.txt"); + int count = 10; + boolean bOutputCreated = false; + while (count > 0) { + if (!outFile.exists()) { + Thread.sleep(3000); + count--; + } else { + bOutputCreated = true; + break; + } + } + + if (!bOutputCreated) { + throw new AssertionError(outFile.getAbsolutePath() + " was not created"); + } + + String output = Files.readString(outFile.toPath()); + String[] result = output.split("\n"); + if (result.length != 3) { + System.err.println(output); + throw new AssertionError( + "Unexpected number of lines: " + result.length); + } + + if (!result[0].trim().equals("jpackage test application")) { + throw new AssertionError("Unexpected result[0]: " + result[0]); + } + + if (!result[1].trim().equals("args.length: 1")) { + throw new AssertionError("Unexpected result[1]: " + result[1]); + } + + File faFile = new File(TEST_NAME + "." + TEST_EXT); + if (!result[2].trim().equals(faFile.getAbsolutePath())) { + throw new AssertionError("Unexpected result[2]: " + result[2]); + } + } + + private static void verifyInstall() throws Exception { + createFileAssociationsTestFile(); + Desktop.getDesktop().open(new File(TEST_NAME + "." + TEST_EXT)); + validateAppOutput(); + + // Validate start menu + JPackageInstallerHelper.validateStartMenu("Unknown", TEST_NAME, true); + + // Validate registry + String[] values1 = {WIN_REGISTRY_NAME}; + JPackageInstallerHelper.validateWinRegistry("HKLM\\Software\\Classes\\." + TEST_EXT, values1, true); + String[] values2 = {TEST_EXT}; + JPackageInstallerHelper.validateWinRegistry("HKLM\\Software\\Classes\\MIME\\Database\\Content Type\\application/" + TEST_EXT, values2, true); + } + + private static void verifyUnInstall() throws Exception { + String folderPath = JPackagePath.getWinInstallFolder(TEST_NAME); + File folder = new File(folderPath); + if (folder.exists()) { + throw new AssertionError("Error: " + folder.getAbsolutePath() + " exist"); + } + + // Validate start menu + JPackageInstallerHelper.validateStartMenu("Unknown", TEST_NAME, false); + + // Validate registry + JPackageInstallerHelper.validateWinRegistry("HKLM\\Software\\Classes\\." + TEST_EXT, null, false); + JPackageInstallerHelper.validateWinRegistry("HKLM\\Software\\Classes\\MIME\\Database\\Content Type\\application/" + TEST_EXT, null, false); + } + + private static void createFileAssociationsTestFile() throws Exception { + try (PrintWriter out = new PrintWriter(new BufferedWriter( + new FileWriter(TEST_NAME + "." + TEST_EXT)))) { + out.println(TEST_NAME); + } + } + + private static void createFileAssociationsProperties() throws Exception { + try (PrintWriter out = new PrintWriter(new BufferedWriter( + new FileWriter("fa.properties")))) { + out.println("extension=" + TEST_EXT); + out.println("mime-type=application/" + TEST_EXT); + out.println("description=jpackage test extention"); + } + } + + private static void init(String name, String ext) { + TEST_NAME = name; + EXT = ext; + TEST_EXT = "jptest2"; + WIN_REGISTRY_NAME = "JPWINTESTREGISTRYNAME"; + OUTPUT = "output" + File.separator + TEST_NAME + "-1.0." + EXT; + CMD = new String[]{ + "create-installer", + "--installer-type", EXT, + "--input", "input", + "--output", "output", + "--name", TEST_NAME, + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--file-associations", "fa.properties", + "--win-registry-name", WIN_REGISTRY_NAME}; + } + + public static void run(String name, String ext) throws Exception { + init(name, ext); + + if (JPackageInstallerHelper.isVerifyInstall()) { + verifyInstall(); + } else if (JPackageInstallerHelper.isVerifyUnInstall()) { + verifyUnInstall(); + } else { + JPackageHelper.createHelloInstallerJar(); + createFileAssociationsProperties(); + testCreateInstaller(); + } + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerWinShortcutBase.java b/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerWinShortcutBase.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerWinShortcutBase.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class JPackageCreateInstallerWinShortcutBase { + + private static String TEST_NAME; + private static String EXT; + private static String OUTPUT; + private static String[] CMD; + + private static void copyResults() throws Exception { + List files = new ArrayList<>(); + files.add(OUTPUT); + JPackageInstallerHelper.copyTestResults(files); + } + + private static void testCreateInstaller() throws Exception { + JPackageHelper.executeCLI(true, CMD); + JPackageInstallerHelper.validateOutput(OUTPUT); + copyResults(); + } + + private static void verifyInstall() throws Exception { + String app = JPackagePath.getWinInstalledApp(TEST_NAME); + JPackageInstallerHelper.validateApp(app); + + // Validate start menu + JPackageInstallerHelper.validateStartMenu("Unknown", TEST_NAME, false); + + // Validate desktop shortcut + JPackageInstallerHelper.validateDesktopShortcut(TEST_NAME, true); + } + + private static void verifyUnInstall() throws Exception { + String folderPath = JPackagePath.getWinInstallFolder(TEST_NAME); + File folder = new File(folderPath); + if (folder.exists()) { + throw new AssertionError("Error: " + folder.getAbsolutePath() + " exist"); + } + + // Validate start menu + JPackageInstallerHelper.validateStartMenu("Unknown", TEST_NAME, false); + + // Validate desktop shortcut + JPackageInstallerHelper.validateDesktopShortcut(TEST_NAME, false); + } + + private static void init(String name, String ext) { + TEST_NAME = name; + EXT = ext; + OUTPUT = "output" + File.separator + TEST_NAME + "-1.0." + EXT; + CMD = new String[]{ + "create-installer", + "--installer-type", EXT, + "--input", "input", + "--output", "output", + "--name", TEST_NAME, + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--win-shortcut"}; + } + + public static void run(String name, String ext) throws Exception { + init(name, ext); + + if (JPackageInstallerHelper.isVerifyInstall()) { + verifyInstall(); + } else if (JPackageInstallerHelper.isVerifyUnInstall()) { + verifyUnInstall(); + } else { + JPackageHelper.createHelloInstallerJar(); + testCreateInstaller(); + } + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerWinUpgradeUUIDBase.java b/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerWinUpgradeUUIDBase.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/base/JPackageCreateInstallerWinUpgradeUUIDBase.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +public class JPackageCreateInstallerWinUpgradeUUIDBase { + + private static String TEST_NAME; + private static String EXT; + private static String OUTPUT_1; + private static String[] CMD_1; + private static String OUTPUT_2; + private static String[] CMD_2; + private static final String FILE_1 = "file1.txt"; + private static final String FILE_2 = "file2.txt"; + + private static void copyResults() throws Exception { + List files = new ArrayList<>(); + files.add(OUTPUT_1); + files.add(OUTPUT_2); + JPackageInstallerHelper.copyTestResults(files); + } + + private static void testCreateInstaller() throws Exception { + JPackageHelper.executeCLI(true, CMD_1); + JPackageInstallerHelper.validateOutput(OUTPUT_1); + JPackageHelper.executeCLI(true, CMD_2); + JPackageInstallerHelper.validateOutput(OUTPUT_2); + copyResults(); + } + + private static void verifyInstall() throws Exception { + String app = JPackagePath.getWinInstalledApp(TEST_NAME); + JPackageInstallerHelper.validateApp(app); + + String file1Path = JPackagePath.getWinInstalledAppFolder(TEST_NAME) + File.separator + FILE_1; + File file1 = new File(file1Path); + if (EXT.equals("msi")) { + if (file1.exists()) { + throw new AssertionError("Unexpected file does exist: " + + file1.getAbsolutePath()); + } + } else if (EXT.equals("exe")) { + if (!file1.exists()) { + throw new AssertionError("Unexpected file does not exist: " + + file1.getAbsolutePath()); + } + } else { + throw new AssertionError("Unknown installer type: " + EXT); + } + + String file2Path = JPackagePath.getWinInstalledAppFolder(TEST_NAME) + File.separator + FILE_2; + File file2 = new File(file2Path); + if (!file2.exists()) { + throw new AssertionError("Unexpected file does not exist: " + + file2.getAbsolutePath()); + } + } + + private static void verifyUnInstall() throws Exception { + String folderPath = JPackagePath.getWinInstallFolder(TEST_NAME); + File folder = new File(folderPath); + if (folder.exists()) { + throw new AssertionError("Error: " + folder.getAbsolutePath() + " exist"); + } + } + + private static void init(String name, String ext) { + TEST_NAME = name; + EXT = ext; + OUTPUT_1 = "output" + File.separator + TEST_NAME + "-1.0." + EXT; + CMD_1 = new String[]{ + "create-installer", + "--installer-type", EXT, + "--input", "input", + "--output", "output", + "--name", TEST_NAME, + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--app-version", "1.0", + "--win-upgrade-uuid", "F0B18E75-52AD-41A2-BC86-6BE4FCD50BEB"}; + OUTPUT_2 = "output" + File.separator + TEST_NAME + "-2.0." + EXT; + CMD_2 = new String[]{ + "create-installer", + "--installer-type", EXT, + "--input", "input", + "--output", "output", + "--name", TEST_NAME, + "--main-jar", "hello.jar", + "--main-class", "Hello", + "--app-version", "2.0", + "--win-upgrade-uuid", "F0B18E75-52AD-41A2-BC86-6BE4FCD50BEB"}; + } + + private static void createInputFile(String name, String context) throws Exception { + try (PrintWriter out = new PrintWriter( + new BufferedWriter(new FileWriter("input" + File.separator + name)))) { + out.println(context); + } + } + + public static void run(String name, String ext) throws Exception { + init(name, ext); + + if (JPackageInstallerHelper.isVerifyInstall()) { + verifyInstall(); + } else if (JPackageInstallerHelper.isVerifyUnInstall()) { + verifyUnInstall(); + } else { + JPackageHelper.createHelloInstallerJar(); + createInputFile(FILE_1, FILE_1); + createInputFile(FILE_2, FILE_2); + testCreateInstaller(); + } + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerFileAssociationsInstallDirTest.java b/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerFileAssociationsInstallDirTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerFileAssociationsInstallDirTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerFileAssociationsBase + * @requires (os.family == "windows") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerFileAssociationsInstallDirTest + */ +public class JPackageCreateInstallerFileAssociationsInstallDirTest { + private static final String TEST_NAME = "JPackageCreateInstallerFileAssociationsInstallDirTest"; + private static final String EXT = "exe"; + private static final String INSTALL_DIR + = "TestVendor\\JPackageCreateInstallerFileAssociationsInstallDirTestDir"; + private static final String TEST_EXT = "jptest3"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerFileAssociationsBase.run(TEST_NAME, EXT, INSTALL_DIR, TEST_EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerFileAssociationsTest.java b/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerFileAssociationsTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerFileAssociationsTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerFileAssociationsBase + * @requires (os.family == "windows") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerFileAssociationsTest + */ +public class JPackageCreateInstallerFileAssociationsTest { + private static final String TEST_NAME = "JPackageCreateInstallerFileAssociationsTest"; + private static final String EXT = "exe"; + private static final String TEST_EXT = "jptest1"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerFileAssociationsBase.run(TEST_NAME, EXT, null, TEST_EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerInstallDirTest.java b/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerInstallDirTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerInstallDirTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer install dir test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerInstallDirBase + * @requires (os.family == "windows") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerInstallDirTest + */ +public class JPackageCreateInstallerInstallDirTest { + private static final String TEST_NAME = "JPackageCreateInstallerInstallDirTest"; + private static final String EXT = "exe"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerInstallDirBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerLicenseTest.java b/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerLicenseTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerLicenseTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerLicenseBase + * @requires (os.family == "windows") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerLicenseTest + */ +public class JPackageCreateInstallerLicenseTest { + private static final String TEST_NAME = "JPackageCreateInstallerLicenseTest"; + private static final String EXT = "exe"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerLicenseBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerTest.java b/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerBase + * @requires (os.family == "windows") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerTest + */ +public class JPackageCreateInstallerTest { + private static final String TEST_NAME = "JPackageCreateInstallerTest"; + private static final String EXT = "exe"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerWinDirChooserTest.java b/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerWinDirChooserTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerWinDirChooserTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerWinDirChooserBase + * @requires (os.family == "windows") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerWinDirChooserTest + */ +public class JPackageCreateInstallerWinDirChooserTest { + private static final String TEST_NAME = "JPackageCreateInstallerWinDirChooserTest"; + private static final String EXT = "exe"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerWinDirChooserBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerWinMenuGroupTest.java b/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerWinMenuGroupTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerWinMenuGroupTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerWinMenuGroupBase + * @requires (os.family == "windows") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerWinMenuGroupTest + */ +public class JPackageCreateInstallerWinMenuGroupTest { + private static final String TEST_NAME = "JPackageCreateInstallerWinMenuGroupTest"; + private static final String EXT = "exe"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerWinMenuGroupBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerWinMenuTest.java b/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerWinMenuTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerWinMenuTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerWinMenuBase + * @requires (os.family == "windows") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerWinMenuTest + */ +public class JPackageCreateInstallerWinMenuTest { + private static final String TEST_NAME = "JPackageCreateInstallerWinMenuTest"; + private static final String EXT = "exe"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerWinMenuBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerWinPerUserInstallTest.java b/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerWinPerUserInstallTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerWinPerUserInstallTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerWinPerUserInstallBase + * @requires (os.family == "windows") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerWinPerUserInstallTest + */ +public class JPackageCreateInstallerWinPerUserInstallTest { + private static final String TEST_NAME = "JPackageCreateInstallerWinPerUserInstallTest"; + private static final String EXT = "exe"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerWinPerUserInstallBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerWinRegistryNameTest.java b/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerWinRegistryNameTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerWinRegistryNameTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerWinRegistryNameBase + * @requires (os.family == "windows") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerWinRegistryNameTest + */ +public class JPackageCreateInstallerWinRegistryNameTest { + private static final String TEST_NAME = "JPackageCreateInstallerWinRegistryNameTest"; + private static final String EXT = "exe"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerWinRegistryNameBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerWinShortcutTest.java b/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerWinShortcutTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerWinShortcutTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerWinShortcutBase + * @requires (os.family == "windows") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerWinShortcutTest + */ +public class JPackageCreateInstallerWinShortcutTest { + private static final String TEST_NAME = "JPackageCreateInstallerWinShortcutTest"; + private static final String EXT = "exe"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerWinShortcutBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerWinUpgradeUUIDTest.java b/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerWinUpgradeUUIDTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/exe/JPackageCreateInstallerWinUpgradeUUIDTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerWinUpgradeUUIDBase + * @requires (os.family == "windows") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerWinUpgradeUUIDTest + */ +public class JPackageCreateInstallerWinUpgradeUUIDTest { + private static final String TEST_NAME = "JPackageCreateInstallerWinUpgradeUUIDTest"; + private static final String EXT = "exe"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerWinUpgradeUUIDBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/exe/install.bat b/test/jdk/tools/jpackage/createinstaller/windows/exe/install.bat new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/exe/install.bat @@ -0,0 +1,16 @@ +JPackageCreateInstallerTest-1.0.exe +JPackageCreateInstallerWinMenuTest-1.0.exe +JPackageCreateInstallerWinMenuGroupTest-1.0.exe +JPackageCreateInstallerWinPerUserInstallTest-1.0.exe +JPackageCreateInstallerFileAssociationsTest-1.0.exe +ECHO Make sure that installer asks to select installation location. Install to default one. +JPackageCreateInstallerWinDirChooserTest-1.0.exe +JPackageCreateInstallerWinRegistryNameTest-1.0.exe +JPackageCreateInstallerWinShortcutTest-1.0.exe +ECHO Make sure that installer shows license +JPackageCreateInstallerLicenseTest-1.0.exe +JPackageCreateInstallerWinUpgradeUUIDTest-1.0.exe +JPackageCreateInstallerWinUpgradeUUIDTest-2.0.exe +JPackageCreateInstallerInstallDirTest-1.0.exe +JPackageCreateInstallerFileAssociationsInstallDirTest-1.0.exe +PAUSE diff --git a/test/jdk/tools/jpackage/createinstaller/windows/exe/uninstall.bat b/test/jdk/tools/jpackage/createinstaller/windows/exe/uninstall.bat new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/exe/uninstall.bat @@ -0,0 +1,13 @@ +"%ProgramFiles%\JPackageCreateInstallerTest\unins000.exe" +"%ProgramFiles%\JPackageCreateInstallerWinMenuTest\unins000.exe" +"%ProgramFiles%\JPackageCreateInstallerWinMenuGroupTest\unins000.exe" +"%localappdata%\JPackageCreateInstallerWinPerUserInstallTest\unins000.exe" +"%ProgramFiles%\JPackageCreateInstallerFileAssociationsTest\unins000.exe" +"%ProgramFiles%\JPackageCreateInstallerWinDirChooserTest\unins000.exe" +"%ProgramFiles%\JPackageCreateInstallerWinRegistryNameTest\unins000.exe" +"%ProgramFiles%\JPackageCreateInstallerWinShortcutTest\unins000.exe" +"%ProgramFiles%\JPackageCreateInstallerLicenseTest\unins000.exe" +"%ProgramFiles%\JPackageCreateInstallerWinUpgradeUUIDTest\unins000.exe" +"%ProgramFiles%\TestVendor\JPackageCreateInstallerInstallDirTestDir\unins000.exe" +"%ProgramFiles%\TestVendor\JPackageCreateInstallerFileAssociationsInstallDirTestDir\unins000.exe" +PAUSE \ No newline at end of file diff --git a/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerFileAssociationsInstallDirTest.java b/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerFileAssociationsInstallDirTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerFileAssociationsInstallDirTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerFileAssociationsBase + * @requires (os.family == "windows") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerFileAssociationsInstallDirTest + */ +public class JPackageCreateInstallerFileAssociationsInstallDirTest { + private static final String TEST_NAME = "JPackageCreateInstallerFileAssociationsInstallDirTest"; + private static final String EXT = "msi"; + private static final String INSTALL_DIR + = "TestVendor\\JPackageCreateInstallerFileAssociationsInstallDirTestDir"; + private static final String TEST_EXT = "jptest3"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerFileAssociationsBase.run(TEST_NAME, EXT, INSTALL_DIR, TEST_EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerFileAssociationsTest.java b/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerFileAssociationsTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerFileAssociationsTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerFileAssociationsBase + * @requires (os.family == "windows") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerFileAssociationsTest + */ +public class JPackageCreateInstallerFileAssociationsTest { + private static final String TEST_NAME = "JPackageCreateInstallerFileAssociationsTest"; + private static final String EXT = "msi"; + private static final String TEST_EXT = "jptest1"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerFileAssociationsBase.run(TEST_NAME, EXT, null, TEST_EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerInstallDirTest.java b/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerInstallDirTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerInstallDirTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer install dir test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerInstallDirBase + * @requires (os.family == "windows") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerInstallDirTest + */ +public class JPackageCreateInstallerInstallDirTest { + private static final String TEST_NAME = "JPackageCreateInstallerInstallDirTest"; + private static final String EXT = "msi"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerInstallDirBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerLicenseTest.java b/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerLicenseTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerLicenseTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerLicenseBase + * @requires (os.family == "windows") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerLicenseTest + */ +public class JPackageCreateInstallerLicenseTest { + private static final String TEST_NAME = "JPackageCreateInstallerLicenseTest"; + private static final String EXT = "msi"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerLicenseBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerTest.java b/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerBase + * @requires (os.family == "windows") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerTest + */ +public class JPackageCreateInstallerTest { + private static final String TEST_NAME = "JPackageCreateInstallerTest"; + private static final String EXT = "msi"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerWinDirChooserTest.java b/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerWinDirChooserTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerWinDirChooserTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerWinDirChooserBase + * @requires (os.family == "windows") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerWinDirChooserTest + */ +public class JPackageCreateInstallerWinDirChooserTest { + private static final String TEST_NAME = "JPackageCreateInstallerWinDirChooserTest"; + private static final String EXT = "msi"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerWinDirChooserBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerWinMenuGroupTest.java b/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerWinMenuGroupTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerWinMenuGroupTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerWinMenuGroupBase + * @requires (os.family == "windows") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerWinMenuGroupTest + */ +public class JPackageCreateInstallerWinMenuGroupTest { + private static final String TEST_NAME = "JPackageCreateInstallerWinMenuGroupTest"; + private static final String EXT = "msi"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerWinMenuGroupBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerWinMenuTest.java b/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerWinMenuTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerWinMenuTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerWinMenuBase + * @requires (os.family == "windows") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerWinMenuTest + */ +public class JPackageCreateInstallerWinMenuTest { + private static final String TEST_NAME = "JPackageCreateInstallerWinMenuTest"; + private static final String EXT = "msi"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerWinMenuBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerWinPerUserInstallTest.java b/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerWinPerUserInstallTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerWinPerUserInstallTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerWinPerUserInstallBase + * @requires (os.family == "windows") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerWinPerUserInstallTest + */ +public class JPackageCreateInstallerWinPerUserInstallTest { + private static final String TEST_NAME = "JPackageCreateInstallerWinPerUserInstallTest"; + private static final String EXT = "msi"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerWinPerUserInstallBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerWinRegistryNameTest.java b/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerWinRegistryNameTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerWinRegistryNameTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerWinRegistryNameBase + * @requires (os.family == "windows") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerWinRegistryNameTest + */ +public class JPackageCreateInstallerWinRegistryNameTest { + private static final String TEST_NAME = "JPackageCreateInstallerWinRegistryNameTest"; + private static final String EXT = "msi"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerWinRegistryNameBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerWinShortcutTest.java b/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerWinShortcutTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerWinShortcutTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerWinShortcutBase + * @requires (os.family == "windows") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerWinShortcutTest + */ +public class JPackageCreateInstallerWinShortcutTest { + private static final String TEST_NAME = "JPackageCreateInstallerWinShortcutTest"; + private static final String EXT = "msi"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerWinShortcutBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerWinUpgradeUUIDTest.java b/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerWinUpgradeUUIDTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/msi/JPackageCreateInstallerWinUpgradeUUIDTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary jpackage create installer test + * @library ../../../helpers + * @library ../base + * @build JPackageHelper + * @build JPackagePath + * @build JPackageInstallerHelper + * @build JPackageCreateInstallerWinUpgradeUUIDBase + * @requires (os.family == "windows") + * @modules jdk.jpackage + * @ignore + * @run main/othervm -Xmx512m JPackageCreateInstallerWinUpgradeUUIDTest + */ +public class JPackageCreateInstallerWinUpgradeUUIDTest { + private static final String TEST_NAME = "JPackageCreateInstallerWinUpgradeUUIDTest"; + private static final String EXT = "msi"; + + public static void main(String[] args) throws Exception { + JPackageCreateInstallerWinUpgradeUUIDBase.run(TEST_NAME, EXT); + } +} diff --git a/test/jdk/tools/jpackage/createinstaller/windows/msi/install.bat b/test/jdk/tools/jpackage/createinstaller/windows/msi/install.bat new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/msi/install.bat @@ -0,0 +1,16 @@ +JPackageCreateInstallerTest-1.0.msi +JPackageCreateInstallerWinMenuTest-1.0.msi +JPackageCreateInstallerWinMenuGroupTest-1.0.msi +JPackageCreateInstallerWinPerUserInstallTest-1.0.msi +JPackageCreateInstallerFileAssociationsTest-1.0.msi +ECHO Make sure that installer asks to select installation location. Install to default one. +JPackageCreateInstallerWinDirChooserTest-1.0.msi +JPackageCreateInstallerWinRegistryNameTest-1.0.msi +JPackageCreateInstallerWinShortcutTest-1.0.msi +ECHO Make sure that installer shows license +JPackageCreateInstallerLicenseTest-1.0.msi +JPackageCreateInstallerWinUpgradeUUIDTest-1.0.msi +JPackageCreateInstallerWinUpgradeUUIDTest-2.0.msi +JPackageCreateInstallerInstallDirTest-1.0.msi +JPackageCreateInstallerFileAssociationsInstallDirTest-1.0.msi +PAUSE diff --git a/test/jdk/tools/jpackage/createinstaller/windows/msi/uninstall.bat b/test/jdk/tools/jpackage/createinstaller/windows/msi/uninstall.bat new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/createinstaller/windows/msi/uninstall.bat @@ -0,0 +1,13 @@ +MSIEXEC /uninstall JPackageCreateInstallerTest-1.0.msi +MSIEXEC /uninstall JPackageCreateInstallerWinMenuTest-1.0.msi +MSIEXEC /uninstall JPackageCreateInstallerWinMenuGroupTest-1.0.msi +MSIEXEC /uninstall JPackageCreateInstallerWinPerUserInstallTest-1.0.msi +MSIEXEC /uninstall JPackageCreateInstallerFileAssociationsTest-1.0.msi +MSIEXEC /uninstall JPackageCreateInstallerWinDirChooserTest-1.0.msi +MSIEXEC /uninstall JPackageCreateInstallerWinRegistryNameTest-1.0.msi +MSIEXEC /uninstall JPackageCreateInstallerWinShortcutTest-1.0.msi +MSIEXEC /uninstall JPackageCreateInstallerLicenseTest-1.0.msi +MSIEXEC /uninstall JPackageCreateInstallerWinUpgradeUUIDTest-2.0.msi +MSIEXEC /uninstall JPackageCreateInstallerInstallDirTest-1.0.msi +MSIEXEC /uninstall JPackageCreateInstallerFileAssociationsInstallDirTest-1.0.msi +PAUSE \ No newline at end of file diff --git a/test/jdk/tools/jpackage/helpers/JPackageHelper.java b/test/jdk/tools/jpackage/helpers/JPackageHelper.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/helpers/JPackageHelper.java @@ -0,0 +1,580 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.file.FileVisitResult; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.List; + +import java.util.spi.ToolProvider; + +public class JPackageHelper { + + private static final boolean VERBOSE = false; + private static final String OS = System.getProperty("os.name").toLowerCase(); + private static final String JAVA_HOME = System.getProperty("java.home"); + public static final String TEST_SRC_ROOT; + public static final String TEST_SRC; + private static final Path BIN_DIR = Path.of(JAVA_HOME, "bin"); + private static final Path JPACKAGE; + private static final Path JAVAC; + private static final Path JAR; + private static final Path JLINK; + + static { + if (OS.startsWith("win")) { + JPACKAGE = BIN_DIR.resolve("jpackage.exe"); + JAVAC = BIN_DIR.resolve("javac.exe"); + JAR = BIN_DIR.resolve("jar.exe"); + JLINK = BIN_DIR.resolve("jlink.exe"); + } else { + JPACKAGE = BIN_DIR.resolve("jpackage"); + JAVAC = BIN_DIR.resolve("javac"); + JAR = BIN_DIR.resolve("jar"); + JLINK = BIN_DIR.resolve("jlink"); + } + + // Figure out test src based on where we called + File testSrc = new File(System.getProperty("test.src") + File.separator + ".." + + File.separator + "apps"); + if (testSrc.exists()) { + TEST_SRC_ROOT = System.getProperty("test.src") + File.separator + ".."; + } else { + testSrc = new File(System.getProperty("test.src") + File.separator + + ".." + File.separator + ".." + File.separator + "apps"); + if (testSrc.exists()) { + TEST_SRC_ROOT = System.getProperty("test.src") + File.separator + ".." + + File.separator + ".."; + } else { + testSrc = new File(System.getProperty("test.src") + File.separator + + ".." + File.separator + ".." + File.separator + ".." + + File.separator + "apps"); + if (testSrc.exists()) { + TEST_SRC_ROOT = System.getProperty("test.src") + File.separator + ".." + + File.separator + ".." + File.separator + ".."; + } else { + TEST_SRC_ROOT = System.getProperty("test.src"); + } + } + } + + TEST_SRC = System.getProperty("test.src"); + } + + static final ToolProvider JPACKAGE_TOOL = + ToolProvider.findFirst("jpackage").orElseThrow( + () -> new RuntimeException("jpackage tool not found")); + + public static int execute(File out, String... command) throws Exception { + if (VERBOSE) { + System.out.print("Execute command: "); + for (String c : command) { + System.out.print(c); + System.out.print(" "); + } + System.out.println(); + } + + ProcessBuilder builder = new ProcessBuilder(command); + if (out != null) { + builder.redirectErrorStream(true); + builder.redirectOutput(out); + } + + Process process = builder.start(); + return process.waitFor(); + } + + public static Process executeNoWait(File out, String... command) throws Exception { + if (VERBOSE) { + System.out.print("Execute command: "); + for (String c : command) { + System.out.print(c); + System.out.print(" "); + } + System.out.println(); + } + + ProcessBuilder builder = new ProcessBuilder(command); + if (out != null) { + builder.redirectErrorStream(true); + builder.redirectOutput(out); + } + + return builder.start(); + } + + private static String[] getCommand(String... args) { + String[] command; + if (args == null) { + command = new String[1]; + } else { + command = new String[args.length + 1]; + } + + int index = 0; + command[index] = JPACKAGE.toString(); + + if (args != null) { + for (String arg : args) { + index++; + command[index] = arg; + } + } + + return command; + } + + public static void deleteRecursive(File path) throws IOException { + if (!path.exists()) { + return; + } + + Path directory = path.toPath(); + Files.walkFileTree(directory, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, + BasicFileAttributes attr) throws IOException { + if (OS.startsWith("win")) { + Files.setAttribute(file, "dos:readonly", false); + } + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, + BasicFileAttributes attr) throws IOException { + if (OS.startsWith("win")) { + Files.setAttribute(dir, "dos:readonly", false); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException e) + throws IOException { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + }); + } + + public static void deleteOutputFolder(String output) throws IOException { + File outputFolder = new File(output); + System.out.println("AMDEBUG output: " + outputFolder.getAbsolutePath()); + try { + deleteRecursive(outputFolder); + } catch (IOException ioe) { + System.out.println("IOException: " + ioe); + ioe.printStackTrace(); + deleteRecursive(outputFolder); + } + } + + public static String executeCLI(boolean retValZero, String... args) throws Exception { + int retVal; + File outfile = new File("output.log"); + try { + String[] command = getCommand(args); + retVal = execute(outfile, command); + } catch (Exception ex) { + if (outfile.exists()) { + System.err.println(Files.readString(outfile.toPath())); + } + throw ex; + } + + String output = Files.readString(outfile.toPath()); + if (retValZero) { + if (retVal != 0) { + System.err.println(output); + throw new AssertionError("jpackage exited with error: " + retVal); + } + } else { + if (retVal == 0) { + System.err.println(output); + throw new AssertionError("jpackage exited without error: " + retVal); + } + } + + if (VERBOSE) { + System.out.println("output ="); + System.out.println(output); + } + + return output; + } + + public static String executeToolProvider(boolean retValZero, String... args) throws Exception { + StringWriter writer = new StringWriter(); + PrintWriter pw = new PrintWriter(writer); + int retVal = JPACKAGE_TOOL.run(pw, pw, args); + String output = writer.toString(); + + if (retValZero) { + if (retVal != 0) { + System.err.println(output); + throw new AssertionError("jpackage exited with error: " + retVal); + } + } else { + if (retVal == 0) { + System.err.println(output); + throw new AssertionError("jpackage exited without error"); + } + } + + if (VERBOSE) { + System.out.println("output ="); + System.out.println(output); + } + + return output; + } + + public static boolean isWindows() { + return (OS.contains("win")); + } + + public static boolean isOSX() { + return (OS.contains("mac")); + } + + public static boolean isLinux() { + return ((OS.contains("nix") || OS.contains("nux"))); + } + + public static void createHelloImageJar() throws Exception { + createJar(false, "Hello", "image"); + } + + public static void createHelloImageJarWithMainClass() throws Exception { + createJar(true, "Hello", "image"); + } + + public static void createHelloInstallerJar() throws Exception { + createJar(false, "Hello", "installer"); + } + + public static void createHelloInstallerJarWithMainClass() throws Exception { + createJar(true, "Hello", "installer"); + } + + private static void createJar(boolean mainClassAttribute, String name, + String testType) throws Exception { + int retVal; + + File input = new File("input"); + if (!input.exists()) { + input.mkdir(); + } + + Files.copy(Path.of(TEST_SRC_ROOT + File.separator + "apps" + File.separator + + testType + File.separator + name + ".java"), Path.of(name + ".java")); + + File javacLog = new File("javac.log"); + try { + retVal = execute(javacLog, JAVAC.toString(), name + ".java"); + } catch (Exception ex) { + if (javacLog.exists()) { + System.err.println(Files.readString(javacLog.toPath())); + } + throw ex; + } + + if (retVal != 0) { + if (javacLog.exists()) { + System.err.println(Files.readString(javacLog.toPath())); + } + throw new AssertionError("javac exited with error: " + retVal); + } + + File jarLog = new File("jar.log"); + try { + List args = new ArrayList<>(); + args.add(JAR.toString()); + args.add("-c"); + args.add("-v"); + args.add("-f"); + args.add("input" + File.separator + name.toLowerCase() + ".jar"); + if (mainClassAttribute) { + args.add("-e"); + args.add(name); + } + args.add(name + ".class"); + retVal = execute(jarLog, args.stream().toArray(String[]::new)); + } catch (Exception ex) { + if (jarLog.exists()) { + System.err.println(Files.readString(jarLog.toPath())); + } + throw ex; + } + + if (retVal != 0) { + if (jarLog.exists()) { + System.err.println(Files.readString(jarLog.toPath())); + } + throw new AssertionError("jar exited with error: " + retVal); + } + } + + public static void createHelloModule() throws Exception { + createModule("Hello.java", "input", "hello"); + } + + public static void createOtherModule() throws Exception { + createModule("Other.java", "input-other", "other"); + } + + private static void createModule(String javaFile, String inputDir, + String aName) throws Exception { + int retVal; + + File input = new File(inputDir); + if (!input.exists()) { + input.mkdir(); + } + + File module = new File("module" + File.separator + "com." + aName); + if (!module.exists()) { + module.mkdirs(); + } + + File javacLog = new File("javac.log"); + try { + List args = new ArrayList<>(); + args.add(JAVAC.toString()); + args.add("-d"); + args.add("module" + File.separator + "com." + aName); + args.add(TEST_SRC_ROOT + File.separator + "apps" + File.separator + + "com." + aName + File.separator + "module-info.java"); + args.add(TEST_SRC_ROOT + File.separator + "apps" + + File.separator + "com." + aName + File.separator + "com" + + File.separator + aName + File.separator + javaFile); + retVal = execute(javacLog, args.stream().toArray(String[]::new)); + } catch (Exception ex) { + if (javacLog.exists()) { + System.err.println(Files.readString(javacLog.toPath())); + } + throw ex; + } + + if (retVal != 0) { + if (javacLog.exists()) { + System.err.println(Files.readString(javacLog.toPath())); + } + throw new AssertionError("javac exited with error: " + retVal); + } + + File jarLog = new File("jar.log"); + try { + List args = new ArrayList<>(); + args.add(JAR.toString()); + args.add("--create"); + args.add("--file"); + args.add(inputDir + File.separator + "com." + aName + ".jar"); + args.add("-C"); + args.add("module" + File.separator + "com." + aName); + args.add("."); + + retVal = execute(jarLog, args.stream().toArray(String[]::new)); + } catch (Exception ex) { + if (jarLog.exists()) { + System.err.println(Files.readString(jarLog.toPath())); + } + throw ex; + } + + if (retVal != 0) { + if (jarLog.exists()) { + System.err.println(Files.readString(jarLog.toPath())); + } + throw new AssertionError("jar exited with error: " + retVal); + } + } + + public static void createRuntime() throws Exception { + int retVal; + + File jlinkLog = new File("jlink.log"); + try { + List args = new ArrayList<>(); + args.add(JLINK.toString()); + args.add("--output"); + args.add("runtime"); + args.add("--add-modules"); + args.add("java.base"); + retVal = execute(jlinkLog, args.stream().toArray(String[]::new)); + } catch (Exception ex) { + if (jlinkLog.exists()) { + System.err.println(Files.readString(jlinkLog.toPath())); + } + throw ex; + } + + if (retVal != 0) { + if (jlinkLog.exists()) { + System.err.println(Files.readString(jlinkLog.toPath())); + } + throw new AssertionError("jlink exited with error: " + retVal); + } + } + + public static String listToArgumentsMap(List arguments, boolean toolProvider) { + if (arguments.isEmpty()) { + return ""; + } + + String argsStr = ""; + for (int i = 0; i < arguments.size(); i++) { + String arg = arguments.get(i); + argsStr += quote(arg, toolProvider); + if ((i + 1) != arguments.size()) { + argsStr += " "; + } + } + + if (!toolProvider && isWindows()) { + if (argsStr.contains(" ")) { + if (argsStr.contains("\"")) { + argsStr = escapeQuote(argsStr, toolProvider); + } + argsStr = "\"" + argsStr + "\""; + } + } + return argsStr; + } + + private static String quote(String in, boolean toolProvider) { + if (in == null) { + return null; + } + + if (in.isEmpty()) { + return ""; + } + + if (!in.contains("=")) { + // Not a property + if (in.contains(" ")) { + in = escapeQuote(in, toolProvider); + return "\"" + in + "\""; + } + return in; + } + + if (!in.contains(" ")) { + return in; // No need to quote + } + + int paramIndex = in.indexOf("="); + if (paramIndex <= 0) { + return in; // Something wrong, just skip quoting + } + + String param = in.substring(0, paramIndex); + String value = in.substring(paramIndex + 1); + + if (value.length() == 0) { + return in; // No need to quote + } + + value = escapeQuote(value, toolProvider); + + return param + "=" + "\"" + value + "\""; + } + + private static String escapeQuote(String in, boolean toolProvider) { + if (in == null) { + return null; + } + + if (in.isEmpty()) { + return ""; + } + + if (in.contains("\"")) { + // Use code points to preserve non-ASCII chars + StringBuilder sb = new StringBuilder(); + int codeLen = in.codePointCount(0, in.length()); + for (int i = 0; i < codeLen; i++) { + int code = in.codePointAt(i); + // Note: No need to escape '\' on Linux or OS X + // jpackage expects us to pass arguments and properties with + // quotes and spaces as a map + // with quotes being escaped with additional \ for + // internal quotes. + // So if we want two properties below: + // -Djnlp.Prop1=Some "Value" 1 + // -Djnlp.Prop2=Some Value 2 + // jpackage will need: + // "-Djnlp.Prop1=\"Some \\"Value\\" 1\" -Djnlp.Prop2=\"Some Value 2\"" + // but since we using ProcessBuilder to run jpackage we will need to escape + // our escape symbols as well, so we will need to pass string below to ProcessBuilder: + // "-Djnlp.Prop1=\\\"Some \\\\\\\"Value\\\\\\\" 1\\\" -Djnlp.Prop2=\\\"Some Value 2\\\"" + switch (code) { + case '"': + // " -> \" -> \\\" + if (i == 0 || in.codePointAt(i - 1) != '\\') { + sb.appendCodePoint('\\'); + sb.appendCodePoint(code); + } + break; + case '\\': + // We need to escape already escaped symbols as well + if ((i + 1) < codeLen) { + int nextCode = in.codePointAt(i + 1); + if (nextCode == '"') { + // \" -> \\\" + sb.appendCodePoint('\\'); + sb.appendCodePoint('\\'); + sb.appendCodePoint('\\'); + sb.appendCodePoint(nextCode); + } else { + sb.appendCodePoint('\\'); + sb.appendCodePoint(code); + } + } else { + sb.appendCodePoint(code); + } + break; + default: + sb.appendCodePoint(code); + break; + } + } + return sb.toString(); + } + + return in; + } + +} diff --git a/test/jdk/tools/jpackage/helpers/JPackageInstallerHelper.java b/test/jdk/tools/jpackage/helpers/JPackageInstallerHelper.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/helpers/JPackageInstallerHelper.java @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.List; + +public class JPackageInstallerHelper { + private static final String JPACKAGE_TEST_OUTPUT = "jpackage.test.output"; + private static final String JPACKAGE_VERIFY_INSTALL = "jpackage.verify.install"; + private static final String JPACKAGE_VERIFY_UNINSTALL = "jpackage.verify.uninstall"; + private static String testOutput; + private static final boolean isTestOutputSet; + private static final boolean isVerifyInstall; + private static final boolean isVerifyUnInstall; + + static { + String out = System.getProperty(JPACKAGE_TEST_OUTPUT); + isTestOutputSet = (out != null); + if (isTestOutputSet) { + File file = new File(out); + if (!file.exists()) { + throw new AssertionError(file.getAbsolutePath() + " does not exist"); + } + + if (!file.isDirectory()) { + throw new AssertionError(file.getAbsolutePath() + " is not a directory"); + } + + if (!file.canWrite()) { + throw new AssertionError(file.getAbsolutePath() + " is not writable"); + } + + if (out.endsWith(File.separator)) { + out = out.substring(0, out.length() - 2); + } + + testOutput = out; + } + + isVerifyInstall = (System.getProperty(JPACKAGE_VERIFY_INSTALL) != null); + isVerifyUnInstall = (System.getProperty(JPACKAGE_VERIFY_UNINSTALL) != null); + } + + public static boolean isTestOutputSet() { + return isTestOutputSet; + } + + public static boolean isVerifyInstall() { + return isVerifyInstall; + } + + public static boolean isVerifyUnInstall() { + return isVerifyUnInstall; + } + + public static void copyTestResults(List files) throws Exception { + if (!isTestOutputSet()) { + return; + } + + File dest = new File(testOutput); + if (!dest.exists()) { + dest.mkdirs(); + } + + if (JPackageHelper.isWindows()) { + files.add(JPackagePath.getTestSrc() + File.separator + "install.bat"); + files.add(JPackagePath.getTestSrc() + File.separator + "uninstall.bat"); + } else { + files.add(JPackagePath.getTestSrc() + File.separator + "install.sh"); + files.add(JPackagePath.getTestSrc() + File.separator + "uninstall.sh"); + } + + for (String file : files) { + Path source = Path.of(file); + Path target = Path.of(dest.toPath() + File.separator + source.getFileName()); + Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); + } + } + + public static void validateApp(String app) throws Exception { + File outFile = new File("appOutput.txt"); + if (outFile.exists()) { + outFile.delete(); + } + + int retVal = JPackageHelper.execute(outFile, app); + if (retVal != 0) { + throw new AssertionError( + "Test application exited with error: " + retVal); + } + + if (!outFile.exists()) { + throw new AssertionError(outFile.getAbsolutePath() + " was not created"); + } + + String output = Files.readString(outFile.toPath()); + String[] result = output.split("\n"); + if (result.length != 2) { + System.err.println(output); + throw new AssertionError( + "Unexpected number of lines: " + result.length); + } + + if (!result[0].trim().equals("jpackage test application")) { + throw new AssertionError("Unexpected result[0]: " + result[0]); + } + + if (!result[1].trim().equals("args.length: 0")) { + throw new AssertionError("Unexpected result[1]: " + result[1]); + } + } + + public static void validateOutput(String output) throws Exception { + File file = new File(output); + if (!file.exists()) { + // Try lower case in case of OS is case sensitive + file = new File(output.toLowerCase()); + if (!file.exists()) { + throw new AssertionError("Cannot find " + file.getAbsolutePath()); + } + } + } + + public static void validateStartMenu(String menuGroup, String menu, boolean exist) + throws Exception { + String startMenuLink = JPackagePath.getWinStartMenu() + + File.separator + menuGroup + File.separator + + menu + ".lnk"; + + File link = new File(startMenuLink); + if (exist) { + if (!link.exists()) { + throw new AssertionError("Cannot find " + link.getAbsolutePath()); + } + } else { + if (link.exists()) { + throw new AssertionError("Error: " + link.getAbsolutePath() + " exist"); + } + } + } + + public static void validateDesktopShortcut(String name, boolean exist) + throws Exception { + String shortcutLink = JPackagePath.getWinPublicDesktop() + + File.separator + name + ".lnk"; + + File link = new File(shortcutLink); + if (exist) { + if (!link.exists()) { + throw new AssertionError("Cannot find " + link.getAbsolutePath()); + } + } else { + if (link.exists()) { + throw new AssertionError("Error: " + link.getAbsolutePath() + " exist"); + } + } + } + + public static void validateUserLocalStartMenu(String menuGroup, String menu, boolean exist) + throws Exception { + String startMenuLink = JPackagePath.getWinUserLocalStartMenu() + + File.separator + menuGroup + File.separator + + menu + ".lnk"; + + File link = new File(startMenuLink); + if (exist) { + if (!link.exists()) { + throw new AssertionError("Cannot find " + link.getAbsolutePath()); + } + } else { + if (link.exists()) { + throw new AssertionError("Error: " + link.getAbsolutePath() + " exist"); + } + } + } + + public static void validateWinRegistry(String key, String [] values, boolean retValZero) + throws Exception { + File outFile = new File("regOutput.txt"); + if (outFile.exists()) { + outFile.delete(); + } + + int retVal = JPackageHelper.execute(outFile, "reg.exe", "query", key); + if (retValZero) { + if (retVal != 0) { + System.out.println("validateWinRegistry() key=" + key); + if (outFile.exists()) { + System.err.println(Files.readString(outFile.toPath())); + } + throw new AssertionError( + "Reg.exe application exited with error: " + retVal); + } + } else { + if (retVal == 0) { + System.out.println("validateWinRegistry() key=" + key); + if (outFile.exists()) { + System.err.println(Files.readString(outFile.toPath())); + } + throw new AssertionError( + "Reg.exe application exited without error: " + retVal); + } else { + return; // Done + } + } + + if (!outFile.exists()) { + throw new AssertionError(outFile.getAbsolutePath() + " was not created"); + } + + String output = Files.readString(outFile.toPath()); + for (String value : values) { + if (!output.contains(value)) { + System.err.println(output); + throw new AssertionError("Cannot find in registry: " + value); + } + } + } +} diff --git a/test/jdk/tools/jpackage/helpers/JPackagePath.java b/test/jdk/tools/jpackage/helpers/JPackagePath.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/helpers/JPackagePath.java @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; + +/** + * Helper class which contains functions to get different system dependent paths used by tests + */ +public class JPackagePath { + + // Path to Windows "Program Files" folder + // Probably better to figure this out programattically + private static final String WIN_PROGRAM_FILES = "C:\\Program Files"; + + // Path to Windows Start menu items + private static final String WIN_START_MENU = "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs"; + + // Path to Windows public desktop location + private static final String WIN_PUBLIC_DESKTOP = "C:\\Users\\Public\\Desktop"; + + // Return path to test src adjusted to location of caller + public static String getTestSrcRoot() { + return JPackageHelper.TEST_SRC_ROOT; + } + + // Return path to calling test + public static String getTestSrc() { + return JPackageHelper.TEST_SRC; + } + + // Returns path to generate test application + public static String getApp() { + if (JPackageHelper.isWindows()) { + return "output" + File.separator + "test" + File.separator + "test.exe"; + } else if (JPackageHelper.isOSX()) { + return "output" + File.separator + "test.app" + File.separator + "Contents" + + File.separator + "MacOS" + File.separator + "test"; + } else if (JPackageHelper.isLinux()) { + return "output" + File.separator + "test" + File.separator + "test"; + } else { + throw new AssertionError("Cannot detect platform"); + } + } + + // Returns path to generate test application icon + public static String getAppIcon() { + if (JPackageHelper.isWindows()) { + return "output" + File.separator + "test" + File.separator + "test.ico"; + } else if (JPackageHelper.isOSX()) { + return "output" + File.separator + "test.app" + File.separator + "Contents" + + File.separator + "Resources" + File.separator + "test.icns"; + } else if (JPackageHelper.isLinux()) { + return "output" + File.separator + "test" + File.separator + + File.separator + "resources"+ File.separator + "test.png"; + } else { + throw new AssertionError("Cannot detect platform"); + } + } + + // Returns path to generate test application without --name argument + public static String getAppNoName() { + if (JPackageHelper.isWindows()) { + return "output" + File.separator + "Hello" + File.separator + "Hello.exe"; + } else if (JPackageHelper.isOSX()) { + return "output" + File.separator + "Hello.app" + File.separator + "Contents" + + File.separator + "MacOS" + File.separator + "Hello"; + } else if (JPackageHelper.isLinux()) { + return "output" + File.separator + "Hello" + File.separator + "Hello"; + } else { + throw new AssertionError("Cannot detect platform"); + } + } + + // Returns path to generate secondary launcher of test application + public static String getAppSL() { + if (JPackageHelper.isWindows()) { + return "output" + File.separator + "test" + File.separator + "test2.exe"; + } else if (JPackageHelper.isOSX()) { + return "output" + File.separator + "test.app" + File.separator + "Contents" + + File.separator + "MacOS" + File.separator + "test2"; + } else if (JPackageHelper.isLinux()) { + return "output" + File.separator + "test" + File.separator + "test2"; + } else { + throw new AssertionError("Cannot detect platform"); + } + } + + // Returns path to app working directory (where test application generates its output) + public static String getAppWorkingDir() { + if (JPackageHelper.isWindows()) { + return "output" + File.separator + "test" + File.separator + "app"; + } else if (JPackageHelper.isOSX()) { + return "output" + File.separator + "test.app" + File.separator + "Contents" + + File.separator + "Java"; + } else if (JPackageHelper.isLinux()) { + return "output" + File.separator + "test" + File.separator + "app"; + } else { + throw new AssertionError("Cannot detect platform"); + } + } + + // Returns path to test application cfg file + public static String getAppCfg() { + if (JPackageHelper.isWindows()) { + return "output" + File.separator + "test" + File.separator + "app" + File.separator + + "test.cfg"; + } else if (JPackageHelper.isOSX()) { + return "output" + File.separator + "test.app" + File.separator + "Contents" + + File.separator + "Java" + File.separator + "test.cfg"; + } else if (JPackageHelper.isLinux()) { + return "output" + File.separator + "test" + File.separator + "app" + File.separator + + "test.cfg"; + } else { + throw new AssertionError("Cannot detect platform"); + } + } + + // Returns path to app working directory without --name (where test application generates its output) + public static String getAppWorkingDirNoName() { + if (JPackageHelper.isWindows()) { + return "output" + File.separator + "Hello" + File.separator + "app"; + } else if (JPackageHelper.isOSX()) { + return "output" + File.separator + "Hello.app" + File.separator + "Contents" + + File.separator + "Java"; + } else if (JPackageHelper.isLinux()) { + return "output" + File.separator + "Hello" + File.separator + "app"; + } else { + throw new AssertionError("Cannot detect platform"); + } + } + + // Returns path including executable to java in image runtime folder + public static String getRuntimeJava() { + if (JPackageHelper.isWindows()) { + return "output" + File.separator + "test" + + File.separator + "runtime" + File.separator + + "bin" + File.separator + "java.exe"; + } else if (JPackageHelper.isOSX()) { + return "output" + File.separator + "test.app" + File.separator + + "Contents" + File.separator + + "runtime" + File.separator + "Contents" + File.separator + + "Home" + File.separator + "bin" + File.separator + "java"; + } else if (JPackageHelper.isLinux()) { + return "output" + File.separator + "test" + + File.separator + "runtime" + File.separator + + "bin" + File.separator + "java"; + } else { + throw new AssertionError("Cannot detect platform"); + } + } + + // Returns output file name generate by test application + public static String getAppOutputFile() { + return "appOutput.txt"; + } + + // Returns path to bin folder in image runtime + public static String getRuntimeBin() { + if (JPackageHelper.isWindows()) { + return "output" + File.separator + "test" + + File.separator + "runtime" + File.separator + "bin"; + } else if (JPackageHelper.isOSX()) { + return "output" + File.separator + "test.app" + + File.separator + "Contents" + + File.separator + "runtime" + + File.separator + "Contents" + + File.separator + "Home" + File.separator + "bin"; + } else if (JPackageHelper.isLinux()) { + return "output" + File.separator + "test" + + File.separator + "runtime" + File.separator + "bin"; + } else { + throw new AssertionError("Cannot detect platform"); + } + } + + public static String getWinProgramFiles() { + return WIN_PROGRAM_FILES; + } + + public static String getWinUserLocal() { + return System.getProperty("user.home") + File.separator + "AppData" + + File.separator + "Local"; + } + + public static String getWinStartMenu() { + return WIN_START_MENU; + } + + public static String getWinPublicDesktop() { + return WIN_PUBLIC_DESKTOP; + } + + public static String getWinUserLocalStartMenu() { + return System.getProperty("user.home") + File.separator + "AppData" + + File.separator + "Roaming" + File.separator + "Microsoft" + + File.separator + "Windows" + File.separator + "Start Menu" + + File.separator + "Programs"; + + } + + public static String getWinInstalledApp(String testName) { + return getWinProgramFiles() + File.separator + testName + File.separator + + testName + ".exe"; + } + + public static String getWinInstalledApp(String installDir, String testName) { + return getWinProgramFiles() + File.separator + installDir + File.separator + + testName + ".exe"; + } + + public static String getOSXInstalledApp(String testName) { + return File.separator + "Applications" + File.separator + testName + + ".app" + File.separator + "Contents" + File.separator + + "MacOS" + File.separator + testName; + } + + public static String getLinuxInstalledApp(String testName) { + return File.separator + "opt" + File.separator + testName + + File.separator + testName; + } + + public static String getOSXInstalledApp(String subDir, String testName) { + return File.separator + "Applications" + File.separator + subDir + + File.separator + testName + ".app" + File.separator + + "Contents" + File.separator + "MacOS" + File.separator + + testName; + } + + public static String getLinuxInstalledApp(String subDir, String testName) { + return File.separator + "opt" + File.separator + subDir + File.separator + + testName + File.separator + testName; + } + + public static String getWinInstallFolder(String testName) { + return getWinProgramFiles() + File.separator + testName; + } + + public static String getLinuxInstallFolder(String testName) { + return File.separator + "opt" + File.separator + testName; + } + + public static String getLinuxInstallFolder(String subDir, String testName) { + if (testName == null) { + return File.separator + "opt" + File.separator + subDir; + } else { + return File.separator + "opt" + File.separator + subDir + + File.separator + testName; + } + } + + public static String getWinUserLocalInstalledApp(String testName) { + return getWinUserLocal() + File.separator + testName + File.separator + testName + ".exe"; + } + + public static String getWinUserLocalInstallFolder(String testName) { + return getWinUserLocal() + File.separator + testName; + } + + // Returs path to test license file + public static String getLicenseFilePath() { + String path = JPackagePath.getTestSrcRoot() + File.separator + "resources" + + File.separator + "license.txt"; + + return path; + } + + // Returns path to app folder of installed application + public static String getWinInstalledAppFolder(String testName) { + return getWinProgramFiles() + File.separator + testName + File.separator + + "app"; + } +} diff --git a/test/jdk/tools/jpackage/jdk/jpackage/internal/DeployParamsTest.java b/test/jdk/tools/jpackage/jdk/jpackage/internal/DeployParamsTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/tools/jpackage/jdk/jpackage/internal/DeployParamsTest.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import jdk.jpackage.internal.Arguments; +import jdk.jpackage.internal.DeployParams; +import jdk.jpackage.internal.PackagerException; +import java.io.File; + +/* + * @test + * @bug 8211285 + * @summary DeployParamsTest + * @modules jdk.jpackage + * @modules jdk.jpackage/jdk.jpackage.internal + * @run main/othervm -Xmx512m DeployParamsTest + */ +public class DeployParamsTest { + + private static File testRoot = null; + + private static void setUp() { + testRoot = new File("deployParamsTest"); + System.out.println("DeployParamsTest: " + testRoot.getAbsolutePath()); + testRoot.mkdir(); + } + + private static void tearDown() { + if (testRoot != null) { + testRoot.delete(); + } + } + + private static void testValidateAppName1() throws Exception { + DeployParams params = getParamsAppName(); + + setAppName(params, "Test"); + params.validate(); + + setAppName(params, "Test Name"); + params.validate(); + + setAppName(params, "Test - Name !!!"); + params.validate(); + } + + private static void testValidateAppName2() throws Exception { + DeployParams params = getParamsAppName(); + + setAppName(params, "Test\nName"); + appName2TestHelper(params); + + setAppName(params, "Test\rName"); + appName2TestHelper(params); + + setAppName(params, "TestName\\"); + appName2TestHelper(params); + + setAppName(params, "Test \" Name"); + appName2TestHelper(params); + } + + private static void appName2TestHelper(DeployParams params) throws Exception { + try { + params.validate(); + } catch (PackagerException pe) { + if (!pe.getMessage().startsWith("Error: Invalid Application name")) { + throw new Exception("Unexpected PackagerException received: " + pe); + } + + return; // Done + } + + throw new Exception("Expecting PackagerException"); + } + + // Returns deploy params initialized to pass all validation, except for + // app name + private static DeployParams getParamsAppName() { + DeployParams params = new DeployParams(); + + params.setOutput(testRoot); + params.addResource(testRoot, new File(testRoot, "test.jar")); + params.addBundleArgument(Arguments.CLIOptions.APPCLASS.getId(), "TestClass"); + params.addBundleArgument(Arguments.CLIOptions.MAIN_JAR.getId(), "test.jar"); + + return params; + } + + private static void setAppName(DeployParams params, String appName) { + params.addBundleArgument(Arguments.CLIOptions.NAME.getId(), appName); + } + + public static void main(String[] args) throws Exception { + setUp(); + + try { + testValidateAppName1(); + testValidateAppName2(); + } finally { + tearDown(); + } + } + +} diff --git a/test/jdk/tools/jpackage/resources/icon.icns b/test/jdk/tools/jpackage/resources/icon.icns new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..7e43c04df6a780d178172c282d6ec1798eea72d4 GIT binary patch literal 83256 zc%1CK1yEhjx95wy6Otf7f`ni}g9S_Q;E+Io1osf!3GVI|+}$05ySux)%R$cBbNK!5 zyESj#)KtBynVLIy&KIcNyKQ%`UcLH5)j8Wl@0Sf6ycmzkPY!lCIK&VDKmdU5KLC8+ zbrJyoI1EjlF8=|LZl(d?AQ3?61%NJKw=oVt8NLG6>el+>0pxZ700VtK0K!cTC=`I- z+VTLfcTZ>gyKjKo(Z>M*xD5v2+Pwhy#0L)majbiKA`F1*_X6P7JOSk1Y2d7HAOwKx z^aqghAA$LfOAlxtd%gg|X~&U2fRPRW5CGUYJ01u70`S)me*obj3*reNK1{7_M*#?% z4G%_<)2%57p3jqDVAasQ702mYvAoK$R(3|V7j(h0M#>ynr6xDZ40DS%KQ|cbt z0nAn~Tm3Jv)lVLCGdMUTGyo=;VEVsSCbmy!GC2nvRR5!TmjD9z0dfZ*-L5TNKyCr# ztMT%dJ;)6-FstV6w=kYrTSAABJ;!p~Xnoow-wB==G{Ke-ZL@ZOQxR zdeff$PyWMjcHbQ|QbIr%_eap?3weJpeWBUw!`t8^0CBsd>hS4yvSbKa3my#K01%IB zi#MTp;H84bb7;W5Tb(;!f9g1Q?kVIRR28p#Kg}R3z8|y(Al(gS_uN9O-*jXT-+2J2 zkn5$IFyFGHH2@j1Tv)yXjoG;1`MlZ_0CK(7Q<7JfQMd{1?z}Z;0NM=qurhDyVYjCw zw|sE5v0w!PZ3bR0D%*N$cGHvDGTD$*+PiTJo!$}z+U(+QTE_*n2(nR@l9JZAdJO?? zI`d}0>rW#p%$vCL0Z{Iyi_;r-Akg4{*OxQ)m<+%}Anj1&j~7&~gM9!b(AMG+7P>e-Vn*rIO)G%(xKLQJ8v>w%XS16xft$S(XfMc+yQ?b@BoIK`D$ncd zE+|`kgdT7ZFr*S{ftKvy`w)O=v!w1Ave(hR4?PFLH%F_3Z5`7W&~p$SG??3dc{!Nh zeCh{Xzsu7*Xe01ke%%qY(P>Zb8F1W`J^lb(E%#IxWMr509z(~D4S@jXjrj`@;IOu6 z1p-|!@ag_-6@YZwkUs|w2A(|}9zFmMquE`zC;cV4MV0MSEBjER;XIz6KJJ!R9Y7D# zuI!1&Cy%+ieXM%2Lst8B2tWYs7=SDnG^}^$mh`V3UOhsfz2lq@51-EEw_E}M)X@g- zpRBZAP`m|+08sZU^Xu}<=b?0fb68uu*_AVL55C!NP3;D?LD#W4edsO%Ksm1uk1Jh( zngM6OqGGzbWc_Nft1>NR=&8=*Om@w21axndI7SZNhC-{9m-ZC=J=v_!Ev)L<2e$)A z`(@cnkZ|bilFT!X!vMVf%Ho=wk-MXbvGtRi`-=;3>(fq1AHIL8lj+)Y9|jN|ROI9p ztwO+{d+3ZV$9kYs20ua?p|+jO>9~PbydSAvhCr#}pgbwL?$`%Fzi7>!0v#<(t=%>P zh^vJSXAuCx?b%&8v`T@0;vmQez_@PB?w_j9%P5+EXn^jj;>v>@0C_jjA?wNiG0y7K z%kp!oN>Y1np`~jDHODys(yE_L6UZ09*v|`0EFIZjXq>#OhdSp#PS;tJmCjm zEC)Me&)ou7TW63U=v4L(ZXnQ(Asgkz%aClSiP~%eHhcke$gr#Xz>`Ztml*kMsJv|S z3i^bDmYl9j==G+`Hg@l6N6#b{9fboZC%t(YIW31-019}&ExUFb+9lFyzGKP()Xy#! zwxElT@Nf(DUexoRyvm8i^_z464f1flP@i47{Fn3C&!_vIKg;4S7iFPE3^rvS+JONH4JpiuzzaU!pGv$FCa6+pgj zjV#!Ki~y*hp4^Guf92iu$7XLm<()SbEbl|}plxp^OEWqypl$an%J&W`Dxl9mft>V| zcOCpg!#6kgJy4r17dD+A{PQqe(9Xu?)1JKBpX%QnXpfUQ-JqwosZTM_13-V= z8?4IB$;=(NhL%2zKzAs#^dJ+!ykD-%DQq4aC@fuvbUqd34nG_~2a*9`-7i+;{#`tO zyza>v0R5|M$QeH)QMMY&zeRFeFdg&%~wkQV`Wo@~gH_&}^oMMyG zRFu=W2@V2K!CST2H4Rne9u=sig?ahK4fCKbXyvNhlDui?oNt!sHugz>Z3j*-Z zTC-Yrj}M^3*xy-OTHZK$=!W`4T~czz9(3DYwG=D^j~nH=i{OB#?OIf{4&5uzHR*#c zbbDlI{l!zI)`DdS0P4-?ya@ylH;O6`C!t0h4)tld{-=w=B?tgn$tynygqpN2e*zk3 zo{kP^7m&5=q|paI0Q2cN(A7Atff^k8pu35JvN@0!)NfC=ulu1Je{t#*@~>*pzJS|~ ztjWhf=t0+?S2lI!@nrDMwp~a+bSFOqKx?(-%|UD3_hgMc1OZrQeT7Bc8y7d5Ex9en zvj8S&AonT6T(@OUJ_ZAr7vp6)CCzOWIdy9fvj7HUsjv>Zy-w>2Rzsmj*IHLaer{pM z_TxO%$hCzl(DSIOY%lBy4|W%)md_yz&`Q0zV-LVyS;ao|pF8j)cnNxT&g8XSLY8u> zPp$y8owBl>o9>M68)*8fEq7$A0y^0%=;oTuDer?u;T!1U6vUKOj^062*X?PI6aNqt z>z79>?WD2u;96s?^hP*-{=qabs z&T|aPUr-RsF4}^U6e4JI3v~aqcK>hO`}049Mi>mjU=Rj_Fc^fvAPfd!FbIP|7!1N- z5C(%V7=*zf39OfbO&6HG9{1QSd!!2}abFu?>9OfbO&6HG9{1QSd!!2}abFu?>9OfbO&6HG9{ z1QSd!!2}ab{{dv8$I1f-C!!Ar2Zv>;g11LKQpv-vNW}1r+j*){J&l0p;u$* z^|^_$zBL>iJlsEjNT{e!b>MLIEPh(U|C7Xn{=L@IH!y@l`sW@V4)On~4Tk{zfrH-v zt6M!l9#Dj1hlBg~?*3i>%{>w-Jp8}^0{U>&aPXh34RmZRtobOVb!-$2tSP@5SlgIb z{Gw!I;bmoIVFQ%lu>R=-83_pv9?*m%6c(10RQRudf$wn7;Qzb12>;z&#Q$$@1l*Ik z;Dn*$0istu*KmgM@bn=zp=hWD_RUsAYS?d{!{rw3+Oj(Yjy~_yb zM%N?hrmZg`VfN<<^p!7aIsM<;e6;%6GdRsM|IPyj|0#LYDW$iEF@2M!vjLW^k-5S1 zBfFi+HON&-lZp|^FK3jwt>^*-M$`2glslH(Urm4EL-tv>5qZ<2!j(hNf{4RBd~d0a`q!NGhRQmMU%eooDboG*-W)m!6!;kZ+JsEqEJZ^-9X zm4|a61RKKB5IlMpHYv&x~;wU1iC^x^#RzHQC~aI2kR~ z9+@xUdzFyMt+mSN=;L8~{_0GM8&S^;9ZC2`?IejC5BMenTnhB?aUb+kxYNM7PUl6$tS}Y~a;{HYC`kW<^_*F|ciNw!1lmvqlNs{NUmG@*pt-Iv!8qeuYdi56t>DB)T`uqAJRfa8qC$$a;QE)H zBeKqO3-FN~?z4`IpPMgMGiFN-9|=*rxvsPa7_;2VS#Y$OBa8#mB56E`<6(w{8TS7y z!+MN%e$qQ`pt@OE8#@kq8$QG=HDUE~?o!SY(_+#Yl)h2c(m6vfej9z(_H$}v1v$5m zud26Nar2@1ba!y=>Kb3JJ0WhWU=nM9PPaF?RYnIt`v+??vtZzN@&&O=4nJ23L9;0M zt;~eaGm<=)pOQRkpQDM%oNSb7S_Io*Pz&ZyeaQ`aSvgJD&W-l$Wyg_w!`_Ou6s^5dH&Y!%Mckw}$R@X^TV*ijYW=Q!)B_sSW$j9mOg2mi-JLr_27> z27FUx24145tyeAGeWzJT$mBHe^bAVbk!%VbB-L0FJik{0*MerqtAC~TbZ1+{!K)@H zMdCMhwH~(5`YiTZzf7#cg`+gf_z4k5DgXW~>UZVy3+Bmdr(NsFk6ne-6G`MOqHi=K zx$%N6f6lkP)i@w62cyzsYzAjIC^q%5zl$P%Lm8lCKwcc{e*Lis$ssHW;C6eAZx8VZ zk)Grti$}{27@=t*9r%k~|H^I?2|2s?SC3T$Y6r;V$0NRp@>TlsvehMNc%I*b- zIn4!O=R9CRN@m713^qK3*5^ZA-6RZc@ZGwS>2X%m-D;DV5Y$BWBJ4|QdlntK?YY9g zUYyDWe9Xc6xY~CP+^Q1nr2${)nCjjk>s`uKNj(4NgXkb%Y4bf%&4xJQ8L1_iwsg?S zZ&6$It4sq`H*R6Bt-x-3S3PaLY;K|Vox@~%VDBtY*>-s?+jI9rPIV1*CSxUo>ac@Z ztmOB5d)xcPRdmg79-64-Ykl5FtW2&G++m#{eGkLi>4kYb%;RAm5A%4K$N%T`c#qD5 zy^j=M_uB3r&{pMgu|o%uN=XOn>-w@kV5Pe_oO`#rt9EdLqmW2fZn(Tjj9Tjdpg3%{ zi%xc%Mn+E~8TklvXuHt9#^~YNRYtAbul<(V+c9Q#`@EUmX-fXXEx4b+Uon6j(d5E`y6YxJnLy<)KRu&wm&r4x>zpgx8v#oFf_N^{f0K(fTXaXR>jS#?B)@n+ZKl6{;bA*T5~NFV@9W)VbfuLhecFt+<&J}ixV%L4DqT218DC_>0aNaZe zNgxKiVv3*d2r2<}O!7krU(M4;@b8V#VozoDhh~e1!9@(!1UGYk_%1NieLEw!GsHJ~ zJ`fZ^QNGgr#j&k#D!n(>45g=3=Xej3YLZsu#Y-RZtU2R69{sxy%@?E247u zON02dNvw8+eyl}PCbAKI;pyw7ZQrw=6wBc{WA)QkD9pJJn;vL60+Y}LAisv6d_|uL z_xg`^RaDFM$J;QOCBX}ZBw71s%h3gIn}}GxOtu(yfczQR5xx?Cn2cWp*ZU=nFzDtl zY0YGmc*j1>%Zh-6J<}j&@|z}~Bv)LN5BbDxi7sn!j%tI9xrMk8A_Yw{5iBSqWhJN7 zY6Zll&jkfO3BBSUtx1(1F=MbH%&`d>r5q!6dR@yM+rRe5$Nhj&t8-qwpKO>n(vlKn zyx_jbR7A8XHmw<&j49!xn&YG(K4kc!)y~z$YyXHN;{D4)xe@$qk+~CbU%g-}(RS1c zcogP&EadPL)jld}yVs)Kdl{eYm7V6t3@--0s+D1Ar=~&j&n`CyP|4Y7O7ZZ)@!&nR zB+N3qbW2Z>iXM5xc8nc?zfpkuR;wo|gV>*$=sBC18E#ENbe0_JLw;JcjKFo#p8hZew3NM^m3)sj8t{(;(rkcVr8tp-wUSySf`A& zoD<=N6?|E5K_RsjRMQSeH8m@cW5x1|+e5kseME{NV`==T`P8f!fv&l%XWubMTyoWg zL|y?)!{B!t{PX?dW4QxfAsw%Gszz@$=qc6$oTjI4ES9S3+7F}w?6BO@J6hXiPpMBP zjzZIulhebNv27Xpmb5Pqu(5vJ{HEh7rkxwRB_%P$^F;l~*!m`26oKbEmR z#amdsg~eM~yoJSESiFVBTUfmP&lGPx*4*R@-DLgm7y+pjSKR&yivIjo>ec=QGQ_i+ zvh`J#B{z!CZCf4450J;OYf7EHQ%{+{-tyDIV#c&n^77?Fpy#03$4NFaoA=QPbPlaW zu~zO5M6Q_A-S{fwWT|&n1)E8>&z9~ z@Y#?|DrHs-OJ?c}20T3AGFtQpa*>65KmL{r9jf;(YtO)vP-sB%c=&3tB}rw&{y^Zv zaVZs~R;8T4?4U+&96Ww0UDhRkmy=gSB8)ZgJ3Y{Dp?T}v8~d0R>9|@qbB$38Q~%UWuEuFJ#61b zNH2YV-jkmpKk}etO=?gq77R>(;Hm~?is7`T=w=jRnH(x5>d;(|Zf>gg(DR7@V%gl1 zL7QI2qWwwpKDawP?bea_XLoRoL|y${03^+(KcFOPTs;FNT_49`*pau|$*=DvB4adt zrTV6-`>)$_c3a)Yo5Gs)cym?>?O+=xi6LBRQ5h1_tpTIqgd@LKugP9<@uTT^oQzQG z8ZbxLODJh@Dk-b1@FTMM%to)3wdd;~_Y}64ES&fJd`V?oLZ;P!;XmY9LnZQ)Ux}!I z_<4-wyV{KvDUMNT>+p$5vwSa!hw8~sS?a6t>>gOee--k?B^w(;YDM7Rwk;jLD0>qv zON>J8z2jb%7m)c}wo8Gyzp%{$|@}?fI@1>kAFrKOI;IyE$U?5!_AuHY@dPtBa!Hq;y?YE(9=g z@io-aaIj~1wc~g|*7H#ra&oAY_=RB>8Jn;-| z$5#4!K*EvyTR*@&{0hZ7XLO7g5itlkg~cFB2w?o7E#vKj5~3uynpBeM=ZsCNNIx9Y z|0ca6mCNF1Si(mSQHi!`+uuSLss!pDrQcBSJuxJ27_x)92;(P6T_f}P6oMmDmVS+U z94H8USg`g*CO77yVq;N155xm0XscvUAKz2*AZQv&$Hj zG;_mzfqHDCG3qE;d)JXeVBj~;_l5$^Z2#3S)Zn{?`PNwdMpx&9qY{>=pCaoFsu971 z;Ov*aJrv!3V_4aiCv@^Sq|5>}myLSQP-HzDtFbcm@^qUkTilg@EW34ob`&Uw3$CMg zGZ@&%oKz`oLN#!?RXyGmO$uAolA7(NR65_ptvY$5Gk-uhJ};?TsC9ACIoxAEJ^hff zpZbd(7bUm(O9{L{qTZWQ8tynF(Cf1=H;9gJ(*?v&m;kdYnwNzOeC40v)S6f%KFyTg zGfq6$mc>oO{9p`dzW?0B`)oz@Y@est>lSKB8*0WKxEY)vwmtAOtfYcN;LzDBRh zO1b*;2TRl>3Ww#zk9KC%f2+Q*aUn}?Gqnfoi*s2`GEB%&RwD=Hd;d&-@Da4BU!F{q zVp@>+$&Y=Mi$lM|*WPRu$1vAAeZ?)qrFY2LE+ad6r&q-MHCN2tc0_|tO3)c`U)-0f zLJCV1qrzRZfZ|TH@eET@8aFF2$_2ITE?0FIhlT|$XlLjZlavY7x7wO2AvfH=e8HGG zUacg;Ew#3PNGHo;5w5>{oClqj&R+GrzQQ!(n0XG@#W>J(8cUpOO!Q}C(g%l01@1e% z1P8w29r*^a{ajEa@7E9Vi6FB~MM*kJpANij*>!@Mic{KAzNy|Hd<6*~NU?Vu;w)%n zlNoK;Ahw*Pt4SZ$TNKjR z9!26$v?0FSqbBbNw|9*6=h+T6?*h4&FGqKWx2|wT*Q_6M)Eyzj3`4}hZ25xE$aJrO ze~R5eQwgo0EMI`{Wln?NHwg(@(JvH*hCLDWOvtF%8Zs?jeOmhWhi4D0tm>rTYx<5w zx;R%MM}m^nIJ)nj1e+0<5yH(};5kpelubJ6P|;%k`7$PH2XbSLk`OP?FRbbz^FA~g zuFq8nF2y~mAyF@xxh6|8^ml~Q`|v~EX`|Q#fqh|Y2CfNS_5pr#E6eA`c(jews>I~_ zx{n08QAB5dW#UkE>yC+MsQwINvTNnj!v4(i;FoNyx|Is z4Z4;@J2XFsXHtH5Mc(>hU8y{UEO z3veBc;wJ^IE3lkNp2s(fBKGM*9vbCTn}uR^P>U$iZalg7&-YS78U}$NfJ?UaU3i}l z>%yxPC62+e>kIhL_Jy+`ajb!$#CGk#Ea%F;*T0OnLg@MRdLM;3*`@B%ThkAT0iSim znP0u4SXa`8$zNj7XxiJ_bP3ybcqm@TUDYoAXzmmaB+iPK5i_bcYy}zeve@S2nY&dS za5PhEB07JVQ@9i0&@ogdU#P)%ZpN$TE3z0w*;K{gl((@MOuZZe3$Pdis5E(`$97|6 zpx&*IU#!)B;xnj5fkUvu6=f|QN2-HM+0Ry=Rp)+*uNWpacV`s{_401G%Sqd#{Cwq&Y!Zd{xbhwALi|n{n~PD(~_xbl>JY{*^j^@je=|RsJbwOwAxYze*U+YlFDf z=``kxuVYdH9QYBeMX^nKGhsa6CEkPS&_EqZC!<4U^1UugkA&iHb1<#RC}MN@;NEQl(dOuU0tsR`zFpSsMSQF5@Jl-CHBKV$=%r=<89DA?0)m8U17t}p{-1_BBk>+_sE#A4kyl1@2vD^WXAqV zH5C`Uj+9|9$ZC+jJHx@ZegU~(REcW1xA5fcVuNe9^qXSso|hkLUocAD=p-JO3rUd= z@}MUHFHZZXC<2jo#yF^hURsALf60L}-<0*Foj=<8PSEN=EEvEUBYkaH$uReR`xlw6 z^Do?rJj=%G<*&SLYBBnB7UY5i<0_FoHd*Dn=$|ZK6S%of0c2il=d&GH?routcxe&u z4<9#7)7C#z#bLiv+Pa0H4&EI#K|+Vvp6%Lv7`vgN+kRfe8pFC9$m%4N_1ac@9p|NH zYT(`v`EQI_--}5cFzf04dSiwTNe@TIX%r-vJ^3lO9{@$mrA2s|&_K@pbKXdJk-jYj zG`Y7;=-gU9_h!$;2!vp03qxBN+QQHlhPE)Yg`q7BZDD8&Lt7Zy!q66mwlK7Xp)Cw; zVQ33OTNv8H&=!Wa|B=wv1DH3_W95Z|O9B7?f5!sCKW|L zeUOjny^C>K_IbeTmc_oWhzZAwIrPCQ;jznc*SzdaIQ@C@L-wpDV)jG{*O|ToT?5)H z!FiekB=PfT??7ClKi3S7KN{K+p<;g*`5LK`Wd$c&Y_Q#N^BFew|2<I@H-w*xarT`%rNi65)VCQZ{Wpq>*_HRJC)jnhqrAM+_2kD|{Re0H+%= z+t_mb=exxVJaxFY6I6w4^8fYM(>>*i(t|1aV>{TC`-LwC!h||--qKXo|CDuuuca8K z7hBJ+FjRCyC(V&S_B<)}^{nybbg66%J=JqVLZ_&JP@B!EH2%pK*x3%0?Rva&dz`sM zmsiQuuJzOa7_Xvj>RLtA=t8eabNXRsg_-sL9J6}#E@tB2Ea5#^M1)_FnXk+J#3JK>bGO-54ay7n>+;cQk{(>)05HG$LmSW{>6n%N%D&h(d+Ud#L+ z`AL0Iov_9M%NSRqk_Ma^leo@%olkgDtqI0b6C8ZxGMU_#;o|#dMzSKO^XT$)(s1mb zkAGp$yTni)Jd(fOEtC_f_w8tgh@ig8eixuS9p1Y$8s7ZM62)Utf-VrQ?r)}p1{(LC zFnz5lYuxE!qIkZE`haw(t#_eqhOqBQ^Z5q58U~K*H8X0uP_u0akLdDr_s&(y#yont ze_pnH+ii8Mk$m^-;@$Q84lje9vL4CoLw5TyZ!U?+a*L%k64N2J8&i?XF76J={XDME;xe{R$mwI>nQX=fLy7A{k zlQOfoM|zc!0OygLEzIkko5TNw#-XbA9XR0gwwhH&#fLem7;Mc7ZEec0$mL5GGts3pIIDFoCJyhQs#2gOp zYEp^~23rYh!CW5Z@-UZ&xjf9}|1-P%)B6;@QhYrWzI&h%duqpri=eHiB!TdidD6ZK(%}4c04W4qsGw&8KQKv&K80^>h9WyQabXJBE zyT3Xe7ldL&Sc!b@3CQB^_4{HIf`Ql>scF;ToaOh2JDysy&nEK$k?&wT}sG zB)J{<uOU{xF%D>-~I7Uyxn!+nUoeiw7UO6Huk>=;y6yG6Pk1 zYApJ>cZi6Iqf;wpJDDEv^5~M)(zq3AqO<*%eH#fr)J6#@2|=bM(HpYC#(Qcz#sqaq z^6Ei&>lP2glN4kLMIUPklNI$=R!vT26+YCuDOPLt9*enM(x%-R)}CvjFa4I@dX{^IDSG56+hq#;p2>qdcz;s<9o_r0R!zvIsafj;sDAfNOQtsk;hE z15uf{X}Afc#-|oX`g4<2Lk~9xwCk*|L}v8AG6!)MogsB}chIvzJhF0{kZRKv(}Mae z(k2@|QXl0VPDrOd1RT={%q%?06Jp6#qZ`rJ(V>`rjHNqLYKh8vt^B-Z<<3SifKpJ@ zDR4;97wbKRO8e;ab*G!F^!i_;*O*AkW**)56&NZk=RUHgH|cwC(-JJ+(IyL=^L49{ zF;+2aY=7lX*fM_arE4aP64 zbiCRTWwpS+jtSMTumY4Z1Rbks_YXEDx&tz zd_vMn67BQPF=6wpH_+Z?FZ-%J_!q>rBWUn5)lM3h*BjULw$Hq+Ss%N(Ntjs)A*4~_ zrprWY+Na#@uNF)HE2C^Y)o-L)c2@^l}2Zl>u z`MNuerpjRoec9eWbsF;1>FdSq=yivj;sGymHs6N9(8H=Vq!4bbC!YNd7&X@ay^=mMbyz(Du`L*iZ z))A2zE?U3J?7?*H6`SS9RkNrpYzE%$48g6+-kn~nuyX^Gnc3=72j-S)d+j5Oa-Q*UvDg2uN z)Jy>7^6Wkr#Cs)kMUbo6Um0}~j@!5$^>W$wW##L2x%}^nB)VGTTx_8<&-B0PP@DWp z4s-|qapy;)3DG{NydQa2OZBSx&t|9NV&d~pyS(r^=%p6;faJ9QyM_!gwAsFT&Yhia zGc2M?2d^i_!th@a@Eruk0Us+7a~}jy*ty?-7}H3v97W^o`qrX>Q|e4;<;!Qx*?M&- zq|yS&gy@?}P~(ls!~cq-t(Npwg^;LJ9o22B?)&bTV3{nJ;FuDrI_-xQqG~7~BVT!V zxTe`VdYHTX& zKrR+Sg+;?EyT5%WW>$2`E}QU|^BX^hmwj90YfcPV9+Be`E5bN_j~H3F9|yh6%|Wdp z-~R-w$gWIO+7?yto#szrnrQ1r6{XZL)`ZEXs1xM%gM zXliP?L_(IL5fXIb2ne0@*IeZrDE8IuW7#J!){Q5-($NomcdWWy&RIA({dhlrS;KtP zV|R1POPNo+X7tH3McT(=C!Wsr2rXG6ox6*Ta@CYswZz9Ave=7esj7KYC=o(#J@%%Z zKQ_-Uf20%ZjcSXJx8$RCatO-y|y&lDSB=&CbX7NM%K{ z>zLKV##N)tcDw#(J)y5iKY~4-cwS!rEL%3u{D4sndj5s}Q}T;64{6DE78QxyAEX``? za9tj9ybylexv#OWyA)5ZH~}nk=XOcAN+quMf|->`7RbzxLp}Z6mX%Svv0QwZKdpYP za>eLfq0+}q;__57eNH(JxoUqFZ0S*Ia|}Uc*y9P-qvz17^Ku0pF>gR%%YFlqKG{E}gGF|!N zc1~C%mtYFZId^@dcao}H`AcXij&}DhGZoi=LiRni8Qwl?;mXB&AKEH6P9xcSpgC{B zxji0^Nrp%X0V@@L)9!U|U+~hJTwG^CwkR^QD=UE`Bo)!9G%K2lGk06xUbb-F5Eu|IUDq~eS@DAe4){Q?Uhmsf7!T1y4D$X!6I++`ovQwH)vWZ zVxLM~_NRo8HEy`i-xvq#>`T4h)F`& zCml>L!2)A6>{S6QQ0XuaH8Dn_eI9eNVB}?zYUWPqXyomJD9+-e%qxu@XKqA|OXD9} zAyLGX`8KkZgPeBgTz!&Z7ZKAmW=%K43%qm0yfPR?U@9J^;L10N+!7ys+SaV%Wkn0{-DAiINQiqYSwwQeQ7_9Uvbqj0 z4l5lJxUd-7-^*%pt@5JklE6uTvTBH`)=u(TgxbDvMK>G^tm=EyyST_DrXYiVleaxs z@nHvo3rQsmQU6jV=qN5vT}eW;zr;`cs0vfoMSYIXyo-pnrzUA<-qft#jY1V|W!)2Y z*q4WAt6?;vVMJ<5W4882H=Wk_(qd=dxK#rA8DqoQxd|(UpubVj(?^4Osk+|ZkV$2$ zlBK_KO{Km*VZD;{9AjhbK|XjNg!ctaTTnaG$Fu77~g|n&-hOm}XNNeBy$|uP`F> zz1pb-8$`4M8Tvc&?5G3T_zW|mKBl36{1(Jfy#M`oh)J3ij=40>1E+14=5~P2Q7kWf z(`3i{x8MTRZ=YPg`ErI8fXnRXB;72Kbtx@{UcX4%YuGd<^I~&kD!Ze~A!`pxGNoy+ zdP}`S-)roO-3>_fBr>JH$ZvkN?jdURW8TdStCEkNGLC*4 znHYP9qF%X3FcWjuyHz6MO7EY{J^Wrk`ojxH1b>TxN9!=|#g$82xd`N>TTyVM=!)-` zrY>xCJNjVk(C=7s2(7Vxqhn1c4KG*iP9uNIER18kb`aZPq>DypnUq9jP2->4=t8pg zAW)Pyk<3%XsJKy*7_FWOPhAZ8K_gH24*YCWK~7mM?TgO0YgI?YT7=OU>U?`O+o%q^}+%h;yk}}~jbCeumzOsIPKpjK<*hzay?{s)oOcap;#Gzu zhpj}XnD^nLT4Oq=k*8k4(Fh|t*T zAft~(b=~Zh13F2MF;2VAq=O|Y(1mxLzR6V&jSIWZ~vzQe7 zpPzqn+7RS2SUntiD^+K6x!n&*wDkHe7@F?YIra6$ttAD%2Y@0_hP$I^pTbsHq2AxH z^@1r|2>Wk@2Ha?D5q|C)_w7mddFya9Es%W~>HNa5MZU;O8`t+u1>xcq4)Wqvwk>3m zvA@mKK5CLt@(zicy@k7J(;kGMzFp`+roPzNqbzcwN&oTABQ<+WUfVhc!13V5SDdEm zkmK$4jSRIG#JVUom3nlFRjgBX9S&kF5+v}4JpW;LTM9TLbkYU?eQSYyC`)3MjyRAR z&tBJ$GQ{vFm*?wFSDmtu4WCJuEj-GbFvx=4A#>q!u+x^kf`wXh*U8&YlHC?d$nG;= zN2{rtDk*wGlRWo{p#N92I!@TMN*on z(7s*#)3+D3;dHljE7sErBX1aa!^j&(-Z1ipkvELIVdM=XZy0&Q$Qwr9F!F|xH;lYt zp?&xxpVs=kd*`9U`Yn;IR_6kRzv3FSmwnLh?~j79?zmOl z#t)6S>wCCo4{t%gr=)~%ts$>?uE_MiKg``LrZ+Ux@)rJ7xf>OTNS)#IBhv}!HVN-* zh}yk5HK)eGZ{wqFHw!xxYyZOzyry?8HivV;im#WLw6%5)V6+?5=8jtFWeU(UYH$A& zpef)p?j9uI><;6sYJ$>v_xTv>ti-7w~U7XOm=#avpFI#iNiv56d+2+i=Emta1 zcM%}}?LQXJN`xR`*)uX{m3-KoU!4s8DSCLpURwMPUHP+S9!hbZ*)hp1DK}HHhWXx} zh+)5n9zKO@)tKw!hBmqJf`X>Eza;r>d}Ivkxw0X^V&2jm;^+Mh$Ml^s=T=zBvr4rf ziX$nC)xl%u>DaffNF2LL8evsb%4+)Uc8z*o$Nq0;ML~9x8creN?xbss2wL%sQN(PF z#`v{cua&ZNsiQj1I5io}$Ip{KiXi4$R5 z&hL0Ht62AvIygEz3P@Mkbi>pWe;orVViGsx)PZ zymVrGAh`&`L7f{Y0$&r=AG}#6<@)H_A^({uT{v98Ue6F?GJY2_n0oV$aobeLU<`QY ziYJG$8z&VVps@Fl5^zePF?2>AUloS(VjZXGb57}z$zF>VQi9{b^#0rULHa=6#;$Oq ze$ex5!_K?kLuvXNvXOs}DU**?)c-0mf6sSIMn4F1<$M`XB_Mx+yYTJTrE^>QQMdXJ zIrH@Pf)CFr&FXSwL|e2#-F*?duBJSZ_lPeQxLhT#D%0~^hbL>KmG~Djm9p3N@mYBj zzk8iW+TQ{*Xjt-_mxP48Q>6Yi{?>4M^}Q@*XvA-w z!Zc?#RDG=nI1(@L?%qn^H*jEt>3(G3=G>40A_8;PsZ7uhB3p0~H~FiY4=8^Fa%24= zq6Z@8*H(yI6;4jSN9UwWSc(nX&SIjL@4SL^5x>0CkR`y_Rqh>#DfB^H zwNp(9aew67!RH&nxgZ zEczKbT34k><9ohOBYu13#$0E@cE}t~WvLy-D6BbI@V0-M*rz z5t5D#Y>WC(mrj>gl2--BYGv4&8lr->PjF)*qhBTVqy>%8JXFl%#}N|#MPZW+3ND5+ zW<&-^7xl!n{x)vzL^>=Le*#r?%t93-yTw%!Z`}Js{|mPIF-jj6X|@j>7$&ZBMi+mn zuya+MR@`5IQ)?lvZKE10{nCS>FZj`~-p~JLyqVH4*iNKERO{u*kH-D*Mk@n1Mg5AE zdclRPmPVwqJj`%|6rsmLLlK4K6k_;t3Kg3|hdrYUr(-jdg@QY7d1ri$^%hPd1X zwIWGmwXF$7M#{F96qU+W3okE=@FVL4R#dBged93|??t9`#y}C4t0>W{^u;4-YpTK$ z;fSj&OCArN39e`KVSfKh<+^;$hQ~z=8A848KKZpKCl6)Sknsz?I2^^yHwA8}RX1Tv zg40syk(P%>bgoUt)e%br3O~DVj214LrGp+`RpYQvgdhY92gJ=UW?!7VMZh%mhfzQIOX(UOWl-w`8;>?96iu7hsYG=y*mG4h*>FaOVp>tjZ zuW4pvJ{<2*k^iY;lnAPXpSq}K;WIKI1lZFz2XjnC;S?mCNnPKcM$TcLB)z^qdG21N zfDMmdLm^YqQ`qH4_~r`k^TiO_YN5j8X$&~oHe+Fl)07M$Cs{?4C_RXDmsiq6hZ&I& znJxiNw#HEG(UNxeIYPwJ^R#J?nIns>&jo!nyE6i2zMmxUYwq-aEw2#g&d`rNYHFCr zzmuhn2tdUaPUFF2?dsUhI#-FvCTgM#B#i;R8lik}-dEDzO`A3qaFmJazqItF>!_dQ zzFHs~J{Iyo$_=GBWevOCx3MXnI5-|c{+`Wb@$gObs^jeA4FBSrE`lPTPX@V~DF3BB zyU>cVVH7A#4IL5!64H%yh=6p1q%d?ibfbWDOE(hI-O@Qo3`2KIhw#xL4qbEp`#A4S z?6vlK=5{m*@1-SV^q91fX5=M=N5a)`)FO%kCFeDP$}8fU%}Ek(ojQ*Pa`Vp_~K+;>glv!|JwU8Y}TbE@DhHxj|mud)kG$_bIIAJ6){wh#mBdh5s8TU#n4 zY5E46CM%!gUm}?o8jfmIW(loIL6-I@id{4l>|$aj2>Ou-kq%uQfFl~;exq} zi7qG|)4vw!C&oHLkK}jZ4;M~2thKDVJJ5AC8S+c+$dJAvX1_`TbXi6tGl_fsY7EJpT4)O3qxhMqKDGH zDLfe6Tii0(b-1IEIh<+8_M0M-r*m=SiU*Go^Sc^Iy|KV#iT$Zt{SCQivFr2iQ|#++ z4n{qP86c&w?|sfO`Y#^i_Wj{j(8oIO`>i%?8eTPUPbH$NyLXwY`$30u6qQ41U!jdH zg;EH*=kzK3Kt%K6@yN042Is{=k>s{Q`P-<4Kmrs?QqT+49~edi{1e*>!;*?GHJ?IW zr;l>=S(8{ce%jG}CDm1&Yk)=1U?SWO51E=E%^T`~`Op%*4KC_R*|p_Jk2yG~Doqq4 zox|j*2H#{=`fLP*>kOb&S57rdYj(#DYnD`3AbI&JpxO6M5T74yICq#6ugpWSB&zIq zSTZl=RBl4@TEtjM(|UFc9g{PxGQ+pKg@;Qvs3d|-3$OiG)G3ZwET|?w z+S@4O_uKLe+mfykN-T=A%ca0HKNWTNKHG=;xr1|RbRC>8UJp! z!$Xu7<Q8@=_DHX}G|M+q$lqG?DW`+qJkU zLQF6TF6S;1O`Y8Tg{kUlVQ4}{G0t%&*>c4x?txh*uVjUq$hiiJ>fx2FKdIG#n6h9#hGh;5uQySRqra7r=o(aMK)H^C!FQl2J(Ni!gy6Z zMq2oxb)zuNHBvw}bvd0#sX=ped0HZc$Co3&yqIo%s6NNAg_*>#1-)l6;f1c`ObJTp z+abu}StkDYMy>Kga{ zi{9wZ)XP!UYqHY3%yrMcI!UZN8t+h))Tg}CmK0ZndhCC+cL^kBQp|kC))y%%yEoWb z98mD#;dpwcHpk1>ov;LuFk-~5i?u6a8@nBPdN)9$V%W7?sCAts zxY9s$4<5J;H%FNq_>0)sPNG?QC6S+-=rFUsS!)k9a#Vy|^4^kM6_n}YrjQpIRt~E1 zT#E8FW#`cyzV-KRow0rGWgGh|pv^^gR$|3!DtZ96$V{8kn zPhUCN;9X|6Xljwp-|)oJv01i$f2U_g*|ZCe`YXYWFb+jIS8ccqKFztg)!48roa3D& zJ<;LGD@q*U&dC$)@Dh`AZ#NGkAJ<5)f9rw4du5f>(k8*aMI?37+e|@-o?RrpFfpur zrk$rFmI1lJ=PGT-_F!7!yVlri@G)9hLR!C3^Wj;xxXhFyx?({Fy=4|J>$?_zn@XVe z%k82Rx?D72RD``}a6ex=4yA}F`c$Hg6X9^J9Hm|N-r%>)AD9Zu5^tafn4vsLQ#beN z2<4wvvIWA50#4CZ_X|zSi3Ppb)11dqEIk-!VjMuyn+o{)+c$&wto3mZM6*IA| ziZ5SZze8^aH}o?|40JCnf0UN!`YUzA1wA++Ly)+mm(f+t$*`V4Eg}61v&bN5G;P2 zqIMc{vDrdj8uW_s_z)9pM3ECo?|8@pY7HIs@jMc~HOI-aU6GkNXl?nca;V;2@zF@8 z;177*4p*nQbvnPvx2QK_!D7S59`T4IGKsbha05jn$Ra~&;PIc0b6oCCLX3t&;GC=C zT~72lT5pa}Uq({_qRo3s%oS>pmr4kC1l!{!=4m(e5! zBb8Z$x>rAreEhc;K5%Ks9>gUAG<&PgM#x(zo_^?H7fPFA+6g7qr{h9YemR@aHh z#3*rGCY@goh)Xbo|6V3gm~1(_Rnup&E+MtCkSxhsCOu2fL0HsqoOFl03b4_nMyS>( zEdVkpgQP`zFzCZ%vUoJ^r}`tBf8@Fu*<8VJiuF0)Ds_ioLVchH_;;9Q&@G!xaKP$JCL`4%q$0s6G?bZ6%{=mO1 zFOBYEN4pZblar~`v8req@ZrtRb37u@0B~)j9a+m#ylm4OO0q3=O4Cz=B(fmiG<1ww z+@U%OXF)q&hdK1WS83ks@s?$q)d%r~e>B{_M9i~;cBt|aCP?>M=8dEC_x(uwMJKV> zsHibAf~vwD%Ikvstgw;(@ON^dZ`oyLdYJl5y^WzRCK-u6^F~PyR`nYP*;jJK?3&tE)0T4}P6BMm#|vUO(iime>NxLF;`f;& zUSX4Rf1m|5Qys^<51g^y1Qs;7(f)18uIG}i@^NkEj4zdOjJs!o%&q*%G>$}bMt9uQ zf0|Z^Y=4E<;$^q!^~=PnKjp2Rh;+0SGL}4;%!%Ejd}t@U|DC_HN)n#n$gS8z z4-QDC>TSRxLZF-PW#eHG)}~hn2o+D4erbV`&>U_(T&!uo!QCqg--}P~Xy-w&ilA(# z9LY$;I?FR>e1c`zG9|BKG%YeJqy1Ge7FKp}=xdm>Lo%$UYY$e-*NjgHInJBjB9;BU zu#eZg8;J$R3R@0NNen|gAW9tT?ZkC zmYX^5FO1i1T=*2#u_0LGdu|#Bz8+pEBA)dpkoV)b7FopLBYX0Hi^@H2G>r_KT2bJw z;m{vdeepz>n`3GWQ3ESnd=WA4xD}$_8ubtOXGZ|Hk$(E8iGcl+lw4xIgWua4xxtdn6%2&pt#+ z{*>2s&WdT60%G{EU1@%g$+W>;(Ql5#&c19U*p0SZZrPK~XPh~DDXEuQU<`F^nE|@L zxy@!(-H{T8_PX@8ZpkFR6nyuUS%l^$9EL8jS7>WkJPFo;N&CZ%PpRm1Pr=69pk-No z{;rXiD9of&wnZ$>;&n7ksg_<9rg+_V;f~ymC3Rmk7>xG~hmU14R|WTb7^(b79?Q?F zR&h6et4~SWf^adwS@~NX4ND-Uq-j^k-;6}*|2R0io~9&SOUK5zHe@){Hql6)TJFTp z(q_%N+tAESk!0t(O`APsWJyl#oWF4{i9eHe(gxK^f-96FqKUjP_V*k7@kNXZJBH9H z?l@7(3uTh^yFQpB(7fJ!B>w05zrh{Kmei%67QKD%Psa?!-tc*$5ylMBr(N7H#4G5p z@vBkzb~D%7px->RUyIw9ehjyOEX}mE#VhfA za~?WRmj@{im-vRA_ve>RqelN+v@FOaCo0zVAhR*0z`|d!1#KJsqOjO~WHuyFC|5bH z^V*1+CYNCNzr^U5VmW2l?EAB#HE1I4^)X4c45$mO!*$7U%bf&KohzRh5DuQ4+%u(r zu+sWq(eV-NrTrZ9{bQRVr9wEgMB;L-7P62&K)i2`PH8knJ0U1`D9FI7?`b50~Tmliw-KPck{bWkgP_X z5*j6ryuvk&&v-^XHNFfAgbl5a|20N{%NUO{1G+{W5c*AnzJwwe%GWAvXr#7c=uuUVD$ zEcExz1m(NGgWGFG&-X;^3bnDTet0f@^x3Qw z-xV{@h@DvIR4hKK&pg8H(@fBX$5TF@$b8FP-HjQRYJ4D~LG9P6((QdOF;L%Y&+!$* z8YVf-#Qh%x?G>Y)#f7KdSp5CXa=Y*B-#_z8KUf~RD+_aDJ7#GHQPv7hQX|Z>MxNmu zu($aJq1%I$S=3Zj5nMmGyf5AhouFuCn*B*6F-Qq-rQ}y^7@dJBCTnae$#5ytur+F_ z5}cmn;iNgU<8gYZx;#ai43_8WY)psmJ2!pqbOz^K@o%57^~Z_u~yS z#XuPgYr``0{g*5;D?4oX!oSV{yE@P*cOiBhRyroS>c+kK)($%Ui;1Jv@}RpovJ7p9 zB*=oDc%0mW`cgp?9z4eOlIYLxN}clW>30MMY@olkB7M6uvUE5@XHIDIzPoQeeU^?b z9+-|hp`H`)q;NUU#%AnBh(KR2sOa~6t;`EoqU_=K{-RVBf-yPIH{rPZ33pjjODF$- zrOqpCov=)bPTiaQe)R{5v)mV+lKpuSFKRv27@E)@V!fd~gKj7CrdQ~Ud2Z{3$#S;P zyIPyZ+(Ozt?BG)-R=(?#Hk$?{D|#dWcPau$e`&{B$@zF*`tPmsg#&LPs~i4yG24!# z?!pK12az-X99Ca&c5bovKS9B$s1oD{lJ8?e!n-lsqc;@c{Z+S`p=L-ik)i8P&j~K_ z$t&1YM)N5f0_R$OHk`R7WF?UuwP3z?XNQ7&?fj-gGKj^y!X5?BT)nJ=$L$5*%?kcY z(Yx*uTQrx-6r1n!A;Z%1YySuu?L&BQ#nhv#NTSld@EL6U2v+q**E@qVex$B%;*RPl z)>9ML`0LF;I$+W_^W`!!glxI1&9<$58B!vlS!z@VUobo8cnoOSZqGDf5bFsbUJSIG zn#k27c^xo}#rW+t2b@0ihev(QO3Eey48Jix4$UNKqmc8{uZ@bAhBRv^lkWo(M)|d- zR|D=&1T6i=^_|6yt9auH6{cr*J-Q!ewvq39jWDzS`IX9}{6olW$y&#H!+Y)dnFw!= zM?^LmZiB~?+ke@sWTw9>)m83$2k%Tf&}&{yELXRj*o*o-)LPLI^N zt&jRIUk76?PDUlMi^CuRb&eDd0<1=K02TvS3}7*U#Q+uqSPWn>fW-h716T}TF@VJY z76VueU@?Hj02TvS3}7*U#Q+uqSPWn>fW-h716T}TF@VJY76VueU@?Hj02TvS3}7*U z#Q+uqSPWn>fW-h716T}TF@VJY76VueU@?Hj02TvS3}7*U#Q+uqSPWn>fW-h716T}T M@&5-b4n{8jAO5tySpWb4 diff --git a/test/jdk/tools/jpackage/resources/icon.ico b/test/jdk/tools/jpackage/resources/icon.ico new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..00bc0ad44b7bac2426f5a110cf3ebbf185c358f3 GIT binary patch literal 2086 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