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