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.packager.internal.windows; 27 28 import jdk.packager.internal.AbstractImageBundler; 29 import jdk.packager.internal.BundlerParamInfo; 30 import jdk.packager.internal.ConfigException; 31 import jdk.packager.internal.IOUtils; 32 import jdk.packager.internal.Log; 33 import jdk.packager.internal.Platform; 34 import jdk.packager.internal.StandardBundlerParam; 35 import jdk.packager.internal.UnsupportedPlatformException; 36 import jdk.packager.internal.builders.windows.WindowsAppImageBuilder; 37 import jdk.packager.internal.resources.windows.WinResources; 38 import jdk.packager.internal.JLinkBundlerHelper; 39 import jdk.packager.internal.Arguments; 40 import jdk.packager.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.packager.internal.windows.WindowsBundlerParam.*; 53 import static jdk.packager.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.packager.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.info(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 // it is static for the sake of sharing with "Exe" bundles 151 // that may skip calls to validate/bundle in this class! 152 private static File getRootDir(File outDir, Map<String, ? super Object> p) { 153 return new File(outDir, APP_NAME.fetchFrom(p)); 154 } 155 156 private static boolean usePredefineAppName(Map<String, ? super Object> p) { 157 return (PREDEFINED_APP_IMAGE.fetchFrom(p) != null); 158 } 159 160 private static String appName; 161 private synchronized static String getAppName( 162 Map<String, ? super Object> p) { 163 // If we building from predefined app image, then we should use names 164 // from image and not from CLI. 165 if (usePredefineAppName(p)) { 166 if (appName == null) { 167 // Use WIN_APP_IMAGE here, since we already copy pre-defined 168 // image to WIN_APP_IMAGE 169 File appImageDir = new File( 170 WIN_APP_IMAGE.fetchFrom(p).toString() + "\\app"); 171 File [] files = appImageDir.listFiles( 172 (File dir, String name) -> name.endsWith(".cfg")); 173 if (files == null || files.length != 1) { 174 throw new RuntimeException(MessageFormat.format( 175 I18N.getString("error.cannot-find-cfg"), 176 appImageDir)); 177 } else { 178 appName = files[0].getName(); 179 int index = appName.indexOf("."); 180 if (index != -1) { 181 appName = appName.substring(0, index); 182 } 183 } 184 185 return appName; 186 } else { 187 return appName; 188 } 189 } 190 191 return APP_NAME.fetchFrom(p); 192 } 193 194 public static String getLauncherName(Map<String, ? super Object> p) { 195 return getAppName(p) + ".exe"; 196 } 197 198 public static String getLauncherCfgName(Map<String, ? super Object> p) { 199 return "app\\" + getAppName(p) +".cfg"; 200 } 201 202 public boolean bundle(Map<String, ? super Object> p, File outputDirectory) { 203 return doBundle(p, outputDirectory, false) != null; 204 } 205 206 private File createRoot(Map<String, ? super Object> p, 207 File outputDirectory, boolean dependentTask) throws IOException { 208 if (!outputDirectory.isDirectory() && !outputDirectory.mkdirs()) { 209 throw new RuntimeException(MessageFormat.format( 210 I18N.getString("error.cannot-create-output-dir"), 211 outputDirectory.getAbsolutePath())); 212 } 213 if (!outputDirectory.canWrite()) { 214 throw new RuntimeException(MessageFormat.format( 215 I18N.getString("error.cannot-write-to-output-dir"), 216 outputDirectory.getAbsolutePath())); 217 } 218 if (!dependentTask) { 219 Log.info(MessageFormat.format( 220 I18N.getString("message.creating-app-bundle"), 221 APP_NAME.fetchFrom(p), outputDirectory.getAbsolutePath())); 222 } 223 224 // Create directory structure 225 File rootDirectory = getRootDir(outputDirectory, p); 226 IOUtils.deleteRecursive(rootDirectory); 227 rootDirectory.mkdirs(); 228 229 if (!p.containsKey(JLinkBundlerHelper.JLINK_BUILDER.getID())) { 230 p.put(JLinkBundlerHelper.JLINK_BUILDER.getID(), 231 "windowsapp-image-builder"); 232 } 233 234 return rootDirectory; 235 } 236 237 File doBundle(Map<String, ? super Object> p, 238 File outputDirectory, boolean dependentTask) { 239 if (Arguments.CREATE_JRE_INSTALLER.fetchFrom(p)) { 240 return doJreBundle(p, outputDirectory, dependentTask); 241 } else { 242 return doAppBundle(p, outputDirectory, dependentTask); 243 } 244 } 245 246 File doJreBundle(Map<String, ? super Object> p, 247 File outputDirectory, boolean dependentTask) { 248 try { 249 File rootDirectory = createRoot(p, outputDirectory, dependentTask); 250 AbstractAppImageBuilder appBuilder = new WindowsAppImageBuilder( 251 APP_NAME.fetchFrom(p), 252 outputDirectory.toPath()); 253 File predefined = PREDEFINED_RUNTIME_IMAGE.fetchFrom(p); 254 if (predefined == null ) { 255 JLinkBundlerHelper.generateServerJre(p, appBuilder); 256 } else { 257 return predefined; 258 } 259 return rootDirectory; 260 } catch (IOException ex) { 261 Log.info("Exception: "+ex); 262 Log.debug(ex); 263 return null; 264 } catch (Exception ex) { 265 Log.info("Exception: "+ex); 266 Log.debug(ex); 267 return null; 268 } 269 } 270 271 File doAppBundle(Map<String, ? super Object> p, 272 File outputDirectory, boolean dependentTask) { 273 try { 274 File rootDirectory = 275 createRoot(p, outputDirectory, dependentTask); 276 AbstractAppImageBuilder appBuilder = 277 new WindowsAppImageBuilder(p, outputDirectory.toPath()); 278 if (PREDEFINED_RUNTIME_IMAGE.fetchFrom(p) == null ) { 279 JLinkBundlerHelper.execute(p, appBuilder); 280 } else { 281 StandardBundlerParam.copyPredefinedRuntimeImage(p, appBuilder); 282 } 283 if (!dependentTask) { 284 Log.info(MessageFormat.format( 285 I18N.getString("message.result-dir"), 286 outputDirectory.getAbsolutePath())); 287 } 288 return rootDirectory; 289 } catch (IOException ex) { 290 Log.info(ex.toString()); 291 Log.verbose(ex); 292 return null; 293 } catch (Exception ex) { 294 Log.info("Exception: "+ex); 295 Log.debug(ex); 296 return null; 297 } 298 } 299 300 private static final String RUNTIME_AUTO_DETECT = ".runtime.autodetect"; 301 302 public static void extractFlagsFromRuntime( 303 Map<String, ? super Object> params) { 304 if (params.containsKey(".runtime.autodetect")) return; 305 306 params.put(RUNTIME_AUTO_DETECT, "attempted"); 307 308 String commandline; 309 File runtimePath = JLinkBundlerHelper.getJDKHome(params).toFile(); 310 File launcherPath = new File(runtimePath, "bin\\java.exe"); 311 312 ProcessBuilder pb = 313 new ProcessBuilder(launcherPath.getAbsolutePath(), "-version"); 314 try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { 315 try (PrintStream pout = new PrintStream(baos)) { 316 IOUtils.exec(pb, Log.isDebug(), true, pout); 317 } 318 319 commandline = baos.toString(); 320 } catch (IOException e) { 321 e.printStackTrace(); 322 params.put(RUNTIME_AUTO_DETECT, "failed"); 323 return; 324 } 325 326 AbstractImageBundler.extractFlagsFromVersion(params, commandline); 327 params.put(RUNTIME_AUTO_DETECT, "succeeded"); 328 } 329 330 @Override 331 public String getName() { 332 return I18N.getString("bundler.name"); 333 } 334 335 @Override 336 public String getDescription() { 337 return I18N.getString("bundler.description"); 338 } 339 340 @Override 341 public String getID() { 342 return "windows.app"; 343 } 344 345 @Override 346 public String getBundleType() { 347 return "IMAGE"; 348 } 349 350 @Override 351 public Collection<BundlerParamInfo<?>> getBundleParameters() { 352 return getAppBundleParameters(); 353 } 354 355 public static Collection<BundlerParamInfo<?>> getAppBundleParameters() { 356 return Arrays.asList( 357 APP_NAME, 358 APP_RESOURCES, 359 ARGUMENTS, 360 CLASSPATH, 361 ICON_ICO, 362 JVM_OPTIONS, 363 JVM_PROPERTIES, 364 MAIN_CLASS, 365 MAIN_JAR, 366 PREFERENCES_ID, 367 VERSION, 368 VERBOSE 369 ); 370 } 371 372 @Override 373 public File execute( 374 Map<String, ? super Object> params, File outputParentDir) { 375 return doBundle(params, outputParentDir, false); 376 } 377 378 @Override 379 public boolean supported() { 380 return (Platform.getPlatform() == Platform.WINDOWS); 381 } 382 383 }