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