1 /* 2 * Copyright (c) 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. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 package jdk.jpackage.test; 24 25 import java.io.FileOutputStream; 26 import java.io.IOException; 27 import java.nio.file.Files; 28 import java.nio.file.Path; 29 import java.security.SecureRandom; 30 import java.util.*; 31 import java.util.function.Consumer; 32 import java.util.function.Function; 33 import java.util.function.Predicate; 34 import java.util.function.Supplier; 35 import java.util.regex.Pattern; 36 import java.util.stream.Collectors; 37 import java.util.stream.Stream; 38 import jdk.incubator.jpackage.internal.ApplicationLayout; 39 import jdk.jpackage.test.Functional.ThrowingConsumer; 40 import jdk.jpackage.test.Functional.ThrowingFunction; 41 import jdk.jpackage.test.Functional.ThrowingSupplier; 42 43 /** 44 * jpackage command line with prerequisite actions. Prerequisite actions can be 45 * anything. The simplest is to compile test application and pack in a jar for 46 * use on jpackage command line. 47 */ 48 public final class JPackageCommand extends CommandArguments<JPackageCommand> { 49 50 public JPackageCommand() { 51 prerequisiteActions = new Actions(); 52 verifyActions = new Actions(); 53 } 54 55 public JPackageCommand(JPackageCommand cmd) { 56 args.addAll(cmd.args); 57 withToolProvider = cmd.withToolProvider; 58 saveConsoleOutput = cmd.saveConsoleOutput; 59 suppressOutput = cmd.suppressOutput; 60 ignoreDefaultRuntime = cmd.ignoreDefaultRuntime; 61 ignoreDefaultVerbose = cmd.ignoreDefaultVerbose; 62 immutable = cmd.immutable; 63 prerequisiteActions = new Actions(cmd.prerequisiteActions); 64 verifyActions = new Actions(cmd.verifyActions); 65 } 66 67 JPackageCommand createImmutableCopy() { 68 JPackageCommand reply = new JPackageCommand(this); 69 reply.immutable = true; 70 return reply; 71 } 72 73 public JPackageCommand setArgumentValue(String argName, String newValue) { 74 verifyMutable(); 75 76 String prevArg = null; 77 ListIterator<String> it = args.listIterator(); 78 while (it.hasNext()) { 79 String value = it.next(); 80 if (prevArg != null && prevArg.equals(argName)) { 81 if (newValue != null) { 82 it.set(newValue); 83 } else { 84 it.remove(); 85 it.previous(); 86 it.remove(); 87 } 88 return this; 89 } 90 prevArg = value; 91 } 92 93 if (newValue != null) { 94 addArguments(argName, newValue); 95 } 96 97 return this; 98 } 99 100 public JPackageCommand setArgumentValue(String argName, Path newValue) { 101 return setArgumentValue(argName, newValue.toString()); 102 } 103 104 public JPackageCommand removeArgumentWithValue(String argName) { 105 return setArgumentValue(argName, (String)null); 106 } 107 108 public JPackageCommand removeArgument(String argName) { 109 args = args.stream().filter(arg -> !arg.equals(argName)).collect( 110 Collectors.toList()); 111 return this; 112 } 113 114 public boolean hasArgument(String argName) { 115 return args.contains(argName); 116 } 117 118 public <T> T getArgumentValue(String argName, 119 Function<JPackageCommand, T> defaultValueSupplier, 120 Function<String, T> stringConverter) { 121 String prevArg = null; 122 for (String arg : args) { 123 if (prevArg != null && prevArg.equals(argName)) { 124 return stringConverter.apply(arg); 125 } 126 prevArg = arg; 127 } 128 if (defaultValueSupplier != null) { 129 return defaultValueSupplier.apply(this); 130 } 131 return null; 132 } 133 134 public String getArgumentValue(String argName, 135 Function<JPackageCommand, String> defaultValueSupplier) { 136 return getArgumentValue(argName, defaultValueSupplier, v -> v); 137 } 138 139 public <T> T getArgumentValue(String argName, 140 Supplier<T> defaultValueSupplier, 141 Function<String, T> stringConverter) { 142 return getArgumentValue(argName, (unused) -> defaultValueSupplier.get(), 143 stringConverter); 144 } 145 146 public String getArgumentValue(String argName, 147 Supplier<String> defaultValueSupplier) { 148 return getArgumentValue(argName, defaultValueSupplier, v -> v); 149 } 150 151 public String getArgumentValue(String argName) { 152 return getArgumentValue(argName, (Supplier<String>)null); 153 } 154 155 public String[] getAllArgumentValues(String argName) { 156 List<String> values = new ArrayList<>(); 157 String prevArg = null; 158 for (String arg : args) { 159 if (prevArg != null && prevArg.equals(argName)) { 160 values.add(arg); 161 } 162 prevArg = arg; 163 } 164 return values.toArray(String[]::new); 165 } 166 167 public JPackageCommand addArguments(String name, Path value) { 168 return addArguments(name, value.toString()); 169 } 170 171 public boolean isImagePackageType() { 172 return PackageType.IMAGE == getArgumentValue("--type", 173 () -> null, PACKAGE_TYPES::get); 174 } 175 176 public PackageType packageType() { 177 // Don't try to be in sync with jpackage defaults. Keep it simple: 178 // if no `--type` explicitely set on the command line, consider 179 // this is operator's fault. 180 return getArgumentValue("--type", 181 () -> { 182 throw new IllegalStateException("Package type not set"); 183 }, PACKAGE_TYPES::get); 184 } 185 186 public Path outputDir() { 187 return getArgumentValue("--dest", () -> Path.of("."), Path::of); 188 } 189 190 public Path inputDir() { 191 return getArgumentValue("--input", () -> null, Path::of); 192 } 193 194 public String version() { 195 return getArgumentValue("--app-version", () -> "1.0"); 196 } 197 198 public String name() { 199 return getArgumentValue("--name", () -> getArgumentValue("--main-class")); 200 } 201 202 public boolean isRuntime() { 203 return hasArgument("--runtime-image") 204 && !hasArgument("--main-jar") 205 && !hasArgument("--module") 206 && !hasArgument("--app-image"); 207 } 208 209 public JPackageCommand setDefaultInputOutput() { 210 setArgumentValue("--input", TKit.workDir().resolve("input")); 211 setArgumentValue("--dest", TKit.workDir().resolve("output")); 212 return this; 213 } 214 215 public JPackageCommand setFakeRuntime() { 216 verifyMutable(); 217 218 ThrowingConsumer<Path> createBulkFile = path -> { 219 Files.createDirectories(path.getParent()); 220 try (FileOutputStream out = new FileOutputStream(path.toFile())) { 221 byte[] bytes = new byte[4 * 1024]; 222 new SecureRandom().nextBytes(bytes); 223 out.write(bytes); 224 } 225 }; 226 227 addPrerequisiteAction(cmd -> { 228 Path fakeRuntimeDir = TKit.workDir().resolve("fake_runtime"); 229 230 TKit.trace(String.format("Init fake runtime in [%s] directory", 231 fakeRuntimeDir)); 232 233 Files.createDirectories(fakeRuntimeDir); 234 235 if (TKit.isWindows() || TKit.isLinux()) { 236 // Needed to make WindowsAppBundler happy as it copies MSVC dlls 237 // from `bin` directory. 238 // Need to make the code in rpm spec happy as it assumes there is 239 // always something in application image. 240 fakeRuntimeDir.resolve("bin").toFile().mkdir(); 241 } 242 243 if (TKit.isOSX()) { 244 // Make MacAppImageBuilder happy 245 createBulkFile.accept(fakeRuntimeDir.resolve(Path.of( 246 "Contents/Home/lib/jli/libjli.dylib"))); 247 } 248 249 // Mak sure fake runtime takes some disk space. 250 // Package bundles with 0KB size are unexpected and considered 251 // an error by PackageTest. 252 createBulkFile.accept(fakeRuntimeDir.resolve(Path.of("bin", "bulk"))); 253 254 cmd.addArguments("--runtime-image", fakeRuntimeDir); 255 }); 256 257 return this; 258 } 259 260 JPackageCommand addPrerequisiteAction(ThrowingConsumer<JPackageCommand> action) { 261 verifyMutable(); 262 prerequisiteActions.add(action); 263 return this; 264 } 265 266 JPackageCommand addVerifyAction(ThrowingConsumer<JPackageCommand> action) { 267 verifyMutable(); 268 verifyActions.add(action); 269 return this; 270 } 271 272 /** 273 * Shorthand for {@code helloAppImage(null)}. 274 */ 275 public static JPackageCommand helloAppImage() { 276 JavaAppDesc javaAppDesc = null; 277 return helloAppImage(javaAppDesc); 278 } 279 280 /** 281 * Creates new JPackageCommand instance configured with the test Java app. 282 * For the explanation of `javaAppDesc` parameter, see documentation for 283 * #JavaAppDesc.parse() method. 284 * 285 * @param javaAppDesc Java application description 286 * @return this 287 */ 288 public static JPackageCommand helloAppImage(String javaAppDesc) { 289 final JavaAppDesc appDesc; 290 if (javaAppDesc == null) { 291 appDesc = null; 292 } else { 293 appDesc = JavaAppDesc.parse(javaAppDesc); 294 } 295 return helloAppImage(appDesc); 296 } 297 298 public static JPackageCommand helloAppImage(JavaAppDesc javaAppDesc) { 299 JPackageCommand cmd = new JPackageCommand(); 300 cmd.setDefaultInputOutput().setDefaultAppName(); 301 PackageType.IMAGE.applyTo(cmd); 302 new HelloApp(javaAppDesc).addTo(cmd); 303 return cmd; 304 } 305 306 public JPackageCommand setPackageType(PackageType type) { 307 verifyMutable(); 308 type.applyTo(this); 309 return this; 310 } 311 312 JPackageCommand setDefaultAppName() { 313 return addArguments("--name", TKit.getCurrentDefaultAppName()); 314 } 315 316 /** 317 * Returns path to output bundle of configured jpackage command. 318 * 319 * If this is build image command, returns path to application image directory. 320 */ 321 public Path outputBundle() { 322 final String bundleName; 323 if (isImagePackageType()) { 324 String dirName = name(); 325 if (TKit.isOSX()) { 326 dirName = dirName + ".app"; 327 } 328 bundleName = dirName; 329 } else if (TKit.isLinux()) { 330 bundleName = LinuxHelper.getBundleName(this); 331 } else if (TKit.isWindows()) { 332 bundleName = WindowsHelper.getBundleName(this); 333 } else if (TKit.isOSX()) { 334 bundleName = MacHelper.getBundleName(this); 335 } else { 336 throw TKit.throwUnknownPlatformError(); 337 } 338 339 return outputDir().resolve(bundleName); 340 } 341 342 /** 343 * Returns application layout. 344 * 345 * If this is build image command, returns application image layout of the 346 * output bundle relative to output directory. Otherwise returns layout of 347 * installed application relative to the root directory. 348 * 349 * If this command builds Java runtime, not an application, returns 350 * corresponding layout. 351 */ 352 public ApplicationLayout appLayout() { 353 final ApplicationLayout layout; 354 if (isRuntime()) { 355 layout = ApplicationLayout.javaRuntime(); 356 } else { 357 layout = ApplicationLayout.platformAppImage(); 358 } 359 360 if (isImagePackageType()) { 361 return layout.resolveAt(outputBundle()); 362 } 363 364 return layout.resolveAt(appInstallationDirectory()); 365 } 366 367 /** 368 * Returns path to directory where application will be installed or null if 369 * this is build image command. 370 * 371 * E.g. on Linux for app named Foo default the function will return 372 * `/opt/foo` 373 */ 374 public Path appInstallationDirectory() { 375 Path unpackedDir = getArgumentValue(UNPACKED_PATH_ARGNAME, () -> null, 376 Path::of); 377 if (unpackedDir != null) { 378 return unpackedDir; 379 } 380 381 if (isImagePackageType()) { 382 return null; 383 } 384 385 if (TKit.isLinux()) { 386 if (isRuntime()) { 387 // Not fancy, but OK. 388 return Path.of(getArgumentValue("--install-dir", () -> "/opt"), 389 LinuxHelper.getPackageName(this)); 390 } 391 392 // Launcher is in "bin" subfolder of the installation directory. 393 return appLauncherPath().getParent().getParent(); 394 } 395 396 if (TKit.isWindows()) { 397 return WindowsHelper.getInstallationDirectory(this); 398 } 399 400 if (TKit.isOSX()) { 401 return MacHelper.getInstallationDirectory(this); 402 } 403 404 throw TKit.throwUnknownPlatformError(); 405 } 406 407 /** 408 * Returns path to application's Java runtime. 409 * If the command will package Java runtime only, returns correct path to 410 * runtime directory. 411 * 412 * E.g.: 413 * [jpackage --name Foo --type rpm] -> `/opt/foo/lib/runtime` 414 * [jpackage --name Foo --type app-image --dest bar] -> `bar/Foo/lib/runtime` 415 * [jpackage --name Foo --type rpm --runtime-image java] -> `/opt/foo` 416 */ 417 public Path appRuntimeDirectory() { 418 return appLayout().runtimeDirectory(); 419 } 420 421 /** 422 * Returns path for application launcher with the given name. 423 * 424 * E.g.: [jpackage --name Foo --type rpm] -> `/opt/foo/bin/Foo` 425 * [jpackage --name Foo --type app-image --dest bar] -> 426 * `bar/Foo/bin/Foo` 427 * 428 * @param launcherName name of launcher or {@code null} for the main 429 * launcher 430 * 431 * @throws IllegalArgumentException if the command is configured for 432 * packaging Java runtime 433 */ 434 public Path appLauncherPath(String launcherName) { 435 verifyNotRuntime(); 436 if (launcherName == null) { 437 launcherName = name(); 438 } 439 440 if (TKit.isWindows()) { 441 launcherName = launcherName + ".exe"; 442 } 443 444 if (isImagePackageType() || isPackageUnpacked()) { 445 return appLayout().launchersDirectory().resolve(launcherName); 446 } 447 448 if (TKit.isLinux()) { 449 return LinuxHelper.getLauncherPath(this).getParent().resolve(launcherName); 450 } 451 452 return appLayout().launchersDirectory().resolve(launcherName); 453 } 454 455 /** 456 * Shorthand for {@code appLauncherPath(null)}. 457 */ 458 public Path appLauncherPath() { 459 return appLauncherPath(null); 460 } 461 462 private void verifyNotRuntime() { 463 if (isRuntime()) { 464 throw new IllegalArgumentException("Java runtime packaging"); 465 } 466 } 467 468 /** 469 * Returns path to .cfg file of the given application launcher. 470 * 471 * E.g.: 472 * [jpackage --name Foo --type rpm] -> `/opt/foo/lib/app/Foo.cfg` 473 * [jpackage --name Foo --type app-image --dest bar] -> `bar/Foo/lib/app/Foo.cfg` 474 * 475 * @param launcher name of launcher or {@code null} for the main launcher 476 * 477 * @throws IllegalArgumentException if the command is configured for 478 * packaging Java runtime 479 */ 480 public Path appLauncherCfgPath(String launcherName) { 481 verifyNotRuntime(); 482 if (launcherName == null) { 483 launcherName = name(); 484 } 485 return appLayout().appDirectory().resolve(launcherName + ".cfg"); 486 } 487 488 public boolean isFakeRuntime(String msg) { 489 Path runtimeDir = appRuntimeDirectory(); 490 491 final Collection<Path> criticalRuntimeFiles; 492 if (TKit.isWindows()) { 493 criticalRuntimeFiles = WindowsHelper.CRITICAL_RUNTIME_FILES; 494 } else if (TKit.isLinux()) { 495 criticalRuntimeFiles = LinuxHelper.CRITICAL_RUNTIME_FILES; 496 } else if (TKit.isOSX()) { 497 criticalRuntimeFiles = MacHelper.CRITICAL_RUNTIME_FILES; 498 } else { 499 throw TKit.throwUnknownPlatformError(); 500 } 501 502 if (criticalRuntimeFiles.stream().filter( 503 v -> runtimeDir.resolve(v).toFile().exists()).findFirst().orElse( 504 null) == null) { 505 // Fake runtime 506 TKit.trace(String.format( 507 "%s because application runtime directory [%s] is incomplete", 508 msg, runtimeDir)); 509 return true; 510 } 511 return false; 512 } 513 514 public boolean isPackageUnpacked(String msg) { 515 if (isPackageUnpacked()) { 516 TKit.trace(String.format( 517 "%s because package was unpacked, not installed", msg)); 518 return true; 519 } 520 return false; 521 } 522 523 boolean isPackageUnpacked() { 524 return hasArgument(UNPACKED_PATH_ARGNAME); 525 } 526 527 public static void useToolProviderByDefault() { 528 defaultWithToolProvider = true; 529 } 530 531 public static void useExecutableByDefault() { 532 defaultWithToolProvider = false; 533 } 534 535 public JPackageCommand useToolProvider(boolean v) { 536 verifyMutable(); 537 withToolProvider = v; 538 return this; 539 } 540 541 public JPackageCommand saveConsoleOutput(boolean v) { 542 verifyMutable(); 543 saveConsoleOutput = v; 544 return this; 545 } 546 547 public JPackageCommand dumpOutput(boolean v) { 548 verifyMutable(); 549 suppressOutput = !v; 550 return this; 551 } 552 553 public JPackageCommand ignoreDefaultRuntime(boolean v) { 554 verifyMutable(); 555 ignoreDefaultRuntime = v; 556 return this; 557 } 558 559 public JPackageCommand ignoreDefaultVerbose(boolean v) { 560 verifyMutable(); 561 ignoreDefaultVerbose = v; 562 return this; 563 } 564 565 public boolean isWithToolProvider() { 566 return Optional.ofNullable(withToolProvider).orElse( 567 defaultWithToolProvider); 568 } 569 570 public JPackageCommand executePrerequisiteActions() { 571 prerequisiteActions.run(); 572 return this; 573 } 574 575 public JPackageCommand executeVerifyActions() { 576 verifyActions.run(); 577 return this; 578 } 579 580 private Executor createExecutor() { 581 Executor exec = new Executor() 582 .saveOutput(saveConsoleOutput).dumpOutput(!suppressOutput) 583 .addArguments(args); 584 585 if (isWithToolProvider()) { 586 exec.setToolProvider(JavaTool.JPACKAGE); 587 } else { 588 exec.setExecutable(JavaTool.JPACKAGE); 589 } 590 591 return exec; 592 } 593 594 public Executor.Result execute() { 595 return execute(0); 596 } 597 598 public Executor.Result execute(int expectedExitCode) { 599 executePrerequisiteActions(); 600 601 if (isImagePackageType()) { 602 TKit.deleteDirectoryContentsRecursive(outputDir()); 603 } else if (ThrowingSupplier.toSupplier(() -> Files.deleteIfExists( 604 outputBundle())).get()) { 605 TKit.trace( 606 String.format("Deleted [%s] file before running jpackage", 607 outputBundle())); 608 } 609 610 Path resourceDir = getArgumentValue("--resource-dir", () -> null, Path::of); 611 if (resourceDir != null && Files.isDirectory(resourceDir)) { 612 TKit.trace(String.format("Files in [%s] resource dir:", 613 resourceDir)); 614 try (var files = Files.walk(resourceDir, 1)) { 615 files.sequential() 616 .filter(Predicate.not(resourceDir::equals)) 617 .map(path -> String.format("[%s]", path.getFileName())) 618 .forEachOrdered(TKit::trace); 619 TKit.trace("Done"); 620 } catch (IOException ex) { 621 TKit.trace(String.format( 622 "Failed to list files in [%s] resource directory: %s", 623 resourceDir, ex)); 624 } 625 } 626 627 Executor.Result result = new JPackageCommand(this) 628 .adjustArgumentsBeforeExecution() 629 .createExecutor() 630 .execute(expectedExitCode); 631 632 if (result.exitCode == 0) { 633 executeVerifyActions(); 634 } 635 636 return result; 637 } 638 639 public Executor.Result executeAndAssertHelloAppImageCreated() { 640 Executor.Result result = executeAndAssertImageCreated(); 641 HelloApp.executeLauncherAndVerifyOutput(this); 642 return result; 643 } 644 645 public Executor.Result executeAndAssertImageCreated() { 646 Executor.Result result = execute(); 647 assertImageCreated(); 648 return result; 649 } 650 651 public JPackageCommand assertImageCreated() { 652 verifyIsOfType(PackageType.IMAGE); 653 TKit.assertDirectoryExists(appRuntimeDirectory()); 654 655 if (!isRuntime()) { 656 TKit.assertExecutableFileExists(appLauncherPath()); 657 TKit.assertFileExists(appLauncherCfgPath(null)); 658 } 659 660 return this; 661 } 662 663 JPackageCommand setUnpackedPackageLocation(Path path) { 664 verifyIsOfType(PackageType.NATIVE); 665 setArgumentValue(UNPACKED_PATH_ARGNAME, path); 666 return this; 667 } 668 669 private JPackageCommand adjustArgumentsBeforeExecution() { 670 if (!hasArgument("--runtime-image") && !hasArgument("--app-image") && DEFAULT_RUNTIME_IMAGE != null && !ignoreDefaultRuntime) { 671 addArguments("--runtime-image", DEFAULT_RUNTIME_IMAGE); 672 } 673 674 if (!hasArgument("--verbose") && TKit.VERBOSE_JPACKAGE && !ignoreDefaultVerbose) { 675 addArgument("--verbose"); 676 } 677 678 return this; 679 } 680 681 public String getPrintableCommandLine() { 682 return createExecutor().getPrintableCommandLine(); 683 } 684 685 public void verifyIsOfType(Collection<PackageType> types) { 686 verifyIsOfType(types.toArray(PackageType[]::new)); 687 } 688 689 public void verifyIsOfType(PackageType ... types) { 690 final var typesSet = Stream.of(types).collect(Collectors.toSet()); 691 if (!hasArgument("--type")) { 692 if (!isImagePackageType()) { 693 if (TKit.isLinux() && typesSet.equals(PackageType.LINUX)) { 694 return; 695 } 696 697 if (TKit.isWindows() && typesSet.equals(PackageType.WINDOWS)) { 698 return; 699 } 700 701 if (TKit.isOSX() && typesSet.equals(PackageType.MAC)) { 702 return; 703 } 704 } else if (typesSet.equals(Set.of(PackageType.IMAGE))) { 705 return; 706 } 707 } 708 709 if (!typesSet.contains(packageType())) { 710 throw new IllegalArgumentException("Unexpected type"); 711 } 712 } 713 714 public CfgFile readLaunherCfgFile() { 715 return readLaunherCfgFile(null); 716 } 717 718 public CfgFile readLaunherCfgFile(String launcherName) { 719 verifyIsOfType(PackageType.IMAGE); 720 if (isRuntime()) { 721 return null; 722 } 723 return ThrowingFunction.toFunction(CfgFile::readFromFile).apply( 724 appLauncherCfgPath(launcherName)); 725 } 726 727 public static String escapeAndJoin(String... args) { 728 return escapeAndJoin(List.of(args)); 729 } 730 731 public static String escapeAndJoin(List<String> args) { 732 Pattern whitespaceRegexp = Pattern.compile("\\s"); 733 734 return args.stream().map(v -> { 735 String str = v; 736 // Escape quotes. 737 str = str.replace("\"", "\\\""); 738 // Escape backslashes. 739 str = str.replace("\\", "\\\\"); 740 // If value contains whitespace characters, put the value in quotes 741 if (whitespaceRegexp.matcher(str).find()) { 742 str = "\"" + str + "\""; 743 } 744 return str; 745 }).collect(Collectors.joining(" ")); 746 } 747 748 public static Path relativePathInRuntime(JavaTool tool) { 749 Path path = tool.relativePathInJavaHome(); 750 if (TKit.isOSX()) { 751 path = Path.of("Contents/Home").resolve(path); 752 } 753 return path; 754 } 755 756 public static Stream<String> filterOutput(Stream<String> jpackageOutput) { 757 // Skip "WARNING: Using incubator ..." first line of output 758 return jpackageOutput.skip(1); 759 } 760 761 public static List<String> filterOutput(List<String> jpackageOutput) { 762 return filterOutput(jpackageOutput.stream()).collect(Collectors.toList()); 763 } 764 765 @Override 766 protected boolean isMutable() { 767 return !immutable; 768 } 769 770 private final class Actions implements Runnable { 771 Actions() { 772 actions = new ArrayList<>(); 773 } 774 775 Actions(Actions other) { 776 this(); 777 actions.addAll(other.actions); 778 } 779 780 void add(ThrowingConsumer<JPackageCommand> action) { 781 Objects.requireNonNull(action); 782 verifyMutable(); 783 actions.add(new Consumer<JPackageCommand>() { 784 @Override 785 public void accept(JPackageCommand t) { 786 if (!executed) { 787 executed = true; 788 ThrowingConsumer.toConsumer(action).accept(t); 789 } 790 } 791 private boolean executed; 792 }); 793 } 794 795 @Override 796 public void run() { 797 verifyMutable(); 798 actions.forEach(action -> action.accept(JPackageCommand.this)); 799 } 800 801 private final List<Consumer<JPackageCommand>> actions; 802 } 803 804 private Boolean withToolProvider; 805 private boolean saveConsoleOutput; 806 private boolean suppressOutput; 807 private boolean ignoreDefaultRuntime; 808 private boolean ignoreDefaultVerbose; 809 private boolean immutable; 810 private final Actions prerequisiteActions; 811 private final Actions verifyActions; 812 private static boolean defaultWithToolProvider; 813 814 private final static Map<String, PackageType> PACKAGE_TYPES = Functional.identity( 815 () -> { 816 Map<String, PackageType> reply = new HashMap<>(); 817 for (PackageType type : PackageType.values()) { 818 reply.put(type.getName(), type); 819 } 820 return reply; 821 }).get(); 822 823 public final static Path DEFAULT_RUNTIME_IMAGE = Functional.identity(() -> { 824 // Set the property to the path of run-time image to speed up 825 // building app images and platform bundles by avoiding running jlink 826 // The value of the property will be automativcally appended to 827 // jpackage command line if the command line doesn't have 828 // `--runtime-image` parameter set. 829 String val = TKit.getConfigProperty("runtime-image"); 830 if (val != null) { 831 return Path.of(val); 832 } 833 return null; 834 }).get(); 835 836 private final static String UNPACKED_PATH_ARGNAME = "jpt-unpacked-folder"; 837 }