test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java
branchJDK-8200758-branch
changeset 58648 3bf53ffa9ae7
parent 58416 f09bf58c1f17
child 58671 3b578a5976df
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java	Wed Oct 16 09:57:23 2019 -0400
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java	Wed Oct 16 10:32:08 2019 -0400
@@ -31,10 +31,13 @@
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Supplier;
+import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
+import jdk.jpackage.internal.ApplicationLayout;
 import jdk.jpackage.test.Functional.ThrowingConsumer;
+import jdk.jpackage.test.Functional.ThrowingFunction;
 
 /**
  * jpackage command line with prerequisite actions. Prerequisite actions can be
@@ -47,10 +50,20 @@
         actions = new ArrayList<>();
     }
 
+    public JPackageCommand(JPackageCommand cmd) {
+        this();
+        args.addAll(cmd.args);
+        withToolProvider = cmd.withToolProvider;
+        saveConsoleOutput = cmd.saveConsoleOutput;
+        suppressOutput = cmd.suppressOutput;
+        ignoreDefaultRuntime = cmd.ignoreDefaultRuntime;
+        immutable = cmd.immutable;
+        actionsExecuted = cmd.actionsExecuted;
+    }
+
     JPackageCommand createImmutableCopy() {
-        JPackageCommand reply = new JPackageCommand();
+        JPackageCommand reply = new JPackageCommand(this);
         reply.immutable = true;
-        reply.args.addAll(args);
         return reply;
     }
 
@@ -152,6 +165,11 @@
         return addArguments(name, value.toString());
     }
 
+    public boolean isImagePackageType() {
+        return PackageType.IMAGE == getArgumentValue("--package-type",
+                () -> null, PACKAGE_TYPES::get);
+    }
+
     public PackageType packageType() {
         // Don't try to be in sync with jpackage defaults. Keep it simple:
         // if no `--package-type` explicitely set on the command line, consider
@@ -194,8 +212,21 @@
     public JPackageCommand setFakeRuntime() {
         verifyMutable();
 
-        try {
+        ThrowingConsumer<Path> createBulkFile = path -> {
+            Files.createDirectories(path.getParent());
+            try (FileOutputStream out = new FileOutputStream(path.toFile())) {
+                byte[] bytes = new byte[4 * 1024];
+                new SecureRandom().nextBytes(bytes);
+                out.write(bytes);
+            }
+        };
+
+        addAction(cmd -> {
             Path fakeRuntimeDir = TKit.workDir().resolve("fake_runtime");
+
+            TKit.trace(String.format("Init fake runtime in [%s] directory",
+                    fakeRuntimeDir));
+
             Files.createDirectories(fakeRuntimeDir);
 
             if (TKit.isWindows() || TKit.isLinux()) {
@@ -206,22 +237,19 @@
                 fakeRuntimeDir.resolve("bin").toFile().mkdir();
             }
 
-            Path bulk = fakeRuntimeDir.resolve(Path.of("bin", "bulk"));
+            if (TKit.isOSX()) {
+                // Make MacAppImageBuilder happy
+                createBulkFile.accept(fakeRuntimeDir.resolve(Path.of(
+                        "Contents/Home/lib/jli/libjli.dylib")));
+            }
 
             // Mak sure fake runtime takes some disk space.
             // Package bundles with 0KB size are unexpected and considered
             // an error by PackageTest.
-            Files.createDirectories(bulk.getParent());
-            try (FileOutputStream out = new FileOutputStream(bulk.toFile())) {
-                byte[] bytes = new byte[4 * 1024];
-                new SecureRandom().nextBytes(bytes);
-                out.write(bytes);
-            }
+            createBulkFile.accept(fakeRuntimeDir.resolve(Path.of("bin", "bulk")));
 
-            addArguments("--runtime-image", fakeRuntimeDir);
-        } catch (IOException ex) {
-            throw new RuntimeException(ex);
-        }
+            cmd.addArguments("--runtime-image", fakeRuntimeDir);
+        });
 
         return this;
     }
@@ -232,23 +260,37 @@
         return this;
     }
 
+    /**
+     * Shorthand for {@code helloAppImage(null)}.
+     */
     public static JPackageCommand helloAppImage() {
-        return helloAppImage(null);
+        JavaAppDesc javaAppDesc = null;
+        return helloAppImage(javaAppDesc);
     }
 
     /**
      * Creates new JPackageCommand instance configured with the test Java app.
      * For the explanation of `javaAppDesc` parameter, see documentation for
-     * HelloApp.addTo() method.
+     * #JavaAppDesc.parse() method.
      *
      * @param javaAppDesc Java application description
      * @return this
      */
     public static JPackageCommand helloAppImage(String javaAppDesc) {
+        final JavaAppDesc appDesc;
+        if (javaAppDesc == null) {
+            appDesc = null;
+        } else {
+            appDesc = JavaAppDesc.parse(javaAppDesc);
+        }
+        return helloAppImage(appDesc);
+    }
+
+    public static JPackageCommand helloAppImage(JavaAppDesc javaAppDesc) {
         JPackageCommand cmd = new JPackageCommand();
         cmd.setDefaultInputOutput().setDefaultAppName();
         PackageType.IMAGE.applyTo(cmd);
-        HelloApp.addTo(cmd, javaAppDesc);
+        new HelloApp(javaAppDesc).addTo(cmd);
         return cmd;
     }
 
