1 /* 2 * Copyright (c) 2014, 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 com.sun.tools.sjavac.server; 27 28 import java.io.BufferedReader; 29 import java.io.File; 30 import java.io.FileReader; 31 import java.io.IOException; 32 import java.io.InputStreamReader; 33 import java.io.PrintStream; 34 import java.io.PrintWriter; 35 import java.io.StringWriter; 36 import java.net.InetAddress; 37 import java.net.InetSocketAddress; 38 import java.net.Socket; 39 import java.net.SocketAddress; 40 import java.net.URI; 41 import java.util.Collections; 42 import java.util.HashMap; 43 import java.util.HashSet; 44 import java.util.List; 45 import java.util.Map; 46 import java.util.Set; 47 48 import com.sun.tools.sjavac.Util; 49 import com.sun.tools.sjavac.options.Options; 50 51 import static com.sun.tools.sjavac.server.CompilationResult.ERROR_BUT_TRY_AGAIN; 52 import static com.sun.tools.sjavac.server.CompilationResult.ERROR_FATAL; 53 54 public class JavacServiceClient implements JavacService { 55 56 57 // The id can perhaps be used in the future by the javac server to reuse the 58 // JavaCompiler instance for several compiles using the same id. 59 private final String id; 60 private final String portfile; 61 private final String logfile; 62 private final String stdouterrfile; 63 private final boolean background; 64 65 // Default keepalive for server is 120 seconds. 66 // I.e. it will accept 120 seconds of inactivity before quitting. 67 private final int keepalive; 68 private final int poolsize; 69 70 // The sjavac option specifies how the server part of sjavac is spawned. 71 // If you have the experimental sjavac in your path, you are done. If not, you have 72 // to point to a com.sun.tools.sjavac.Main that supports --startserver 73 // for example by setting: sjavac=java%20-jar%20...javac.jar%com.sun.tools.sjavac.Main 74 private final String sjavac; 75 76 // Store the server conf settings here. 77 private final String settings; 78 79 public JavacServiceClient(Options options) { 80 String tmpServerConf = options.getServerConf(); 81 String serverConf = (tmpServerConf!=null)? tmpServerConf : ""; 82 String tmpId = Util.extractStringOption("id", serverConf); 83 id = (tmpId!=null) ? tmpId : "id"+(((new java.util.Random()).nextLong())&Long.MAX_VALUE); 84 String p = Util.extractStringOption("portfile", serverConf); 85 portfile = (p!=null) ? p : options.getStateDir().toFile().getAbsolutePath()+File.separatorChar+"javac_server"; 86 logfile = Util.extractStringOption("logfile", serverConf, portfile + ".javaclog"); 87 stdouterrfile = Util.extractStringOption("stdouterrfile", serverConf, portfile + ".stdouterr"); 88 background = Util.extractBooleanOption("background", serverConf, true); 89 sjavac = Util.extractStringOption("sjavac", serverConf, "sjavac"); 90 int poolsize = Util.extractIntOption("poolsize", serverConf); 91 keepalive = Util.extractIntOption("keepalive", serverConf, 120); 92 93 this.poolsize = poolsize > 0 ? poolsize : Runtime.getRuntime().availableProcessors(); 94 settings = (serverConf.equals("")) ? "id="+id+",portfile="+portfile : serverConf; 95 } 96 97 /** 98 * Hand out the server settings. 99 * @return The server settings, possibly a default value. 100 */ 101 public String serverSettings() { 102 return settings; 103 } 104 105 /** 106 * Make a request to the server only to get the maximum possible heap size to use for compilations. 107 * 108 * @param port_file The port file used to synchronize creation of this server. 109 * @param id The identify of the compilation. 110 * @param out Standard out information. 111 * @param err Standard err information. 112 * @return The maximum heap size in bytes. 113 */ 114 @Override 115 public SysInfo getSysInfo() { 116 try { 117 CompilationResult cr = useServer(new String[0], 118 Collections.<URI>emptySet(), 119 Collections.<URI>emptySet(), 120 Collections.<URI, Set<String>>emptyMap()); 121 return cr.sysinfo; 122 } catch (Exception e) { 123 return new SysInfo(-1, -1); 124 } 125 } 126 127 @Override 128 public CompilationResult compile(String protocolId, 129 String invocationId, 130 String[] args, 131 List<File> explicitSources, 132 Set<URI> sourcesToCompile, 133 Set<URI> visibleSources) { 134 // Delegate to useServer, which delegates to compileHelper 135 return useServer(args, sourcesToCompile, visibleSources, null); 136 } 137 138 /** 139 * Connect and compile using the javac server settings and the args. When using more advanced features, the sources_to_compile and visible_sources are 140 * supplied to the server and meta data is returned in package_artifacts, package_dependencies and package_pubapis. 141 */ 142 public CompilationResult compileHelper(String id, 143 String[] args, 144 Set<URI> sourcesToCompile, 145 Set<URI> visibleSources) { 146 147 CompilationResult rc = new CompilationResult(-3); 148 149 try { 150 PortFile portFile = JavacServer.getPortFile(this.portfile); 151 152 int port = portFile.containsPortInfo() ? portFile.getPort() : 0; 153 if (port == 0) { 154 return new CompilationResult(ERROR_BUT_TRY_AGAIN); 155 } 156 long cookie = portFile.getCookie(); 157 // Acquire the localhost/127.0.0.1 address. 158 InetAddress addr = InetAddress.getByName(null); 159 SocketAddress sockaddr = new InetSocketAddress(addr, port); 160 Socket sock = new Socket(); 161 int timeoutMs = JavacServer.CONNECTION_TIMEOUT * 1000; 162 try { 163 sock.connect(sockaddr, timeoutMs); 164 } catch (java.net.ConnectException e) { 165 rc.setReturnCode(ERROR_BUT_TRY_AGAIN); 166 rc.stderr = "Could not connect to javac server found in portfile: " + portFile.getFilename() + " " + e; 167 return rc; 168 } 169 if (!sock.isConnected()) { 170 rc.setReturnCode(ERROR_BUT_TRY_AGAIN); 171 rc.stderr = "Could not connect to javac server found in portfile: " + portFile.getFilename(); 172 return rc; 173 } 174 175 // 176 // Send arguments 177 // 178 BufferedReader in = new BufferedReader(new InputStreamReader(sock.getInputStream())); 179 PrintWriter sockout = new PrintWriter(sock.getOutputStream()); 180 181 sockout.println(JavacServer.PROTOCOL_COOKIE_VERSION); 182 sockout.println("" + cookie); 183 sockout.println(JavacServer.PROTOCOL_CWD); 184 sockout.println(System.getProperty("user.dir")); 185 sockout.println(JavacServer.PROTOCOL_ID); 186 sockout.println(id); 187 sockout.println(JavacServer.PROTOCOL_ARGS); 188 for (String s : args) { 189 StringBuffer buf = new StringBuffer(); 190 String[] paths = s.split(File.pathSeparator); 191 int c = 0; 192 for (String path : paths) { 193 File f = new File(path); 194 if (f.isFile() || f.isDirectory()) { 195 buf.append(f.getAbsolutePath()); 196 c++; 197 if (c < paths.length) { 198 buf.append(File.pathSeparator); 199 } 200 } else { 201 buf = new StringBuffer(s); 202 break; 203 } 204 } 205 sockout.println(buf.toString()); 206 } 207 sockout.println(JavacServer.PROTOCOL_SOURCES_TO_COMPILE); 208 for (URI uri : sourcesToCompile) { 209 sockout.println(uri.toString()); 210 } 211 sockout.println(JavacServer.PROTOCOL_VISIBLE_SOURCES); 212 for (URI uri : visibleSources) { 213 sockout.println(uri.toString()); 214 } 215 sockout.println(JavacServer.PROTOCOL_END); 216 sockout.flush(); 217 218 // 219 // Receive result 220 // 221 StringBuffer stdout = new StringBuffer(); 222 StringBuffer stderr = new StringBuffer(); 223 224 if (!JavacServiceClient.expect(in, JavacServer.PROTOCOL_STDOUT)) { 225 return new CompilationResult(ERROR_FATAL); 226 } 227 // Load stdout 228 for (;;) { 229 String l = in.readLine(); 230 if (l == null) { 231 return new CompilationResult(ERROR_FATAL); 232 } 233 if (l.equals(JavacServer.PROTOCOL_STDERR)) { 234 break; 235 } 236 stdout.append(l); 237 stdout.append('\n'); 238 } 239 // Load stderr 240 for (;;) { 241 String l = in.readLine(); 242 if (l == null) { 243 return new CompilationResult(ERROR_FATAL); 244 } 245 if (l.equals(JavacServer.PROTOCOL_PACKAGE_ARTIFACTS)) { 246 break; 247 } 248 stderr.append(l); 249 stderr.append('\n'); 250 } 251 // Load the package artifacts 252 Set<URI> lastUriSet = null; 253 for (;;) { 254 String l = in.readLine(); 255 if (l == null) { 256 return new CompilationResult(ERROR_FATAL); 257 } 258 if (l.equals(JavacServer.PROTOCOL_PACKAGE_DEPENDENCIES)) { 259 break; 260 } 261 if (l.length() > 1 && l.charAt(0) == '+') { 262 String pkg = l.substring(1); 263 lastUriSet = new HashSet<>(); 264 rc.packageArtifacts.put(pkg, lastUriSet); 265 } else if (l.length() > 1 && lastUriSet != null) { 266 lastUriSet.add(new URI(l.substring(1))); 267 } 268 } 269 // Load package dependencies 270 Set<String> lastPackageSet = null; 271 for (;;) { 272 String l = in.readLine(); 273 if (l == null) { 274 return new CompilationResult(ERROR_FATAL); 275 } 276 if (l.equals(JavacServer.PROTOCOL_PACKAGE_PUBLIC_APIS)) { 277 break; 278 } 279 if (l.length() > 1 && l.charAt(0) == '+') { 280 String pkg = l.substring(1); 281 lastPackageSet = new HashSet<>(); 282 rc.packageDependencies.put(pkg, lastPackageSet); 283 } else if (l.length() > 1 && lastPackageSet != null) { 284 lastPackageSet.add(l.substring(1)); 285 } 286 } 287 // Load package pubapis 288 Map<String, StringBuffer> tmp = new HashMap<>(); 289 StringBuffer lastPublicApi = null; 290 for (;;) { 291 String l = in.readLine(); 292 if (l == null) { 293 return new CompilationResult(ERROR_FATAL); 294 } 295 if (l.equals(JavacServer.PROTOCOL_SYSINFO)) { 296 break; 297 } 298 if (l.length() > 1 && l.charAt(0) == '+') { 299 String pkg = l.substring(1); 300 lastPublicApi = new StringBuffer(); 301 tmp.put(pkg, lastPublicApi); 302 } else if (l.length() > 1 && lastPublicApi != null) { 303 lastPublicApi.append(l.substring(1)); 304 lastPublicApi.append("\n"); 305 } 306 } 307 for (String p : tmp.keySet()) { 308 //assert (packagePublicApis.get(p) == null); 309 String api = tmp.get(p).toString(); 310 rc.packagePubapis.put(p, api); 311 } 312 // Now reading the max memory possible. 313 for (;;) { 314 String l = in.readLine(); 315 if (l == null) { 316 return new CompilationResult(ERROR_FATAL); 317 } 318 if (l.equals(JavacServer.PROTOCOL_RETURN_CODE)) { 319 break; 320 } 321 if (l.startsWith("num_cores=")) { 322 rc.sysinfo.numCores = Integer.parseInt(l.substring(10)); 323 } 324 if (l.startsWith("max_memory=")) { 325 rc.sysinfo.maxMemory = Long.parseLong(l.substring(11)); 326 } 327 } 328 String l = in.readLine(); 329 if (l == null) { 330 rc.setReturnCode(ERROR_FATAL); 331 rc.stderr = "No return value from the server!"; 332 return rc; 333 } 334 rc.setReturnCode(Integer.parseInt(l)); 335 rc.stdout = stdout.toString(); 336 rc.stderr = stderr.toString(); 337 } catch (Exception e) { 338 StringWriter sw = new StringWriter(); 339 e.printStackTrace(new PrintWriter(sw)); 340 rc.stderr = sw.toString(); 341 } 342 return rc; 343 } 344 345 /** 346 * Dispatch a compilation request to a javac server. 347 * 348 * @param args are the command line args to javac and is allowed to contain source files, @file and other command line options to javac. 349 * 350 * The generated classes, h files and other artifacts from the javac invocation are stored by the javac server to disk. 351 * 352 * @param sources_to_compile The sources to compile. 353 * 354 * @param visibleSources If visible sources has a non zero size, then visible_sources are the only files in the file system that the javac server can see! 355 * (Sources to compile are always visible.) The visible sources are those supplied by the (filtered) -sourcepath 356 * 357 * @param visibleClasses If visible classes for a specific root/jar has a non zero size, then visible_classes are the only class files that the javac server 358 * can see, in that root/jar. It maps from a classpath root or a jar file to the set of visible classes for that root/jar. 359 * 360 * The server return meta data about the build in the following parameters. 361 * @param package_artifacts, map from package name to set of created artifacts for that package. 362 * @param package_dependencies, map from package name to set of packages that it depends upon. 363 * @param package_pubapis, map from package name to unique string identifying its pub api. 364 */ 365 public CompilationResult useServer(String[] args, 366 Set<URI> sourcesToCompile, 367 Set<URI> visibleSources, 368 Map<URI, Set<String>> visibleClasses) { 369 try { 370 if (portfile == null) { 371 CompilationResult cr = new CompilationResult(CompilationResult.ERROR_FATAL); 372 cr.stderr = "No portfile was specified!"; 373 return cr; 374 } 375 376 int attempts = 0; 377 CompilationResult rc; 378 do { 379 PortFile port_file = JavacServer.getPortFile(portfile); 380 synchronized (port_file) { 381 port_file.lock(); 382 port_file.getValues(); 383 port_file.unlock(); 384 } 385 if (!port_file.containsPortInfo()) { 386 String cmd = JavacServer.fork(sjavac, port_file.getFilename(), logfile, poolsize, keepalive, System.err, stdouterrfile, background); 387 388 if (background && !port_file.waitForValidValues()) { 389 // Ouch the server did not start! Lets print its stdouterrfile and the command used. 390 StringWriter sw = new StringWriter(); 391 JavacServiceClient.printFailedAttempt(cmd, stdouterrfile, new PrintWriter(sw)); 392 // And give up. 393 CompilationResult cr = new CompilationResult(ERROR_FATAL); 394 cr.stderr = sw.toString(); 395 return cr; 396 } 397 } 398 rc = compileHelper(id, args, sourcesToCompile, visibleSources); 399 // Try again until we manage to connect. Any error after that 400 // will cause the compilation to fail. 401 if (rc.returnCode == CompilationResult.ERROR_BUT_TRY_AGAIN) { 402 // We could not connect to the server. Try again. 403 attempts++; 404 try { 405 Thread.sleep(JavacServer.WAIT_BETWEEN_CONNECT_ATTEMPTS * 1000); 406 } catch (InterruptedException e) { 407 } 408 } 409 } while (rc.returnCode == ERROR_BUT_TRY_AGAIN && attempts < JavacServer.MAX_NUM_CONNECT_ATTEMPTS); 410 return rc; 411 } catch (Exception e) { 412 StringWriter sw = new StringWriter(); 413 e.printStackTrace(new PrintWriter(sw)); 414 CompilationResult cr = new CompilationResult(ERROR_FATAL); 415 cr.stderr = sw.toString(); 416 return cr; 417 } 418 } 419 420 public static void printFailedAttempt(String cmd, String f, PrintWriter err) { 421 err.println("---- Failed to start javac server with this command -----"); 422 err.println(cmd); 423 try { 424 BufferedReader in = new BufferedReader(new FileReader(f)); 425 err.println("---- stdout/stderr output from attempt to start javac server -----"); 426 for (;;) { 427 String l = in.readLine(); 428 if (l == null) { 429 break; 430 } 431 err.println(l); 432 } 433 err.println("------------------------------------------------------------------"); 434 } catch (Exception e) { 435 err.println("The stdout/stderr output in file " + f + " does not exist and the server did not start."); 436 } 437 } 438 439 /** 440 * Expect this key on the next line read from the reader. 441 */ 442 public static boolean expect(BufferedReader in, String key) throws IOException { 443 String s = in.readLine(); 444 if (s != null && s.equals(key)) { 445 return true; 446 } 447 return false; 448 } 449 }