diff --git a/java/compiler/impl/compiler-impl.iml b/java/compiler/impl/compiler-impl.iml index 57d6c410b0b2..ff23af7fa33f 100644 --- a/java/compiler/impl/compiler-impl.iml +++ b/java/compiler/impl/compiler-impl.iml @@ -23,6 +23,7 @@ + diff --git a/java/compiler/impl/src/com/intellij/compiler/CompileServerManager.java b/java/compiler/impl/src/com/intellij/compiler/CompileServerManager.java index 4457a9d94f92..c79d369873f4 100644 --- a/java/compiler/impl/src/com/intellij/compiler/CompileServerManager.java +++ b/java/compiler/impl/src/com/intellij/compiler/CompileServerManager.java @@ -376,7 +376,7 @@ public class CompileServerManager implements ApplicationComponent{ @NotNull @Override public String getComponentName() { - return "com.intellij.compiler.JpsServerManager"; + return "com.intellij.compiler.CompileServerManager"; } // executed in one thread at a time @@ -609,10 +609,10 @@ public class CompileServerManager implements ApplicationComponent{ } if (Registry.is("compiler.server.use.memory.temp.cache")) { - cmdLine.addParameter("-D"+ GlobalOptions.USE_MEMORY_TEMP_CACHE_OPTION + "=true"); + cmdLine.addParameter("-D"+ GlobalOptions.USE_MEMORY_TEMP_CACHE_OPTION); } if (Registry.is("compiler.server.use.external.javac.process")) { - cmdLine.addParameter("-D"+ GlobalOptions.USE_EXTERNAL_JAVAC_OPTION + "=true"); + cmdLine.addParameter("-D"+ GlobalOptions.USE_EXTERNAL_JAVAC_OPTION); } cmdLine.addParameter("-D"+ GlobalOptions.HOSTNAME_OPTION + "=" + NetUtils.getLocalHostString()); diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java b/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java index e3de4408059a..16524a9c94c5 100644 --- a/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java +++ b/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java @@ -28,6 +28,8 @@ import com.intellij.compiler.make.CacheUtils; import com.intellij.compiler.make.ChangedConstantsDependencyProcessor; import com.intellij.compiler.make.DependencyCache; import com.intellij.compiler.progress.CompilerTask; +import com.intellij.compiler.server.BuildManager; +import com.intellij.compiler.server.BuildMessageHandler; import com.intellij.diagnostic.IdeErrorsDialog; import com.intellij.diagnostic.PluginException; import com.intellij.openapi.application.ApplicationManager; @@ -91,6 +93,7 @@ import gnu.trove.TObjectHashingStrategy; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.api.CmdlineRemoteProto; import org.jetbrains.jps.api.JpsRemoteProto; import org.jetbrains.jps.api.JpsServerResponseHandler; import org.jetbrains.jps.api.RequestFuture; @@ -104,6 +107,7 @@ public class CompileDriver { private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.CompileDriver"); // to be used in tests only for debug output public static volatile boolean ourDebugMode = false; + private static final boolean OUT_OF_PROCESS_MAKE_AS_SERVER = true; // out-of-process make implementation switch (server / per-project build process) private final Project myProject; private final Map, Pair> myGenerationCompilerModuleToOutputDirMap; // [IntermediateOutputCompiler, Module] -> [ProductionSources, TestSources] @@ -427,9 +431,6 @@ public class CompileDriver { artifactNames.add(artifact.getName()); } - final CompileServerManager csManager = CompileServerManager.getInstance(); - final MessageBus messageBus = myProject.getMessageBus(); - csManager.cancelAutoMakeTasks(myProject); final CompileScope scope = compileContext.getCompileScope(); // need to pass scope's user data to server final Map exported = scope.exportUserData(); @@ -445,94 +446,190 @@ public class CompileDriver { else { builderParams = Collections.emptyMap(); } - return csManager.submitCompilationTask(myProject, compileContext.isRebuild(), compileContext.isMake(), moduleNames, artifactNames, paths, - builderParams, new JpsServerResponseHandler() { - @Override - public void handleCompileMessage(JpsRemoteProto.Message.Response.CompileMessage compilerMessage) { - final JpsRemoteProto.Message.Response.CompileMessage.Kind kind = compilerMessage.getKind(); - //System.out.println(compilerMessage.getText()); - if (kind == JpsRemoteProto.Message.Response.CompileMessage.Kind.PROGRESS) { - final ProgressIndicator indicator = compileContext.getProgressIndicator(); - indicator.setText(compilerMessage.getText()); - if (compilerMessage.hasDone()) { - indicator.setFraction(compilerMessage.getDone()); - } - } - else { - final CompilerMessageCategory category = kind == JpsRemoteProto.Message.Response.CompileMessage.Kind.ERROR ? CompilerMessageCategory.ERROR - : kind == JpsRemoteProto.Message.Response.CompileMessage.Kind.WARNING ? CompilerMessageCategory.WARNING : CompilerMessageCategory.INFORMATION; + final MessageBus messageBus = myProject.getMessageBus(); - String sourceFilePath = compilerMessage.hasSourceFilePath() ? compilerMessage.getSourceFilePath() : null; - if (sourceFilePath != null) { - sourceFilePath = FileUtil.toSystemIndependentName(sourceFilePath); - } - final long line = compilerMessage.hasLine() ? compilerMessage.getLine() : -1; - final long column = compilerMessage.hasColumn() ? compilerMessage.getColumn() : -1; - final String srcUrl = sourceFilePath != null ? VirtualFileManager.constructUrl(LocalFileSystem.PROTOCOL, sourceFilePath) : null; - compileContext.addMessage( - category, compilerMessage.getText(), srcUrl, (int)line, (int)column - ); - } - } + if (OUT_OF_PROCESS_MAKE_AS_SERVER) { + final CompileServerManager csManager = CompileServerManager.getInstance(); + csManager.cancelAutoMakeTasks(myProject); + return csManager.submitCompilationTask(myProject, compileContext.isRebuild(), compileContext.isMake(), moduleNames, artifactNames, paths, + builderParams, new JpsServerResponseHandler() { - @Override - public boolean handleBuildEvent(JpsRemoteProto.Message.Response.BuildEvent event) { - final JpsRemoteProto.Message.Response.BuildEvent.Type eventType = event.getEventType(); - switch (eventType) { - case BUILD_STARTED: - //compileContext.getProgressIndicator().setText("Compilation started"); - break; - case FILES_GENERATED: - final List generated = event.getGeneratedFilesList(); - final CompilationStatusListener publisher = messageBus.syncPublisher(CompilerTopics.COMPILATION_STATUS); - for (JpsRemoteProto.Message.Response.BuildEvent.GeneratedFile generatedFile : generated) { - final String root = FileUtil.toSystemIndependentName(generatedFile.getOutputRoot()); - final String relativePath = FileUtil.toSystemIndependentName(generatedFile.getRelativePath()); - publisher.fileGenerated(root, relativePath); + @Override + public void handleCompileMessage(JpsRemoteProto.Message.Response.CompileMessage compilerMessage) { + final JpsRemoteProto.Message.Response.CompileMessage.Kind kind = compilerMessage.getKind(); + //System.out.println(compilerMessage.getText()); + if (kind == JpsRemoteProto.Message.Response.CompileMessage.Kind.PROGRESS) { + final ProgressIndicator indicator = compileContext.getProgressIndicator(); + indicator.setText(compilerMessage.getText()); + if (compilerMessage.hasDone()) { + indicator.setFraction(compilerMessage.getDone()); } - break; - case BUILD_COMPLETED: - ExitStatus status = ExitStatus.SUCCESS; - if (event.hasCompletionStatus()) { - final JpsRemoteProto.Message.Response.BuildEvent.Status completionStatus = event.getCompletionStatus(); - switch(completionStatus) { - case CANCELED: - status = ExitStatus.CANCELLED; - break; - case ERRORS: - status = ExitStatus.ERRORS; - break; - case SUCCESS: - status = ExitStatus.SUCCESS; - break; - case UP_TO_DATE: - status = ExitStatus.UP_TO_DATE; - break; + } + else { + final CompilerMessageCategory category = kind == JpsRemoteProto.Message.Response.CompileMessage.Kind.ERROR ? CompilerMessageCategory.ERROR + : kind == JpsRemoteProto.Message.Response.CompileMessage.Kind.WARNING ? CompilerMessageCategory.WARNING : CompilerMessageCategory.INFORMATION; + + String sourceFilePath = compilerMessage.hasSourceFilePath() ? compilerMessage.getSourceFilePath() : null; + if (sourceFilePath != null) { + sourceFilePath = FileUtil.toSystemIndependentName(sourceFilePath); + } + final long line = compilerMessage.hasLine() ? compilerMessage.getLine() : -1; + final long column = compilerMessage.hasColumn() ? compilerMessage.getColumn() : -1; + final String srcUrl = sourceFilePath != null ? VirtualFileManager.constructUrl(LocalFileSystem.PROTOCOL, sourceFilePath) : null; + compileContext.addMessage( + category, compilerMessage.getText(), srcUrl, (int)line, (int)column + ); + } + } + + @Override + public boolean handleBuildEvent(JpsRemoteProto.Message.Response.BuildEvent event) { + final JpsRemoteProto.Message.Response.BuildEvent.Type eventType = event.getEventType(); + switch (eventType) { + case BUILD_STARTED: + //compileContext.getProgressIndicator().setText("Compilation started"); + break; + case FILES_GENERATED: + final List generated = event.getGeneratedFilesList(); + final CompilationStatusListener publisher = messageBus.syncPublisher(CompilerTopics.COMPILATION_STATUS); + for (JpsRemoteProto.Message.Response.BuildEvent.GeneratedFile generatedFile : generated) { + final String root = FileUtil.toSystemIndependentName(generatedFile.getOutputRoot()); + final String relativePath = FileUtil.toSystemIndependentName(generatedFile.getRelativePath()); + publisher.fileGenerated(root, relativePath); } + break; + case BUILD_COMPLETED: + ExitStatus status = ExitStatus.SUCCESS; + if (event.hasCompletionStatus()) { + final JpsRemoteProto.Message.Response.BuildEvent.Status completionStatus = event.getCompletionStatus(); + switch(completionStatus) { + case CANCELED: + status = ExitStatus.CANCELLED; + break; + case ERRORS: + status = ExitStatus.ERRORS; + break; + case SUCCESS: + status = ExitStatus.SUCCESS; + break; + case UP_TO_DATE: + status = ExitStatus.UP_TO_DATE; + break; + } + } + compileContext.putUserDataIfAbsent(COMPILE_SERVER_BUILD_STATUS, status); + break; + } + return eventType == JpsRemoteProto.Message.Response.BuildEvent.Type.BUILD_COMPLETED; + } + + @Override + public void handleFailure(JpsRemoteProto.Message.Failure failure) { + compileContext.addMessage(CompilerMessageCategory.ERROR, failure.getDescription(), null, -1, -1); + final String trace = failure.getStacktrace(); + if (trace != null) { + LOG.info(trace); + System.out.println(trace); + } + compileContext.putUserData(COMPILE_SERVER_BUILD_STATUS, ExitStatus.ERRORS); + } + + @Override + public void sessionTerminated() { + notifyCompilationCompleted(compileContext, callback, COMPILE_SERVER_BUILD_STATUS.get(compileContext, ExitStatus.SUCCESS)); + } + }); + } + else { + final BuildManager buildManager = BuildManager.getInstance(); + buildManager.cancelAutoMakeTasks(myProject); + return buildManager.scheduleBuild(myProject, compileContext.isRebuild(), compileContext.isMake(), moduleNames, artifactNames, paths, builderParams, new BuildMessageHandler() { + @Override + public void sessionTerminated() { + final ExitStatus status = COMPILE_SERVER_BUILD_STATUS.get(compileContext); + if (status != null) { + notifyCompilationCompleted(compileContext, callback, status); + } + } + + @Override + public void handleFailure(CmdlineRemoteProto.Message.Failure failure) { + compileContext.addMessage(CompilerMessageCategory.ERROR, failure.getDescription(), null, -1, -1); + final String trace = failure.getStacktrace(); + if (trace != null) { + LOG.info(trace); + System.out.println(trace); + } + compileContext.putUserData(COMPILE_SERVER_BUILD_STATUS, ExitStatus.ERRORS); + } + + @Override + protected void handleCompileMessage(CmdlineRemoteProto.Message.BuilderMessage.CompileMessage message) { + final CmdlineRemoteProto.Message.BuilderMessage.CompileMessage.Kind kind = message.getKind(); + //System.out.println(compilerMessage.getText()); + if (kind == CmdlineRemoteProto.Message.BuilderMessage.CompileMessage.Kind.PROGRESS) { + final ProgressIndicator indicator = compileContext.getProgressIndicator(); + indicator.setText(message.getText()); + if (message.hasDone()) { + indicator.setFraction(message.getDone()); } - compileContext.putUserDataIfAbsent(COMPILE_SERVER_BUILD_STATUS, status); - break; - } - return eventType == JpsRemoteProto.Message.Response.BuildEvent.Type.BUILD_COMPLETED; - } + } + else { + final CompilerMessageCategory category = kind == CmdlineRemoteProto.Message.BuilderMessage.CompileMessage.Kind.ERROR ? CompilerMessageCategory.ERROR + : kind == CmdlineRemoteProto.Message.BuilderMessage.CompileMessage.Kind.WARNING ? CompilerMessageCategory.WARNING : CompilerMessageCategory.INFORMATION; - @Override - public void handleFailure(JpsRemoteProto.Message.Failure failure) { - compileContext.addMessage(CompilerMessageCategory.ERROR, failure.getDescription(), null, -1, -1); - final String trace = failure.getStacktrace(); - if (trace != null) { - LOG.info(trace); - System.out.println(trace); + String sourceFilePath = message.hasSourceFilePath() ? message.getSourceFilePath() : null; + if (sourceFilePath != null) { + sourceFilePath = FileUtil.toSystemIndependentName(sourceFilePath); + } + final long line = message.hasLine() ? message.getLine() : -1; + final long column = message.hasColumn() ? message.getColumn() : -1; + final String srcUrl = sourceFilePath != null ? VirtualFileManager.constructUrl(LocalFileSystem.PROTOCOL, sourceFilePath) : null; + compileContext.addMessage( + category, message.getText(), srcUrl, (int)line, (int)column + ); + } } - compileContext.putUserData(COMPILE_SERVER_BUILD_STATUS, ExitStatus.ERRORS); - } - @Override - public void sessionTerminated() { - notifyCompilationCompleted(compileContext, callback, COMPILE_SERVER_BUILD_STATUS.get(compileContext, ExitStatus.SUCCESS)); - } - }); + @Override + protected void handleBuildEvent(CmdlineRemoteProto.Message.BuilderMessage.BuildEvent event) { + final CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.Type eventType = event.getEventType(); + switch (eventType) { + case FILES_GENERATED: + final List generated = event.getGeneratedFilesList(); + final CompilationStatusListener publisher = messageBus.syncPublisher(CompilerTopics.COMPILATION_STATUS); + for (CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.GeneratedFile generatedFile : generated) { + final String root = FileUtil.toSystemIndependentName(generatedFile.getOutputRoot()); + final String relativePath = FileUtil.toSystemIndependentName(generatedFile.getRelativePath()); + publisher.fileGenerated(root, relativePath); + } + break; + case BUILD_COMPLETED: + ExitStatus status = ExitStatus.SUCCESS; + if (event.hasCompletionStatus()) { + final CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.Status completionStatus = event.getCompletionStatus(); + switch(completionStatus) { + case CANCELED: + status = ExitStatus.CANCELLED; + break; + case ERRORS: + status = ExitStatus.ERRORS; + break; + case SUCCESS: + status = ExitStatus.SUCCESS; + break; + case UP_TO_DATE: + status = ExitStatus.UP_TO_DATE; + break; + } + } + compileContext.putUserDataIfAbsent(COMPILE_SERVER_BUILD_STATUS, status); + break; + } + } + }); + } } @@ -592,7 +689,7 @@ public class CompileDriver { if (myProject.isDisposed()) { return; } - LOG.info("COMPILATION STARTED (COMPILE SERVER)"); + LOG.info("COMPILATION STARTED " + (OUT_OF_PROCESS_MAKE_AS_SERVER? "(COMPILE SERVER)" : "(BUILD PROCESS)")); if (message != null) { compileContext.addMessage(message); } @@ -618,7 +715,7 @@ public class CompileDriver { finally { final long finish = System.currentTimeMillis(); CompilerUtil.logDuration( - "\tCOMPILATION FINISHED (COMPILE SERVER); Errors: " + + "\tCOMPILATION FINISHED " + (OUT_OF_PROCESS_MAKE_AS_SERVER? "(COMPILE SERVER)" : "(BUILD PROCESS)") + "; Errors: " + compileContext.getMessageCount(CompilerMessageCategory.ERROR) + "; warnings: " + compileContext.getMessageCount(CompilerMessageCategory.WARNING), diff --git a/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java b/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java new file mode 100644 index 000000000000..ef9fc662c3e4 --- /dev/null +++ b/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java @@ -0,0 +1,956 @@ +/* + * 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 com.intellij.compiler.server; + +import com.intellij.ProjectTopics; +import com.intellij.application.options.PathMacrosImpl; +import com.intellij.compiler.CompilerWorkspaceConfiguration; +import com.intellij.compiler.server.impl.CompileServerClasspathManager; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.configurations.GeneralCommandLine; +import com.intellij.execution.process.OSProcessHandler; +import com.intellij.execution.process.ProcessAdapter; +import com.intellij.execution.process.ProcessEvent; +import com.intellij.notification.Notification; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.PathMacros; +import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.compiler.CompilationStatusListener; +import com.intellij.openapi.compiler.CompilerManager; +import com.intellij.openapi.compiler.CompilerTopics; +import com.intellij.openapi.components.ApplicationComponent; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.project.ProjectManagerAdapter; +import com.intellij.openapi.projectRoots.*; +import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl; +import com.intellij.openapi.roots.ModuleRootEvent; +import com.intellij.openapi.roots.ModuleRootListener; +import com.intellij.openapi.roots.OrderRootType; +import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar; +import com.intellij.openapi.ui.MessageType; +import com.intellij.openapi.util.JDOMUtil; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.ShutDownTracker; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.*; +import com.intellij.openapi.vfs.encoding.EncodingManager; +import com.intellij.openapi.vfs.newvfs.BulkFileListener; +import com.intellij.openapi.vfs.newvfs.events.VFileEvent; +import com.intellij.problems.Problem; +import com.intellij.problems.WolfTheProblemSolver; +import com.intellij.util.Alarm; +import com.intellij.util.containers.ConcurrentHashSet; +import com.intellij.util.messages.MessageBusConnection; +import com.intellij.util.net.NetUtils; +import org.jboss.netty.bootstrap.ServerBootstrap; +import org.jboss.netty.channel.*; +import org.jboss.netty.channel.group.ChannelGroup; +import org.jboss.netty.channel.group.ChannelGroupFuture; +import org.jboss.netty.channel.group.DefaultChannelGroup; +import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; +import org.jboss.netty.handler.codec.protobuf.ProtobufDecoder; +import org.jboss.netty.handler.codec.protobuf.ProtobufEncoder; +import org.jboss.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; +import org.jboss.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; +import org.jdom.Element; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.api.*; +import org.jetbrains.jps.cmdline.BuildMain; +import org.jetbrains.jps.server.ClasspathBootstrap; +import org.jetbrains.jps.server.Server; + +import javax.tools.*; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.InetSocketAddress; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * @author Eugene Zhuravlev + * Date: 9/6/11 + */ +public class BuildManager implements ApplicationComponent{ + private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.server.BuildManager"); + private static final String SYSTEM_ROOT = "compile-server"; + private static final String LOGGER_CONFIG = "log.xml"; + private static final String DEFAULT_LOGGER_CONFIG = "defaultLogConfig.xml"; + private final File mySystemDirectory; + private final ProjectManager myProjectManager; + private static final int MAKE_TRIGGER_DELAY = 5 * 1000 /*5 seconds*/; + private final Map myAutomakeFutures = new HashMap(); + private final CompileServerClasspathManager myClasspathManager = new CompileServerClasspathManager(); + private final Executor myPooledThreadExecutor = new Executor() { + @Override + public void execute(Runnable command) { + ApplicationManager.getApplication().executeOnPooledThread(command); + } + }; + private final Map myProjectToExecutorMap = Collections.synchronizedMap(new HashMap()); + + private final ChannelGroup myAllOpenChannels = new DefaultChannelGroup("compile-manager"); + private final BuildMessageDispatcher myMessageDispatcher = new BuildMessageDispatcher(); + private int myListenPort = -1; + private volatile CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings myGlobals; + + public BuildManager(final ProjectManager projectManager) { + myProjectManager = projectManager; + final String systemPath = PathManager.getSystemPath(); + File system = new File(systemPath); + try { + system = system.getCanonicalFile(); + } + catch (IOException e) { + LOG.info(e); + } + mySystemDirectory = system; + + projectManager.addProjectManagerListener(new ProjectWatcher()); + final MessageBusConnection conn = ApplicationManager.getApplication().getMessageBus().connect(); + conn.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() { + private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD); + private final AtomicBoolean myAutoMakeInProgress = new AtomicBoolean(false); + @Override + public void before(@NotNull List events) { + } + + @Override + public void after(@NotNull List events) { + if (shouldTriggerMake(events)) { + scheduleMake(new Runnable() { + @Override + public void run() { + if (!myAutoMakeInProgress.getAndSet(true)) { + try { + ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { + @Override + public void run() { + try { + runAutoMake(); + } + finally { + myAutoMakeInProgress.set(false); + } + } + }); + } + catch (RejectedExecutionException ignored) { + // we were shut down + } + } + else { + scheduleMake(this); + } + } + }); + } + } + + private void scheduleMake(Runnable runnable) { + myAlarm.cancelAllRequests(); + myAlarm.addRequest(runnable, MAKE_TRIGGER_DELAY); + } + + private boolean shouldTriggerMake(List events) { + for (VFileEvent event : events) { + if (event.isFromRefresh() || event.getRequestor() instanceof SavingRequestor) { + return true; + } + } + return false; + } + }); + + ShutDownTracker.getInstance().registerShutdownTask(new Runnable() { + @Override + public void run() { + stopListening(); + } + }); + } + + public static BuildManager getInstance() { + return ApplicationManager.getApplication().getComponent(BuildManager.class); + } + + public void notifyFilesChanged(Collection paths) { + // todo: update state + } + + public void notifyFilesDeleted(Collection paths) { + // todo: update state + } + + @Nullable + private static String getProjectPath(final Project project) { + final String path = project.getPresentableUrl(); + if (path == null) { + return null; + } + final VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(path); + return vFile != null ? vFile.getPath() : null; + } + + private void clearGlobalsCache() { + myGlobals = null; + } + + private void runAutoMake() { + if (ApplicationManager.getApplication().isUnitTestMode()) { + return; + } + final Project[] openProjects = myProjectManager.getOpenProjects(); + if (openProjects.length > 0) { + final List futures = new ArrayList(); + for (final Project project : openProjects) { + if (project.isDefault() || project.isDisposed()) { + continue; + } + final CompilerWorkspaceConfiguration config = CompilerWorkspaceConfiguration.getInstance(project); + if (!config.useCompileServer() || !config.MAKE_PROJECT_ON_SAVE) { + continue; + } + final List emptyList = Collections.emptyList(); + final RequestFuture future = scheduleBuild( + project, false, true, emptyList, emptyList, emptyList, Collections.emptyMap(), + new AutoMakeMessageHandler(project) + ); + if (future != null) { + futures.add(future); + synchronized (myAutomakeFutures) { + myAutomakeFutures.put(future, project); + } + } + } + try { + for (RequestFuture future : futures) { + future.waitFor(); + } + } + finally { + synchronized (myAutomakeFutures) { + myAutomakeFutures.keySet().removeAll(futures); + } + } + } + } + + public void cancelAutoMakeTasks(Project project) { + synchronized (myAutomakeFutures) { + for (Map.Entry entry : myAutomakeFutures.entrySet()) { + if (entry.getValue().equals(project)) { + entry.getKey().cancel(false); + } + } + } + } + + + // todo: avoid FS scan on every start + + @Nullable + public RequestFuture scheduleBuild( + final Project project, final boolean isRebuild, + final boolean isMake, + final Collection modules, + final Collection artifacts, + final Collection paths, + final Map userData, final BuildMessageHandler handler) { + + final String projectPath = getProjectPath(project); + final UUID sessionId = UUID.randomUUID(); + final CmdlineRemoteProto.Message.ControllerMessage params; + CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings globals = myGlobals; + if (globals == null) { + globals = buildGlobalSettings(); + myGlobals = globals; + } + if (isRebuild) { + params = CmdlineProtoUtil.createRebuildRequest(projectPath, userData, globals); + } + else { + params = isMake ? + CmdlineProtoUtil.createMakeRequest(projectPath, modules, artifacts, userData, globals) : + CmdlineProtoUtil.createForceCompileRequest(projectPath, modules, artifacts, paths, userData, globals); + } + + myMessageDispatcher.registerBuildMessageHandler(sessionId, handler, params); + + // ensure server is listening + if (myListenPort < 0) { + try { + myListenPort = startListening(); + } + catch (Exception e) { + myMessageDispatcher.unregisterBuildMessageHandler(sessionId); + handler.handleFailure(CmdlineProtoUtil.createFailure(e.getMessage(), null)); + handler.sessionTerminated(); + return null; + } + } + + final RequestFuture future = new RequestFuture(handler, sessionId, new RequestFuture.CancelAction() { + @Override + public void cancel(RequestFuture future) throws Exception { + myMessageDispatcher.cancelSession(future.getRequestID()); + } + }); + + SequentialTaskExecutor sequential; + synchronized (myProjectToExecutorMap) { + sequential = myProjectToExecutorMap.get(projectPath); + if (sequential == null) { + sequential = new SequentialTaskExecutor(myPooledThreadExecutor); + myProjectToExecutorMap.put(projectPath, sequential); + } + } + + sequential.submit(new Runnable() { + @Override + public void run() { + try { + if (project.isDisposed()) { + future.cancel(false); + return; + } + final Process process = launchBuildProcess(myListenPort, sessionId); + final OSProcessHandler processHandler = new OSProcessHandler(process, null) { + @Override + protected boolean shouldDestroyProcessRecursively() { + return true; + } + }; + processHandler.addProcessListener(new ProcessAdapter() { + @Override + public void processTerminated(ProcessEvent event) { + final BuildMessageHandler handler = myMessageDispatcher.unregisterBuildMessageHandler(sessionId); + if (handler != null) { + handler.sessionTerminated(); + } + } + + @Override + public void onTextAvailable(ProcessEvent event, Key outputType) { + // re-translate builder's output to idea.log + final String text = event.getText(); + if (!StringUtil.isEmpty(text)) { + LOG.info("BUILDER_PROCESS [" +outputType.toString() +"]: "+ text.trim()); + } + } + }); + processHandler.startNotify(); + processHandler.waitFor(); + } + catch (ExecutionException e) { + myMessageDispatcher.unregisterBuildMessageHandler(sessionId); + handler.handleFailure(CmdlineProtoUtil.createFailure(e.getMessage(), e)); + handler.sessionTerminated(); + } + finally { + future.setDone(); + } + } + }); + + return future; + } + + @Override + public void initComponent() { + } + + @Override + public void disposeComponent() { + stopListening(); + } + + @NotNull + @Override + public String getComponentName() { + return "com.intellij.compiler.server.BuildManager"; + } + + private static CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings buildGlobalSettings() { + final Map data = new HashMap(); + + for (Map.Entry entry : PathMacrosImpl.getGlobalSystemMacros().entrySet()) { + data.put(entry.getKey(), FileUtil.toSystemIndependentName(entry.getValue())); + } + + final PathMacros pathVars = PathMacros.getInstance(); + for (String name : pathVars.getAllMacroNames()) { + final String path = pathVars.getValue(name); + if (path != null) { + data.put(name, FileUtil.toSystemIndependentName(path)); + } + } + + final List globals = new ArrayList(); + + fillSdks(globals); + fillGlobalLibraries(globals); + + final CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings.Builder cmdBuilder = + CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings.newBuilder(); + + if (!data.isEmpty()) { + for (Map.Entry entry : data.entrySet()) { + final String var = entry.getKey(); + final String value = entry.getValue(); + if (var != null && value != null) { + cmdBuilder.addPathVariable(CmdlineProtoUtil.createPair(var, value)); + } + } + } + + if (!globals.isEmpty()) { + for (GlobalLibrary lib : globals) { + final CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings.GlobalLibrary.Builder libBuilder = + CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings.GlobalLibrary.newBuilder(); + libBuilder.setName(lib.getName()).addAllPath(lib.getPaths()); + if (lib instanceof SdkLibrary) { + final SdkLibrary sdk = (SdkLibrary)lib; + libBuilder.setHomePath(sdk.getHomePath()); + libBuilder.setTypeName(sdk.getTypeName()); + final String additional = sdk.getAdditionalDataXml(); + if (additional != null) { + libBuilder.setAdditionalDataXml(additional); + } + final String version = sdk.getVersion(); + if (version != null) { + libBuilder.setVersion(version); + } + } + cmdBuilder.addGlobalLibrary(libBuilder.build()); + } + } + + final String defaultCharset = EncodingManager.getInstance().getDefaultCharsetName(); + if (defaultCharset != null) { + cmdBuilder.setGlobalEncoding(defaultCharset); + } + + final String ignoredFilesList = FileTypeManager.getInstance().getIgnoredFilesList(); + cmdBuilder.setIgnoredFilesPatterns(ignoredFilesList); + return cmdBuilder.build(); + } + + private static void fillSdks(List globals) { + for (Sdk sdk : ProjectJdkTable.getInstance().getAllJdks()) { + final String name = sdk.getName(); + final String homePath = sdk.getHomePath(); + if (homePath == null) { + continue; + } + final SdkAdditionalData data = sdk.getSdkAdditionalData(); + final String additionalDataXml; + final SdkType sdkType = sdk.getSdkType(); + if (data == null) { + additionalDataXml = null; + } + else { + final Element element = new Element("additional"); + sdkType.saveAdditionalData(data, element); + additionalDataXml = JDOMUtil.writeElement(element, "\n"); + } + final List paths = convertToLocalPaths(sdk.getRootProvider().getFiles(OrderRootType.CLASSES)); + String versionString = sdk.getVersionString(); + if (versionString != null && sdkType instanceof JavaSdk) { + final JavaSdkVersion version = ((JavaSdk)sdkType).getVersion(versionString); + if (version != null) { + versionString = version.getDescription(); + } + } + globals.add(new SdkLibrary(name, sdkType.getName(), versionString, homePath, paths, additionalDataXml)); + } + } + + private static void fillGlobalLibraries(List globals) { + final Iterator iterator = LibraryTablesRegistrar.getInstance().getLibraryTable().getLibraryIterator(); + while (iterator.hasNext()) { + Library library = iterator.next(); + final String name = library.getName(); + + if (name != null) { + final List paths = convertToLocalPaths(library.getFiles(OrderRootType.CLASSES)); + globals.add(new GlobalLibrary(name, paths)); + } + } + } + + private static List convertToLocalPaths(VirtualFile[] files) { + final List paths = new ArrayList(); + for (VirtualFile file : files) { + if (file.isValid()) { + paths.add(StringUtil.trimEnd(FileUtil.toSystemIndependentName(file.getPath()), JarFileSystem.JAR_SEPARATOR)); + } + } + return paths; + } + + private Process launchBuildProcess(final int port, final UUID sessionId) throws ExecutionException { + // todo: add code for choosing the jdk with which to run + // validate tools.jar presence + final JavaCompiler systemCompiler = ToolProvider.getSystemJavaCompiler(); + if (systemCompiler == null) { + throw new ExecutionException("No system java compiler is provided by the JRE. Make sure tools.jar is present in IntelliJ IDEA classpath."); + } + + final Sdk projectJdk = JavaAwareProjectJdkTableImpl.getInstanceEx().getInternalJdk(); + final GeneralCommandLine cmdLine = new GeneralCommandLine(); + final String vmExecutablePath = ((JavaSdkType)projectJdk.getSdkType()).getVMExecutablePath(projectJdk); + cmdLine.setExePath(vmExecutablePath); + cmdLine.addParameter("-server"); + cmdLine.addParameter("-XX:MaxPermSize=150m"); + cmdLine.addParameter("-XX:ReservedCodeCacheSize=64m"); + cmdLine.addParameter("-Xmx" + Registry.intValue("compiler.server.heap.size") + "m"); + cmdLine.addParameter("-Djava.awt.headless=true"); + + final String shouldGenerateIndex = System.getProperty(GlobalOptions.GENERATE_CLASSPATH_INDEX_OPTION); + if (shouldGenerateIndex != null) { + cmdLine.addParameter("-D"+ GlobalOptions.GENERATE_CLASSPATH_INDEX_OPTION +"=" + shouldGenerateIndex); + } + + final String additionalOptions = Registry.stringValue("compiler.server.vm.options"); + if (!StringUtil.isEmpty(additionalOptions)) { + final StringTokenizer tokenizer = new StringTokenizer(additionalOptions, " ", false); + while (tokenizer.hasMoreTokens()) { + cmdLine.addParameter(tokenizer.nextToken()); + } + } + + // debugging + final int debugPort = Registry.intValue("compiler.server.debug.port"); + if (debugPort > 0) { + cmdLine.addParameter("-XX:+HeapDumpOnOutOfMemoryError"); + cmdLine.addParameter("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=" + debugPort); + } + + if (Registry.is("compiler.server.use.memory.temp.cache")) { + cmdLine.addParameter("-D"+ GlobalOptions.USE_MEMORY_TEMP_CACHE_OPTION); + } + if (Registry.is("compiler.server.use.external.javac.process")) { + cmdLine.addParameter("-D"+ GlobalOptions.USE_EXTERNAL_JAVAC_OPTION); + } + final String host = NetUtils.getLocalHostString(); + cmdLine.addParameter("-D"+ GlobalOptions.HOSTNAME_OPTION + "=" + host); + + // javac's VM should use the same default locale that IDEA uses in order for javac to print messages in 'correct' language + final String lang = System.getProperty("user.language"); + if (lang != null) { + //noinspection HardCodedStringLiteral + cmdLine.addParameter("-Duser.language=" + lang); + } + final String country = System.getProperty("user.country"); + if (country != null) { + //noinspection HardCodedStringLiteral + cmdLine.addParameter("-Duser.country=" + country); + } + //noinspection HardCodedStringLiteral + final String region = System.getProperty("user.region"); + if (region != null) { + //noinspection HardCodedStringLiteral + cmdLine.addParameter("-Duser.region=" + region); + } + + cmdLine.addParameter("-classpath"); + + final List cp = ClasspathBootstrap.getCompileServerApplicationClasspath(); + cp.addAll(myClasspathManager.getCompileServerPluginsClasspath()); + + cmdLine.addParameter(classpathToString(cp)); + + cmdLine.addParameter(BuildMain.class.getName()); + cmdLine.addParameter(host); + cmdLine.addParameter(Integer.toString(port)); + cmdLine.addParameter(sessionId.toString()); + + final File workDirectory = new File(mySystemDirectory, SYSTEM_ROOT); + workDirectory.mkdirs(); + ensureLogConfigExists(workDirectory); + + cmdLine.addParameter(FileUtil.toSystemIndependentName(workDirectory.getPath())); + + cmdLine.setWorkDirectory(workDirectory); + + return cmdLine.createProcess(); + } + + private static void ensureLogConfigExists(File workDirectory) { + final File logConfig = new File(workDirectory, LOGGER_CONFIG); + if (!logConfig.exists()) { + FileUtil.createIfDoesntExist(logConfig); + try { + final InputStream in = Server.class.getResourceAsStream("/" + DEFAULT_LOGGER_CONFIG); + if (in != null) { + try { + final FileOutputStream out = new FileOutputStream(logConfig); + try { + FileUtil.copy(in, out); + } + finally { + out.close(); + } + } + finally { + in.close(); + } + } + } + catch (IOException e) { + LOG.error(e); + } + } + } + + public void stopListening() { + final ChannelGroupFuture closeFuture = myAllOpenChannels.close(); + closeFuture.awaitUninterruptibly(); + } + + private int startListening() throws Exception { + final ChannelFactory channelFactory = new NioServerSocketChannelFactory(myPooledThreadExecutor, myPooledThreadExecutor); + final SimpleChannelUpstreamHandler channelRegistrar = new SimpleChannelUpstreamHandler() { + public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { + myAllOpenChannels.add(e.getChannel()); + super.channelOpen(ctx, e); + } + + @Override + public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { + myAllOpenChannels.remove(e.getChannel()); + super.channelClosed(ctx, e); + } + }; + ChannelPipelineFactory pipelineFactory = new ChannelPipelineFactory() { + public ChannelPipeline getPipeline() throws Exception { + return Channels.pipeline( + channelRegistrar, + new ProtobufVarint32FrameDecoder(), + new ProtobufDecoder(CmdlineRemoteProto.Message.getDefaultInstance()), + new ProtobufVarint32LengthFieldPrepender(), + new ProtobufEncoder(), + myMessageDispatcher + ); + } + }; + final ServerBootstrap bootstrap = new ServerBootstrap(channelFactory); + bootstrap.setPipelineFactory(pipelineFactory); + bootstrap.setOption("child.tcpNoDelay", true); + bootstrap.setOption("child.keepAlive", true); + final int listenPort = NetUtils.findAvailableSocketPort(); + final Channel serverChannel = bootstrap.bind(new InetSocketAddress(listenPort)); + myAllOpenChannels.add(serverChannel); + return listenPort; + } + + private static String classpathToString(List cp) { + StringBuilder builder = new StringBuilder(); + for (File file : cp) { + if (builder.length() > 0) { + builder.append(File.pathSeparator); + } + builder.append(FileUtil.toCanonicalPath(file.getPath())); + } + return builder.toString(); + } + + private static class AutoMakeMessageHandler extends BuildMessageHandler { + private CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.Status myBuildStatus; + private final Project myProject; + private final WolfTheProblemSolver myWolf; + + public AutoMakeMessageHandler(Project project) { + myProject = project; + myBuildStatus = CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.Status.SUCCESS; + myWolf = WolfTheProblemSolver.getInstance(project); + } + + @Override + protected void handleBuildEvent(CmdlineRemoteProto.Message.BuilderMessage.BuildEvent event) { + if (myProject.isDisposed()) { + return; + } + switch (event.getEventType()) { + case BUILD_COMPLETED: + if (event.hasCompletionStatus()) { + myBuildStatus = event.getCompletionStatus(); + } + return; + + case FILES_GENERATED: + final CompilationStatusListener publisher = myProject.getMessageBus().syncPublisher(CompilerTopics.COMPILATION_STATUS); + for (CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.GeneratedFile generatedFile : event.getGeneratedFilesList()) { + final String root = FileUtil.toSystemIndependentName(generatedFile.getOutputRoot()); + final String relativePath = FileUtil.toSystemIndependentName(generatedFile.getRelativePath()); + publisher.fileGenerated(root, relativePath); + } + return; + + default: + return; + } + } + + @Override + protected void handleCompileMessage(CmdlineRemoteProto.Message.BuilderMessage.CompileMessage message) { + if (myProject.isDisposed()) { + return; + } + final CmdlineRemoteProto.Message.BuilderMessage.CompileMessage.Kind kind = message.getKind(); + if (kind == CmdlineRemoteProto.Message.BuilderMessage.CompileMessage.Kind.ERROR) { + informWolf(myProject, message); + } + } + + @Override + public void handleFailure(CmdlineRemoteProto.Message.Failure failure) { + CompilerManager.NOTIFICATION_GROUP.createNotification("Auto make failure: " + failure.getDescription(), MessageType.INFO); + } + + @Override + public void sessionTerminated() { + String statusMessage = null/*"Auto make completed"*/; + switch (myBuildStatus) { + case SUCCESS: + //statusMessage = "Auto make completed successfully"; + break; + case UP_TO_DATE: + //statusMessage = "All files are up-to-date"; + break; + case ERRORS: + statusMessage = "Auto make completed with errors"; + break; + case CANCELED: + //statusMessage = "Auto make has been canceled"; + break; + } + if (statusMessage != null) { + final Notification notification = CompilerManager.NOTIFICATION_GROUP.createNotification(statusMessage, MessageType.INFO); + if (!myProject.isDisposed()) { + notification.notify(myProject); + } + } + } + + private void informWolf(Project project, CmdlineRemoteProto.Message.BuilderMessage.CompileMessage message) { + final String srcPath = message.getSourceFilePath(); + if (srcPath != null && !project.isDisposed()) { + final VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(srcPath); + if (vFile != null) { + final int line = (int)message.getLine(); + final int column = (int)message.getColumn(); + if (line > 0 && column > 0) { + final Problem problem = myWolf.convertToProblem(vFile, line, column, new String[]{message.getText()}); + myWolf.weHaveGotProblems(vFile, Collections.singletonList(problem)); + } + else { + myWolf.queue(vFile); + } + } + } + } + } + + private class ProjectWatcher extends ProjectManagerAdapter { + private final Map myConnections = new HashMap(); + + @Override + public void projectOpened(final Project project) { + final MessageBusConnection conn = project.getMessageBus().connect(); + myConnections.put(project, conn); + conn.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootListener() { + @Override + public void beforeRootsChange(final ModuleRootEvent event) { + } + + @Override + public void rootsChanged(final ModuleRootEvent event) { + clearGlobalsCache(); + } + }); + } + + @Override + public void projectClosing(Project project) { + } + + @Override + public void projectClosed(Project project) { + myProjectToExecutorMap.remove(getProjectPath(project)); + final MessageBusConnection conn = myConnections.remove(project); + if (conn != null) { + conn.disconnect(); + } + } + } + + private static class BuildMessageDispatcher extends SimpleChannelHandler { + private final Map myMessageHandlers = new ConcurrentHashMap(); + private final Set myCanceledSessions = new ConcurrentHashSet(); + + public void registerBuildMessageHandler(UUID sessionId, + BuildMessageHandler handler, + CmdlineRemoteProto.Message.ControllerMessage params) { + myMessageHandlers.put(sessionId, new SessionData(sessionId, handler, params)); + } + + @Nullable + public BuildMessageHandler unregisterBuildMessageHandler(UUID sessionId) { + myCanceledSessions.remove(sessionId); + final SessionData data = myMessageHandlers.remove(sessionId); + return data != null? data.handler : null; + } + + public void cancelSession(UUID sessionId) { + if (myCanceledSessions.add(sessionId)) { + final SessionData data = myMessageHandlers.get(sessionId); + if (data != null) { + final Channel channel = data.channel; + if (channel != null) { + Channels.write(channel, CmdlineProtoUtil.toMessage(sessionId, CmdlineProtoUtil.createCancelCommand())); + } + } + } + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { + final CmdlineRemoteProto.Message message = (CmdlineRemoteProto.Message)e.getMessage(); + + SessionData sessionData = (SessionData)ctx.getAttachment(); + + UUID sessionId; + if (sessionData == null) { + // this is the first message for this session, so fill session data with missing info + final CmdlineRemoteProto.Message.UUID id = message.getSessionId(); + sessionId = new UUID(id.getMostSigBits(), id.getLeastSigBits()); + + sessionData = myMessageHandlers.get(sessionId); + if (sessionData != null) { + sessionData.channel = ctx.getChannel(); + ctx.setAttachment(sessionData); + } + if (myCanceledSessions.contains(sessionId)) { + Channels.write(ctx.getChannel(), CmdlineProtoUtil.toMessage(sessionId, CmdlineProtoUtil.createCancelCommand())); + } + } + else { + sessionId = sessionData.sessionId; + } + + final BuildMessageHandler handler = sessionData != null? sessionData.handler : null; + if (handler == null) { + // todo + LOG.info("No message handler registered for session " + sessionId); + return; + } + + final CmdlineRemoteProto.Message.Type messageType = message.getType(); + switch (messageType) { + case FAILURE: + handler.handleFailure(message.getFailure()); + break; + + case BUILDER_MESSAGE: + final CmdlineRemoteProto.Message.BuilderMessage builderMessage = message.getBuilderMessage(); + final CmdlineRemoteProto.Message.BuilderMessage.Type responseType = builderMessage.getType(); + if (responseType == CmdlineRemoteProto.Message.BuilderMessage.Type.PARAM_REQUEST) { + final CmdlineRemoteProto.Message.ControllerMessage params = sessionData.params; + if (params != null) { + sessionData.params = null; + Channels.write(ctx.getChannel(), CmdlineProtoUtil.toMessage(sessionId, params)); + } + else { + cancelSession(sessionId); + } + } + else { + handler.handleBuildMessage(builderMessage); + } + break; + + default: + LOG.info("Unsupported message type " + messageType); + break; + } + } + + @Override + public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { + try { + super.channelClosed(ctx, e); + } + finally { + final SessionData sessionData = (SessionData)ctx.getAttachment(); + if (sessionData != null) { + final BuildMessageHandler handler = unregisterBuildMessageHandler(sessionData.sessionId); + if (handler != null) { + // notify the handler only if it has not been notified yet + handler.sessionTerminated(); + } + } + } + } + + + @Override + public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { + try { + super.channelDisconnected(ctx, e); + } + finally { + final SessionData sessionData = (SessionData)ctx.getAttachment(); + if (sessionData != null) { // this is the context corresponding to some session + final Channel channel = e.getChannel(); + ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { + @Override + public void run() { + channel.close(); + } + }); + } + } + } + } + + + private static final class SessionData { + final UUID sessionId; + final BuildMessageHandler handler; + volatile CmdlineRemoteProto.Message.ControllerMessage params; + volatile Channel channel; + + private SessionData(UUID sessionId, BuildMessageHandler handler, CmdlineRemoteProto.Message.ControllerMessage params) { + this.sessionId = sessionId; + this.handler = handler; + this.params = params; + } + } + +} diff --git a/java/compiler/impl/src/com/intellij/compiler/server/BuildMessageHandler.java b/java/compiler/impl/src/com/intellij/compiler/server/BuildMessageHandler.java new file mode 100644 index 000000000000..e5f1f096fed9 --- /dev/null +++ b/java/compiler/impl/src/com/intellij/compiler/server/BuildMessageHandler.java @@ -0,0 +1,45 @@ +/* + * 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 com.intellij.compiler.server; + +import org.jetbrains.jps.api.CmdlineRemoteProto; + +/** + * @author Eugene Zhuravlev + * Date: 4/18/12 + */ +public abstract class BuildMessageHandler { + + public abstract void sessionTerminated(); + + public abstract void handleFailure(CmdlineRemoteProto.Message.Failure failure); + + public final void handleBuildMessage(CmdlineRemoteProto.Message.BuilderMessage msg) { + switch (msg.getType()) { + case BUILD_EVENT: + handleBuildEvent(msg.getBuildEvent()); + break; + case COMPILE_MESSAGE: + handleCompileMessage(msg.getCompileMessage()); + break; + } + } + + protected abstract void handleCompileMessage(CmdlineRemoteProto.Message.BuilderMessage.CompileMessage message); + + protected abstract void handleBuildEvent(CmdlineRemoteProto.Message.BuilderMessage.BuildEvent event); + +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/api/CmdlineProtoUtil.java b/jps/jps-builders/src/org/jetbrains/jps/api/CmdlineProtoUtil.java index 55b23a31597f..0142c85d082e 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/api/CmdlineProtoUtil.java +++ b/jps/jps-builders/src/org/jetbrains/jps/api/CmdlineProtoUtil.java @@ -7,6 +7,8 @@ import org.jetbrains.jps.incremental.messages.BuildMessage; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.util.Collection; +import java.util.Collections; +import java.util.Map; import java.util.UUID; /** @@ -14,6 +16,86 @@ import java.util.UUID; * Date: 4/17/12 */ public class CmdlineProtoUtil { + + public static CmdlineRemoteProto.Message.ControllerMessage createMakeRequest(String project, + Collection modules, + Collection artifacts, + final Map userData, + final CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings globals) { + return createBuildParametersMessage(CmdlineRemoteProto.Message.ControllerMessage.ParametersMessage.Type.MAKE, project, modules, artifacts, + userData, Collections.emptyList(), + globals); + } + + public static CmdlineRemoteProto.Message.ControllerMessage createForceCompileRequest(String project, + Collection modules, + Collection artifacts, + Collection paths, + final Map userData, + final CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings globals) { + return createBuildParametersMessage(CmdlineRemoteProto.Message.ControllerMessage.ParametersMessage.Type.FORCED_COMPILATION, project, modules, + artifacts, + userData, paths, globals); + } + + public static CmdlineRemoteProto.Message.ControllerMessage createRebuildRequest(String project, + final Map userData, + final CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings globals) { + return createBuildParametersMessage(CmdlineRemoteProto.Message.ControllerMessage.ParametersMessage.Type.REBUILD, project, + Collections.emptyList(), + Collections.emptyList(), userData, Collections.emptyList(), + globals); + } + + public static CmdlineRemoteProto.Message.ControllerMessage createCleanRequest(String project, + Collection modules, + Collection artifacts, + final Map userData, + final CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings globals) { + return createBuildParametersMessage( + CmdlineRemoteProto.Message.ControllerMessage.ParametersMessage.Type.CLEAN, project, modules, artifacts, userData, Collections.emptyList(), + globals + ); + } + + public static CmdlineRemoteProto.Message.ControllerMessage createBuildParametersMessage(CmdlineRemoteProto.Message.ControllerMessage.ParametersMessage.Type buildType, + String project, + Collection modules, + Collection artifacts, + Map userData, + Collection paths, + final CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings globals) { + final CmdlineRemoteProto.Message.ControllerMessage.ParametersMessage.Builder + builder = CmdlineRemoteProto.Message.ControllerMessage.ParametersMessage.newBuilder(); + builder.setGlobalSettings(globals); + builder.setBuildType(buildType); + builder.setProjectId(project); + if (!modules.isEmpty()) { + builder.addAllModuleName(modules); + } + if (!artifacts.isEmpty()) { + builder.addAllArtifactName(artifacts); + } + if (!userData.isEmpty()) { + for (Map.Entry entry : userData.entrySet()) { + final String key = entry.getKey(); + final String value = entry.getValue(); + if (key != null && value != null) { + builder.addBuilderParameter(createPair(key, value)); + } + } + } + if (!paths.isEmpty()) { + builder.addAllFilePath(paths); + } + return CmdlineRemoteProto.Message.ControllerMessage.newBuilder().setType(CmdlineRemoteProto.Message.ControllerMessage.Type.BUILD_PARAMETERS).setParamsMessage(builder.build()).build(); + } + + public static CmdlineRemoteProto.Message.KeyValuePair createPair(String key, String value) { + return CmdlineRemoteProto.Message.KeyValuePair.newBuilder().setKey(key).setValue(value).build(); + } + + public static CmdlineRemoteProto.Message.Failure createFailure(String description, Throwable cause) { final CmdlineRemoteProto.Message.Failure.Builder builder = CmdlineRemoteProto.Message.Failure.newBuilder(); builder.setDescription(description); @@ -25,15 +107,20 @@ public class CmdlineProtoUtil { return builder.build(); } - public static CmdlineRemoteProto.Message.BuilderMessage createCompileProgressMessageResponse(String text, float done) { - return createCompileMessageResponse(BuildMessage.Kind.PROGRESS, text, null, -1L, -1L, -1L, -1, -1, done); + public static CmdlineRemoteProto.Message.ControllerMessage createCancelCommand() { + return CmdlineRemoteProto.Message.ControllerMessage.newBuilder() + .setType(CmdlineRemoteProto.Message.ControllerMessage.Type.CANCEL_BUILD_COMMAND).build(); } - public static CmdlineRemoteProto.Message.BuilderMessage createCompileMessageResponse(final BuildMessage.Kind kind, - String text, - String path, - long beginOffset, long endOffset, long offset, long line, - long column, float done) { + public static CmdlineRemoteProto.Message.BuilderMessage createCompileProgressMessageResponse(String text, float done) { + return createCompileMessage(BuildMessage.Kind.PROGRESS, text, null, -1L, -1L, -1L, -1, -1, done); + } + + public static CmdlineRemoteProto.Message.BuilderMessage createCompileMessage(final BuildMessage.Kind kind, + String text, + String path, + long beginOffset, long endOffset, long offset, long line, + long column, float done) { final CmdlineRemoteProto.Message.BuilderMessage.CompileMessage.Builder builder = CmdlineRemoteProto.Message.BuilderMessage.CompileMessage.newBuilder(); switch (kind) { @@ -110,6 +197,11 @@ public class CmdlineProtoUtil { return CmdlineRemoteProto.Message.newBuilder().setSessionId(toProtoUUID(sessionId)).setType(CmdlineRemoteProto.Message.Type.BUILDER_MESSAGE).setBuilderMessage(builderMessage).build(); } + public static CmdlineRemoteProto.Message toMessage(UUID sessionId, CmdlineRemoteProto.Message.ControllerMessage builderMessage) { + return CmdlineRemoteProto.Message.newBuilder().setSessionId(toProtoUUID(sessionId)).setType(CmdlineRemoteProto.Message.Type.CONTROLLER_MESSAGE).setControllerMessage( + builderMessage).build(); + } + public static CmdlineRemoteProto.Message toMessage(UUID sessionId, CmdlineRemoteProto.Message.Failure failure) { return CmdlineRemoteProto.Message.newBuilder().setSessionId(toProtoUUID(sessionId)).setType(CmdlineRemoteProto.Message.Type.FAILURE).setFailure(failure).build(); } diff --git a/jps/jps-builders/src/org/jetbrains/jps/cmdline/BuildMain.java b/jps/jps-builders/src/org/jetbrains/jps/cmdline/BuildMain.java index a7df294dff3a..93227418429a 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/cmdline/BuildMain.java +++ b/jps/jps-builders/src/org/jetbrains/jps/cmdline/BuildMain.java @@ -1,6 +1,9 @@ package org.jetbrains.jps.cmdline; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.io.FileUtil; +import org.apache.log4j.Level; +import org.apache.log4j.xml.DOMConfigurator; import org.jboss.netty.bootstrap.ClientBootstrap; import org.jboss.netty.channel.*; import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; @@ -8,9 +11,12 @@ import org.jboss.netty.handler.codec.protobuf.ProtobufDecoder; import org.jboss.netty.handler.codec.protobuf.ProtobufEncoder; import org.jboss.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; import org.jboss.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.api.CmdlineProtoUtil; import org.jetbrains.jps.api.CmdlineRemoteProto; import org.jetbrains.jps.api.SharedThreadPool; +import org.jetbrains.jps.incremental.Utils; import java.io.File; import java.net.InetSocketAddress; @@ -21,14 +27,17 @@ import java.util.UUID; * Date: 4/16/12 */ public class BuildMain { + private static final String LOG_FILE_NAME = "log.xml"; private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.cmdline.BuildMain"); public static void main(String[] args){ final String host = args[0]; final int port = Integer.parseInt(args[1]); final UUID sessionId = UUID.fromString(args[2]); - final File systemDir = new File(args[3]); - systemDir.mkdirs(); + final File systemDir = new File(FileUtil.toCanonicalPath(args[3])); + Utils.setSystemRoot(systemDir); + + initLoggers(); final ClientBootstrap bootstrap = new ClientBootstrap(new NioClientSocketChannelFactory(SharedThreadPool.INSTANCE, SharedThreadPool.INSTANCE, 1)); bootstrap.setPipelineFactory(new ChannelPipelineFactory() { @@ -141,4 +150,65 @@ public class BuildMain { } }); } + + private static void initLoggers() { + if (new File(LOG_FILE_NAME).exists()) { + DOMConfigurator.configure(LOG_FILE_NAME); + } + + Logger.setFactory(new Logger.Factory() { + @Override + public Logger getLoggerInstance(String category) { + final org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(category); + + return new Logger() { + @Override + public boolean isDebugEnabled() { + return logger.isDebugEnabled(); + } + + @Override + public void debug(@NonNls String message) { + logger.debug(message); + } + + @Override + public void debug(@Nullable Throwable t) { + logger.debug("", t); + } + + @Override + public void debug(@NonNls String message, @Nullable Throwable t) { + logger.debug(message, t); + } + + @Override + public void error(@NonNls String message, @Nullable Throwable t, @NonNls String... details) { + logger.debug(message, t); + } + + @Override + public void info(@NonNls String message) { + logger.info(message); + } + + @Override + public void info(@NonNls String message, @Nullable Throwable t) { + logger.info(message, t); + } + + @Override + public void warn(@NonNls String message, @Nullable Throwable t) { + logger.warn(message, t); + } + + @Override + public void setLevel(Level level) { + logger.setLevel(level); + } + }; + } + }); + } + } diff --git a/jps/jps-builders/src/org/jetbrains/jps/cmdline/BuildSession.java b/jps/jps-builders/src/org/jetbrains/jps/cmdline/BuildSession.java index 77d616fd1f78..74448f645336 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/cmdline/BuildSession.java +++ b/jps/jps-builders/src/org/jetbrains/jps/cmdline/BuildSession.java @@ -114,7 +114,7 @@ final class BuildSession implements Runnable, CanceledStatus { if (kind == BuildMessage.Kind.ERROR) { hasErrors.set(true); } - response = CmdlineProtoUtil.createCompileMessageResponse( + response = CmdlineProtoUtil.createCompileMessage( kind, text, compilerMessage.getSourcePath(), compilerMessage.getProblemBeginOffset(), compilerMessage.getProblemEndOffset(), compilerMessage.getProblemLocationOffset(), compilerMessage.getLine(), compilerMessage.getColumn(), @@ -267,39 +267,8 @@ final class BuildSession implements Runnable, CanceledStatus { final long start = System.currentTimeMillis(); try { final Project project = new Project(); - // setup JDKs and global libraries - final MethodClosure fakeClosure = new MethodClosure(new Object(), "hashCode"); - for (GlobalLibrary library : myGlobalLibraries) { - if (library instanceof SdkLibrary) { - final SdkLibrary sdk = (SdkLibrary)library; - Node additionalData = null; - final String additionalXml = sdk.getAdditionalDataXml(); - if (additionalXml != null) { - try { - additionalData = new XmlParser(false, false).parseText(additionalXml); - } - catch (Exception e) { - LOG.info(e); - } - } - final Sdk jdk = project.createSdk(sdk.getTypeName(), sdk.getName(), sdk.getVersion(), sdk.getHomePath(), additionalData); - if (jdk != null) { - jdk.setClasspath(sdk.getPaths()); - } - else { - LOG.info("Failed to load SDK " + sdk.getName() + ", type: " + sdk.getTypeName()); - } - } - else { - final Library lib = project.createGlobalLibrary(library.getName(), fakeClosure); - if (lib != null) { - lib.setClasspath(library.getPaths()); - } - else { - LOG.info("Failed to load global library " + library.getName()); - } - } - } + + initSdksAndGlobalLibraries(project); final File projectFile = new File(projectPath); @@ -320,6 +289,41 @@ final class BuildSession implements Runnable, CanceledStatus { } } + private void initSdksAndGlobalLibraries(Project project) { + final MethodClosure fakeClosure = new MethodClosure(new Object(), "hashCode"); + for (GlobalLibrary library : myGlobalLibraries) { + if (library instanceof SdkLibrary) { + final SdkLibrary sdk = (SdkLibrary)library; + Node additionalData = null; + final String additionalXml = sdk.getAdditionalDataXml(); + if (additionalXml != null) { + try { + additionalData = new XmlParser(false, false).parseText(additionalXml); + } + catch (Exception e) { + LOG.info(e); + } + } + final Sdk jdk = project.createSdk(sdk.getTypeName(), sdk.getName(), sdk.getVersion(), sdk.getHomePath(), additionalData); + if (jdk != null) { + jdk.setClasspath(sdk.getPaths()); + } + else { + LOG.info("Failed to load SDK " + sdk.getName() + ", type: " + sdk.getTypeName()); + } + } + else { + final Library lib = project.createGlobalLibrary(library.getName(), fakeClosure); + if (lib != null) { + lib.setClasspath(library.getPaths()); + } + else { + LOG.info("Failed to load global library " + library.getName()); + } + } + } + } + private static boolean isDirectoryBased(File projectFile) { return !(projectFile.isFile() && projectFile.getName().endsWith(".ipr")); } diff --git a/resources/src/componentSets/Compiler.xml b/resources/src/componentSets/Compiler.xml index 373ac0e0362d..be10f8d1aed1 100644 --- a/resources/src/componentSets/Compiler.xml +++ b/resources/src/componentSets/Compiler.xml @@ -6,6 +6,9 @@ com.intellij.compiler.CompileServerManager + + com.intellij.compiler.server.BuildManager +