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 private 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 deleteRecursive(outputFolder); 195 } 196 197 public static String executeCLI(boolean retValZero, String... args) throws Exception { 198 int retVal; 199 File outfile = new File("output.log"); 200 try { 201 String[] command = getCommand(args); 202 retVal = execute(outfile, command); 203 } catch (Exception ex) { 204 if (outfile.exists()) { 205 System.err.println(Files.readString(outfile.toPath())); 206 } 207 throw ex; 208 } 209 210 String output = Files.readString(outfile.toPath()); 211 if (retValZero) { 212 if (retVal != 0) { 213 System.err.println(output); 214 throw new AssertionError("jpackage exited with error: " + retVal); 215 } 216 } else { 217 if (retVal == 0) { 218 System.err.println(output); 219 throw new AssertionError("jpackage exited without error: " + retVal); 220 } 221 } 222 223 if (VERBOSE) { 224 System.out.println("output ="); 225 System.out.println(output); 226 } 227 228 return output; 229 } 230 231 public static String executeToolProvider(boolean retValZero, String... args) throws Exception { 232 StringWriter writer = new StringWriter(); 233 PrintWriter pw = new PrintWriter(writer); 234 int retVal = JPACKAGE_TOOL.run(pw, pw, args); 235 String output = writer.toString(); 236 237 if (retValZero) { 238 if (retVal != 0) { 239 System.err.println(output); 240 throw new AssertionError("jpackage exited with error: " + retVal); 241 } 242 } else { 243 if (retVal == 0) { 244 System.err.println(output); 245 throw new AssertionError("jpackage exited without error"); 246 } 247 } 248 249 if (VERBOSE) { 250 System.out.println("output ="); 251 System.out.println(output); 252 } 253 254 return output; 255 } 256 257 public static boolean isWindows() { 258 return (OS.contains("win")); 259 } 260 261 public static boolean isOSX() { 262 return (OS.contains("mac")); 263 } 264 265 public static boolean isLinux() { 266 return ((OS.contains("nix") || OS.contains("nux"))); 267 } 268 269 public static void createHelloImageJar() throws Exception { 270 createJar(false, "Hello", "image"); 271 } 272 273 public static void createHelloImageJarWithMainClass() throws Exception { 274 createJar(true, "Hello", "image"); 275 } 276 277 public static void createHelloInstallerJar() throws Exception { 278 createJar(false, "Hello", "installer"); 279 } 280 281 public static void createHelloInstallerJarWithMainClass() throws Exception { 282 createJar(true, "Hello", "installer"); 283 } 284 285 private static void createJar(boolean mainClassAttribute, String name, 286 String testType) throws Exception { 287 int retVal; 288 289 File input = new File("input"); 290 if (!input.exists()) { 291 input.mkdir(); 292 } 293 294 Files.copy(Path.of(TEST_SRC_ROOT + File.separator + "apps" + File.separator 295 + testType + File.separator + name + ".java"), Path.of(name + ".java")); 296 297 File javacLog = new File("javac.log"); 298 try { 299 retVal = execute(javacLog, JAVAC.toString(), name + ".java"); 300 } catch (Exception ex) { 301 if (javacLog.exists()) { 302 System.err.println(Files.readString(javacLog.toPath())); 303 } 304 throw ex; 305 } 306 307 if (retVal != 0) { 308 if (javacLog.exists()) { 309 System.err.println(Files.readString(javacLog.toPath())); 310 } 311 throw new AssertionError("javac exited with error: " + retVal); 312 } 313 314 File jarLog = new File("jar.log"); 315 try { 316 List<String> args = new ArrayList<>(); 317 args.add(JAR.toString()); 318 args.add("-c"); 319 args.add("-v"); 320 args.add("-f"); 321 args.add("input" + File.separator + name.toLowerCase() + ".jar"); 322 if (mainClassAttribute) { 323 args.add("-e"); 324 args.add(name); 325 } 326 args.add(name + ".class"); 327 retVal = execute(jarLog, args.stream().toArray(String[]::new)); 328 } catch (Exception ex) { 329 if (jarLog.exists()) { 330 System.err.println(Files.readString(jarLog.toPath())); 331 } 332 throw ex; 333 } 334 335 if (retVal != 0) { 336 if (jarLog.exists()) { 337 System.err.println(Files.readString(jarLog.toPath())); 338 } 339 throw new AssertionError("jar exited with error: " + retVal); 340 } 341 } 342 343 public static void createHelloModule() throws Exception { 344 createModule("Hello.java"); 345 } 346 347 private static void createModule(String javaFile) throws Exception { 348 int retVal; 349 350 File input = new File("input"); 351 if (!input.exists()) { 352 input.mkdir(); 353 } 354 355 File module = new File("module" + File.separator + "com.hello"); 356 if (!module.exists()) { 357 module.mkdirs(); 358 } 359 360 File javacLog = new File("javac.log"); 361 try { 362 List<String> args = new ArrayList<>(); 363 args.add(JAVAC.toString()); 364 args.add("-d"); 365 args.add("module" + File.separator + "com.hello"); 366 args.add(TEST_SRC_ROOT + File.separator + "apps" + File.separator + "com.hello" 367 + File.separator + "module-info.java"); 368 args.add(TEST_SRC_ROOT + File.separator + "apps" + File.separator + "com.hello" 369 + File.separator + "com" + File.separator + "hello" + File.separator 370 + javaFile); 371 retVal = execute(javacLog, args.stream().toArray(String[]::new)); 372 } catch (Exception ex) { 373 if (javacLog.exists()) { 374 System.err.println(Files.readString(javacLog.toPath())); 375 } 376 throw ex; 377 } 378 379 if (retVal != 0) { 380 if (javacLog.exists()) { 381 System.err.println(Files.readString(javacLog.toPath())); 382 } 383 throw new AssertionError("javac exited with error: " + retVal); 384 } 385 386 File jarLog = new File("jar.log"); 387 try { 388 List<String> args = new ArrayList<>(); 389 args.add(JAR.toString()); 390 args.add("--create"); 391 args.add("--file"); 392 args.add("input" + File.separator + "com.hello.jar"); 393 args.add("-C"); 394 args.add("module" + File.separator + "com.hello"); 395 args.add("."); 396 397 retVal = execute(jarLog, args.stream().toArray(String[]::new)); 398 } catch (Exception ex) { 399 if (jarLog.exists()) { 400 System.err.println(Files.readString(jarLog.toPath())); 401 } 402 throw ex; 403 } 404 405 if (retVal != 0) { 406 if (jarLog.exists()) { 407 System.err.println(Files.readString(jarLog.toPath())); 408 } 409 throw new AssertionError("jar exited with error: " + retVal); 410 } 411 } 412 413 public static void createRuntime() throws Exception { 414 int retVal; 415 416 File jlinkLog = new File("jlink.log"); 417 try { 418 List<String> args = new ArrayList<>(); 419 args.add(JLINK.toString()); 420 args.add("--output"); 421 args.add("runtime"); 422 args.add("--add-modules"); 423 args.add("java.base"); 424 retVal = execute(jlinkLog, args.stream().toArray(String[]::new)); 425 } catch (Exception ex) { 426 if (jlinkLog.exists()) { 427 System.err.println(Files.readString(jlinkLog.toPath())); 428 } 429 throw ex; 430 } 431 432 if (retVal != 0) { 433 if (jlinkLog.exists()) { 434 System.err.println(Files.readString(jlinkLog.toPath())); 435 } 436 throw new AssertionError("jlink exited with error: " + retVal); 437 } 438 } 439 440 public static String listToArgumentsMap(List<String> arguments, boolean toolProvider) { 441 if (arguments.isEmpty()) { 442 return ""; 443 } 444 445 String argsStr = ""; 446 for (int i = 0; i < arguments.size(); i++) { 447 String arg = arguments.get(i); 448 argsStr += quote(arg, toolProvider); 449 if ((i + 1) != arguments.size()) { 450 argsStr += " "; 451 } 452 } 453 454 if (!toolProvider && isWindows()) { 455 if (argsStr.contains(" ")) { 456 if (argsStr.contains("\"")) { 457 argsStr = escapeQuote(argsStr, toolProvider); 458 } 459 argsStr = "\"" + argsStr + "\""; 460 } 461 } 462 return argsStr; 463 } 464 465 private static String quote(String in, boolean toolProvider) { 466 if (in == null) { 467 return null; 468 } 469 470 if (in.isEmpty()) { 471 return ""; 472 } 473 474 if (!in.contains("=")) { 475 // Not a property 476 if (in.contains(" ")) { 477 in = escapeQuote(in, toolProvider); 478 return "\"" + in + "\""; 479 } 480 return in; 481 } 482 483 if (!in.contains(" ")) { 484 return in; // No need to quote 485 } 486 487 int paramIndex = in.indexOf("="); 488 if (paramIndex <= 0) { 489 return in; // Something wrong, just skip quoting 490 } 491 492 String param = in.substring(0, paramIndex); 493 String value = in.substring(paramIndex + 1); 494 495 if (value.length() == 0) { 496 return in; // No need to quote 497 } 498 499 value = escapeQuote(value, toolProvider); 500 501 return param + "=" + "\"" + value + "\""; 502 } 503 504 private static String escapeQuote(String in, boolean toolProvider) { 505 if (in == null) { 506 return null; 507 } 508 509 if (in.isEmpty()) { 510 return ""; 511 } 512 513 if (in.contains("\"")) { 514 // Use code points to preserve non-ASCII chars 515 StringBuilder sb = new StringBuilder(); 516 int codeLen = in.codePointCount(0, in.length()); 517 for (int i = 0; i < codeLen; i++) { 518 int code = in.codePointAt(i); 519 // Note: No need to escape '\' on Linux or OS X 520 // jpackage expects us to pass arguments and properties with 521 // quotes and spaces as a map 522 // with quotes being escaped with additional \ for 523 // internal quotes. 524 // So if we want two properties below: 525 // -Djnlp.Prop1=Some "Value" 1 526 // -Djnlp.Prop2=Some Value 2 527 // jpackage will need: 528 // "-Djnlp.Prop1=\"Some \\"Value\\" 1\" -Djnlp.Prop2=\"Some Value 2\"" 529 // but since we using ProcessBuilder to run jpackage we will need to escape 530 // our escape symbols as well, so we will need to pass string below to ProcessBuilder: 531 // "-Djnlp.Prop1=\\\"Some \\\\\\\"Value\\\\\\\" 1\\\" -Djnlp.Prop2=\\\"Some Value 2\\\"" 532 switch (code) { 533 case '"': 534 // " -> \" -> \\\" 535 if (i == 0 || in.codePointAt(i - 1) != '\\') { 536 sb.appendCodePoint('\\'); 537 sb.appendCodePoint(code); 538 } 539 break; 540 case '\\': 541 // We need to escape already escaped symbols as well 542 if ((i + 1) < codeLen) { 543 int nextCode = in.codePointAt(i + 1); 544 if (nextCode == '"') { 545 // \" -> \\\" 546 sb.appendCodePoint('\\'); 547 sb.appendCodePoint('\\'); 548 sb.appendCodePoint('\\'); 549 sb.appendCodePoint(nextCode); 550 } else { 551 sb.appendCodePoint('\\'); 552 sb.appendCodePoint(code); 553 } 554 } else { 555 sb.appendCodePoint(code); 556 } 557 break; 558 default: 559 sb.appendCodePoint(code); 560 break; 561 } 562 } 563 return sb.toString(); 564 } 565 566 return in; 567 } 568 569 }