1 /* 2 * Copyright (c) 2014, 2018, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.packager.internal.mac; 27 28 import jdk.packager.internal.AbstractBundler; 29 import jdk.packager.internal.BundlerParamInfo; 30 import jdk.packager.internal.StandardBundlerParam; 31 import jdk.packager.internal.Arguments; 32 import jdk.packager.internal.Log; 33 import jdk.packager.internal.ConfigException; 34 import jdk.packager.internal.IOUtils; 35 import jdk.packager.internal.Platform; 36 import jdk.packager.internal.UnsupportedPlatformException; 37 38 import java.io.ByteArrayOutputStream; 39 import java.io.File; 40 import java.io.IOException; 41 import java.io.PrintStream; 42 import java.nio.file.Files; 43 import java.text.MessageFormat; 44 import java.util.ArrayList; 45 import java.util.Arrays; 46 import java.util.Collection; 47 import java.util.LinkedHashSet; 48 import java.util.List; 49 import java.util.Map; 50 import java.util.ResourceBundle; 51 import java.util.regex.Matcher; 52 import java.util.regex.Pattern; 53 54 import static jdk.packager.internal.StandardBundlerParam.*; 55 56 public abstract class MacBaseInstallerBundler extends AbstractBundler { 57 58 private static final ResourceBundle I18N = 59 ResourceBundle.getBundle( 60 "jdk.packager.internal.resources.mac.MacBaseInstallerBundler"); 61 62 // This could be generalized more to be for any type of Image Bundler 63 public static final BundlerParamInfo<MacAppBundler> APP_BUNDLER = 64 new StandardBundlerParam<>( 65 I18N.getString("param.app-bundler.name"), 66 I18N.getString("param.app-bundle.description"), 67 "mac.app.bundler", 68 MacAppBundler.class, 69 params -> new MacAppBundler(), 70 (s, p) -> null); 71 72 public final BundlerParamInfo<File> APP_IMAGE_BUILD_ROOT = 73 new StandardBundlerParam<>( 74 I18N.getString("param.app-image-build-root.name"), 75 I18N.getString("param.app-image-build-root.description"), 76 "mac.app.imageRoot", 77 File.class, 78 params -> { 79 File imageDir = IMAGES_ROOT.fetchFrom(params); 80 if (!imageDir.exists()) imageDir.mkdirs(); 81 try { 82 return Files.createTempDirectory( 83 imageDir.toPath(), "image-").toFile(); 84 } catch (IOException e) { 85 return new File(imageDir, getID()+ ".image"); 86 } 87 }, 88 (s, p) -> new File(s)); 89 90 public static final BundlerParamInfo<File> CONFIG_ROOT = 91 new StandardBundlerParam<>( 92 I18N.getString("param.config-root.name"), 93 I18N.getString("param.config-root.description"), 94 "configRoot", 95 File.class, 96 params -> { 97 File imagesRoot = 98 new File(BUILD_ROOT.fetchFrom(params), "macosx"); 99 imagesRoot.mkdirs(); 100 return imagesRoot; 101 }, 102 (s, p) -> null); 103 104 public static final BundlerParamInfo<String> SIGNING_KEY_USER = 105 new StandardBundlerParam<>( 106 I18N.getString("param.signing-key-name.name"), 107 I18N.getString("param.signing-key-name.description"), 108 Arguments.CLIOptions.MAC_SIGNING_KEY_NAME.getId(), 109 String.class, 110 params -> "", 111 null); 112 113 public static final BundlerParamInfo<String> SIGNING_KEYCHAIN = 114 new StandardBundlerParam<>( 115 I18N.getString("param.signing-keychain.name"), 116 I18N.getString("param.signing-keychain.description"), 117 Arguments.CLIOptions.MAC_SIGNING_KEYCHAIN.getId(), 118 String.class, 119 params -> "", 120 null); 121 122 public static final BundlerParamInfo<String> INSTALLER_NAME = 123 new StandardBundlerParam<> ( 124 I18N.getString("param.installer-name.name"), 125 I18N.getString("param.installer-name.description"), 126 "mac.installerName", 127 String.class, 128 params -> { 129 String nm = APP_NAME.fetchFrom(params); 130 if (nm == null) return null; 131 132 String version = VERSION.fetchFrom(params); 133 if (version == null) { 134 return nm; 135 } else { 136 return nm + "-" + version; 137 } 138 }, 139 (s, p) -> s); 140 141 protected void validateAppImageAndBundeler( 142 Map<String, ? super Object> params) 143 throws ConfigException, UnsupportedPlatformException { 144 if (PREDEFINED_APP_IMAGE.fetchFrom(params) != null) { 145 File applicationImage = PREDEFINED_APP_IMAGE.fetchFrom(params); 146 if (!applicationImage.exists()) { 147 throw new ConfigException( 148 MessageFormat.format(I18N.getString( 149 "message.app-image-dir-does-not-exist"), 150 PREDEFINED_APP_IMAGE.getID(), 151 applicationImage.toString()), 152 MessageFormat.format(I18N.getString( 153 "message.app-image-dir-does-not-exist.advice"), 154 PREDEFINED_APP_IMAGE.getID())); 155 } 156 if (APP_NAME.fetchFrom(params) == null) { 157 throw new ConfigException( 158 I18N.getString("message.app-image-requires-app-name"), 159 I18N.getString( 160 "message.app-image-requires-app-name.advice")); 161 } 162 if (IDENTIFIER.fetchFrom(params) == null) { 163 throw new ConfigException( 164 I18N.getString("message.app-image-requires-identifier"), 165 I18N.getString( 166 "message.app-image-requires-identifier.advice")); 167 } 168 } else { 169 APP_BUNDLER.fetchFrom(params).doValidate(params); 170 } 171 } 172 173 protected File prepareAppBundle( 174 Map<String, ? super Object> p, boolean pkg) { 175 File predefinedImage = StandardBundlerParam.getPredefinedAppImage(p); 176 if (predefinedImage != null) { 177 return predefinedImage; 178 } 179 File appImageRoot = APP_IMAGE_BUILD_ROOT.fetchFrom(p); 180 if (pkg) { 181 // create pkg in dmg 182 return new MacPkgBundler().bundle(p, appImageRoot); 183 } else { 184 return APP_BUNDLER.fetchFrom(p).doBundle(p, appImageRoot, true); 185 } 186 } 187 188 @Override 189 public Collection<BundlerParamInfo<?>> getBundleParameters() { 190 Collection<BundlerParamInfo<?>> results = new LinkedHashSet<>(); 191 192 results.addAll(MacAppBundler.getAppBundleParameters()); 193 results.addAll(Arrays.asList( 194 APP_BUNDLER, 195 CONFIG_ROOT, 196 APP_IMAGE_BUILD_ROOT, 197 PREDEFINED_APP_IMAGE 198 )); 199 200 return results; 201 } 202 203 @Override 204 public String getBundleType() { 205 return "INSTALLER"; 206 } 207 208 public static String findKey(String key, String keychainName, 209 boolean verbose) { 210 if (Platform.getPlatform() != Platform.MAC) { 211 return null; 212 } 213 214 try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); 215 PrintStream ps = new PrintStream(baos)) { 216 List<String> searchOptions = new ArrayList<>(); 217 searchOptions.add("security"); 218 searchOptions.add("find-certificate"); 219 searchOptions.add("-c"); 220 searchOptions.add(key); 221 searchOptions.add("-a"); 222 if (keychainName != null && !keychainName.isEmpty()) { 223 searchOptions.add(keychainName); 224 } 225 226 ProcessBuilder pb = new ProcessBuilder(searchOptions); 227 228 IOUtils.exec(pb, verbose, false, ps); 229 Pattern p = Pattern.compile("\"alis\"<blob>=\"([^\"]+)\""); 230 Matcher m = p.matcher(baos.toString()); 231 if (!m.find()) { 232 Log.info("Did not find a key matching '" + key + "'"); 233 return null; 234 } 235 String matchedKey = m.group(1); 236 if (m.find()) { 237 Log.info("Found more than one key matching '" + key + "'"); 238 return null; 239 } 240 Log.debug("Using key '" + matchedKey + "'"); 241 return matchedKey; 242 } catch (IOException ioe) { 243 Log.verbose(ioe); 244 return null; 245 } 246 } 247 }