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 }