1 /* 2 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 package jdk.jpackage.test; 24 25 import java.io.ByteArrayInputStream; 26 import java.io.IOException; 27 import java.nio.charset.StandardCharsets; 28 import java.nio.file.Files; 29 import java.nio.file.Path; 30 import java.util.List; 31 import java.util.Set; 32 import java.util.regex.Pattern; 33 import java.util.stream.Collectors; 34 import java.util.stream.Stream; 35 import javax.xml.parsers.DocumentBuilder; 36 import javax.xml.parsers.DocumentBuilderFactory; 37 import javax.xml.parsers.ParserConfigurationException; 38 import javax.xml.xpath.XPath; 39 import javax.xml.xpath.XPathConstants; 40 import javax.xml.xpath.XPathFactory; 41 import jdk.jpackage.test.Functional.ThrowingConsumer; 42 import jdk.jpackage.test.Functional.ThrowingSupplier; 43 import jdk.jpackage.test.PackageTest.PackageHandlers; 44 import org.xml.sax.SAXException; 45 46 public class MacHelper { 47 48 public static void withExplodedDmg(JPackageCommand cmd, 49 ThrowingConsumer<Path> consumer) { 50 cmd.verifyIsOfType(PackageType.MAC_DMG); 51 52 // Explode DMG assuming this can require interaction, thus use `yes`. 53 var plist = readPList(Executor.of("sh", "-c", 54 String.join(" ", "yes", "|", "/usr/bin/hdiutil", "attach", 55 JPackageCommand.escapeAndJoin( 56 cmd.outputBundle().toString()), "-plist")) 57 .dumpOutput() 58 .executeAndGetOutput()); 59 60 final Path mountPoint = Path.of(plist.queryValue("mount-point")); 61 try { 62 Path dmgImage = mountPoint.resolve(cmd.name() + ".app"); 63 TKit.trace(String.format("Exploded [%s] in [%s] directory", 64 cmd.outputBundle(), dmgImage)); 65 ThrowingConsumer.toConsumer(consumer).accept(dmgImage); 66 } finally { 67 Executor.of("/usr/bin/hdiutil", "detach").addArgument(mountPoint).execute(); 68 } 69 } 70 71 public static PListWrapper readPListFromAppImage(Path appImage) { 72 return readPList(appImage.resolve("Contents/Info.plist")); 73 } 74 75 public static PListWrapper readPList(Path path) { 76 TKit.assertReadableFileExists(path); 77 return ThrowingSupplier.toSupplier(() -> readPList(Files.readAllLines( 78 path))).get(); 79 } 80 81 public static PListWrapper readPList(List<String> lines) { 82 return readPList(lines.stream()); 83 } 84 85 public static PListWrapper readPList(Stream<String> lines) { 86 return ThrowingSupplier.toSupplier(() -> new PListWrapper(lines 87 // Skip leading lines before xml declaration 88 .dropWhile(Pattern.compile("\\s?<\\?xml\\b.+\\?>").asPredicate().negate()) 89 .collect(Collectors.joining()))).get(); 90 } 91 92 static PackageHandlers createDmgPackageHandlers() { 93 PackageHandlers dmg = new PackageHandlers(); 94 95 dmg.installHandler = cmd -> { 96 withExplodedDmg(cmd, dmgImage -> { 97 Executor.of("sudo", "cp", "-r") 98 .addArgument(dmgImage) 99 .addArgument("/Applications") 100 .execute(); 101 }); 102 }; 103 dmg.unpackHandler = (cmd, destinationDir) -> { 104 Path[] unpackedFolder = new Path[1]; 105 withExplodedDmg(cmd, dmgImage -> { 106 Executor.of("cp", "-r") 107 .addArgument(dmgImage) 108 .addArgument(destinationDir) 109 .execute(); 110 unpackedFolder[0] = destinationDir.resolve(dmgImage.getFileName()); 111 }); 112 return unpackedFolder[0]; 113 }; 114 dmg.uninstallHandler = cmd -> { 115 cmd.verifyIsOfType(PackageType.MAC_DMG); 116 Executor.of("sudo", "rm", "-rf") 117 .addArgument(cmd.appInstallationDirectory()) 118 .execute(); 119 }; 120 121 return dmg; 122 } 123 124 static PackageHandlers createPkgPackageHandlers() { 125 PackageHandlers pkg = new PackageHandlers(); 126 127 pkg.installHandler = cmd -> { 128 cmd.verifyIsOfType(PackageType.MAC_PKG); 129 Executor.of("sudo", "/usr/sbin/installer", "-allowUntrusted", "-pkg") 130 .addArgument(cmd.outputBundle()) 131 .addArguments("-target", "/") 132 .execute(); 133 }; 134 pkg.uninstallHandler = cmd -> { 135 cmd.verifyIsOfType(PackageType.MAC_PKG); 136 Executor.of("sudo", "rm", "-rf") 137 .addArgument(cmd.appInstallationDirectory()) 138 .execute(); 139 }; 140 141 return pkg; 142 } 143 144 static String getBundleName(JPackageCommand cmd) { 145 cmd.verifyIsOfType(PackageType.MAC); 146 return String.format("%s-%s%s", getPackageName(cmd), cmd.version(), 147 cmd.packageType().getSuffix()); 148 } 149 150 static Path getInstallationDirectory(JPackageCommand cmd) { 151 cmd.verifyIsOfType(PackageType.MAC); 152 return Path.of(cmd.getArgumentValue("--install-dir", () -> "/Applications")) 153 .resolve(cmd.name() + ".app"); 154 } 155 156 private static String getPackageName(JPackageCommand cmd) { 157 return cmd.getArgumentValue("--mac-package-name", 158 () -> cmd.name()); 159 } 160 161 public static final class PListWrapper { 162 public String queryValue(String keyName) { 163 XPath xPath = XPathFactory.newInstance().newXPath(); 164 // Query for the value of <string> element preceding <key> element 165 // with value equal to `keyName` 166 String query = String.format( 167 "//string[preceding-sibling::key = \"%s\"][1]", keyName); 168 return ThrowingSupplier.toSupplier(() -> (String) xPath.evaluate( 169 query, doc, XPathConstants.STRING)).get(); 170 } 171 172 PListWrapper(String xml) throws ParserConfigurationException, 173 SAXException, IOException { 174 doc = createDocumentBuilder().parse(new ByteArrayInputStream( 175 xml.getBytes(StandardCharsets.UTF_8))); 176 } 177 178 private static DocumentBuilder createDocumentBuilder() throws 179 ParserConfigurationException { 180 DocumentBuilderFactory dbf = DocumentBuilderFactory.newDefaultInstance(); 181 dbf.setFeature( 182 "http://apache.org/xml/features/nonvalidating/load-external-dtd", 183 false); 184 return dbf.newDocumentBuilder(); 185 } 186 187 private final org.w3c.dom.Document doc; 188 } 189 190 static final Set<Path> CRITICAL_RUNTIME_FILES = Set.of(Path.of( 191 "Contents/Home/lib/server/libjvm.dylib")); 192 193 }