1 /* 2 * Copyright (c) 2014, 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.bundlers.mac; 27 28 import com.oracle.bundlers.BundlerParamInfo; 29 import com.oracle.bundlers.StandardBundlerParam; 30 31 import com.sun.javafx.tools.packager.Log; 32 import com.sun.javafx.tools.packager.bundlers.ConfigException; 33 import com.sun.javafx.tools.packager.bundlers.IOUtils; 34 import com.sun.javafx.tools.packager.bundlers.UnsupportedPlatformException; 35 import com.sun.javafx.tools.resource.mac.MacResources; 36 37 import java.io.*; 38 import java.text.MessageFormat;import java.util.ArrayList; 39 import java.util.Collection; 40 import java.util.HashMap; 41 import java.util.List; 42 import java.util.Map; 43 import java.util.ResourceBundle; 44 45 import static com.oracle.bundlers.StandardBundlerParam.APP_NAME; 46 import static com.oracle.bundlers.StandardBundlerParam.BUILD_ROOT; 47 import static com.oracle.bundlers.StandardBundlerParam.IDENTIFIER; 48 import static com.oracle.bundlers.StandardBundlerParam.SERVICE_HINT; 49 import static com.oracle.bundlers.StandardBundlerParam.VERBOSE; 50 51 public class MacPKGBundler extends MacBaseInstallerBundler { 52 53 private static final ResourceBundle I18N = 54 ResourceBundle.getBundle("com.oracle.bundlers.mac.MacPKGBundler"); 55 56 public final static String MAC_BUNDLER_PREFIX = 57 BUNDLER_PREFIX + "macosx" + File.separator; 58 59 private static final String TEMPLATE_PREINSTALL_SCRIPT = "preinstall.template"; 60 private static final String TEMPLATE_POSTINSTALL_SCRIPT = "postinstall.template"; 61 62 private static final BundlerParamInfo<File> PACKAGES_ROOT = new StandardBundlerParam<>( 63 I18N.getString("param.packages-root.name"), 64 I18N.getString("param.packages-root.description"), 65 "mac.pkg.packagesRoot", 66 File.class, 67 null, 68 params -> { 69 File packagesRoot = new File(BUILD_ROOT.fetchFrom(params), "packages"); 70 packagesRoot.mkdirs(); 71 return packagesRoot; 72 }, 73 false, 74 (s, p) -> new File(s)); 75 76 77 protected final BundlerParamInfo<File> SCRIPTS_DIR = new StandardBundlerParam<>( 78 I18N.getString("param.scripts-dir.name"), 79 I18N.getString("param.scripts-dir.description"), 80 "mac.pkg.scriptsDir", 81 File.class, 82 null, 83 params -> { 84 File scriptsDir = new File(CONFIG_ROOT.fetchFrom(params), "scripts"); 85 scriptsDir.mkdirs(); 86 return scriptsDir; 87 }, 88 false, 89 (s, p) -> new File(s)); 90 91 92 public MacPKGBundler() { 93 super(); 94 baseResourceLoader = MacResources.class; 95 } 96 97 //@Override 98 public File bundle(Map<String, ? super Object> p, File outdir) { 99 Log.info(MessageFormat.format(I18N.getString("message.building-pkg"), APP_NAME.fetchFrom(p))); 100 101 File appImageDir = APP_IMAGE_BUILD_ROOT.fetchFrom(p); 102 File daemonImageDir = DAEMON_IMAGE_BUILD_ROOT.fetchFrom(p); 103 104 try { 105 appImageDir.mkdirs(); 106 prepareAppBundle(p); 107 108 if (SERVICE_HINT.fetchFrom(p)) { 109 daemonImageDir.mkdirs(); 110 prepareDaemonBundle(p); 111 } 112 113 return createPKG(p, outdir); 114 } catch (Exception ex) { 115 return null; 116 } 117 } 118 119 private File getPackages_AppPackage(Map<String, ? super Object> params) { 120 return new File(PACKAGES_ROOT.fetchFrom(params), APP_NAME.fetchFrom(params) + "-app.pkg"); 121 } 122 123 private File getPackages_DaemonPackage(Map<String, ? super Object> params) { 124 return new File(PACKAGES_ROOT.fetchFrom(params), APP_NAME.fetchFrom(params) + "-daemon.pkg"); 125 } 126 127 private void cleanupPackagesFiles(Map<String, ? super Object> params) { 128 if (getPackages_AppPackage(params) != null) { 129 getPackages_AppPackage(params).delete(); 130 } 131 if (getPackages_DaemonPackage(params) != null) { 132 getPackages_DaemonPackage(params).delete(); 133 } 134 } 135 136 private File getScripts_PreinstallFile(Map<String, ? super Object> params) { 137 return new File(SCRIPTS_DIR.fetchFrom(params), "preinstall"); 138 } 139 140 private File getScripts_PostinstallFile(Map<String, ? super Object> params) { 141 return new File(SCRIPTS_DIR.fetchFrom(params), "postinstall"); 142 } 143 144 private void cleanupPackageScripts(Map<String, ? super Object> params) { 145 if (getScripts_PreinstallFile(params) != null) { 146 getScripts_PreinstallFile(params).delete(); 147 } 148 if (getScripts_PostinstallFile(params) != null) { 149 getScripts_PostinstallFile(params).delete(); 150 } 151 } 152 153 private String getDaemonIdentifier(Map<String, ? super Object> params) { 154 return IDENTIFIER.fetchFrom(params).toLowerCase() + ".daemon"; 155 } 156 157 private void preparePackageScripts(Map<String, ? super Object> params) throws IOException 158 { 159 Log.verbose(I18N.getString("message.preparing-scripts")); 160 161 Map<String, String> data = new HashMap<>(); 162 163 data.put("DEPLOY_DAEMON_IDENTIFIER", getDaemonIdentifier(params)); 164 data.put("DEPLOY_LAUNCHD_PLIST_FILE", 165 IDENTIFIER.fetchFrom(params).toLowerCase() + ".launchd.plist"); 166 167 Writer w = new BufferedWriter(new FileWriter(getScripts_PreinstallFile(params))); 168 String content = preprocessTextResource( 169 MAC_BUNDLER_PREFIX + getScripts_PreinstallFile(params).getName(), 170 I18N.getString("resource.pkg-preinstall-script"), 171 TEMPLATE_PREINSTALL_SCRIPT, 172 data, 173 VERBOSE.fetchFrom(params)); 174 w.write(content); 175 w.close(); 176 getScripts_PreinstallFile(params).setExecutable(true, false); 177 178 w = new BufferedWriter(new FileWriter(getScripts_PostinstallFile(params))); 179 content = preprocessTextResource( 180 MAC_BUNDLER_PREFIX + getScripts_PostinstallFile(params).getName(), 181 I18N.getString("resource.pkg-postinstall-script"), 182 TEMPLATE_POSTINSTALL_SCRIPT, 183 data, 184 VERBOSE.fetchFrom(params)); 185 w.write(content); 186 w.close(); 187 getScripts_PostinstallFile(params).setExecutable(true, false); 188 } 189 190 private File createPKG(Map<String, ? super Object> params, File outdir) { 191 //generic find attempt 192 try { 193 String appLocation = 194 APP_IMAGE_BUILD_ROOT.fetchFrom(params) + "/" + APP_NAME.fetchFrom(params) + ".app"; 195 File predefinedImage = getPredefinedImage(params); 196 if (predefinedImage != null) { 197 appLocation = predefinedImage.getAbsolutePath(); 198 } 199 200 String daemonLocation = DAEMON_IMAGE_BUILD_ROOT.fetchFrom(params) + "/" + APP_NAME.fetchFrom(params) + ".daemon"; 201 202 File appPKG = getPackages_AppPackage(params); 203 File daemonPKG = getPackages_DaemonPackage(params); 204 205 // build application package 206 ProcessBuilder pb = new ProcessBuilder("pkgbuild", 207 "--component", 208 appLocation, 209 "--install-location", 210 "/Applications", 211 appPKG.getAbsolutePath()); 212 IOUtils.exec(pb, VERBOSE.fetchFrom(params)); 213 214 // build daemon package if requested 215 if (SERVICE_HINT.fetchFrom(params)) { 216 preparePackageScripts(params); 217 218 pb = new ProcessBuilder("pkgbuild", 219 "--identifier", 220 APP_NAME.fetchFrom(params) + ".daemon", 221 "--root", 222 daemonLocation, 223 "--scripts", 224 SCRIPTS_DIR.fetchFrom(params).getAbsolutePath(), 225 daemonPKG.getAbsolutePath()); 226 IOUtils.exec(pb, VERBOSE.fetchFrom(params)); 227 } 228 229 // build final package 230 File finalPKG = new File(outdir, APP_NAME.fetchFrom(params)+".pkg"); 231 outdir.mkdirs(); 232 233 List<String> commandLine = new ArrayList<>(); 234 commandLine.add("productbuild"); 235 commandLine.add("--package"); 236 commandLine.add(appPKG.getAbsolutePath()); 237 if (SERVICE_HINT.fetchFrom(params)) { 238 commandLine.add("--package"); 239 commandLine.add(daemonPKG.getAbsolutePath()); 240 } 241 commandLine.add(finalPKG.getAbsolutePath()); 242 243 pb = new ProcessBuilder(commandLine); 244 IOUtils.exec(pb, VERBOSE.fetchFrom(params)); 245 246 return finalPKG; 247 } catch (Exception ignored) { 248 Log.verbose(ignored); 249 return null; 250 } finally { 251 if (!VERBOSE.fetchFrom(params)) { 252 cleanupPackagesFiles(params); 253 254 if (SERVICE_HINT.fetchFrom(params)) { 255 cleanupPackageScripts(params); 256 } 257 } 258 } 259 } 260 261 ////////////////////////////////////////////////////////////////////////////////// 262 // Implement Bundler 263 ////////////////////////////////////////////////////////////////////////////////// 264 265 @Override 266 public String getName() { 267 return I18N.getString("bundler.name"); 268 } 269 270 @Override 271 public String getDescription() { 272 return I18N.getString("bundler.description"); 273 } 274 275 @Override 276 public String getID() { 277 return "pkg"; 278 } 279 280 @Override 281 public Collection<BundlerParamInfo<?>> getBundleParameters() { 282 //Add PKG Specific parameters as required 283 return super.getBundleParameters(); 284 } 285 286 @Override 287 public boolean validate(Map<String, ? super Object> params) throws UnsupportedPlatformException, ConfigException { 288 try { 289 if (params == null) throw new ConfigException( 290 I18N.getString("error.parameters-null"), 291 I18N.getString("error.parameters-null.advice")); 292 293 // hdiutil is always available so there's no need to test for availability. 294 //run basic validation to ensure requirements are met 295 296 //run basic validation to ensure requirements are met 297 //we are not interested in return code, only possible exception 298 APP_BUNDLER.fetchFrom(params).doValidate(params); 299 return true; 300 } catch (RuntimeException re) { 301 throw new ConfigException(re); 302 } 303 } 304 305 @Override 306 public File execute(Map<String, ? super Object> params, File outputParentDir) { 307 return bundle(params, outputParentDir); 308 } 309 310 }