1 /* 2 * Copyright (c) 2015, 2016 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package workaround; 27 28 import java.io.BufferedWriter; 29 import java.io.DataInputStream; 30 import java.io.File; 31 import java.io.FileOutputStream; 32 import java.io.IOException; 33 import java.io.InputStream; 34 import java.io.OutputStream; 35 import java.io.OutputStreamWriter; 36 import java.lang.reflect.Constructor; 37 import java.net.URL; 38 import java.net.URLClassLoader; 39 import java.util.ArrayList; 40 import java.util.Collections; 41 import java.util.Enumeration; 42 import java.util.HashSet; 43 import java.util.Properties; 44 45 /* 46 * This wrapper is designed to workaround a class loader issue 47 * when working with JDK9 and Gradle in the JFX builds 48 * NOTE: this worker assumes the @argfile support found in JDK9 49 * 50 * Due to the nature of the command line and where the this class is 51 * specified, certain command line properties may either be : 52 * an argument - because it came after this file 53 * a property - because it was before this file and was processed as a property 54 * Because of this, everything is checked as both. 55 * 56 * The worker specific properties are found below as worker.*. These 57 * properties also need to be forwarded as system properties just in case 58 * we have something like MainLauncherTest that needs some of these to 59 * construct a new java command. 60 * 61 */ 62 public class GradleJUnitWorker { 63 64 public static boolean debug = false; 65 66 private static final String ENCODERCLASS 67 = "jarjar.org.gradle.process.internal.child.EncodedStream$EncodedInput"; 68 69 private static URL fileToURL(File file) throws IOException { 70 return file.getCanonicalFile().toURI().toURL(); 71 } 72 73 // all of the "standard" System Properties that we will not forward 74 // to the worker. 75 private final static String[] defSysProps = { 76 "awt.toolkit", 77 "file.encoding.pkg", 78 "file.encoding", 79 "file.separator", 80 "ftp.nonProxyHosts", 81 "gopherProxySet", 82 "http.nonProxyHosts", 83 "java.awt.graphicsenv", 84 "java.awt.printerjob", 85 "java.class.path", 86 "java.class.version", 87 "java.endorsed.dirs", 88 "java.ext.dirs", 89 "java.home", 90 "java.io.tmpdir", 91 "java.library.path", 92 "java.runtime.name", 93 "java.runtime.version", 94 "java.specification.name", 95 "java.specification.vendor", 96 "java.specification.version", 97 "java.vendor.url.bug", 98 "java.vendor.url", 99 "java.vendor", 100 "java.version", 101 "java.vm.info", 102 "java.vm.name", 103 "java.vm.specification.name", 104 "java.vm.specification.vendor", 105 "java.vm.specification.version", 106 "java.vm.vendor", 107 "java.vm.version", 108 "line.separator", 109 "os.arch", 110 "os.name", 111 "os.version", 112 "path.separator", 113 "socksNonProxyHosts", 114 "sun.arch.data.model", 115 "sun.boot.class.path", 116 "sun.boot.library.path", 117 "sun.cpu.endian", 118 "sun.cpu.isalist", 119 "sun.io.unicode.encoding", 120 "sun.java.command", 121 "sun.java.launcher", 122 "sun.jnu.encoding", 123 "sun.management.compiler", 124 "sun.os.patch.level", 125 "user.country", 126 "user.dir", 127 "user.home", 128 "user.language", 129 "user.name", 130 "user.timezone", 131 // windows 132 "user.script", 133 "user.variant", 134 "sun.desktop", 135 // Jake 136 "java.vm.compressedOopsMode", 137 "jdk.boot.class.path.append",}; 138 139 static HashSet<String> ignoreSysProps = new HashSet(defSysProps.length + 10); 140 141 public static void main(String args[]) { 142 143 try { 144 final ArrayList<String> cmd = new ArrayList<>(30); 145 String gradleWorkerJar = null; 146 String patchesDir = null; 147 String exportsFile = null; 148 String classpathFile = null; 149 String libraryPath = null; 150 String java9path = null; 151 152 final String exportsFileProperty = "worker.exports.file"; 153 final String workerDebugProperty = "worker.debug"; 154 final String patchesDirProperty = "worker.xpatch.dir"; 155 final String classpathFileProperty = "worker.classpath.file"; 156 final String libraryPathProperty = "worker.library.path"; 157 final String javaCmdProperty = "worker.java.cmd"; 158 159 Collections.addAll(ignoreSysProps, defSysProps); 160 ignoreSysProps.add(exportsFileProperty); 161 ignoreSysProps.add(workerDebugProperty); 162 ignoreSysProps.add(patchesDirProperty); 163 ignoreSysProps.add(classpathFileProperty); 164 ignoreSysProps.add(libraryPathProperty); 165 ignoreSysProps.add(javaCmdProperty); 166 167 debug = Boolean.parseBoolean(System.getProperty(workerDebugProperty, "false")); 168 169 ArrayList<String> newArgs = new ArrayList<>(50); 170 for (int i = 0; i < args.length; i++) { 171 if (args[i].startsWith("-cp")) { 172 gradleWorkerJar = args[i + 1]; 173 i++; // skip the path argument 174 if (debug) System.err.println("XWORKER gradleWorkerJar="+exportsFile); 175 } else if (args[i].contains(exportsFileProperty)) { 176 int equals = args[i].indexOf("="); 177 exportsFile = args[i].substring(equals+1); 178 if (debug) System.err.println("XWORKER "+exportsFileProperty+"="+exportsFile); 179 } else if (args[i].contains(patchesDirProperty)) { 180 int equals = args[i].indexOf("="); 181 patchesDir = args[i].substring(equals+1); 182 if (debug) System.err.println("XWORKER "+patchesDirProperty+"="+patchesDir); 183 } else if (args[i].contains(javaCmdProperty)) { 184 int equals = args[i].indexOf("="); 185 java9path = args[i].substring(equals+1); 186 if (debug) System.err.println("XWORKER "+javaCmdProperty+"="+java9path); 187 } else if (args[i].contains(classpathFileProperty)) { 188 int equals = args[i].indexOf("="); 189 classpathFile = args[i].substring(equals+1); 190 if (debug) System.err.println("XWORKER "+classpathFileProperty+"="+classpathFile); 191 } else if (args[i].contains(libraryPathProperty)) { 192 int equals = args[i].indexOf("="); 193 libraryPath = args[i].substring(equals+1); 194 if (debug) System.err.println("XWORKER "+libraryPathProperty+"="+libraryPath); 195 } else { 196 if (debug) System.err.println("XWORKER forwarding cmd "+args[i]); 197 newArgs.add(args[i]); 198 } 199 } 200 201 // Debug routine to capture a worker stream for replaying 202 if (false) { 203 File dumper = new File("datadump.txt"); 204 FileOutputStream out 205 = new FileOutputStream(dumper); 206 207 byte[] buf = new byte[1024]; 208 int cnt; 209 210 while ((cnt = System.in.read(buf)) > 0) { 211 out.write(buf, 0, cnt); 212 } 213 out.close(); 214 System.exit(-1); 215 } 216 217 //Next we need to get the gradle encoder class from 218 // the gradle-worker.jar, which usually is on the classpath 219 // but as it is an arg in our case we need to load the jar 220 // into a classloader 221 ArrayList<URL> urlList = new ArrayList(); 222 urlList.add(fileToURL(new File(gradleWorkerJar))); 223 224 URL[] urls = (URL[]) urlList.toArray(new URL[0]); 225 ClassLoader cloader = new URLClassLoader(urls); 226 227 if (debug) { 228 System.err.println("AND WE HAVE " + urls[0]); 229 } 230 231 // try to find the Gradle class that decodes the input 232 // stream. In newer versions (> 2. ?) they prefix with jarjar. 233 Class encoderClass = null; 234 if (encoderClass == null) { 235 try { 236 encoderClass = Class.forName(ENCODERCLASS, true, cloader); 237 } catch (ClassNotFoundException e) { 238 if (debug) { 239 System.err.println("did not find " + ENCODERCLASS); 240 } 241 } 242 } 243 244 if (encoderClass != null) { 245 if (debug) { 246 System.err.println("Found EncoderClass " + encoderClass.getName()); 247 } 248 } else { 249 throw new RuntimeException("Encoder not found"); 250 } 251 252 Constructor constructor 253 = encoderClass.getConstructor(new Class[]{InputStream.class}); 254 255 InputStream encodedStream 256 = (InputStream) constructor.newInstance(System.in); 257 258 DataInputStream inputStream 259 = new DataInputStream(encodedStream); 260 261 int count = inputStream.readInt(); 262 263 //And now lets build a command line. 264 ArrayList<String> cpelement = new ArrayList<>(50); 265 266 // start with the gradle-worker.jar 267 cpelement.add(gradleWorkerJar); 268 269 // read from the data stream sent by gradle to classpath setter 270 for (int dex = 0; dex < count; dex++) { 271 String entry = inputStream.readUTF(); 272 // skipping any mention of jfxrt.jar.... 273 if (!entry.contains("jfxrt.jar")) { 274 File file = new File(entry); 275 cpelement.add(file.toString()); 276 } 277 } 278 279 if (debug) { 280 for (int i = 0; i < cpelement.size(); i++) { 281 System.err.println(" " + i + " " + cpelement.get(i)); 282 } 283 } 284 285 String securityManagerType = inputStream.readUTF(); 286 if (debug) { 287 System.out.println("XWORKER security manager is " + securityManagerType); 288 } 289 290 /* 291 End read from the data stream send by gradle to classpath setter 292 Now we need to write this into our @argfile 293 */ 294 File classPathArgFile; 295 296 if (classpathFile == null) { 297 classpathFile = System.getProperty(classpathFileProperty); 298 } 299 300 if (classpathFile != null) { 301 classPathArgFile = new File(classpathFile); 302 } else if (debug) { 303 classPathArgFile = new File("classpath.txt"); 304 } else { 305 classPathArgFile = File.createTempFile("xworker-classpath", ".tmp"); 306 } 307 try { 308 BufferedWriter br = new BufferedWriter(new OutputStreamWriter( 309 new FileOutputStream(classPathArgFile))); 310 311 br.write("-classpath"); 312 br.newLine(); 313 for (int i = 0; i < cpelement.size(); i++) { 314 if (i == 0) { 315 br.write(" \""); 316 } else { 317 br.write(" "); 318 } 319 320 br.write(cpelement.get(i).replace('\\', '/')); 321 if (i < cpelement.size() - 1) { 322 br.write(File.pathSeparator); 323 br.write("\\"); 324 br.newLine(); 325 } 326 327 } 328 br.write("\""); 329 br.newLine(); 330 br.close(); 331 } catch (IOException e) { 332 throw new RuntimeException("Could not write @classpath.txt"); 333 } 334 335 String jdk_home_env = System.getenv("JDK9_HOME"); 336 if (debug) System.err.println("XWORKER JDK9_HOME (env) is set to " + jdk_home_env); 337 338 if (jdk_home_env == null) { 339 jdk_home_env = System.getenv("JDK_HOME"); 340 if (debug) System.err.println("XWORKER JDK_HOME (env) is set to " + jdk_home_env); 341 } 342 343 String jdk_home = System.getProperty("JDK_HOME"); 344 if (debug) System.err.println("XWORKER JDK_HOME is set to " + jdk_home); 345 346 if (java9path == null) { 347 java9path = System.getProperty(javaCmdProperty); 348 } 349 350 String java_cmd = "java"; 351 if (java9path != null) { 352 // good we have it - probably the safest way on windows 353 java_cmd = java9path; 354 if (debug) System.err.println("XWORKER JAVA9 is set to " + java_cmd); 355 } else if (jdk_home_env != null) { 356 java9path = jdk_home_env 357 + File.separatorChar + "bin" 358 + File.separatorChar + "java"; 359 } else if (jdk_home != null) { 360 java_cmd = jdk_home 361 + File.separatorChar + "bin" 362 + File.separatorChar + "java"; 363 } 364 365 if (debug) { 366 System.err.println("XWORKER using java " + java_cmd); 367 } 368 369 cmd.add(java_cmd); 370 371 cmd.add("-D"+javaCmdProperty+"="+java_cmd); 372 373 if (patchesDir == null) { 374 patchesDir = System.getProperty(patchesDirProperty); 375 } 376 377 if (patchesDir != null) { 378 cmd.add("-Xpatch:" + patchesDir); 379 cmd.add("-D"+patchesDirProperty+"="+patchesDir); 380 } 381 382 if (exportsFile == null) { 383 exportsFile = System.getProperty(exportsFileProperty); 384 } 385 386 if (exportsFile != null) { 387 cmd.add("@" + exportsFile); 388 cmd.add("-D"+exportsFileProperty+"="+exportsFile); 389 } 390 391 final String cleanpath = 392 classPathArgFile.getAbsolutePath().replaceAll("\\\\", "/"); 393 cmd.add("@" + cleanpath); 394 cmd.add("-D"+classpathFileProperty+"="+cleanpath); 395 396 if (libraryPath == null) { 397 libraryPath = System.getProperty(libraryPathProperty); 398 } 399 400 if (libraryPath != null) { 401 cmd.add("-Djava.library.path=" + libraryPath); 402 cmd.add("-D"+libraryPathProperty+"="+libraryPath); 403 } 404 405 if (debug) { 406 cmd.add("-D"+workerDebugProperty+"="+debug); 407 } 408 409 //forward any old system properties, other than the stock ones 410 Properties p = System.getProperties(); 411 Enumeration keys = p.keys(); 412 while (keys.hasMoreElements()) { 413 String key = (String) keys.nextElement(); 414 if (!ignoreSysProps.contains(key)) { 415 String value = (String) p.get(key); 416 String newprop = "-D"+key+"="+value; 417 cmd.add(newprop); 418 if (debug) System.out.println("XWORKER adding "+newprop); 419 420 } 421 } 422 423 newArgs.forEach(s-> { 424 cmd.add(s); 425 }); 426 427 if (debug) { 428 System.err.println("XWORKER: cmd is"); 429 cmd.stream().forEach((s) -> { 430 System.err.println(" " + s); 431 }); 432 System.err.println("XWORKER: end cmd"); 433 } 434 435 // Now we have a full command line, start the new worker process 436 ProcessBuilder pb = new ProcessBuilder(cmd); 437 Process proc = pb.start(); 438 439 // And now setup and forward the input to the worker thread 440 // and the output from the worker thread. 441 442 new Thread(new Runnable() { 443 444 @Override 445 public void run() { 446 try { 447 OutputStream os = proc.getOutputStream(); 448 449 byte[] input = new byte[512]; 450 int cnt; 451 452 while ((cnt = System.in.read(input)) > 0) { 453 os.write(input, 0, cnt); 454 } 455 456 os.close(); 457 458 } catch (IOException io) { 459 io.printStackTrace(); 460 } 461 } 462 }).start(); 463 464 new Thread(new Runnable() { 465 466 @Override 467 public void run() { 468 try { 469 InputStream is = proc.getInputStream(); 470 471 byte[] input = new byte[1024]; 472 int cnt; 473 474 while ((cnt = is.read(input)) > 0) { 475 System.out.write(input, 0, cnt); 476 } 477 478 is.close(); 479 480 } catch (IOException io) { 481 io.printStackTrace(); 482 } 483 } 484 }).start(); 485 486 new Thread(new Runnable() { 487 488 @Override 489 public void run() { 490 try { 491 InputStream es = proc.getErrorStream(); 492 493 byte[] input = new byte[1024]; 494 int cnt; 495 496 while ((cnt = es.read(input)) > 0) { 497 System.err.write(input, 0, cnt); 498 } 499 500 es.close(); 501 502 } catch (IOException io) { 503 io.printStackTrace(); 504 } 505 } 506 }).start(); 507 508 // And lastly - forward the exit code from the worker 509 System.exit(proc.waitFor()); 510 511 } catch (Exception e) { 512 throw new RuntimeException("Could not initialise system classpath.", e); 513 } 514 } 515 }