2 * Copyright 2000-2011 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.compiler;
18 import com.intellij.ProjectTopics;
19 import com.intellij.application.options.PathMacrosImpl;
20 import com.intellij.execution.ExecutionException;
21 import com.intellij.execution.configurations.GeneralCommandLine;
22 import com.intellij.execution.process.OSProcessHandler;
23 import com.intellij.execution.process.ProcessAdapter;
24 import com.intellij.execution.process.ProcessEvent;
25 import com.intellij.execution.process.ProcessOutputTypes;
26 import com.intellij.notification.Notification;
27 import com.intellij.openapi.application.ApplicationManager;
28 import com.intellij.openapi.application.PathMacros;
29 import com.intellij.openapi.application.PathManager;
30 import com.intellij.openapi.compiler.CompilerManager;
31 import com.intellij.openapi.components.ApplicationComponent;
32 import com.intellij.openapi.diagnostic.Logger;
33 import com.intellij.openapi.project.Project;
34 import com.intellij.openapi.project.ProjectManager;
35 import com.intellij.openapi.project.ProjectManagerAdapter;
36 import com.intellij.openapi.projectRoots.JavaSdkType;
37 import com.intellij.openapi.projectRoots.ProjectJdkTable;
38 import com.intellij.openapi.projectRoots.Sdk;
39 import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl;
40 import com.intellij.openapi.roots.ModuleRootEvent;
41 import com.intellij.openapi.roots.ModuleRootListener;
42 import com.intellij.openapi.roots.OrderRootType;
43 import com.intellij.openapi.roots.libraries.Library;
44 import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar;
45 import com.intellij.openapi.ui.MessageType;
46 import com.intellij.openapi.util.Key;
47 import com.intellij.openapi.util.Ref;
48 import com.intellij.openapi.util.ShutDownTracker;
49 import com.intellij.openapi.util.io.FileUtil;
50 import com.intellij.openapi.util.registry.Registry;
51 import com.intellij.openapi.util.text.StringUtil;
52 import com.intellij.openapi.vfs.*;
53 import com.intellij.openapi.vfs.encoding.EncodingManager;
54 import com.intellij.openapi.vfs.newvfs.BulkFileListener;
55 import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
56 import com.intellij.problems.Problem;
57 import com.intellij.problems.WolfTheProblemSolver;
58 import com.intellij.util.Alarm;
59 import com.intellij.util.concurrency.Semaphore;
60 import com.intellij.util.messages.MessageBusConnection;
61 import com.intellij.util.net.NetUtils;
62 import org.jetbrains.annotations.NotNull;
63 import org.jetbrains.annotations.Nullable;
64 import org.jetbrains.jps.api.*;
65 import org.jetbrains.jps.client.CompileServerClient;
66 import org.jetbrains.jps.server.ClasspathBootstrap;
67 import org.jetbrains.jps.server.Server;
71 import java.io.FileOutputStream;
72 import java.io.IOException;
73 import java.io.InputStream;
75 import java.util.concurrent.Future;
76 import java.util.concurrent.RunnableFuture;
77 import java.util.concurrent.TimeUnit;
78 import java.util.concurrent.atomic.AtomicBoolean;
81 * @author Eugene Zhuravlev
84 public class CompileServerManager implements ApplicationComponent{
85 private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.CompileServerManager");
86 private static final String COMPILE_SERVER_SYSTEM_ROOT = "compile-server";
87 private static final String LOGGER_CONFIG = "log.xml";
88 private static final String DEFAULT_LOGGER_CONFIG = "defaultLogConfig.xml";
89 private volatile OSProcessHandler myProcessHandler;
90 private final File mySystemDirectory;
91 private volatile CompileServerClient myClient = new CompileServerClient();
92 private final SequentialTaskExecutor myTaskExecutor = new SequentialTaskExecutor(new SequentialTaskExecutor.AsyncTaskExecutor() {
93 public void submit(Runnable runnable) {
94 ApplicationManager.getApplication().executeOnPooledThread(runnable);
97 private final ProjectManager myProjectManager;
98 private static final int MAKE_TRIGGER_DELAY = 5 * 1000 /*5 seconds*/;
99 private final Map<RequestFuture, Project> myAutomakeFutures = new HashMap<RequestFuture, Project>();
101 public CompileServerManager(final ProjectManager projectManager) {
102 myProjectManager = projectManager;
103 final String systemPath = PathManager.getSystemPath();
104 File system = new File(systemPath);
106 system = system.getCanonicalFile();
108 catch (IOException e) {
111 mySystemDirectory = system;
113 projectManager.addProjectManagerListener(new ProjectWatcher());
114 final MessageBusConnection conn = ApplicationManager.getApplication().getMessageBus().connect();
115 conn.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() {
116 private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
117 private final AtomicBoolean myAutoMakeInProgress = new AtomicBoolean(false);
119 public void before(List<? extends VFileEvent> events) {
123 public void after(List<? extends VFileEvent> events) {
124 if (shouldTriggerMake(events)) {
125 scheduleMake(new Runnable() {
128 if (!myAutoMakeInProgress.getAndSet(true)) {
133 myAutoMakeInProgress.set(false);
144 private void scheduleMake(Runnable runnable) {
145 myAlarm.cancelAllRequests();
146 myAlarm.addRequest(runnable, MAKE_TRIGGER_DELAY);
149 private boolean shouldTriggerMake(List<? extends VFileEvent> events) {
150 for (VFileEvent event : events) {
151 if (event.isFromRefresh() || event.getRequestor() instanceof SavingRequestor) {
159 ShutDownTracker.getInstance().registerShutdownTask(new Runnable() {
162 shutdownServer(myClient, myProcessHandler);
167 public static CompileServerManager getInstance() {
168 return ApplicationManager.getApplication().getComponent(CompileServerManager.class);
171 public void notifyFilesChanged(Collection<String> paths) {
172 sendNotification(paths, false);
175 public void notifyFilesDeleted(Collection<String> paths) {
176 sendNotification(paths, true);
179 public void sendReloadRequest(final Project project) {
180 if (!project.isDefault() && project.isOpen()) {
181 myTaskExecutor.submit(new Runnable() {
185 if (!project.isDisposed()) {
186 final CompileServerClient client = ensureServerRunningAndClientConnected(false);
187 if (client != null) {
188 client.sendProjectReloadRequest(Collections.singletonList(project.getLocation()));
192 catch (Throwable e) {
200 public void sendCancelBuildRequest(final UUID sessionId) {
201 myTaskExecutor.submit(new Runnable() {
205 final CompileServerClient client = ensureServerRunningAndClientConnected(false);
206 if (client != null) {
207 client.sendCancelBuildRequest(sessionId);
210 catch (Throwable e) {
217 private void sendNotification(final Collection<String> paths, final boolean isDeleted) {
218 if (paths.isEmpty()) {
222 final CompileServerClient client = ensureServerRunningAndClientConnected(false);
223 if (client != null) {
224 myTaskExecutor.submit(new Runnable() {
226 final Project[] openProjects = myProjectManager.getOpenProjects();
227 if (openProjects.length > 0) {
228 final Collection<String> changed, deleted;
230 changed = Collections.emptyList();
235 deleted = Collections.emptyList();
237 for (Project project : openProjects) {
239 client.sendFSEvent(project.getLocation(), changed, deleted);
241 catch (Exception e) {
250 catch (Throwable th) {
251 LOG.error(th); // should not happen
255 private void runAutoMake() {
256 final Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
257 if (openProjects.length > 0) {
258 final List<RequestFuture> futures = new ArrayList<RequestFuture>();
259 for (final Project project : openProjects) {
260 if (project.isDefault()) {
263 final CompilerWorkspaceConfiguration config = CompilerWorkspaceConfiguration.getInstance(project);
264 if (!config.USE_COMPILE_SERVER || !config.MAKE_PROJECT_ON_SAVE) {
267 final RequestFuture future = submitCompilationTask(
268 project, false, true, Collections.<String>emptyList(), Collections.<String>emptyList(), new AutoMakeResponseHandler(project)
270 if (future != null) {
272 synchronized (myAutomakeFutures) {
273 myAutomakeFutures.put(future, project);
278 for (RequestFuture future : futures) {
282 catch (InterruptedException ignored) {
284 catch (java.util.concurrent.ExecutionException ignored) {
289 synchronized (myAutomakeFutures) {
290 myAutomakeFutures.keySet().removeAll(futures);
296 public void cancelAutoMakeTasks(Project project) {
297 synchronized (myAutomakeFutures) {
298 for (Map.Entry<RequestFuture, Project> entry : myAutomakeFutures.entrySet()) {
299 if (entry.getValue().equals(project)) {
300 entry.getKey().cancel(true);
307 public RequestFuture submitCompilationTask(final Project project, final boolean isRebuild, final boolean isMake, final Collection<String> modules, final Collection<String> paths, final JpsServerResponseHandler handler) {
308 final String projectId = project.getLocation();
309 final Ref<RequestFuture> futureRef = new Ref<RequestFuture>(null);
310 final RunnableFuture future = myTaskExecutor.submit(new Runnable() {
313 final CompileServerClient client = ensureServerRunningAndClientConnected(true);
314 if (client != null) {
315 final RequestFuture requestFuture = isRebuild ?
316 client.sendRebuildRequest(projectId, handler) :
317 client.sendCompileRequest(isMake, projectId, modules, paths, handler);
318 futureRef.set(requestFuture);
321 handler.sessionTerminated();
324 catch (Throwable e) {
326 handler.handleFailure(ProtoUtil.createFailure(e.getMessage(), e));
329 handler.sessionTerminated();
337 catch (Throwable e) {
340 return futureRef.get();
344 public void initComponent() {
348 public void disposeComponent() {
349 shutdownServer(myClient, myProcessHandler);
354 public String getComponentName() {
355 return "com.intellij.compiler.JpsServerManager";
358 // executed in one thread at a time
360 private CompileServerClient ensureServerRunningAndClientConnected(boolean forceRestart) throws Throwable {
361 final OSProcessHandler ph = myProcessHandler;
362 final CompileServerClient cl = myClient;
363 final boolean processNotRunning = ph == null || ph.isProcessTerminated() || ph.isProcessTerminating();
364 final boolean clientNotConnected = cl == null || !cl.isConnected();
366 if (processNotRunning || clientNotConnected) {
367 // cleanup; ensure the process is not running
368 shutdownServer(cl, ph);
369 myProcessHandler = null;
376 final int port = NetUtils.findAvailableSocketPort();
377 final Process process = launchServer(port);
379 final OSProcessHandler processHandler = new OSProcessHandler(process, null) {
380 protected boolean shouldDestroyProcessRecursively() {
384 final StringBuilder serverStartMessage = new StringBuilder();
385 final Semaphore semaphore = new Semaphore();
387 processHandler.addProcessListener(new ProcessAdapter() {
388 public void onTextAvailable(ProcessEvent event, Key outputType) {
389 // re-translate server's output to idea.log
390 final String text = event.getText();
391 if (!StringUtil.isEmpty(text)) {
392 LOG.info("COMPILE_SERVER [" +outputType.toString() +"]: "+ text.trim());
396 processHandler.addProcessListener(new ProcessAdapter() {
397 public void processTerminated(ProcessEvent event) {
399 processHandler.removeProcessListener(this);
406 public void onTextAvailable(ProcessEvent event, Key outputType) {
407 if (outputType == ProcessOutputTypes.STDERR) {
409 final String text = event.getText();
411 if (text.contains(Server.SERVER_SUCCESS_START_MESSAGE) || text.contains(Server.SERVER_ERROR_START_MESSAGE)) {
412 processHandler.removeProcessListener(this);
414 if (serverStartMessage.length() > 0) {
415 serverStartMessage.append("\n");
417 serverStartMessage.append(text);
426 processHandler.startNotify();
429 final String startupMsg = serverStartMessage.toString();
430 if (!startupMsg.contains(Server.SERVER_SUCCESS_START_MESSAGE)) {
431 throw new Exception("Server startup failed: " + startupMsg);
434 CompileServerClient client = new CompileServerClient();
435 boolean connected = false;
437 connected = client.connect(NetUtils.getLocalHostString(), port);
439 final RequestFuture setupFuture = sendSetupRequest(client);
441 myProcessHandler = processHandler;
447 shutdownServer(cl, processHandler);
454 private static RequestFuture sendSetupRequest(final @NotNull CompileServerClient client) throws Exception {
455 final Map<String, String> data = new HashMap<String, String>();
457 // need this for tests and when this macro is missing from PathMacros registry
458 data.put(PathMacrosImpl.APPLICATION_HOME_MACRO_NAME, FileUtil.toSystemIndependentName(PathManager.getHomePath()));
460 final PathMacros pathVars = PathMacros.getInstance();
461 for (String name : pathVars.getAllMacroNames()) {
462 final String path = pathVars.getValue(name);
464 data.put(name, FileUtil.toSystemIndependentName(path));
468 final List<GlobalLibrary> globals = new ArrayList<GlobalLibrary>();
471 fillGlobalLibraries(globals);
473 return client.sendSetupRequest(data, globals, EncodingManager.getInstance().getDefaultCharsetName());
476 private static void fillSdks(List<GlobalLibrary> globals) {
477 for (Sdk sdk : ProjectJdkTable.getInstance().getAllJdks()) {
478 final String name = sdk.getName();
479 final String homePath = sdk.getHomePath();
480 if (homePath == null) {
483 final List<String> paths = convertToLocalPaths(sdk.getRootProvider().getFiles(OrderRootType.CLASSES));
484 globals.add(new SdkLibrary(name, homePath, paths));
488 private static void fillGlobalLibraries(List<GlobalLibrary> globals) {
489 final Iterator<Library> iterator = LibraryTablesRegistrar.getInstance().getLibraryTable().getLibraryIterator();
490 while (iterator.hasNext()) {
491 Library library = iterator.next();
492 final String name = library.getName();
495 final List<String> paths = convertToLocalPaths(library.getFiles(OrderRootType.CLASSES));
496 globals.add(new GlobalLibrary(name, paths));
501 private static List<String> convertToLocalPaths(VirtualFile[] files) {
502 final List<String> paths = new ArrayList<String>();
503 for (VirtualFile file : files) {
504 if (file.isValid()) {
505 paths.add(StringUtil.trimEnd(FileUtil.toSystemIndependentName(file.getPath()), JarFileSystem.JAR_SEPARATOR));
511 //public static void addLocaleOptions(final List<String> commandLine, final boolean launcherUsed) {
512 // // need to specify default encoding so that javac outputs messages in 'correct' language
513 // commandLine.add((launcherUsed? "-J" : "") + "-D" + CharsetToolkit.FILE_ENCODING_PROPERTY + "=" + CharsetToolkit.getDefaultSystemCharset().name());
516 private Process launchServer(final int port) throws ExecutionException {
517 // validate tools.jar presence
518 final JavaCompiler systemCompiler = ToolProvider.getSystemJavaCompiler();
519 if (systemCompiler == null) {
520 throw new ExecutionException("No system java compiler is provided by the JRE. Make sure tools.jar is present in IntelliJ IDEA classpath.");
523 final Sdk projectJdk = JavaAwareProjectJdkTableImpl.getInstanceEx().getInternalJdk();
524 final GeneralCommandLine cmdLine = new GeneralCommandLine();
525 final String vmExecutablePath = ((JavaSdkType)projectJdk.getSdkType()).getVMExecutablePath(projectJdk);
526 cmdLine.setExePath(vmExecutablePath);
527 cmdLine.addParameter("-server");
528 cmdLine.addParameter("-XX:MaxPermSize=150m");
529 cmdLine.addParameter("-XX:ReservedCodeCacheSize=64m");
530 cmdLine.addParameter("-Xmx" + Registry.intValue("compiler.server.heap.size") + "m");
531 cmdLine.addParameter("-Djava.awt.headless=true");
532 //cmdLine.addParameter("-DuseJavaUtilZip");
533 final String additionalOptions = Registry.stringValue("compiler.server.vm.options");
534 if (!StringUtil.isEmpty(additionalOptions)) {
535 final StringTokenizer tokenizer = new StringTokenizer(additionalOptions, " ", false);
536 while (tokenizer.hasMoreTokens()) {
537 cmdLine.addParameter(tokenizer.nextToken());
542 final int debugPort = Registry.intValue("compiler.server.debug.port");
544 cmdLine.addParameter("-XX:+HeapDumpOnOutOfMemoryError");
545 cmdLine.addParameter("-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=" + debugPort);
548 if (Registry.is("compiler.server.use.memory.temp.cache")) {
549 cmdLine.addParameter("-D"+ GlobalOptions.USE_MEMORY_TEMP_CACHE_OPTION + "=true");
551 if (Registry.is("compiler.server.use.external.javac.process")) {
552 cmdLine.addParameter("-D"+ GlobalOptions.USE_EXTERNAL_JAVAC_OPTION + "=true");
554 cmdLine.addParameter("-D"+ GlobalOptions.HOSTNAME_OPTION + "=" + NetUtils.getLocalHostString());
555 cmdLine.addParameter("-D"+ GlobalOptions.VM_EXE_PATH_OPTION + "=" + FileUtil.toSystemIndependentName(vmExecutablePath));
557 // javac's VM should use the same default locale that IDEA uses in order for javac to print messages in 'correct' language
558 final String lang = System.getProperty("user.language");
560 //noinspection HardCodedStringLiteral
561 cmdLine.addParameter("-Duser.language=" + lang);
563 final String country = System.getProperty("user.country");
564 if (country != null) {
565 //noinspection HardCodedStringLiteral
566 cmdLine.addParameter("-Duser.country=" + country);
568 //noinspection HardCodedStringLiteral
569 final String region = System.getProperty("user.region");
570 if (region != null) {
571 //noinspection HardCodedStringLiteral
572 cmdLine.addParameter("-Duser.region=" + region);
575 cmdLine.addParameter("-classpath");
577 final List<File> cp = ClasspathBootstrap.getCompileServerApplicationClasspath();
579 cmdLine.addParameter(classpathToString(cp));
581 cmdLine.addParameter(org.jetbrains.jps.server.Server.class.getName());
582 cmdLine.addParameter(Integer.toString(port));
584 final File workDirectory = new File(mySystemDirectory, COMPILE_SERVER_SYSTEM_ROOT);
585 workDirectory.mkdirs();
586 ensureLogConfigExists(workDirectory);
588 cmdLine.addParameter(FileUtil.toSystemIndependentName(workDirectory.getPath()));
590 cmdLine.setWorkDirectory(workDirectory);
593 return cmdLine.createProcess();
596 private static void ensureLogConfigExists(File workDirectory) {
597 final File logConfig = new File(workDirectory, LOGGER_CONFIG);
598 if (!logConfig.exists()) {
599 FileUtil.createIfDoesntExist(logConfig);
601 final InputStream in = Server.class.getResourceAsStream("/" + DEFAULT_LOGGER_CONFIG);
604 final FileOutputStream out = new FileOutputStream(logConfig);
606 FileUtil.copy(in, out);
617 catch (IOException e) {
623 public void shutdownServer() {
624 shutdownServer(myClient, myProcessHandler);
627 private static void shutdownServer(final CompileServerClient client, final OSProcessHandler processHandler) {
629 if (client != null && client.isConnected()) {
630 final Future future = client.sendShutdownRequest();
631 future.get(500, TimeUnit.MILLISECONDS);
635 catch (Throwable ignored) {
639 if (processHandler != null) {
640 processHandler.destroyProcess();
645 private static String classpathToString(List<File> cp) {
646 StringBuilder builder = new StringBuilder();
647 for (File file : cp) {
648 if (builder.length() > 0) {
649 builder.append(File.pathSeparator);
651 builder.append(file.getAbsolutePath());
653 return builder.toString();
656 private static class AutoMakeResponseHandler extends JpsServerResponseHandler {
657 private JpsRemoteProto.Message.Response.BuildEvent.Status myBuildStatus;
658 private final Project myProject;
659 private final WolfTheProblemSolver myWolf;
661 public AutoMakeResponseHandler(Project project) {
663 myBuildStatus = JpsRemoteProto.Message.Response.BuildEvent.Status.SUCCESS;
664 myWolf = WolfTheProblemSolver.getInstance(project);
668 public boolean handleBuildEvent(JpsRemoteProto.Message.Response.BuildEvent event) {
669 final JpsRemoteProto.Message.Response.BuildEvent.Type type = event.getEventType();
670 if (type == JpsRemoteProto.Message.Response.BuildEvent.Type.FILES_GENERATED) {
671 for (JpsRemoteProto.Message.Response.BuildEvent.GeneratedFile gf : event.getGeneratedFilesList()) {
674 if (type == JpsRemoteProto.Message.Response.BuildEvent.Type.BUILD_COMPLETED) {
675 if (event.hasCompletionStatus()) {
676 myBuildStatus = event.getCompletionStatus();
684 public void handleCompileMessage(JpsRemoteProto.Message.Response.CompileMessage compileResponse) {
685 final JpsRemoteProto.Message.Response.CompileMessage.Kind kind = compileResponse.getKind();
686 if (kind == JpsRemoteProto.Message.Response.CompileMessage.Kind.ERROR) {
687 informWolf(myProject, compileResponse);
692 public void handleFailure(JpsRemoteProto.Message.Failure failure) {
693 CompilerManager.NOTIFICATION_GROUP.createNotification("Auto make failure: " + failure.getDescription(), MessageType.INFO);
697 public void sessionTerminated() {
698 String statusMessage = null/*"Auto make completed"*/;
699 switch (myBuildStatus) {
701 //statusMessage = "Auto make completed successfully";
704 //statusMessage = "All files are up-to-date";
707 statusMessage = "Auto make completed with errors";
710 statusMessage = "Auto make has been canceled";
713 if (statusMessage != null) {
714 final Notification notification = CompilerManager.NOTIFICATION_GROUP.createNotification(statusMessage, MessageType.INFO);
715 if (!myProject.isDisposed()) {
716 notification.notify(myProject);
721 private void informWolf(Project project, JpsRemoteProto.Message.Response.CompileMessage message) {
722 final String srcPath = message.getSourceFilePath();
723 if (srcPath != null && !project.isDisposed()) {
724 final VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(srcPath);
726 final int line = (int)message.getLine();
727 final int column = (int)message.getColumn();
728 if (line > 0 && column > 0) {
729 final Problem problem = myWolf.convertToProblem(vFile, line, column, new String[]{message.getText()});
730 myWolf.weHaveGotProblems(vFile, Collections.singletonList(problem));
740 private class ProjectWatcher extends ProjectManagerAdapter {
741 private final Map<Project, MessageBusConnection> myConnections = new HashMap<Project, MessageBusConnection>();
743 public void projectOpened(final Project project) {
744 final MessageBusConnection conn = project.getMessageBus().connect();
745 myConnections.put(project, conn);
746 conn.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootListener() {
747 public void beforeRootsChange(final ModuleRootEvent event) {
748 sendReloadRequest(project);
751 public void rootsChanged(final ModuleRootEvent event) {
752 myTaskExecutor.submit(new Runnable() {
755 // this will reload sdks and global libraries
756 final CompileServerClient client = ensureServerRunningAndClientConnected(false);
757 if (client != null) {
758 sendSetupRequest(client);
761 catch (Throwable e) {
770 public void projectClosing(Project project) {
771 sendReloadRequest(project);
774 public void projectClosed(Project project) {
775 final MessageBusConnection conn = myConnections.remove(project);