jdk-24/test/jdk/tools/jpackage/helpers/JPackageHelper.java
Alexey Semenyuk 14459b2ad2 8233270: Add support to jtreg helpers to unpack packages
8230933: Default icon is not set for additional launchers

Reviewed-by: herrick, prr, almatvee
2019-12-17 13:56:47 -05:00

680 lines
23 KiB
Java

/*
* Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.BufferedWriter;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.spi.ToolProvider;
public class JPackageHelper {
private static final boolean VERBOSE = false;
private static final String OS = System.getProperty("os.name").toLowerCase();
private static final String JAVA_HOME = System.getProperty("java.home");
public static final String TEST_SRC_ROOT;
public static final String TEST_SRC;
private static final Path BIN_DIR = Path.of(JAVA_HOME, "bin");
private static final Path JPACKAGE;
private static final Path JAVAC;
private static final Path JAR;
private static final Path JLINK;
public static class ModuleArgs {
private final String version;
private final String mainClass;
ModuleArgs(String version, String mainClass) {
this.version = version;
this.mainClass = mainClass;
}
public String getVersion() {
return version;
}
public String getMainClass() {
return mainClass;
}
}
static {
if (OS.startsWith("win")) {
JPACKAGE = BIN_DIR.resolve("jpackage.exe");
JAVAC = BIN_DIR.resolve("javac.exe");
JAR = BIN_DIR.resolve("jar.exe");
JLINK = BIN_DIR.resolve("jlink.exe");
} else {
JPACKAGE = BIN_DIR.resolve("jpackage");
JAVAC = BIN_DIR.resolve("javac");
JAR = BIN_DIR.resolve("jar");
JLINK = BIN_DIR.resolve("jlink");
}
// Figure out test src based on where we called
TEST_SRC = System.getProperty("test.src");
Path root = Path.of(TEST_SRC);
Path apps = Path.of(TEST_SRC, "apps");
if (apps.toFile().exists()) {
// fine - test is at root
} else {
apps = Path.of(TEST_SRC, "..", "apps");
if (apps.toFile().exists()) {
root = apps.getParent().normalize(); // test is 1 level down
} else {
apps = Path.of(TEST_SRC, "..", "..", "apps");
if (apps.toFile().exists()) {
root = apps.getParent().normalize(); // 2 levels down
} else {
apps = Path.of(TEST_SRC, "..", "..", "..", "apps");
if (apps.toFile().exists()) {
root = apps.getParent().normalize(); // 3 levels down
} else {
// if we ever have tests more than three levels
// down we need to add code here
throw new RuntimeException("we should never get here");
}
}
}
}
TEST_SRC_ROOT = root.toString();
}
static final ToolProvider JPACKAGE_TOOL =
ToolProvider.findFirst("jpackage").orElseThrow(
() -> new RuntimeException("jpackage tool not found"));
public static int execute(File out, String... command) throws Exception {
if (VERBOSE) {
System.out.print("Execute command: ");
for (String c : command) {
System.out.print(c);
System.out.print(" ");
}
System.out.println();
}
ProcessBuilder builder = new ProcessBuilder(command);
if (out != null) {
builder.redirectErrorStream(true);
builder.redirectOutput(out);
}
Process process = builder.start();
return process.waitFor();
}
public static Process executeNoWait(File out, String... command) throws Exception {
if (VERBOSE) {
System.out.print("Execute command: ");
for (String c : command) {
System.out.print(c);
System.out.print(" ");
}
System.out.println();
}
ProcessBuilder builder = new ProcessBuilder(command);
if (out != null) {
builder.redirectErrorStream(true);
builder.redirectOutput(out);
}
return builder.start();
}
private static String[] getCommand(String... args) {
String[] command;
if (args == null) {
command = new String[1];
} else {
command = new String[args.length + 1];
}
int index = 0;
command[index] = JPACKAGE.toString();
if (args != null) {
for (String arg : args) {
index++;
command[index] = arg;
}
}
return command;
}
public static void deleteRecursive(File path) throws IOException {
if (!path.exists()) {
return;
}
Path directory = path.toPath();
Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file,
BasicFileAttributes attr) throws IOException {
file.toFile().setWritable(true);
if (OS.startsWith("win")) {
try {
Files.setAttribute(file, "dos:readonly", false);
} catch (Exception ioe) {
// just report and try to contune
System.err.println("IOException: " + ioe);
ioe.printStackTrace(System.err);
}
}
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult preVisitDirectory(Path dir,
BasicFileAttributes attr) throws IOException {
if (OS.startsWith("win")) {
Files.setAttribute(dir, "dos:readonly", false);
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException e)
throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
}
public static void deleteOutputFolder(String output) throws IOException {
File outputFolder = new File(output);
System.out.println("deleteOutputFolder: " + outputFolder.getAbsolutePath());
try {
deleteRecursive(outputFolder);
} catch (IOException ioe) {
System.err.println("IOException: " + ioe);
ioe.printStackTrace(System.err);
deleteRecursive(outputFolder);
}
}
public static String executeCLI(boolean retValZero, String... args) throws Exception {
int retVal;
File outfile = new File("output.log");
String[] command = getCommand(args);
try {
retVal = execute(outfile, command);
} catch (Exception ex) {
if (outfile.exists()) {
System.err.println(Files.readString(outfile.toPath()));
}
throw ex;
}
String output = Files.readString(outfile.toPath());
if (retValZero) {
if (retVal != 0) {
System.err.println("command run:");
for (String s : command) { System.err.println(s); }
System.err.println("command output:");
System.err.println(output);
throw new AssertionError("jpackage exited with error: " + retVal);
}
} else {
if (retVal == 0) {
System.err.println(output);
throw new AssertionError("jpackage exited without error: " + retVal);
}
}
if (VERBOSE) {
System.out.println("output =");
System.out.println(output);
}
return output;
}
public static String executeToolProvider(boolean retValZero, String... args) throws Exception {
StringWriter writer = new StringWriter();
PrintWriter pw = new PrintWriter(writer);
int retVal = JPACKAGE_TOOL.run(pw, pw, args);
String output = writer.toString();
if (retValZero) {
if (retVal != 0) {
System.err.println(output);
throw new AssertionError("jpackage exited with error: " + retVal);
}
} else {
if (retVal == 0) {
System.err.println(output);
throw new AssertionError("jpackage exited without error");
}
}
if (VERBOSE) {
System.out.println("output =");
System.out.println(output);
}
return output;
}
public static boolean isWindows() {
return (OS.contains("win"));
}
public static boolean isOSX() {
return (OS.contains("mac"));
}
public static boolean isLinux() {
return ((OS.contains("nix") || OS.contains("nux")));
}
public static void createHelloImageJar(String inputDir) throws Exception {
createJar(false, "Hello", "image", inputDir);
}
public static void createHelloImageJar() throws Exception {
createJar(false, "Hello", "image", "input");
}
public static void createHelloImageJarWithMainClass() throws Exception {
createJar(true, "Hello", "image", "input");
}
public static void createHelloInstallerJar() throws Exception {
createJar(false, "Hello", "installer", "input");
}
public static void createHelloInstallerJarWithMainClass() throws Exception {
createJar(true, "Hello", "installer", "input");
}
private static void createJar(boolean mainClassAttribute, String name,
String testType, String inputDir) throws Exception {
int retVal;
File input = new File(inputDir);
if (!input.exists()) {
input.mkdirs();
}
Path src = Path.of(TEST_SRC_ROOT + File.separator + "apps"
+ File.separator + testType + File.separator + name + ".java");
Path dst = Path.of(name + ".java");
if (dst.toFile().exists()) {
Files.delete(dst);
}
Files.copy(src, dst);
File javacLog = new File("javac.log");
try {
retVal = execute(javacLog, JAVAC.toString(), name + ".java");
} catch (Exception ex) {
if (javacLog.exists()) {
System.err.println(Files.readString(javacLog.toPath()));
}
throw ex;
}
if (retVal != 0) {
if (javacLog.exists()) {
System.err.println(Files.readString(javacLog.toPath()));
}
throw new AssertionError("javac exited with error: " + retVal);
}
File jarLog = new File("jar.log");
try {
List<String> args = new ArrayList<>();
args.add(JAR.toString());
args.add("-c");
args.add("-v");
args.add("-f");
args.add(inputDir + File.separator + name.toLowerCase() + ".jar");
if (mainClassAttribute) {
args.add("-e");
args.add(name);
}
args.add(name + ".class");
retVal = execute(jarLog, args.stream().toArray(String[]::new));
} catch (Exception ex) {
if (jarLog.exists()) {
System.err.println(Files.readString(jarLog.toPath()));
}
throw ex;
}
if (retVal != 0) {
if (jarLog.exists()) {
System.err.println(Files.readString(jarLog.toPath()));
}
throw new AssertionError("jar exited with error: " + retVal);
}
}
public static void createHelloModule() throws Exception {
createModule("Hello.java", "input", "hello", null, true);
}
public static void createHelloModule(ModuleArgs moduleArgs) throws Exception {
createModule("Hello.java", "input", "hello", moduleArgs, true);
}
private static void createModule(String javaFile, String inputDir, String aName,
ModuleArgs moduleArgs, boolean createModularJar) throws Exception {
int retVal;
File input = new File(inputDir);
if (!input.exists()) {
input.mkdir();
}
File module = new File("module" + File.separator + "com." + aName);
if (!module.exists()) {
module.mkdirs();
}
File javacLog = new File("javac.log");
try {
List<String> args = new ArrayList<>();
args.add(JAVAC.toString());
args.add("-d");
args.add("module" + File.separator + "com." + aName);
args.add(TEST_SRC_ROOT + File.separator + "apps" + File.separator
+ "com." + aName + File.separator + "module-info.java");
args.add(TEST_SRC_ROOT + File.separator + "apps"
+ File.separator + "com." + aName + File.separator + "com"
+ File.separator + aName + File.separator + javaFile);
retVal = execute(javacLog, args.stream().toArray(String[]::new));
} catch (Exception ex) {
if (javacLog.exists()) {
System.err.println(Files.readString(javacLog.toPath()));
}
throw ex;
}
if (retVal != 0) {
if (javacLog.exists()) {
System.err.println(Files.readString(javacLog.toPath()));
}
throw new AssertionError("javac exited with error: " + retVal);
}
if (createModularJar) {
File jarLog = new File("jar.log");
try {
List<String> args = new ArrayList<>();
args.add(JAR.toString());
args.add("--create");
args.add("--file");
args.add(inputDir + File.separator + "com." + aName + ".jar");
if (moduleArgs != null) {
if (moduleArgs.getVersion() != null) {
args.add("--module-version");
args.add(moduleArgs.getVersion());
}
if (moduleArgs.getMainClass()!= null) {
args.add("--main-class");
args.add(moduleArgs.getMainClass());
}
}
args.add("-C");
args.add("module" + File.separator + "com." + aName);
args.add(".");
retVal = execute(jarLog, args.stream().toArray(String[]::new));
} catch (Exception ex) {
if (jarLog.exists()) {
System.err.println(Files.readString(jarLog.toPath()));
}
throw ex;
}
if (retVal != 0) {
if (jarLog.exists()) {
System.err.println(Files.readString(jarLog.toPath()));
}
throw new AssertionError("jar exited with error: " + retVal);
}
}
}
public static void createRuntime() throws Exception {
List<String> moreArgs = new ArrayList<>();
createRuntime(moreArgs);
}
public static void createRuntime(List<String> moreArgs) throws Exception {
int retVal;
File jlinkLog = new File("jlink.log");
try {
List<String> args = new ArrayList<>();
args.add(JLINK.toString());
args.add("--output");
args.add("runtime");
args.add("--add-modules");
args.add("java.base");
args.addAll(moreArgs);
retVal = execute(jlinkLog, args.stream().toArray(String[]::new));
} catch (Exception ex) {
if (jlinkLog.exists()) {
System.err.println(Files.readString(jlinkLog.toPath()));
}
throw ex;
}
if (retVal != 0) {
if (jlinkLog.exists()) {
System.err.println(Files.readString(jlinkLog.toPath()));
}
throw new AssertionError("jlink exited with error: " + retVal);
}
}
public static String listToArgumentsMap(List<String> arguments, boolean toolProvider) {
if (arguments.isEmpty()) {
return "";
}
String argsStr = "";
for (int i = 0; i < arguments.size(); i++) {
String arg = arguments.get(i);
argsStr += quote(arg, toolProvider);
if ((i + 1) != arguments.size()) {
argsStr += " ";
}
}
if (!toolProvider && isWindows()) {
if (argsStr.contains(" ")) {
if (argsStr.contains("\"")) {
argsStr = escapeQuote(argsStr, toolProvider);
}
argsStr = "\"" + argsStr + "\"";
}
}
return argsStr;
}
public static String[] cmdWithAtFilename(String [] cmd, int ndx, int len)
throws IOException {
ArrayList<String> newAList = new ArrayList<>();
String fileString = null;
for (int i=0; i<cmd.length; i++) {
if (i == ndx) {
newAList.add("@argfile.cmds");
fileString = cmd[i];
} else if (i > ndx && i < ndx + len) {
fileString += " " + cmd[i];
} else {
newAList.add(cmd[i]);
}
}
if (fileString != null) {
Path path = new File("argfile.cmds").toPath();
try (BufferedWriter bw = Files.newBufferedWriter(path);
PrintWriter out = new PrintWriter(bw)) {
out.println(fileString);
}
}
return newAList.toArray(new String[0]);
}
public static String [] splitAndFilter(String output) {
if (output == null) {
return null;
}
return Stream.of(output.split("\\R"))
.filter(str -> !str.startsWith("Picked up"))
.filter(str -> !str.startsWith("WARNING: Using incubator"))
.filter(str -> !str.startsWith("hello: "))
.collect(Collectors.toList()).toArray(String[]::new);
}
private static String quote(String in, boolean toolProvider) {
if (in == null) {
return null;
}
if (in.isEmpty()) {
return "";
}
if (!in.contains("=")) {
// Not a property
if (in.contains(" ")) {
in = escapeQuote(in, toolProvider);
return "\"" + in + "\"";
}
return in;
}
if (!in.contains(" ")) {
return in; // No need to quote
}
int paramIndex = in.indexOf("=");
if (paramIndex <= 0) {
return in; // Something wrong, just skip quoting
}
String param = in.substring(0, paramIndex);
String value = in.substring(paramIndex + 1);
if (value.length() == 0) {
return in; // No need to quote
}
value = escapeQuote(value, toolProvider);
return param + "=" + "\"" + value + "\"";
}
private static String escapeQuote(String in, boolean toolProvider) {
if (in == null) {
return null;
}
if (in.isEmpty()) {
return "";
}
if (in.contains("\"")) {
// Use code points to preserve non-ASCII chars
StringBuilder sb = new StringBuilder();
int codeLen = in.codePointCount(0, in.length());
for (int i = 0; i < codeLen; i++) {
int code = in.codePointAt(i);
// Note: No need to escape '\' on Linux or OS X
// jpackage expects us to pass arguments and properties with
// quotes and spaces as a map
// with quotes being escaped with additional \ for
// internal quotes.
// So if we want two properties below:
// -Djnlp.Prop1=Some "Value" 1
// -Djnlp.Prop2=Some Value 2
// jpackage will need:
// "-Djnlp.Prop1=\"Some \\"Value\\" 1\" -Djnlp.Prop2=\"Some Value 2\""
// but since we using ProcessBuilder to run jpackage we will need to escape
// our escape symbols as well, so we will need to pass string below to ProcessBuilder:
// "-Djnlp.Prop1=\\\"Some \\\\\\\"Value\\\\\\\" 1\\\" -Djnlp.Prop2=\\\"Some Value 2\\\""
switch (code) {
case '"':
// " -> \" -> \\\"
if (i == 0 || in.codePointAt(i - 1) != '\\') {
sb.appendCodePoint('\\');
sb.appendCodePoint(code);
}
break;
case '\\':
// We need to escape already escaped symbols as well
if ((i + 1) < codeLen) {
int nextCode = in.codePointAt(i + 1);
if (nextCode == '"') {
// \" -> \\\"
sb.appendCodePoint('\\');
sb.appendCodePoint('\\');
sb.appendCodePoint('\\');
sb.appendCodePoint(nextCode);
} else {
sb.appendCodePoint('\\');
sb.appendCodePoint(code);
}
} else {
sb.appendCodePoint(code);
}
break;
default:
sb.appendCodePoint(code);
break;
}
}
return sb.toString();
}
return in;
}
}