8223325: Improve wix sources generated by jpackage
Submitted-by: asemenyuk
Reviewed-by: aherrick, almatvee
--- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/DesktopIntegration.java Fri Oct 18 11:00:57 2019 -0400
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/DesktopIntegration.java Fri Oct 18 14:14:37 2019 -0400
@@ -31,7 +31,6 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.imageio.ImageIO;
-import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import static jdk.jpackage.internal.LinuxAppBundler.ICON_PNG;
@@ -353,15 +352,9 @@
}
private void createFileAssociationsMimeInfoFile() throws IOException {
- XMLOutputFactory xmlFactory = XMLOutputFactory.newInstance();
-
- try (Writer w = new BufferedWriter(new FileWriter(
- mimeInfoFile.srcPath().toFile()))) {
- XMLStreamWriter xml = xmlFactory.createXMLStreamWriter(w);
-
- xml.writeStartDocument();
+ IOUtils.createXml(mimeInfoFile.srcPath(), xml -> {
xml.writeStartElement("mime-info");
- xml.writeNamespace("xmlns",
+ xml.writeDefaultNamespace(
"http://www.freedesktop.org/standards/shared-mime-info");
for (var assoc : associations) {
@@ -369,14 +362,7 @@
}
xml.writeEndElement();
- xml.writeEndDocument();
- xml.flush();
- xml.close();
-
- } catch (XMLStreamException ex) {
- Log.verbose(ex);
- throw new IOException(ex);
- }
+ });
}
private Map<String, Path> createFileAssociationIconFiles() throws
--- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java Fri Oct 18 11:00:57 2019 -0400
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java Fri Oct 18 14:14:37 2019 -0400
@@ -26,7 +26,6 @@
package jdk.jpackage.internal;
import java.io.*;
-import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -140,9 +139,7 @@
try {
String licenseFile = LICENSE_FILE.fetchFrom(params);
if (licenseFile != null) {
- return Files.lines(Path.of(licenseFile),
- StandardCharsets.UTF_8).collect(
- Collectors.joining("\n"));
+ return Files.readString(Path.of(licenseFile));
}
} catch (IOException e) {
Log.verbose(e);
--- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgBundler.java Fri Oct 18 11:00:57 2019 -0400
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgBundler.java Fri Oct 18 14:14:37 2019 -0400
@@ -25,12 +25,7 @@
package jdk.jpackage.internal;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.io.Writer;
+import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
@@ -42,9 +37,6 @@
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
-import javax.xml.stream.XMLOutputFactory;
-import javax.xml.stream.XMLStreamException;
-import javax.xml.stream.XMLStreamWriter;
import static jdk.jpackage.internal.StandardBundlerParam.*;
import static jdk.jpackage.internal.MacBaseInstallerBundler.SIGNING_KEYCHAIN;
@@ -155,7 +147,7 @@
return createPKG(params, outdir, appImageDir);
}
return null;
- } catch (IOException|URISyntaxException ex) {
+ } catch (IOException ex) {
Log.verbose(ex);
throw new PackagerException(ex);
}
@@ -219,25 +211,19 @@
getScripts_PostinstallFile(params).setExecutable(true, false);
}
- private String URLEncoding(String pkgName) throws URISyntaxException {
+ private static String URLEncoding(String pkgName) throws URISyntaxException {
URI uri = new URI(null, null, pkgName, null);
return uri.toASCIIString();
}
private void prepareDistributionXMLFile(Map<String, ? super Object> params)
- throws IOException, URISyntaxException {
+ throws IOException {
File f = getConfig_DistributionXMLFile(params);
Log.verbose(MessageFormat.format(I18N.getString(
"message.preparing-distribution-dist"), f.getAbsolutePath()));
- XMLOutputFactory xmlFactory = XMLOutputFactory.newInstance();
-
- try (Writer w = new OutputStreamWriter(new FileOutputStream(f), "UTF-8")) {
- XMLStreamWriter xml = xmlFactory.createXMLStreamWriter(w);
-
- xml.writeStartDocument("UTF-8", "1.0");
-
+ IOUtils.createXml(f.toPath(), xml -> {
xml.writeStartElement("installer-gui-script");
xml.writeAttribute("minSpecVersion", "1");
@@ -303,24 +289,20 @@
xml.writeAttribute("id", appId);
xml.writeAttribute("version", VERSION.fetchFrom(params));
xml.writeAttribute("onConclusion", "none");
- xml.writeCharacters(URLEncoding(
- getPackages_AppPackage(params).getName()));
+ try {
+ xml.writeCharacters(URLEncoding(
+ getPackages_AppPackage(params).getName()));
+ } catch (URISyntaxException ex) {
+ throw new IOException(ex);
+ }
xml.writeEndElement(); // </pkg-ref>
xml.writeEndElement(); // </installer-gui-script>
-
- xml.writeEndDocument();
- xml.flush();
- xml.close();
-
- } catch (XMLStreamException ex) {
- Log.verbose(ex);
- throw new IOException(ex);
- }
+ });
}
private boolean prepareConfigFiles(Map<String, ? super Object> params)
- throws IOException, URISyntaxException {
+ throws IOException {
createResource(DEFAULT_BACKGROUND_IMAGE, params)
.setCategory(I18N.getString("resource.pkg-background-image"))
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java Fri Oct 18 11:00:57 2019 -0400
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java Fri Oct 18 14:14:37 2019 -0400
@@ -1,218 +1,233 @@
-/*
- * 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.BufferedWriter;
-import java.io.FileInputStream;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.Writer;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.ArrayList;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.stream.XMLOutputFactory;
-import javax.xml.stream.XMLStreamException;
-import javax.xml.stream.XMLStreamWriter;
-import javax.xml.xpath.XPath;
-import javax.xml.xpath.XPathConstants;
-import javax.xml.xpath.XPathExpressionException;
-import javax.xml.xpath.XPathFactory;
-import org.w3c.dom.Document;
-import org.w3c.dom.NodeList;
-import org.xml.sax.SAXException;
-
-import static jdk.jpackage.internal.StandardBundlerParam.*;
-
-class AppImageFile {
-
- // These values will be loaded from AppImage xml file.
- private final String creatorVersion;
- private final String creatorPlatform;
- private final String launcherName;
- private final List<String> addLauncherNames;
-
- final static String XML_FILENAME = ".jpackage.xml";
-
- private final static Map<Platform, String> PLATFORM_LABELS = Map.of(
- Platform.LINUX, "linux", Platform.WINDOWS, "windows", Platform.MAC,
- "macOS");
-
-
- private AppImageFile() {
- this(null, null, null, null);
- }
-
- private AppImageFile(String launcherName, List<String> addLauncherNames,
- String creatorVersion, String creatorPlatform) {
- this.launcherName = launcherName;
- this.addLauncherNames = addLauncherNames;
- this.creatorVersion = creatorVersion;
- this.creatorPlatform = creatorPlatform;
- }
-
- /**
- * Would return null to indicate stored command line is invalid.
- */
- List<String> getAddLauncherNames() {
- return addLauncherNames;
- }
-
- String getLauncherName() {
- return launcherName;
- }
-
- void verifyCompatible() throws ConfigException {
- // Just do nohing for now.
- }
-
- static void save(Path appImage, Map<String, Object> params)
- throws IOException {
- Path xmlFile = appImage.resolve(XML_FILENAME);
- XMLOutputFactory xmlFactory = XMLOutputFactory.newInstance();
-
- try (Writer w = new BufferedWriter(new FileWriter(xmlFile.toFile()))) {
- XMLStreamWriter xml = xmlFactory.createXMLStreamWriter(w);
-
- xml.writeStartDocument();
- xml.writeStartElement("jpackage-state");
- xml.writeAttribute("version", getVersion());
- xml.writeAttribute("platform", getPlatform());
-
- xml.writeStartElement("main-launcher");
- xml.writeCharacters(APP_NAME.fetchFrom(params));
- xml.writeEndElement();
-
- List<Map<String, ? super Object>> addLaunchers =
- ADD_LAUNCHERS.fetchFrom(params);
-
- for (int i = 0; i < addLaunchers.size(); i++) {
- Map<String, ? super Object> sl = addLaunchers.get(i);
- xml.writeStartElement("add-launcher");
- xml.writeCharacters(APP_NAME.fetchFrom(sl));
- xml.writeEndElement();
- }
-
- xml.writeEndElement();
- xml.writeEndDocument();
- xml.flush();
- xml.close();
-
- } catch (XMLStreamException ex) {
- Log.verbose(ex);
- throw new IOException(ex);
- }
- }
-
- static AppImageFile load(Path appImageDir) throws IOException {
- try {
- Path path = appImageDir.resolve(XML_FILENAME);
- DocumentBuilderFactory dbf =
- DocumentBuilderFactory.newDefaultInstance();
- dbf.setFeature(
- "http://apache.org/xml/features/nonvalidating/load-external-dtd",
- false);
- DocumentBuilder b = dbf.newDocumentBuilder();
- Document doc = b.parse(new FileInputStream(path.toFile()));
-
- XPath xPath = XPathFactory.newInstance().newXPath();
-
- String mainLauncher = xpathQueryNullable(xPath,
- "/jpackage-state/main-launcher/text()", doc);
- if (mainLauncher == null) {
- // No main launcher, this is fatal.
- return new AppImageFile();
- }
-
- List<String> addLaunchers = new ArrayList<String>();
-
- String platform = xpathQueryNullable(xPath,
- "/jpackage-state/@platform", doc);
-
- String version = xpathQueryNullable(xPath,
- "/jpackage-state/@version", doc);
-
- NodeList launcherNameNodes = (NodeList) xPath.evaluate(
- "/jpackage-state/add-launcher/text()", doc,
- XPathConstants.NODESET);
-
- for (int i = 0; i != launcherNameNodes.getLength(); i++) {
- addLaunchers.add(launcherNameNodes.item(i).getNodeValue());
- }
-
- AppImageFile file = new AppImageFile(
- mainLauncher, addLaunchers, version, platform);
- if (!file.isValid()) {
- file = new AppImageFile();
- }
- return file;
- } catch (ParserConfigurationException | SAXException ex) {
- // Let caller sort this out
- throw new IOException(ex);
- } catch (XPathExpressionException ex) {
- // This should never happen as XPath expressions should be correct
- throw new RuntimeException(ex);
- }
- }
-
- private static String xpathQueryNullable(XPath xPath, String xpathExpr,
- Document xml) throws XPathExpressionException {
- NodeList nodes = (NodeList) xPath.evaluate(xpathExpr, xml,
- XPathConstants.NODESET);
- if (nodes != null && nodes.getLength() > 0) {
- return nodes.item(0).getNodeValue();
- }
- return null;
- }
-
- private static String getVersion() {
- return System.getProperty("java.version");
- }
-
- private static String getPlatform() {
- return PLATFORM_LABELS.get(Platform.getPlatform());
- }
-
- private boolean isValid() {
- if (launcherName == null || launcherName.length() == 0 ||
- addLauncherNames.indexOf("") != -1) {
- // Some launchers have empty names. This is invalid.
- return false;
- }
-
- // Add more validation.
-
- return true;
- }
-
-}
+/*
+ * 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.FileInputStream;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import static jdk.jpackage.internal.StandardBundlerParam.*;
+
+public class AppImageFile {
+
+ // These values will be loaded from AppImage xml file.
+ private final String creatorVersion;
+ private final String creatorPlatform;
+ private final String launcherName;
+ private final List<String> addLauncherNames;
+
+ public final static String FILENAME = ".jpackage.xml";
+
+ private final static Map<Platform, String> PLATFORM_LABELS = Map.of(
+ Platform.LINUX, "linux", Platform.WINDOWS, "windows", Platform.MAC,
+ "macOS");
+
+
+ private AppImageFile() {
+ this(null, null, null, null);
+ }
+
+ private AppImageFile(String launcherName, List<String> addLauncherNames,
+ String creatorVersion, String creatorPlatform) {
+ this.launcherName = launcherName;
+ this.addLauncherNames = addLauncherNames;
+ this.creatorVersion = creatorVersion;
+ this.creatorPlatform = creatorPlatform;
+ }
+
+ /**
+ * Returns list of additional launchers configured for the application.
+ * Each item in the list is not null or empty string.
+ * Returns empty list for application without additional launchers.
+ */
+ List<String> getAddLauncherNames() {
+ return addLauncherNames;
+ }
+
+ /**
+ * Returns main application launcher name. Never returns null or empty value.
+ */
+ String getLauncherName() {
+ return launcherName;
+ }
+
+ void verifyCompatible() throws ConfigException {
+ // Just do nothing for now.
+ }
+
+ /**
+ * Saves file with application image info in application image.
+ * @param appImageDir - path to application image
+ * @throws IOException
+ */
+ static void save(Path appImage, Map<String, Object> params)
+ throws IOException {
+ IOUtils.createXml(appImage.resolve(FILENAME), xml -> {
+ xml.writeStartElement("jpackage-state");
+ xml.writeAttribute("version", getVersion());
+ xml.writeAttribute("platform", getPlatform());
+
+ xml.writeStartElement("main-launcher");
+ xml.writeCharacters(APP_NAME.fetchFrom(params));
+ xml.writeEndElement();
+
+ List<Map<String, ? super Object>> addLaunchers =
+ ADD_LAUNCHERS.fetchFrom(params);
+
+ for (int i = 0; i < addLaunchers.size(); i++) {
+ Map<String, ? super Object> sl = addLaunchers.get(i);
+ xml.writeStartElement("add-launcher");
+ xml.writeCharacters(APP_NAME.fetchFrom(sl));
+ xml.writeEndElement();
+ }
+ });
+ }
+
+ /**
+ * Loads application image info from application image.
+ * @param appImageDir - path to application image
+ * @return valid info about application image or null
+ * @throws IOException
+ */
+ static AppImageFile load(Path appImageDir) throws IOException {
+ try {
+ Path path = appImageDir.resolve(FILENAME);
+ DocumentBuilderFactory dbf =
+ DocumentBuilderFactory.newDefaultInstance();
+ dbf.setFeature(
+ "http://apache.org/xml/features/nonvalidating/load-external-dtd",
+ false);
+ DocumentBuilder b = dbf.newDocumentBuilder();
+ Document doc = b.parse(new FileInputStream(path.toFile()));
+
+ XPath xPath = XPathFactory.newInstance().newXPath();
+
+ String mainLauncher = xpathQueryNullable(xPath,
+ "/jpackage-state/main-launcher/text()", doc);
+ if (mainLauncher == null) {
+ // No main launcher, this is fatal.
+ return new AppImageFile();
+ }
+
+ List<String> addLaunchers = new ArrayList<String>();
+
+ String platform = xpathQueryNullable(xPath,
+ "/jpackage-state/@platform", doc);
+
+ String version = xpathQueryNullable(xPath,
+ "/jpackage-state/@version", doc);
+
+ NodeList launcherNameNodes = (NodeList) xPath.evaluate(
+ "/jpackage-state/add-launcher/text()", doc,
+ XPathConstants.NODESET);
+
+ for (int i = 0; i != launcherNameNodes.getLength(); i++) {
+ addLaunchers.add(launcherNameNodes.item(i).getNodeValue());
+ }
+
+ AppImageFile file = new AppImageFile(
+ mainLauncher, addLaunchers, version, platform);
+ if (!file.isValid()) {
+ file = new AppImageFile();
+ }
+ return file;
+ } catch (ParserConfigurationException | SAXException ex) {
+ // Let caller sort this out
+ throw new IOException(ex);
+ } catch (XPathExpressionException ex) {
+ // This should never happen as XPath expressions should be correct
+ throw new RuntimeException(ex);
+ }
+ }
+
+ /**
+ * Returns list of launcher names configured for the application.
+ * The first item in the returned list is man launcher name.
+ * Following items in the list are names of additional launchers.
+ */
+ static List<String> getLauncherNames(Path appImageDir,
+ Map<String, ? super Object> params) {
+ List<String> launchers = new ArrayList<>();
+ try {
+ AppImageFile appImageInfo = AppImageFile.load(appImageDir);
+ if (appImageInfo != null) {
+ launchers.add(appImageInfo.getLauncherName());
+ launchers.addAll(appImageInfo.getAddLauncherNames());
+ return launchers;
+ }
+ } catch (IOException ioe) {
+ Log.verbose(ioe);
+ }
+
+ launchers.add(APP_NAME.fetchFrom(params));
+ ADD_LAUNCHERS.fetchFrom(params).stream().map(APP_NAME::fetchFrom).forEach(
+ launchers::add);
+ return launchers;
+ }
+
+ private static String xpathQueryNullable(XPath xPath, String xpathExpr,
+ Document xml) throws XPathExpressionException {
+ NodeList nodes = (NodeList) xPath.evaluate(xpathExpr, xml,
+ XPathConstants.NODESET);
+ if (nodes != null && nodes.getLength() > 0) {
+ return nodes.item(0).getNodeValue();
+ }
+ return null;
+ }
+
+ private static String getVersion() {
+ return System.getProperty("java.version");
+ }
+
+ private static String getPlatform() {
+ return PLATFORM_LABELS.get(Platform.getPlatform());
+ }
+
+ private boolean isValid() {
+ if (launcherName == null || launcherName.length() == 0 ||
+ addLauncherNames.indexOf("") != -1) {
+ // Some launchers have empty names. This is invalid.
+ return false;
+ }
+
+ // Add more validation.
+
+ return true;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DottedVersion.java Fri Oct 18 14:14:37 2019 -0400
@@ -0,0 +1,101 @@
+/*
+ * 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.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Dotted numeric version string.
+ * E.g.: 1.0.37, 10, 0.5
+ */
+class DottedVersion implements Comparable<String> {
+
+ public DottedVersion(String version) {
+ components = parseVersionString(version);
+ value = version;
+ }
+
+ @Override
+ public int compareTo(String o) {
+ int result = 0;
+ int[] otherComponents = parseVersionString(o);
+ for (int i = 0; i < Math.min(components.length, otherComponents.length)
+ && result == 0; ++i) {
+ result = components[i] - otherComponents[i];
+ }
+
+ if (result == 0) {
+ result = components.length - otherComponents.length;
+ }
+
+ return result;
+ }
+
+ private static int[] parseVersionString(String version) {
+ Objects.requireNonNull(version);
+ if (version.isEmpty()) {
+ throw new IllegalArgumentException("Version may not be empty string");
+ }
+
+ int lastNotZeroIdx = -1;
+ List<Integer> components = new ArrayList<>();
+ for (var component : version.split("\\.", -1)) {
+ if (component.isEmpty()) {
+ throw new IllegalArgumentException(String.format(
+ "Version [%s] contains a zero lenght component", version));
+ }
+
+ int num = Integer.parseInt(component);
+ if (num < 0) {
+ throw new IllegalArgumentException(String.format(
+ "Version [%s] contains invalid component [%s]", version,
+ component));
+ }
+
+ if (num != 0) {
+ lastNotZeroIdx = components.size();
+ }
+ components.add(num);
+ }
+
+ if (lastNotZeroIdx + 1 != components.size()) {
+ // Strip trailing zeros.
+ components = components.subList(0, lastNotZeroIdx + 1);
+ }
+
+ return components.stream().mapToInt(Integer::intValue).toArray();
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+
+ final private int[] components;
+ final private String value;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FileAssociation.java Fri Oct 18 14:14:37 2019 -0400
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jpackage.internal;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.stream.Collectors;
+import static jdk.jpackage.internal.StandardBundlerParam.*;
+
+final class FileAssociation {
+ void verify() {
+ if (extensions.isEmpty()) {
+ Log.error(I18N.getString(
+ "message.creating-association-with-null-extension"));
+ }
+ }
+
+ static List<FileAssociation> fetchFrom(Map<String, ? super Object> params) {
+ String launcherName = APP_NAME.fetchFrom(params);
+
+ return FILE_ASSOCIATIONS.fetchFrom(params).stream().filter(
+ Objects::nonNull).map(fa -> {
+ FileAssociation assoc = new FileAssociation();
+
+ assoc.launcherPath = Path.of(launcherName);
+ assoc.description = FA_DESCRIPTION.fetchFrom(fa);
+ assoc.extensions = Optional.ofNullable(
+ FA_EXTENSIONS.fetchFrom(fa)).orElse(Collections.emptyList());
+ assoc.mimeTypes = Optional.ofNullable(
+ FA_CONTENT_TYPE.fetchFrom(fa)).orElse(Collections.emptyList());
+
+ File icon = FA_ICON.fetchFrom(fa);
+ if (icon != null) {
+ assoc.iconPath = icon.toPath();
+ }
+
+ return assoc;
+ }).collect(Collectors.toList());
+ }
+
+ Path launcherPath;
+ Path iconPath;
+ List<String> mimeTypes;
+ List<String> extensions;
+ String description;
+}
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java Fri Oct 18 11:00:57 2019 -0400
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java Fri Oct 18 14:14:37 2019 -0400
@@ -26,14 +26,19 @@
package jdk.jpackage.internal;
import java.io.*;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
import java.nio.channels.FileChannel;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.*;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
/**
* IOUtils
@@ -221,4 +226,105 @@
file.getAbsolutePath());
}
}
+
+ public static Path replaceSuffix(Path path, String suffix) {
+ Path parent = path.getParent();
+ String filename = path.getFileName().toString().replaceAll("\\.[^.]*$", "")
+ + Optional.ofNullable(suffix).orElse("");
+ return parent != null ? parent.resolve(filename) : Path.of(filename);
+ }
+
+ public static Path addSuffix(Path path, String suffix) {
+ Path parent = path.getParent();
+ String filename = path.getFileName().toString() + suffix;
+ return parent != null ? parent.resolve(filename) : Path.of(filename);
+ }
+
+ @FunctionalInterface
+ public static interface XmlConsumer {
+ void accept(XMLStreamWriter xml) throws IOException, XMLStreamException;
+ }
+
+ public static void createXml(Path dstFile, XmlConsumer xmlConsumer) throws
+ IOException {
+ XMLOutputFactory xmlFactory = XMLOutputFactory.newInstance();
+ try (Writer w = new BufferedWriter(new FileWriter(dstFile.toFile()))) {
+ // Wrap with pretty print proxy
+ XMLStreamWriter xml = (XMLStreamWriter) Proxy.newProxyInstance(
+ XMLStreamWriter.class.getClassLoader(), new Class<?>[]{
+ XMLStreamWriter.class}, new PrettyPrintHandler(
+ xmlFactory.createXMLStreamWriter(w)));
+
+ xml.writeStartDocument();
+ xmlConsumer.accept(xml);
+ xml.writeEndDocument();
+ xml.flush();
+ xml.close();
+ } catch (XMLStreamException ex) {
+ throw new IOException(ex);
+ } catch (IOException ex) {
+ throw ex;
+ }
+ }
+
+ private static class PrettyPrintHandler implements InvocationHandler {
+
+ PrettyPrintHandler(XMLStreamWriter target) {
+ this.target = target;
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws
+ Throwable {
+ switch (method.getName()) {
+ case "writeStartElement":
+ // update state of parent node
+ if (depth > 0) {
+ hasChildElement.put(depth - 1, true);
+ }
+ // reset state of current node
+ hasChildElement.put(depth, false);
+ // indent for current depth
+ target.writeCharacters(EOL);
+ target.writeCharacters(repeat(depth, INDENT));
+ depth++;
+ break;
+ case "writeEndElement":
+ depth--;
+ if (hasChildElement.get(depth) == true) {
+ target.writeCharacters(EOL);
+ target.writeCharacters(repeat(depth, INDENT));
+ }
+ break;
+ case "writeProcessingInstruction":
+ case "writeEmptyElement":
+ // update state of parent node
+ if (depth > 0) {
+ hasChildElement.put(depth - 1, true);
+ }
+ // indent for current depth
+ target.writeCharacters(EOL);
+ target.writeCharacters(repeat(depth, INDENT));
+ break;
+ default:
+ break;
+ }
+ method.invoke(target, args);
+ return null;
+ }
+
+ private static String repeat(int d, String s) {
+ StringBuilder sb = new StringBuilder();
+ while (d-- > 0) {
+ sb.append(s);
+ }
+ return sb.toString();
+ }
+
+ private final XMLStreamWriter target;
+ private int depth = 0;
+ private final Map<Integer, Boolean> hasChildElement = new HashMap<>();
+ private static final String INDENT = " ";
+ private static final String EOL = "\n";
+ }
}
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PathGroup.java Fri Oct 18 11:00:57 2019 -0400
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PathGroup.java Fri Oct 18 14:14:37 2019 -0400
@@ -45,13 +45,24 @@
*/
final class PathGroup {
PathGroup(Map<Object, Path> paths) {
- entries = Collections.unmodifiableMap(paths);
+ entries = new HashMap<>(paths);
}
Path getPath(Object id) {
+ if (id == null) {
+ throw new NullPointerException();
+ }
return entries.get(id);
}
+ void setPath(Object id, Path path) {
+ if (path != null) {
+ entries.put(id, path);
+ } else {
+ entries.remove(id);
+ }
+ }
+
/**
* All configured entries.
*/
@@ -98,11 +109,15 @@
}
void copy(PathGroup dst) throws IOException {
- copy(this, dst, false);
+ copy(this, dst, null, false);
}
void move(PathGroup dst) throws IOException {
- copy(this, dst, true);
+ copy(this, dst, null, true);
+ }
+
+ void transform(PathGroup dst, TransformHandler handler) throws IOException {
+ copy(this, dst, handler, false);
}
static interface Facade<T> {
@@ -129,18 +144,57 @@
default void move(Facade<T> dst) throws IOException {
pathGroup().move(dst.pathGroup());
}
+
+ default void transform(Facade<T> dst, TransformHandler handler) throws
+ IOException {
+ pathGroup().transform(dst.pathGroup(), handler);
+ }
+ }
+
+ static interface TransformHandler {
+ public void copyFile(Path src, Path dst) throws IOException;
+ public void createDirectory(Path dir) throws IOException;
}
- private static void copy(PathGroup src, PathGroup dst, boolean move) throws
- IOException {
- copy(move, src.entries.keySet().stream().filter(
- id -> dst.entries.containsKey(id)).map(id -> Map.entry(
- src.entries.get(id), dst.entries.get(id))).collect(
- Collectors.toCollection(ArrayList::new)));
+ private static void copy(PathGroup src, PathGroup dst,
+ TransformHandler handler, boolean move) throws IOException {
+ List<Map.Entry<Path, Path>> copyItems = new ArrayList<>();
+ List<Path> excludeItems = new ArrayList<>();
+
+ for (var id: src.entries.keySet()) {
+ Path srcPath = src.entries.get(id);
+ if (dst.entries.containsKey(id)) {
+ copyItems.add(Map.entry(srcPath, dst.entries.get(id)));
+ } else {
+ excludeItems.add(srcPath);
+ }
+ }
+
+ copy(move, copyItems, excludeItems, handler);
}
- private static void copy(boolean move, List<Map.Entry<Path, Path>> entries)
- throws IOException {
+ private static void copy(boolean move, List<Map.Entry<Path, Path>> entries,
+ List<Path> excludePaths, TransformHandler handler) throws
+ IOException {
+
+ if (handler == null) {
+ handler = new TransformHandler() {
+ @Override
+ public void copyFile(Path src, Path dst) throws IOException {
+ Files.createDirectories(dst.getParent());
+ if (move) {
+ Files.move(src, dst);
+ } else {
+ Files.copy(src, dst);
+ }
+ }
+
+ @Override
+ public void createDirectory(Path dir) throws IOException {
+ Files.createDirectories(dir);
+ }
+ };
+ }
// destination -> source file mapping
Map<Path, Path> actions = new HashMap<>();
@@ -149,12 +203,11 @@
Path dst = action.getValue();
if (src.toFile().isDirectory()) {
try (Stream<Path> stream = Files.walk(src)) {
- stream.forEach(path -> actions.put(dst.resolve(
- src.relativize(path)).toAbsolutePath().normalize(),
- path));
+ stream.sequential().forEach(path -> actions.put(dst.resolve(
+ src.relativize(path)).normalize(), path));
}
} else {
- actions.put(dst.toAbsolutePath().normalize(), src);
+ actions.put(dst.normalize(), src);
}
}
@@ -162,19 +215,18 @@
Path dst = action.getKey();
Path src = action.getValue();
+ if (excludePaths.stream().anyMatch(src::startsWith)) {
+ continue;
+ }
+
if (src.equals(dst) || !src.toFile().exists()) {
continue;
}
if (src.toFile().isDirectory()) {
- Files.createDirectories(dst);
+ handler.createDirectory(dst);
} else {
- Files.createDirectories(dst.getParent());
- if (move) {
- Files.move(src, dst);
- } else {
- Files.copy(src, dst);
- }
+ handler.copyFile(src, dst);
}
}
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ToolValidator.java Fri Oct 18 11:00:57 2019 -0400
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ToolValidator.java Fri Oct 18 14:14:37 2019 -0400
@@ -25,23 +25,32 @@
package jdk.jpackage.internal;
import java.io.IOException;
+import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Stream;
public final class ToolValidator {
- ToolValidator(String name) {
- this.name = name;
+ ToolValidator(String tool) {
+ this(Path.of(tool));
+ }
+
+ ToolValidator(Path toolPath) {
+ this.toolPath = toolPath;
args = new ArrayList<>();
if (Platform.getPlatform() == Platform.LINUX) {
setCommandLine("--version");
}
+
+ setToolNotFoundErrorHandler(null);
+ setToolOldVersionErrorHandler(null);
}
ToolValidator setCommandLine(String... args) {
@@ -59,10 +68,23 @@
return this;
}
+ ToolValidator setToolNotFoundErrorHandler(
+ BiFunction<String, IOException, ConfigException> v) {
+ toolNotFoundErrorHandler = v;
+ return this;
+ }
+
+ ToolValidator setToolOldVersionErrorHandler(BiFunction<String, String, ConfigException> v) {
+ toolOldVersionErrorHandler = v;
+ return this;
+ }
+
ConfigException validate() {
List<String> cmdline = new ArrayList<>();
- cmdline.add(name);
+ cmdline.add(toolPath.toString());
cmdline.addAll(args);
+
+ String name = toolPath.getFileName().toString();
try {
ProcessBuilder pb = new ProcessBuilder(cmdline);
AtomicBoolean canUseTool = new AtomicBoolean();
@@ -71,16 +93,20 @@
canUseTool.setPlain(true);
}
+ String[] version = new String[1];
Executor.of(pb).setOutputConsumer(lines -> {
if (versionParser != null && minimalVersion != null) {
- String version = versionParser.apply(lines);
- if (minimalVersion.compareTo(version) < 0) {
+ version[0] = versionParser.apply(lines);
+ if (minimalVersion.compareTo(version[0]) < 0) {
canUseTool.setPlain(true);
}
}
}).execute();
if (!canUseTool.getPlain()) {
+ if (toolOldVersionErrorHandler != null) {
+ return toolOldVersionErrorHandler.apply(name, version[0]);
+ }
return new ConfigException(MessageFormat.format(I18N.getString(
"error.tool-old-version"), name, minimalVersion),
MessageFormat.format(I18N.getString(
@@ -88,6 +114,9 @@
minimalVersion));
}
} catch (IOException e) {
+ if (toolNotFoundErrorHandler != null) {
+ return toolNotFoundErrorHandler.apply(name, e);
+ }
return new ConfigException(MessageFormat.format(I18N.getString(
"error.tool-not-found"), name, e.getMessage()),
MessageFormat.format(I18N.getString(
@@ -98,8 +127,10 @@
return null;
}
- private final String name;
+ private final Path toolPath;
private List<String> args;
private String minimalVersion;
private Function<Stream<String>, String> versionParser;
+ private BiFunction<String, IOException, ConfigException> toolNotFoundErrorHandler;
+ private BiFunction<String, String, ConfigException> toolOldVersionErrorHandler;
}
--- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/VersionExtractor.java Fri Oct 18 11:00:57 2019 -0400
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,91 +0,0 @@
-/*
- * 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.WinResources");
-
- 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("error.version-compare"),
- version, compareTo));
- }
-
- if (compareTo == null || compareTo.isEmpty()) {
- throw new RuntimeException(MessageFormat.format(
- I18N.getString("error.version-compare"),
- 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;
- }
-}
--- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinExeBundler.java Fri Oct 18 11:00:57 2019 -0400
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinExeBundler.java Fri Oct 18 14:14:37 2019 -0400
@@ -85,7 +85,7 @@
@Override
public boolean supported(boolean platformInstaller) {
- return WinMsiBundler.isSupported();
+ return msiBundler.supported(platformInstaller);
}
@Override
@@ -96,7 +96,7 @@
@Override
public boolean validate(Map<String, ? super Object> params)
throws ConfigException {
- return new WinMsiBundler().validate(params);
+ return msiBundler.validate(params);
}
public File bundle(Map<String, ? super Object> params, File outdir)
@@ -107,7 +107,7 @@
File exeImageDir = EXE_IMAGE_DIR.fetchFrom(params);
// Write msi to temporary directory.
- File msi = new WinMsiBundler().bundle(params, exeImageDir);
+ File msi = msiBundler.bundle(params, exeImageDir);
try {
return buildEXE(msi, outdir);
@@ -151,5 +151,7 @@
return I18N.getString(key);
}
+ private final WinMsiBundler msiBundler = new WinMsiBundler();
+
private static native int embedMSI(String exePath, String msiPath);
}
--- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java Fri Oct 18 11:00:57 2019 -0400
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java Fri Oct 18 14:14:37 2019 -0400
@@ -33,10 +33,12 @@
import java.text.MessageFormat;
import java.util.*;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import static jdk.jpackage.internal.OverridableResource.createResource;
+import static jdk.jpackage.internal.StandardBundlerParam.*;
import static jdk.jpackage.internal.WindowsBundlerParam.*;
@@ -52,10 +54,8 @@
* directory. The following WiX source files are generated:
* <ul>
* <li>main.wxs. Main source file with the installer description
- * <li>bundle.wxi. Source file with application and Java run-time directory tree
- * description. This source file is included from main.wxs
- * <li>icons.wxi. Source file with the list of icons used by the application.
- * This source file is included from main.wxs
+ * <li>bundle.wxf. Source file with application and Java run-time directory tree
+ * description.
* </ul>
* <p>
* main.wxs file is a copy of main.wxs resource from
@@ -86,15 +86,10 @@
* files.
* <li>JpIsSystemWide. Set to "yes" if --win-per-user-install command line
* option was not specified. Undefined otherwise
- * <li>JpWixVersion36OrNewer. Set to "yes" if WiX Toolkit v3.6 or newer is used.
- * Undefined otherwise
* </ul>
*/
public class WinMsiBundler extends AbstractBundler {
- private static final ResourceBundle I18N = ResourceBundle.getBundle(
- "jdk.jpackage.internal.resources.WinResources");
-
public static final BundlerParamInfo<WinAppBundler> APP_BUNDLER =
new WindowsBundlerParam<>(
"win.app.bundler",
@@ -102,13 +97,6 @@
params -> new WinAppBundler(),
null);
- public static final BundlerParamInfo<Boolean> CAN_USE_WIX36 =
- new WindowsBundlerParam<>(
- "win.msi.canUseWix36",
- Boolean.class,
- params -> false,
- (s, p) -> Boolean.valueOf(s));
-
public static final BundlerParamInfo<File> MSI_IMAGE_DIR =
new WindowsBundlerParam<>(
"win.msi.imageDir",
@@ -154,66 +142,6 @@
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";
-
- private static String getCandlePath() {
- 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;
- }
-
- private static String getLightPath() {
- 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;
- }
-
-
- public static final StandardBundlerParam<Boolean> 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<Boolean> 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");
@@ -237,17 +165,10 @@
@Override
public boolean supported(boolean platformInstaller) {
- return isSupported();
- }
-
- @Override
- public boolean isDefault() {
- return false;
- }
-
- public static boolean isSupported() {
try {
- validateWixTools();
+ if (wixToolset == null) {
+ wixToolset = WixTool.toolset();
+ }
return true;
} catch (ConfigException ce) {
Log.error(ce.getMessage());
@@ -260,71 +181,29 @@
return false;
}
- 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, true, ve);
- String version = ve.getVersion();
- Log.verbose(MessageFormat.format(
- I18N.getString("message.tool-version"),
- toolName, version));
- return version;
- } catch (Exception e) {
- Log.verbose(e);
- return null;
- }
- }
-
- public static void validateWixTools() throws ConfigException{
- String candleVersion = findToolVersion(getCandlePath());
- String lightVersion = findToolVersion(getLightPath());
-
- // WiX 3.0+ is required
- String minVersion = "3.0";
- if (candleVersion == null || lightVersion == null) {
- throw new ConfigException(
- I18N.getString("error.no-wix-tools"),
- I18N.getString("error.no-wix-tools.advice"));
- }
-
- if (VersionExtractor.isLessThan(candleVersion, minVersion)) {
- throw new ConfigException(
- MessageFormat.format(
- I18N.getString("message.wrong-tool-version"),
- TOOL_CANDLE, candleVersion, minVersion),
- I18N.getString("error.no-wix-tools.advice"));
- }
- if (VersionExtractor.isLessThan(lightVersion, minVersion)) {
- throw new ConfigException(
- MessageFormat.format(
- I18N.getString("message.wrong-tool-version"),
- TOOL_LIGHT, lightVersion, minVersion),
- I18N.getString("error.no-wix-tools.advice"));
- }
+ @Override
+ public boolean isDefault() {
+ return false;
}
@Override
public boolean validate(Map<String, ? super Object> params)
throws 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
+ if (wixToolset == null) {
+ wixToolset = WixTool.toolset();
+ }
- String lightVersion = findToolVersion(getLightPath());
- if (!VersionExtractor.isLessThan(lightVersion, "3.6")) {
- Log.verbose(I18N.getString("message.use-wix36-features"));
- params.put(CAN_USE_WIX36.getID(), Boolean.TRUE);
+ 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);
@@ -415,7 +294,7 @@
return true;
}
- private boolean prepareProto(Map<String, ? super Object> params)
+ private void prepareProto(Map<String, ? super Object> params)
throws PackagerException, IOException {
File appImage = StandardBundlerParam.getPredefinedAppImage(params);
File appDir = null;
@@ -445,28 +324,6 @@
destFile.setWritable(true);
ensureByMutationFileIsRTF(destFile);
}
-
- // copy file association icons
- List<Map<String, ? super Object>> fileAssociations =
- FILE_ASSOCIATIONS.fetchFrom(params);
- for (Map<String, ? super Object> fa : fileAssociations) {
- File icon = FA_ICON.fetchFrom(fa);
- 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<String, ? super Object> params, File outdir)
@@ -474,145 +331,37 @@
IOUtils.writableOutputDir(outdir.toPath());
- // validate we have valid tools before continuing
- String light = getLightPath();
- String candle = getCandlePath();
- 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");
- }
+ Path imageDir = MSI_IMAGE_DIR.fetchFrom(params).toPath();
+ try {
+ Files.createDirectories(imageDir);
- Map<String, String> wixVars = null;
+ Path postImageScript = imageDir.resolve(APP_NAME.fetchFrom(params) + "-post-image.wsf");
+ createResource(null, params)
+ .setCategory(I18N.getString("resource.post-install-script"))
+ .saveToFile(postImageScript);
- File imageDir = MSI_IMAGE_DIR.fetchFrom(params);
- try {
- imageDir.mkdirs();
+ prepareProto(params);
- prepareBasicProjectConfig(params);
- if (prepareProto(params)) {
- wixVars = prepareWiXConfig(params);
+ wixSourcesBuilder
+ .initFromParams(WIN_APP_IMAGE.fetchFrom(params).toPath(), params)
+ .createMainFragment(CONFIG_ROOT.fetchFrom(params).toPath().resolve(
+ "bundle.wxf"));
- File configScriptSrc = getConfig_Script(params);
- 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?
+ Map<String, String> wixVars = prepareMainProjectFile(params);
- // 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);
- }
- return buildMSI(params, wixVars, outdir);
+ if (Files.exists(postImageScript)) {
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.running-wsh-script"),
+ postImageScript.toAbsolutePath()));
+ Executor.of("wscript", postImageScript.toString()).executeExpectSuccess();
}
- return null;
+ return buildMSI(params, wixVars, outdir);
} catch (IOException ex) {
Log.verbose(ex);
throw new PackagerException(ex);
}
}
- // name of post-image script
- private File getConfig_Script(Map<String, ? super Object> params) {
- return new File(CONFIG_ROOT.fetchFrom(params),
- APP_NAME.fetchFrom(params) + "-post-image.wsf");
- }
-
- private void prepareBasicProjectConfig(
- Map<String, ? super Object> params) throws IOException {
-
- Path scriptPath = getConfig_Script(params).toPath();
-
- createResource(null, params)
- .setCategory(I18N.getString("resource.post-install-script"))
- .saveToFile(scriptPath);
- }
-
- private static String relativePath(File basedir, File file) {
- return file.getAbsolutePath().substring(
- basedir.getAbsolutePath().length() + 1);
- }
-
- private AppImageFile appImageFile = null;
- private String[] getLaunchers( Map<String, ? super Object> params) {
- try {
- ArrayList<String> launchers = new ArrayList<String>();
- if (appImageFile == null) {
- appImageFile = AppImageFile.load(
- WIN_APP_IMAGE.fetchFrom(params).toPath());
- }
- launchers.add(appImageFile.getLauncherName());
- launchers.addAll(appImageFile.getAddLauncherNames());
- return launchers.toArray(new String[0]);
- } catch (IOException ioe) {
- Log.verbose(ioe.getMessage());
- }
- String [] launcherNames = new String [1];
- launcherNames[0] = APP_NAME.fetchFrom(params);
- return launcherNames;
- }
-
- private void prepareIconsFile(
- Map<String, ? super Object> params) throws IOException {
-
- File imageRootDir = WIN_APP_IMAGE.fetchFrom(params);
-
- List<Map<String, ? super Object>> addLaunchers =
- ADD_LAUNCHERS.fetchFrom(params);
-
- XMLOutputFactory xmlFactory = XMLOutputFactory.newInstance();
- try (Writer w = new BufferedWriter(new FileWriter(new File(
- CONFIG_ROOT.fetchFrom(params), "icons.wxi")))) {
- XMLStreamWriter xml = xmlFactory.createXMLStreamWriter(w);
-
- xml.writeStartDocument();
- xml.writeStartElement("Include");
-
- String[] launcherNames = getLaunchers(params);
-
- File[] icons = new File[launcherNames.length];
- for (int i=0; i<launcherNames.length; i++) {
- icons[i] = new File(imageRootDir, launcherNames[i] + ".ico");
- }
-
- for (int i = 0; i < icons.length; i++) {
- if (icons[i].exists()) {
- String iconPath = icons[i].getAbsolutePath();
-
- if (MENU_HINT.fetchFrom(params)) {
- xml.writeStartElement("Icon");
- xml.writeAttribute("Id", "StartMenuIcon.exe" + i);
- xml.writeAttribute("SourceFile", iconPath);
- xml.writeEndElement();
- }
- if (SHORTCUT_HINT.fetchFrom(params)) {
- xml.writeStartElement("Icon");
- xml.writeAttribute("Id", "DesktopIcon.exe" + i);
- xml.writeAttribute("SourceFile", iconPath);
- xml.writeEndElement();
- }
- }
- }
-
- xml.writeEndElement();
- xml.writeEndDocument();
- xml.flush();
- xml.close();
- } catch (XMLStreamException ex) {
- Log.verbose(ex);
- throw new IOException(ex);
- }
- }
-
Map<String, String> prepareMainProjectFile(
Map<String, ? super Object> params) throws IOException {
Map<String, String> data = new HashMap<>();
@@ -636,10 +385,6 @@
data.put("JpAllowDowngrades", "yes");
}
- if (CAN_USE_WIX36.fetchFrom(params)) {
- data.put("JpWixVersion36OrNewer", "yes");
- }
-
data.put("JpAppName", APP_NAME.fetchFrom(params));
data.put("JpAppDescription", DESCRIPTION.fetchFrom(params));
data.put("JpAppVendor", VENDOR.fetchFrom(params));
@@ -648,8 +393,6 @@
data.put("JpConfigDir",
CONFIG_ROOT.fetchFrom(params).getAbsolutePath());
- File imageRootDir = WIN_APP_IMAGE.fetchFrom(params);
-
if (MSI_SYSTEM_WIDE.fetchFrom(params)) {
data.put("JpIsSystemWide", "yes");
}
@@ -689,367 +432,30 @@
return data;
}
- private int id;
- private int compId;
- private final static String LAUNCHER_ID = "LauncherId";
-
- private void walkFileTree(Map<String, ? super Object> params,
- File root, PrintStream out, String prefix) {
- List<File> dirs = new ArrayList<>();
- List<File> 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 + " <Component Id=\"comp" + (compId++)
- + "\" DiskId=\"1\""
- + " Guid=\"" + UUID.randomUUID().toString() + "\""
- + " Win64=\"yes\""
- + ">");
- out.println(prefix + " <CreateFolder/>");
- out.println(prefix + " <RemoveFolder Id=\"RemoveDir"
- + (id++) + "\" On=\"uninstall\" />");
-
-
- File imageRootDir = WIN_APP_IMAGE.fetchFrom(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
- boolean menuShortcut = MENU_HINT.fetchFrom(params);
- boolean desktopShortcut = SHORTCUT_HINT.fetchFrom(params);
- boolean needRegistryKey = !MSI_SYSTEM_WIDE.fetchFrom(params) ||
- menuShortcut || desktopShortcut;
-
- if (needRegistryKey) {
- // has to be under HKCU to make WiX happy
- out.println(prefix + " <RegistryKey Root=\"HKCU\" "
- + " Key=\"Software\\" + VENDOR.fetchFrom(params) + "\\"
- + APP_NAME.fetchFrom(params) + "\""
- + (CAN_USE_WIX36.fetchFrom(params) ?
- ">" : " Action=\"createAndRemoveOnUninstall\">"));
- out.println(prefix
- + " <RegistryValue Name=\"Version\" Value=\""
- + VERSION.fetchFrom(params)
- + "\" Type=\"string\" KeyPath=\"yes\"/>");
- out.println(prefix + " </RegistryKey>");
- }
-
- String[] launcherNames = getLaunchers(params);
-
- File[] launcherFiles = new File[launcherNames.length];
- for (int i=0; i<launcherNames.length; i++) {
- launcherFiles[i] =
- new File(imageRootDir, launcherNames[i] + ".exe");
- }
- Map<String, String> idToFileMap = new TreeMap<>();
- boolean launcherSet = false;
-
- for (File f : files) {
- boolean isMainLauncher =
- launcherFiles.length > 0 && f.equals(launcherFiles[0]);
-
- launcherSet = launcherSet || isMainLauncher;
-
- String thisFileId = isMainLauncher ? LAUNCHER_ID : ("FileId" + (id++));
- idToFileMap.put(f.getName(), thisFileId);
-
- out.println(prefix + " <File Id=\"" +
- thisFileId + "\""
- + " Name=\"" + f.getName() + "\" "
- + " Source=\"" + relativePath(imageRootDir, f) + "\""
- + " ProcessorArchitecture=\"x64\"" + ">");
- if (isMainLauncher && desktopShortcut) {
- out.println(prefix
- + " <Shortcut Id=\"desktopShortcut\" Directory="
- + "\"DesktopFolder\""
- + " Name=\"" + launcherNames[0]
- + "\" WorkingDirectory=\"INSTALLDIR\""
- + " Advertise=\"no\" Icon=\"DesktopIcon.exe0\""
- + " IconIndex=\"0\" />");
- }
- if (isMainLauncher && menuShortcut) {
- out.println(prefix
- + " <Shortcut Id=\"ExeShortcut\" Directory="
- + "\"ProgramMenuDir\""
- + " Name=\"" + launcherNames[0]
- + "\" Advertise=\"no\" Icon=\"StartMenuIcon.exe0\""
- + " IconIndex=\"0\" />");
- }
-
- // any additional launchers
- for (int index = 1; index < launcherNames.length; index++ ) {
-
- if (f.equals(launcherFiles[index])) {
- if (desktopShortcut) {
- out.println(prefix
- + " <Shortcut Id=\"desktopShortcut"
- + index + "\" Directory=\"DesktopFolder\""
- + " Name=\"" + launcherNames[index]
- + "\" WorkingDirectory=\"INSTALLDIR\""
- + " Advertise=\"no\" Icon=\"DesktopIcon.exe"
- + index + "\""
- + " IconIndex=\"0\" />");
- }
- if (menuShortcut) {
- out.println(prefix
- + " <Shortcut Id=\"ExeShortcut"
- + index + "\" Directory=\"ProgramMenuDir\""
- + " Name=\"" + launcherNames[index]
- + "\" Advertise=\"no\" Icon=\"StartMenuIcon.exe"
- + index + "\""
- + " IconIndex=\"0\" />");
- }
- }
- }
- out.println(prefix + " </File>");
- }
-
- if (launcherSet) {
- List<Map<String, ? super Object>> fileAssociations =
- FILE_ASSOCIATIONS.fetchFrom(params);
- Set<String> defaultedMimes = new TreeSet<>();
- for (Map<String, ? super Object> fa : fileAssociations) {
- String description = FA_DESCRIPTION.fetchFrom(fa);
- List<String> extensions = FA_EXTENSIONS.fetchFrom(fa);
- List<String> mimeTypes = FA_CONTENT_TYPE.fetchFrom(fa);
- File icon = FA_ICON.fetchFrom(fa);
-
- String mime = (mimeTypes == null ||
- mimeTypes.isEmpty()) ? null : mimeTypes.get(0);
-
- String entryName = APP_REGISTRY_NAME.fetchFrom(params) + "File";
-
- if (extensions == null) {
- Log.verbose(I18N.getString(
- "message.creating-association-with-null-extension"));
-
- out.print(prefix + " <ProgId Id='" + entryName
- + "' Description='" + description + "'");
- if (icon != null && icon.exists()) {
- out.print(" Icon='" + idToFileMap.get(icon.getName())
- + "' IconIndex='0'");
- }
- out.println(" />");
- } else {
- for (String ext : extensions) {
-
- entryName = ext.toUpperCase() + "File";
-
- out.print(prefix + " <ProgId Id='" + entryName
- + "' Description='" + description + "'");
- if (icon != null && icon.exists()) {
- out.print(" Icon='"
- + idToFileMap.get(icon.getName())
- + "' IconIndex='0'");
- }
- out.println(">");
-
- out.print(prefix + " <Extension Id='"
- + ext + "' Advertise='no'");
- if (mime == null) {
- out.println(">");
- } else {
- out.println(" ContentType='" + mime + "'>");
- if (!defaultedMimes.contains(mime)) {
- out.println(prefix
- + " <MIME ContentType='"
- + mime + "' Default='yes' />");
- defaultedMimes.add(mime);
- }
- }
- out.println(prefix
- + " <Verb Id='open' Command='Open' "
- + "TargetFile='" + LAUNCHER_ID
- + "' Argument='\"%1\"' />");
- out.println(prefix + " </Extension>");
- out.println(prefix + " </ProgId>");
- }
- }
- }
- }
-
- out.println(prefix + " </Component>");
-
- for (File d : dirs) {
- out.println(prefix + " <Directory Id=\"dirid" + (id++)
- + "\" Name=\"" + d.getName() + "\">");
- walkFileTree(params, d, out, prefix + " ");
- out.println(prefix + " </Directory>");
- }
- }
-
- void prepareContentList(Map<String, ? super Object> params)
- throws FileNotFoundException {
- File f = new File(
- CONFIG_ROOT.fetchFrom(params), MSI_PROJECT_CONTENT_FILE);
-
- try (PrintStream out = new PrintStream(f)) {
-
- // opening
- out.println("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
- out.println("<Include>");
-
- out.println(" <Directory Id=\"TARGETDIR\" Name=\"SourceDir\">");
- if (MSI_SYSTEM_WIDE.fetchFrom(params)) {
- // install to programfiles
- out.println(" <Directory Id=\"ProgramFiles64Folder\" "
- + "Name=\"PFiles\">");
- } else {
- // install to user folder
- out.println(
- " <Directory Name=\"AppData\" Id=\"LocalAppDataFolder\">");
- }
-
- // reset counters
- compId = 0;
- id = 0;
-
- // 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(" <Directory Id=\"SUBDIR" + i + "\" Name=\""
- + installDirs[i] + "\">");
- if (!MSI_SYSTEM_WIDE.fetchFrom(params)) {
- out.println(" <Component Id=\"comp" + (compId++)
- + "\" DiskId=\"1\""
- + " Guid=\"" + UUID.randomUUID().toString() + "\""
- + " Win64=\"yes\""
- + ">");
- out.println("<CreateFolder/>");
- // has to be under HKCU to make WiX happy
- out.println(" <RegistryKey Root=\"HKCU\" "
- + " Key=\"Software\\" + VENDOR.fetchFrom(params) + "\\"
- + APP_NAME.fetchFrom(params) + "\""
- + (CAN_USE_WIX36.fetchFrom(params) ?
- ">" : " Action=\"createAndRemoveOnUninstall\">"));
- out.println(" <RegistryValue Name=\"Version\" Value=\""
- + VERSION.fetchFrom(params)
- + "\" Type=\"string\" KeyPath=\"yes\"/>");
- out.println(" </RegistryKey>");
- out.println(" <RemoveFolder Id=\"RemoveDir"
- + (id++) + "\" Directory=\"SUBDIR" + i
- + "\" On=\"uninstall\" />");
- out.println("</Component>");
- }
- }
-
- out.println(" <Directory Id=\"APPLICATIONFOLDER\" Name=\""
- + installDirs[installDirs.length - 1] + "\">");
-
- // dynamic part
- walkFileTree(params, WIN_APP_IMAGE.fetchFrom(params), out, " ");
-
- // closing
- for (int i = 0; i < installDirs.length; i++) {
- out.println(" </Directory>");
- }
- out.println(" </Directory>");
-
- // for shortcuts
- if (SHORTCUT_HINT.fetchFrom(params)) {
- out.println(" <Directory Id=\"DesktopFolder\" />");
- }
- if (MENU_HINT.fetchFrom(params)) {
- out.println(" <Directory Id=\"ProgramMenuFolder\">");
- out.println(" <Directory Id=\"ProgramMenuDir\" Name=\""
- + MENU_GROUP.fetchFrom(params) + "\">");
- out.println(" <Component Id=\"comp" + (compId++) + "\""
- + " Guid=\"" + UUID.randomUUID().toString() + "\""
- + " Win64=\"yes\""
- + ">");
- out.println(" <RemoveFolder Id=\"ProgramMenuDir\" "
- + "On=\"uninstall\" />");
- // 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 are appealing.
- // Leave it for now
- out.println(
- " <RegistryValue Root=\"HKCU\" Key=\"Software\\"
- + VENDOR.fetchFrom(params) + "\\"
- + APP_NAME.fetchFrom(params)
- + "\" Type=\"string\" Value=\"\" />");
- out.println(" </Component>");
- out.println(" </Directory>");
- out.println(" </Directory>");
- }
-
- out.println(" </Directory>");
-
- out.println(" <Feature Id=\"DefaultFeature\" "
- + "Title=\"Main Feature\" Level=\"1\">");
- for (int j = 0; j < compId; j++) {
- out.println(" <ComponentRef Id=\"comp" + j + "\" />");
- }
- // component is defined in the main.wsx
- out.println(
- " <ComponentRef Id=\"CleanupMainApplicationFolder\" />");
- out.println(" </Feature>");
- out.println("</Include>");
-
- }
- }
private File getConfig_ProjectFile(Map<String, ? super Object> params) {
- return new File(CONFIG_ROOT.fetchFrom(params),
- APP_NAME.fetchFrom(params) + ".wxs");
+ return new File(CONFIG_ROOT.fetchFrom(params), "main.wxs");
}
- private Map<String, String> prepareWiXConfig(
- Map<String, ? super Object> params) throws IOException {
- prepareContentList(params);
- prepareIconsFile(params);
- return prepareMainProjectFile(params);
- }
-
- private final static String MSI_PROJECT_CONTENT_FILE = "bundle.wxi";
-
private File buildMSI(Map<String, ? super Object> params,
Map<String, String> wixVars, 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();
-
- List<String> commandLine = new ArrayList<>(Arrays.asList(
- getCandlePath(),
- "-nologo",
- getConfig_ProjectFile(params).getAbsolutePath(),
- "-ext", "WixUtilExtension",
- "-out", candleOut.getAbsolutePath()));
- for(Map.Entry<String, String> wixVar: wixVars.entrySet()) {
- String v = "-d" + wixVar.getKey() + "=" + wixVar.getValue();
- commandLine.add(v);
- }
- ProcessBuilder pb = new ProcessBuilder(commandLine);
- pb = pb.directory(WIN_APP_IMAGE.fetchFrom(params));
- IOUtils.exec(pb);
+ WixPipeline wixPipeline = new WixPipeline()
+ .setToolset(wixToolset.entrySet().stream().collect(
+ Collectors.toMap(
+ entry -> entry.getKey(),
+ entry -> entry.getValue().path)))
+ .setWixObjDir(TEMP_ROOT.fetchFrom(params).toPath().resolve("wixobj"))
+ .setWorkDir(WIN_APP_IMAGE.fetchFrom(params).toPath())
+ .addSource(CONFIG_ROOT.fetchFrom(params).toPath().resolve("main.wxs"), wixVars)
+ .addSource(CONFIG_ROOT.fetchFrom(params).toPath().resolve("bundle.wxf"), null);
Log.verbose(MessageFormat.format(I18N.getString(
"message.generating-msi"), msiOut.getAbsolutePath()));
@@ -1057,44 +463,25 @@
boolean enableLicenseUI = (LICENSE_FILE.fetchFrom(params) != null);
boolean enableInstalldirUI = INSTALLDIR_CHOOSER.fetchFrom(params);
- commandLine = new ArrayList<>();
+ List<String> lightArgs = new ArrayList<>();
- commandLine.add(getLightPath());
-
- commandLine.add("-nologo");
- commandLine.add("-spdb");
if (!MSI_SYSTEM_WIDE.fetchFrom(params)) {
- commandLine.add("-sice:ICE91");
+ wixPipeline.addLightOptions("-sice:ICE91");
}
- commandLine.add(candleOut.getAbsolutePath());
- commandLine.add("-ext");
- commandLine.add("WixUtilExtension");
if (enableLicenseUI || enableInstalldirUI) {
- commandLine.add("-ext");
- commandLine.add("WixUIExtension");
+ wixPipeline.addLightOptions("-ext", "WixUIExtension");
}
- commandLine.add("-loc");
- commandLine.add(new File(CONFIG_ROOT.fetchFrom(params), I18N.getString(
- "resource.wxl-file-name")).getAbsolutePath());
+ wixPipeline.addLightOptions("-loc",
+ CONFIG_ROOT.fetchFrom(params).toPath().resolve(I18N.getString(
+ "resource.wxl-file-name")).toAbsolutePath().toString());
// Only needed if we using CA dll, so Wix can find it
if (enableInstalldirUI) {
- commandLine.add("-b");
- commandLine.add(CONFIG_ROOT.fetchFrom(params).getAbsolutePath());
+ wixPipeline.addLightOptions("-b", 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);
-
- candleOut.delete();
- IOUtils.deleteRecursive(tmpDir);
+ wixPipeline.buildMsi(msiOut.toPath().toAbsolutePath());
return msiOut;
}
@@ -1170,4 +557,7 @@
}
+ private Map<WixTool, WixTool.ToolInfo> wixToolset;
+ private WixSourcesBuilder wixSourcesBuilder = new WixSourcesBuilder();
+
}
--- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsBundlerParam.java Fri Oct 18 11:00:57 2019 -0400
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsBundlerParam.java Fri Oct 18 14:14:37 2019 -0400
@@ -60,18 +60,6 @@
},
(s, p) -> s);
- static final BundlerParamInfo<String> APP_REGISTRY_NAME =
- new StandardBundlerParam<> (
- "win.registryName",
- 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<String> MENU_GROUP =
new StandardBundlerParam<>(
Arguments.CLIOptions.WIN_MENU_GROUP.getId(),
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixPipeline.java Fri Oct 18 14:14:37 2019 -0400
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jpackage.internal;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.function.UnaryOperator;
+import java.util.stream.Stream;
+
+/**
+ * WiX pipeline. Compiles and links WiX sources.
+ */
+public class WixPipeline {
+ WixPipeline() {
+ sources = new ArrayList<>();
+ lightOptions = new ArrayList<>();
+ }
+
+ WixPipeline setToolset(Map<WixTool, Path> v) {
+ toolset = v;
+ return this;
+ }
+
+ WixPipeline setWixVariables(Map<String, String> v) {
+ wixVariables = v;
+ return this;
+ }
+
+ WixPipeline setWixObjDir(Path v) {
+ wixObjDir = v;
+ return this;
+ }
+
+ WixPipeline setWorkDir(Path v) {
+ workDir = v;
+ return this;
+ }
+
+ WixPipeline addSource(Path source, Map<String, String> wixVariables) {
+ WixSource entry = new WixSource();
+ entry.source = source;
+ entry.variables = wixVariables;
+ sources.add(entry);
+ return this;
+ }
+
+ WixPipeline addLightOptions(String ... v) {
+ lightOptions.addAll(List.of(v));
+ return this;
+ }
+
+ void buildMsi(Path msi) throws IOException {
+ List<Path> wixObjs = new ArrayList<>();
+ for (var source : sources) {
+ wixObjs.add(compile(source));
+ }
+
+ List<String> lightCmdline = new ArrayList<>(List.of(
+ toolset.get(WixTool.Light).toString(),
+ "-nologo",
+ "-spdb",
+ "-ext", "WixUtilExtension",
+ "-out", msi.toString()
+ ));
+
+ lightCmdline.addAll(lightOptions);
+ wixObjs.stream().map(Path::toString).forEach(lightCmdline::add);
+
+ Files.createDirectories(msi.getParent());
+ execute(lightCmdline);
+ }
+
+ private Path compile(WixSource wixSource) throws IOException {
+ UnaryOperator<Path> adjustPath = path -> {
+ return workDir != null ? path.toAbsolutePath() : path;
+ };
+
+ Path wixObj = adjustPath.apply(wixObjDir).resolve(IOUtils.replaceSuffix(
+ wixSource.source.getFileName(), ".wixobj"));
+
+ List<String> cmdline = new ArrayList<>(List.of(
+ toolset.get(WixTool.Candle).toString(),
+ "-nologo",
+ adjustPath.apply(wixSource.source).toString(),
+ "-ext", "WixUtilExtension",
+ "-arch", "x64",
+ "-out", wixObj.toAbsolutePath().toString()
+ ));
+
+ Map<String, String> appliedVaribales = new HashMap<>();
+ Stream.of(wixVariables, wixSource.variables)
+ .filter(Objects::nonNull)
+ .forEachOrdered(appliedVaribales::putAll);
+
+ appliedVaribales.entrySet().stream().map(wixVar -> String.format("-d%s=%s",
+ wixVar.getKey(), wixVar.getValue())).forEachOrdered(
+ cmdline::add);
+
+ execute(cmdline);
+
+ return wixObj;
+ }
+
+ private void execute(List<String> cmdline) throws IOException {
+ Executor.of(new ProcessBuilder(cmdline).directory(
+ workDir != null ? workDir.toFile() : null)).executeExpectSuccess();
+ }
+
+ private final static class WixSource {
+ Path source;
+ Map<String, String> variables;
+ }
+
+ private Map<WixTool, Path> toolset;
+ private Map<String, String> wixVariables;
+ private List<String> lightOptions;
+ private Path wixObjDir;
+ private Path workDir;
+ private List<WixSource> sources;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourcesBuilder.java Fri Oct 18 14:14:37 2019 -0400
@@ -0,0 +1,839 @@
+/*
+ * 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.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import jdk.jpackage.internal.IOUtils.XmlConsumer;
+import static jdk.jpackage.internal.StandardBundlerParam.*;
+import static jdk.jpackage.internal.WinMsiBundler.*;
+import static jdk.jpackage.internal.WindowsBundlerParam.MENU_GROUP;
+import static jdk.jpackage.internal.WindowsBundlerParam.WINDOWS_INSTALL_DIR;
+
+/**
+ * Creates application WiX source files.
+ */
+class WixSourcesBuilder {
+
+ WixSourcesBuilder setWixVersion(DottedVersion v) {
+ wixVersion = v;
+ return this;
+ }
+
+ WixSourcesBuilder initFromParams(Path appImageRoot,
+ Map<String, ? super Object> params) {
+ Supplier<ApplicationLayout> appImageSupplier = () -> {
+ if (StandardBundlerParam.isRuntimeInstaller(params)) {
+ return ApplicationLayout.javaRuntime();
+ } else {
+ return ApplicationLayout.platformAppImage();
+ }
+ };
+
+ systemWide = MSI_SYSTEM_WIDE.fetchFrom(params);
+
+ registryKeyPath = Path.of("Software",
+ VENDOR.fetchFrom(params),
+ APP_NAME.fetchFrom(params),
+ VERSION.fetchFrom(params)).toString();
+
+ installDir = (systemWide ? PROGRAM_FILES : LOCAL_PROGRAM_FILES).resolve(
+ WINDOWS_INSTALL_DIR.fetchFrom(params));
+
+ do {
+ ApplicationLayout layout = appImageSupplier.get();
+ // Don't want AppImageFile.FILENAME in installed application.
+ // Register it with app image at a role without a match in installed
+ // app layout to exclude it from layout transformation.
+ layout.pathGroup().setPath(new Object(), Path.of(AppImageFile.FILENAME));
+
+ // Want absolute paths to source files in generated WiX sources.
+ // This is to handle scenario if sources would be processed from
+ // differnt current directory.
+ appImage = layout.resolveAt(appImageRoot.toAbsolutePath().normalize());
+ } while (false);
+
+ installedAppImage = appImageSupplier.get().resolveAt(INSTALLDIR);
+
+ shortcutFolders = new HashSet<>();
+ if (SHORTCUT_HINT.fetchFrom(params)) {
+ shortcutFolders.add(ShortcutsFolder.Desktop);
+ }
+ if (MENU_HINT.fetchFrom(params)) {
+ shortcutFolders.add(ShortcutsFolder.ProgramMenu);
+ }
+
+ if (StandardBundlerParam.isRuntimeInstaller(params)) {
+ launcherPaths = Collections.emptyList();
+ } else {
+ launcherPaths = AppImageFile.getLauncherNames(appImageRoot, params).stream()
+ .map(name -> installedAppImage.launchersDirectory().resolve(name))
+ .map(WixSourcesBuilder::addExeSuffixToPath)
+ .collect(Collectors.toList());
+ }
+
+ programMenuFolderName = MENU_GROUP.fetchFrom(params);
+
+ initFileAssociations(params);
+
+ return this;
+ }
+
+ void createMainFragment(Path file) throws IOException {
+ removeFolderItems = new HashMap<>();
+ defaultedMimes = new HashSet<>();
+ IOUtils.createXml(file, xml -> {
+ xml.writeStartElement("Wix");
+ xml.writeDefaultNamespace("http://schemas.microsoft.com/wix/2006/wi");
+ xml.writeNamespace("util",
+ "http://schemas.microsoft.com/wix/UtilExtension");
+
+ xml.writeStartElement("Fragment");
+
+ addFaComponentGroup(xml);
+
+ addShortcutComponentGroup(xml);
+
+ addFilesComponentGroup(xml);
+
+ xml.writeEndElement(); // <Fragment>
+
+ addIconsFragment(xml);
+
+ xml.writeEndElement(); // <Wix>
+ });
+ }
+
+ void logWixFeatures() {
+ if (wixVersion.compareTo("3.6") >= 0) {
+ Log.verbose(I18N.getString("message.use-wix36-features"));
+ }
+ }
+
+ private void normalizeFileAssociation(FileAssociation fa) {
+ fa.launcherPath = addExeSuffixToPath(
+ installedAppImage.launchersDirectory().resolve(fa.launcherPath));
+
+ if (fa.iconPath != null && !fa.iconPath.toFile().exists()) {
+ fa.iconPath = null;
+ }
+
+ // Filter out empty extensions.
+ fa.extensions = fa.extensions.stream().filter(Predicate.not(
+ String::isEmpty)).collect(Collectors.toList());
+ }
+
+ private static Path addExeSuffixToPath(Path path) {
+ return IOUtils.addSuffix(path, ".exe");
+ }
+
+ private Path getInstalledFaIcoPath(FileAssociation fa) {
+ String fname = String.format("fa_%s.ico", String.join("_", fa.extensions));
+ return installedAppImage.destktopIntegrationDirectory().resolve(fname);
+ }
+
+ private void initFileAssociations(Map<String, ? super Object> params) {
+ associations = FileAssociation.fetchFrom(params).stream()
+ .peek(this::normalizeFileAssociation)
+ // Filter out file associations without extensions.
+ .filter(fa -> !fa.extensions.isEmpty())
+ .collect(Collectors.toList());
+
+ associations.stream().filter(fa -> fa.iconPath != null).forEach(fa -> {
+ // Need to add fa icon in the image.
+ Object key = new Object();
+ appImage.pathGroup().setPath(key, fa.iconPath);
+ installedAppImage.pathGroup().setPath(key, getInstalledFaIcoPath(fa));
+ });
+ }
+
+ private static UUID createNameUUID(String str) {
+ return UUID.nameUUIDFromBytes(str.getBytes(StandardCharsets.UTF_8));
+ }
+
+ private static UUID createNameUUID(Path path, String role) {
+ if (path.isAbsolute() || !ROOT_DIRS.contains(path.getName(0))) {
+ throw throwInvalidPathException(path);
+ }
+ // Paths are case insensitive on Windows
+ String keyPath = path.toString().toLowerCase();
+ if (role != null) {
+ keyPath = role + "@" + keyPath;
+ }
+ return createNameUUID(keyPath);
+ }
+
+ /**
+ * Value for Id attribute of various WiX elements.
+ */
+ enum Id {
+ File,
+ Folder("dir"),
+ Shortcut,
+ ProgId,
+ Icon,
+ CreateFolder("mkdir"),
+ RemoveFolder("rm");
+
+ Id() {
+ this.prefix = name().toLowerCase();
+ }
+
+ Id(String prefix) {
+ this.prefix = prefix;
+ }
+
+ String of(Path path) {
+ if (this == Folder && KNOWN_DIRS.contains(path)) {
+ return path.getFileName().toString();
+ }
+
+ String result = of(path, prefix, name());
+
+ if (this == Icon) {
+ // Icon id constructed from UUID value is too long and triggers
+ // CNDL1000 warning, so use Java hash code instead.
+ result = String.format("%s%d", prefix, result.hashCode()).replace(
+ "-", "_");
+ }
+
+ return result;
+ }
+
+ private static String of(Path path, String prefix, String role) {
+ Objects.requireNonNull(role);
+ Objects.requireNonNull(prefix);
+ return String.format("%s%s", prefix,
+ createNameUUID(path, role).toString().replace("-", ""));
+ }
+
+ static String of(Path path, String prefix) {
+ return of(path, prefix, prefix);
+ }
+
+ private final String prefix;
+ }
+
+ enum Component {
+ File(cfg().file()),
+ Shortcut(cfg().file().withRegistryKeyPath()),
+ ProgId(cfg().file().withRegistryKeyPath()),
+ CreateFolder(cfg().withRegistryKeyPath()),
+ RemoveFolder(cfg().withRegistryKeyPath());
+
+ Component() {
+ this.cfg = cfg();
+ this.id = Id.valueOf(name());
+ }
+
+ Component(Config cfg) {
+ this.cfg = cfg;
+ this.id = Id.valueOf(name());
+ }
+
+ UUID guidOf(Path path) {
+ return createNameUUID(path, name());
+ }
+
+ String idOf(Path path) {
+ return id.of(path);
+ }
+
+ boolean isRegistryKeyPath() {
+ return cfg.withRegistryKeyPath;
+ }
+
+ boolean isFile() {
+ return cfg.isFile;
+ }
+
+ private static final class Config {
+ Config withRegistryKeyPath() {
+ withRegistryKeyPath = true;
+ return this;
+ }
+
+ Config file() {
+ isFile = true;
+ return this;
+ }
+
+ private boolean isFile;
+ private boolean withRegistryKeyPath;
+ }
+
+ private static Config cfg() {
+ return new Config();
+ }
+
+ private final Config cfg;
+ private final Id id;
+ };
+
+ private static void addComponentGroup(XMLStreamWriter xml, String id,
+ List<String> componentIds) throws XMLStreamException, IOException {
+ xml.writeStartElement("ComponentGroup");
+ xml.writeAttribute("Id", id);
+ componentIds = componentIds.stream().filter(Objects::nonNull).collect(
+ Collectors.toList());
+ for (var componentId : componentIds) {
+ xml.writeStartElement("ComponentRef");
+ xml.writeAttribute("Id", componentId);
+ xml.writeEndElement();
+ }
+ xml.writeEndElement();
+ }
+
+ private String addComponent(XMLStreamWriter xml, Path path,
+ Component role, XmlConsumer xmlConsumer) throws XMLStreamException,
+ IOException {
+
+ final Path directoryRefPath;
+ if (role.isFile()) {
+ directoryRefPath = path.getParent();
+ } else {
+ directoryRefPath = path;
+ }
+
+ xml.writeStartElement("DirectoryRef");
+ xml.writeAttribute("Id", Id.Folder.of(directoryRefPath));
+
+ final String componentId = "c" + role.idOf(path);
+ xml.writeStartElement("Component");
+ xml.writeAttribute("Id", componentId);
+ xml.writeAttribute("Guid", String.format("{%s}", role.guidOf(path)));
+
+ boolean isRegistryKeyPath = !systemWide || role.isRegistryKeyPath();
+ if (isRegistryKeyPath) {
+ addRegistryKeyPath(xml, directoryRefPath);
+ if ((role.isFile() || (role == Component.CreateFolder
+ && !systemWide)) && !SYSTEM_DIRS.contains(directoryRefPath)) {
+ xml.writeStartElement("RemoveFolder");
+ int counter = Optional.ofNullable(removeFolderItems.get(
+ directoryRefPath)).orElse(Integer.valueOf(0)).intValue() + 1;
+ removeFolderItems.put(directoryRefPath, counter);
+ xml.writeAttribute("Id", String.format("%s_%d", Id.RemoveFolder.of(
+ directoryRefPath), counter));
+ xml.writeAttribute("On", "uninstall");
+ xml.writeEndElement();
+ }
+ }
+
+ xml.writeStartElement(role.name());
+ if (role != Component.CreateFolder) {
+ xml.writeAttribute("Id", role.idOf(path));
+ }
+
+ if (!isRegistryKeyPath) {
+ xml.writeAttribute("KeyPath", "yes");
+ }
+
+ xmlConsumer.accept(xml);
+ xml.writeEndElement();
+
+ xml.writeEndElement(); // <Component>
+ xml.writeEndElement(); // <DirectoryRef>
+
+ return componentId;
+ }
+
+ private void addFaComponentGroup(XMLStreamWriter xml)
+ throws XMLStreamException, IOException {
+
+ List<String> componentIds = new ArrayList<>();
+ for (var fa : associations) {
+ componentIds.addAll(addFaComponents(xml, fa));
+ }
+ addComponentGroup(xml, "FileAssociations", componentIds);
+ }
+
+ private void addShortcutComponentGroup(XMLStreamWriter xml) throws
+ XMLStreamException, IOException {
+ List<String> componentIds = new ArrayList<>();
+ Set<ShortcutsFolder> defineShortcutFolders = new HashSet<>();
+ for (var launcherPath : launcherPaths) {
+ for (var folder : shortcutFolders) {
+ String componentId = addShortcutComponent(xml, launcherPath,
+ folder);
+ if (componentId != null) {
+ defineShortcutFolders.add(folder);
+ componentIds.add(componentId);
+ }
+ }
+ }
+
+ for (var folder : defineShortcutFolders) {
+ Path path = folder.getPath(this);
+ componentIds.addAll(addRootBranch(xml, path));
+
+ if (!KNOWN_DIRS.contains(path)) {
+ componentIds.add(addDirectoryCleaner(xml, path));
+ }
+ }
+
+ addComponentGroup(xml, "Shortcuts", componentIds);
+ }
+
+ private String addShortcutComponent(XMLStreamWriter xml, Path launcherPath,
+ ShortcutsFolder folder) throws XMLStreamException, IOException {
+ Objects.requireNonNull(folder);
+
+ if (!INSTALLDIR.equals(launcherPath.getName(0))) {
+ throw throwInvalidPathException(launcherPath);
+ }
+
+ String launcherBasename = IOUtils.replaceSuffix(
+ launcherPath.getFileName(), "").toString();
+
+ Path shortcutPath = folder.getPath(this).resolve(launcherBasename);
+ return addComponent(xml, shortcutPath, Component.Shortcut, unused -> {
+ final Path icoFile = IOUtils.addSuffix(
+ installedAppImage.destktopIntegrationDirectory().resolve(
+ launcherBasename), ".ico");
+
+ xml.writeAttribute("Name", launcherBasename);
+ xml.writeAttribute("WorkingDirectory", INSTALLDIR.toString());
+ xml.writeAttribute("Advertise", "no");
+ xml.writeAttribute("IconIndex", "0");
+ xml.writeAttribute("Target", String.format("[#%s]",
+ Component.File.idOf(launcherPath)));
+ xml.writeAttribute("Icon", Id.Icon.of(icoFile));
+ });
+ }
+
+ private List<String> addFaComponents(XMLStreamWriter xml,
+ FileAssociation fa) throws XMLStreamException, IOException {
+ List<String> components = new ArrayList<>();
+ for (var extension: fa.extensions) {
+ Path path = INSTALLDIR.resolve(String.format("%s_%s", extension,
+ fa.launcherPath.getFileName()));
+ components.add(addComponent(xml, path, Component.ProgId, unused -> {
+ xml.writeAttribute("Description", fa.description);
+
+ if (fa.iconPath != null) {
+ xml.writeAttribute("Icon", Id.File.of(getInstalledFaIcoPath(
+ fa)));
+ xml.writeAttribute("IconIndex", "0");
+ }
+
+ xml.writeStartElement("Extension");
+ xml.writeAttribute("Id", extension);
+ xml.writeAttribute("Advertise", "no");
+
+ var mimeIt = fa.mimeTypes.iterator();
+ if (mimeIt.hasNext()) {
+ String mime = mimeIt.next();
+ xml.writeAttribute("ContentType", mime);
+
+ if (!defaultedMimes.contains(mime)) {
+ xml.writeStartElement("MIME");
+ xml.writeAttribute("ContentType", mime);
+ xml.writeAttribute("Default", "yes");
+ xml.writeEndElement();
+ defaultedMimes.add(mime);
+ }
+ }
+
+ xml.writeStartElement("Verb");
+ xml.writeAttribute("Id", "open");
+ xml.writeAttribute("Command", "Open");
+ xml.writeAttribute("Argument", "%1");
+ xml.writeAttribute("TargetFile", Id.File.of(fa.launcherPath));
+ xml.writeEndElement(); // <Verb>
+
+ xml.writeEndElement(); // <Extension>
+ }));
+ }
+
+ return components;
+ }
+
+ private List<String> addRootBranch(XMLStreamWriter xml, Path path)
+ throws XMLStreamException, IOException {
+ if (!ROOT_DIRS.contains(path.getName(0))) {
+ throw throwInvalidPathException(path);
+ }
+
+ Function<Path, String> createDirectoryName = dir -> null;
+
+ boolean sysDir = true;
+ int levels = 1;
+ var dirIt = path.iterator();
+ xml.writeStartElement("DirectoryRef");
+ xml.writeAttribute("Id", dirIt.next().toString());
+
+ path = path.getName(0);
+ while (dirIt.hasNext()) {
+ levels++;
+ Path name = dirIt.next();
+ path = path.resolve(name);
+
+ if (sysDir && !SYSTEM_DIRS.contains(path)) {
+ sysDir = false;
+ createDirectoryName = dir -> dir.getFileName().toString();
+ }
+
+ final String directoryId;
+ if (!sysDir && path.equals(installDir)) {
+ directoryId = INSTALLDIR.toString();
+ } else {
+ directoryId = Id.Folder.of(path);
+ }
+ xml.writeStartElement("Directory");
+ xml.writeAttribute("Id", directoryId);
+
+ String directoryName = createDirectoryName.apply(path);
+ if (directoryName != null) {
+ xml.writeAttribute("Name", directoryName);
+ }
+ }
+
+ while (0 != levels--) {
+ xml.writeEndElement();
+ }
+
+ List<String> componentIds = new ArrayList<>();
+ while (!SYSTEM_DIRS.contains(path = path.getParent())) {
+ componentIds.add(addRemoveDirectoryComponent(xml, path));
+ }
+
+ return componentIds;
+ }
+
+ private String addRemoveDirectoryComponent(XMLStreamWriter xml, Path path)
+ throws XMLStreamException, IOException {
+ return addComponent(xml, path, Component.RemoveFolder,
+ unused -> xml.writeAttribute("On", "uninstall"));
+ }
+
+ private List<String> addDirectoryHierarchy(XMLStreamWriter xml)
+ throws XMLStreamException, IOException {
+
+ Set<Path> allDirs = new HashSet<>();
+ Set<Path> emptyDirs = new HashSet<>();
+ appImage.transform(installedAppImage, new PathGroup.TransformHandler() {
+ @Override
+ public void copyFile(Path src, Path dst) throws IOException {
+ Path dir = dst.getParent();
+ createDirectory(dir);
+ emptyDirs.remove(dir);
+ }
+
+ @Override
+ public void createDirectory(final Path dir) throws IOException {
+ if (!allDirs.contains(dir)) {
+ emptyDirs.add(dir);
+ }
+
+ Path it = dir;
+ while (it != null && allDirs.add(it)) {
+ it = it.getParent();
+ }
+
+ it = dir;
+ while ((it = it.getParent()) != null && emptyDirs.remove(it));
+ }
+ });
+
+ List<String> componentIds = new ArrayList<>();
+ for (var dir : emptyDirs) {
+ componentIds.add(addComponent(xml, dir, Component.CreateFolder,
+ unused -> {}));
+ }
+
+ if (!systemWide) {
+ // Per-user install requires <RemoveFolder> component in every
+ // directory.
+ for (var dir : allDirs.stream()
+ .filter(Predicate.not(emptyDirs::contains))
+ .filter(Predicate.not(removeFolderItems::containsKey))
+ .collect(Collectors.toList())) {
+ componentIds.add(addRemoveDirectoryComponent(xml, dir));
+ }
+ }
+
+ allDirs.remove(INSTALLDIR);
+ for (var dir : allDirs) {
+ xml.writeStartElement("DirectoryRef");
+ xml.writeAttribute("Id", Id.Folder.of(dir.getParent()));
+ xml.writeStartElement("Directory");
+ xml.writeAttribute("Id", Id.Folder.of(dir));
+ xml.writeAttribute("Name", dir.getFileName().toString());
+ xml.writeEndElement();
+ xml.writeEndElement();
+ }
+
+ componentIds.addAll(addRootBranch(xml, installDir));
+
+ return componentIds;
+ }
+
+ private void addFilesComponentGroup(XMLStreamWriter xml)
+ throws XMLStreamException, IOException {
+
+ List<Map.Entry<Path, Path>> files = new ArrayList<>();
+ appImage.transform(installedAppImage, new PathGroup.TransformHandler() {
+ @Override
+ public void copyFile(Path src, Path dst) throws IOException {
+ files.add(Map.entry(src, dst));
+ }
+
+ @Override
+ public void createDirectory(final Path dir) throws IOException {
+ }
+ });
+
+ List<String> componentIds = new ArrayList<>();
+ for (var file : files) {
+ Path src = file.getKey();
+ Path dst = file.getValue();
+
+ componentIds.add(addComponent(xml, dst, Component.File, unused -> {
+ xml.writeAttribute("Source", src.normalize().toString());
+ Path name = dst.getFileName();
+ if (!name.equals(src.getFileName())) {
+ xml.writeAttribute("Name", name.toString());
+ }
+ }));
+ }
+
+ componentIds.addAll(addDirectoryHierarchy(xml));
+
+ componentIds.add(addDirectoryCleaner(xml, INSTALLDIR));
+
+ addComponentGroup(xml, "Files", componentIds);
+ }
+
+ private void addIconsFragment(XMLStreamWriter xml) throws
+ XMLStreamException, IOException {
+
+ PathGroup srcPathGroup = appImage.pathGroup();
+ PathGroup dstPathGroup = installedAppImage.pathGroup();
+
+ // Build list of copy operations for all .ico files in application image
+ List<Map.Entry<Path, Path>> icoFiles = new ArrayList<>();
+ srcPathGroup.transform(dstPathGroup, new PathGroup.TransformHandler() {
+ @Override
+ public void copyFile(Path src, Path dst) throws IOException {
+ if (src.getFileName().toString().endsWith(".ico")) {
+ icoFiles.add(Map.entry(src, dst));
+ }
+ }
+
+ @Override
+ public void createDirectory(Path dst) throws IOException {
+ }
+ });
+
+ xml.writeStartElement("Fragment");
+ for (var icoFile : icoFiles) {
+ xml.writeStartElement("Icon");
+ xml.writeAttribute("Id", Id.Icon.of(icoFile.getValue()));
+ xml.writeAttribute("SourceFile", icoFile.getKey().toString());
+ xml.writeEndElement();
+ }
+ xml.writeEndElement();
+ }
+
+ private void addRegistryKeyPath(XMLStreamWriter xml, Path path) throws
+ XMLStreamException, IOException {
+ addRegistryKeyPath(xml, path, () -> "ProductCode", () -> "[ProductCode]");
+ }
+
+ private void addRegistryKeyPath(XMLStreamWriter xml, Path path,
+ Supplier<String> nameAttr, Supplier<String> valueAttr) throws
+ XMLStreamException, IOException {
+
+ String regRoot = USER_PROFILE_DIRS.stream().anyMatch(path::startsWith)
+ || !systemWide ? "HKCU" : "HKLM";
+
+ xml.writeStartElement("RegistryKey");
+ xml.writeAttribute("Root", regRoot);
+ xml.writeAttribute("Key", registryKeyPath);
+ if (wixVersion.compareTo("3.6") < 0) {
+ xml.writeAttribute("Action", "createAndRemoveOnUninstall");
+ }
+ xml.writeStartElement("RegistryValue");
+ xml.writeAttribute("Type", "string");
+ xml.writeAttribute("KeyPath", "yes");
+ xml.writeAttribute("Name", nameAttr.get());
+ xml.writeAttribute("Value", valueAttr.get());
+ xml.writeEndElement(); // <RegistryValue>
+ xml.writeEndElement(); // <RegistryKey>
+ }
+
+ private String addDirectoryCleaner(XMLStreamWriter xml, Path path) throws
+ XMLStreamException, IOException {
+ if (wixVersion.compareTo("3.6") < 0) {
+ return null;
+ }
+
+ // rm -rf
+ final String baseId = Id.of(path, "rm_rf");
+ final String propertyId = baseId.toUpperCase();
+ final String componentId = ("c" + baseId);
+
+ xml.writeStartElement("Property");
+ xml.writeAttribute("Id", propertyId);
+ xml.writeStartElement("RegistrySearch");
+ xml.writeAttribute("Id", Id.of(path, "regsearch"));
+ xml.writeAttribute("Root", systemWide ? "HKLM" : "HKCU");
+ xml.writeAttribute("Key", registryKeyPath);
+ xml.writeAttribute("Type", "raw");
+ xml.writeAttribute("Name", propertyId);
+ xml.writeEndElement(); // <RegistrySearch>
+ xml.writeEndElement(); // <Property>
+
+ xml.writeStartElement("DirectoryRef");
+ xml.writeAttribute("Id", INSTALLDIR.toString());
+ xml.writeStartElement("Component");
+ xml.writeAttribute("Id", componentId);
+ xml.writeAttribute("Guid", "*");
+
+ addRegistryKeyPath(xml, INSTALLDIR, () -> propertyId, () -> {
+ // The following code converts a path to value to be saved in registry.
+ // E.g.:
+ // INSTALLDIR -> [INSTALLDIR]
+ // TERGETDIR/ProgramFiles64Folder/foo/bar -> [ProgramFiles64Folder]foo/bar
+ final Path rootDir = KNOWN_DIRS.stream()
+ .sorted(Comparator.comparing(Path::getNameCount).reversed())
+ .filter(path::startsWith)
+ .findFirst().get();
+ StringBuilder sb = new StringBuilder();
+ sb.append(String.format("[%s]", rootDir.getFileName().toString()));
+ sb.append(rootDir.relativize(path).toString());
+ return sb.toString();
+ });
+
+ xml.writeStartElement(
+ "http://schemas.microsoft.com/wix/UtilExtension",
+ "RemoveFolderEx");
+ xml.writeAttribute("On", "uninstall");
+ xml.writeAttribute("Property", propertyId);
+ xml.writeEndElement(); // <RemoveFolderEx>
+ xml.writeEndElement(); // <Component>
+ xml.writeEndElement(); // <DirectoryRef>
+
+ return componentId;
+ }
+
+ private static IllegalArgumentException throwInvalidPathException(Path v) {
+ throw new IllegalArgumentException(String.format("Invalid path [%s]", v));
+ }
+
+ enum ShortcutsFolder {
+ ProgramMenu(PROGRAM_MENU_PATH),
+ Desktop(DESKTOP_PATH);
+
+ private ShortcutsFolder(Path root) {
+ this.root = root;
+ }
+
+ Path getPath(WixSourcesBuilder outer) {
+ if (this == ProgramMenu) {
+ return root.resolve(outer.programMenuFolderName);
+ }
+ return root;
+ }
+
+ private final Path root;
+ }
+
+ private DottedVersion wixVersion;
+
+ private boolean systemWide;
+
+ private String registryKeyPath;
+
+ private Path installDir;
+
+ private String programMenuFolderName;
+
+ private List<FileAssociation> associations;
+
+ private Set<ShortcutsFolder> shortcutFolders;
+
+ private List<Path> launcherPaths;
+
+ private ApplicationLayout appImage;
+ private ApplicationLayout installedAppImage;
+
+ private Map<Path, Integer> removeFolderItems;
+ private Set<String> defaultedMimes;
+
+ private final static Path TARGETDIR = Path.of("TARGETDIR");
+
+ private final static Path INSTALLDIR = Path.of("INSTALLDIR");
+
+ private final static Set<Path> ROOT_DIRS = Set.of(INSTALLDIR, TARGETDIR);
+
+ private final static Path PROGRAM_MENU_PATH = TARGETDIR.resolve("ProgramMenuFolder");
+
+ private final static Path DESKTOP_PATH = TARGETDIR.resolve("DesktopFolder");
+
+ private final static Path PROGRAM_FILES = TARGETDIR.resolve("ProgramFiles64Folder");
+
+ private final static Path LOCAL_PROGRAM_FILES = TARGETDIR.resolve("LocalAppDataFolder");
+
+ private final static Set<Path> SYSTEM_DIRS = Set.of(TARGETDIR,
+ PROGRAM_MENU_PATH, DESKTOP_PATH, PROGRAM_FILES, LOCAL_PROGRAM_FILES);
+
+ private final static Set<Path> KNOWN_DIRS = Stream.of(Set.of(INSTALLDIR),
+ SYSTEM_DIRS).flatMap(Set::stream).collect(
+ Collectors.toUnmodifiableSet());
+
+ private final static Set<Path> USER_PROFILE_DIRS = Set.of(LOCAL_PROGRAM_FILES,
+ PROGRAM_MENU_PATH, DESKTOP_PATH);
+
+ private static final StandardBundlerParam<Boolean> 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)
+ );
+
+ private static final StandardBundlerParam<Boolean> 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)
+ );
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java Fri Oct 18 14:14:37 2019 -0400
@@ -0,0 +1,146 @@
+/*
+ * 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.IOException;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.text.MessageFormat;
+import java.util.*;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * WiX tool.
+ */
+public enum WixTool {
+ Candle, Light;
+
+ static final class ToolInfo {
+ ToolInfo(Path path, String version) {
+ this.path = path;
+ this.version = new DottedVersion(version);
+ }
+
+ final Path path;
+ final DottedVersion version;
+ }
+
+ static Map<WixTool, ToolInfo> toolset() throws ConfigException {
+ Map<WixTool, ToolInfo> toolset = new HashMap<>();
+ for (var tool : values()) {
+ toolset.put(tool, tool.find());
+ }
+ return toolset;
+ }
+
+ ToolInfo find() throws ConfigException {
+ final Path toolFileName = IOUtils.addSuffix(
+ Path.of(name().toLowerCase()), ".exe");
+
+ String[] version = new String[1];
+ ConfigException reason = createToolValidator(toolFileName, version).get();
+ if (version[0] != null) {
+ if (reason == null) {
+ // Found in PATH.
+ return new ToolInfo(toolFileName, version[0]);
+ }
+
+ // Found in PATH, but something went wrong.
+ throw reason;
+ }
+
+ for (var dir : findWixInstallDirs()) {
+ Path path = dir.resolve(toolFileName);
+ if (path.toFile().exists()) {
+ reason = createToolValidator(path, version).get();
+ if (reason != null) {
+ throw reason;
+ }
+ return new ToolInfo(path, version[0]);
+ }
+ }
+
+ throw reason;
+ }
+
+ private static Supplier<ConfigException> createToolValidator(Path toolPath,
+ String[] versionCtnr) {
+ return new ToolValidator(toolPath)
+ .setCommandLine("/?")
+ .setMinimalVersion(MINIMAL_VERSION)
+ .setToolNotFoundErrorHandler(
+ (name, ex) -> new ConfigException(
+ I18N.getString("error.no-wix-tools"),
+ I18N.getString("error.no-wix-tools.advice")))
+ .setToolOldVersionErrorHandler(
+ (name, version) -> new ConfigException(
+ MessageFormat.format(I18N.getString(
+ "message.wrong-tool-version"), name,
+ version, MINIMAL_VERSION),
+ I18N.getString("error.no-wix-tools.advice")))
+ .setVersionParser(output -> {
+ versionCtnr[0] = "";
+ String firstLineOfOutput = output.findFirst().orElse("");
+ int separatorIdx = firstLineOfOutput.lastIndexOf(' ');
+ if (separatorIdx == -1) {
+ return null;
+ }
+ versionCtnr[0] = firstLineOfOutput.substring(separatorIdx + 1);
+ return versionCtnr[0];
+ })::validate;
+ }
+
+ private final static String MINIMAL_VERSION = "3.0";
+
+ private final static Path PROGRAM_FILES = Path.of("C:\\Program Files");
+ private final static Path PROGRAM_FILES_X86 = Path.of("C:\\Program Files (x86)");
+
+ private static List<Path> findWixInstallDirs() {
+ PathMatcher wixInstallDirMatcher = FileSystems.getDefault().getPathMatcher(
+ "glob:WiX Toolset v*");
+
+ // Returns list of WiX install directories ordered by WiX version number.
+ // Newer versions go first.
+ return Stream.of(PROGRAM_FILES, PROGRAM_FILES_X86).map(path -> {
+ List<Path> result;
+ try (var paths = Files.walk(path, 1)) {
+ result = paths.collect(Collectors.toList());
+ } catch (IOException ex) {
+ Log.verbose(ex);
+ result = Collections.emptyList();
+ }
+ return result;
+ }).flatMap(List::stream)
+ .filter(path -> wixInstallDirMatcher.matches(path.getFileName()))
+ .sorted(Comparator.comparing(Path::getFileName).reversed())
+ .map(path -> path.resolve("bin"))
+ .collect(Collectors.toList());
+ }
+}
--- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/main.wxs Fri Oct 18 11:00:57 2019 -0400
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/main.wxs Fri Oct 18 14:14:37 2019 -0400
@@ -1,15 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
-
+
<?ifdef JpIsSystemWide ?>
<?define JpInstallScope="perMachine"?>
- <?define JpRegistryRoot="HKLM"?>
<?else?>
<?define JpInstallScope="perUser"?>
- <?define JpRegistryRoot="HKCU"?>
<?endif?>
-
+
<Product Id="$(var.JpProductCode)" Name="$(var.JpAppName)"
Language="1033" Version="$(var.JpAppVersion)"
Manufacturer="$(var.JpAppVendor)"
@@ -23,90 +21,67 @@
<?ifdef JpAllowDowngrades ?>
<MajorUpgrade AllowDowngrades="yes"/>
<?endif?>
-
- <!-- We use RemoveFolderEx to ensure application folder is fully
- removed on uninstall. Including files created outside of MSI
- after application had been installed (e.g. on AU or user state).
+
+ <!-- Standard required root -->
+ <Directory Id="TARGETDIR" Name="SourceDir"/>
+
+ <Feature Id="DefaultFeature" Title="Main Feature" Level="1">
+ <ComponentGroupRef Id="Shortcuts"/>
+ <ComponentGroupRef Id="Files"/>
+ <ComponentGroupRef Id="FileAssociations"/>
+ </Feature>
- However, RemoveFolderEx is only available in WiX 3.6,
- we will comment it out if we running older WiX.
+ <?ifdef JpInstallDirChooser ?>
+ <Binary Id="JpCaDll" SourceFile="wixhelper.dll"/>
+ <CustomAction Id="JpCheckInstallDir" BinaryKey="JpCaDll" DllEntry="CheckInstallDir" />
+ <?endif?>
+
+ <UI>
+ <?ifdef JpInstallDirChooser ?>
+ <Dialog Id="JpInvalidInstallDir" Width="300" Height="85" Title="[ProductName] Setup" NoMinimize="yes">
+ <Control Id="JpInvalidInstallDirYes" Type="PushButton" X="100" Y="55" Width="50" Height="15" Default="no" Cancel="no" Text="Yes">
+ <Publish Event="NewDialog" Value="VerifyReadyDlg">1</Publish>
+ </Control>
+ <Control Id="JpInvalidInstallDirNo" Type="PushButton" X="150" Y="55" Width="50" Height="15" Default="yes" Cancel="yes" Text="No">
+ <Publish Event="NewDialog" Value="InstallDirDlg">1</Publish>
+ </Control>
+ <Control Id="Text" Type="Text" X="25" Y="15" Width="250" Height="30" TabSkip="no">
+ <Text>!(loc.message.install.dir.exist)</Text>
+ </Control>
+ </Dialog>
- RemoveFolderEx requires that we "remember" the path for uninstall.
- Read the path value and set the APPLICATIONFOLDER property with the value.
- -->
- <Property Id="APPLICATIONFOLDER">
- <RegistrySearch Key="SOFTWARE\$(var.JpAppVendor)\$(var.JpAppName)"
- Root="$(var.JpRegistryRoot)" Type="raw" Win64="yes"
- Id="APPLICATIONFOLDER_REGSEARCH" Name="Path" />
- </Property>
- <DirectoryRef Id="APPLICATIONFOLDER">
- <Component Id="CleanupMainApplicationFolder" Guid="*" Win64="yes">
- <RegistryValue Root="$(var.JpRegistryRoot)"
- Key="SOFTWARE\$(var.JpAppVendor)\$(var.JpAppName)"
- Name="Path" Type="string" Value="[APPLICATIONFOLDER]"
- KeyPath="yes" />
- <!-- We need to use APPLICATIONFOLDER variable here or RemoveFolderEx
- will not remove on "install". But only if WiX 3.6 is used. -->
- <?ifdef JpWixVersion36OrNewer ?>
- <util:RemoveFolderEx On="uninstall" Property="APPLICATIONFOLDER" />
- <?endif?>
- </Component>
- </DirectoryRef>
-
- <?include $(var.JpConfigDir)/bundle.wxi ?>
-
- <?ifdef JpInstallDirChooser ?>
- <Binary Id="JpCaDll" SourceFile="wixhelper.dll"/>
- <CustomAction Id="JpCheckInstallDir" BinaryKey="JpCaDll" DllEntry="CheckInstallDir" />
- <?endif?>
-
- <UI>
- <?ifdef JpInstallDirChooser ?>
- <Dialog Id="JpInvalidInstallDir" Width="300" Height="85" Title="[ProductName] Setup" NoMinimize="yes">
- <Control Id="JpInvalidInstallDirYes" Type="PushButton" X="100" Y="55" Width="50" Height="15" Default="no" Cancel="no" Text="Yes">
- <Publish Event="NewDialog" Value="VerifyReadyDlg">1</Publish>
- </Control>
- <Control Id="JpInvalidInstallDirNo" Type="PushButton" X="150" Y="55" Width="50" Height="15" Default="yes" Cancel="yes" Text="No">
- <Publish Event="NewDialog" Value="InstallDirDlg">1</Publish>
- </Control>
- <Control Id="Text" Type="Text" X="25" Y="15" Width="250" Height="30" TabSkip="no">
- <Text>!(loc.message.install.dir.exist)</Text>
- </Control>
- </Dialog>
-
- <!--
- Run WixUI_InstallDir dialog in the default install directory.
- -->
- <Property Id="WIXUI_INSTALLDIR" Value="APPLICATIONFOLDER"/>
- <UIRef Id="WixUI_InstallDir" />
+ <!--
+ Run WixUI_InstallDir dialog in the default install directory.
+ -->
+ <Property Id="WIXUI_INSTALLDIR" Value="APPLICATIONFOLDER"/>
+ <UIRef Id="WixUI_InstallDir" />
+
+ <Publish Dialog="InstallDirDlg" Control="Next" Event="DoAction" Value="JpCheckInstallDir" Order="3">1</Publish>
+ <Publish Dialog="InstallDirDlg" Control="Next" Event="NewDialog" Value="JpInvalidInstallDir" Order="5">INSTALLDIR_VALID="0"</Publish>
+ <Publish Dialog="InstallDirDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg" Order="5">INSTALLDIR_VALID="1"</Publish>
- <Publish Dialog="InstallDirDlg" Control="Next" Event="DoAction" Value="JpCheckInstallDir" Order="3">1</Publish>
- <Publish Dialog="InstallDirDlg" Control="Next" Event="NewDialog" Value="JpInvalidInstallDir" Order="5">INSTALLDIR_VALID="0"</Publish>
- <Publish Dialog="InstallDirDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg" Order="5">INSTALLDIR_VALID="1"</Publish>
-
- <?ifndef JpLicenseRtf ?>
- <!--
- No license file provided.
- Override the dialog sequence in built-in dialog set "WixUI_InstallDir"
- to exclude license dialog.
- -->
- <Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="InstallDirDlg" Order="2">1</Publish>
- <Publish Dialog="InstallDirDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg" Order="2">1</Publish>
- <?endif?>
+ <?ifndef JpLicenseRtf ?>
+ <!--
+ No license file provided.
+ Override the dialog sequence in built-in dialog set "WixUI_InstallDir"
+ to exclude license dialog.
+ -->
+ <Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="InstallDirDlg" Order="2">1</Publish>
+ <Publish Dialog="InstallDirDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg" Order="2">1</Publish>
+ <?endif?>
+
+ <?else?>
- <?else?>
-
- <?ifdef JpLicenseRtf ?>
- <UIRef Id="WixUI_Minimal" />
- <?endif?>
-
- <?endif?>
- </UI>
-
- <?ifdef JpLicenseRtf ?>
- <WixVariable Id="WixUILicenseRtf" Value="$(var.JpLicenseRtf)"/>
- <?endif?>
-
- <?include $(var.JpConfigDir)/icons.wxi ?>
- </Product>
+ <?ifdef JpLicenseRtf ?>
+ <UIRef Id="WixUI_Minimal" />
+ <?endif?>
+
+ <?endif?>
+ </UI>
+
+ <?ifdef JpLicenseRtf ?>
+ <WixVariable Id="WixUILicenseRtf" Value="$(var.JpLicenseRtf)"/>
+ <?endif?>
+
+ </Product>
</Wix>
Binary file test/jdk/tools/jpackage/helpers/jdk/jpackage/test/.Executor.java.swp has changed
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/FileAssociations.java Fri Oct 18 11:00:57 2019 -0400
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/FileAssociations.java Fri Oct 18 14:14:37 2019 -0400
@@ -23,10 +23,11 @@
package jdk.jpackage.test;
import java.nio.file.Path;
+import java.util.HashMap;
import java.util.Map;
-public class FileAssociations {
+final public class FileAssociations {
public FileAssociations(String faSuffixName) {
suffixName = faSuffixName;
setFilename("fa");
@@ -34,22 +35,36 @@
}
public void createFile() {
- TKit.createPropertiesFile(file,
- Map.entry("extension", suffixName),
- Map.entry("mime-type", getMime()),
- Map.entry("description", description));
+ Map<String, String> entries = new HashMap<>(Map.of(
+ "extension", suffixName,
+ "mime-type", getMime(),
+ "description", description
+ ));
+ if (icon != null) {
+ if (TKit.isWindows()) {
+ entries.put("icon", icon.toString().replace("\\", "/"));
+ } else {
+ entries.put("icon", icon.toString());
+ }
+ }
+ TKit.createPropertiesFile(file, entries);
}
- final public FileAssociations setFilename(String v) {
+ public FileAssociations setFilename(String v) {
file = TKit.workDir().resolve(v + ".properties");
return this;
}
- final public FileAssociations setDescription(String v) {
+ public FileAssociations setDescription(String v) {
description = v;
return this;
}
+ public FileAssociations setIcon(Path v) {
+ icon = v;
+ return this;
+ }
+
public Path getPropertiesFile() {
return file;
}
@@ -65,4 +80,5 @@
private Path file;
final private String suffixName;
private String description;
+ private Path icon;
}
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/HelloApp.java Fri Oct 18 11:00:57 2019 -0400
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/HelloApp.java Fri Oct 18 14:14:37 2019 -0400
@@ -126,7 +126,7 @@
if (moduleName == null && CLASS_NAME.equals(qualifiedClassName)) {
// Use Hello.java as is.
cmd.addAction((self) -> {
- File jarFile = self.inputDir().resolve(jarFileName).toFile();
+ Path jarFile = self.inputDir().resolve(jarFileName);
createJarBuilder().setOutputJar(jarFile).addSourceFile(
HELLO_JAVA).create();
});
@@ -144,8 +144,7 @@
}
TKit.withTempDirectory("src",
- workDir -> prepareSources(workDir).setOutputJar(
- jarFile.toFile()).create());
+ workDir -> prepareSources(workDir).setOutputJar(jarFile).create());
});
}
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JarBuilder.java Fri Oct 18 11:00:57 2019 -0400
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JarBuilder.java Fri Oct 18 14:14:37 2019 -0400
@@ -40,7 +40,7 @@
sourceFiles = new ArrayList<>();
}
- public JarBuilder setOutputJar(File v) {
+ public JarBuilder setOutputJar(Path v) {
outputJar = v;
return this;
}
@@ -69,9 +69,16 @@
.addPathArguments(sourceFiles)
.execute().assertExitCodeIsZero();
}
- Path tmpJar = workDir.resolve("foo.jar");
- Executor jarExe = new Executor();
- jarExe.setToolProvider(JavaTool.JAR).addArguments("-c", "-f", tmpJar.toString());
+
+ Files.createDirectories(outputJar.getParent());
+ if (Files.exists(outputJar)) {
+ TKit.trace(String.format("Delete [%s] existing jar file", outputJar));
+ Files.deleteIfExists(outputJar);
+ }
+
+ Executor jarExe = new Executor()
+ .setToolProvider(JavaTool.JAR)
+ .addArguments("-c", "-f", outputJar.toString());
if (moduleVersion != null) {
jarExe.addArguments(String.format("--module-version=%s",
moduleVersion));
@@ -81,12 +88,10 @@
}
jarExe.addArguments("-C", workDir.toString(), ".");
jarExe.execute().assertExitCodeIsZero();
- outputJar.getParentFile().mkdirs();
- Files.copy(tmpJar, outputJar.toPath(), REPLACE_EXISTING);
});
}
private List<Path> sourceFiles;
- private File outputJar;
+ private Path outputJar;
private String mainClass;
private String moduleVersion;
}
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JavaAppDesc.java Fri Oct 18 11:00:57 2019 -0400
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JavaAppDesc.java Fri Oct 18 14:14:37 2019 -0400
@@ -22,7 +22,8 @@
*/
package jdk.jpackage.test;
-import java.util.Objects;
+import java.io.File;
+import java.nio.file.Path;
public final class JavaAppDesc {
@@ -58,6 +59,11 @@
return qualifiedClassName;
}
+ public Path classFilePath() {
+ return Path.of(qualifiedClassName.replace(".", File.separator)
+ + ".class");
+ }
+
public String moduleName() {
return moduleName;
}
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java Fri Oct 18 11:00:57 2019 -0400
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java Fri Oct 18 14:14:37 2019 -0400
@@ -33,6 +33,7 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jdk.jpackage.test.Functional.ThrowingConsumer;
+import jdk.jpackage.internal.AppImageFile;
import static jdk.jpackage.test.PackageType.*;
/**
@@ -83,7 +84,8 @@
return this;
}
- private PackageTest addInitializer(ThrowingConsumer<JPackageCommand> v, String id) {
+ private PackageTest addInitializer(ThrowingConsumer<JPackageCommand> v,
+ String id) {
if (id != null) {
if (namedInitializers.contains(id)) {
return this;
@@ -149,8 +151,10 @@
return this;
}
- static void withTestFileAssociationsFile(FileAssociations fa, ThrowingConsumer<Path> consumer) {
- final String testFileDefaultName = String.join(".", "test", fa.getSuffix());
+ static void withTestFileAssociationsFile(FileAssociations fa,
+ ThrowingConsumer<Path> consumer) {
+ final String testFileDefaultName = String.join(".", "test",
+ fa.getSuffix());
TKit.withTempFile(testFileDefaultName, fa.getSuffix(), testFile -> {
if (TKit.isLinux()) {
LinuxHelper.initFileAssociationsTestFile(testFile);
@@ -347,6 +351,9 @@
}
}
+ TKit.assertPathExists(cmd.appInstallationDirectory().resolve(
+ AppImageFile.FILENAME), false);
+
installVerifiers.stream().forEach(v -> v.accept(cmd));
}
@@ -413,7 +420,8 @@
bundleOutputDir = new File(val).getAbsoluteFile();
if (!bundleOutputDir.isDirectory()) {
- throw new IllegalArgumentException(String.format("Invalid value of %s sytem property: [%s]. Should be existing directory",
+ throw new IllegalArgumentException(String.format(
+ "Invalid value of %s sytem property: [%s]. Should be existing directory",
TKit.getConfigPropertyName(propertyName),
bundleOutputDir));
}
@@ -425,7 +433,9 @@
String action = Optional.ofNullable(TKit.getConfigProperty(propertyName)).orElse(
Action.CREATE.toString()).toLowerCase();
DEFAULT_ACTION = Stream.of(Action.values()).filter(
- a -> a.toString().equals(action)).findFirst().orElseThrow(() -> new IllegalArgumentException(String.format("Unrecognized value of %s property: [%s]",
+ a -> a.toString().equals(action)).findFirst().orElseThrow(
+ () -> new IllegalArgumentException(String.format(
+ "Unrecognized value of %s property: [%s]",
TKit.getConfigPropertyName(propertyName), action)));
}
}
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java Fri Oct 18 11:00:57 2019 -0400
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java Fri Oct 18 14:14:37 2019 -0400
@@ -45,6 +45,8 @@
final public class TKit {
+ private static final String OS = System.getProperty("os.name").toLowerCase();
+
public static final Path TEST_SRC_ROOT = Functional.identity(() -> {
Path root = Path.of(System.getProperty("test.src"));
@@ -58,6 +60,22 @@
throw new RuntimeException("Failed to locate apps directory");
}).get();
+ public final static String ICON_SUFFIX = Functional.identity(() -> {
+ if (isOSX()) {
+ return ".icns";
+ }
+
+ if (isLinux()) {
+ return ".png";
+ }
+
+ if (isWindows()) {
+ return ".ico";
+ }
+
+ throw throwUnknownPlatformError();
+ }).get();
+
public static void run(String args[], ThrowingRunnable testBody) {
if (currentTest != null) {
throw new IllegalStateException(
@@ -816,6 +834,4 @@
VERBOSE_TEST_SETUP = isNonOf.test(Set.of("init", "i"));
}
}
-
- private static final String OS = System.getProperty("os.name").toLowerCase();
}
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TestInstance.java Fri Oct 18 11:00:57 2019 -0400
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TestInstance.java Fri Oct 18 14:14:37 2019 -0400
@@ -153,6 +153,7 @@
this.afterActions = Collections.emptyList();
this.testDesc = TestDesc.createBuilder().get();
this.dryRun = false;
+ this.workDir = createWorkDirName(testDesc);
}
TestInstance(MethodCall testBody, List<ThrowingConsumer> beforeActions,
@@ -164,6 +165,7 @@
this.afterActions = afterActions;
this.testDesc = testBody.createDescription();
this.dryRun = dryRun;
+ this.workDir = createWorkDirName(testDesc);
}
void notifyAssert() {
@@ -205,28 +207,7 @@
}
Path workDir() {
- Path result = Path.of(".");
- List<String> components = new ArrayList<>();
-
- String testFunctionName = functionName();
- if (testFunctionName != null) {
- components.add(testFunctionName);
- }
-
- if (isPrametrized()) {
- components.add(String.format("%08x", fullName().hashCode()));
- }
-
- if (!components.isEmpty()) {
- result = result.resolve(String.join(".", components));
- }
-
- return result;
- }
-
- boolean isPrametrized() {
- return Stream.of(testDesc.functionArgs, testDesc.instanceArgs).anyMatch(
- Objects::nonNull);
+ return workDir;
}
@Override
@@ -239,7 +220,7 @@
testInstance));
try {
if (!dryRun) {
- Files.createDirectories(workDir());
+ Files.createDirectories(workDir);
testBody.accept(testInstance);
}
} finally {
@@ -255,7 +236,7 @@
}
if (!KEEP_WORK_DIR.contains(status)) {
- TKit.deleteDirectoryRecursive(workDir());
+ TKit.deleteDirectoryRecursive(workDir);
}
TKit.log(String.format("%s %s; checks=%d", status, fullName,
@@ -274,6 +255,42 @@
return null;
}
+ private static boolean isCalledByJavatest() {
+ StackTraceElement st[] = Thread.currentThread().getStackTrace();
+ for (StackTraceElement ste : st) {
+ if (ste.getClassName().startsWith("com.sun.javatest.")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static Path createWorkDirName(TestDesc testDesc) {
+ Path result = Path.of(".");
+ if (!isCalledByJavatest()) {
+ result = result.resolve(testDesc.clazz.getSimpleName());
+ }
+
+ List<String> components = new ArrayList<>();
+
+ final String testFunctionName = testDesc.functionName;
+ if (testFunctionName != null) {
+ components.add(testFunctionName);
+ }
+
+ final boolean isPrametrized = Stream.of(testDesc.functionArgs,
+ testDesc.instanceArgs).anyMatch(Objects::nonNull);
+ if (isPrametrized) {
+ components.add(String.format("%08x", testDesc.testFullName().hashCode()));
+ }
+
+ if (!components.isEmpty()) {
+ result = result.resolve(String.join(".", components));
+ }
+
+ return result;
+ }
+
private enum Status {
Passed("[ OK ]"),
Failed("[ FAILED ]"),
@@ -300,6 +317,7 @@
private final List<ThrowingConsumer> beforeActions;
private final List<ThrowingConsumer> afterActions;
private final boolean dryRun;
+ private final Path workDir;
private final static Set<Status> KEEP_WORK_DIR = Functional.identity(
() -> {
--- a/test/jdk/tools/jpackage/junit/jdk/jpackage/internal/AppImageFileTest.java Fri Oct 18 11:00:57 2019 -0400
+++ b/test/jdk/tools/jpackage/junit/jdk/jpackage/internal/AppImageFileTest.java Fri Oct 18 14:14:37 2019 -0400
@@ -154,7 +154,7 @@
private AppImageFile createFromXml(String... xmlData) throws IOException {
Path directory = tempFolder.getRoot().toPath();
- Path path = directory.resolve(AppImageFile.XML_FILENAME);
+ Path path = directory.resolve(AppImageFile.FILENAME);
path.toFile().mkdirs();
Files.delete(path);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/junit/jdk/jpackage/internal/CompareDottedVersionTest.java Fri Oct 18 14:14:37 2019 -0400
@@ -0,0 +1,84 @@
+/*
+ * 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.
+ */
+package jdk.jpackage.internal;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import junit.framework.*;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import static org.junit.Assert.*;
+
+@RunWith(Parameterized.class)
+public class CompareDottedVersionTest {
+
+ public CompareDottedVersionTest(String version1, String version2, int result) {
+ this.version1 = version1;
+ this.version2 = version2;
+ this.expectedResult = result;
+ }
+
+ @Parameters
+ public static List<Object[]> data() {
+ return List.of(new Object[][] {
+ { "00.0.0", "0", 0 },
+ { "0.035", "0.0035", 0 },
+ { "1", "1", 0 },
+ { "2", "2.0", 0 },
+ { "2.00", "2.0", 0 },
+ { "1.2.3.4", "1.2.3.4.5", -1 },
+ { "34", "33", 1 },
+ { "34.0.78", "34.1.78", -1 }
+ });
+ }
+
+ @Test
+ public void testIt() {
+ int actualResult = compare(version1, version2);
+ assertEquals(expectedResult, actualResult);
+
+ int actualNegateResult = compare(version2, version1);
+ assertEquals(actualResult, -1 * actualNegateResult);
+ }
+
+ private static int compare(String x, String y) {
+ int result = new DottedVersion(x).compareTo(y);
+
+ if (result < 0) {
+ return -1;
+ }
+
+ if (result > 0) {
+ return 1;
+ }
+
+ return 0;
+ }
+
+ private final String version1;
+ private final String version2;
+ private final int expectedResult;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/junit/jdk/jpackage/internal/DottedVersionTest.java Fri Oct 18 14:14:37 2019 -0400
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+package jdk.jpackage.internal;
+
+import java.util.stream.Stream;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import static org.junit.Assert.*;
+
+public class DottedVersionTest {
+
+ @Rule
+ public ExpectedException exceptionRule = ExpectedException.none();
+
+ @Test
+ public void testValid() {
+ Stream.of(
+ "1.0",
+ "1",
+ "2.234.045",
+ "2.234.0",
+ "0",
+ "0.1"
+ ).forEach(value -> {
+ DottedVersion version = new DottedVersion(value);
+ assertEquals(version.toString(), value);
+ });
+ }
+
+ @Test
+ public void testNull() {
+ exceptionRule.expect(NullPointerException.class);
+ new DottedVersion(null);
+ }
+
+ @Test
+ public void testEmpty() {
+ exceptionRule.expect(IllegalArgumentException.class);
+ exceptionRule.expectMessage("Version may not be empty string");
+ new DottedVersion("");
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/junit/jdk/jpackage/internal/InvalidDottedVersionTest.java Fri Oct 18 14:14:37 2019 -0400
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+package jdk.jpackage.internal;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InvalidDottedVersionTest {
+
+ public InvalidDottedVersionTest(String version) {
+ this.version = version;
+ }
+
+ @Parameters
+ public static List<Object[]> data() {
+ return Stream.of(
+ "1.-1",
+ "5.",
+ "4.2.",
+ "3..2",
+ "2.a",
+ "0a",
+ ".",
+ " ",
+ " 1",
+ "1. 2"
+ ).map(version -> new Object[] { version }).collect(Collectors.toList());
+ }
+
+ @Rule
+ public ExpectedException exceptionRule = ExpectedException.none();
+
+ @Test
+ public void testIt() {
+ exceptionRule.expect(IllegalArgumentException.class);
+ new DottedVersion(version);
+ }
+
+ private final String version;
+}
--- a/test/jdk/tools/jpackage/junit/jdk/jpackage/internal/PathGroupTest.java Fri Oct 18 11:00:57 2019 -0400
+++ b/test/jdk/tools/jpackage/junit/jdk/jpackage/internal/PathGroupTest.java Fri Oct 18 14:14:37 2019 -0400
@@ -24,19 +24,27 @@
*/
package jdk.jpackage.internal;
+import java.io.IOException;
+import java.nio.file.Files;
import java.nio.file.Path;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.function.UnaryOperator;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.*;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
public class PathGroupTest {
- public PathGroupTest() {
- }
+
+ @Rule
+ public final TemporaryFolder tempFolder = new TemporaryFolder();
@Test(expected = NullPointerException.class)
public void testNullId() {
@@ -125,7 +133,139 @@
assertEquals(aPath, pg2.roots().get(0));
}
+ @Test
+ public void testTransform() throws IOException {
+ for (var transform : TransformType.values()) {
+ testTransform(false, transform);
+ }
+ }
+
+ @Test
+ public void testTransformWithExcludes() throws IOException {
+ for (var transform : TransformType.values()) {
+ testTransform(true, transform);
+ }
+ }
+
+ enum TransformType { Copy, Move, Handler };
+
+ private void testTransform(boolean withExcludes, TransformType transform)
+ throws IOException {
+ final PathGroup pg = new PathGroup(Map.of(0, PATH_FOO, 1, PATH_BAR, 2,
+ PATH_EMPTY, 3, PATH_BAZ));
+
+ final Path srcDir = tempFolder.newFolder().toPath();
+ final Path dstDir = tempFolder.newFolder().toPath();
+
+ Files.createDirectories(srcDir.resolve(PATH_FOO).resolve("a/b/c/d"));
+ Files.createFile(srcDir.resolve(PATH_FOO).resolve("a/b/c/file1"));
+ Files.createFile(srcDir.resolve(PATH_FOO).resolve("a/b/file2"));
+ Files.createFile(srcDir.resolve(PATH_FOO).resolve("a/b/file3"));
+ Files.createFile(srcDir.resolve(PATH_BAR));
+ Files.createFile(srcDir.resolve(PATH_EMPTY).resolve("file4"));
+ Files.createDirectories(srcDir.resolve(PATH_BAZ).resolve("1/2/3"));
+
+ var dst = pg.resolveAt(dstDir);
+ var src = pg.resolveAt(srcDir);
+ if (withExcludes) {
+ // Exclude from transformation.
+ src.setPath(new Object(), srcDir.resolve(PATH_FOO).resolve("a/b/c"));
+ src.setPath(new Object(), srcDir.resolve(PATH_EMPTY).resolve("file4"));
+ }
+
+ var srcFilesBeforeTransform = walkFiles(srcDir);
+
+ if (transform == TransformType.Handler) {
+ List<Map.Entry<Path, Path>> copyFile = new ArrayList<>();
+ List<Path> createDirectory = new ArrayList<>();
+ src.transform(dst, new PathGroup.TransformHandler() {
+ @Override
+ public void copyFile(Path src, Path dst) throws IOException {
+ copyFile.add(Map.entry(src, dst));
+ }
+
+ @Override
+ public void createDirectory(Path dir) throws IOException {
+ createDirectory.add(dir);
+ }
+ });
+
+ Consumer<Path> assertFile = path -> {
+ var entry = Map.entry(srcDir.resolve(path), dstDir.resolve(path));
+ assertTrue(copyFile.contains(entry));
+ };
+
+ Consumer<Path> assertDir = path -> {
+ assertTrue(createDirectory.contains(dstDir.resolve(path)));
+ };
+
+ assertEquals(withExcludes ? 3 : 5, copyFile.size());
+ assertEquals(withExcludes ? 8 : 10, createDirectory.size());
+
+ assertFile.accept(PATH_FOO.resolve("a/b/file2"));
+ assertFile.accept(PATH_FOO.resolve("a/b/file3"));
+ assertFile.accept(PATH_BAR);
+ assertDir.accept(PATH_FOO.resolve("a/b"));
+ assertDir.accept(PATH_FOO.resolve("a"));
+ assertDir.accept(PATH_FOO);
+ assertDir.accept(PATH_BAZ);
+ assertDir.accept(PATH_BAZ.resolve("1"));
+ assertDir.accept(PATH_BAZ.resolve("1/2"));
+ assertDir.accept(PATH_BAZ.resolve("1/2/3"));
+ assertDir.accept(PATH_EMPTY);
+
+ if (!withExcludes) {
+ assertFile.accept(PATH_FOO.resolve("a/b/c/file1"));
+ assertFile.accept(PATH_EMPTY.resolve("file4"));
+ assertDir.accept(PATH_FOO.resolve("a/b/c/d"));
+ assertDir.accept(PATH_FOO.resolve("a/b/c"));
+ }
+
+ assertArrayEquals(new Path[] { Path.of("") }, walkFiles(dstDir));
+ return;
+ }
+
+ if (transform == TransformType.Copy) {
+ src.copy(dst);
+ } else if (transform == TransformType.Move) {
+ src.move(dst);
+ }
+
+ final List<Path> excludedPaths;
+ if (withExcludes) {
+ excludedPaths = List.of(
+ PATH_EMPTY.resolve("file4"),
+ PATH_FOO.resolve("a/b/c")
+ );
+ } else {
+ excludedPaths = Collections.emptyList();
+ }
+ UnaryOperator<Path[]> removeExcludes = paths -> {
+ return Stream.of(paths)
+ .filter(path -> !excludedPaths.stream().anyMatch(
+ path::startsWith))
+ .collect(Collectors.toList()).toArray(Path[]::new);
+ };
+
+ var dstFiles = walkFiles(dstDir);
+ assertArrayEquals(removeExcludes.apply(srcFilesBeforeTransform), dstFiles);
+
+ if (transform == TransformType.Copy) {
+ assertArrayEquals(dstFiles, removeExcludes.apply(walkFiles(srcDir)));
+ } else if (transform == TransformType.Move) {
+ assertFalse(Files.exists(srcDir));
+ }
+ }
+
+ private static Path[] walkFiles(Path root) throws IOException {
+ try (var files = Files.walk(root)) {
+ return files.map(root::relativize).sorted().collect(
+ Collectors.toList()).toArray(Path[]::new);
+ }
+ }
+
private final static Path PATH_FOO = Path.of("foo");
private final static Path PATH_BAR = Path.of("bar");
+ private final static Path PATH_BAZ = Path.of("baz");
private final static Path PATH_EMPTY = Path.of("");
}
--- a/test/jdk/tools/jpackage/share/FileAssociationsTest.java Fri Oct 18 11:00:57 2019 -0400
+++ b/test/jdk/tools/jpackage/share/FileAssociationsTest.java Fri Oct 18 14:14:37 2019 -0400
@@ -21,6 +21,7 @@
* questions.
*/
+import java.nio.file.Path;
import jdk.jpackage.test.TKit;
import jdk.jpackage.test.PackageTest;
import jdk.jpackage.test.FileAssociations;
@@ -42,6 +43,11 @@
* On Linux use "echo > foo.jptest1" and not "touch foo.jptest1" to create test
* file as empty files are always interpreted as plain text and will not be
* opened with the test app. This is a known bug.
+ *
+ * Icon associated with the main launcher should be associated with files with
+ * ".jptest1" suffix. Different icon should be associated with files with with
+ * ".jptest2" suffix. Icon for files with ".jptest1" suffix is platform specific
+ * and is one of 'icon.*' files in test/jdk/tools/jpackage/resources directory.
*/
/*
@@ -60,8 +66,12 @@
PackageTest packageTest = new PackageTest();
applyFileAssociations(packageTest, new FileAssociations("jptest1"));
+
+ Path icon = TKit.TEST_SRC_ROOT.resolve(Path.of("resources", "icon"
+ + TKit.ICON_SUFFIX));
+
applyFileAssociations(packageTest,
- new FileAssociations("jptest2").setFilename("fa2"));
+ new FileAssociations("jptest2").setFilename("fa2").setIcon(icon));
packageTest.run();
}
--- a/test/jdk/tools/jpackage/share/IconTest.java Fri Oct 18 11:00:57 2019 -0400
+++ b/test/jdk/tools/jpackage/share/IconTest.java Fri Oct 18 14:14:37 2019 -0400
@@ -25,6 +25,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
+import jdk.jpackage.internal.IOUtils;
import jdk.jpackage.test.TKit;
import jdk.jpackage.test.Functional;
import jdk.jpackage.test.Annotations.*;
@@ -70,8 +71,8 @@
}
private static String appIconFileName(JPackageCommand cmd) {
- return cmd.appLauncherPath().getFileName().toString().replaceAll(
- "\\.[^.]*$", "") + ICON_SUFFIX;
+ return IOUtils.replaceSuffix(cmd.appLauncherPath().getFileName(),
+ TKit.ICON_SUFFIX).toString();
}
private static void testIt(JPackageCommand cmd) throws IOException {
@@ -87,22 +88,6 @@
iconPath, GOLDEN_ICON));
}
- private final static String ICON_SUFFIX = Functional.identity(() -> {
- if (TKit.isOSX()) {
- return ".icns";
- }
-
- if (TKit.isLinux()) {
- return ".png";
- }
-
- if (TKit.isWindows()) {
- return ".ico";
- }
-
- throw TKit.throwUnknownPlatformError();
- }).get();
-
private final static Path GOLDEN_ICON = TKit.TEST_SRC_ROOT.resolve(Path.of(
- "resources", "icon" + ICON_SUFFIX));
+ "resources", "icon" + TKit.ICON_SUFFIX));
}
--- a/test/jdk/tools/jpackage/share/RuntimePackageTest.java Fri Oct 18 11:00:57 2019 -0400
+++ b/test/jdk/tools/jpackage/share/RuntimePackageTest.java Fri Oct 18 14:14:37 2019 -0400
@@ -48,7 +48,7 @@
* @comment Temporary disable for Linux and OSX until functionality implemented
* @requires (os.family != "mac")
* @modules jdk.jpackage/jdk.jpackage.internal
- * @run main/othervm/timeout=360 -Xmx512m RuntimePackageTest
+ * @run main/othervm/timeout=720 -Xmx512m RuntimePackageTest
*/
public class RuntimePackageTest {
--- a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/MainClassTest.java Fri Oct 18 11:00:57 2019 -0400
+++ b/test/jdk/tools/jpackage/share/jdk/jpackage/tests/MainClassTest.java Fri Oct 18 14:14:37 2019 -0400
@@ -53,7 +53,6 @@
* @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
* --jpt-run=jdk.jpackage.tests.MainClassTest
* --jpt-space-subst=_
- * --jpt-exclude=modular=y;_main-class=n;_jar-main-class=b;_jlink=y
*/
public final class MainClassTest {
@@ -64,7 +63,7 @@
}
Script modular(boolean v) {
- appDesc.setModuleName(v ? null : "com.other");
+ appDesc.setModuleName(v ? "com.other" : null);
return this;
}
@@ -138,9 +137,12 @@
script.appDesc.packageName(), "ThereIsNoSuchClass").filter(
Objects::nonNull).collect(Collectors.joining("."));
- cmd = JPackageCommand.helloAppImage(script.appDesc);
+ cmd = JPackageCommand
+ .helloAppImage(script.appDesc)
+ .ignoreDefaultRuntime(true);
if (!script.withJLink) {
- cmd.setFakeRuntime();
+ cmd.addArguments("--runtime-image", Path.of(System.getProperty(
+ "java.home")));
}
final String moduleName = script.appDesc.moduleName();
@@ -173,13 +175,6 @@
for (var modular : List.of(true, false)) {
for (var mainClass : Script.MainClassType.values()) {
for (var jarMainClass : Script.MainClassType.values()) {
- if (!withJLink && (jarMainClass == SetWrong || mainClass
- == SetWrong)) {
- // Without runtime can't run app to verify it will fail, so
- // there is no point in such testing.
- continue;
- }
-
Script script = new Script()
.modular(modular)
.withJLink(withJLink)
@@ -190,10 +185,10 @@
|| withMainClass.contains(mainClass)) {
} else if (modular) {
script.expectedErrorMessage(
- "A main class was not specified nor was one found in the jar");
+ "Error: Main application class is missing");
} else {
script.expectedErrorMessage(
- "Error: Main application class is missing");
+ "A main class was not specified nor was one found in the jar");
}
scripts.add(new Script[]{script});
@@ -250,13 +245,13 @@
}
}
- private void initJarWithWrongMainClass() {
+ private void initJarWithWrongMainClass() throws IOException {
// Call JPackageCommand.executePrerequisiteActions() to build app's jar.
// executePrerequisiteActions() is called by JPackageCommand instance
// only once.
cmd.executePrerequisiteActions();
- Path jarFile;
+ final Path jarFile;
if (script.appDesc.moduleName() != null) {
jarFile = Path.of(cmd.getArgumentValue("--module-path"),
script.appDesc.jarFileName());
@@ -264,37 +259,58 @@
jarFile = cmd.inputDir().resolve(cmd.getArgumentValue("--main-jar"));
}
- // Create jar file with the main class attribute in manifest set to
- // non-existing class.
+ // Create new jar file filtering out main class from the old jar file.
TKit.withTempDirectory("repack-jar", workDir -> {
- Path manifestFile = workDir.resolve("META-INF/MANIFEST.MF");
- try (var jar = new JarFile(jarFile.toFile())) {
- jar.stream()
- .filter(Predicate.not(JarEntry::isDirectory))
- .sequential().forEachOrdered(ThrowingConsumer.toConsumer(
- jarEntry -> {
- try (var in = jar.getInputStream(jarEntry)) {
- Path fileName = workDir.resolve(jarEntry.getName());
- Files.createDirectories(fileName.getParent());
- Files.copy(in, fileName);
- }
- }));
- }
+ // Extract app's class from the old jar.
+ explodeJar(jarFile, workDir,
+ jarEntry -> Path.of(jarEntry.getName()).equals(
+ script.appDesc.classFilePath()));
+
+ // Create app's jar file with different main class.
+ var badAppDesc = JavaAppDesc.parse(script.appDesc.toString()).setClassName(
+ nonExistingMainClass);
+ JPackageCommand.helloAppImage(badAppDesc).executePrerequisiteActions();
+
+ // Extract new jar but skip app's class.
+ explodeJar(jarFile, workDir,
+ jarEntry -> !Path.of(jarEntry.getName()).equals(
+ badAppDesc.classFilePath()));
+
+ // At this point we should have:
+ // 1. Manifest from the new jar referencing non-existing class
+ // as the main class.
+ // 2. Module descriptor referencing non-existing class as the main
+ // class in case of modular app.
+ // 3. App's class from the old jar. We need it to let jlink find some
+ // classes in the package declared in module descriptor
+ // in case of modular app.
Files.delete(jarFile);
-
- // Adjust manifest.
- TKit.createTextFile(manifestFile, Files.readAllLines(
- manifestFile).stream().map(line -> line.replace(
- script.appDesc.className(), nonExistingMainClass)));
-
new Executor().setToolProvider(JavaTool.JAR)
- .addArguments("-c", "-M", "-f", jarFile.toString())
+ .addArguments("-v", "-c", "-M", "-f", jarFile.toString())
.addArguments("-C", workDir.toString(), ".")
+ .dumpOutput()
.execute().assertExitCodeIsZero();
});
}
+ private static void explodeJar(Path jarFile, Path workDir,
+ Predicate<JarEntry> filter) throws IOException {
+ try (var jar = new JarFile(jarFile.toFile())) {
+ jar.stream()
+ .filter(Predicate.not(JarEntry::isDirectory))
+ .filter(filter)
+ .sequential().forEachOrdered(ThrowingConsumer.toConsumer(
+ jarEntry -> {
+ try (var in = jar.getInputStream(jarEntry)) {
+ Path fileName = workDir.resolve(jarEntry.getName());
+ Files.createDirectories(fileName.getParent());
+ Files.copy(in, fileName);
+ }
+ }));
+ }
+ }
+
private final JPackageCommand cmd;
private final Script script;
private final String nonExistingMainClass;