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