1 /*
   2  * Copyright (c) 2012, 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.jpackager.internal.windows;
  27 
  28 import jdk.jpackager.internal.AbstractImageBundler;
  29 import jdk.jpackager.internal.BundlerParamInfo;
  30 import jdk.jpackager.internal.ConfigException;
  31 import jdk.jpackager.internal.IOUtils;
  32 import jdk.jpackager.internal.Log;
  33 import jdk.jpackager.internal.Platform;
  34 import jdk.jpackager.internal.StandardBundlerParam;
  35 import jdk.jpackager.internal.UnsupportedPlatformException;
  36 import jdk.jpackager.internal.builders.windows.WindowsAppImageBuilder;
  37 import jdk.jpackager.internal.resources.windows.WinResources;
  38 import jdk.jpackager.internal.JLinkBundlerHelper;
  39 import jdk.jpackager.internal.Arguments;
  40 import jdk.jpackager.internal.builders.AbstractAppImageBuilder;
  41 
  42 import java.io.ByteArrayOutputStream;
  43 import java.io.File;
  44 import java.io.IOException;
  45 import java.io.PrintStream;
  46 import java.text.MessageFormat;
  47 import java.util.Arrays;
  48 import java.util.Collection;
  49 import java.util.Map;
  50 import java.util.ResourceBundle;
  51 
  52 import static jdk.jpackager.internal.windows.WindowsBundlerParam.*;
  53 import static jdk.jpackager.internal.windows.WinMsiBundler.WIN_APP_IMAGE;
  54 
  55 public class WinAppBundler extends AbstractImageBundler {
  56 
  57     private static final ResourceBundle I18N =
  58             ResourceBundle.getBundle(
  59             "jdk.jpackager.internal.resources.windows.WinAppBundler");
  60 
  61     public static final BundlerParamInfo<File> ICON_ICO =
  62             new StandardBundlerParam<>(
  63             I18N.getString("param.icon-ico.name"),
  64             I18N.getString("param.icon-ico.description"),
  65             "icon.ico",
  66             File.class,
  67             params -> {
  68                 File f = ICON.fetchFrom(params);
  69                 if (f != null && !f.getName().toLowerCase().endsWith(".ico")) {
  70                     Log.error(MessageFormat.format(
  71                             I18N.getString("message.icon-not-ico"), f));
  72                     return null;
  73                 }
  74                 return f;
  75             },
  76             (s, p) -> new File(s));
  77 
  78     public WinAppBundler() {
  79         super();
  80         baseResourceLoader = WinResources.class;
  81     }
  82 
  83     public final static String WIN_BUNDLER_PREFIX =
  84             BUNDLER_PREFIX + "windows/";
  85 
  86     @Override
  87     public boolean validate(Map<String, ? super Object> params)
  88             throws UnsupportedPlatformException, ConfigException {
  89         try {
  90             if (params == null) throw new ConfigException(
  91                     I18N.getString("error.parameters-null"),
  92                     I18N.getString("error.parameters-null.advice"));
  93 
  94             return doValidate(params);
  95         } catch (RuntimeException re) {
  96             if (re.getCause() instanceof ConfigException) {
  97                 throw (ConfigException) re.getCause();
  98             } else {
  99                 throw new ConfigException(re);
 100             }
 101         }
 102     }
 103 
 104     // to be used by chained bundlers, e.g. by EXE bundler to avoid
 105     // skipping validation if p.type does not include "image"
 106     boolean doValidate(Map<String, ? super Object> p)
 107             throws UnsupportedPlatformException, ConfigException {
 108         if (Platform.getPlatform() != Platform.WINDOWS) {
 109             throw new UnsupportedPlatformException();
 110         }
 111 
 112         imageBundleValidation(p);
 113 
 114         if (StandardBundlerParam.getPredefinedAppImage(p) != null) {
 115             return true;
 116         }
 117 
 118         // Make sure that jpackager.exe exists.
 119         File tool = new File(
 120                 System.getProperty("java.home") + "\\bin\\jpackager.exe");
 121 
 122         if (!tool.exists()) {
 123             throw new ConfigException(
 124                     I18N.getString("error.no-windows-resources"),
 125                     I18N.getString("error.no-windows-resources.advice"));
 126         }
 127 
 128         // validate runtime bit-architectire
 129         testRuntimeBitArchitecture(p);
 130 
 131         return true;
 132     }
 133 
 134     private static void testRuntimeBitArchitecture(
 135             Map<String, ? super Object> params) throws ConfigException {
 136         if ("true".equalsIgnoreCase(System.getProperty(
 137                 "fxpackager.disableBitArchitectureMismatchCheck"))) {
 138             Log.debug(I18N.getString("message.disable-bit-architecture-check"));
 139             return;
 140         }
 141 
 142         if ((BIT_ARCH_64.fetchFrom(params) !=
 143                 BIT_ARCH_64_RUNTIME.fetchFrom(params))) {
 144             throw new ConfigException(
 145                     I18N.getString("error.bit-architecture-mismatch"),
 146                     I18N.getString("error.bit-architecture-mismatch.advice"));
 147         }
 148     }
 149 
 150     private static boolean usePredefineAppName(Map<String, ? super Object> p) {
 151         return (PREDEFINED_APP_IMAGE.fetchFrom(p) != null);
 152     }
 153 
 154     private static String appName;
 155     synchronized static String getAppName(
 156             Map<String, ? super Object> p) {
 157         // If we building from predefined app image, then we should use names
 158         // from image and not from CLI.
 159         if (usePredefineAppName(p)) {
 160             if (appName == null) {
 161                 // Use WIN_APP_IMAGE here, since we already copy pre-defined
 162                 // image to WIN_APP_IMAGE
 163                 File appImageDir = new File(
 164                         WIN_APP_IMAGE.fetchFrom(p).toString() + "\\app");
 165                 File [] files = appImageDir.listFiles(
 166                         (File dir, String name) -> name.endsWith(".cfg"));
 167                 if (files == null || files.length == 0) {
 168                     throw new RuntimeException(MessageFormat.format(
 169                         I18N.getString("error.cannot-find-launcher"),
 170                         appImageDir));
 171                 } else {
 172                     appName = files[0].getName();
 173                     int index = appName.indexOf(".");
 174                     if (index != -1) {
 175                         appName = appName.substring(0, index);
 176                     }
 177                     if (files.length > 1) {
 178                         Log.error(MessageFormat.format(I18N.getString(
 179                                 "message.multiple-launchers"), appName));
 180                     }
 181                 }
 182                 return appName;
 183             } else {
 184                 return appName;
 185             }
 186         }
 187 
 188         return APP_NAME.fetchFrom(p);
 189     }
 190 
 191     public static String getLauncherName(Map<String, ? super Object> p) {
 192         return getAppName(p) + ".exe";
 193     }
 194 
 195     public static String getLauncherCfgName(Map<String, ? super Object> p) {
 196         return "app\\" + getAppName(p) +".cfg";
 197     }
 198 
 199     public boolean bundle(Map<String, ? super Object> p, File outputDirectory) {
 200         return doBundle(p, outputDirectory, false) != null;
 201     }
 202 
 203     File doBundle(Map<String, ? super Object> p,
 204                 File outputDirectory, boolean dependentTask) {
 205         if (Arguments.CREATE_JRE_INSTALLER.fetchFrom(p)) {
 206             return doJreBundle(p, outputDirectory, dependentTask);
 207         } else {
 208             return doAppBundle(p, outputDirectory, dependentTask);
 209         }
 210     }
 211 
 212     File doJreBundle(Map<String, ? super Object> p,
 213             File outputDirectory, boolean dependentTask) {
 214         try {
 215             File rootDirectory = createRoot(p, outputDirectory, dependentTask,
 216                 APP_NAME.fetchFrom(p), "windowsapp-image-builder");
 217             AbstractAppImageBuilder appBuilder = new WindowsAppImageBuilder(
 218                     APP_NAME.fetchFrom(p),
 219                     outputDirectory.toPath());
 220             File predefined = PREDEFINED_RUNTIME_IMAGE.fetchFrom(p);
 221             if (predefined == null ) {
 222                 JLinkBundlerHelper.generateServerJre(p, appBuilder);
 223             } else {
 224                 return predefined;
 225             }
 226             return rootDirectory;
 227         } catch (Exception ex) {
 228             Log.error("Exception: "+ex);
 229             Log.verbose(ex);
 230             return null;
 231         }
 232     }
 233 
 234     File doAppBundle(Map<String, ? super Object> p,
 235             File outputDirectory, boolean dependentTask) {
 236         try {
 237             File rootDirectory = createRoot(p, outputDirectory, dependentTask,
 238                     APP_NAME.fetchFrom(p), "windowsapp-image-builder");
 239             AbstractAppImageBuilder appBuilder =
 240                     new WindowsAppImageBuilder(p, outputDirectory.toPath());
 241             if (PREDEFINED_RUNTIME_IMAGE.fetchFrom(p) == null ) {
 242                 JLinkBundlerHelper.execute(p, appBuilder);
 243             } else {
 244                 StandardBundlerParam.copyPredefinedRuntimeImage(p, appBuilder);
 245             }
 246             if (!dependentTask) {
 247                 Log.verbose(MessageFormat.format(
 248                         I18N.getString("message.result-dir"),
 249                         outputDirectory.getAbsolutePath()));
 250             }
 251             return rootDirectory;
 252         } catch (Exception ex) {
 253             Log.error("Exception: "+ex);
 254             Log.verbose(ex);
 255             return null;
 256         }
 257     }
 258 
 259     private static final String RUNTIME_AUTO_DETECT = ".runtime.autodetect";
 260 
 261     public static void extractFlagsFromRuntime(
 262             Map<String, ? super Object> params) {
 263         if (params.containsKey(".runtime.autodetect")) return;
 264 
 265         params.put(RUNTIME_AUTO_DETECT, "attempted");
 266 
 267         String commandline;
 268         File runtimePath = JLinkBundlerHelper.getJDKHome(params).toFile();
 269         File launcherPath = new File(runtimePath, "bin\\java.exe");
 270 
 271         ProcessBuilder pb =
 272                  new ProcessBuilder(launcherPath.getAbsolutePath(), "-version");
 273         try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
 274             try (PrintStream pout = new PrintStream(baos)) {
 275                 IOUtils.exec(pb, Log.isDebug(), true, pout);
 276             }
 277 
 278             commandline = baos.toString();
 279         } catch (IOException e) {
 280             e.printStackTrace();
 281             params.put(RUNTIME_AUTO_DETECT, "failed");
 282             return;
 283         }
 284 
 285         AbstractImageBundler.extractFlagsFromVersion(params, commandline);
 286         params.put(RUNTIME_AUTO_DETECT, "succeeded");
 287     }
 288 
 289     @Override
 290     public String getName() {
 291         return I18N.getString("bundler.name");
 292     }
 293 
 294     @Override
 295     public String getDescription() {
 296         return I18N.getString("bundler.description");
 297     }
 298 
 299     @Override
 300     public String getID() {
 301         return "windows.app";
 302     }
 303 
 304     @Override
 305     public String getBundleType() {
 306         return "IMAGE";
 307     }
 308 
 309     @Override
 310     public Collection<BundlerParamInfo<?>> getBundleParameters() {
 311         return getAppBundleParameters();
 312     }
 313 
 314     public static Collection<BundlerParamInfo<?>> getAppBundleParameters() {
 315         return Arrays.asList(
 316                 APP_NAME,
 317                 APP_RESOURCES,
 318                 ARGUMENTS,
 319                 CLASSPATH,
 320                 ICON_ICO,
 321                 JVM_OPTIONS,
 322                 JVM_PROPERTIES,
 323                 MAIN_CLASS,
 324                 MAIN_JAR,
 325                 PREFERENCES_ID,
 326                 VERSION,
 327                 VERBOSE
 328             );
 329     }
 330 
 331     @Override
 332     public File execute(
 333             Map<String, ? super Object> params, File outputParentDir) {
 334         return doBundle(params, outputParentDir, false);
 335     }
 336 
 337     @Override
 338     public boolean supported() {
 339         return (Platform.getPlatform() == Platform.WINDOWS);
 340     }
 341 
 342 }