@@ -262,37 +304,70 @@
         return addArguments("--name", TKit.getCurrentDefaultAppName());
     }
 
+    /**
+     * Returns path to output bundle of configured jpackage command.
+     *
+     * If this is build image command, returns path to application image directory.
+     */
     public Path outputBundle() {
-        final PackageType type = packageType();
-        if (PackageType.IMAGE == type) {
-            return null;
-        }
-
-        String bundleName = null;
-        if (PackageType.LINUX.contains(type)) {
+        final String bundleName;
+        if (isImagePackageType()) {
+            String dirName = name();
+            if (TKit.isOSX()) {
+                dirName = dirName + ".app";
+            }
+            bundleName = dirName;
+        } else if (TKit.isLinux()) {
             bundleName = LinuxHelper.getBundleName(this);
-        } else if (PackageType.WINDOWS.contains(type)) {
+        } else if (TKit.isWindows()) {
             bundleName = WindowsHelper.getBundleName(this);
-        } else if (PackageType.MAC.contains(type)) {
+        } else if (TKit.isOSX()) {
             bundleName = MacHelper.getBundleName(this);
+        } else {
+            throw TKit.throwUnknownPlatformError();
         }
 
         return outputDir().resolve(bundleName);
     }
 
     /**
-     * Returns path to directory where application will be installed.
+     * Returns application layout.
+     *
+     * If this is build image command, returns application image layout of the
+     * output bundle relative to output directory. Otherwise returns layout of
+     * installed application relative to the root directory.
+     *
+     * If this command builds Java runtime, not an application, returns
+     * corresponding layout.
+     */
+    public ApplicationLayout appLayout() {
+        final ApplicationLayout layout;
+        if (isRuntime()) {
+            layout = ApplicationLayout.javaRuntime();
+        } else {
+            layout = ApplicationLayout.platformAppImage();
+        }
+
+        if (isImagePackageType()) {
+            return layout.resolveAt(outputBundle());
+        }
+
+        return layout.resolveAt(appInstallationDirectory());
+    }
+
+    /**
+     * Returns path to directory where application will be installed or null if
+     * this is build image command.
      *
      * E.g. on Linux for app named Foo default the function will return
      * `/opt/foo`
      */
     public Path appInstallationDirectory() {
-        final PackageType type = packageType();
-        if (PackageType.IMAGE == type) {
+        if (isImagePackageType()) {
             return null;
         }
 
-        if (PackageType.LINUX.contains(type)) {
+        if (TKit.isLinux()) {
             if (isRuntime()) {
                 // Not fancy, but OK.
                 return Path.of(getArgumentValue("--install-dir", () -> "/opt"),
@@ -300,139 +375,104 @@
             }
 
             // Launcher is in "bin" subfolder of the installation directory.
-            return launcherInstallationPath().getParent().getParent();
+            return appLauncherPath().getParent().getParent();
         }
 
-        if (PackageType.WINDOWS.contains(type)) {
+        if (TKit.isWindows()) {
             return WindowsHelper.getInstallationDirectory(this);
         }
 
-        if (PackageType.MAC.contains(type)) {
+        if (TKit.isOSX()) {
             return MacHelper.getInstallationDirectory(this);
         }
 
-        throw throwUnexpectedPackageTypeError();
-    }
-
-    /**
-     * Returns path where application's Java runtime will be installed.
-     * If the command will package Java run-time only, still returns path to
-     * runtime subdirectory.
-     *
-     * E.g. on Linux for app named `Foo` the function will return
-     * `/opt/foo/runtime`
-     */
-    public Path appRuntimeInstallationDirectory() {
-        if (PackageType.IMAGE == packageType()) {
-            return null;
-        }
-        return appInstallationDirectory().resolve(appRuntimePath(packageType()));
-    }
-
-    /**
-     * Returns path where application launcher will be installed.
-     * If the command will package Java run-time only, still returns path to
-     * application launcher.
-     *
-     * E.g. on Linux for app named Foo default the function will return
-     * `/opt/foo/bin/Foo`
-     */
-    public Path launcherInstallationPath() {
-        final PackageType type = packageType();
-        if (PackageType.IMAGE == type) {
-            return null;
-        }
-
-        if (PackageType.LINUX.contains(type)) {
-            return outputDir().resolve(LinuxHelper.getLauncherPath(this));
-        }
-
-        if (PackageType.WINDOWS.contains(type)) {
-            return appInstallationDirectory().resolve(name() + ".exe");
-        }
-
-        if (PackageType.MAC.contains(type)) {
-            return appInstallationDirectory().resolve(Path.of("Contents", "MacOS", name()));
-        }
-
-        throw throwUnexpectedPackageTypeError();
-    }
-
-    /**
-     * Returns path to application image directory.
-     *
-     * E.g. if --dest is set to `foo` and --name is set to `bar` the function
-     * will return `foo/bar` path on Linux and Windows and `foo/bar.app` on macOS.
-     *
-     * @throws IllegalArgumentException is command is doing platform packaging
-     */
-    public Path appImage() {
-        verifyIsOfType(PackageType.IMAGE);
-        String dirName = name();
-        if (TKit.isOSX()) {
-            dirName = dirName + ".app";
-        }
-        return outputDir().resolve(dirName);
-    }
-
-    /**
-     * Returns path to application launcher relative to image directory.
-     *
-     * E.g. if --name is set to `Foo` the function will return `bin/Foo` path on
-     * Linux, and `Foo.exe` on Windows.
-     *
-     * @throws IllegalArgumentException is command is doing platform packaging
-     */
-    public Path launcherPathInAppImage() {
-        verifyIsOfType(PackageType.IMAGE);
-
-        if (TKit.isLinux()) {
-            return Path.of("bin", name());
-        }
-
-        if (TKit.isOSX()) {
-            return Path.of("Contents", "MacOS", name());
-        }
-
-        if (TKit.isWindows()) {
-            return Path.of(name() + ".exe");
-        }
-
         throw TKit.throwUnknownPlatformError();
     }
 
     /**
-     * Returns path to runtime directory relative to image directory.
+     * Returns path to application's Java runtime.
+     * If the command will package Java runtime only, returns correct path to
+     * runtime directory.
      *
-     * @throws IllegalArgumentException if command is configured for platform
-     * packaging
+     * E.g.:
+     * [jpackage --name Foo --package-type rpm] -> `/opt/foo/lib/runtime`
+     * [jpackage --name Foo --package-type app-image --dest bar] -> `bar/Foo/lib/runtime`
+     * [jpackage --name Foo --package-type rpm --runtime-image java] -> `/opt/foo`
      */
-    public Path appRuntimeDirectoryInAppImage() {
-        verifyIsOfType(PackageType.IMAGE);
-        return appRuntimePath(packageType());
+    public Path appRuntimeDirectory() {
+        return appLayout().runtimeDirectory();
     }
 
-    private static Path appRuntimePath(PackageType type) {
-        if (TKit.isLinux()) {
-            return Path.of("lib/runtime");
-        }
-        if (TKit.isOSX()) {
-            return Path.of("Contents/runtime");
+    /**
+     * Returns path for application launcher with the given name.
+     *
+     * E.g.: [jpackage --name Foo --package-type rpm] -> `/opt/foo/bin/Foo`
+     * [jpackage --name Foo --package-type app-image --dest bar] ->
+     * `bar/Foo/bin/Foo`
+     *
+     * @param launcherName name of launcher or {@code null} for the main
+     * launcher
+     *
+     * @throws IllegalArgumentException if the command is configured for
+     * packaging Java runtime
+     */
+    public Path appLauncherPath(String launcherName) {
+        verifyNotRuntime();
+        if (launcherName == null) {
+            launcherName = name();
         }
 
-        return Path.of("runtime");
+        if (TKit.isWindows()) {
+            launcherName = launcherName + ".exe";
+        }
+
+        if (isImagePackageType()) {
+            return appLayout().launchersDirectory().resolve(launcherName);
+        }
+
+        if (TKit.isLinux()) {
+            LinuxHelper.getLauncherPath(this).getParent().resolve(launcherName);
+        }
+
+        return appLayout().launchersDirectory().resolve(launcherName);
     }
 
-    public boolean isFakeRuntimeInAppImage(String msg) {
-        return isFakeRuntime(appImage().resolve(
-                appRuntimeDirectoryInAppImage()), msg);
+    /**
+     * Shorthand for {@code appLauncherPath(null)}.
+     */
+    public Path appLauncherPath() {
+        return appLauncherPath(null);
+    }
+
+    private void verifyNotRuntime() {
+        if (isRuntime()) {
+            throw new IllegalArgumentException("Java runtime packaging");
+        }
     }
 
-    public boolean isFakeRuntimeInstalled(String msg) {
-        return isFakeRuntime(appRuntimeInstallationDirectory(), msg);
+    /**
+     * Returns path to .cfg file of the given application launcher.
+     *
+     * E.g.:
+     * [jpackage --name Foo --package-type rpm] -> `/opt/foo/lib/app/Foo.cfg`
+     * [jpackage --name Foo --package-type app-image --dest bar] -> `bar/Foo/lib/app/Foo.cfg`
+     *
+     * @param launcher name of launcher or {@code null} for the main launcher
+     *
+     * @throws IllegalArgumentException if the command is configured for
+     * packaging Java runtime
+     */
+    public Path appLauncherCfgPath(String launcherName) {
+        verifyNotRuntime();
+        if (launcherName == null) {
+            launcherName = name();
+        }
+        return appLayout().appDirectory().resolve(launcherName + ".cfg");
     }
 
-    private static boolean isFakeRuntime(Path runtimeDir, String msg) {
+    public boolean isFakeRuntime(String msg) {
+        Path runtimeDir = appRuntimeDirectory();
+
         final Collection<Path> criticalRuntimeFiles;
         if (TKit.isWindows()) {
             criticalRuntimeFiles = WindowsHelper.CRITICAL_RUNTIME_FILES;
@@ -482,12 +522,18 @@
         return this;
     }
 
+    public JPackageCommand ignoreDefaultRuntime(boolean v) {
+        verifyMutable();
+        ignoreDefaultRuntime = v;
+        return this;
+    }
+
     public boolean isWithToolProvider() {
         return Optional.ofNullable(withToolProvider).orElse(
                 defaultWithToolProvider);
     }
 
-    public void executePrerequisiteActions() {
+    public JPackageCommand executePrerequisiteActions() {
         verifyMutable();
         if (!actionsExecuted) {
             actionsExecuted = true;
@@ -495,26 +541,35 @@
                 actions.stream().forEach(r -> r.accept(this));
             }
         }
+        return this;
+    }
+
+    public Executor createExecutor() {
+        verifyMutable();
+        Executor exec = new Executor()
+                .saveOutput(saveConsoleOutput).dumpOutput(!suppressOutput)
+                .addArguments(args);
+
+        if (isWithToolProvider()) {
+            exec.setToolProvider(JavaTool.JPACKAGE);
+        } else {
+            exec.setExecutable(JavaTool.JPACKAGE);
+        }
+
+        return exec;
     }
 
     public Executor.Result execute() {
         executePrerequisiteActions();
 
-        if (packageType() == PackageType.IMAGE) {
+        if (isImagePackageType()) {
             TKit.deleteDirectoryContentsRecursive(outputDir());
         }
 
-        Executor exec = new Executor()
-                .saveOutput(saveConsoleOutput).dumpOutput(!suppressOutput)
-                .addArguments(new JPackageCommand().addArguments(
-                                args).adjustArgumentsBeforeExecution().args);
-
-        if (isWithToolProvider()) {
-            exec.setToolProvider(JavaTool.JPACKAGE.asToolProvider());
-        } else {
-            exec.setExecutable(JavaTool.JPACKAGE);
-        }
-        return exec.execute();
+        return new JPackageCommand(this)
+                .adjustArgumentsBeforeExecution()
+                .createExecutor()
+                .execute();
     }
 
     public JPackageCommand executeAndAssertHelloAppImageCreated() {
@@ -530,15 +585,18 @@
 
     public JPackageCommand assertImageCreated() {
         verifyIsOfType(PackageType.IMAGE);
-        TKit.assertExecutableFileExists(appImage().resolve(
-                launcherPathInAppImage()));
-        TKit.assertDirectoryExists(appImage().resolve(
-                appRuntimeDirectoryInAppImage()));
+        TKit.assertDirectoryExists(appRuntimeDirectory());
+
+        if (!isRuntime()) {
+            TKit.assertExecutableFileExists(appLauncherPath());
+            TKit.assertFileExists(appLauncherCfgPath(null));
+        }
+
         return this;
     }
 
     private JPackageCommand adjustArgumentsBeforeExecution() {
-        if (!hasArgument("--runtime-image") && !hasArgument("--app-image") && DEFAULT_RUNTIME_IMAGE != null) {
+        if (!hasArgument("--runtime-image") && !hasArgument("--app-image") && DEFAULT_RUNTIME_IMAGE != null && !ignoreDefaultRuntime) {
             addArguments("--runtime-image", DEFAULT_RUNTIME_IMAGE);
         }
 
@@ -549,10 +607,6 @@
         return this;
     }
 
-    private static RuntimeException throwUnexpectedPackageTypeError() {
-        throw new IllegalArgumentException("Unexpected package type");
-    }
-
     String getPrintableCommandLine() {
         return new Executor()
                 .setExecutable(JavaTool.JPACKAGE)
@@ -565,9 +619,41 @@
     }
 
     public void verifyIsOfType(PackageType ... types) {
-        if (!Arrays.asList(types).contains(packageType())) {
-            throwUnexpectedPackageTypeError();
+        final Set<PackageType> typesSet = Set.of(types);
+        if (!hasArgument("--package-type")) {
+            if (!isImagePackageType()) {
+                if (TKit.isLinux() && typesSet.equals(PackageType.LINUX)) {
+                    return;
+                }
+
+                if (TKit.isWindows() && typesSet.equals(PackageType.WINDOWS)) {
+                    return;
+                }
+
+                if (TKit.isOSX() && typesSet.equals(PackageType.MAC)) {
+                    return;
+                }
+            } else if (typesSet.equals(Set.of(PackageType.IMAGE))) {
+                return;
+            }
         }
+
+        if (!typesSet.contains(packageType())) {
+            throw new IllegalArgumentException("Unexpected package type");
+        }
+    }
+
+    public CfgFile readLaunherCfgFile() {
+        return readLaunherCfgFile(null);
+    }
+
+    public CfgFile readLaunherCfgFile(String launcherName) {
+        verifyIsOfType(PackageType.IMAGE);
+        if (isRuntime()) {
+            return null;
+        }
+        return ThrowingFunction.toFunction(CfgFile::readFromFile).apply(
+                appLauncherCfgPath(launcherName));
     }
 
     public static String escapeAndJoin(String... args) {
@@ -616,6 +702,7 @@
     private Boolean withToolProvider;
     private boolean saveConsoleOutput;
     private boolean suppressOutput;
+    private boolean ignoreDefaultRuntime;
     private boolean immutable;
     private boolean actionsExecuted;
     private final List<Consumer<JPackageCommand>> actions;