1 /* 2 * Copyright (c) 2014, 2016, 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 com.oracle.tools.packager.mac; 27 28 import com.oracle.tools.packager.AbstractBundler; 29 import com.oracle.tools.packager.BundlerParamInfo; 30 import com.oracle.tools.packager.StandardBundlerParam; 31 import com.oracle.tools.packager.Log; 32 import com.oracle.tools.packager.ConfigException; 33 import com.oracle.tools.packager.IOUtils; 34 import com.oracle.tools.packager.Platform; 35 import com.oracle.tools.packager.UnsupportedPlatformException; 36 37 import java.io.ByteArrayOutputStream; 38 import java.io.File; 39 import java.io.IOException; 40 import java.io.PrintStream; 41 import java.nio.file.Files; 42 import java.nio.file.Path; 43 import java.nio.file.attribute.PosixFilePermission; 44 import java.text.MessageFormat; 45 import java.util.ArrayList; 46 import java.util.Arrays; 47 import java.util.Collection; 48 import java.util.EnumSet; 49 import java.util.LinkedHashSet; 50 import java.util.List; 51 import java.util.Map; 52 import java.util.ResourceBundle; 53 import java.util.Set; 54 import java.util.concurrent.atomic.AtomicReference; 55 import java.util.function.Consumer; 56 import java.util.regex.Matcher; 57 import java.util.regex.Pattern; 58 59 import static com.oracle.tools.packager.StandardBundlerParam.*; 60 61 public abstract class MacBaseInstallerBundler extends AbstractBundler { 62 63 private static final ResourceBundle I18N = 64 ResourceBundle.getBundle(MacBaseInstallerBundler.class.getName()); 65 66 //This could be generalized more to be for any type of Image Bundler 67 public static final BundlerParamInfo<MacAppBundler> APP_BUNDLER = new StandardBundlerParam<>( 68 I18N.getString("param.app-bundler.name"), 69 I18N.getString("param.app-bundle.description"), 70 "mac.app.bundler", 71 MacAppBundler.class, 72 params -> new MacAppBundler(), 73 (s, p) -> null); 74 75 public final BundlerParamInfo<File> APP_IMAGE_BUILD_ROOT = new StandardBundlerParam<>( 76 I18N.getString("param.app-image-build-root.name"), 77 I18N.getString("param.app-image-build-root.description"), 78 "mac.app.imageRoot", 79 File.class, 80 params -> { 81 File imageDir = IMAGES_ROOT.fetchFrom(params); 82 if (!imageDir.exists()) imageDir.mkdirs(); 83 try { 84 return Files.createTempDirectory(imageDir.toPath(), "image-").toFile(); 85 } catch (IOException e) { 86 return new File(imageDir, getID()+ ".image"); 87 } 88 }, 89 (s, p) -> new File(s)); 90 91 public static final StandardBundlerParam<File> MAC_APP_IMAGE = new StandardBundlerParam<>( 92 I18N.getString("param.app-image.name"), 93 I18N.getString("param.app-image.description"), 94 "mac.app.image", 95 File.class, 96 params -> null, 97 (s, p) -> new File(s)); 98 99 100 public static final BundlerParamInfo<MacDaemonBundler> DAEMON_BUNDLER = new StandardBundlerParam<>( 101 I18N.getString("param.daemon-bundler.name"), 102 I18N.getString("param.daemon-bundler.description"), 103 "mac.daemon.bundler", 104 MacDaemonBundler.class, 105 params -> new MacDaemonBundler(), 106 (s, p) -> null); 107 108 109 public final BundlerParamInfo<File> DAEMON_IMAGE_BUILD_ROOT = new StandardBundlerParam<>( 110 I18N.getString("param.daemon-image-build-root.name"), 111 I18N.getString("param.daemon-image-build-root.description"), 112 "mac.daemon.image", 113 File.class, 114 params -> { 115 File imageDir = IMAGES_ROOT.fetchFrom(params); 116 if (!imageDir.exists()) imageDir.mkdirs(); 117 return new File(imageDir, getID()+ ".daemon"); 118 }, 119 (s, p) -> new File(s)); 120 121 122 public static final BundlerParamInfo<File> CONFIG_ROOT = new StandardBundlerParam<>( 123 I18N.getString("param.config-root.name"), 124 I18N.getString("param.config-root.description"), 125 "configRoot", 126 File.class, 127 params -> { 128 File imagesRoot = new File(BUILD_ROOT.fetchFrom(params), "macosx"); 129 imagesRoot.mkdirs(); 130 return imagesRoot; 131 }, 132 (s, p) -> null); 133 134 public static final BundlerParamInfo<String> SIGNING_KEY_USER = new StandardBundlerParam<>( 135 I18N.getString("param.signing-key-name.name"), 136 I18N.getString("param.signing-key-name.description"), 137 "mac.signing-key-user-name", 138 String.class, 139 params -> "", 140 null); 141 142 public static final BundlerParamInfo<String> SIGNING_KEYCHAIN = new StandardBundlerParam<>( 143 I18N.getString("param.signing-keychain.name"), 144 I18N.getString("param.signing-keychain.description"), 145 "mac.signing-keychain", 146 String.class, 147 params -> "", 148 null); 149 150 public static final BundlerParamInfo<String> INSTALLER_NAME = new StandardBundlerParam<> ( 151 I18N.getString("param.installer-name.name"), 152 I18N.getString("param.installer-name.description"), 153 "mac.installerName", 154 String.class, 155 params -> { 156 String nm = APP_NAME.fetchFrom(params); 157 if (nm == null) return null; 158 159 String version = VERSION.fetchFrom(params); 160 if (version == null) { 161 return nm; 162 } else { 163 return nm + "-" + version; 164 } 165 }, 166 (s, p) -> s); 167 168 public static File getPredefinedImage(Map<String, ? super Object> p) { 169 File applicationImage = null; 170 if (MAC_APP_IMAGE.fetchFrom(p) != null) { 171 applicationImage = MAC_APP_IMAGE.fetchFrom(p); 172 Log.debug("Using App Image from " + applicationImage); 173 if (!applicationImage.exists()) { 174 throw new RuntimeException( 175 MessageFormat.format(I18N.getString("message.app-image-dir-does-not-exist"), MAC_APP_IMAGE.getID(), applicationImage.toString())); 176 } 177 } 178 return applicationImage; 179 } 180 181 protected void validateAppImageAndBundeler(Map<String, ? super Object> params) throws ConfigException, UnsupportedPlatformException { 182 if (MAC_APP_IMAGE.fetchFrom(params) != null) { 183 File applicationImage = MAC_APP_IMAGE.fetchFrom(params); 184 if (!applicationImage.exists()) { 185 throw new ConfigException( 186 MessageFormat.format(I18N.getString("message.app-image-dir-does-not-exist"), MAC_APP_IMAGE.getID(), applicationImage.toString()), 187 MessageFormat.format(I18N.getString("message.app-image-dir-does-not-exist.advice"), MAC_APP_IMAGE.getID())); 188 } 189 if (APP_NAME.fetchFrom(params) == null) { 190 throw new ConfigException( 191 I18N.getString("message.app-image-requires-app-name"), 192 I18N.getString("message.app-image-requires-app-name.advice")); 193 } 194 if (IDENTIFIER.fetchFrom(params) == null) { 195 throw new ConfigException( 196 I18N.getString("message.app-image-requires-identifier"), 197 I18N.getString("message.app-image-requires-identifier.advice")); 198 } 199 } else { 200 APP_BUNDLER.fetchFrom(params).doValidate(params); 201 } 202 } 203 204 protected File prepareAppBundle(Map<String, ? super Object> p) { 205 File predefinedImage = getPredefinedImage(p); 206 if (predefinedImage != null) { 207 return predefinedImage; 208 } 209 210 File appImageRoot = APP_IMAGE_BUILD_ROOT.fetchFrom(p); 211 return APP_BUNDLER.fetchFrom(p).doBundle(p, appImageRoot, true); 212 } 213 214 protected File prepareDaemonBundle(Map<String, ? super Object> p) { 215 File daemonImageRoot = DAEMON_IMAGE_BUILD_ROOT.fetchFrom(p); 216 return DAEMON_BUNDLER.fetchFrom(p).doBundle(p, daemonImageRoot, true); 217 } 218 219 @Override 220 public Collection<BundlerParamInfo<?>> getBundleParameters() { 221 Collection<BundlerParamInfo<?>> results = new LinkedHashSet<>(); 222 223 results.addAll(MacAppBundler.getAppBundleParameters()); 224 results.addAll(Arrays.asList( 225 APP_BUNDLER, 226 CONFIG_ROOT, 227 APP_IMAGE_BUILD_ROOT, 228 MAC_APP_IMAGE 229 )); 230 231 return results; 232 } 233 234 @Override 235 public String getBundleType() { 236 return "INSTALLER"; 237 } 238 239 public static String findKey(String key, String keychainName, boolean verbose) { 240 if (Platform.getPlatform() != Platform.MAC) { 241 return null; 242 } 243 244 try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(baos)) { 245 List<String> searchOptions = new ArrayList<>(); 246 searchOptions.add("security"); 247 searchOptions.add("find-certificate"); 248 searchOptions.add("-c"); 249 searchOptions.add(key); 250 searchOptions.add("-a"); 251 if (keychainName != null && !keychainName.isEmpty()) { 252 searchOptions.add(keychainName); 253 } 254 255 ProcessBuilder pb = new ProcessBuilder(searchOptions); 256 257 IOUtils.exec(pb, verbose, false, ps); 258 Pattern p = Pattern.compile("\"alis\"<blob>=\"([^\"]+)\""); 259 Matcher m = p.matcher(baos.toString()); 260 if (!m.find()) { 261 Log.info("Did not find a key matching '" + key + "'"); 262 return null; 263 } 264 String matchedKey = m.group(1); 265 if (m.find()) { 266 Log.info("Found more than one key matching '" + key + "'"); 267 return null; 268 } 269 Log.debug("Using key '" + matchedKey + "'"); 270 return matchedKey; 271 } catch (IOException ioe) { 272 ioe.printStackTrace(); 273 Log.verbose(ioe); 274 return null; 275 } 276 } 277 }