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. 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 24 import java.io.File; 25 import java.io.IOException; 26 import java.io.PrintWriter; 27 import java.io.StringWriter; 28 import java.io.BufferedWriter; 29 import java.nio.file.FileVisitResult; 30 31 import java.nio.file.Files; 32 import java.nio.file.Path; 33 import java.nio.file.SimpleFileVisitor; 34 import java.nio.file.attribute.BasicFileAttributes; 35 import java.util.ArrayList; 36 import java.util.List; 37 import java.util.Arrays; 38 39 import java.util.spi.ToolProvider; 40 41 public class JPackageHelper { 42 43 private static final boolean VERBOSE = false; 44 private static final String OS = System.getProperty("os.name").toLowerCase(); 45 private static final String JAVA_HOME = System.getProperty("java.home"); 46 public static final String TEST_SRC_ROOT; 47 public static final String TEST_SRC; 48 private static final Path BIN_DIR = Path.of(JAVA_HOME, "bin"); 49 private static final Path JPACKAGE; 50 private static final Path JAVAC; 51 private static final Path JAR; 52 private static final Path JLINK; 53 54 static { 55 if (OS.startsWith("win")) { 56 JPACKAGE = BIN_DIR.resolve("jpackage.exe"); 57 JAVAC = BIN_DIR.resolve("javac.exe"); 58 JAR = BIN_DIR.resolve("jar.exe"); 59 JLINK = BIN_DIR.resolve("jlink.exe"); 60 } else { 61 JPACKAGE = BIN_DIR.resolve("jpackage"); 62 JAVAC = BIN_DIR.resolve("javac"); 63 JAR = BIN_DIR.resolve("jar"); 64 JLINK = BIN_DIR.resolve("jlink"); 65 } 66 67 // Figure out test src based on where we called 68 TEST_SRC = System.getProperty("test.src"); 69 Path root = Path.of(TEST_SRC); 70 Path apps = Path.of(TEST_SRC, "apps"); 71 if (apps.toFile().exists()) { 72 // fine - test is at root 73 } else { 74 apps = Path.of(TEST_SRC, "..", "apps"); 75 if (apps.toFile().exists()) { 76 root = apps.getParent().normalize(); // test is 1 level down 77 } else { 78 apps = Path.of(TEST_SRC, "..", "..", "apps"); 79 if (apps.toFile().exists()) { 80 root = apps.getParent().normalize(); // 2 levels down 81 } else { 82 apps = Path.of(TEST_SRC, "..", "..", "..", "apps"); 83 if (apps.toFile().exists()) { 84 root = apps.getParent().normalize(); // 3 levels down 85 } else { 86 // if we ever have tests more than three levels 87 // down we need to add code here 88 throw new RuntimeException("we should never get here"); 89 } 90 } 91 } 92 } 93 TEST_SRC_ROOT = root.toString(); 94 } 95 96 static final ToolProvider JPACKAGE_TOOL = 97 ToolProvider.findFirst("jpackage").orElseThrow( 98 () -> new RuntimeException("jpackage tool not found")); 99 100 public static int execute(File out, String... command) throws Exception { 101 if (VERBOSE) { 102 System.out.print("Execute command: "); 103 for (String c : command) { 104 System.out.print(c); 105 System.out.print(" "); 106 } 107 System.out.println(); 108 } 109 110 ProcessBuilder builder = new ProcessBuilder(command); 111 if (out != null) { 112 builder.redirectErrorStream(true); 113 builder.redirectOutput(out); 114 } 115 116 Process process = builder.start(); 117 return process.waitFor(); 118 } 119 120 public static Process executeNoWait(File out, String... command) throws Exception { 121 if (VERBOSE) { 122 System.out.print("Execute command: "); 123 for (String c : command) { 124 System.out.print(c); 125 System.out.print(" "); 126 } 127 System.out.println(); 128 } 129 130 ProcessBuilder builder = new ProcessBuilder(command); 131 if (out != null) { 132 builder.redirectErrorStream(true); 133 builder.redirectOutput(out); 134 } 135 136 return builder.start(); 137 } 138 139 private static String[] getCommand(String... args) { 140 String[] command; 141 if (args == null) { 142 command = new String[1]; 143 } else { 144 command = new String[args.length + 1]; 145 } 146 147 int index = 0; 148 command[index] = JPACKAGE.toString(); 149 150 if (args != null) { 151 for (String arg : args) { 152 index++; 153 command[index] = arg; 154 } 155 } 156 157 return command; 158 } 159 160 public static void deleteRecursive(File path) throws IOException { 161 if (!path.exists()) { 162 return; 163 } 164 165 Path directory = path.toPath(); 166 Files.walkFileTree(directory, new SimpleFileVisitor<Path>() { 167 @Override 168 public FileVisitResult visitFile(Path file, 169 BasicFileAttributes attr) throws IOException { 170 file.toFile().setWritable(true); 171 if (OS.startsWith("win")) { 172 try { 173 Files.setAttribute(file, "dos:readonly", false); 174 } catch (Exception ioe) { 175 // just report and try to contune 176 System.err.println("IOException: " + ioe); 177 ioe.printStackTrace(System.err); 178 } 179 } 180 Files.delete(file); 181 return FileVisitResult.CONTINUE; 182 } 183 184 @Override 185 public FileVisitResult preVisitDirectory(Path dir, 186 BasicFileAttributes attr) throws IOException { 187 if (OS.startsWith("win")) { 188 Files.setAttribute(dir, "dos:readonly", false); 189 } 190 return FileVisitResult.CONTINUE; 191 } 192 193 @Override 194 public FileVisitResult postVisitDirectory(Path dir, IOException e) 195 throws IOException { 196 Files.delete(dir); 197 return FileVisitResult.CONTINUE; 198 } 199 }); 200 } 201 202 public static void deleteOutputFolder(String output) throws IOException { 203 File outputFolder = new File(output); 204 System.out.println("deleteOutputFolder: " + outputFolder.getAbsolutePath()); 205 try { 206 deleteRecursive(outputFolder); 207 } catch (IOException ioe) { 208 System.err.println("IOException: " + ioe); 209 ioe.printStackTrace(System.err); 210 deleteRecursive(outputFolder); 211 } 212 } 213 214 public static String executeCLI(boolean retValZero, String... args) throws Exception { 215 int retVal; 216 File outfile = new File("output.log"); 217 String[] command = getCommand(args); 218 try { 219 retVal = execute(outfile, command); 220 } catch (Exception ex) { 221 if (outfile.exists()) { 222 System.err.println(Files.readString(outfile.toPath())); 223 } 224 throw ex; 225 } 226 227 String output = Files.readString(outfile.toPath()); 228 if (retValZero) { 229 if (retVal != 0) { 230 System.err.println("command run:"); 231 for (String s : command) { System.err.println(s); } 232 System.err.println("command output:"); 233 System.err.println(output); 234 throw new AssertionError("jpackage exited with error: " + retVal); 235 } 236 } else { 237 if (retVal == 0) { 238 System.err.println(output); 239 throw new AssertionError("jpackage exited without error: " + retVal); 240 } 241 } 242 243 if (VERBOSE) { 244 System.out.println("output ="); 245 System.out.println(output); 246 } 247 248 return output; 249 } 250 251 public static String executeToolProvider(boolean retValZero, String... args) throws Exception { 252 StringWriter writer = new StringWriter(); 253 PrintWriter pw = new PrintWriter(writer); 254 int retVal = JPACKAGE_TOOL.run(pw, pw, args); 255 String output = writer.toString(); 256 257 if (retValZero) { 258 if (retVal != 0) { 259 System.err.println(output); 260 throw new AssertionError("jpackage exited with error: " + retVal); 261 } 262 } else { 263 if (retVal == 0) { 264 System.err.println(output); 265 throw new AssertionError("jpackage exited without error"); 266 } 267 } 268 269 if (VERBOSE) { 270 System.out.println("output ="); 271 System.out.println(output); 272 } 273 274 return output; 275 } 276 277 public static boolean isWindows() { 278 return (OS.contains("win")); 279 } 280 281 public static boolean isOSX() { 282 return (OS.contains("mac")); 283 } 284 285 public static boolean isLinux() { 286 return ((OS.contains("nix") || OS.contains("nux"))); 287 } 288 289 public static void createHelloImageJar(String inputDir) throws Exception { 290 createJar(false, "Hello", "image", inputDir); 291 } 292 293 public static void createHelloImageJar() throws Exception { 294 createJar(false, "Hello", "image", "input"); 295 } 296 297 public static void createHelloImageJarWithMainClass() throws Exception { 298 createJar(true, "Hello", "image", "input"); 299 } 300 301 public static void createHelloInstallerJar() throws Exception { 302 createJar(false, "Hello", "installer", "input"); 303 } 304 305 public static void createHelloInstallerJarWithMainClass() throws Exception { 306 createJar(true, "Hello", "installer", "input"); 307 } 308 309 private static void createJar(boolean mainClassAttribute, String name, 310 String testType, String inputDir) throws Exception { 311 int retVal; 312 313 File input = new File(inputDir); 314 if (!input.exists()) { 315 input.mkdirs(); 316 } 317 318 Path src = Path.of(TEST_SRC_ROOT + File.separator + "apps" 319 + File.separator + testType + File.separator + name + ".java"); 320 Path dst = Path.of(name + ".java"); 321 322 if (dst.toFile().exists()) { 323 Files.delete(dst); 324 } 325 Files.copy(src, dst); 326 327 328 File javacLog = new File("javac.log"); 329 try { 330 retVal = execute(javacLog, JAVAC.toString(), name + ".java"); 331 } catch (Exception ex) { 332 if (javacLog.exists()) { 333 System.err.println(Files.readString(javacLog.toPath())); 334 } 335 throw ex; 336 } 337 338 if (retVal != 0) { 339 if (javacLog.exists()) { 340 System.err.println(Files.readString(javacLog.toPath())); 341 } 342 throw new AssertionError("javac exited with error: " + retVal); 343 } 344 345 File jarLog = new File("jar.log"); 346 try { 347 List<String> args = new ArrayList<>(); 348 args.add(JAR.toString()); 349 args.add("-c"); 350 args.add("-v"); 351 args.add("-f"); 352 args.add(inputDir + File.separator + name.toLowerCase() + ".jar"); 353 if (mainClassAttribute) { 354 args.add("-e"); 355 args.add(name); 356 } 357 args.add(name + ".class"); 358 retVal = execute(jarLog, args.stream().toArray(String[]::new)); 359 } catch (Exception ex) { 360 if (jarLog.exists()) { 361 System.err.println(Files.readString(jarLog.toPath())); 362 } 363 throw ex; 364 } 365 366 if (retVal != 0) { 367 if (jarLog.exists()) { 368 System.err.println(Files.readString(jarLog.toPath())); 369 } 370 throw new AssertionError("jar exited with error: " + retVal); 371 } 372 } 373 374 public static void createHelloModule() throws Exception { 375 createModule("Hello.java", "input", "hello", null, true); 376 } 377 378 public static void createHelloModule(String version) throws Exception { 379 createModule("Hello.java", "input", "hello", version, true); 380 } 381 382 public static void createOtherModule() throws Exception { 383 createModule("Other.java", "input-other", "other", null, false); 384 } 385 386 private static void createModule(String javaFile, String inputDir, 387 String aName, String version, boolean createModularJar) throws Exception { 388 int retVal; 389 390 File input = new File(inputDir); 391 if (!input.exists()) { 392 input.mkdir(); 393 } 394 395 File module = new File("module" + File.separator + "com." + aName); 396 if (!module.exists()) { 397 module.mkdirs(); 398 } 399 400 File javacLog = new File("javac.log"); 401 try { 402 List<String> args = new ArrayList<>(); 403 args.add(JAVAC.toString()); 404 args.add("-d"); 405 args.add("module" + File.separator + "com." + aName); 406 args.add(TEST_SRC_ROOT + File.separator + "apps" + File.separator 407 + "com." + aName + File.separator + "module-info.java"); 408 args.add(TEST_SRC_ROOT + File.separator + "apps" 409 + File.separator + "com." + aName + File.separator + "com" 410 + File.separator + aName + File.separator + javaFile); 411 retVal = execute(javacLog, args.stream().toArray(String[]::new)); 412 } catch (Exception ex) { 413 if (javacLog.exists()) { 414 System.err.println(Files.readString(javacLog.toPath())); 415 } 416 throw ex; 417 } 418 419 if (retVal != 0) { 420 if (javacLog.exists()) { 421 System.err.println(Files.readString(javacLog.toPath())); 422 } 423 throw new AssertionError("javac exited with error: " + retVal); 424 } 425 426 if (createModularJar) { 427 File jarLog = new File("jar.log"); 428 try { 429 List<String> args = new ArrayList<>(); 430 args.add(JAR.toString()); 431 args.add("--create"); 432 args.add("--file"); 433 args.add(inputDir + File.separator + "com." + aName + ".jar"); 434 if (version != null) { 435 args.add("--module-version"); 436 args.add(version); 437 } 438 args.add("-C"); 439 args.add("module" + File.separator + "com." + aName); 440 args.add("."); 441 442 retVal = execute(jarLog, args.stream().toArray(String[]::new)); 443 } catch (Exception ex) { 444 if (jarLog.exists()) { 445 System.err.println(Files.readString(jarLog.toPath())); 446 } 447 throw ex; 448 } 449 450 if (retVal != 0) { 451 if (jarLog.exists()) { 452 System.err.println(Files.readString(jarLog.toPath())); 453 } 454 throw new AssertionError("jar exited with error: " + retVal); 455 } 456 } 457 } 458 459 public static void createRuntime() throws Exception { 460 List<String> moreArgs = new ArrayList<>(); 461 createRuntime(moreArgs); 462 } 463 464 public static void createRuntime(List<String> moreArgs) throws Exception { 465 int retVal; 466 467 File jlinkLog = new File("jlink.log"); 468 try { 469 List<String> args = new ArrayList<>(); 470 args.add(JLINK.toString()); 471 args.add("--output"); 472 args.add("runtime"); 473 args.add("--add-modules"); 474 args.add("java.base"); 475 args.addAll(moreArgs); 476 477 retVal = execute(jlinkLog, args.stream().toArray(String[]::new)); 478 } catch (Exception ex) { 479 if (jlinkLog.exists()) { 480 System.err.println(Files.readString(jlinkLog.toPath())); 481 } 482 throw ex; 483 } 484 485 if (retVal != 0) { 486 if (jlinkLog.exists()) { 487 System.err.println(Files.readString(jlinkLog.toPath())); 488 } 489 throw new AssertionError("jlink exited with error: " + retVal); 490 } 491 } 492 493 public static String listToArgumentsMap(List<String> arguments, boolean toolProvider) { 494 if (arguments.isEmpty()) { 495 return ""; 496 } 497 498 String argsStr = ""; 499 for (int i = 0; i < arguments.size(); i++) { 500 String arg = arguments.get(i); 501 argsStr += quote(arg, toolProvider); 502 if ((i + 1) != arguments.size()) { 503 argsStr += " "; 504 } 505 } 506 507 if (!toolProvider && isWindows()) { 508 if (argsStr.contains(" ")) { 509 if (argsStr.contains("\"")) { 510 argsStr = escapeQuote(argsStr, toolProvider); 511 } 512 argsStr = "\"" + argsStr + "\""; 513 } 514 } 515 return argsStr; 516 } 517 518 public static String[] cmdWithAtFilename(String [] cmd, int ndx, int len) 519 throws IOException { 520 ArrayList<String> newAList = new ArrayList<>(); 521 String fileString = null; 522 for (int i=0; i<cmd.length; i++) { 523 if (i == ndx) { 524 newAList.add("@argfile.cmds"); 525 fileString = cmd[i]; 526 } else if (i > ndx && i < ndx + len) { 527 fileString += " " + cmd[i]; 528 } else { 529 newAList.add(cmd[i]); 530 } 531 } 532 if (fileString != null) { 533 Path path = new File("argfile.cmds").toPath(); 534 try (BufferedWriter bw = Files.newBufferedWriter(path); 535 PrintWriter out = new PrintWriter(bw)) { 536 out.println(fileString); 537 } 538 } 539 return newAList.toArray(new String[0]); 540 } 541 542 public static String [] splitAndFilter(String output) { 543 if (output == null) { 544 return null; 545 } 546 547 String[] result = output.split("\n"); 548 if (result == null || result.length == 0) { 549 return result; 550 } 551 552 List<String> origList = new ArrayList(Arrays.asList(result)); 553 List<String> newlist = new ArrayList(); 554 origList.stream().filter((str) -> 555 (!str.startsWith("Picked up"))).forEachOrdered((str) -> { 556 newlist.add(str); 557 }); 558 559 return newlist.toArray(new String[newlist.size()]); 560 } 561 562 private static String quote(String in, boolean toolProvider) { 563 if (in == null) { 564 return null; 565 } 566 567 if (in.isEmpty()) { 568 return ""; 569 } 570 571 if (!in.contains("=")) { 572 // Not a property 573 if (in.contains(" ")) { 574 in = escapeQuote(in, toolProvider); 575 return "\"" + in + "\""; 576 } 577 return in; 578 } 579 580 if (!in.contains(" ")) { 581 return in; // No need to quote 582 } 583 584 int paramIndex = in.indexOf("="); 585 if (paramIndex <= 0) { 586 return in; // Something wrong, just skip quoting 587 } 588 589 String param = in.substring(0, paramIndex); 590 String value = in.substring(paramIndex + 1); 591 592 if (value.length() == 0) { 593 return in; // No need to quote 594 } 595 596 value = escapeQuote(value, toolProvider); 597 598 return param + "=" + "\"" + value + "\""; 599 } 600 601 private static String escapeQuote(String in, boolean toolProvider) { 602 if (in == null) { 603 return null; 604 } 605 606 if (in.isEmpty()) { 607 return ""; 608 } 609 610 if (in.contains("\"")) { 611 // Use code points to preserve non-ASCII chars 612 StringBuilder sb = new StringBuilder(); 613 int codeLen = in.codePointCount(0, in.length()); 614 for (int i = 0; i < codeLen; i++) { 615 int code = in.codePointAt(i); 616 // Note: No need to escape '\' on Linux or OS X 617 // jpackage expects us to pass arguments and properties with 618 // quotes and spaces as a map 619 // with quotes being escaped with additional \ for 620 // internal quotes. 621 // So if we want two properties below: 622 // -Djnlp.Prop1=Some "Value" 1 623 // -Djnlp.Prop2=Some Value 2 624 // jpackage will need: 625 // "-Djnlp.Prop1=\"Some \\"Value\\" 1\" -Djnlp.Prop2=\"Some Value 2\"" 626 // but since we using ProcessBuilder to run jpackage we will need to escape 627 // our escape symbols as well, so we will need to pass string below to ProcessBuilder: 628 // "-Djnlp.Prop1=\\\"Some \\\\\\\"Value\\\\\\\" 1\\\" -Djnlp.Prop2=\\\"Some Value 2\\\"" 629 switch (code) { 630 case '"': 631 // " -> \" -> \\\" 632 if (i == 0 || in.codePointAt(i - 1) != '\\') { 633 sb.appendCodePoint('\\'); 634 sb.appendCodePoint(code); 635 } 636 break; 637 case '\\': 638 // We need to escape already escaped symbols as well 639 if ((i + 1) < codeLen) { 640 int nextCode = in.codePointAt(i + 1); 641 if (nextCode == '"') { 642 // \" -> \\\" 643 sb.appendCodePoint('\\'); 644 sb.appendCodePoint('\\'); 645 sb.appendCodePoint('\\'); 646 sb.appendCodePoint(nextCode); 647 } else { 648 sb.appendCodePoint('\\'); 649 sb.appendCodePoint(code); 650 } 651 } else { 652 sb.appendCodePoint(code); 653 } 654 break; 655 default: 656 sb.appendCodePoint(code); 657 break; 658 } 659 } 660 return sb.toString(); 661 } 662 663 return in; 664 } 665 }