1 /* 2 * Copyright (c) 2018, 2020, 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 package jdk.incubator.jpackage.internal; 26 27 import java.io.File; 28 import java.io.FileInputStream; 29 import java.io.IOException; 30 import java.nio.file.Files; 31 import java.nio.file.Path; 32 import java.nio.file.Paths; 33 import java.text.MessageFormat; 34 import java.util.ArrayList; 35 import java.util.Arrays; 36 import java.util.Collection; 37 import java.util.EnumSet; 38 import java.util.HashMap; 39 import java.util.HashSet; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Set; 43 import java.util.Properties; 44 import java.util.ResourceBundle; 45 import java.util.jar.Attributes; 46 import java.util.jar.JarFile; 47 import java.util.jar.Manifest; 48 import java.util.stream.Stream; 49 import java.util.regex.Matcher; 50 import java.util.regex.Pattern; 51 52 /** 53 * Arguments 54 * 55 * This class encapsulates and processes the command line arguments, 56 * in effect, implementing all the work of jpackage tool. 57 * 58 * The primary entry point, processArguments(): 59 * Processes and validates command line arguments, constructing DeployParams. 60 * Validates the DeployParams, and generate the BundleParams. 61 * Generates List of Bundlers from BundleParams valid for this platform. 62 * Executes each Bundler in the list. 63 */ 64 public class Arguments { 65 private static final ResourceBundle I18N = ResourceBundle.getBundle( 66 "jdk.incubator.jpackage.internal.resources.MainResources"); 67 68 private static final String FA_EXTENSIONS = "extension"; 69 private static final String FA_CONTENT_TYPE = "mime-type"; 70 private static final String FA_DESCRIPTION = "description"; 71 private static final String FA_ICON = "icon"; 72 73 // Mac specific file association keys 74 // String 75 public static final String MAC_CFBUNDLETYPEROLE = "mac.CFBundleTypeRole"; 76 public static final String MAC_LSHANDLERRANK = "mac.LSHandlerRank"; 77 public static final String MAC_NSSTORETYPEKEY = "mac.NSPersistentStoreTypeKey"; 78 public static final String MAC_NSDOCUMENTCLASS = "mac.NSDocumentClass"; 79 // Boolean 80 public static final String MAC_LSTYPEISPACKAGE = "mac.LSTypeIsPackage"; 81 public static final String MAC_LSDOCINPLACE = "mac.LSSupportsOpeningDocumentsInPlace"; 82 public static final String MAC_UIDOCBROWSER = "mac.UISupportsDocumentBrowser"; 83 // Array of strings 84 public static final String MAC_NSEXPORTABLETYPES = "mac.NSExportableTypes"; 85 public static final String MAC_UTTYPECONFORMSTO = "mac.UTTypeConformsTo"; 86 87 // regexp for parsing args (for example, for additional launchers) 88 private static Pattern pattern = Pattern.compile( 89 "(?:(?:([\"'])(?:\\\\\\1|.)*?(?:\\1|$))|(?:\\\\[\"'\\s]|[^\\s]))++"); 90 91 private DeployParams deployParams = null; 92 93 private int pos = 0; 94 private List<String> argList = null; 95 96 private List<CLIOptions> allOptions = null; 97 98 private String input = null; 99 private String output = null; 100 101 private boolean hasMainJar = false; 102 private boolean hasMainClass = false; 103 private boolean hasMainModule = false; 104 public boolean userProvidedBuildRoot = false; 105 106 private String buildRoot = null; 107 private String mainJarPath = null; 108 109 private static boolean runtimeInstaller = false; 110 111 private List<AddLauncherArguments> addLaunchers = null; 112 113 private static Map<String, CLIOptions> argIds = new HashMap<>(); 114 private static Map<String, CLIOptions> argShortIds = new HashMap<>(); 115 116 static { 117 // init maps for parsing arguments 118 (EnumSet.allOf(CLIOptions.class)).forEach(option -> { 119 argIds.put(option.getIdWithPrefix(), option); 120 if (option.getShortIdWithPrefix() != null) { 121 argShortIds.put(option.getShortIdWithPrefix(), option); 122 } 123 }); 124 } 125 Arguments(String[] args)126 public Arguments(String[] args) { 127 argList = new ArrayList<String>(args.length); 128 for (String arg : args) { 129 argList.add(arg); 130 } 131 Log.verbose ("\njpackage argument list: \n" + argList + "\n"); 132 pos = 0; 133 134 deployParams = new DeployParams(); 135 136 allOptions = new ArrayList<>(); 137 138 addLaunchers = new ArrayList<>(); 139 140 output = Paths.get("").toAbsolutePath().toString(); 141 deployParams.setOutput(new File(output)); 142 } 143 144 // CLIOptions is public for DeployParamsTest 145 public enum CLIOptions { 146 PACKAGE_TYPE("type", "t", OptionCategories.PROPERTY, () -> { 147 context().deployParams.setTargetFormat(popArg()); 148 }), 149 150 INPUT ("input", "i", OptionCategories.PROPERTY, () -> { 151 context().input = popArg(); 152 setOptionValue("input", context().input); 153 }), 154 155 OUTPUT ("dest", "d", OptionCategories.PROPERTY, () -> { 156 context().output = popArg(); 157 context().deployParams.setOutput(new File(context().output)); 158 }), 159 160 DESCRIPTION ("description", OptionCategories.PROPERTY), 161 162 VENDOR ("vendor", OptionCategories.PROPERTY), 163 164 APPCLASS ("main-class", OptionCategories.PROPERTY, () -> { 165 context().hasMainClass = true; 166 setOptionValue("main-class", popArg()); 167 }), 168 169 NAME ("name", "n", OptionCategories.PROPERTY), 170 171 VERBOSE ("verbose", OptionCategories.PROPERTY, () -> { 172 setOptionValue("verbose", true); 173 Log.setVerbose(); 174 }), 175 176 RESOURCE_DIR("resource-dir", 177 OptionCategories.PROPERTY, () -> { 178 String resourceDir = popArg(); 179 setOptionValue("resource-dir", resourceDir); 180 }), 181 182 ARGUMENTS ("arguments", OptionCategories.PROPERTY, () -> { 183 List<String> arguments = getArgumentList(popArg()); 184 setOptionValue("arguments", arguments); 185 }), 186 187 JLINK_OPTIONS ("jlink-options", OptionCategories.PROPERTY, () -> { 188 List<String> options = getArgumentList(popArg()); 189 setOptionValue("jlink-options", options); 190 }), 191 192 ICON ("icon", OptionCategories.PROPERTY), 193 194 COPYRIGHT ("copyright", OptionCategories.PROPERTY), 195 196 LICENSE_FILE ("license-file", OptionCategories.PROPERTY), 197 198 VERSION ("app-version", OptionCategories.PROPERTY), 199 200 RELEASE ("linux-app-release", OptionCategories.PROPERTY), 201 202 JAVA_OPTIONS ("java-options", OptionCategories.PROPERTY, () -> { 203 List<String> args = getArgumentList(popArg()); 204 args.forEach(a -> setOptionValue("java-options", a)); 205 }), 206 207 FILE_ASSOCIATIONS ("file-associations", 208 OptionCategories.PROPERTY, () -> { 209 Map<String, ? super Object> args = new HashMap<>(); 210 211 // load .properties file 212 Map<String, String> initialMap = getPropertiesFromFile(popArg()); 213 214 putUnlessNull(args, StandardBundlerParam.FA_EXTENSIONS.getID(), 215 initialMap.get(FA_EXTENSIONS)); 216 217 putUnlessNull(args, StandardBundlerParam.FA_CONTENT_TYPE.getID(), 218 initialMap.get(FA_CONTENT_TYPE)); 219 220 putUnlessNull(args, StandardBundlerParam.FA_DESCRIPTION.getID(), 221 initialMap.get(FA_DESCRIPTION)); 222 223 putUnlessNull(args, StandardBundlerParam.FA_ICON.getID(), 224 initialMap.get(FA_ICON)); 225 226 // Mac extended file association arguments 227 putUnlessNull(args, MAC_CFBUNDLETYPEROLE, 228 initialMap.get(MAC_CFBUNDLETYPEROLE)); 229 230 putUnlessNull(args, MAC_LSHANDLERRANK, 231 initialMap.get(MAC_LSHANDLERRANK)); 232 233 putUnlessNull(args, MAC_NSSTORETYPEKEY, 234 initialMap.get(MAC_NSSTORETYPEKEY)); 235 236 putUnlessNull(args, MAC_NSDOCUMENTCLASS, 237 initialMap.get(MAC_NSDOCUMENTCLASS)); 238 239 putUnlessNull(args, MAC_LSTYPEISPACKAGE, 240 initialMap.get(MAC_LSTYPEISPACKAGE)); 241 242 putUnlessNull(args, MAC_LSDOCINPLACE, 243 initialMap.get(MAC_LSDOCINPLACE)); 244 245 putUnlessNull(args, MAC_UIDOCBROWSER, 246 initialMap.get(MAC_UIDOCBROWSER)); 247 248 putUnlessNull(args, MAC_NSEXPORTABLETYPES, 249 initialMap.get(MAC_NSEXPORTABLETYPES)); 250 251 putUnlessNull(args, MAC_UTTYPECONFORMSTO, 252 initialMap.get(MAC_UTTYPECONFORMSTO)); 253 254 ArrayList<Map<String, ? super Object>> associationList = 255 new ArrayList<Map<String, ? super Object>>(); 256 257 associationList.add(args); 258 259 // check that we really add _another_ value to the list 260 setOptionValue("file-associations", associationList); 261 262 }), 263 264 ADD_LAUNCHER ("add-launcher", 265 OptionCategories.PROPERTY, () -> { 266 String spec = popArg(); 267 String name = null; 268 String filename = spec; 269 if (spec.contains("=")) { 270 String[] values = spec.split("=", 2); 271 name = values[0]; 272 filename = values[1]; 273 } 274 context().addLaunchers.add( 275 new AddLauncherArguments(name, filename)); 276 }), 277 278 TEMP_ROOT ("temp", OptionCategories.PROPERTY, () -> { 279 context().buildRoot = popArg(); 280 context().userProvidedBuildRoot = true; 281 setOptionValue("temp", context().buildRoot); 282 }), 283 284 INSTALL_DIR ("install-dir", OptionCategories.PROPERTY), 285 286 PREDEFINED_APP_IMAGE ("app-image", OptionCategories.PROPERTY), 287 288 PREDEFINED_RUNTIME_IMAGE ("runtime-image", OptionCategories.PROPERTY), 289 290 MAIN_JAR ("main-jar", OptionCategories.PROPERTY, () -> { 291 context().mainJarPath = popArg(); 292 context().hasMainJar = true; 293 setOptionValue("main-jar", context().mainJarPath); 294 }), 295 296 MODULE ("module", "m", OptionCategories.MODULAR, () -> { 297 context().hasMainModule = true; 298 setOptionValue("module", popArg()); 299 }), 300 301 ADD_MODULES ("add-modules", OptionCategories.MODULAR), 302 303 MODULE_PATH ("module-path", "p", OptionCategories.MODULAR), 304 305 BIND_SERVICES ("bind-services", OptionCategories.PROPERTY, () -> { 306 showDeprecation("bind-services"); 307 setOptionValue("bind-services", true); 308 }), 309 310 MAC_SIGN ("mac-sign", "s", OptionCategories.PLATFORM_MAC, () -> { 311 setOptionValue("mac-sign", true); 312 }), 313 314 MAC_BUNDLE_NAME ("mac-package-name", OptionCategories.PLATFORM_MAC), 315 316 MAC_BUNDLE_IDENTIFIER("mac-package-identifier", 317 OptionCategories.PLATFORM_MAC), 318 319 MAC_BUNDLE_SIGNING_PREFIX ("mac-package-signing-prefix", 320 OptionCategories.PLATFORM_MAC), 321 322 MAC_SIGNING_KEY_NAME ("mac-signing-key-user-name", 323 OptionCategories.PLATFORM_MAC), 324 325 MAC_SIGNING_KEYCHAIN ("mac-signing-keychain", 326 OptionCategories.PLATFORM_MAC), 327 328 WIN_MENU_HINT ("win-menu", OptionCategories.PLATFORM_WIN, () -> { 329 setOptionValue("win-menu", true); 330 }), 331 332 WIN_MENU_GROUP ("win-menu-group", OptionCategories.PLATFORM_WIN), 333 334 WIN_SHORTCUT_HINT ("win-shortcut", 335 OptionCategories.PLATFORM_WIN, () -> { 336 setOptionValue("win-shortcut", true); 337 }), 338 339 WIN_PER_USER_INSTALLATION ("win-per-user-install", 340 OptionCategories.PLATFORM_WIN, () -> { 341 setOptionValue("win-per-user-install", false); 342 }), 343 344 WIN_DIR_CHOOSER ("win-dir-chooser", 345 OptionCategories.PLATFORM_WIN, () -> { 346 setOptionValue("win-dir-chooser", true); 347 }), 348 349 WIN_UPGRADE_UUID ("win-upgrade-uuid", 350 OptionCategories.PLATFORM_WIN), 351 352 WIN_CONSOLE_HINT ("win-console", OptionCategories.PLATFORM_WIN, () -> { 353 setOptionValue("win-console", true); 354 }), 355 356 LINUX_BUNDLE_NAME ("linux-package-name", 357 OptionCategories.PLATFORM_LINUX), 358 359 LINUX_DEB_MAINTAINER ("linux-deb-maintainer", 360 OptionCategories.PLATFORM_LINUX), 361 362 LINUX_CATEGORY ("linux-app-category", 363 OptionCategories.PLATFORM_LINUX), 364 365 LINUX_RPM_LICENSE_TYPE ("linux-rpm-license-type", 366 OptionCategories.PLATFORM_LINUX), 367 368 LINUX_PACKAGE_DEPENDENCIES ("linux-package-deps", 369 OptionCategories.PLATFORM_LINUX), 370 371 LINUX_SHORTCUT_HINT ("linux-shortcut", 372 OptionCategories.PLATFORM_LINUX, () -> { 373 setOptionValue("linux-shortcut", true); 374 }), 375 376 LINUX_MENU_GROUP ("linux-menu-group", OptionCategories.PLATFORM_LINUX); 377 378 private final String id; 379 private final String shortId; 380 private final OptionCategories category; 381 private final Runnable action; 382 private static Arguments argContext; 383 CLIOptions(String id, OptionCategories category)384 private CLIOptions(String id, OptionCategories category) { 385 this(id, null, category, null); 386 } 387 CLIOptions(String id, String shortId, OptionCategories category)388 private CLIOptions(String id, String shortId, 389 OptionCategories category) { 390 this(id, shortId, category, null); 391 } 392 CLIOptions(String id, OptionCategories category, Runnable action)393 private CLIOptions(String id, 394 OptionCategories category, Runnable action) { 395 this(id, null, category, action); 396 } 397 CLIOptions(String id, String shortId, OptionCategories category, Runnable action)398 private CLIOptions(String id, String shortId, 399 OptionCategories category, Runnable action) { 400 this.id = id; 401 this.shortId = shortId; 402 this.action = action; 403 this.category = category; 404 } 405 setContext(Arguments context)406 static void setContext(Arguments context) { 407 argContext = context; 408 } 409 context()410 public static Arguments context() { 411 if (argContext != null) { 412 return argContext; 413 } else { 414 throw new RuntimeException("Argument context is not set."); 415 } 416 } 417 getId()418 public String getId() { 419 return this.id; 420 } 421 getIdWithPrefix()422 String getIdWithPrefix() { 423 return "--" + this.id; 424 } 425 getShortIdWithPrefix()426 String getShortIdWithPrefix() { 427 return this.shortId == null ? null : "-" + this.shortId; 428 } 429 execute()430 void execute() { 431 if (action != null) { 432 action.run(); 433 } else { 434 defaultAction(); 435 } 436 } 437 defaultAction()438 private void defaultAction() { 439 context().deployParams.addBundleArgument(id, popArg()); 440 } 441 setOptionValue(String option, Object value)442 private static void setOptionValue(String option, Object value) { 443 context().deployParams.addBundleArgument(option, value); 444 } 445 popArg()446 private static String popArg() { 447 nextArg(); 448 return (context().pos >= context().argList.size()) ? 449 "" : context().argList.get(context().pos); 450 } 451 getArg()452 private static String getArg() { 453 return (context().pos >= context().argList.size()) ? 454 "" : context().argList.get(context().pos); 455 } 456 nextArg()457 private static void nextArg() { 458 context().pos++; 459 } 460 hasNextArg()461 private static boolean hasNextArg() { 462 return context().pos < context().argList.size(); 463 } 464 } 465 466 enum OptionCategories { 467 MODULAR, 468 PROPERTY, 469 PLATFORM_MAC, 470 PLATFORM_WIN, 471 PLATFORM_LINUX; 472 } 473 processArguments()474 public boolean processArguments() { 475 try { 476 477 // init context of arguments 478 CLIOptions.setContext(this); 479 480 // parse cmd line 481 String arg; 482 CLIOptions option; 483 for (; CLIOptions.hasNextArg(); CLIOptions.nextArg()) { 484 arg = CLIOptions.getArg(); 485 if ((option = toCLIOption(arg)) != null) { 486 // found a CLI option 487 allOptions.add(option); 488 option.execute(); 489 } else { 490 throw new PackagerException("ERR_InvalidOption", arg); 491 } 492 } 493 494 if (hasMainJar && !hasMainClass) { 495 // try to get main-class from manifest 496 String mainClass = getMainClassFromManifest(); 497 if (mainClass != null) { 498 CLIOptions.setOptionValue( 499 CLIOptions.APPCLASS.getId(), mainClass); 500 } 501 } 502 503 // display error for arguments that are not supported 504 // for current configuration. 505 506 validateArguments(); 507 508 List<Map<String, ? super Object>> launchersAsMap = 509 new ArrayList<>(); 510 511 for (AddLauncherArguments sl : addLaunchers) { 512 launchersAsMap.add(sl.getLauncherMap()); 513 } 514 515 deployParams.addBundleArgument( 516 StandardBundlerParam.ADD_LAUNCHERS.getID(), 517 launchersAsMap); 518 519 // at this point deployParams should be already configured 520 521 deployParams.validate(); 522 523 BundleParams bp = deployParams.getBundleParams(); 524 525 // validate name(s) 526 ArrayList<String> usedNames = new ArrayList<String>(); 527 usedNames.add(bp.getName()); // add main app name 528 529 for (AddLauncherArguments sl : addLaunchers) { 530 Map<String, ? super Object> slMap = sl.getLauncherMap(); 531 String slName = 532 (String) slMap.get(Arguments.CLIOptions.NAME.getId()); 533 if (slName == null) { 534 throw new PackagerException("ERR_NoAddLauncherName"); 535 } 536 // same rules apply to additional launcher names as app name 537 DeployParams.validateName(slName, false); 538 for (String usedName : usedNames) { 539 if (slName.equals(usedName)) { 540 throw new PackagerException("ERR_NoUniqueName"); 541 } 542 } 543 usedNames.add(slName); 544 } 545 if (runtimeInstaller && bp.getName() == null) { 546 throw new PackagerException("ERR_NoJreInstallerName"); 547 } 548 549 generateBundle(bp.getBundleParamsAsMap()); 550 return true; 551 } catch (Exception e) { 552 if (Log.isVerbose()) { 553 Log.verbose(e); 554 } else { 555 String msg1 = e.getMessage(); 556 Log.error(msg1); 557 if (e.getCause() != null && e.getCause() != e) { 558 String msg2 = e.getCause().getMessage(); 559 if (msg2 != null && !msg1.contains(msg2)) { 560 Log.error(msg2); 561 } 562 } 563 } 564 return false; 565 } 566 } 567 validateArguments()568 private void validateArguments() throws PackagerException { 569 String type = deployParams.getTargetFormat(); 570 String ptype = (type != null) ? type : "default"; 571 boolean imageOnly = deployParams.isTargetAppImage(); 572 boolean hasAppImage = allOptions.contains( 573 CLIOptions.PREDEFINED_APP_IMAGE); 574 boolean hasRuntime = allOptions.contains( 575 CLIOptions.PREDEFINED_RUNTIME_IMAGE); 576 boolean installerOnly = !imageOnly && hasAppImage; 577 runtimeInstaller = !imageOnly && hasRuntime && !hasAppImage && 578 !hasMainModule && !hasMainJar; 579 580 for (CLIOptions option : allOptions) { 581 if (!ValidOptions.checkIfSupported(option)) { 582 // includes option valid only on different platform 583 throw new PackagerException("ERR_UnsupportedOption", 584 option.getIdWithPrefix()); 585 } 586 if (imageOnly) { 587 if (!ValidOptions.checkIfImageSupported(option)) { 588 throw new PackagerException("ERR_InvalidTypeOption", 589 option.getIdWithPrefix(), type); 590 } 591 } else if (installerOnly || runtimeInstaller) { 592 if (!ValidOptions.checkIfInstallerSupported(option)) { 593 if (runtimeInstaller) { 594 throw new PackagerException("ERR_NoInstallerEntryPoint", 595 option.getIdWithPrefix()); 596 } else { 597 throw new PackagerException("ERR_InvalidTypeOption", 598 option.getIdWithPrefix(), ptype); 599 } 600 } 601 } 602 } 603 if (hasRuntime) { 604 if (hasAppImage) { 605 // note --runtime-image is only for image or runtime installer. 606 throw new PackagerException("ERR_MutuallyExclusiveOptions", 607 CLIOptions.PREDEFINED_RUNTIME_IMAGE.getIdWithPrefix(), 608 CLIOptions.PREDEFINED_APP_IMAGE.getIdWithPrefix()); 609 } 610 if (allOptions.contains(CLIOptions.ADD_MODULES)) { 611 throw new PackagerException("ERR_MutuallyExclusiveOptions", 612 CLIOptions.PREDEFINED_RUNTIME_IMAGE.getIdWithPrefix(), 613 CLIOptions.ADD_MODULES.getIdWithPrefix()); 614 } 615 if (allOptions.contains(CLIOptions.BIND_SERVICES)) { 616 throw new PackagerException("ERR_MutuallyExclusiveOptions", 617 CLIOptions.PREDEFINED_RUNTIME_IMAGE.getIdWithPrefix(), 618 CLIOptions.BIND_SERVICES.getIdWithPrefix()); 619 } 620 if (allOptions.contains(CLIOptions.JLINK_OPTIONS)) { 621 throw new PackagerException("ERR_MutuallyExclusiveOptions", 622 CLIOptions.PREDEFINED_RUNTIME_IMAGE.getIdWithPrefix(), 623 CLIOptions.JLINK_OPTIONS.getIdWithPrefix()); 624 } 625 } 626 if (hasMainJar && hasMainModule) { 627 throw new PackagerException("ERR_BothMainJarAndModule"); 628 } 629 if (imageOnly && !hasMainJar && !hasMainModule) { 630 throw new PackagerException("ERR_NoEntryPoint"); 631 } 632 } 633 getPlatformBundler()634 private jdk.incubator.jpackage.internal.Bundler getPlatformBundler() { 635 boolean appImage = deployParams.isTargetAppImage(); 636 String type = deployParams.getTargetFormat(); 637 String bundleType = (appImage ? "IMAGE" : "INSTALLER"); 638 639 for (jdk.incubator.jpackage.internal.Bundler bundler : 640 Bundlers.createBundlersInstance().getBundlers(bundleType)) { 641 if (type == null) { 642 if (bundler.isDefault() 643 && bundler.supported(runtimeInstaller)) { 644 return bundler; 645 } 646 } else { 647 if ((appImage || type.equalsIgnoreCase(bundler.getID())) 648 && bundler.supported(runtimeInstaller)) { 649 return bundler; 650 } 651 } 652 } 653 return null; 654 } 655 generateBundle(Map<String,? super Object> params)656 private void generateBundle(Map<String,? super Object> params) 657 throws PackagerException { 658 659 boolean bundleCreated = false; 660 661 // the temp dir needs to be fetched from the params early, 662 // to prevent each copy of the params (such as may be used for 663 // additional launchers) from generating a separate temp dir when 664 // the default is used (the default is a new temp directory) 665 // The bundler.cleanup() below would not otherwise be able to 666 // clean these extra (and unneeded) temp directories. 667 StandardBundlerParam.TEMP_ROOT.fetchFrom(params); 668 669 // determine what bundler to run 670 jdk.incubator.jpackage.internal.Bundler bundler = getPlatformBundler(); 671 672 if (bundler == null) { 673 throw new PackagerException("ERR_InvalidInstallerType", 674 deployParams.getTargetFormat()); 675 } 676 677 Map<String, ? super Object> localParams = new HashMap<>(params); 678 try { 679 bundler.validate(localParams); 680 File result = bundler.execute(localParams, deployParams.outdir); 681 if (result == null) { 682 throw new PackagerException("MSG_BundlerFailed", 683 bundler.getID(), bundler.getName()); 684 } 685 Log.verbose(MessageFormat.format( 686 I18N.getString("message.bundle-created"), 687 bundler.getName())); 688 } catch (ConfigException e) { 689 Log.verbose(e); 690 if (e.getAdvice() != null) { 691 throw new PackagerException(e, "MSG_BundlerConfigException", 692 bundler.getName(), e.getMessage(), e.getAdvice()); 693 } else { 694 throw new PackagerException(e, 695 "MSG_BundlerConfigExceptionNoAdvice", 696 bundler.getName(), e.getMessage()); 697 } 698 } catch (RuntimeException re) { 699 Log.verbose(re); 700 throw new PackagerException(re, "MSG_BundlerRuntimeException", 701 bundler.getName(), re.toString()); 702 } finally { 703 if (userProvidedBuildRoot) { 704 Log.verbose(MessageFormat.format( 705 I18N.getString("message.debug-working-directory"), 706 (new File(buildRoot)).getAbsolutePath())); 707 } else { 708 // always clean up the temporary directory created 709 // when --temp option not used. 710 bundler.cleanup(localParams); 711 } 712 } 713 } 714 toCLIOption(String arg)715 static CLIOptions toCLIOption(String arg) { 716 CLIOptions option; 717 if ((option = argIds.get(arg)) == null) { 718 option = argShortIds.get(arg); 719 } 720 return option; 721 } 722 getPropertiesFromFile(String filename)723 static Map<String, String> getPropertiesFromFile(String filename) { 724 Map<String, String> map = new HashMap<>(); 725 // load properties file 726 File file = new File(filename); 727 Properties properties = new Properties(); 728 try (FileInputStream in = new FileInputStream(file)) { 729 properties.load(in); 730 } catch (IOException e) { 731 Log.error("Exception: " + e.getMessage()); 732 } 733 734 for (final String name: properties.stringPropertyNames()) { 735 map.put(name, properties.getProperty(name)); 736 } 737 738 return map; 739 } 740 getArgumentList(String inputString)741 static List<String> getArgumentList(String inputString) { 742 List<String> list = new ArrayList<>(); 743 if (inputString == null || inputString.isEmpty()) { 744 return list; 745 } 746 747 // The "pattern" regexp attempts to abide to the rule that 748 // strings are delimited by whitespace unless surrounded by 749 // quotes, then it is anything (including spaces) in the quotes. 750 Matcher m = pattern.matcher(inputString); 751 while (m.find()) { 752 String s = inputString.substring(m.start(), m.end()).trim(); 753 // Ensure we do not have an empty string. trim() will take care of 754 // whitespace only strings. The regex preserves quotes and escaped 755 // chars so we need to clean them before adding to the List 756 if (!s.isEmpty()) { 757 list.add(unquoteIfNeeded(s)); 758 } 759 } 760 return list; 761 } 762 putUnlessNull(Map<String, ? super Object> params, String param, Object value)763 static void putUnlessNull(Map<String, ? super Object> params, 764 String param, Object value) { 765 if (value != null) { 766 params.put(param, value); 767 } 768 } 769 unquoteIfNeeded(String in)770 private static String unquoteIfNeeded(String in) { 771 if (in == null) { 772 return null; 773 } 774 775 if (in.isEmpty()) { 776 return ""; 777 } 778 779 // Use code points to preserve non-ASCII chars 780 StringBuilder sb = new StringBuilder(); 781 int codeLen = in.codePointCount(0, in.length()); 782 int quoteChar = -1; 783 for (int i = 0; i < codeLen; i++) { 784 int code = in.codePointAt(i); 785 if (code == '"' || code == '\'') { 786 // If quote is escaped make sure to copy it 787 if (i > 0 && in.codePointAt(i - 1) == '\\') { 788 sb.deleteCharAt(sb.length() - 1); 789 sb.appendCodePoint(code); 790 continue; 791 } 792 if (quoteChar != -1) { 793 if (code == quoteChar) { 794 // close quote, skip char 795 quoteChar = -1; 796 } else { 797 sb.appendCodePoint(code); 798 } 799 } else { 800 // opening quote, skip char 801 quoteChar = code; 802 } 803 } else { 804 sb.appendCodePoint(code); 805 } 806 } 807 return sb.toString(); 808 } 809 getMainClassFromManifest()810 private String getMainClassFromManifest() { 811 if (mainJarPath == null || 812 input == null ) { 813 return null; 814 } 815 816 JarFile jf; 817 try { 818 File file = new File(input, mainJarPath); 819 if (!file.exists()) { 820 return null; 821 } 822 jf = new JarFile(file); 823 Manifest m = jf.getManifest(); 824 Attributes attrs = (m != null) ? m.getMainAttributes() : null; 825 if (attrs != null) { 826 return attrs.getValue(Attributes.Name.MAIN_CLASS); 827 } 828 } catch (IOException ignore) {} 829 return null; 830 } 831 showDeprecation(String option)832 private static void showDeprecation(String option) { 833 Log.error(MessageFormat.format(I18N.getString("warning.deprecation"), 834 option)); 835 } 836 } 837