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 }