--- /dev/null 2019-11-20 11:02:14.000000000 -0500
+++ new/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WinMsiBundler.java 2019-11-20 11:02:12.659490100 -0500
@@ -0,0 +1,580 @@
+/*
+ * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.jpackage.internal;
+
+import java.io.*;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.text.MessageFormat;
+import java.util.*;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+
+import static jdk.incubator.jpackage.internal.WindowsBundlerParam.*;
+
+/**
+ * WinMsiBundler
+ *
+ * Produces .msi installer from application image. Uses WiX Toolkit to build
+ * .msi installer.
+ *
+ * {@link #execute} method creates a number of source files with the description
+ * of installer to be processed by WiX tools. Generated source files are stored
+ * in "config" subdirectory next to "app" subdirectory in the root work
+ * directory. The following WiX source files are generated:
+ *
+ * - main.wxs. Main source file with the installer description
+ *
- bundle.wxf. Source file with application and Java run-time directory tree
+ * description.
+ *
+ *
+ * main.wxs file is a copy of main.wxs resource from
+ * jdk.incubator.jpackage.internal.resources package. It is parametrized with the
+ * following WiX variables:
+ *
+ * - JpAppName. Name of the application. Set to the value of --name command
+ * line option
+ *
- JpAppVersion. Version of the application. Set to the value of
+ * --app-version command line option
+ *
- JpAppVendor. Vendor of the application. Set to the value of --vendor
+ * command line option
+ *
- JpAppDescription. Description of the application. Set to the value of
+ * --description command line option
+ *
- JpProductCode. Set to product code UUID of the application. Random value
+ * generated by jpackage every time {@link #execute} method is called
+ *
- JpProductUpgradeCode. Set to upgrade code UUID of the application. Random
+ * value generated by jpackage every time {@link #execute} method is called if
+ * --win-upgrade-uuid command line option is not specified. Otherwise this
+ * variable is set to the value of --win-upgrade-uuid command line option
+ *
- JpAllowDowngrades. Set to "yes" if --win-upgrade-uuid command line option
+ * was specified. Undefined otherwise
+ *
- JpLicenseRtf. Set to the value of --license-file command line option.
+ * Undefined is --license-file command line option was not specified
+ *
- JpInstallDirChooser. Set to "yes" if --win-dir-chooser command line
+ * option was specified. Undefined otherwise
+ *
- JpConfigDir. Absolute path to the directory with generated WiX source
+ * files.
+ *
- JpIsSystemWide. Set to "yes" if --win-per-user-install command line
+ * option was not specified. Undefined otherwise
+ *
+ */
+public class WinMsiBundler extends AbstractBundler {
+
+ public static final BundlerParamInfo APP_BUNDLER =
+ new WindowsBundlerParam<>(
+ "win.app.bundler",
+ WinAppBundler.class,
+ params -> new WinAppBundler(),
+ null);
+
+ 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
+ );
+
+ private static final BundlerParamInfo UPGRADE_UUID =
+ new WindowsBundlerParam<>(
+ Arguments.CLIOptions.WIN_UPGRADE_UUID.getId(),
+ String.class,
+ null,
+ (s, p) -> s);
+
+ @Override
+ public String getName() {
+ return I18N.getString("msi.bundler.name");
+ }
+
+ @Override
+ public String getID() {
+ return "msi";
+ }
+
+ @Override
+ public String getBundleType() {
+ return "INSTALLER";
+ }
+
+ @Override
+ public File execute(Map params,
+ File outputParentDir) throws PackagerException {
+ return bundle(params, outputParentDir);
+ }
+
+ @Override
+ public boolean supported(boolean platformInstaller) {
+ try {
+ if (wixToolset == null) {
+ wixToolset = WixTool.toolset();
+ }
+ return true;
+ } catch (ConfigException ce) {
+ Log.error(ce.getMessage());
+ if (ce.getAdvice() != null) {
+ Log.error(ce.getAdvice());
+ }
+ } catch (Exception e) {
+ Log.error(e.getMessage());
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isDefault() {
+ return false;
+ }
+
+ private static UUID getUpgradeCode(Map params) {
+ String upgradeCode = UPGRADE_UUID.fetchFrom(params);
+ if (upgradeCode != null) {
+ return UUID.fromString(upgradeCode);
+ }
+ return createNameUUID("UpgradeCode", params, List.of(VENDOR, APP_NAME));
+ }
+
+ private static UUID getProductCode(Map params) {
+ return createNameUUID("ProductCode", params, List.of(VENDOR, APP_NAME,
+ VERSION));
+ }
+
+ private static UUID createNameUUID(String prefix,
+ Map params,
+ List> components) {
+ String key = Stream.concat(Stream.of(prefix), components.stream().map(
+ c -> c.fetchFrom(params))).collect(Collectors.joining("/"));
+ return UUID.nameUUIDFromBytes(key.getBytes(StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public boolean validate(Map params)
+ throws ConfigException {
+ try {
+ if (wixToolset == null) {
+ wixToolset = WixTool.toolset();
+ }
+
+ try {
+ getUpgradeCode(params);
+ } catch (IllegalArgumentException ex) {
+ throw new ConfigException(ex);
+ }
+
+ for (var toolInfo: wixToolset.values()) {
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.tool-version"), toolInfo.path.getFileName(),
+ toolInfo.version));
+ }
+
+ wixSourcesBuilder.setWixVersion(wixToolset.get(WixTool.Light).version);
+
+ wixSourcesBuilder.logWixFeatures();
+
+ /********* validate bundle parameters *************/
+
+ String version = PRODUCT_VERSION.fetchFrom(params);
+ if (!isVersionStringValid(version)) {
+ throw new ConfigException(
+ MessageFormat.format(I18N.getString(
+ "error.version-string-wrong-format"), version),
+ MessageFormat.format(I18N.getString(
+ "error.version-string-wrong-format.advice"),
+ PRODUCT_VERSION.getID()));
+ }
+
+ // only one mime type per association, at least one file extension
+ List