1 /* 2 * Copyright (c) 2011, 2016, 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.ant; 27 28 import java.io.File; 29 import java.util.ArrayList; 30 import java.util.LinkedList; 31 import java.util.List; 32 import java.util.Map; 33 import java.util.stream.Collectors; 34 35 import com.oracle.tools.packager.StandardBundlerParam; 36 import com.sun.javafx.tools.ant.Platform.Jvmarg; 37 import com.sun.javafx.tools.ant.Platform.Property; 38 import com.sun.javafx.tools.packager.DeployParams; 39 import com.oracle.tools.packager.Log; 40 import com.sun.javafx.tools.packager.PackagerException; 41 import com.sun.javafx.tools.packager.PackagerLib; 42 import com.sun.javafx.tools.packager.bundlers.Bundler.BundleType; 43 import org.apache.tools.ant.BuildException; 44 import org.apache.tools.ant.DynamicAttribute; 45 import org.apache.tools.ant.Task; 46 import org.apache.tools.ant.types.DataType; 47 48 /** 49 * Generates package for Web deployment and redistribution of application. 50 * Package includes of set of jar files, JNLP file and HTML file. 51 * 52 * Minimal example: 53 * <pre> 54 * <fx:deploy width="600" height="400" 55 * outdir="web-dist" outfile="Fish"> 56 * <info title="Sample application"/> 57 * <fx:application refid="myapp"/> 58 * <fx:resources refid="myresources"/> 59 * </fx:deploy> 60 * </pre> 61 * Above example will generate HTML/JNLP files into the web-dist directory 62 * and use "Fish" as prefix for generated files. Details about application and 63 * its resources are defined elsewhere in the application and resource elements. 64 * <p> 65 * Minimal complete example: 66 * <pre> 67 * <fx:deploy width="600" height="400" 68 * outdir="web-dist" outfile="Fish"> 69 * <info title="Sample application"/> 70 * <fx:application name="SampleApp" 71 * mainClass="testapp.MainApp" 72 * preloaderClass="testpreloader.Preloader"/> 73 * <fx:resources> 74 * <fx:fileset requiredFor="preloader" dir="dist"> 75 * <include name="preloader.jar"/> 76 * </fx:fileset> 77 * <fx:fileset dir="dist"> 78 * <include name="helloworld.jar"/> 79 * </fx:fileset> 80 * </fx:resources> 81 * </fx:deploy> 82 * </pre> 83 * Same as above but here application and resource details are defined in place. 84 * Note that using references helps with reducing code duplication as fx:jar need 85 * to be used for double clickable jars. 86 * 87 * @ant.task name="deploy" category="javafx" 88 */ 89 public class DeployFXTask extends Task implements DynamicAttribute { 90 private String width = null; 91 private String height = null; 92 private String embeddedWidth = null; 93 private String embeddedHeight = null; 94 private String outfile = null; 95 private String outdir = null; 96 private boolean embedJNLP; 97 private boolean isExtension = false; 98 private Boolean signBundle; 99 100 //Before FCS default is to include DT files with app 101 // to ensure tests are using latest and compatible. 102 //After FCS default is to use shared copy. 103 private boolean includeDT = false; 104 105 private String updateMode="background"; 106 private Info appInfo = null; 107 private Application app = null; 108 private Resources resources = null; 109 private Preferences prefs = null; 110 private String codebase = null; 111 112 //container to embed application into 113 //could be either string id or js code. If it is string id then it needs to 114 //be escaped 115 private String placeholder; 116 117 private PackagerLib packager; 118 private DeployParams deployParams; 119 120 private Callbacks callbacks; 121 122 boolean offlineAllowed = true; 123 124 //default native bundle settings 125 // use NONE to avoid large disk space and build time overhead 126 BundleType nativeBundles = BundleType.NONE; 127 String bundleFormat = null; 128 129 private boolean verbose = false; 130 public void setVerbose(boolean v) { 131 verbose = v; 132 } 133 134 public void setCodebase(String str) { 135 codebase = str; 136 } 137 138 public DeployFXTask() { 139 packager = new PackagerLib(); 140 deployParams = new DeployParams(); 141 } 142 143 @Override 144 public void execute() { 145 deployParams.setOutfile(outfile); 146 deployParams.setOutdir(new File(outdir)); 147 deployParams.setOfflineAllowed(offlineAllowed); 148 deployParams.setVerbose(verbose); 149 deployParams.setCodebase(codebase); 150 deployParams.setSignBundle(signBundle); 151 152 if (width != null) { 153 deployParams.setWidth(Integer.valueOf(width)); 154 } 155 156 if (height != null) { 157 deployParams.setHeight(Integer.valueOf(height)); 158 } 159 160 if (embeddedWidth != null && embeddedHeight != null) { 161 deployParams.setEmbeddedDimensions(embeddedWidth, embeddedHeight); 162 } 163 164 deployParams.setEmbedJNLP(embedJNLP); 165 if (perms != null) { 166 deployParams.setAllPermissions(perms.getElevated()); 167 } 168 169 if (app != null) { 170 if (app.getModule() == null) { 171 deployParams.setApplicationClass(app.get().mainClass); 172 } 173 else { 174 nativeBundles = BundleType.NATIVE; 175 int index = app.getModule().indexOf("/"); 176 177 if (index > 0) { 178 deployParams.setModule(app.getModule()); 179 } 180 else { 181 deployParams.setModule(app.getModule() + "/" + app.get().mainClass); 182 } 183 } 184 185 deployParams.setPreloader(app.get().preloaderClass); 186 deployParams.setAppId(app.get().id); 187 deployParams.setAppName(app.get().name); 188 deployParams.setParams(app.get().parameters); 189 deployParams.setArguments(app.get().getArguments()); 190 deployParams.setHtmlParams(app.get().htmlParameters); 191 deployParams.setFallback(app.get().fallbackApp); 192 deployParams.setSwingAppWithEmbeddedJavaFX(app.get().embeddedIntoSwing); 193 deployParams.setVersion(app.get().version); 194 deployParams.setId(app.get().id); 195 deployParams.setServiceHint(app.get().daemon); 196 197 if (runtime != null) { 198 for (String s : runtime.getAddModules()) { 199 deployParams.addAddModule(s); 200 } 201 202 for (String s : runtime.getLimitModules()) { 203 deployParams.addLimitModule(s); 204 } 205 206 deployParams.setModulePath(runtime.getModulePath()); 207 208 Boolean stripNativeCommands = runtime.getStripNativeCommands(); 209 210 if (stripNativeCommands != null) { 211 deployParams.setStripNativeCommands(stripNativeCommands); 212 } 213 214 Boolean detectModules = runtime.getDetectModules(); 215 216 if (detectModules != null) { 217 deployParams.setDetectModules(detectModules); 218 } 219 } 220 } 221 222 if (appInfo != null) { 223 deployParams.setTitle(appInfo.title); 224 deployParams.setVendor(appInfo.vendor); 225 deployParams.setDescription(appInfo.appDescription); 226 deployParams.setCategory(appInfo.category); 227 deployParams.setLicenseType(appInfo.licenseType); 228 deployParams.setCopyright(appInfo.copyright); 229 deployParams.setEmail(appInfo.email); 230 231 for (Info.Icon i: appInfo.icons) { 232 if (i instanceof Info.Splash) { 233 deployParams.addIcon(i.href, i.kind, i.width, i.height, i.depth, 234 ((Info.Splash) i).mode); 235 } else { 236 deployParams.addIcon(i.href, i.kind, i.width, i.height, i.depth, 237 DeployParams.RunMode.WEBSTART); 238 } 239 } 240 241 deployParams.addBundleArgument(StandardBundlerParam.FILE_ASSOCIATIONS.getID(), 242 appInfo.fileAssociations.stream() 243 .map(FileAssociation::createLauncherMap) 244 .collect(Collectors.toList())); 245 } 246 247 deployParams.setUpdateMode(updateMode); 248 deployParams.setExtension(isExtension); 249 deployParams.setIncludeDT(includeDT); 250 251 if (platform != null) { 252 Platform pl = platform.get(); 253 if (pl.j2se != null) { 254 deployParams.setJRE(pl.j2se); 255 } 256 if (pl.javafx != null) { 257 deployParams.setJavafx(pl.javafx); 258 } 259 260 //only pass it further if it was explicitly set 261 // as we do not want to override default 262 if (pl.javaRoot != null) { 263 if (Platform.USE_SYSTEM_JRE.equals(pl.javaRoot)) { 264 deployParams.setJavaRuntimeSource(null); 265 } else { 266 deployParams.setJavaRuntimeSource(new File(pl.javaRoot)); 267 } 268 } 269 for (Property p: pl.properties) { 270 deployParams.addJvmProperty(p.name, p.value); 271 } 272 for (Jvmarg a: pl.jvmargs) { 273 deployParams.addJvmArg(a.value); 274 } 275 for (Property a: pl.jvmUserArgs) { 276 deployParams.addJvmUserArg(a.name, a.value); 277 } 278 } 279 280 if (callbacks != null) { 281 for (Callback cb: callbacks.callbacks) { 282 deployParams.addCallback(cb.getName(), cb.getCmd()); 283 } 284 } 285 286 if (prefs != null) { 287 deployParams.setNeedShortcut(prefs.getShortcut()); 288 deployParams.setNeedInstall(prefs.getInstall()); 289 deployParams.setNeedMenu(prefs.getMenu()); 290 deployParams.setSystemWide(prefs.getSystemInstall()); 291 deployParams.setInstalldirChooser(prefs.getInstalldirChooser()); 292 } 293 294 for (Template t: templateList) { 295 deployParams.addTemplate(t.infile, t.outfile); 296 } 297 298 for (BundleArgument ba : bundleArgumentList) { 299 deployParams.addBundleArgument(ba.arg, ba.value); 300 } 301 302 deployParams.setPlaceholder(placeholder); 303 304 if (resources != null) { 305 for (FileSet fs: resources.getResources()) { 306 Utils.addResources(deployParams, fs); 307 } 308 } 309 310 List<Map<String, ? super Object>> launchersAsMap = new ArrayList<>(); 311 for (SecondaryLauncher sl : secondaryLaunchers) { 312 launchersAsMap.add(sl.createLauncherMap()); 313 } 314 315 deployParams.addBundleArgument( 316 StandardBundlerParam.SECONDARY_LAUNCHERS.getID(), 317 launchersAsMap); 318 319 deployParams.setBundleType(nativeBundles); 320 deployParams.setTargetFormat(bundleFormat); 321 322 Log.setLogger(new AntLog(this.getProject())); 323 324 try { 325 packager.generateDeploymentPackages(deployParams); 326 } catch (PackagerException pe) { 327 if (pe.getCause() != null) { 328 throw new BuildException(pe.getCause().getMessage(), 329 pe.getCause()); 330 } else { 331 throw new BuildException(pe.getMessage(), 332 pe); 333 } 334 } catch (Exception e) { 335 throw new BuildException(e.getMessage(), e); 336 } finally { 337 Log.setLogger(null); 338 } 339 } 340 341 /** 342 * Set to true if we are generating an 'extension' JNLP. 343 * 344 * @ant.not-required Default is false. 345 */ 346 public void setExtension(boolean v) { 347 isExtension = v; 348 } 349 350 public void setNativeBundles(String v) { 351 if ("false".equals(v) || "none".equals(v)) { 352 nativeBundles = BundleType.NONE; 353 } else if ("all".equals(v) || "true".equals(v)) { 354 nativeBundles = BundleType.ALL; 355 } else if ("image".equals(v)) { 356 nativeBundles = BundleType.IMAGE; 357 } else if ("installer".equals(v)) { 358 nativeBundles = BundleType.INSTALLER; 359 } else { 360 //assume it is request to build only specific format (like exe or msi) 361 nativeBundles = BundleType.INSTALLER; 362 bundleFormat = (v != null) ? v.toLowerCase() : null; 363 } 364 } 365 366 /** 367 * Indicates the preferences for when checks for application updates 368 * are performed. Supported modes are always, timeout and background. 369 * 370 * @ant.not-required Default is background. 371 */ 372 public void setUpdateMode(String v) { 373 String l = v.toLowerCase(); 374 if ("eager".equals(l)) { 375 //workaround for doc bug in 2.0 376 l="always"; 377 } 378 if (!"always".equals(l) && !"background".equals(l) 379 && !"timeout".equals(l)) { 380 throw new BuildException("Unknown update mode: ["+l+"]." + 381 "Supported modes are: 'always', 'timeout' and 'background'"); 382 } 383 updateMode = l; 384 } 385 386 /** 387 * Indicates if the application can be launched offline. 388 * 389 * If application is already downloaded and update mode is eager then 390 * the check will timeout after a few seconds, in which case the cached 391 * application will be launched instead. 392 * 393 * Given a reasonably fast server connection, 394 * the latest version of the application will usually be run, 395 * but it is not guaranteed. The application, however, can be run offline. 396 * 397 * @ant.not-required Default is true. 398 */ 399 public void setOfflineAllowed(boolean v) { 400 offlineAllowed = v; 401 } 402 403 /** 404 * Application width for embedding application into Web page 405 * 406 * @ant.optional 407 */ 408 public void setEmbeddedWidth(String w) { 409 embeddedWidth = w; 410 } 411 412 /** 413 * Application width. Used for webstart and embedded applications 414 * unless emdeddedWidth is specified 415 * 416 * @ant.required 417 */ 418 public void setWidth(String v) { 419 width = v; 420 } 421 422 /** 423 * Application width for embedding application into Web page 424 * 425 * @ant.optional 426 */ 427 public void setEmbeddedHeight(String w) { 428 embeddedHeight = w; 429 } 430 431 /** 432 * Application height. Used for webstart and embedded applications 433 * unless emdeddedHeight is specified 434 * 435 * @ant.required 436 */ 437 public void setHeight(String v) { 438 height = v; 439 } 440 441 /** 442 * Enable embedding JNLP descriptor into Web page. 443 * Reduces number of network connections to be made on startup and 444 * help to improve startup time. 445 * 446 * @ant.not-required Default is false. 447 */ 448 public void setEmbedJNLP(boolean v) { 449 embedJNLP = v; 450 } 451 452 /** 453 * Directory where application package will be saved. 454 * 455 * @ant.required 456 */ 457 public void setOutdir(String v) { 458 outdir = v; 459 } 460 461 /** 462 * Prefix to be used for new generated files. 463 * 464 * @ant.required 465 */ 466 public void setOutfile(String v) { 467 outfile = v; 468 } 469 470 /** 471 * If true then web deployment is done using javascript files 472 * on java.com. Otherwise copy of javascript file is included into 473 * application package. 474 * 475 * @ant.not-required Before FCS default is false. For FCS default is true. 476 */ 477 public void setIncludeDT(Boolean v) { 478 includeDT = v; 479 } 480 481 /** 482 * Placeholder in the web page where application will be embedded. 483 * This is expected to be Javascript DOM object. 484 * 485 * @ant.required Either reference or id of placeholder is required. 486 */ 487 public void setPlaceholderRef(String p) { 488 this.placeholder = p; 489 } 490 491 /** 492 * Id of the placeholder in the web page where application will be embedded. 493 * Javascript's document.getElementById() is expected to be able to resolve it. 494 * 495 * @ant.required Either reference or id of placeholder is required. 496 */ 497 public void setPlaceholderId(String id) { 498 //raw id of the placeholder, need to escape it 499 this.placeholder = "'"+id+"'"; 500 } 501 502 public void setSignBundle(boolean signBundle) { 503 this.signBundle = signBundle; 504 } 505 506 public Info createInfo() { 507 appInfo = new Info(); 508 return appInfo; 509 } 510 511 public Application createApplication() { 512 app = new Application(); 513 return app; 514 } 515 516 public Preferences createPreferences() { 517 prefs = new Preferences(); 518 return prefs; 519 } 520 521 public Callbacks createCallbacks() { 522 if (callbacks != null) { 523 throw new BuildException("Only one callbacks element is supported."); 524 } 525 callbacks = new Callbacks(); 526 return callbacks; 527 } 528 529 public Resources createResources() { 530 if (resources != null) { 531 throw new BuildException("Only one resources element is supported."); 532 } 533 resources = new Resources(); 534 return resources; 535 } 536 537 List<Template> templateList = new LinkedList<>(); 538 539 public Template createTemplate() { 540 Template t = new Template(); 541 templateList.add(t); 542 return t; 543 } 544 545 Platform platform; 546 547 public Platform createPlatform() { 548 platform = new Platform(); 549 return platform; 550 } 551 552 private Permissions perms = null; 553 554 public Permissions createPermissions() { 555 perms = new Permissions(); 556 return perms; 557 } 558 559 List<BundleArgument> bundleArgumentList = new LinkedList<>(); 560 561 public BundleArgument createBundleArgument() { 562 BundleArgument ba = new BundleArgument(); 563 bundleArgumentList.add(ba); 564 return ba; 565 } 566 567 private List<SecondaryLauncher> secondaryLaunchers = new ArrayList<>(); 568 569 public SecondaryLauncher createSecondaryLauncher() { 570 SecondaryLauncher sl = new SecondaryLauncher(); 571 secondaryLaunchers.add(sl); 572 return sl; 573 } 574 575 private Runtime runtime = null; 576 577 public Runtime createRuntime() { 578 runtime = new Runtime(); 579 return runtime; 580 } 581 582 @Override 583 public void setDynamicAttribute(String name, String value) throws BuildException { 584 //Use qName and value - can't really validate anything until we know which bundlers we have, so this has 585 //to done (way) downstream 586 bundleArgumentList.add(new BundleArgument(name, value)); 587 } 588 589 /** 590 * Template to preprocess. 591 * <p> 592 * Template is the HTML file containing markers to be replaced with 593 * javascript or HTML snippets needed to deploy JavaFX application on the 594 * Web page. This allows to deploy application into "real" Web pages 595 * and simplify development process if application is tightly 596 * integrated with the page (e.g. uses javascript to communicate to it). 597 * <p> 598 * Marker has the form of #XXX# or #XXX(id)#. Where id is identifier 599 * of an application and XXX is one of following: 600 * <ul> 601 * <li>DT.SCRIPT.URL - location of dtjava.js 602 * <li>DT.SCRIPT.CODE - script element to include dtjava.js 603 * <li>DT.EMBED.CODE.DYNAMIC - code to embed application into given placeholder 604 * It is expected it will be wrapped into function() 605 * <li>DT.EMBED.CODE.ONLOAD - all code needed to embed application into Web page 606 * using onload hook (except inclusion of dtjava.js) 607 * <li>DT.LAUNCH.CODE - code need to launch application. 608 * Expected to be wrappend into function(). 609 * </ul> 610 * 611 * Page with multiple different applications can be processed multiple times 612 * - one per application. To avoid confusion markers need to use 613 * application ids (alphanumeric string no spaces). 614 * <p> 615 * If input and output files are the same then template is processed in place. 616 * <p> 617 * Example: 618 * <pre> 619 * <template file="App_template.html" tofile="App.html"/> 620 * </pre> 621 * 622 * @ant.type name="Template" category="javafx" 623 */ 624 public static class Template extends DataType { 625 File infile = null; 626 File outfile = null; 627 628 /** 629 * Input file. 630 * 631 * @ant.required 632 */ 633 public void setFile(File f) { 634 infile = f; 635 } 636 637 /** 638 * Output file (after preprocessing). 639 * 640 * @ant.not-required Default is the same as input file. 641 */ 642 public void setTofile(File f) { 643 outfile = f; 644 } 645 } 646 647 /** 648 * An argument to be passed off to the bundlers. 649 * 650 * Each bundler uses a set of arguments that may be shared across 651 * the different bundlers or it may be specific to each bundler. 652 * 653 * Some bundlers declare argument types that are not known to the JDK 654 * and may be specific to the particular bundler (such as Mac App Store 655 * categories). These arguments allow you to set and adjust these a 656 * rguments. 657 * 658 * @ant.type name="BundleArgument" category="javafx" 659 */ 660 public static class BundleArgument extends DataType { 661 String arg = null; 662 String value = null; 663 664 BundleArgument() { 665 666 } 667 668 BundleArgument(String arg, String value) { 669 this.arg = arg; 670 this.value = value; 671 } 672 673 /** 674 * Name of the bundle argument. 675 * 676 * @ant.required 677 */ 678 public void setArg(String arg) { 679 this.arg = arg; 680 } 681 682 /** 683 * Value for the bundle argument. 684 * 685 * @ant.not-required Default is a literal null 686 */ 687 public void setValue(String value) { 688 this.value = value; 689 } 690 } 691 }