blob: 0ecfe76171601aa61283607c948b52e3c4eb8786 [file] [log] [blame]
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jps.javac;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.ChannelGroupFuture;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
import io.netty.util.concurrent.ImmediateEventExecutor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.api.CanceledStatus;
import org.jetbrains.jps.builders.impl.java.JavacCompilerTool;
import org.jetbrains.jps.builders.java.JavaBuilderUtil;
import org.jetbrains.jps.builders.java.JavaCompilingTool;
import org.jetbrains.jps.service.SharedThreadPool;
import javax.tools.*;
import java.io.File;
import java.util.*;
/**
* @author Eugene Zhuravlev
* Date: 1/22/12
*/
@SuppressWarnings("UseOfSystemOutOrSystemErr")
public class JavacServer {
public static final int DEFAULT_SERVER_PORT = 7878;
public static final String SERVER_SUCCESS_START_MESSAGE = "Javac server started successfully. Listening on port: ";
public static final String SERVER_ERROR_START_MESSAGE = "Error starting Javac Server: ";
public static final String JPS_JAVA_COMPILING_TOOL_PROPERTY = "jps.java.compiling.tool";
private ChannelRegistrar myChannelRegistrar;
public void start(int listenPort) {
final ServerBootstrap bootstrap = new ServerBootstrap().group(new NioEventLoopGroup(1, SharedThreadPool.getInstance())).channel(NioServerSocketChannel.class);
bootstrap.childOption(ChannelOption.TCP_NODELAY, true).childOption(ChannelOption.SO_KEEPALIVE, true);
myChannelRegistrar = new ChannelRegistrar();
final ChannelHandler compilationRequestsHandler = new CompilationRequestsHandler();
bootstrap.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(Channel channel) throws Exception {
channel.pipeline().addLast(myChannelRegistrar,
new ProtobufVarint32FrameDecoder(),
new ProtobufDecoder(JavacRemoteProto.Message.getDefaultInstance()),
new ProtobufVarint32LengthFieldPrepender(),
new ProtobufEncoder(),
compilationRequestsHandler);
}
});
myChannelRegistrar.add(bootstrap.bind(listenPort).syncUninterruptibly().channel());
}
public void stop() {
myChannelRegistrar.close().awaitUninterruptibly();
}
public static void main(String[] args) {
JavacServer server = null;
try {
int port = DEFAULT_SERVER_PORT;
if (args.length > 0) {
try {
port = Integer.parseInt(args[0]);
}
catch (NumberFormatException e) {
System.err.println("Error parsing port: " + e.getMessage());
System.exit(-1);
}
}
server = new JavacServer();
server.start(port);
final JavacServer finalServer = server;
Runtime.getRuntime().addShutdownHook(new Thread("Shutdown hook thread") {
@Override
public void run() {
finalServer.stop();
}
});
System.out.println("Server classpath: " + System.getProperty("java.class.path"));
System.err.println(SERVER_SUCCESS_START_MESSAGE + port);
}
catch (Throwable e) {
System.err.println(SERVER_ERROR_START_MESSAGE + e.getMessage());
e.printStackTrace(System.err);
try {
if (server != null) {
server.stop();
}
}
finally {
System.exit(-1);
}
}
}
public static JavacRemoteProto.Message compile(final ChannelHandlerContext context,
final UUID sessionId,
List<String> options,
Collection<File> files,
Collection<File> classpath,
Collection<File> platformCp,
Collection<File> sourcePath,
Map<File, Set<File>> outs,
final CanceledStatus canceledStatus) {
final DiagnosticOutputConsumer diagnostic = new DiagnosticOutputConsumer() {
@Override
public void javaFileLoaded(File file) {
context.channel().writeAndFlush(JavacProtoUtil.toMessage(sessionId, JavacProtoUtil.createSourceFileLoadedResponse(file)));
}
@Override
public void outputLineAvailable(String line) {
context.channel().writeAndFlush(JavacProtoUtil.toMessage(sessionId, JavacProtoUtil.createStdOutputResponse(line)));
}
@Override
public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
final JavacRemoteProto.Message.Response response = JavacProtoUtil.createBuildMessageResponse(diagnostic);
context.channel().writeAndFlush(JavacProtoUtil.toMessage(sessionId, response));
}
@Override
public void registerImports(String className, Collection<String> imports, Collection<String> staticImports) {
final JavacRemoteProto.Message.Response response = JavacProtoUtil.createClassDataResponse(className, imports, staticImports);
context.channel().writeAndFlush(JavacProtoUtil.toMessage(sessionId, response));
}
};
final OutputFileConsumer outputSink = new OutputFileConsumer() {
@Override
public void save(@NotNull OutputFileObject fileObject) {
context.channel().writeAndFlush(JavacProtoUtil.toMessage(sessionId, JavacProtoUtil.createOutputObjectResponse(fileObject)));
}
};
try {
JavaCompilingTool tool = getCompilingTool();
final boolean rc = JavacMain.compile(options, files, classpath, platformCp, sourcePath, outs, diagnostic, outputSink, canceledStatus, tool);
return JavacProtoUtil.toMessage(sessionId, JavacProtoUtil.createBuildCompletedResponse(rc));
}
catch (Throwable e) {
//noinspection UseOfSystemOutOrSystemErr
e.printStackTrace(System.err);
return JavacProtoUtil.toMessage(sessionId, JavacProtoUtil.createFailure(e.getMessage(), e));
}
}
private static JavaCompilingTool getCompilingTool() {
String property = System.getProperty(JPS_JAVA_COMPILING_TOOL_PROPERTY);
if (property != null) {
JavaCompilingTool tool = JavaBuilderUtil.findCompilingTool(property);
if (tool != null) {
return tool;
}
}
return new JavacCompilerTool();
}
private final Set<CancelHandler> myCancelHandlers = Collections.synchronizedSet(new HashSet<CancelHandler>());
public void cancelBuilds() {
synchronized (myCancelHandlers) {
for (CancelHandler handler : myCancelHandlers) {
handler.cancel();
}
}
}
private static List<File> toFiles(List<String> paths) {
final List<File> files = new ArrayList<File>(paths.size());
for (String path : paths) {
files.add(new File(path));
}
return files;
}
@ChannelHandler.Sharable
private class CompilationRequestsHandler extends SimpleChannelInboundHandler<JavacRemoteProto.Message> {
@Override
public void channelRead0(final ChannelHandlerContext context, JavacRemoteProto.Message message) throws Exception {
final UUID sessionId = JavacProtoUtil.fromProtoUUID(message.getSessionId());
final JavacRemoteProto.Message.Type messageType = message.getMessageType();
JavacRemoteProto.Message reply = null;
try {
if (messageType == JavacRemoteProto.Message.Type.REQUEST) {
final JavacRemoteProto.Message.Request request = message.getRequest();
final JavacRemoteProto.Message.Request.Type requestType = request.getRequestType();
if (requestType == JavacRemoteProto.Message.Request.Type.COMPILE) {
final List<String> options = request.getOptionList();
final List<File> files = toFiles(request.getFileList());
final List<File> cp = toFiles(request.getClasspathList());
final List<File> platformCp = toFiles(request.getPlatformClasspathList());
final List<File> srcPath = toFiles(request.getSourcepathList());
final Map<File, Set<File>> outs = new HashMap<File, Set<File>>();
for (JavacRemoteProto.Message.Request.OutputGroup outputGroup : request.getOutputList()) {
final Set<File> srcRoots = new HashSet<File>();
for (String root : outputGroup.getSourceRootList()) {
srcRoots.add(new File(root));
}
outs.put(new File(outputGroup.getOutputRoot()), srcRoots);
}
final CancelHandler cancelHandler = new CancelHandler();
myCancelHandlers.add(cancelHandler);
SharedThreadPool.getInstance().executeOnPooledThread(new Runnable() {
@Override
public void run() {
try {
context.channel()
.writeAndFlush(compile(context, sessionId, options, files, cp, platformCp, srcPath, outs, cancelHandler));
}
finally {
myCancelHandlers.remove(cancelHandler);
}
}
});
}
else if (requestType == JavacRemoteProto.Message.Request.Type.CANCEL){
cancelBuilds();
reply = JavacProtoUtil.toMessage(sessionId, JavacProtoUtil.createRequestAckResponse());
}
else if (requestType == JavacRemoteProto.Message.Request.Type.SHUTDOWN){
cancelBuilds();
new Thread("StopThread") {
@Override
public void run() {
//noinspection finally
try {
JavacServer.this.stop();
}
finally {
System.exit(0);
}
}
}.start();
}
else {
reply = JavacProtoUtil.toMessage(sessionId, JavacProtoUtil.createFailure("Unsupported request type: " + requestType.name(), null));
}
}
else {
reply = JavacProtoUtil.toMessage(sessionId, JavacProtoUtil.createFailure("Unsupported message: " + messageType.name(), null));
}
}
finally {
if (reply != null) {
context.channel().writeAndFlush(reply);
}
}
}
}
@ChannelHandler.Sharable
private static final class ChannelRegistrar extends ChannelInboundHandlerAdapter {
private final ChannelGroup openChannels = new DefaultChannelGroup(ImmediateEventExecutor.INSTANCE);
public boolean isEmpty() {
return openChannels.isEmpty();
}
public void add(@NotNull Channel serverChannel) {
assert serverChannel instanceof ServerChannel;
openChannels.add(serverChannel);
}
@Override
public void channelActive(ChannelHandlerContext context) throws Exception {
// we don't need to remove channel on close - ChannelGroup do it
openChannels.add(context.channel());
super.channelActive(context);
}
public ChannelGroupFuture close() {
EventLoopGroup eventLoopGroup = null;
for (Channel channel : openChannels) {
if (channel instanceof ServerChannel) {
eventLoopGroup = channel.eventLoop().parent();
break;
}
}
ChannelGroupFuture future;
try {
future = openChannels.close();
}
finally {
assert eventLoopGroup != null;
eventLoopGroup.shutdownGracefully();
}
return future;
}
}
private static class CancelHandler implements CanceledStatus {
private volatile boolean myIsCanceled = false;
private CancelHandler() {
}
public void cancel() {
myIsCanceled = true;
}
@Override
public boolean isCanceled() {
return myIsCanceled;
}
}
}