9648285288a1ae149f7658a1bfd1904c900ed4fb
[idea/community.git] / java / compiler / impl / src / com / intellij / compiler / server / BuildManager.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.compiler.server;
17
18 import com.intellij.ProjectTopics;
19 import com.intellij.compiler.CompilerWorkspaceConfiguration;
20 import com.intellij.compiler.impl.CompilerUtil;
21 import com.intellij.compiler.impl.javaCompiler.javac.JavacConfiguration;
22 import com.intellij.compiler.server.impl.BuildProcessClasspathManager;
23 import com.intellij.execution.ExecutionAdapter;
24 import com.intellij.execution.ExecutionException;
25 import com.intellij.execution.ExecutionManager;
26 import com.intellij.execution.configurations.GeneralCommandLine;
27 import com.intellij.execution.configurations.RunProfile;
28 import com.intellij.execution.process.OSProcessHandler;
29 import com.intellij.execution.process.ProcessAdapter;
30 import com.intellij.execution.process.ProcessEvent;
31 import com.intellij.execution.process.ProcessHandler;
32 import com.intellij.ide.DataManager;
33 import com.intellij.ide.PowerSaveMode;
34 import com.intellij.ide.file.BatchFileChangeListener;
35 import com.intellij.openapi.Disposable;
36 import com.intellij.openapi.actionSystem.CommonDataKeys;
37 import com.intellij.openapi.application.Application;
38 import com.intellij.openapi.application.ApplicationManager;
39 import com.intellij.openapi.application.ModalityState;
40 import com.intellij.openapi.application.PathManager;
41 import com.intellij.openapi.compiler.CompilationStatusListener;
42 import com.intellij.openapi.compiler.CompileContext;
43 import com.intellij.openapi.compiler.CompilerTopics;
44 import com.intellij.openapi.components.ApplicationComponent;
45 import com.intellij.openapi.diagnostic.Logger;
46 import com.intellij.openapi.editor.EditorFactory;
47 import com.intellij.openapi.editor.event.DocumentAdapter;
48 import com.intellij.openapi.editor.event.DocumentEvent;
49 import com.intellij.openapi.fileEditor.FileDocumentManager;
50 import com.intellij.openapi.fileEditor.impl.FileDocumentManagerImpl;
51 import com.intellij.openapi.fileTypes.impl.FileTypeManagerImpl;
52 import com.intellij.openapi.module.Module;
53 import com.intellij.openapi.module.ModuleManager;
54 import com.intellij.openapi.project.Project;
55 import com.intellij.openapi.project.ProjectCoreUtil;
56 import com.intellij.openapi.project.ProjectManager;
57 import com.intellij.openapi.project.ProjectManagerAdapter;
58 import com.intellij.openapi.projectRoots.JavaSdk;
59 import com.intellij.openapi.projectRoots.JavaSdkType;
60 import com.intellij.openapi.projectRoots.JavaSdkVersion;
61 import com.intellij.openapi.projectRoots.Sdk;
62 import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl;
63 import com.intellij.openapi.roots.*;
64 import com.intellij.openapi.startup.StartupManager;
65 import com.intellij.openapi.util.*;
66 import com.intellij.openapi.util.io.FileUtil;
67 import com.intellij.openapi.util.registry.Registry;
68 import com.intellij.openapi.util.text.StringUtil;
69 import com.intellij.openapi.vfs.CharsetToolkit;
70 import com.intellij.openapi.vfs.LocalFileSystem;
71 import com.intellij.openapi.vfs.VirtualFile;
72 import com.intellij.openapi.vfs.VirtualFileManager;
73 import com.intellij.openapi.vfs.newvfs.BulkFileListener;
74 import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
75 import com.intellij.openapi.vfs.newvfs.impl.FileNameCache;
76 import com.intellij.openapi.wm.IdeFrame;
77 import com.intellij.util.Alarm;
78 import com.intellij.util.ArrayUtil;
79 import com.intellij.util.Function;
80 import com.intellij.util.SmartList;
81 import com.intellij.util.concurrency.Semaphore;
82 import com.intellij.util.concurrency.SequentialTaskExecutor;
83 import com.intellij.util.containers.IntArrayList;
84 import com.intellij.util.io.storage.HeavyProcessLatch;
85 import com.intellij.util.messages.MessageBusConnection;
86 import com.intellij.util.net.NetUtils;
87 import gnu.trove.THashSet;
88 import io.netty.bootstrap.ServerBootstrap;
89 import io.netty.channel.Channel;
90 import io.netty.channel.ChannelInitializer;
91 import io.netty.channel.nio.NioEventLoopGroup;
92 import io.netty.handler.codec.protobuf.ProtobufDecoder;
93 import io.netty.handler.codec.protobuf.ProtobufEncoder;
94 import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
95 import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
96 import io.netty.util.internal.ThreadLocalRandom;
97 import org.jetbrains.annotations.NotNull;
98 import org.jetbrains.annotations.Nullable;
99 import org.jetbrains.ide.PooledThreadExecutor;
100 import org.jetbrains.io.ChannelRegistrar;
101 import org.jetbrains.io.NettyUtil;
102 import org.jetbrains.jps.api.*;
103 import org.jetbrains.jps.cmdline.BuildMain;
104 import org.jetbrains.jps.cmdline.ClasspathBootstrap;
105 import org.jetbrains.jps.incremental.Utils;
106 import org.jetbrains.jps.model.serialization.JpsGlobalLoader;
107
108 import javax.swing.*;
109 import javax.tools.*;
110 import java.awt.*;
111 import java.io.File;
112 import java.io.IOException;
113 import java.net.InetSocketAddress;
114 import java.nio.charset.Charset;
115 import java.util.*;
116 import java.util.List;
117 import java.util.concurrent.*;
118 import java.util.concurrent.atomic.AtomicBoolean;
119
120 import static org.jetbrains.jps.api.CmdlineRemoteProto.Message.ControllerMessage.ParametersMessage.TargetTypeBuildScope;
121
122 /**
123  * @author Eugene Zhuravlev
124  *         Date: 9/6/11
125  */
126 public class BuildManager implements ApplicationComponent{
127   public static final Key<Boolean> ALLOW_AUTOMAKE = Key.create("_allow_automake_when_process_is_active_");
128   private static final Key<String> FORCE_MODEL_LOADING_PARAMETER = Key.create(BuildParametersKeys.FORCE_MODEL_LOADING);
129   private static final Key<CharSequence> STDERR_OUTPUT = Key.create("_process_launch_errors_");
130
131   private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.server.BuildManager");
132   private static final String COMPILER_PROCESS_JDK_PROPERTY = "compiler.process.jdk";
133   public static final String SYSTEM_ROOT = "compile-server";
134   public static final String TEMP_DIR_NAME = "_temp_";
135   private static final boolean IS_UNIT_TEST_MODE = ApplicationManager.getApplication().isUnitTestMode();
136   private static final String IWS_EXTENSION = ".iws";
137   private static final String IPR_EXTENSION = ".ipr";
138   private static final String IDEA_PROJECT_DIR_PATTERN = "/.idea/";
139   private static final Function<String, Boolean> PATH_FILTER =
140     SystemInfo.isFileSystemCaseSensitive?
141     new Function<String, Boolean>() {
142       @Override
143       public Boolean fun(String s) {
144         return !(s.contains(IDEA_PROJECT_DIR_PATTERN) || s.endsWith(IWS_EXTENSION) || s.endsWith(IPR_EXTENSION));
145       }
146     } :
147     new Function<String, Boolean>() {
148       @Override
149       public Boolean fun(String s) {
150         return !(StringUtil.endsWithIgnoreCase(s, IWS_EXTENSION) || StringUtil.endsWithIgnoreCase(s, IPR_EXTENSION) || StringUtil.containsIgnoreCase(s, IDEA_PROJECT_DIR_PATTERN));
151       }
152     };
153
154   private final File mySystemDirectory;
155   private final ProjectManager myProjectManager;
156
157   private final Map<TaskFuture, Project> myAutomakeFutures = Collections.synchronizedMap(new HashMap<TaskFuture, Project>());
158   private final Map<String, RequestFuture> myBuildsInProgress = Collections.synchronizedMap(new HashMap<String, RequestFuture>());
159   private final Map<String, Future<Pair<RequestFuture<PreloadedProcessMessageHandler>, OSProcessHandler>>> myPreloadedBuilds =
160     Collections.synchronizedMap(new HashMap<String, Future<Pair<RequestFuture<PreloadedProcessMessageHandler>, OSProcessHandler>>>());
161   private final BuildProcessClasspathManager myClasspathManager = new BuildProcessClasspathManager();
162   private final SequentialTaskExecutor myRequestsProcessor = new SequentialTaskExecutor(PooledThreadExecutor.INSTANCE);
163   private final Map<String, ProjectData> myProjectDataMap = Collections.synchronizedMap(new HashMap<String, ProjectData>());
164
165   private final BuildManagerPeriodicTask myAutoMakeTask = new BuildManagerPeriodicTask() {
166     @Override
167     protected int getDelay() {
168       return Registry.intValue("compiler.automake.trigger.delay");
169     }
170
171     @Override
172     protected void runTask() {
173       runAutoMake();
174     }
175   };
176
177   private final BuildManagerPeriodicTask myDocumentSaveTask = new BuildManagerPeriodicTask() {
178     @Override
179     protected int getDelay() {
180       return Registry.intValue("compiler.document.save.trigger.delay");
181     }
182
183     private final Semaphore mySemaphore = new Semaphore();
184     private final Runnable mySaveDocsRunnable = new Runnable() {
185       @Override
186       public void run() {
187         try {
188           ((FileDocumentManagerImpl)FileDocumentManager.getInstance()).saveAllDocuments(false);
189         }
190         finally {
191           mySemaphore.up();
192         }
193       }
194     };
195
196     @Override
197     public void runTask() {
198       if (shouldSaveDocuments()) {
199         mySemaphore.down();
200         ApplicationManager.getApplication().invokeLater(mySaveDocsRunnable, ModalityState.NON_MODAL);
201         mySemaphore.waitFor();
202       }
203     }
204
205     private boolean shouldSaveDocuments() {
206       final Project contextProject = getCurrentContextProject();
207       return contextProject != null && canStartAutoMake(contextProject);
208     }
209   };
210
211   private final ChannelRegistrar myChannelRegistrar = new ChannelRegistrar();
212
213   private final BuildMessageDispatcher myMessageDispatcher = new BuildMessageDispatcher();
214   private volatile int myListenPort = -1;
215   @NotNull
216   private final Charset mySystemCharset = CharsetToolkit.getDefaultSystemCharset();
217
218   public BuildManager(final ProjectManager projectManager) {
219     final Application application = ApplicationManager.getApplication();
220     myProjectManager = projectManager;
221     final String systemPath = PathManager.getSystemPath();
222     File system = new File(systemPath);
223     try {
224       system = system.getCanonicalFile();
225     }
226     catch (IOException e) {
227       LOG.info(e);
228     }
229     mySystemDirectory = system;
230
231     projectManager.addProjectManagerListener(new ProjectWatcher());
232
233     final MessageBusConnection conn = application.getMessageBus().connect();
234     conn.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener.Adapter() {
235       @Override
236       public void after(@NotNull List<? extends VFileEvent> events) {
237         if (shouldTriggerMake(events)) {
238           scheduleAutoMake();
239         }
240       }
241
242       private boolean shouldTriggerMake(List<? extends VFileEvent> events) {
243         if (PowerSaveMode.isEnabled()) {
244           return false;
245         }
246
247         Project project = null;
248         ProjectFileIndex fileIndex = null;
249
250         for (VFileEvent event : events) {
251           final VirtualFile eventFile = event.getFile();
252           if (eventFile == null) {
253             continue;
254           }
255           if (!eventFile.isValid()) {
256             return true; // should be deleted
257           }
258
259           if (project == null) {
260             // lazy init
261             project = getCurrentContextProject();
262             if (project == null) {
263               return false;
264             }
265             fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
266           }
267
268           if (fileIndex.isInContent(eventFile)) {
269             if (ProjectCoreUtil.isProjectOrWorkspaceFile(eventFile)) {
270               continue;
271             }
272
273             return true;
274           }
275         }
276         return false;
277       }
278
279     });
280
281     conn.subscribe(BatchFileChangeListener.TOPIC, new BatchFileChangeListener.Adapter() {
282       public void batchChangeStarted(Project project) {
283         cancelAutoMakeTasks(project);
284       }
285     });
286
287     EditorFactory.getInstance().getEventMulticaster().addDocumentListener(new DocumentAdapter() {
288       @Override
289       public void documentChanged(DocumentEvent e) {
290         scheduleProjectSave();
291       }
292     });
293
294     ShutDownTracker.getInstance().registerShutdownTask(new Runnable() {
295       @Override
296       public void run() {
297         stopListening();
298       }
299     });
300   }
301
302   private List<Project> getOpenProjects() {
303     final Project[] projects = myProjectManager.getOpenProjects();
304     if (projects.length == 0) {
305       return Collections.emptyList();
306     }
307     final List<Project> projectList = new SmartList<Project>();
308     for (Project project : projects) {
309       if (isValidProject(project)) {
310         projectList.add(project);
311       }
312     }
313     return projectList;
314   }
315
316   private static boolean isValidProject(@Nullable Project project) {
317     return project != null && !project.isDisposed() && !project.isDefault() && project.isInitialized();
318   }
319
320   public static BuildManager getInstance() {
321     return ApplicationManager.getApplication().getComponent(BuildManager.class);
322   }
323
324   public void notifyFilesChanged(final Collection<File> paths) {
325     doNotify(paths, false);
326   }
327
328   public void notifyFilesDeleted(Collection<File> paths) {
329     doNotify(paths, true);
330   }
331
332   public void runCommand(Runnable command) {
333     myRequestsProcessor.submit(command);
334   }
335
336   private void doNotify(final Collection<File> paths, final boolean notifyDeletion) {
337     // ensure events processed in the order they arrived
338     runCommand(new Runnable() {
339
340       @Override
341       public void run() {
342         final List<String> filtered = new ArrayList<String>(paths.size());
343         for (File file : paths) {
344           final String path = FileUtil.toSystemIndependentName(file.getPath());
345           if (PATH_FILTER.fun(path)) {
346             filtered.add(path);
347           }
348         }
349         if (filtered.isEmpty()) {
350           return;
351         }
352         synchronized (myProjectDataMap) {
353           if (IS_UNIT_TEST_MODE) {
354             if (notifyDeletion) {
355               LOG.info("Registering deleted paths: " + filtered);
356             }
357             else {
358               LOG.info("Registering changed paths: " + filtered);
359             }
360           }
361           for (Map.Entry<String, ProjectData> entry : myProjectDataMap.entrySet()) {
362             final ProjectData data = entry.getValue();
363             if (notifyDeletion) {
364               data.addDeleted(filtered);
365             }
366             else {
367               data.addChanged(filtered);
368             }
369             final RequestFuture future = myBuildsInProgress.get(entry.getKey());
370             if (future != null && !future.isCancelled() && !future.isDone()) {
371               final UUID sessionId = future.getRequestID();
372               final Channel channel = myMessageDispatcher.getConnectedChannel(sessionId);
373               if (channel != null) {
374                 final CmdlineRemoteProto.Message.ControllerMessage message =
375                   CmdlineRemoteProto.Message.ControllerMessage.newBuilder().setType(
376                     CmdlineRemoteProto.Message.ControllerMessage.Type.FS_EVENT).setFsEvent(data.createNextEvent()).build();
377                 channel.writeAndFlush(CmdlineProtoUtil.toMessage(sessionId, message));
378               }
379             }
380           }
381         }
382       }
383     });
384   }
385
386   public static void forceModelLoading(CompileContext context) {
387     context.getCompileScope().putUserData(FORCE_MODEL_LOADING_PARAMETER, Boolean.TRUE.toString());
388   }
389
390   public void clearState(Project project) {
391     final String projectPath = getProjectPath(project);
392
393     cancelPreloadedBuilds(projectPath);
394
395     synchronized (myProjectDataMap) {
396       final ProjectData data = myProjectDataMap.get(projectPath);
397       if (data != null) {
398         data.dropChanges();
399       }
400     }
401     scheduleAutoMake();
402   }
403
404   public boolean isProjectWatched(Project project) {
405     return myProjectDataMap.containsKey(getProjectPath(project));
406   }
407
408   @Nullable
409   public List<String> getFilesChangedSinceLastCompilation(Project project) {
410     String projectPath = getProjectPath(project);
411     synchronized (myProjectDataMap) {
412       ProjectData data = myProjectDataMap.get(projectPath);
413       if (data != null && !data.myNeedRescan) {
414         return convertToStringPaths(data.myChanged);
415       }
416       return null;
417     }
418   }
419
420   private static List<String> convertToStringPaths(final Collection<InternedPath> interned) {
421     final ArrayList<String> list = new ArrayList<String>(interned.size());
422     for (InternedPath path : interned) {
423       list.add(path.getValue());
424     }
425     return list;
426   }
427
428   @Nullable
429   private static String getProjectPath(final Project project) {
430     final String url = project.getPresentableUrl();
431     if (url == null) {
432       return null;
433     }
434     return VirtualFileManager.extractPath(url);
435   }
436
437   public void scheduleAutoMake() {
438     if (!IS_UNIT_TEST_MODE && !PowerSaveMode.isEnabled()) {
439       myAutoMakeTask.schedule();
440     }
441   }
442
443   private void scheduleProjectSave() {
444     if (!IS_UNIT_TEST_MODE && !PowerSaveMode.isEnabled()) {
445       myDocumentSaveTask.schedule();
446     }
447   }
448
449   private void runAutoMake() {
450     final Project project = getCurrentContextProject();
451     if (project == null || !canStartAutoMake(project)) {
452       return;
453     }
454     final List<TargetTypeBuildScope> scopes = CmdlineProtoUtil.createAllModulesScopes(false);
455     final AutoMakeMessageHandler handler = new AutoMakeMessageHandler(project);
456     final TaskFuture future = scheduleBuild(
457       project, false, true, false, scopes, Collections.<String>emptyList(), Collections.<String, String>emptyMap(), handler
458     );
459     if (future != null) {
460       myAutomakeFutures.put(future, project);
461       try {
462         future.waitFor();
463       }
464       finally {
465         myAutomakeFutures.remove(future);
466       }
467     }
468   }
469
470   private static boolean canStartAutoMake(@NotNull Project project) {
471     if (project.isDisposed()) {
472       return false;
473     }
474     final CompilerWorkspaceConfiguration config = CompilerWorkspaceConfiguration.getInstance(project);
475     if (!config.MAKE_PROJECT_ON_SAVE) {
476       return false;
477     }
478     if (!config.allowAutoMakeWhileRunningApplication() && hasRunningProcess(project)) {
479       return false;
480     }
481     return true;
482   }
483
484   @Nullable
485   private Project getCurrentContextProject() {
486     return getContextProject(null);
487   }
488
489   @Nullable
490   private Project getContextProject(@Nullable Window window) {
491     final List<Project> openProjects = getOpenProjects();
492     if (openProjects.isEmpty()) {
493       return null;
494     }
495     if (openProjects.size() == 1) {
496       return openProjects.get(0);
497     }
498
499     if (window == null) {
500       window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
501       if (window == null) {
502         return null;
503       }
504     }
505
506     Component comp = window;
507     while (true) {
508       final Container _parent = comp.getParent();
509       if (_parent == null) {
510         break;
511       }
512       comp = _parent;
513     }
514
515     Project project = null;
516     if (comp instanceof IdeFrame) {
517       project = ((IdeFrame)comp).getProject();
518     }
519     if (project == null) {
520       project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(comp));
521     }
522
523     return isValidProject(project)? project : null;
524   }
525
526   private static boolean hasRunningProcess(Project project) {
527     for (ProcessHandler handler : ExecutionManager.getInstance(project).getRunningProcesses()) {
528       if (!handler.isProcessTerminated() && !ALLOW_AUTOMAKE.get(handler, Boolean.FALSE)) { // active process
529         return true;
530       }
531     }
532     return false;
533   }
534
535   public Collection<TaskFuture> cancelAutoMakeTasks(Project project) {
536     final Collection<TaskFuture> futures = new SmartList<TaskFuture>();
537     synchronized (myAutomakeFutures) {
538       for (Map.Entry<TaskFuture, Project> entry : myAutomakeFutures.entrySet()) {
539         if (entry.getValue().equals(project)) {
540           final TaskFuture future = entry.getKey();
541           future.cancel(false);
542           futures.add(future);
543         }
544       }
545     }
546     return futures;
547   }
548
549   private void cancelPreloadedBuilds(final String projectPath) {
550     runCommand(new Runnable() {
551       @Override
552       public void run() {
553         Pair<RequestFuture<PreloadedProcessMessageHandler>, OSProcessHandler> pair = takePreloadedProcess(projectPath);
554         if (pair != null) {
555           final RequestFuture<PreloadedProcessMessageHandler> future = pair.first;
556           final OSProcessHandler processHandler = pair.second;
557           myMessageDispatcher.cancelSession(future.getRequestID());
558           // waiting for preloaded process from project's task queue guarantees no build is started for this project
559           // until this one gracefully exits and closes all its storages
560           getProjectData(projectPath).taskQueue.submit(new Runnable() {
561             @Override
562             public void run() {
563               Throwable error = null;
564               try {
565                 while (!processHandler.waitFor()) {
566                   LOG.info("processHandler.waitFor() returned false for session " + future.getRequestID() + ", continue waiting");
567                 }
568               }
569               catch (Throwable e) {
570                 error = e;
571               }
572               finally {
573                 notifySessionTerminationIfNeeded(future.getRequestID(), error);
574               }
575             }
576           });
577         }
578       }
579     });
580   }
581
582   @Nullable
583   private Pair<RequestFuture<PreloadedProcessMessageHandler>, OSProcessHandler> takePreloadedProcess(String projectPath) {
584     Pair<RequestFuture<PreloadedProcessMessageHandler>, OSProcessHandler> result;
585     final Future<Pair<RequestFuture<PreloadedProcessMessageHandler>, OSProcessHandler>> preloadProgress = myPreloadedBuilds.remove(projectPath);
586     try {
587       result = preloadProgress != null ? preloadProgress.get() : null;
588     }
589     catch (Throwable e) {
590       LOG.info(e);
591       result = null;
592     }
593     return result;
594   }
595
596   @Nullable
597   public TaskFuture scheduleBuild(
598     final Project project, final boolean isRebuild, final boolean isMake,
599     final boolean onlyCheckUpToDate, final List<TargetTypeBuildScope> scopes,
600     final Collection<String> paths,
601     final Map<String, String> userData, final DefaultMessageHandler messageHandler) {
602
603     final String projectPath = getProjectPath(project);
604     final boolean isAutomake = messageHandler instanceof AutoMakeMessageHandler;
605     final BuilderMessageHandler handler = new NotifyingMessageHandler(project, messageHandler, isAutomake);
606     try {
607       ensureListening();
608     }
609     catch (Exception e) {
610       final UUID sessionId = UUID.randomUUID(); // the actual session did not start, use random UUID
611       handler.handleFailure(sessionId, CmdlineProtoUtil.createFailure(e.getMessage(), null));
612       handler.sessionTerminated(sessionId);
613       return null;
614     }
615
616     final DelegateFuture<BuilderMessageHandler> _future = new DelegateFuture<BuilderMessageHandler>();
617     // by using the same queue that processes events we ensure that
618     // the build will be aware of all events that have happened before this request
619     runCommand(new Runnable() {
620       @Override
621       public void run() {
622
623         final Pair<RequestFuture<PreloadedProcessMessageHandler>, OSProcessHandler> preloaded = takePreloadedProcess(projectPath);
624         final RequestFuture<PreloadedProcessMessageHandler> preloadedFuture = preloaded != null? preloaded.first : null;
625         final boolean usingPreloadedProcess = preloadedFuture != null;
626
627         final UUID sessionId;
628         if (usingPreloadedProcess) {
629           LOG.info("Using preloaded build process to compile " + projectPath);
630           sessionId = preloadedFuture.getRequestID();
631           preloadedFuture.getMessageHandler().setDelegateHandler(handler);
632         }
633         else {
634           sessionId = UUID.randomUUID();
635         }
636
637         final RequestFuture<? extends BuilderMessageHandler> future = usingPreloadedProcess? preloadedFuture : new RequestFuture<BuilderMessageHandler>(handler, sessionId, new CancelBuildSessionAction<BuilderMessageHandler>());
638         _future.setDelegate(future);
639
640         if (!usingPreloadedProcess && (future.isCancelled() || project.isDisposed())) {
641           // in case of preloaded process the process was already running, so the handler will be notified upon process termination
642           handler.sessionTerminated(sessionId);
643           ((BasicFuture)future).setDone();
644           return;
645         }
646
647         final CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings globals =
648           CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings.newBuilder().setGlobalOptionsPath(PathManager.getOptionsPath()).build();
649         CmdlineRemoteProto.Message.ControllerMessage.FSEvent currentFSChanges;
650         final SequentialTaskExecutor projectTaskQueue;
651         final boolean needRescan;
652         synchronized (myProjectDataMap) {
653           final ProjectData data = getProjectData(projectPath);
654           if (isRebuild) {
655             data.dropChanges();
656           }
657           if (IS_UNIT_TEST_MODE) {
658             LOG.info("Scheduling build for " +
659                      projectPath +
660                      "; CHANGED: " +
661                      new HashSet<String>(convertToStringPaths(data.myChanged)) +
662                      "; DELETED: " +
663                      new HashSet<String>(convertToStringPaths(data.myDeleted)));
664           }
665           needRescan = data.getAndResetRescanFlag();
666           currentFSChanges = needRescan ? null : data.createNextEvent();
667           projectTaskQueue = data.taskQueue;
668         }
669
670         final CmdlineRemoteProto.Message.ControllerMessage params;
671         if (isRebuild) {
672           params = CmdlineProtoUtil.createBuildRequest(projectPath, scopes, Collections.<String>emptyList(), userData, globals, null);
673         }
674         else if (onlyCheckUpToDate) {
675           params = CmdlineProtoUtil.createUpToDateCheckRequest(projectPath, scopes, paths, userData, globals, currentFSChanges);
676         }
677         else {
678           params = CmdlineProtoUtil.createBuildRequest(projectPath, scopes, isMake ? Collections.<String>emptyList() : paths, userData, globals, currentFSChanges);
679         }
680         if (!usingPreloadedProcess) {
681           myMessageDispatcher.registerBuildMessageHandler(future, params);
682         }
683
684         try {
685           projectTaskQueue.submit(new Runnable() {
686             @Override
687             public void run() {
688               Throwable execFailure = null;
689               try {
690                 if (project.isDisposed()) {
691                   if (usingPreloadedProcess) {
692                     future.cancel(false);
693                   }
694                   else {
695                     return;
696                   }
697                 }
698                 myBuildsInProgress.put(projectPath, future);
699                 final OSProcessHandler processHandler;
700                 CharSequence errorsOnLaunch;
701                 if (usingPreloadedProcess) {
702                   final boolean paramsSent = myMessageDispatcher.sendBuildParameters(future.getRequestID(), params);
703                   if (!paramsSent) {
704                     myMessageDispatcher.cancelSession(future.getRequestID());
705                   }
706                   processHandler = preloaded.second;
707                   errorsOnLaunch = STDERR_OUTPUT.get(processHandler);
708                 }
709                 else {
710                   if (isAutomake && needRescan) {
711                     // if project state was cleared because of roots changed or this is the first compilation after project opening,
712                     // ensure project model is saved on disk, so that automake sees the latest model state.
713                     // For ordinary make all project, app settings and unsaved docs are always saved before build starts.
714                     try {
715                       SwingUtilities.invokeAndWait(new Runnable() {
716                         public void run() {
717                           project.save();
718                         }
719                       });
720                     }
721                     catch(Throwable e) {
722                       LOG.info(e);
723                     }
724                   }
725                   
726                   processHandler = launchBuildProcess(project, myListenPort, sessionId, false);
727                   errorsOnLaunch = new StringBuffer();
728                   processHandler.addProcessListener(new StdOutputCollector((StringBuffer)errorsOnLaunch));
729                   processHandler.startNotify();
730                 }
731
732                 while (!processHandler.waitFor()) {
733                   LOG.info("processHandler.waitFor() returned false for session " + sessionId + ", continue waiting");
734                 }
735
736                 final int exitValue = processHandler.getProcess().exitValue();
737                 if (exitValue != 0) {
738                   final StringBuilder msg = new StringBuilder();
739                   msg.append("Abnormal build process termination: ");
740                   if (errorsOnLaunch != null && errorsOnLaunch.length() > 0) {
741                     msg.append("\n").append(errorsOnLaunch);
742                   }
743                   else {
744                     msg.append("unknown error");
745                   }
746                   handler.handleFailure(sessionId, CmdlineProtoUtil.createFailure(msg.toString(), null));
747                 }
748
749               }
750               catch (Throwable e) {
751                 execFailure = e;
752               }
753               finally {
754                 myBuildsInProgress.remove(projectPath);
755                 notifySessionTerminationIfNeeded(sessionId, execFailure);
756
757                 if (isProcessPreloadingEnabled() && !project.isDisposed()) {
758                   runCommand(new Runnable() {
759                     public void run() {
760                       if (!myPreloadedBuilds.containsKey(projectPath)) {
761                         try {
762                           final Future<Pair<RequestFuture<PreloadedProcessMessageHandler>, OSProcessHandler>> preloadResult = launchPreloadedBuildProcess(project, projectTaskQueue);
763                           myPreloadedBuilds.put(projectPath, preloadResult);
764                         }
765                         catch (Throwable e) {
766                           LOG.info("Error pre-loading build process for project " + projectPath, e);
767                         }
768                       }
769                     }
770                   });
771                 }
772
773               }
774             }
775           });
776         }
777         catch (Throwable e) {
778           handleProcessExecutionFailure(sessionId, e);
779         }
780       }
781     });
782
783     return _future;
784   }
785
786   private static boolean isProcessPreloadingEnabled() {
787     // automatically disable process preloading when debugging or testing
788     return !IS_UNIT_TEST_MODE && Registry.is("compiler.process.preload") && Registry.intValue("compiler.process.debug.port") <= 0;
789   }
790
791   private void notifySessionTerminationIfNeeded(UUID sessionId, @Nullable Throwable execFailure) {
792     if (myMessageDispatcher.getAssociatedChannel(sessionId) == null) {
793       // either the connection has never been established (process not started or execution failed), or no messages were sent from the launched process.
794       // in this case the session cannot be unregistered by the message dispatcher
795       final BuilderMessageHandler unregistered = myMessageDispatcher.unregisterBuildMessageHandler(sessionId);
796       if (unregistered != null) {
797         if (execFailure != null) {
798           unregistered.handleFailure(sessionId, CmdlineProtoUtil.createFailure(execFailure.getMessage(), execFailure));
799         }
800         unregistered.sessionTerminated(sessionId);
801       }
802     }
803   }
804
805   private void handleProcessExecutionFailure(UUID sessionId, Throwable e) {
806     final BuilderMessageHandler unregistered = myMessageDispatcher.unregisterBuildMessageHandler(sessionId);
807     if (unregistered != null) {
808       unregistered.handleFailure(sessionId, CmdlineProtoUtil.createFailure(e.getMessage(), e));
809       unregistered.sessionTerminated(sessionId);
810     }
811   }
812
813   @NotNull
814   private ProjectData getProjectData(String projectPath) {
815     synchronized (myProjectDataMap) {
816       ProjectData data = myProjectDataMap.get(projectPath);
817       if (data == null) {
818         data = new ProjectData(new SequentialTaskExecutor(PooledThreadExecutor.INSTANCE));
819         myProjectDataMap.put(projectPath, data);
820       }
821       return data;
822     }
823   }
824
825   private void ensureListening() throws Exception {
826     if (myListenPort < 0) {
827       synchronized (this) {
828         if (myListenPort < 0) {
829           myListenPort = startListening();
830         }
831       }
832     }
833   }
834
835   @Override
836   public void initComponent() {
837   }
838
839   @Override
840   public void disposeComponent() {
841     stopListening();
842   }
843
844   @NotNull
845   @Override
846   public String getComponentName() {
847     return "com.intellij.compiler.server.BuildManager";
848   }
849
850   private Future<Pair<RequestFuture<PreloadedProcessMessageHandler>, OSProcessHandler>> launchPreloadedBuildProcess(final Project project, SequentialTaskExecutor projectTaskQueue) throws Exception {
851     ensureListening();
852
853     // launching build process from projectTaskQueue ensures that no other build process for this project is currently running
854     return projectTaskQueue.submit(new Callable<Pair<RequestFuture<PreloadedProcessMessageHandler>, OSProcessHandler>>() {
855       public Pair<RequestFuture<PreloadedProcessMessageHandler>, OSProcessHandler> call() throws Exception {
856         if (project.isDisposed()) {
857           return null;
858         }
859         final RequestFuture<PreloadedProcessMessageHandler> future = new RequestFuture<PreloadedProcessMessageHandler>(new PreloadedProcessMessageHandler(), UUID.randomUUID(), new CancelBuildSessionAction<PreloadedProcessMessageHandler>());
860         try {
861           myMessageDispatcher.registerBuildMessageHandler(future, null);
862           final OSProcessHandler processHandler = launchBuildProcess(project, myListenPort, future.getRequestID(), true);
863           final StringBuffer errors = new StringBuffer();
864           processHandler.addProcessListener(new StdOutputCollector(errors));
865           STDERR_OUTPUT.set(processHandler, errors);
866
867           processHandler.startNotify();
868           return Pair.create(future, processHandler);
869         }
870         catch (Throwable e) {
871           handleProcessExecutionFailure(future.getRequestID(), e);
872           throw e instanceof Exception? (Exception)e : new RuntimeException(e);
873         }
874       }
875     });
876   }
877
878   private OSProcessHandler launchBuildProcess(Project project, final int port, final UUID sessionId, boolean requestProjectPreload) throws ExecutionException {
879     final String compilerPath;
880     final String vmExecutablePath;
881     JavaSdkVersion sdkVersion = null;
882
883     final String forcedCompiledJdkHome = Registry.stringValue(COMPILER_PROCESS_JDK_PROPERTY);
884
885     if (StringUtil.isEmptyOrSpaces(forcedCompiledJdkHome)) {
886       // choosing sdk with which the build process should be run
887       Sdk projectJdk = null;
888       int sdkMinorVersion = 0;
889
890       final Set<Sdk> candidates = new HashSet<Sdk>();
891       final Sdk defaultSdk = ProjectRootManager.getInstance(project).getProjectSdk();
892       if (defaultSdk != null && defaultSdk.getSdkType() instanceof JavaSdkType) {
893         candidates.add(defaultSdk);
894       }
895
896       for (Module module : ModuleManager.getInstance(project).getModules()) {
897         final Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
898         if (sdk != null && sdk.getSdkType() instanceof JavaSdkType) {
899           candidates.add(sdk);
900         }
901       }
902
903       // now select the latest version from the sdks that are used in the project, but not older than the internal sdk version
904       final JavaSdk javaSdkType = JavaSdk.getInstance();
905       for (Sdk candidate : candidates) {
906         final String vs = candidate.getVersionString();
907         if (vs != null) {
908           final JavaSdkVersion candidateVersion = javaSdkType.getVersion(vs);
909           if (candidateVersion != null) {
910             final int candidateMinorVersion = getMinorVersion(vs);
911             if (projectJdk == null) {
912               sdkVersion = candidateVersion;
913               sdkMinorVersion = candidateMinorVersion;
914               projectJdk = candidate;
915             }
916             else {
917               final int result = candidateVersion.compareTo(sdkVersion);
918               if (result > 0 || (result == 0 && candidateMinorVersion > sdkMinorVersion)) {
919                 sdkVersion = candidateVersion;
920                 sdkMinorVersion = candidateMinorVersion;
921                 projectJdk = candidate;
922               }
923             }
924           }
925         }
926       }
927
928       final Sdk internalJdk = JavaAwareProjectJdkTableImpl.getInstanceEx().getInternalJdk();
929       if (projectJdk == null || sdkVersion == null || !sdkVersion.isAtLeast(JavaSdkVersion.JDK_1_6)) {
930         projectJdk = internalJdk;
931       }
932
933       // validate tools.jar presence
934       final JavaSdkType projectJdkType = (JavaSdkType)projectJdk.getSdkType();
935       if (FileUtil.pathsEqual(projectJdk.getHomePath(), internalJdk.getHomePath())) {
936         // important: because internal JDK can be either JDK or JRE,
937         // this is the most universal way to obtain tools.jar path in this particular case
938         final JavaCompiler systemCompiler = ToolProvider.getSystemJavaCompiler();
939         if (systemCompiler == null) {
940           throw new ExecutionException("No system java compiler is provided by the JRE. Make sure tools.jar is present in IntelliJ IDEA classpath.");
941         }
942         compilerPath = ClasspathBootstrap.getResourcePath(systemCompiler.getClass());
943       }
944       else {
945         compilerPath = projectJdkType.getToolsPath(projectJdk);
946         if (compilerPath == null) {
947           throw new ExecutionException("Cannot determine path to 'tools.jar' library for " + projectJdk.getName() + " (" + projectJdk.getHomePath() + ")");
948         }
949       }
950
951       vmExecutablePath = projectJdkType.getVMExecutablePath(projectJdk);
952     }
953     else {
954       compilerPath = new File(forcedCompiledJdkHome, "lib/tools.jar").getAbsolutePath();
955       vmExecutablePath = new File(forcedCompiledJdkHome, "bin/java").getAbsolutePath();
956     }
957
958     final CompilerWorkspaceConfiguration config = CompilerWorkspaceConfiguration.getInstance(project);
959     final GeneralCommandLine cmdLine = new GeneralCommandLine();
960     cmdLine.setExePath(vmExecutablePath);
961     //cmdLine.addParameter("-XX:MaxPermSize=150m");
962     //cmdLine.addParameter("-XX:ReservedCodeCacheSize=64m");
963     final int heapSize = config.getProcessHeapSize(JavacConfiguration.getOptions(project, JavacConfiguration.class).MAXIMUM_HEAP_SIZE);
964
965     cmdLine.addParameter("-Xmx" + heapSize + "m");
966
967     if (SystemInfo.isMac && sdkVersion != null && JavaSdkVersion.JDK_1_6.equals(sdkVersion) && Registry.is("compiler.process.32bit.vm.on.mac")) {
968       // unfortunately -d32 is supported on jdk 1.6 only
969       cmdLine.addParameter("-d32");
970     }
971
972     cmdLine.addParameter("-Djava.awt.headless=true");
973     if (sdkVersion != null && sdkVersion.ordinal() < JavaSdkVersion.JDK_1_9.ordinal()) {
974       //-Djava.endorsed.dirs is not supported in JDK 9+, may result in abnormal process termination
975       cmdLine.addParameter("-Djava.endorsed.dirs=\"\""); // turn off all jre customizations for predictable behaviour
976     }
977     if (IS_UNIT_TEST_MODE) {
978       cmdLine.addParameter("-Dtest.mode=true");
979     }
980     cmdLine.addParameter("-Djdt.compiler.useSingleThread=true"); // always run eclipse compiler in single-threaded mode
981
982     if (requestProjectPreload) {
983       cmdLine.addParameter("-Dpreload.project.path=" + FileUtil.toCanonicalPath(getProjectPath(project)));
984       cmdLine.addParameter("-Dpreload.config.path=" + FileUtil.toCanonicalPath(PathManager.getOptionsPath()));
985     }
986
987     final String shouldGenerateIndex = System.getProperty(GlobalOptions.GENERATE_CLASSPATH_INDEX_OPTION);
988     if (shouldGenerateIndex != null) {
989       cmdLine.addParameter("-D"+ GlobalOptions.GENERATE_CLASSPATH_INDEX_OPTION +"=" + shouldGenerateIndex);
990     }
991     cmdLine.addParameter("-D"+ GlobalOptions.COMPILE_PARALLEL_OPTION +"=" + Boolean.toString(config.PARALLEL_COMPILATION));
992     cmdLine.addParameter("-D"+ GlobalOptions.REBUILD_ON_DEPENDENCY_CHANGE_OPTION + "=" + Boolean.toString(config.REBUILD_ON_DEPENDENCY_CHANGE));
993
994     if (Boolean.TRUE.equals(Boolean.valueOf(System.getProperty("java.net.preferIPv4Stack", "false")))) {
995       cmdLine.addParameter("-Djava.net.preferIPv4Stack=true");
996     }
997
998     final String isFSCaseSensitive = System.getProperty("idea.case.sensitive.fs", null);
999     if (isFSCaseSensitive != null) {
1000       cmdLine.addParameter("-Didea.case.sensitive.fs=" + isFSCaseSensitive);
1001     }
1002
1003     // this will make netty initialization faster on some systems
1004     cmdLine.addParameter("-Dio.netty.initialSeedUniquifier=" + ThreadLocalRandom.getInitialSeedUniquifier());
1005
1006     boolean isProfilingMode = false;
1007     final String additionalOptions = config.COMPILER_PROCESS_ADDITIONAL_VM_OPTIONS;
1008     if (!StringUtil.isEmpty(additionalOptions)) {
1009       final StringTokenizer tokenizer = new StringTokenizer(additionalOptions, " ", false);
1010       while (tokenizer.hasMoreTokens()) {
1011         final String option = tokenizer.nextToken();
1012         if ("-Dprofiling.mode=true".equals(option)) {
1013           isProfilingMode = true;
1014         }
1015         cmdLine.addParameter(option);
1016       }
1017     }
1018
1019     if (isProfilingMode) {
1020       cmdLine.addParameter("-agentlib:yjpagent=disablej2ee,disablealloc,delay=10000,sessionname=ExternalBuild");
1021     }
1022
1023     // debugging
1024     final int debugPort = Registry.intValue("compiler.process.debug.port");
1025     if (debugPort > 0) {
1026       cmdLine.addParameter("-XX:+HeapDumpOnOutOfMemoryError");
1027       cmdLine.addParameter("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=" + debugPort);
1028     }
1029
1030     if (!Registry.is("compiler.process.use.memory.temp.cache")) {
1031       cmdLine.addParameter("-D"+ GlobalOptions.USE_MEMORY_TEMP_CACHE_OPTION + "=false");
1032     }
1033
1034     // javac's VM should use the same default locale that IDEA uses in order for javac to print messages in 'correct' language
1035     cmdLine.setCharset(mySystemCharset);
1036     cmdLine.addParameter("-D" + CharsetToolkit.FILE_ENCODING_PROPERTY + "=" + mySystemCharset.name());
1037     cmdLine.addParameter("-D" + JpsGlobalLoader.FILE_TYPES_COMPONENT_NAME_KEY + "=" + FileTypeManagerImpl.getFileTypeComponentName());
1038     for (String name : new String[]{"user.language", "user.country", "user.region", PathManager.PROPERTY_PATHS_SELECTOR}) {
1039       final String value = System.getProperty(name);
1040       if (value != null) {
1041         cmdLine.addParameter("-D" + name + "=" + value);
1042       }
1043     }
1044     cmdLine.addParameter("-D" + PathManager.PROPERTY_HOME_PATH + "=" + PathManager.getHomePath());
1045     cmdLine.addParameter("-D" + PathManager.PROPERTY_CONFIG_PATH + "=" + PathManager.getConfigPath());
1046     cmdLine.addParameter("-D" + PathManager.PROPERTY_PLUGINS_PATH + "=" + PathManager.getPluginsPath());
1047
1048     cmdLine.addParameter("-D" + GlobalOptions.LOG_DIR_OPTION + "=" + FileUtil.toSystemIndependentName(getBuildLogDirectory().getAbsolutePath()));
1049
1050     final File workDirectory = getBuildSystemDirectory();
1051     //noinspection ResultOfMethodCallIgnored
1052     workDirectory.mkdirs();
1053     cmdLine.addParameter("-Djava.io.tmpdir=" + FileUtil.toSystemIndependentName(workDirectory.getPath()) + "/" + TEMP_DIR_NAME);
1054
1055     for (BuildProcessParametersProvider provider : project.getExtensions(BuildProcessParametersProvider.EP_NAME)) {
1056       final List<String> args = provider.getVMArguments();
1057       cmdLine.addParameters(args);
1058     }
1059
1060     @SuppressWarnings("UnnecessaryFullyQualifiedName")
1061     final Class<?> launcherClass = org.jetbrains.jps.cmdline.Launcher.class;
1062
1063     final List<String> launcherCp = new ArrayList<String>();
1064     launcherCp.add(ClasspathBootstrap.getResourcePath(launcherClass));
1065     launcherCp.add(compilerPath);
1066     ClasspathBootstrap.appendJavaCompilerClasspath(launcherCp);
1067     launcherCp.addAll(BuildProcessClasspathManager.getLauncherClasspath(project));
1068     cmdLine.addParameter("-classpath");
1069     cmdLine.addParameter(classpathToString(launcherCp));
1070
1071     cmdLine.addParameter(launcherClass.getName());
1072
1073     final List<String> cp = ClasspathBootstrap.getBuildProcessApplicationClasspath(true);
1074     cp.addAll(myClasspathManager.getBuildProcessPluginsClasspath(project));
1075     if (isProfilingMode) {
1076       cp.add(new File(workDirectory, "yjp-controller-api-redist.jar").getPath());
1077     }
1078     cmdLine.addParameter(classpathToString(cp));
1079
1080     cmdLine.addParameter(BuildMain.class.getName());
1081     cmdLine.addParameter(Boolean.valueOf(System.getProperty("java.net.preferIPv6Addresses", "false"))? "::1" : "127.0.0.1");
1082     cmdLine.addParameter(Integer.toString(port));
1083     cmdLine.addParameter(sessionId.toString());
1084
1085     cmdLine.addParameter(FileUtil.toSystemIndependentName(workDirectory.getPath()));
1086
1087     cmdLine.setWorkDirectory(workDirectory);
1088
1089     final Process process = cmdLine.createProcess();
1090
1091     final OSProcessHandler processHandler = new OSProcessHandler(process, null, mySystemCharset) {
1092       @Override
1093       protected boolean shouldDestroyProcessRecursively() {
1094         return true;
1095       }
1096     };
1097     processHandler.addProcessListener(new ProcessAdapter() {
1098       @Override
1099       public void onTextAvailable(ProcessEvent event, Key outputType) {
1100         // re-translate builder's output to idea.log
1101         final String text = event.getText();
1102         if (!StringUtil.isEmptyOrSpaces(text)) {
1103           LOG.info("BUILDER_PROCESS [" + outputType.toString() + "]: " + text.trim());
1104         }
1105       }
1106     });
1107
1108     return processHandler;
1109   }
1110
1111   public File getBuildSystemDirectory() {
1112     return new File(mySystemDirectory, SYSTEM_ROOT);
1113   }
1114
1115   public File getBuildLogDirectory() {
1116     return new File(PathManager.getLogPath(), "build-log");
1117   }
1118
1119   @Nullable
1120   public File getProjectSystemDirectory(Project project) {
1121     final String projectPath = getProjectPath(project);
1122     return projectPath != null? Utils.getDataStorageRoot(getBuildSystemDirectory(), projectPath) : null;
1123   }
1124
1125   private static int getMinorVersion(String vs) {
1126     final int dashIndex = vs.lastIndexOf('_');
1127     if (dashIndex >= 0) {
1128       StringBuilder builder = new StringBuilder();
1129       for (int idx = dashIndex + 1; idx < vs.length(); idx++) {
1130         final char ch = vs.charAt(idx);
1131         if (Character.isDigit(ch)) {
1132           builder.append(ch);
1133         }
1134         else {
1135           break;
1136         }
1137       }
1138       if (builder.length() > 0) {
1139         try {
1140           return Integer.parseInt(builder.toString());
1141         }
1142         catch (NumberFormatException ignored) {
1143         }
1144       }
1145     }
1146     return 0;
1147   }
1148
1149   public void stopListening() {
1150     myChannelRegistrar.close();
1151   }
1152
1153   private int startListening() throws Exception {
1154     final ServerBootstrap bootstrap = NettyUtil.nioServerBootstrap(new NioEventLoopGroup(1, PooledThreadExecutor.INSTANCE));
1155     bootstrap.childHandler(new ChannelInitializer() {
1156       @Override
1157       protected void initChannel(Channel channel) throws Exception {
1158         channel.pipeline().addLast(myChannelRegistrar,
1159                                    new ProtobufVarint32FrameDecoder(),
1160                                    new ProtobufDecoder(CmdlineRemoteProto.Message.getDefaultInstance()),
1161                                    new ProtobufVarint32LengthFieldPrepender(),
1162                                    new ProtobufEncoder(),
1163                                    myMessageDispatcher);
1164       }
1165     });
1166     Channel serverChannel = bootstrap.bind(NetUtils.getLoopbackAddress(), 0).syncUninterruptibly().channel();
1167     myChannelRegistrar.add(serverChannel);
1168     return ((InetSocketAddress)serverChannel.localAddress()).getPort();
1169   }
1170
1171   private static String classpathToString(List<String> cp) {
1172     StringBuilder builder = new StringBuilder();
1173     for (String file : cp) {
1174       if (builder.length() > 0) {
1175         builder.append(File.pathSeparator);
1176       }
1177       builder.append(FileUtil.toCanonicalPath(file));
1178     }
1179     return builder.toString();
1180   }
1181
1182   private static abstract class BuildManagerPeriodicTask implements Runnable {
1183     private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
1184     private final AtomicBoolean myInProgress = new AtomicBoolean(false);
1185     private final Runnable myTaskRunnable = new Runnable() {
1186       @Override
1187       public void run() {
1188         try {
1189           runTask();
1190         }
1191         finally {
1192           myInProgress.set(false);
1193         }
1194       }
1195     };
1196
1197     public final void schedule() {
1198       myAlarm.cancelAllRequests();
1199       final int delay = Math.max(100, getDelay());
1200       myAlarm.addRequest(this, delay);
1201     }
1202
1203     protected abstract int getDelay();
1204
1205     protected abstract void runTask();
1206
1207     @Override
1208     public final void run() {
1209       if (!HeavyProcessLatch.INSTANCE.isRunning() && !myInProgress.getAndSet(true)) {
1210         try {
1211           ApplicationManager.getApplication().executeOnPooledThread(myTaskRunnable);
1212         }
1213         catch (RejectedExecutionException ignored) {
1214           // we were shut down
1215           myInProgress.set(false);
1216         }
1217         catch (Throwable e) {
1218           myInProgress.set(false);
1219           throw new RuntimeException(e);
1220         }
1221       }
1222       else {
1223         schedule();
1224       }
1225     }
1226   }
1227
1228   private static class NotifyingMessageHandler extends DelegatingMessageHandler {
1229     private final Project myProject;
1230     private final BuilderMessageHandler myDelegateHandler;
1231     private boolean myIsAutomake;
1232
1233     public NotifyingMessageHandler(@NotNull Project project, @NotNull BuilderMessageHandler delegateHandler, final boolean isAutomake) {
1234       myProject = project;
1235       myDelegateHandler = delegateHandler;
1236       myIsAutomake = isAutomake;
1237     }
1238
1239     @Override
1240     protected BuilderMessageHandler getDelegateHandler() {
1241       return myDelegateHandler;
1242     }
1243
1244     @Override
1245     public void buildStarted(UUID sessionId) {
1246       super.buildStarted(sessionId);
1247       try {
1248         ApplicationManager
1249           .getApplication().getMessageBus().syncPublisher(BuildManagerListener.TOPIC).buildStarted(myProject, sessionId, myIsAutomake);
1250       }
1251       catch (Throwable e) {
1252         LOG.error(e);
1253       }
1254     }
1255
1256     @Override
1257     public void sessionTerminated(UUID sessionId) {
1258       try {
1259         super.sessionTerminated(sessionId);
1260       }
1261       finally {
1262         try {
1263           ApplicationManager.getApplication().getMessageBus().syncPublisher(BuildManagerListener.TOPIC).buildFinished(myProject, sessionId, myIsAutomake);
1264         }
1265         catch (Throwable e) {
1266           LOG.error(e);
1267         }
1268       }
1269     }
1270   }
1271
1272   private static final class StdOutputCollector extends ProcessAdapter {
1273     private final Appendable myOutput;
1274     private int myStoredLength = 0;
1275     public StdOutputCollector(Appendable outputSink) {
1276       myOutput = outputSink;
1277     }
1278
1279     @Override
1280     public void onTextAvailable(ProcessEvent event, Key outputType) {
1281       String text;
1282
1283       synchronized (this) {
1284         if (myStoredLength > 2048) {
1285           return;
1286         }
1287         text = event.getText();
1288         if (StringUtil.isEmptyOrSpaces(text)) {
1289           return;
1290         }
1291         myStoredLength += text.length();
1292       }
1293
1294       try {
1295         myOutput.append(text);
1296       }
1297       catch (IOException ignored) {
1298       }
1299     }
1300   }
1301
1302   private class ProjectWatcher extends ProjectManagerAdapter {
1303     private final Map<Project, MessageBusConnection> myConnections = new HashMap<Project, MessageBusConnection>();
1304
1305     @Override
1306     public void projectOpened(final Project project) {
1307       final MessageBusConnection conn = project.getMessageBus().connect();
1308       myConnections.put(project, conn);
1309       conn.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() {
1310         @Override
1311         public void rootsChanged(final ModuleRootEvent event) {
1312           final Object source = event.getSource();
1313           if (source instanceof Project) {
1314             clearState((Project)source);
1315           }
1316         }
1317       });
1318       conn.subscribe(ExecutionManager.EXECUTION_TOPIC, new ExecutionAdapter() {
1319         @Override
1320         public void processTerminated(@NotNull RunProfile runProfile, @NotNull ProcessHandler handler) {
1321           scheduleAutoMake();
1322         }
1323       });
1324       conn.subscribe(CompilerTopics.COMPILATION_STATUS, new CompilationStatusListener() {
1325         private final Set<String> myRootsToRefresh = new THashSet<String>(FileUtil.PATH_HASHING_STRATEGY);
1326         @Override
1327         public void compilationFinished(boolean aborted, int errors, int warnings, CompileContext compileContext) {
1328           final String[] roots;
1329           synchronized (myRootsToRefresh) {
1330             roots = ArrayUtil.toStringArray(myRootsToRefresh);
1331             myRootsToRefresh.clear();
1332           }
1333           if (roots.length != 0) {
1334             ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
1335               @Override
1336               public void run() {
1337                 if (project.isDisposed()) {
1338                   return;
1339                 }
1340                 final List<File> rootFiles = new ArrayList<File>(roots.length);
1341                 for (String root : roots) {
1342                   rootFiles.add(new File(root));
1343                 }
1344                 // this will ensure that we'll be able to obtain VirtualFile for existing roots
1345                 CompilerUtil.refreshOutputDirectories(rootFiles, false);
1346
1347                 final LocalFileSystem lfs = LocalFileSystem.getInstance();
1348                 final Set<VirtualFile> filesToRefresh = new HashSet<VirtualFile>();
1349                 ApplicationManager.getApplication().runReadAction(new Runnable() {
1350                   public void run() {
1351                     if (project.isDisposed()) {
1352                       return;
1353                     }
1354                     final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
1355                     for (File root : rootFiles) {
1356                       final VirtualFile rootFile = lfs.findFileByIoFile(root);
1357                       if (rootFile != null && fileIndex.isInSourceContent(rootFile)) {
1358                         filesToRefresh.add(rootFile);
1359                       }
1360                     }
1361                     if (!filesToRefresh.isEmpty()) {
1362                       lfs.refreshFiles(filesToRefresh, true, true, null);
1363                     }
1364                   }
1365                 });
1366               }
1367             });
1368           }
1369         }
1370
1371         @Override
1372         public void fileGenerated(String outputRoot, String relativePath) {
1373           synchronized (myRootsToRefresh) {
1374             myRootsToRefresh.add(outputRoot);
1375           }
1376         }
1377       });
1378       final String projectPath = getProjectPath(project);
1379       Disposer.register(project, new Disposable() {
1380         @Override
1381         public void dispose() {
1382           cancelPreloadedBuilds(projectPath);
1383           myProjectDataMap.remove(projectPath);
1384         }
1385       });
1386       StartupManager.getInstance(project).registerPostStartupActivity(new Runnable() {
1387         @Override
1388         public void run() {
1389           scheduleAutoMake(); // run automake after project opened
1390         }
1391       });
1392     }
1393
1394     @Override
1395     public boolean canCloseProject(Project project) {
1396       cancelAutoMakeTasks(project);
1397       return super.canCloseProject(project);
1398     }
1399
1400     @Override
1401     public void projectClosing(Project project) {
1402       cancelPreloadedBuilds(getProjectPath(project));
1403       for (TaskFuture future : cancelAutoMakeTasks(project)) {
1404         future.waitFor(500, TimeUnit.MILLISECONDS);
1405       }
1406     }
1407
1408     @Override
1409     public void projectClosed(Project project) {
1410       myProjectDataMap.remove(getProjectPath(project));
1411       final MessageBusConnection conn = myConnections.remove(project);
1412       if (conn != null) {
1413         conn.disconnect();
1414       }
1415     }
1416   }
1417
1418   private static class ProjectData {
1419     @NotNull
1420     final SequentialTaskExecutor taskQueue;
1421     private final Set<InternedPath> myChanged = new THashSet<InternedPath>();
1422     private final Set<InternedPath> myDeleted = new THashSet<InternedPath>();
1423     private long myNextEventOrdinal = 0L;
1424     private boolean myNeedRescan = true;
1425
1426     private ProjectData(@NotNull SequentialTaskExecutor taskQueue) {
1427       this.taskQueue = taskQueue;
1428     }
1429
1430     public void addChanged(Collection<String> paths) {
1431       if (!myNeedRescan) {
1432         for (String path : paths) {
1433           final InternedPath _path = InternedPath.create(path);
1434           myDeleted.remove(_path);
1435           myChanged.add(_path);
1436         }
1437       }
1438     }
1439
1440     public void addDeleted(Collection<String> paths) {
1441       if (!myNeedRescan) {
1442         for (String path : paths) {
1443           final InternedPath _path = InternedPath.create(path);
1444           myChanged.remove(_path);
1445           myDeleted.add(_path);
1446         }
1447       }
1448     }
1449
1450     public CmdlineRemoteProto.Message.ControllerMessage.FSEvent createNextEvent() {
1451       final CmdlineRemoteProto.Message.ControllerMessage.FSEvent.Builder builder =
1452         CmdlineRemoteProto.Message.ControllerMessage.FSEvent.newBuilder();
1453       builder.setOrdinal(++myNextEventOrdinal);
1454
1455       for (InternedPath path : myChanged) {
1456         builder.addChangedPaths(path.getValue());
1457       }
1458       myChanged.clear();
1459
1460       for (InternedPath path : myDeleted) {
1461         builder.addDeletedPaths(path.getValue());
1462       }
1463       myDeleted.clear();
1464
1465       return builder.build();
1466     }
1467
1468     public boolean getAndResetRescanFlag() {
1469       final boolean rescan = myNeedRescan;
1470       myNeedRescan = false;
1471       return rescan;
1472     }
1473
1474     public void dropChanges() {
1475       myNeedRescan = true;
1476       myNextEventOrdinal = 0L;
1477       myChanged.clear();
1478       myDeleted.clear();
1479     }
1480   }
1481
1482   private static abstract class InternedPath {
1483     protected final int[] myPath;
1484
1485     /**
1486      * @param path assuming system-independent path with forward slashes
1487      */
1488     protected InternedPath(String path) {
1489       final IntArrayList list = new IntArrayList();
1490       final StringTokenizer tokenizer = new StringTokenizer(path, "/", false);
1491       while(tokenizer.hasMoreTokens()) {
1492         final String element = tokenizer.nextToken();
1493         list.add(FileNameCache.storeName(element));
1494       }
1495       myPath = list.toArray();
1496     }
1497
1498     public abstract String getValue();
1499
1500     @Override
1501     public boolean equals(Object o) {
1502       if (this == o) return true;
1503       if (o == null || getClass() != o.getClass()) return false;
1504
1505       InternedPath path = (InternedPath)o;
1506
1507       if (!Arrays.equals(myPath, path.myPath)) return false;
1508
1509       return true;
1510     }
1511
1512     @Override
1513     public int hashCode() {
1514       return Arrays.hashCode(myPath);
1515     }
1516
1517     public static InternedPath create(String path) {
1518       return path.startsWith("/")? new XInternedPath(path) : new WinInternedPath(path);
1519     }
1520   }
1521
1522   private static class WinInternedPath extends InternedPath {
1523     private WinInternedPath(String path) {
1524       super(path);
1525     }
1526
1527     @Override
1528     public String getValue() {
1529       if (myPath.length == 1) {
1530         final String name = FileNameCache.getVFileName(myPath[0]).toString();
1531         // handle case of windows drive letter
1532         return name.length() == 2 && name.endsWith(":")? name + "/" : name;
1533       }
1534
1535       final StringBuilder buf = new StringBuilder();
1536       for (int element : myPath) {
1537         if (buf.length() > 0) {
1538           buf.append("/");
1539         }
1540         buf.append(FileNameCache.getVFileName(element));
1541       }
1542       return buf.toString();
1543     }
1544   }
1545
1546   private static class XInternedPath extends InternedPath {
1547     private XInternedPath(String path) {
1548       super(path);
1549     }
1550
1551     @Override
1552     public String getValue() {
1553       if (myPath.length > 0) {
1554         final StringBuilder buf = new StringBuilder();
1555         for (int element : myPath) {
1556           buf.append("/").append(FileNameCache.getVFileName(element));
1557         }
1558         return buf.toString();
1559       }
1560       return "/";
1561     }
1562   }
1563
1564   private static final class DelegateFuture<T> implements TaskFuture<T> {
1565     @Nullable
1566     private TaskFuture<? extends T> myDelegate;
1567     private Boolean myRequestedCancelState = null;
1568
1569     @NotNull
1570     public synchronized TaskFuture<? extends T> getDelegate() {
1571       TaskFuture<? extends T> delegate = myDelegate;
1572       while (delegate == null) {
1573         try {
1574           wait();
1575         }
1576         catch (InterruptedException ignored) {
1577         }
1578         delegate = myDelegate;
1579       }
1580       return delegate;
1581     }
1582
1583     public synchronized boolean setDelegate(@NotNull TaskFuture<? extends T> delegate) {
1584       if (myDelegate == null) {
1585         try {
1586           myDelegate = delegate;
1587           if (myRequestedCancelState != null) {
1588             myDelegate.cancel(myRequestedCancelState);
1589           }
1590         }
1591         finally {
1592           notifyAll();
1593         }
1594         return true;
1595       }
1596       return false;
1597     }
1598
1599     public synchronized boolean cancel(boolean mayInterruptIfRunning) {
1600       final TaskFuture<? extends T> delegate = myDelegate;
1601       if (delegate == null) {
1602         myRequestedCancelState = mayInterruptIfRunning;
1603         return true;
1604       }
1605       return delegate.cancel(mayInterruptIfRunning);
1606     }
1607
1608     public void waitFor() {
1609       getDelegate().waitFor();
1610     }
1611
1612     public boolean waitFor(long timeout, TimeUnit unit) {
1613       return getDelegate().waitFor(timeout, unit);
1614     }
1615
1616     public boolean isCancelled() {
1617       final TaskFuture<? extends T> delegate;
1618       synchronized (this) {
1619         delegate = myDelegate;
1620         if (delegate == null) {
1621           return myRequestedCancelState != null;
1622         }
1623       }
1624       return delegate.isCancelled();
1625     }
1626
1627     public boolean isDone() {
1628       final TaskFuture<? extends T> delegate;
1629       synchronized (this) {
1630         delegate = myDelegate;
1631         if (delegate == null) {
1632           return false;
1633         }
1634       }
1635       return delegate.isDone();
1636     }
1637
1638     public T get() throws InterruptedException, java.util.concurrent.ExecutionException {
1639       return getDelegate().get();
1640     }
1641
1642     public T get(long timeout, TimeUnit unit) throws InterruptedException, java.util.concurrent.ExecutionException, TimeoutException {
1643       return getDelegate().get(timeout, unit);
1644     }
1645   }
1646
1647   private class CancelBuildSessionAction<T extends BuilderMessageHandler> implements RequestFuture.CancelAction<T> {
1648     @Override
1649     public void cancel(RequestFuture<T> future) throws Exception {
1650       myMessageDispatcher.cancelSession(future.getRequestID());
1651     }
1652   }
1653 }