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 }