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", "input", "hello"); 351 } 352 353 public static void createOtherModule() throws Exception { 354 createModule("Other.java", "input-other", "other"); 355 } 356 357 private static void createModule(String javaFile, String inputDir, 358 String aName) throws Exception { 359 int retVal; 360 361 File input = new File(inputDir); 362 if (!input.exists()) { 363 input.mkdir(); 364 } 365 366 File module = new File("module" + File.separator + "com." + aName); 367 if (!module.exists()) { 368 module.mkdirs(); 369 } 370 371 File javacLog = new File("javac.log"); 372 try { 373 List<String> args = new ArrayList<>(); 374 args.add(JAVAC.toString()); 375 args.add("-d"); 376 args.add("module" + File.separator + "com." + aName); 377 args.add(TEST_SRC_ROOT + File.separator + "apps" + File.separator 378 + "com." + aName + File.separator + "module-info.java"); 379 args.add(TEST_SRC_ROOT + File.separator + "apps" 380 + File.separator + "com." + aName + File.separator + "com" 381 + File.separator + aName + File.separator + javaFile); 382 retVal = execute(javacLog, args.stream().toArray(String[]::new)); 383 } catch (Exception ex) { 384 if (javacLog.exists()) { 385 System.err.println(Files.readString(javacLog.toPath())); 386 } 387 throw ex; 388 } 389 390 if (retVal != 0) { 391 if (javacLog.exists()) { 392 System.err.println(Files.readString(javacLog.toPath())); 393 } 394 throw new AssertionError("javac exited with error: " + retVal); 395 } 396 397 File jarLog = new File("jar.log"); 398 try { 399 List<String> args = new ArrayList<>(); 400 args.add(JAR.toString()); 401 args.add("--create"); 402 args.add("--file"); 403 args.add(inputDir + File.separator + "com." + aName + ".jar"); 404 args.add("-C"); 405 args.add("module" + File.separator + "com." + aName); 406 args.add("."); 407 408 retVal = execute(jarLog, args.stream().toArray(String[]::new)); 409 } catch (Exception ex) { 410 if (jarLog.exists()) { 411 System.err.println(Files.readString(jarLog.toPath())); 412 } 413 throw ex; 414 } 415 416 if (retVal != 0) { 417 if (jarLog.exists()) { 418 System.err.println(Files.readString(jarLog.toPath())); 419 } 420 throw new AssertionError("jar exited with error: " + retVal); 421 } 422 } 423 424 public static void createRuntime() throws Exception { 425 int retVal; 426 427 File jlinkLog = new File("jlink.log"); 428 try { 429 List<String> args = new ArrayList<>(); 430 args.add(JLINK.toString()); 431 args.add("--output"); 432 args.add("runtime"); 433 args.add("--add-modules"); 434 args.add("java.base"); 435 retVal = execute(jlinkLog, args.stream().toArray(String[]::new)); 436 } catch (Exception ex) { 437 if (jlinkLog.exists()) { 438 System.err.println(Files.readString(jlinkLog.toPath())); 439 } 440 throw ex; 441 } 442 443 if (retVal != 0) { 444 if (jlinkLog.exists()) { 445 System.err.println(Files.readString(jlinkLog.toPath())); 446 } 447 throw new AssertionError("jlink exited with error: " + retVal); 448 } 449 } 450 451 public static String listToArgumentsMap(List<String> arguments, boolean toolProvider) { 452 if (arguments.isEmpty()) { 453 return ""; 454 } 455 456 String argsStr = ""; 457 for (int i = 0; i < arguments.size(); i++) { 458 String arg = arguments.get(i); 459 argsStr += quote(arg, toolProvider); 460 if ((i + 1) != arguments.size()) { 461 argsStr += " "; 462 } 463 } 464 465 if (!toolProvider && isWindows()) { 466 if (argsStr.contains(" ")) { 467 if (argsStr.contains("\"")) { 468 argsStr = escapeQuote(argsStr, toolProvider); 469 } 470 argsStr = "\"" + argsStr + "\""; 471 } 472 } 473 return argsStr; 474 } 475 476 private static String quote(String in, boolean toolProvider) { 477 if (in == null) { 478 return null; 479 } 480 481 if (in.isEmpty()) { 482 return ""; 483 } 484 485 if (!in.contains("=")) { 486 // Not a property 487 if (in.contains(" ")) { 488 in = escapeQuote(in, toolProvider); 489 return "\"" + in + "\""; 490 } 491 return in; 492 } 493 494 if (!in.contains(" ")) { 495 return in; // No need to quote 496 } 497 498 int paramIndex = in.indexOf("="); 499 if (paramIndex <= 0) { 500 return in; // Something wrong, just skip quoting 501 } 502 503 String param = in.substring(0, paramIndex); 504 String value = in.substring(paramIndex + 1); 505 506 if (value.length() == 0) { 507 return in; // No need to quote 508 } 509 510 value = escapeQuote(value, toolProvider); 511 512 return param + "=" + "\"" + value + "\""; 513 } 514 515 private static String escapeQuote(String in, boolean toolProvider) { 516 if (in == null) { 517 return null; 518 } 519 520 if (in.isEmpty()) { 521 return ""; 522 } 523 524 if (in.contains("\"")) { 525 // Use code points to preserve non-ASCII chars 526 StringBuilder sb = new StringBuilder(); 527 int codeLen = in.codePointCount(0, in.length()); 528 for (int i = 0; i < codeLen; i++) { 529 int code = in.codePointAt(i); 530 // Note: No need to escape '\' on Linux or OS X 531 // jpackage expects us to pass arguments and properties with 532 // quotes and spaces as a map 533 // with quotes being escaped with additional \ for 534 // internal quotes. 535 // So if we want two properties below: 536 // -Djnlp.Prop1=Some "Value" 1 537 // -Djnlp.Prop2=Some Value 2 538 // jpackage will need: 539 // "-Djnlp.Prop1=\"Some \\"Value\\" 1\" -Djnlp.Prop2=\"Some Value 2\"" 540 // but since we using ProcessBuilder to run jpackage we will need to escape 541 // our escape symbols as well, so we will need to pass string below to ProcessBuilder: 542 // "-Djnlp.Prop1=\\\"Some \\\\\\\"Value\\\\\\\" 1\\\" -Djnlp.Prop2=\\\"Some Value 2\\\"" 543 switch (code) { 544 case '"': 545 // " -> \" -> \\\" 546 if (i == 0 || in.codePointAt(i - 1) != '\\') { 547 sb.appendCodePoint('\\'); 548 sb.appendCodePoint(code); 549 } 550 break; 551 case '\\': 552 // We need to escape already escaped symbols as well 553 if ((i + 1) < codeLen) { 554 int nextCode = in.codePointAt(i + 1); 555 if (nextCode == '"') { 556 // \" -> \\\" 557 sb.appendCodePoint('\\'); 558 sb.appendCodePoint('\\'); 559 sb.appendCodePoint('\\'); 560 sb.appendCodePoint(nextCode); 561 } else { 562 sb.appendCodePoint('\\'); 563 sb.appendCodePoint(code); 564 } 565 } else { 566 sb.appendCodePoint(code); 567 } 568 break; 569 default: 570 sb.appendCodePoint(code); 571 break; 572 } 573 } 574 return sb.toString(); 575 } 576 577 return in; 578 } 579 580 }