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