blob: 8a46c1ecac48b6c7e14faecc381b98f7c25c8a87 [file] [log] [blame]
/*
* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.sjavac.server;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Map;
import java.util.concurrent.Future;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Log;
import com.sun.tools.javac.util.BaseFileManager;
import com.sun.tools.javac.util.StringUtils;
import com.sun.tools.sjavac.comp.Dependencies;
import com.sun.tools.sjavac.comp.JavaCompilerWithDeps;
import com.sun.tools.sjavac.comp.SmartFileManager;
import com.sun.tools.sjavac.comp.ResolveWithDeps;
/**
* The compiler thread maintains a JavaCompiler instance and
* can receive a request from the client, perform the compilation
* requested and report back the results.
*
* * <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own
* risk. This code and its internal interfaces are subject to change
* or deletion without notice.</b></p>
*/
public class CompilerThread implements Runnable {
private JavacServer javacServer;
private CompilerPool compilerPool;
private List<Future<?>> subTasks;
// Communicating over this socket.
private Socket socket;
// The necessary classes to do a compilation.
private com.sun.tools.javac.api.JavacTool compiler;
private StandardJavaFileManager fileManager;
private BaseFileManager fileManagerBase;
private SmartFileManager smartFileManager;
private Context context;
// If true, then this thread is serving a request.
private boolean inUse = false;
CompilerThread(CompilerPool cp) {
compilerPool = cp;
javacServer = cp.getJavacServer();
}
/**
* Execute a minor task, for example generating bytecodes and writing them to disk,
* that belong to a major compiler thread task.
*/
public synchronized void executeSubtask(Runnable r) {
subTasks.add(compilerPool.executeSubtask(this, r));
}
/**
* Count the number of active sub tasks.
*/
public synchronized int numActiveSubTasks() {
int c = 0;
for (Future<?> f : subTasks) {
if (!f.isDone() && !f.isCancelled()) {
c++;
}
}
return c;
}
/**
* Use this socket for the upcoming request.
*/
public void setSocket(Socket s) {
socket = s;
}
/**
* Prepare the compiler thread for use. It is not yet started.
* It will be started by the executor service.
*/
public synchronized void use() {
assert(!inUse);
inUse = true;
compiler = com.sun.tools.javac.api.JavacTool.create();
fileManager = compiler.getStandardFileManager(null, null, null);
fileManagerBase = (BaseFileManager)fileManager;
smartFileManager = new SmartFileManager(fileManager);
context = new Context();
context.put(JavaFileManager.class, smartFileManager);
ResolveWithDeps.preRegister(context);
JavaCompilerWithDeps.preRegister(context, this);
subTasks = new ArrayList<>();
}
/**
* Prepare the compiler thread for idleness.
*/
public synchronized void unuse() {
assert(inUse);
inUse = false;
compiler = null;
fileManager = null;
fileManagerBase = null;
smartFileManager = null;
context = null;
subTasks = null;
}
/**
* Expect this key on the next line read from the reader.
*/
private static boolean expect(BufferedReader in, String key) throws IOException {
String s = in.readLine();
if (s != null && s.equals(key)) {
return true;
}
return false;
}
// The request identifier, for example GENERATE_NEWBYTECODE
String id = "";
public String currentRequestId() {
return id;
}
PrintWriter stdout;
PrintWriter stderr;
int forcedExitCode = 0;
public void logError(String msg) {
stderr.println(msg);
forcedExitCode = -1;
}
/**
* Invoked by the executor service.
*/
public void run() {
// Unique nr that identifies this request.
int thisRequest = compilerPool.startRequest();
long start = System.currentTimeMillis();
int numClasses = 0;
StringBuilder compiledPkgs = new StringBuilder();
use();
PrintWriter out = null;
try {
javacServer.log("<"+thisRequest+"> Connect from "+socket.getRemoteSocketAddress()+" activethreads="+compilerPool.numActiveRequests());
BufferedReader in = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
out = new PrintWriter(new OutputStreamWriter(
socket.getOutputStream()));
if (!expect(in, JavacServer.PROTOCOL_COOKIE_VERSION)) {
javacServer.log("<"+thisRequest+"> Bad protocol from ip "+socket.getRemoteSocketAddress());
return;
}
String cookie = in.readLine();
if (cookie == null || !cookie.equals(""+javacServer.getCookie())) {
javacServer.log("<"+thisRequest+"> Bad cookie from ip "+socket.getRemoteSocketAddress());
return;
}
if (!expect(in, JavacServer.PROTOCOL_CWD)) {
return;
}
String cwd = in.readLine();
if (cwd == null)
return;
if (!expect(in, JavacServer.PROTOCOL_ID)) {
return;
}
id = in.readLine();
if (id == null)
return;
if (!expect(in, JavacServer.PROTOCOL_ARGS)) {
return;
}
ArrayList<String> the_options = new ArrayList<>();
ArrayList<File> the_classes = new ArrayList<>();
Iterable<File> path = Arrays.<File> asList(new File(cwd));
for (;;) {
String l = in.readLine();
if (l == null)
return;
if (l.equals(JavacServer.PROTOCOL_SOURCES_TO_COMPILE))
break;
if (l.startsWith("--server:"))
continue;
if (!l.startsWith("-") && l.endsWith(".java")) {
the_classes.add(new File(l));
numClasses++;
} else {
the_options.add(l);
}
continue;
}
// Load sources to compile
Set<URI> sourcesToCompile = new HashSet<>();
for (;;) {
String l = in.readLine();
if (l == null)
return;
if (l.equals(JavacServer.PROTOCOL_VISIBLE_SOURCES))
break;
try {
sourcesToCompile.add(new URI(l));
numClasses++;
} catch (URISyntaxException e) {
return;
}
}
// Load visible sources
Set<URI> visibleSources = new HashSet<>();
boolean fix_drive_letter_case =
StringUtils.toLowerCase(System.getProperty("os.name")).startsWith("windows");
for (;;) {
String l = in.readLine();
if (l == null)
return;
if (l.equals(JavacServer.PROTOCOL_END))
break;
try {
URI u = new URI(l);
if (fix_drive_letter_case) {
// Make sure the driver letter is lower case.
String s = u.toString();
if (s.startsWith("file:/") &&
Character.isUpperCase(s.charAt(6))) {
u = new URI("file:/"+Character.toLowerCase(s.charAt(6))+s.substring(7));
}
}
visibleSources.add(u);
} catch (URISyntaxException e) {
return;
}
}
// A completed request has been received.
// Now setup the actual compilation....
// First deal with explicit source files on cmdline and in at file.
ListBuffer<JavaFileObject> compilationUnits = new ListBuffer<>();
for (JavaFileObject i : fileManager.getJavaFileObjectsFromFiles(the_classes)) {
compilationUnits.append(i);
}
// Now deal with sources supplied as source_to_compile.
ListBuffer<File> sourcesToCompileFiles = new ListBuffer<>();
for (URI u : sourcesToCompile) {
sourcesToCompileFiles.append(new File(u));
}
for (JavaFileObject i : fileManager.getJavaFileObjectsFromFiles(sourcesToCompileFiles)) {
compilationUnits.append(i);
}
// Log the options to be used.
StringBuilder options = new StringBuilder();
for (String s : the_options) {
options.append(">").append(s).append("< ");
}
javacServer.log(id+" <"+thisRequest+"> options "+options.toString());
forcedExitCode = 0;
// Create a new logger.
StringWriter stdoutLog = new StringWriter();
StringWriter stderrLog = new StringWriter();
stdout = new PrintWriter(stdoutLog);
stderr = new PrintWriter(stderrLog);
com.sun.tools.javac.main.Main.Result rc = com.sun.tools.javac.main.Main.Result.OK;
try {
if (compilationUnits.size() > 0) {
// Bind the new logger to the existing context.
context.put(Log.outKey, stderr);
Log.instance(context).setWriter(Log.WriterKind.NOTICE, stdout);
Log.instance(context).setWriter(Log.WriterKind.WARNING, stderr);
Log.instance(context).setWriter(Log.WriterKind.ERROR, stderr);
// Process the options.
com.sun.tools.javac.api.JavacTool.processOptions(context, smartFileManager, the_options);
fileManagerBase.setContext(context);
smartFileManager.setVisibleSources(visibleSources);
smartFileManager.cleanArtifacts();
smartFileManager.setLog(stdout);
Dependencies.instance(context).reset();
com.sun.tools.javac.main.Main ccompiler = new com.sun.tools.javac.main.Main("javacTask", stderr);
String[] aa = the_options.toArray(new String[0]);
// Do the compilation!
rc = ccompiler.compile(aa, context, compilationUnits.toList(), null);
while (numActiveSubTasks()>0) {
try { Thread.sleep(1000); } catch (InterruptedException e) { }
}
smartFileManager.flush();
}
} catch (Exception e) {
stderr.println(e.getMessage());
forcedExitCode = -1;
}
// Send the response..
out.println(JavacServer.PROTOCOL_STDOUT);
out.print(stdoutLog);
out.println(JavacServer.PROTOCOL_STDERR);
out.print(stderrLog);
// The compilation is complete! And errors will have already been printed on out!
out.println(JavacServer.PROTOCOL_PACKAGE_ARTIFACTS);
Map<String,Set<URI>> pa = smartFileManager.getPackageArtifacts();
for (String aPkgName : pa.keySet()) {
out.println("+"+aPkgName);
Set<URI> as = pa.get(aPkgName);
for (URI a : as) {
out.println(" "+a.toString());
}
}
Dependencies deps = Dependencies.instance(context);
out.println(JavacServer.PROTOCOL_PACKAGE_DEPENDENCIES);
Map<String,Set<String>> pd = deps.getDependencies();
for (String aPkgName : pd.keySet()) {
out.println("+"+aPkgName);
Set<String> ds = pd.get(aPkgName);
// Everything depends on java.lang
if (!ds.contains(":java.lang")) ds.add(":java.lang");
for (String d : ds) {
out.println(" "+d);
}
}
out.println(JavacServer.PROTOCOL_PACKAGE_PUBLIC_APIS);
Map<String,String> pp = deps.getPubapis();
for (String aPkgName : pp.keySet()) {
out.println("+"+aPkgName);
String ps = pp.get(aPkgName);
// getPubapis added a space to each line!
out.println(ps);
compiledPkgs.append(aPkgName+" ");
}
out.println(JavacServer.PROTOCOL_SYSINFO);
out.println("num_cores=" + Runtime.getRuntime().availableProcessors());
out.println("max_memory=" + Runtime.getRuntime().maxMemory());
out.println(JavacServer.PROTOCOL_RETURN_CODE);
// Errors from sjavac that affect compilation status!
int rcv = rc.exitCode;
if (rcv == 0 && forcedExitCode != 0) {
rcv = forcedExitCode;
}
out.println("" + rcv);
out.println(JavacServer.PROTOCOL_END);
out.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (out != null) out.close();
if (!socket.isClosed()) {
socket.close();
}
socket = null;
} catch (Exception e) {
javacServer.log("ERROR "+e);
e.printStackTrace();
}
compilerPool.stopRequest();
long duration = System.currentTimeMillis()-start;
javacServer.addBuildTime(duration);
float classpersec = ((float)numClasses)*(((float)1000.0)/((float)duration));
javacServer.log(id+" <"+thisRequest+"> "+compiledPkgs+" duration " + duration+ " ms num_classes="+numClasses+
" classpersec="+classpersec+" subtasks="+subTasks.size());
javacServer.flushLog();
unuse();
compilerPool.returnCompilerThread(this);
}
}
}