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