1 /* 2 * Copyright (c) 2012, 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.sun.javafx.tools.packager.bundlers; 27 28 import com.oracle.bundlers.AbstractBundler; 29 import com.oracle.bundlers.BundlerParamInfo; 30 import com.oracle.bundlers.StandardBundlerParam; 31 import com.oracle.bundlers.windows.WindowsBundlerParam; 32 import com.sun.javafx.tools.packager.Log; 33 import com.sun.javafx.tools.resource.windows.WinResources; 34 35 import java.io.File; 36 import java.io.FileNotFoundException; 37 import java.io.IOException; 38 import java.io.PrintStream; 39 import java.net.MalformedURLException; 40 import java.net.URL; 41 import java.text.MessageFormat; 42 import java.util.*; 43 44 import static com.oracle.bundlers.JreUtils.*; 45 import static com.oracle.bundlers.StandardBundlerParam.*; 46 import static com.oracle.bundlers.windows.WindowsBundlerParam.BIT_ARCH_64; 47 import static com.oracle.bundlers.windows.WindowsBundlerParam.BIT_ARCH_64_RUNTIME; 48 49 public class WinAppBundler extends AbstractBundler { 50 51 private static final ResourceBundle I18N = 52 ResourceBundle.getBundle("com.oracle.bundlers.windows.WinAppBundler"); 53 54 public static final BundlerParamInfo<File> CONFIG_ROOT = new WindowsBundlerParam<>( 55 I18N.getString("param.config-root.name"), 56 I18N.getString("param.config-root.description"), 57 "configRoot", 58 File.class, null, params -> { 59 File imagesRoot = new File(BUILD_ROOT.fetchFrom(params), "windows"); 60 imagesRoot.mkdirs(); 61 return imagesRoot; 62 }, false, (s, p) -> null); 63 64 //Subsetting of JRE is restricted. 65 //JRE README defines what is allowed to strip: 66 // http://www.oracle.com/technetwork/java/javase/jre-7-readme-430162.html //TODO update when 8 goes GA 67 public static final BundlerParamInfo<Rule[]> WIN_JRE_RULES = new StandardBundlerParam<>( 68 "", 69 "", 70 ".win.runtime.rules", 71 Rule[].class, 72 null, 73 params -> new Rule[]{ 74 Rule.prefixNeg("\\bin\\new_plugin"), 75 Rule.prefixNeg("\\lib\\deploy"), 76 Rule.suffixNeg(".pdb"), 77 Rule.suffixNeg(".map"), 78 Rule.suffixNeg("axbridge.dll"), 79 Rule.suffixNeg("eula.dll"), 80 Rule.substrNeg("javacpl"), 81 Rule.suffixNeg("wsdetect.dll"), 82 Rule.substrNeg("eployjava1.dll"), //NP and IE versions 83 Rule.substrNeg("bin\\jp2"), 84 Rule.substrNeg("bin\\jpi"), 85 //Rule.suffixNeg("lib\\ext"), //need some of jars there for https to work 86 Rule.suffixNeg("ssv.dll"), 87 Rule.substrNeg("npjpi"), 88 Rule.substrNeg("npoji"), 89 Rule.suffixNeg(".exe"), 90 //keep core deploy files as JavaFX APIs use them 91 //Rule.suffixNeg("deploy.dll"), 92 Rule.suffixNeg("deploy.jar"), 93 //Rule.suffixNeg("javaws.jar"), 94 //Rule.suffixNeg("plugin.jar"), 95 Rule.suffix(".jar") 96 }, 97 false, 98 (s, p) -> null 99 ); 100 101 public static final BundlerParamInfo<RelativeFileSet> WIN_RUNTIME = new StandardBundlerParam<>( 102 RUNTIME.getName(), 103 RUNTIME.getDescription(), 104 RUNTIME.getID(), 105 RelativeFileSet.class, 106 null, 107 params -> extractJreAsRelativeFileSet(System.getProperty("java.home"), 108 WIN_JRE_RULES.fetchFrom(params)), 109 false, 110 (s, p) -> extractJreAsRelativeFileSet(s, 111 WIN_JRE_RULES.fetchFrom(p)) 112 ); 113 114 private final static String EXECUTABLE_NAME = "WinLauncher.exe"; 115 private final static String EXECUTABLE_SVC_NAME = "WinLauncherSvc.exe"; 116 117 private static final String TOOL_ICON_SWAP="IconSwap.exe"; 118 119 public static final BundlerParamInfo<URL> RAW_EXECUTABLE_URL = new WindowsBundlerParam<>( 120 I18N.getString("param.raw-executable-url.name"), 121 I18N.getString("param.raw-executable-url.description"), 122 "win.launcher.url", 123 URL.class, null, params -> WinResources.class.getResource(EXECUTABLE_NAME), 124 false, (s, p) -> { 125 try { 126 return new URL(s); 127 } catch (MalformedURLException e) { 128 Log.info(e.toString()); 129 return null; 130 } 131 }); 132 133 public static final BundlerParamInfo<Boolean> REBRAND_EXECUTABLE = new WindowsBundlerParam<>( 134 I18N.getString("param.rebrand-executable.name"), 135 I18N.getString("param.rebrand-executable.description"), 136 "win.launcher.rebrand", 137 Boolean.class, null, params -> Boolean.TRUE, 138 false, (s, p) -> Boolean.valueOf(s)); 139 140 public WinAppBundler() { 141 super(); 142 baseResourceLoader = WinResources.class; 143 } 144 145 public final static String WIN_BUNDLER_PREFIX = 146 BUNDLER_PREFIX + "windows/"; 147 148 File getConfigRoot(Map<String, ? super Object> params) { 149 return CONFIG_ROOT.fetchFrom(params); 150 } 151 152 @Override 153 public boolean validate(Map<String, ? super Object> params) throws UnsupportedPlatformException, ConfigException { 154 try { 155 if (params == null) throw new ConfigException( 156 I18N.getString("error.parameters-null"), 157 I18N.getString("error.parameters-null.advice")); 158 159 return doValidate(params); 160 } catch (RuntimeException re) { 161 throw new ConfigException(re); 162 } 163 } 164 165 //to be used by chained bundlers, e.g. by EXE bundler to avoid 166 // skipping validation if p.type does not include "image" 167 boolean doValidate(Map<String, ? super Object> p) throws UnsupportedPlatformException, ConfigException { 168 if (!System.getProperty("os.name").toLowerCase().startsWith("win")) { 169 throw new UnsupportedPlatformException(); 170 } 171 172 if (WinResources.class.getResource(TOOL_ICON_SWAP) == null) { 173 throw new ConfigException( 174 I18N.getString("error.no-windows-resources"), 175 I18N.getString("error.no-windows-resources.advice")); 176 } 177 178 if (SERVICE_HINT.fetchFrom(p) && WinResources.class.getResource(EXECUTABLE_SVC_NAME) == null) { 179 throw new ConfigException( 180 I18N.getString("error.no-windows-resources"), 181 I18N.getString("error.no-windows-resources.advice")); 182 } 183 184 if (MAIN_JAR.fetchFrom(p) == null) { 185 throw new ConfigException( 186 I18N.getString("error.no-application-jar"), 187 I18N.getString("error.no-application-jar.advice")); 188 } 189 190 //validate required inputs 191 if (USE_FX_PACKAGING.fetchFrom(p)) { 192 testRuntime(p, new String[] {"lib/ext/jfxrt.jar", "lib/jfxrt.jar"}); 193 } 194 195 //validate runtime bit-architectire 196 testRuntimeBitArchitecture(p); 197 198 return true; 199 } 200 201 private static void testRuntimeBitArchitecture(Map<String, ? super Object> params) throws ConfigException { 202 if ("true".equalsIgnoreCase(System.getProperty("fxpackager.disableBitArchitectureMismatchCheck"))) { 203 Log.debug(I18N.getString("message.disable-bit-architecture-check")); 204 return; 205 } 206 207 if (BIT_ARCH_64.fetchFrom(params) != BIT_ARCH_64_RUNTIME.fetchFrom(params)) { 208 throw new ConfigException( 209 I18N.getString("error.bit-architecture-mismatch"), 210 I18N.getString("error.bit-architecture-mismatch.advice")); 211 } 212 } 213 214 static String getAppName(Map<String, ? super Object> p) { 215 return APP_NAME.fetchFrom(p); 216 } 217 218 static String getAppSvcName(Map<String, ? super Object> p) { 219 return APP_NAME.fetchFrom(p) + "Svc"; 220 } 221 222 //it is static for the sake of sharing with "Exe" bundles 223 // that may skip calls to validate/bundle in this class! 224 private static File getRootDir(File outDir, Map<String, ? super Object> p) { 225 return new File(outDir, getAppName(p)); 226 } 227 228 public static File getLauncher(File outDir, Map<String, ? super Object> p) { 229 return new File(getRootDir(outDir, p), getAppName(p)+".exe"); 230 } 231 232 public static File getLauncherSvc(File outDir, Map<String, ? super Object> p) { 233 return new File(getRootDir(outDir, p), getAppName(p)+"Svc.exe"); 234 } 235 236 private File getConfig_AppIcon(Map<String, ? super Object> params) { 237 return new File(getConfigRoot(params), getAppName(params) + ".ico"); 238 } 239 240 private final static String TEMPLATE_APP_ICON ="javalogo_white_48.ico"; 241 242 //remove 243 protected void cleanupConfigFiles(Map<String, ? super Object> params) { 244 if (getConfig_AppIcon(params) != null) { 245 getConfig_AppIcon(params).delete(); 246 } 247 } 248 249 private void prepareConfigFiles(Map<String, ? super Object> params) throws IOException { 250 File iconTarget = getConfig_AppIcon(params); 251 252 File icon = ICON.fetchFrom(params); 253 if (icon != null && icon.exists()) { 254 fetchResource(WIN_BUNDLER_PREFIX + iconTarget.getName(), 255 I18N.getString("resource.application-icon"), 256 icon, 257 iconTarget, 258 VERBOSE.fetchFrom(params)); 259 } else { 260 fetchResource(WIN_BUNDLER_PREFIX + iconTarget.getName(), 261 I18N.getString("resource.application-icon"), 262 WinAppBundler.TEMPLATE_APP_ICON, 263 iconTarget, 264 VERBOSE.fetchFrom(params)); 265 } 266 } 267 268 public boolean bundle(Map<String, ? super Object> p, File outputDirectory) { 269 return doBundle(p, outputDirectory, false) != null; 270 } 271 272 File doBundle(Map<String, ? super Object> p, File outputDirectory, boolean dependentTask) { 273 try { 274 outputDirectory.mkdirs(); 275 276 if (!dependentTask) { 277 Log.info(MessageFormat.format(I18N.getString("message.creating-app-bundle"), getAppName(p), outputDirectory.getAbsolutePath())); 278 } 279 280 prepareConfigFiles(p); 281 282 // Create directory structure 283 File rootDirectory = getRootDir(outputDirectory, p); 284 IOUtils.deleteRecursive(rootDirectory); 285 rootDirectory.mkdirs(); 286 287 File appDirectory = new File(rootDirectory, "app"); 288 appDirectory.mkdirs(); 289 copyApplication(p, appDirectory); 290 291 // Generate PkgInfo 292 File pkgInfoFile = new File(appDirectory, "package.cfg"); 293 pkgInfoFile.createNewFile(); 294 writePkgInfo(p, pkgInfoFile); 295 296 // Copy executable root folder 297 File executableFile = getLauncher(outputDirectory, p); 298 IOUtils.copyFromURL( 299 RAW_EXECUTABLE_URL.fetchFrom(p), 300 executableFile); 301 executableFile.setExecutable(true, false); 302 303 // Copy executable to install application as service 304 if (SERVICE_HINT.fetchFrom(p)) { 305 File executableSvcFile = getLauncherSvc(outputDirectory, p); 306 IOUtils.copyFromURL( 307 WinResources.class.getResource(EXECUTABLE_SVC_NAME), 308 executableSvcFile); 309 executableSvcFile.setExecutable(true, false); 310 } 311 312 //Update branding of exe file 313 if (REBRAND_EXECUTABLE.fetchFrom(p) && getConfig_AppIcon(p).exists()) { 314 //extract helper tool 315 File iconSwapTool = File.createTempFile("iconswap", ".exe"); 316 iconSwapTool.delete(); 317 IOUtils.copyFromURL( 318 WinResources.class.getResource(TOOL_ICON_SWAP), 319 iconSwapTool); 320 iconSwapTool.setExecutable(true, false); 321 iconSwapTool.deleteOnExit(); 322 323 //run it on launcher file 324 executableFile.setWritable(true); 325 ProcessBuilder pb = new ProcessBuilder( 326 iconSwapTool.getAbsolutePath(), 327 getConfig_AppIcon(p).getAbsolutePath(), 328 executableFile.getAbsolutePath()); 329 IOUtils.exec(pb, VERBOSE.fetchFrom(p)); 330 executableFile.setReadOnly(); 331 iconSwapTool.delete(); 332 } 333 334 // Copy runtime to PlugIns folder 335 File runtimeDirectory = new File(rootDirectory, "runtime"); 336 copyRuntime(p, runtimeDirectory); 337 338 IOUtils.copyFile(getConfig_AppIcon(p), 339 new File(getRootDir(outputDirectory, p), getAppName(p) + ".ico")); 340 341 if (!dependentTask) { 342 Log.info(MessageFormat.format(I18N.getString("message.result-dir"), outputDirectory.getAbsolutePath())); 343 } 344 345 return rootDirectory; 346 } catch (IOException ex) { 347 System.out.println("Exception: "+ex); 348 ex.printStackTrace(); 349 return null; 350 } finally { 351 if (VERBOSE.fetchFrom(p)) { 352 Log.info(MessageFormat.format(I18N.getString("message.config-save-location"), getConfigRoot(p).getAbsolutePath())); 353 } else { 354 cleanupConfigFiles(p); 355 } 356 } 357 358 } 359 360 @Override 361 public String toString() { 362 return "Windows Application Bundler"; 363 } 364 365 private void copyApplication(Map<String, ? super Object> params, File appDirectory) throws IOException { 366 RelativeFileSet appResource = APP_RESOURCES.fetchFrom(params); 367 if (appResource == null) { 368 throw new RuntimeException("Null app resources?"); 369 } 370 File srcdir = appResource.getBaseDirectory(); 371 for (String fname : appResource.getIncludedFiles()) { 372 IOUtils.copyFile( 373 new File(srcdir, fname), new File(appDirectory, fname)); 374 } 375 } 376 377 private void writePkgInfo(Map<String, ? super Object> params, File pkgInfoFile) throws FileNotFoundException { 378 pkgInfoFile.delete(); 379 380 PrintStream out = new PrintStream(pkgInfoFile); 381 out.println("app.mainjar=" + MAIN_JAR.fetchFrom(params).getIncludedFiles().iterator().next()); 382 out.println("app.version=" + VERSION.fetchFrom(params)); 383 //for future AU support (to be able to find app in the registry) 384 out.println("app.id=" + IDENTIFIER.fetchFrom(params)); 385 out.println("app.preferences.id=" + PREFERENCES_ID.fetchFrom(params)); 386 387 if (USE_FX_PACKAGING.fetchFrom(params)) { 388 out.println("app.mainclass=" + 389 JAVAFX_LAUNCHER_CLASS.replaceAll("\\.", "/")); 390 } else { 391 out.println("app.mainclass=" + 392 MAIN_CLASS.fetchFrom(params).replaceAll("\\.", "/")); 393 } 394 //This will be emtry string for correctly packaged JavaFX apps 395 out.println("app.classpath=" + MAIN_JAR_CLASSPATH.fetchFrom(params)); 396 397 List<String> jvmargs = JVM_OPTIONS.fetchFrom(params); 398 int idx = 1; 399 for (String a : jvmargs) { 400 out.println("jvmarg."+idx+"="+a); 401 idx++; 402 } 403 Map<String, String> jvmProps = JVM_PROPERTIES.fetchFrom(params); 404 for (Map.Entry<String, String> entry : jvmProps.entrySet()) { 405 out.println("jvmarg."+idx+"=-D"+entry.getKey()+"="+entry.getValue()); 406 idx++; 407 } 408 409 410 Map<String, String> overridableJVMOptions = USER_JVM_OPTIONS.fetchFrom(params); 411 idx = 1; 412 for (Map.Entry<String, String> arg: overridableJVMOptions.entrySet()) { 413 if (arg.getKey() == null || arg.getValue() == null) { 414 Log.info(I18N.getString("message.jvm-user-arg-is-null")); 415 } 416 else { 417 out.println("jvmuserarg."+idx+".name="+arg.getKey()); 418 out.println("jvmuserarg."+idx+".value="+arg.getValue()); 419 } 420 idx++; 421 } 422 out.close(); 423 } 424 425 private void copyRuntime(Map<String, ? super Object> params, File runtimeDirectory) throws IOException { 426 RelativeFileSet runtime = WIN_RUNTIME.fetchFrom(params); 427 if (runtime == null) { 428 //its ok, request to use system JRE 429 return; 430 } 431 runtimeDirectory.mkdirs(); 432 433 File srcdir = runtime.getBaseDirectory(); 434 File destDir = new File(runtimeDirectory, srcdir.getName()); 435 Set<String> filesToCopy = runtime.getIncludedFiles(); 436 for (String fname : filesToCopy) { 437 IOUtils.copyFile( 438 new File(srcdir, fname), new File(destDir, fname)); 439 } 440 } 441 442 @Override 443 public String getName() { 444 return I18N.getString("bundler.name"); 445 } 446 447 @Override 448 public String getDescription() { 449 return I18N.getString("bundler.description"); 450 } 451 452 @Override 453 public String getID() { 454 return "windows.app"; 455 } 456 457 @Override 458 public BundleType getBundleType() { 459 return BundleType.IMAGE; 460 } 461 462 @Override 463 public Collection<BundlerParamInfo<?>> getBundleParameters() { 464 return getAppBundleParameters(); 465 } 466 467 public static Collection<BundlerParamInfo<?>> getAppBundleParameters() { 468 return Arrays.asList( 469 APP_NAME, 470 APP_RESOURCES, 471 BUILD_ROOT, 472 CONFIG_ROOT, 473 ICON, 474 IDENTIFIER, 475 JVM_OPTIONS, 476 JVM_PROPERTIES, 477 MAIN_CLASS, 478 MAIN_JAR, 479 MAIN_JAR_CLASSPATH, 480 PREFERENCES_ID, 481 RAW_EXECUTABLE_URL, 482 WIN_RUNTIME, 483 USE_FX_PACKAGING, 484 USER_JVM_OPTIONS, 485 VERSION 486 ); 487 } 488 489 @Override 490 public File execute(Map<String, ? super Object> params, File outputParentDir) { 491 return doBundle(params, outputParentDir, false); 492 } 493 }