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