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 private static final String ENCODERCLASS_2 69 = "worker.org.gradle.process.internal.streams.EncodedStream$EncodedInput"; 70 71 private static URL fileToURL(File file) throws IOException { 72 return file.getCanonicalFile().toURI().toURL(); 73 } 74 75 // all of the "standard" System Properties that we will not forward 76 // to the worker. 77 private final static String[] defSysProps = { 78 "awt.toolkit", 79 "file.encoding.pkg", 80 "file.encoding", 81 "file.separator", 82 "ftp.nonProxyHosts", 83 "gopherProxySet", 84 "http.nonProxyHosts", 85 "java.awt.graphicsenv", 86 "java.awt.printerjob", 87 "java.class.path", 88 "java.class.version", 89 "java.endorsed.dirs", 90 "java.ext.dirs", 91 "java.home", 92 "java.io.tmpdir", 93 "java.library.path", 94 "java.runtime.name", 95 "java.runtime.version", 96 "java.specification.name", 97 "java.specification.vendor", 98 "java.specification.version", 99 "java.vendor.url.bug", 100 "java.vendor.url", 101 "java.vendor", 102 "java.version", 103 "java.vm.info", 104 "java.vm.name", 105 "java.vm.specification.name", 106 "java.vm.specification.vendor", 107 "java.vm.specification.version", 108 "java.vm.vendor", 109 "java.vm.version", 110 "line.separator", 111 "os.arch", 112 "os.name", 113 "os.version", 114 "path.separator", 115 "socksNonProxyHosts", 116 "sun.arch.data.model", 117 "sun.boot.class.path", 118 "sun.boot.library.path", 119 "sun.cpu.endian", 120 "sun.cpu.isalist", 121 "sun.io.unicode.encoding", 122 "sun.java.command", 123 "sun.java.launcher", 124 "sun.jnu.encoding", 125 "sun.management.compiler", 126 "sun.os.patch.level", 127 "user.country", 128 "user.dir", 129 "user.home", 130 "user.language", 131 "user.name", 132 "user.timezone", 133 // windows 134 "user.script", 135 "user.variant", 136 "sun.desktop", 137 // Jake 138 "java.vm.compressedOopsMode", 139 "jdk.boot.class.path.append",}; 140 141 static HashSet<String> ignoreSysProps = new HashSet(defSysProps.length + 10); 142 143 public static void main(String args[]) { 144 145 try { 146 final ArrayList<String> cmd = new ArrayList<>(30); 147 String gradleWorkerJar = null; 148 String patchmoduleFile = null; 149 String exportsFile = null; 150 String classpathFile = null; 151 String jigsawJavapath = null; 152 153 final String exportsFileProperty = "worker.exports.file"; 154 final String workerDebugProperty = "worker.debug"; 155 final String patchmoduleFileProperty = "worker.patchmodule.file"; 156 final String classpathFileProperty = "worker.classpath.file"; 157 final String javaCmdProperty = "worker.java.cmd"; 158 159 Collections.addAll(ignoreSysProps, defSysProps); 160 ignoreSysProps.add(exportsFileProperty); 161 ignoreSysProps.add(workerDebugProperty); 162 ignoreSysProps.add(patchmoduleFileProperty); 163 ignoreSysProps.add(classpathFileProperty); 164 ignoreSysProps.add(javaCmdProperty); 165 166 debug = Boolean.parseBoolean(System.getProperty(workerDebugProperty, "false")); 167 168 ArrayList<String> newArgs = new ArrayList<>(50); 169 for (int i = 0; i < args.length; i++) { 170 if (args[i].startsWith("-cp")) { 171 gradleWorkerJar = args[i + 1]; 172 i++; // skip the path argument 173 if (debug) System.err.println("XWORKER gradleWorkerJar="+exportsFile); 174 } else if (args[i].contains(exportsFileProperty)) { 175 int equals = args[i].indexOf("="); 176 exportsFile = args[i].substring(equals+1); 177 if (debug) System.err.println("XWORKER "+exportsFileProperty+"="+exportsFile); 178 } else if (args[i].contains(patchmoduleFileProperty)) { 179 int equals = args[i].indexOf("="); 180 patchmoduleFile = args[i].substring(equals+1); 181 if (debug) System.err.println("XWORKER "+patchmoduleFileProperty+"="+patchmoduleFile); 182 } else if (args[i].contains(javaCmdProperty)) { 183 int equals = args[i].indexOf("="); 184 jigsawJavapath = args[i].substring(equals+1); 185 if (debug) System.err.println("XWORKER "+javaCmdProperty+"="+jigsawJavapath); 186 } else if (args[i].contains(classpathFileProperty)) { 187 int equals = args[i].indexOf("="); 188 classpathFile = args[i].substring(equals+1); 189 if (debug) System.err.println("XWORKER "+classpathFileProperty+"="+classpathFile); 190 } else { 191 if (debug) System.err.println("XWORKER forwarding cmd "+args[i]); 192 newArgs.add(args[i]); 193 } 194 } 195 196 // Debug routine to capture a worker stream for replaying 197 if (false) { 198 File dumper = new File("datadump.txt"); 199 FileOutputStream out 200 = new FileOutputStream(dumper); 201 202 byte[] buf = new byte[1024]; 203 int cnt; 204 205 while ((cnt = System.in.read(buf)) > 0) { 206 out.write(buf, 0, cnt); 207 } 208 out.close(); 209 System.exit(-1); 210 } 211 212 //Next we need to get the gradle encoder class from 213 // the gradle-worker.jar, which usually is on the classpath 214 // but as it is an arg in our case we need to load the jar 215 // into a classloader 216 ArrayList<URL> urlList = new ArrayList(); 217 urlList.add(fileToURL(new File(gradleWorkerJar))); 218 219 URL[] urls = (URL[]) urlList.toArray(new URL[0]); 220 ClassLoader cloader = new URLClassLoader(urls); 221 222 if (debug) { 223 System.err.println("AND WE HAVE " + urls[0]); 224 } 225 226 // try to find the Gradle class that decodes the input 227 // stream. In newer versions (> 2. ?) they prefix with jarjar. 228 Class encoderClass = null; 229 if (encoderClass == null) { 230 try { 231 encoderClass = Class.forName(ENCODERCLASS, true, cloader); 232 } catch (ClassNotFoundException e) { 233 if (debug) { 234 System.err.println("did not find " + ENCODERCLASS); 235 } 236 } 237 } 238 if (encoderClass == null) { 239 try { 240 encoderClass = Class.forName(ENCODERCLASS_2, true, cloader); 241 } catch (ClassNotFoundException e) { 242 if (debug) { 243 System.err.println("did not find " + ENCODERCLASS_2); 244 } 245 } 246 } 247 248 if (encoderClass != null) { 249 if (debug) { 250 System.err.println("Found EncoderClass " + encoderClass.getName()); 251 } 252 } else { 253 throw new RuntimeException("Gradle Encoder Class not found"); 254 } 255 256 Constructor constructor 257 = encoderClass.getConstructor(new Class[]{InputStream.class}); 258 259 InputStream encodedStream 260 = (InputStream) constructor.newInstance(System.in); 261 262 DataInputStream inputStream 263 = new DataInputStream(encodedStream); 264 265 int count = inputStream.readInt(); 266 267 //And now lets build a command line. 268 ArrayList<String> cpelement = new ArrayList<>(50); 269 270 // start with the gradle-worker.jar 271 cpelement.add(gradleWorkerJar); 272 273 // read from the data stream sent by gradle to classpath setter 274 for (int dex = 0; dex < count; dex++) { 275 String entry = inputStream.readUTF(); 276 // skipping any mention of jfxrt.jar.... 277 if (!entry.contains("jfxrt.jar")) { 278 File file = new File(entry); 279 cpelement.add(file.toString()); 280 } 281 } 282 283 if (debug) { 284 for (int i = 0; i < cpelement.size(); i++) { 285 System.err.println(" " + i + " " + cpelement.get(i)); 286 } 287 } 288 289 String securityManagerType = inputStream.readUTF(); 290 if (debug) { 291 System.out.println("XWORKER security manager is " + securityManagerType); 292 } 293 294 /* 295 End read from the data stream send by gradle to classpath setter 296 Now we need to write this into our @argfile 297 */ 298 File classPathArgFile; 299 300 if (classpathFile == null) { 301 classpathFile = System.getProperty(classpathFileProperty); 302 } 303 304 if (classpathFile != null) { 305 classPathArgFile = new File(classpathFile); 306 } else if (debug) { 307 classPathArgFile = new File("classpath.txt"); 308 } else { 309 classPathArgFile = File.createTempFile("xworker-classpath", ".tmp"); 310 } 311 try { 312 BufferedWriter br = new BufferedWriter(new OutputStreamWriter( 313 new FileOutputStream(classPathArgFile))); 314 315 br.write("-classpath"); 316 br.newLine(); 317 for (int i = 0; i < cpelement.size(); i++) { 318 if (i == 0) { 319 br.write(" \""); 320 } else { 321 br.write(" "); 322 } 323 324 br.write(cpelement.get(i).replace('\\', '/')); 325 if (i < cpelement.size() - 1) { 326 br.write(File.pathSeparator); 327 br.write("\\"); 328 br.newLine(); 329 } 330 331 } 332 br.write("\""); 333 br.newLine(); 334 br.close(); 335 } catch (IOException e) { 336 throw new RuntimeException("Could not write @classpath.txt"); 337 } 338 339 String jdk_home_env = System.getenv("JIGSAW_HOME"); 340 if (debug) System.err.println("XWORKER JIGSAW_HOME (env) is set to " + jdk_home_env); 341 342 if (jdk_home_env == null) { 343 jdk_home_env = System.getenv("JDK_HOME"); 344 if (debug) System.err.println("XWORKER JDK_HOME (env) is set to " + jdk_home_env); 345 } 346 347 String jdk_home = System.getProperty("JDK_HOME"); 348 if (debug) System.err.println("XWORKER JDK_HOME is set to " + jdk_home); 349 350 if (jigsawJavapath == null) { 351 jigsawJavapath = System.getProperty(javaCmdProperty); 352 } 353 354 String java_cmd = "java"; 355 if (jigsawJavapath != null) { 356 // good we have it - probably the safest way on windows 357 java_cmd = jigsawJavapath; 358 if (debug) System.err.println("XWORKER JIGSAW_JAVA is set to " + java_cmd); 359 } else if (jdk_home_env != null) { 360 jigsawJavapath = jdk_home_env 361 + File.separatorChar + "bin" 362 + File.separatorChar + "java"; 363 } else if (jdk_home != null) { 364 java_cmd = jdk_home 365 + File.separatorChar + "bin" 366 + File.separatorChar + "java"; 367 } 368 369 if (debug) { 370 System.err.println("XWORKER using java " + java_cmd); 371 } 372 373 cmd.add(java_cmd); 374 375 cmd.add("-D"+javaCmdProperty+"="+java_cmd); 376 377 if (patchmoduleFile == null) { 378 patchmoduleFile = System.getProperty(patchmoduleFileProperty); 379 } 380 381 if (patchmoduleFile != null) { 382 cmd.add("@" + patchmoduleFile); 383 cmd.add("-D" + patchmoduleFileProperty + "=" + patchmoduleFile); 384 } 385 386 if (exportsFile == null) { 387 exportsFile = System.getProperty(exportsFileProperty); 388 } 389 390 if (exportsFile != null) { 391 cmd.add("@" + exportsFile); 392 cmd.add("-D"+exportsFileProperty+"="+exportsFile); 393 } 394 395 final String cleanpath = 396 classPathArgFile.getAbsolutePath().replaceAll("\\\\", "/"); 397 cmd.add("@" + cleanpath); 398 cmd.add("-D"+classpathFileProperty+"="+cleanpath); 399 400 if (debug) { 401 cmd.add("-D"+workerDebugProperty+"="+debug); 402 } 403 404 //forward any old system properties, other than the stock ones 405 Properties p = System.getProperties(); 406 Enumeration keys = p.keys(); 407 while (keys.hasMoreElements()) { 408 String key = (String) keys.nextElement(); 409 if (!ignoreSysProps.contains(key)) { 410 String value = (String) p.get(key); 411 String newprop = "-D"+key+"="+value; 412 cmd.add(newprop); 413 if (debug) System.out.println("XWORKER adding "+newprop); 414 415 } 416 } 417 418 newArgs.forEach(s-> { 419 cmd.add(s); 420 }); 421 422 if (debug) { 423 System.err.println("XWORKER: cmd is"); 424 cmd.stream().forEach((s) -> { 425 System.err.println(" " + s); 426 }); 427 System.err.println("XWORKER: end cmd"); 428 } 429 430 // Now we have a full command line, start the new worker process 431 ProcessBuilder pb = new ProcessBuilder(cmd); 432 Process proc = pb.start(); 433 434 // And now setup and forward the input to the worker thread 435 // and the output from the worker thread. 436 437 new Thread(new Runnable() { 438 439 @Override 440 public void run() { 441 try { 442 OutputStream os = proc.getOutputStream(); 443 444 byte[] input = new byte[512]; 445 int cnt; 446 447 while ((cnt = System.in.read(input)) > 0) { 448 os.write(input, 0, cnt); 449 } 450 451 os.close(); 452 453 } catch (IOException io) { 454 io.printStackTrace(); 455 } 456 } 457 }).start(); 458 459 new Thread(new Runnable() { 460 461 @Override 462 public void run() { 463 try { 464 InputStream is = proc.getInputStream(); 465 466 byte[] input = new byte[1024]; 467 int cnt; 468 469 while ((cnt = is.read(input)) > 0) { 470 System.out.write(input, 0, cnt); 471 } 472 473 is.close(); 474 475 } catch (IOException io) { 476 io.printStackTrace(); 477 } 478 } 479 }).start(); 480 481 new Thread(new Runnable() { 482 483 @Override 484 public void run() { 485 try { 486 InputStream es = proc.getErrorStream(); 487 488 byte[] input = new byte[1024]; 489 int cnt; 490 491 while ((cnt = es.read(input)) > 0) { 492 System.err.write(input, 0, cnt); 493 } 494 495 es.close(); 496 497 } catch (IOException io) { 498 io.printStackTrace(); 499 } 500 } 501 }).start(); 502 503 // And lastly - forward the exit code from the worker 504 System.exit(proc.waitFor()); 505 506 } catch (Exception e) { 507 throw new RuntimeException("Could not initialise system classpath.", e); 508 } 509 } 510 }