2 * Copyright 2000-2016 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.server;
18 import com.intellij.ProjectTopics;
19 import com.intellij.compiler.CompilerConfiguration;
20 import com.intellij.compiler.CompilerConfigurationImpl;
21 import com.intellij.compiler.CompilerWorkspaceConfiguration;
22 import com.intellij.compiler.impl.CompilerUtil;
23 import com.intellij.compiler.impl.javaCompiler.BackendCompiler;
24 import com.intellij.compiler.impl.javaCompiler.javac.JavacConfiguration;
25 import com.intellij.compiler.server.impl.BuildProcessClasspathManager;
26 import com.intellij.concurrency.JobScheduler;
27 import com.intellij.execution.ExecutionAdapter;
28 import com.intellij.execution.ExecutionException;
29 import com.intellij.execution.ExecutionManager;
30 import com.intellij.execution.configurations.GeneralCommandLine;
31 import com.intellij.execution.configurations.RunProfile;
32 import com.intellij.execution.process.OSProcessHandler;
33 import com.intellij.execution.process.ProcessAdapter;
34 import com.intellij.execution.process.ProcessEvent;
35 import com.intellij.execution.process.ProcessHandler;
36 import com.intellij.ide.DataManager;
37 import com.intellij.ide.PowerSaveMode;
38 import com.intellij.ide.file.BatchFileChangeListener;
39 import com.intellij.openapi.Disposable;
40 import com.intellij.openapi.actionSystem.CommonDataKeys;
41 import com.intellij.openapi.application.Application;
42 import com.intellij.openapi.application.ApplicationManager;
43 import com.intellij.openapi.application.PathManager;
44 import com.intellij.openapi.application.TransactionGuard;
45 import com.intellij.openapi.compiler.CompilationStatusListener;
46 import com.intellij.openapi.compiler.CompileContext;
47 import com.intellij.openapi.compiler.CompilerPaths;
48 import com.intellij.openapi.compiler.CompilerTopics;
49 import com.intellij.openapi.diagnostic.Logger;
50 import com.intellij.openapi.editor.EditorFactory;
51 import com.intellij.openapi.editor.event.DocumentAdapter;
52 import com.intellij.openapi.editor.event.DocumentEvent;
53 import com.intellij.openapi.fileEditor.FileDocumentManager;
54 import com.intellij.openapi.fileEditor.impl.FileDocumentManagerImpl;
55 import com.intellij.openapi.fileTypes.impl.FileTypeManagerImpl;
56 import com.intellij.openapi.module.Module;
57 import com.intellij.openapi.module.ModuleManager;
58 import com.intellij.openapi.project.Project;
59 import com.intellij.openapi.project.ProjectCoreUtil;
60 import com.intellij.openapi.project.ProjectManager;
61 import com.intellij.openapi.project.ProjectManagerAdapter;
62 import com.intellij.openapi.projectRoots.JavaSdk;
63 import com.intellij.openapi.projectRoots.JavaSdkType;
64 import com.intellij.openapi.projectRoots.JavaSdkVersion;
65 import com.intellij.openapi.projectRoots.Sdk;
66 import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl;
67 import com.intellij.openapi.roots.*;
68 import com.intellij.openapi.startup.StartupManager;
69 import com.intellij.openapi.util.*;
70 import com.intellij.openapi.util.io.FileUtil;
71 import com.intellij.openapi.util.registry.Registry;
72 import com.intellij.openapi.util.text.StringUtil;
73 import com.intellij.openapi.vfs.CharsetToolkit;
74 import com.intellij.openapi.vfs.LocalFileSystem;
75 import com.intellij.openapi.vfs.VirtualFile;
76 import com.intellij.openapi.vfs.VirtualFileManager;
77 import com.intellij.openapi.vfs.newvfs.BulkFileListener;
78 import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
79 import com.intellij.openapi.vfs.newvfs.impl.FileNameCache;
80 import com.intellij.openapi.wm.IdeFrame;
81 import com.intellij.util.*;
82 import com.intellij.util.concurrency.SequentialTaskExecutor;
83 import com.intellij.util.containers.IntArrayList;
84 import com.intellij.util.io.BaseOutputReader;
85 import com.intellij.util.io.storage.HeavyProcessLatch;
86 import com.intellij.util.messages.MessageBusConnection;
87 import com.intellij.util.net.NetUtils;
88 import com.intellij.util.text.DateFormatUtil;
89 import gnu.trove.THashSet;
90 import io.netty.bootstrap.ServerBootstrap;
91 import io.netty.channel.Channel;
92 import io.netty.channel.ChannelInitializer;
93 import io.netty.channel.nio.NioEventLoopGroup;
94 import io.netty.handler.codec.protobuf.ProtobufDecoder;
95 import io.netty.handler.codec.protobuf.ProtobufEncoder;
96 import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
97 import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
98 import io.netty.util.internal.ThreadLocalRandom;
99 import org.jetbrains.annotations.NotNull;
100 import org.jetbrains.annotations.Nullable;
101 import org.jetbrains.io.ChannelRegistrar;
102 import org.jetbrains.io.NettyKt;
103 import org.jetbrains.jps.api.*;
104 import org.jetbrains.jps.cmdline.BuildMain;
105 import org.jetbrains.jps.cmdline.ClasspathBootstrap;
106 import org.jetbrains.jps.incremental.Utils;
107 import org.jetbrains.jps.model.java.JpsJavaSdkType;
108 import org.jetbrains.jps.model.java.compiler.JavaCompilers;
109 import org.jetbrains.jps.model.serialization.JpsGlobalLoader;
111 import javax.tools.*;
114 import java.io.IOException;
115 import java.net.InetAddress;
116 import java.net.InetSocketAddress;
117 import java.nio.charset.Charset;
118 import java.text.SimpleDateFormat;
120 import java.util.List;
121 import java.util.concurrent.*;
122 import java.util.concurrent.atomic.AtomicBoolean;
123 import java.util.stream.Stream;
125 import static org.jetbrains.jps.api.CmdlineRemoteProto.Message.ControllerMessage.ParametersMessage.TargetTypeBuildScope;
128 * @author Eugene Zhuravlev
131 public class BuildManager implements Disposable {
132 public static final Key<Boolean> ALLOW_AUTOMAKE = Key.create("_allow_automake_when_process_is_active_");
133 private static final Key<Integer> COMPILER_PROCESS_DEBUG_PORT = Key.create("_compiler_process_debug_port_");
134 private static final Key<String> FORCE_MODEL_LOADING_PARAMETER = Key.create(BuildParametersKeys.FORCE_MODEL_LOADING);
135 private static final Key<CharSequence> STDERR_OUTPUT = Key.create("_process_launch_errors_");
136 private static final SimpleDateFormat USAGE_STAMP_DATE_FORMAT = new SimpleDateFormat("dd.MM.yyyy");
138 private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.server.BuildManager");
139 private static final String COMPILER_PROCESS_JDK_PROPERTY = "compiler.process.jdk";
140 public static final String SYSTEM_ROOT = "compile-server";
141 public static final String TEMP_DIR_NAME = "_temp_";
142 // do not make static in order not to access application on class load
143 private final boolean IS_UNIT_TEST_MODE;
144 private static final String IWS_EXTENSION = ".iws";
145 private static final String IPR_EXTENSION = ".ipr";
146 private static final String IDEA_PROJECT_DIR_PATTERN = "/.idea/";
147 private static final Function<String, Boolean> PATH_FILTER =
148 SystemInfo.isFileSystemCaseSensitive ?
149 (Function<String, Boolean>)s -> !(s.contains(IDEA_PROJECT_DIR_PATTERN) || s.endsWith(IWS_EXTENSION) || s.endsWith(IPR_EXTENSION)) :
150 (Function<String, Boolean>)s -> !(StringUtil.endsWithIgnoreCase(s, IWS_EXTENSION) || StringUtil.endsWithIgnoreCase(s, IPR_EXTENSION) || StringUtil.containsIgnoreCase(s, IDEA_PROJECT_DIR_PATTERN));
152 private final File mySystemDirectory;
153 private final ProjectManager myProjectManager;
155 private final Map<TaskFuture, Project> myAutomakeFutures = Collections.synchronizedMap(new HashMap<TaskFuture, Project>());
156 private final Map<String, RequestFuture> myBuildsInProgress = Collections.synchronizedMap(new HashMap<String, RequestFuture>());
157 private final Map<String, Future<Pair<RequestFuture<PreloadedProcessMessageHandler>, OSProcessHandler>>> myPreloadedBuilds =
158 Collections.synchronizedMap(new HashMap<String, Future<Pair<RequestFuture<PreloadedProcessMessageHandler>, OSProcessHandler>>>());
159 private final BuildProcessClasspathManager myClasspathManager = new BuildProcessClasspathManager();
160 private final ExecutorService myRequestsProcessor = SequentialTaskExecutor.createSequentialApplicationPoolExecutor();
161 private final Map<String, ProjectData> myProjectDataMap = Collections.synchronizedMap(new HashMap<String, ProjectData>());
163 private final BuildManagerPeriodicTask myAutoMakeTask = new BuildManagerPeriodicTask() {
165 protected int getDelay() {
166 return Registry.intValue("compiler.automake.trigger.delay");
170 protected void runTask() {
175 private final BuildManagerPeriodicTask myDocumentSaveTask = new BuildManagerPeriodicTask() {
177 protected int getDelay() {
178 return Registry.intValue("compiler.document.save.trigger.delay");
182 public void runTask() {
183 if (shouldSaveDocuments()) {
184 TransactionGuard.getInstance().submitTransactionAndWait(() ->
185 ((FileDocumentManagerImpl)FileDocumentManager.getInstance()).saveAllDocuments(false));
189 private boolean shouldSaveDocuments() {
190 final Project contextProject = getCurrentContextProject();
191 return contextProject != null && canStartAutoMake(contextProject);
195 private final Runnable myGCTask = () -> {
196 // todo: make customizable in UI?
197 final int unusedThresholdDays = Registry.intValue("compiler.build.data.unused.threshold", -1);
198 if (unusedThresholdDays <= 0) {
201 final File buildSystemDir = getBuildSystemDirectory();
202 final File[] dirs = buildSystemDir.listFiles(pathname -> pathname.isDirectory() && !TEMP_DIR_NAME.equals(pathname.getName()));
204 final Date now = new Date();
205 for (File buildDataProjectDir : dirs) {
206 final File usageFile = getUsageFile(buildDataProjectDir);
207 if (usageFile.exists()) {
208 final Pair<Date, File> usageData = readUsageFile(usageFile);
209 if (usageData != null) {
210 final File projectFile = usageData.second;
211 if (projectFile != null && !projectFile.exists() || DateFormatUtil.getDifferenceInDays(usageData.first, now) > unusedThresholdDays) {
212 LOG.info("Clearing project build data because the project does not exist or was not opened for more than " + unusedThresholdDays + " days: " + buildDataProjectDir.getPath());
213 FileUtil.delete(buildDataProjectDir);
218 updateUsageFile(null, buildDataProjectDir); // set usage stamp to start countdown
224 private final ChannelRegistrar myChannelRegistrar = new ChannelRegistrar();
226 private final BuildMessageDispatcher myMessageDispatcher = new BuildMessageDispatcher();
227 private volatile int myListenPort = -1;
229 private final Charset mySystemCharset = CharsetToolkit.getDefaultSystemCharset();
230 private volatile boolean myBuildProcessDebuggingEnabled;
232 public BuildManager(final ProjectManager projectManager) {
233 final Application application = ApplicationManager.getApplication();
234 IS_UNIT_TEST_MODE = application.isUnitTestMode();
235 myProjectManager = projectManager;
236 final String systemPath = PathManager.getSystemPath();
237 File system = new File(systemPath);
239 system = system.getCanonicalFile();
241 catch (IOException e) {
244 mySystemDirectory = system;
246 projectManager.addProjectManagerListener(new ProjectWatcher());
248 final MessageBusConnection conn = application.getMessageBus().connect();
249 conn.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener.Adapter() {
251 public void after(@NotNull List<? extends VFileEvent> events) {
252 if (!IS_UNIT_TEST_MODE && shouldTriggerMake(events)) {
257 private boolean shouldTriggerMake(List<? extends VFileEvent> events) {
258 if (PowerSaveMode.isEnabled()) {
262 Project project = null;
263 ProjectFileIndex fileIndex = null;
265 for (VFileEvent event : events) {
266 final VirtualFile eventFile = event.getFile();
267 if (eventFile == null) {
270 if (!eventFile.isValid()) {
271 return true; // should be deleted
274 if (project == null) {
276 project = getCurrentContextProject();
277 if (project == null) {
280 fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
283 if (fileIndex.isInContent(eventFile)) {
284 if (ProjectCoreUtil.isProjectOrWorkspaceFile(eventFile) || GeneratedSourcesFilter.isGeneratedSourceByAnyFilter(eventFile, project)) {
285 // changes in project files or generated stuff should not trigger auto-make
296 conn.subscribe(BatchFileChangeListener.TOPIC, new BatchFileChangeListener.Adapter() {
298 public void batchChangeStarted(Project project) {
299 cancelAutoMakeTasks(project);
303 EditorFactory.getInstance().getEventMulticaster().addDocumentListener(new DocumentAdapter() {
305 public void documentChanged(DocumentEvent e) {
306 scheduleProjectSave();
310 ShutDownTracker.getInstance().registerShutdownTask(() -> stopListening());
312 ScheduledFuture<?> future = JobScheduler.getScheduler().scheduleWithFixedDelay((Runnable)() -> runCommand(myGCTask), 3, 180, TimeUnit.MINUTES);
313 Disposer.register(this, () -> future.cancel(false));
316 private List<Project> getOpenProjects() {
317 final Project[] projects = myProjectManager.getOpenProjects();
318 if (projects.length == 0) {
319 return Collections.emptyList();
321 final List<Project> projectList = new SmartList<Project>();
322 for (Project project : projects) {
323 if (isValidProject(project)) {
324 projectList.add(project);
330 private static boolean isValidProject(@Nullable Project project) {
331 return project != null && !project.isDisposed() && !project.isDefault() && project.isInitialized();
334 public static BuildManager getInstance() {
335 return ApplicationManager.getApplication().getComponent(BuildManager.class);
338 public void notifyFilesChanged(final Collection<File> paths) {
339 doNotify(paths, false);
342 public void notifyFilesDeleted(Collection<File> paths) {
343 doNotify(paths, true);
346 public void runCommand(@NotNull Runnable command) {
347 myRequestsProcessor.submit(command);
350 private void doNotify(final Collection<File> paths, final boolean notifyDeletion) {
351 // ensure events processed in the order they arrived
353 final List<String> filtered = new ArrayList<String>(paths.size());
354 for (File file : paths) {
355 final String path = FileUtil.toSystemIndependentName(file.getPath());
356 if (PATH_FILTER.fun(path)) {
360 if (filtered.isEmpty()) {
363 synchronized (myProjectDataMap) {
364 //if (IS_UNIT_TEST_MODE) {
365 // if (notifyDeletion) {
366 // LOG.info("Registering deleted paths: " + filtered);
369 // LOG.info("Registering changed paths: " + filtered);
372 for (Map.Entry<String, ProjectData> entry : myProjectDataMap.entrySet()) {
373 final ProjectData data = entry.getValue();
374 if (notifyDeletion) {
375 data.addDeleted(filtered);
378 data.addChanged(filtered);
380 final RequestFuture future = myBuildsInProgress.get(entry.getKey());
381 if (future != null && !future.isCancelled() && !future.isDone()) {
382 final UUID sessionId = future.getRequestID();
383 final Channel channel = myMessageDispatcher.getConnectedChannel(sessionId);
384 if (channel != null) {
385 final CmdlineRemoteProto.Message.ControllerMessage message =
386 CmdlineRemoteProto.Message.ControllerMessage.newBuilder().setType(
387 CmdlineRemoteProto.Message.ControllerMessage.Type.FS_EVENT).setFsEvent(data.createNextEvent()).build();
388 channel.writeAndFlush(CmdlineProtoUtil.toMessage(sessionId, message));
396 public static void forceModelLoading(CompileContext context) {
397 context.getCompileScope().putUserData(FORCE_MODEL_LOADING_PARAMETER, Boolean.TRUE.toString());
400 public void clearState(Project project) {
401 final String projectPath = getProjectPath(project);
403 cancelPreloadedBuilds(projectPath);
405 synchronized (myProjectDataMap) {
406 final ProjectData data = myProjectDataMap.get(projectPath);
414 public boolean isProjectWatched(Project project) {
415 return myProjectDataMap.containsKey(getProjectPath(project));
419 public List<String> getFilesChangedSinceLastCompilation(Project project) {
420 String projectPath = getProjectPath(project);
421 synchronized (myProjectDataMap) {
422 ProjectData data = myProjectDataMap.get(projectPath);
423 if (data != null && !data.myNeedRescan) {
424 return convertToStringPaths(data.myChanged);
430 private static List<String> convertToStringPaths(final Collection<InternedPath> interned) {
431 final ArrayList<String> list = new ArrayList<String>(interned.size());
432 for (InternedPath path : interned) {
433 list.add(path.getValue());
439 private static String getProjectPath(final Project project) {
440 final String url = project.getPresentableUrl();
444 return VirtualFileManager.extractPath(url);
447 public void scheduleAutoMake() {
448 if (!IS_UNIT_TEST_MODE && !PowerSaveMode.isEnabled()) {
449 myAutoMakeTask.schedule();
453 private void scheduleProjectSave() {
454 if (!IS_UNIT_TEST_MODE && !PowerSaveMode.isEnabled()) {
455 myDocumentSaveTask.schedule();
459 private void runAutoMake() {
460 final Project project = getCurrentContextProject();
461 if (project == null || !canStartAutoMake(project)) {
464 final List<TargetTypeBuildScope> scopes = CmdlineProtoUtil.createAllModulesScopes(false);
465 final AutoMakeMessageHandler handler = new AutoMakeMessageHandler(project);
466 final TaskFuture future = scheduleBuild(
467 project, false, true, false, scopes, Collections.<String>emptyList(), Collections.<String, String>emptyMap(), handler
469 if (future != null) {
470 myAutomakeFutures.put(future, project);
475 myAutomakeFutures.remove(future);
476 if (handler.unprocessedFSChangesDetected()) {
483 private static boolean canStartAutoMake(@NotNull Project project) {
484 if (project.isDisposed()) {
487 final CompilerWorkspaceConfiguration config = CompilerWorkspaceConfiguration.getInstance(project);
488 if (!config.MAKE_PROJECT_ON_SAVE) {
491 if (!config.allowAutoMakeWhileRunningApplication() && hasRunningProcess(project)) {
498 private Project getCurrentContextProject() {
499 return getContextProject(null);
503 private Project getContextProject(@Nullable Window window) {
504 final List<Project> openProjects = getOpenProjects();
505 if (openProjects.isEmpty()) {
508 if (openProjects.size() == 1) {
509 return openProjects.get(0);
512 if (window == null) {
513 window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
514 if (window == null) {
519 Component comp = window;
521 final Container _parent = comp.getParent();
522 if (_parent == null) {
528 Project project = null;
529 if (comp instanceof IdeFrame) {
530 project = ((IdeFrame)comp).getProject();
532 if (project == null) {
533 project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(comp));
536 return isValidProject(project)? project : null;
539 private static boolean hasRunningProcess(Project project) {
540 for (ProcessHandler handler : ExecutionManager.getInstance(project).getRunningProcesses()) {
541 if (!handler.isProcessTerminated() && !ALLOW_AUTOMAKE.get(handler, Boolean.FALSE)) { // active process
548 public Collection<TaskFuture> cancelAutoMakeTasks(Project project) {
549 final Collection<TaskFuture> futures = new SmartList<TaskFuture>();
550 synchronized (myAutomakeFutures) {
551 for (Map.Entry<TaskFuture, Project> entry : myAutomakeFutures.entrySet()) {
552 if (entry.getValue().equals(project)) {
553 final TaskFuture future = entry.getKey();
554 future.cancel(false);
562 private void cancelAllPreloadedBuilds() {
563 String[] paths = ArrayUtil.toStringArray(myPreloadedBuilds.keySet());
564 for (String path : paths) {
565 cancelPreloadedBuilds(path);
569 private void cancelPreloadedBuilds(final String projectPath) {
571 Pair<RequestFuture<PreloadedProcessMessageHandler>, OSProcessHandler> pair = takePreloadedProcess(projectPath);
573 final RequestFuture<PreloadedProcessMessageHandler> future = pair.first;
574 final OSProcessHandler processHandler = pair.second;
575 myMessageDispatcher.cancelSession(future.getRequestID());
576 // waiting for preloaded process from project's task queue guarantees no build is started for this project
577 // until this one gracefully exits and closes all its storages
578 getProjectData(projectPath).taskQueue.submit(() -> {
579 Throwable error = null;
581 while (!processHandler.waitFor()) {
582 LOG.info("processHandler.waitFor() returned false for session " + future.getRequestID() + ", continue waiting");
585 catch (Throwable e) {
589 notifySessionTerminationIfNeeded(future.getRequestID(), error);
597 private Pair<RequestFuture<PreloadedProcessMessageHandler>, OSProcessHandler> takePreloadedProcess(String projectPath) {
598 Pair<RequestFuture<PreloadedProcessMessageHandler>, OSProcessHandler> result;
599 final Future<Pair<RequestFuture<PreloadedProcessMessageHandler>, OSProcessHandler>> preloadProgress = myPreloadedBuilds.remove(projectPath);
601 result = preloadProgress != null ? preloadProgress.get() : null;
603 catch (Throwable e) {
607 return result != null && !result.first.isDone()? result : null;
611 public TaskFuture scheduleBuild(
612 final Project project, final boolean isRebuild, final boolean isMake,
613 final boolean onlyCheckUpToDate, final List<TargetTypeBuildScope> scopes,
614 final Collection<String> paths,
615 final Map<String, String> userData, final DefaultMessageHandler messageHandler) {
617 final String projectPath = getProjectPath(project);
618 final boolean isAutomake = messageHandler instanceof AutoMakeMessageHandler;
619 final BuilderMessageHandler handler = new NotifyingMessageHandler(project, messageHandler, isAutomake);
623 catch (Exception e) {
624 final UUID sessionId = UUID.randomUUID(); // the actual session did not start, use random UUID
625 handler.handleFailure(sessionId, CmdlineProtoUtil.createFailure(e.getMessage(), null));
626 handler.sessionTerminated(sessionId);
630 final DelegateFuture _future = new DelegateFuture();
631 // by using the same queue that processes events we ensure that
632 // the build will be aware of all events that have happened before this request
634 final Pair<RequestFuture<PreloadedProcessMessageHandler>, OSProcessHandler> preloaded = takePreloadedProcess(projectPath);
635 final RequestFuture<PreloadedProcessMessageHandler> preloadedFuture = preloaded != null? preloaded.first : null;
636 final boolean usingPreloadedProcess = preloadedFuture != null;
638 final UUID sessionId;
639 if (usingPreloadedProcess) {
640 LOG.info("Using preloaded build process to compile " + projectPath);
641 sessionId = preloadedFuture.getRequestID();
642 preloadedFuture.getMessageHandler().setDelegateHandler(handler);
645 sessionId = UUID.randomUUID();
648 final RequestFuture<? extends BuilderMessageHandler> future = usingPreloadedProcess? preloadedFuture : new RequestFuture<BuilderMessageHandler>(handler, sessionId, new CancelBuildSessionAction<BuilderMessageHandler>());
649 // futures we need to wait for: either just "future" or both "future" and "buildFuture" below
650 TaskFuture[] delegatesToWait = {future};
652 if (!usingPreloadedProcess && (future.isCancelled() || project.isDisposed())) {
653 // in case of preloaded process the process was already running, so the handler will be notified upon process termination
654 handler.sessionTerminated(sessionId);
655 ((BasicFuture)future).setDone();
658 final CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings globals =
659 CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings.newBuilder().setGlobalOptionsPath(PathManager.getOptionsPath())
661 CmdlineRemoteProto.Message.ControllerMessage.FSEvent currentFSChanges;
662 final ExecutorService projectTaskQueue;
663 final boolean needRescan;
664 synchronized (myProjectDataMap) {
665 final ProjectData data = getProjectData(projectPath);
669 if (IS_UNIT_TEST_MODE) {
670 LOG.info("Scheduling build for " +
673 new HashSet<String>(convertToStringPaths(data.myChanged)) +
675 new HashSet<String>(convertToStringPaths(data.myDeleted)));
677 needRescan = data.getAndResetRescanFlag();
678 currentFSChanges = needRescan ? null : data.createNextEvent();
679 projectTaskQueue = data.taskQueue;
682 final CmdlineRemoteProto.Message.ControllerMessage params;
684 params = CmdlineProtoUtil.createBuildRequest(projectPath, scopes, Collections.<String>emptyList(), userData, globals, null);
686 else if (onlyCheckUpToDate) {
687 params = CmdlineProtoUtil.createUpToDateCheckRequest(projectPath, scopes, paths, userData, globals, currentFSChanges);
690 params = CmdlineProtoUtil.createBuildRequest(projectPath, scopes, isMake ? Collections.<String>emptyList() : paths, userData, globals, currentFSChanges);
692 if (!usingPreloadedProcess) {
693 myMessageDispatcher.registerBuildMessageHandler(future, params);
697 Future<?> buildFuture = projectTaskQueue.submit(() -> {
698 Throwable execFailure = null;
700 if (project.isDisposed()) {
701 if (usingPreloadedProcess) {
702 future.cancel(false);
708 myBuildsInProgress.put(projectPath, future);
709 final OSProcessHandler processHandler;
710 CharSequence errorsOnLaunch;
711 if (usingPreloadedProcess) {
712 final boolean paramsSent = myMessageDispatcher.sendBuildParameters(future.getRequestID(), params);
714 myMessageDispatcher.cancelSession(future.getRequestID());
716 processHandler = preloaded.second;
717 errorsOnLaunch = STDERR_OUTPUT.get(processHandler);
720 if (isAutomake && needRescan) {
721 // if project state was cleared because of roots changed or this is the first compilation after project opening,
722 // ensure project model is saved on disk, so that automake sees the latest model state.
723 // For ordinary make all project, app settings and unsaved docs are always saved before build starts.
725 TransactionGuard.getInstance().submitTransactionAndWait(project::save);
727 catch (Throwable e) {
732 processHandler = launchBuildProcess(project, myListenPort, sessionId, false);
733 errorsOnLaunch = new StringBuffer();
734 processHandler.addProcessListener(new StdOutputCollector((StringBuffer)errorsOnLaunch));
735 processHandler.startNotify();
738 Integer debugPort = processHandler.getUserData(COMPILER_PROCESS_DEBUG_PORT);
739 if (debugPort != null) {
740 String message = "Make: waiting for debugger connection on port " + debugPort;
742 .handleCompileMessage(sessionId, CmdlineProtoUtil.createCompileProgressMessageResponse(message).getCompileMessage());
745 while (!processHandler.waitFor()) {
746 LOG.info("processHandler.waitFor() returned false for session " + sessionId + ", continue waiting");
749 final int exitValue = processHandler.getProcess().exitValue();
750 if (exitValue != 0) {
751 final StringBuilder msg = new StringBuilder();
752 msg.append("Abnormal build process termination: ");
753 if (errorsOnLaunch != null && errorsOnLaunch.length() > 0) {
754 msg.append("\n").append(errorsOnLaunch);
755 if (StringUtil.contains(errorsOnLaunch, "java.lang.NoSuchMethodError")) {
757 "\nThe error may be caused by JARs in Java Extensions directory which conflicts with libraries used by the external build process.")
759 "\nTry adding -Djava.ext.dirs=\"\" argument to 'Build process VM options' in File | Settings | Build, Execution, Deployment | Compiler to fix the problem.");
763 msg.append("unknown error");
765 handler.handleFailure(sessionId, CmdlineProtoUtil.createFailure(msg.toString(), null));
768 catch (Throwable e) {
772 myBuildsInProgress.remove(projectPath);
773 notifySessionTerminationIfNeeded(sessionId, execFailure);
775 if (isProcessPreloadingEnabled(project)) {
777 if (!myPreloadedBuilds.containsKey(projectPath)) {
779 final Future<Pair<RequestFuture<PreloadedProcessMessageHandler>, OSProcessHandler>> preloadResult =
780 launchPreloadedBuildProcess(project, projectTaskQueue);
781 myPreloadedBuilds.put(projectPath, preloadResult);
783 catch (Throwable e) {
784 LOG.info("Error pre-loading build process for project " + projectPath, e);
791 delegatesToWait = new TaskFuture[]{future, new TaskFutureAdapter<>(buildFuture)};
793 catch (Throwable e) {
794 handleProcessExecutionFailure(sessionId, e);
797 boolean set = _future.setDelegates(delegatesToWait);
804 private boolean isProcessPreloadingEnabled(Project project) {
805 // automatically disable process preloading when debugging or testing
806 if (IS_UNIT_TEST_MODE || !Registry.is("compiler.process.preload") || myBuildProcessDebuggingEnabled) {
809 if (project.isDisposed()) {
812 for (BuildProcessParametersProvider provider : project.getExtensions(BuildProcessParametersProvider.EP_NAME)) {
813 if (!provider.isProcessPreloadingEnabled()) {
820 private void notifySessionTerminationIfNeeded(UUID sessionId, @Nullable Throwable execFailure) {
821 if (myMessageDispatcher.getAssociatedChannel(sessionId) == null) {
822 // either the connection has never been established (process not started or execution failed), or no messages were sent from the launched process.
823 // in this case the session cannot be unregistered by the message dispatcher
824 final BuilderMessageHandler unregistered = myMessageDispatcher.unregisterBuildMessageHandler(sessionId);
825 if (unregistered != null) {
826 if (execFailure != null) {
827 unregistered.handleFailure(sessionId, CmdlineProtoUtil.createFailure(execFailure.getMessage(), execFailure));
829 unregistered.sessionTerminated(sessionId);
834 private void handleProcessExecutionFailure(UUID sessionId, Throwable e) {
835 final BuilderMessageHandler unregistered = myMessageDispatcher.unregisterBuildMessageHandler(sessionId);
836 if (unregistered != null) {
837 unregistered.handleFailure(sessionId, CmdlineProtoUtil.createFailure(e.getMessage(), e));
838 unregistered.sessionTerminated(sessionId);
843 private ProjectData getProjectData(String projectPath) {
844 synchronized (myProjectDataMap) {
845 ProjectData data = myProjectDataMap.get(projectPath);
847 data = new ProjectData(SequentialTaskExecutor.createSequentialApplicationPoolExecutor());
848 myProjectDataMap.put(projectPath, data);
854 private void ensureListening() throws Exception {
855 if (myListenPort < 0) {
856 synchronized (this) {
857 if (myListenPort < 0) {
858 myListenPort = startListening();
865 public void dispose() {
870 public static Pair<Sdk, JavaSdkVersion> getBuildProcessRuntimeSdk(Project project) {
871 final Set<Sdk> candidates = new LinkedHashSet<Sdk>();
872 final Sdk defaultSdk = ProjectRootManager.getInstance(project).getProjectSdk();
873 if (defaultSdk != null && defaultSdk.getSdkType() instanceof JavaSdkType) {
874 candidates.add(defaultSdk);
877 for (Module module : ModuleManager.getInstance(project).getModules()) {
878 final Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
879 if (sdk != null && sdk.getSdkType() instanceof JavaSdkType) {
884 // now select the latest version from the sdks that are used in the project, but not older than the internal sdk version
885 final JavaSdk javaSdkType = JavaSdk.getInstance();
886 Sdk projectJdk = null;
887 int sdkMinorVersion = 0;
888 JavaSdkVersion sdkVersion = null;
889 for (Sdk candidate : candidates) {
890 final String vs = candidate.getVersionString();
892 final JavaSdkVersion candidateVersion = getSdkVersion(javaSdkType, vs);
893 if (candidateVersion != null) {
894 final int candidateMinorVersion = getMinorVersion(vs);
895 if (projectJdk == null) {
896 sdkVersion = candidateVersion;
897 sdkMinorVersion = candidateMinorVersion;
898 projectJdk = candidate;
901 final int result = candidateVersion.compareTo(sdkVersion);
902 if (result > 0 || result == 0 && candidateMinorVersion > sdkMinorVersion) {
903 sdkVersion = candidateVersion;
904 sdkMinorVersion = candidateMinorVersion;
905 projectJdk = candidate;
912 final JavaSdkVersion oldestPossible = getOldestPossiblePlatformForBuildProcess(project);
914 if (projectJdk == null || sdkVersion == null || !sdkVersion.isAtLeast(oldestPossible)) {
915 final Sdk internalJdk = JavaAwareProjectJdkTableImpl.getInstanceEx().getInternalJdk();
916 projectJdk = internalJdk;
917 sdkVersion = javaSdkType.getVersion(internalJdk);
919 return Pair.create(projectJdk, sdkVersion);
923 private static JavaSdkVersion getSdkVersion(final JavaSdk javaSdkType, final String vs) {
924 JavaSdkVersion version = javaSdkType.getVersion(vs);
925 if (version == null) {
926 // Unexpected version string: e.g. early access or experimental JDK build
927 // trying to find the 'known' sdk version that would best describe the passed version string
928 final int parsed = JpsJavaSdkType.parseVersion(vs);
930 version = javaSdkType.getVersion(parsed + ".0");
937 private static JavaSdkVersion getOldestPossiblePlatformForBuildProcess(Project project) {
938 final BackendCompiler javaCompiler = ((CompilerConfigurationImpl)CompilerConfiguration.getInstance(project)).getDefaultCompiler();
939 final String id = javaCompiler != null? javaCompiler.getId() : JavaCompilers.JAVAC_ID;
940 if (id == JavaCompilers.ECLIPSE_ID || id == JavaCompilers.ECLIPSE_EMBEDDED_ID) {
941 return JavaSdkVersion.JDK_1_7; // because bundled ecj is compiled against 1.7
943 return JavaSdkVersion.JDK_1_6;
946 private Future<Pair<RequestFuture<PreloadedProcessMessageHandler>, OSProcessHandler>> launchPreloadedBuildProcess(final Project project, ExecutorService projectTaskQueue) throws Exception {
949 // launching build process from projectTaskQueue ensures that no other build process for this project is currently running
950 return projectTaskQueue.submit(() -> {
951 if (project.isDisposed()) {
954 final RequestFuture<PreloadedProcessMessageHandler> future = new RequestFuture<PreloadedProcessMessageHandler>(new PreloadedProcessMessageHandler(), UUID.randomUUID(), new CancelBuildSessionAction<PreloadedProcessMessageHandler>());
956 myMessageDispatcher.registerBuildMessageHandler(future, null);
957 final OSProcessHandler processHandler = launchBuildProcess(project, myListenPort, future.getRequestID(), true);
958 final StringBuffer errors = new StringBuffer();
959 processHandler.addProcessListener(new StdOutputCollector(errors));
960 STDERR_OUTPUT.set(processHandler, errors);
962 processHandler.startNotify();
963 return Pair.create(future, processHandler);
965 catch (Throwable e) {
966 handleProcessExecutionFailure(future.getRequestID(), e);
967 throw e instanceof Exception? (Exception)e : new RuntimeException(e);
972 private OSProcessHandler launchBuildProcess(Project project, final int port, final UUID sessionId, boolean requestProjectPreload) throws ExecutionException {
973 final String compilerPath;
974 final String vmExecutablePath;
975 JavaSdkVersion sdkVersion = null;
977 final String forcedCompiledJdkHome = Registry.stringValue(COMPILER_PROCESS_JDK_PROPERTY);
979 if (StringUtil.isEmptyOrSpaces(forcedCompiledJdkHome)) {
980 // choosing sdk with which the build process should be run
981 final Pair<Sdk, JavaSdkVersion> pair = getBuildProcessRuntimeSdk(project);
982 final Sdk projectJdk = pair.first;
983 sdkVersion = pair.second;
985 final Sdk internalJdk = JavaAwareProjectJdkTableImpl.getInstanceEx().getInternalJdk();
986 // validate tools.jar presence
987 final JavaSdkType projectJdkType = (JavaSdkType)projectJdk.getSdkType();
988 if (FileUtil.pathsEqual(projectJdk.getHomePath(), internalJdk.getHomePath())) {
989 // important: because internal JDK can be either JDK or JRE,
990 // this is the most universal way to obtain tools.jar path in this particular case
991 final JavaCompiler systemCompiler = ToolProvider.getSystemJavaCompiler();
992 if (systemCompiler == null) {
993 throw new ExecutionException("No system java compiler is provided by the JRE. Make sure tools.jar is present in IntelliJ IDEA classpath.");
995 compilerPath = ClasspathBootstrap.getResourcePath(systemCompiler.getClass());
998 compilerPath = projectJdkType.getToolsPath(projectJdk);
999 if (compilerPath == null) {
1000 throw new ExecutionException("Cannot determine path to 'tools.jar' library for " + projectJdk.getName() + " (" + projectJdk.getHomePath() + ")");
1004 vmExecutablePath = projectJdkType.getVMExecutablePath(projectJdk);
1007 compilerPath = new File(forcedCompiledJdkHome, "lib/tools.jar").getAbsolutePath();
1008 vmExecutablePath = new File(forcedCompiledJdkHome, "bin/java").getAbsolutePath();
1011 final CompilerConfiguration projectConfig = CompilerConfiguration.getInstance(project);
1012 final CompilerWorkspaceConfiguration config = CompilerWorkspaceConfiguration.getInstance(project);
1013 final GeneralCommandLine cmdLine = new GeneralCommandLine();
1014 cmdLine.setExePath(vmExecutablePath);
1015 //cmdLine.addParameter("-XX:MaxPermSize=150m");
1016 //cmdLine.addParameter("-XX:ReservedCodeCacheSize=64m");
1018 boolean isProfilingMode = false;
1019 String userDefinedHeapSize = null;
1020 final List<String> userAdditionalOptionsList = new SmartList<String>();
1021 final String userAdditionalVMOptions = config.COMPILER_PROCESS_ADDITIONAL_VM_OPTIONS;
1022 final boolean userLocalOptionsActive = !StringUtil.isEmptyOrSpaces(userAdditionalVMOptions);
1023 final String additionalOptions = userLocalOptionsActive ? userAdditionalVMOptions : projectConfig.getBuildProcessVMOptions();
1024 if (!StringUtil.isEmptyOrSpaces(additionalOptions)) {
1025 final StringTokenizer tokenizer = new StringTokenizer(additionalOptions, " ", false);
1026 while (tokenizer.hasMoreTokens()) {
1027 final String option = tokenizer.nextToken();
1028 if (StringUtil.startsWithIgnoreCase(option, "-Xmx")) {
1029 if (userLocalOptionsActive) {
1030 userDefinedHeapSize = option;
1034 if ("-Dprofiling.mode=true".equals(option)) {
1035 isProfilingMode = true;
1037 userAdditionalOptionsList.add(option);
1042 if (userDefinedHeapSize != null) {
1043 cmdLine.addParameter(userDefinedHeapSize);
1046 final int heapSize = projectConfig.getBuildProcessHeapSize(JavacConfiguration.getOptions(project, JavacConfiguration.class).MAXIMUM_HEAP_SIZE);
1047 cmdLine.addParameter("-Xmx" + heapSize + "m");
1050 if (SystemInfo.isMac && sdkVersion != null && JavaSdkVersion.JDK_1_6.equals(sdkVersion) && Registry.is("compiler.process.32bit.vm.on.mac")) {
1051 // unfortunately -d32 is supported on jdk 1.6 only
1052 cmdLine.addParameter("-d32");
1055 cmdLine.addParameter("-Djava.awt.headless=true");
1056 if (sdkVersion != null && sdkVersion.ordinal() < JavaSdkVersion.JDK_1_9.ordinal()) {
1057 //-Djava.endorsed.dirs is not supported in JDK 9+, may result in abnormal process termination
1058 cmdLine.addParameter("-Djava.endorsed.dirs=\"\""); // turn off all jre customizations for predictable behaviour
1060 if (IS_UNIT_TEST_MODE) {
1061 cmdLine.addParameter("-Dtest.mode=true");
1063 cmdLine.addParameter("-Djdt.compiler.useSingleThread=true"); // always run eclipse compiler in single-threaded mode
1065 if (requestProjectPreload) {
1066 cmdLine.addParameter("-Dpreload.project.path=" + FileUtil.toCanonicalPath(getProjectPath(project)));
1067 cmdLine.addParameter("-Dpreload.config.path=" + FileUtil.toCanonicalPath(PathManager.getOptionsPath()));
1070 final String shouldGenerateIndex = System.getProperty(GlobalOptions.GENERATE_CLASSPATH_INDEX_OPTION);
1071 if (shouldGenerateIndex != null) {
1072 cmdLine.addParameter("-D"+ GlobalOptions.GENERATE_CLASSPATH_INDEX_OPTION +"=" + shouldGenerateIndex);
1074 cmdLine.addParameter("-D"+ GlobalOptions.COMPILE_PARALLEL_OPTION +"=" + Boolean.toString(config.PARALLEL_COMPILATION));
1075 cmdLine.addParameter("-D"+ GlobalOptions.REBUILD_ON_DEPENDENCY_CHANGE_OPTION + "=" + Boolean.toString(config.REBUILD_ON_DEPENDENCY_CHANGE));
1077 if (Boolean.TRUE.equals(Boolean.valueOf(System.getProperty("java.net.preferIPv4Stack", "false")))) {
1078 cmdLine.addParameter("-Djava.net.preferIPv4Stack=true");
1081 // this will make netty initialization faster on some systems
1082 cmdLine.addParameter("-Dio.netty.initialSeedUniquifier=" + ThreadLocalRandom.getInitialSeedUniquifier());
1084 for (String option : userAdditionalOptionsList) {
1085 cmdLine.addParameter(option);
1087 if (isProfilingMode) {
1088 cmdLine.addParameter("-agentlib:yjpagent=disablealloc,delay=10000,sessionname=ExternalBuild");
1093 if (myBuildProcessDebuggingEnabled) {
1094 debugPort = Registry.intValue("compiler.process.debug.port");
1095 if (debugPort <= 0) {
1097 debugPort = NetUtils.findAvailableSocketPort();
1099 catch (IOException e) {
1100 throw new ExecutionException("Cannot find free port to debug build process", e);
1103 if (debugPort > 0) {
1104 cmdLine.addParameter("-XX:+HeapDumpOnOutOfMemoryError");
1105 cmdLine.addParameter("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=" + debugPort);
1109 if (!Registry.is("compiler.process.use.memory.temp.cache")) {
1110 cmdLine.addParameter("-D"+ GlobalOptions.USE_MEMORY_TEMP_CACHE_OPTION + "=false");
1113 // javac's VM should use the same default locale that IDEA uses in order for javac to print messages in 'correct' language
1114 cmdLine.setCharset(mySystemCharset);
1115 cmdLine.addParameter("-D" + CharsetToolkit.FILE_ENCODING_PROPERTY + "=" + mySystemCharset.name());
1116 cmdLine.addParameter("-D" + JpsGlobalLoader.FILE_TYPES_COMPONENT_NAME_KEY + "=" + FileTypeManagerImpl.getFileTypeComponentName());
1117 String[] propertiesToPass = {"user.language", "user.country", "user.region", PathManager.PROPERTY_PATHS_SELECTOR, "idea.case.sensitive.fs"};
1118 for (String name : propertiesToPass) {
1119 final String value = System.getProperty(name);
1120 if (value != null) {
1121 cmdLine.addParameter("-D" + name + "=" + value);
1124 cmdLine.addParameter("-D" + PathManager.PROPERTY_HOME_PATH + "=" + PathManager.getHomePath());
1125 cmdLine.addParameter("-D" + PathManager.PROPERTY_CONFIG_PATH + "=" + PathManager.getConfigPath());
1126 cmdLine.addParameter("-D" + PathManager.PROPERTY_PLUGINS_PATH + "=" + PathManager.getPluginsPath());
1128 cmdLine.addParameter("-D" + GlobalOptions.LOG_DIR_OPTION + "=" + FileUtil.toSystemIndependentName(getBuildLogDirectory().getAbsolutePath()));
1129 cmdLine.addParameter("-D" + GlobalOptions.FALLBACK_JDK_HOME + "=" + FileUtil.toSystemIndependentName(SystemProperties.getJavaHome()));
1130 cmdLine.addParameter("-D" + GlobalOptions.FALLBACK_JDK_VERSION + "=" + SystemProperties.getJavaVersion());
1132 final File workDirectory = getBuildSystemDirectory();
1133 //noinspection ResultOfMethodCallIgnored
1134 workDirectory.mkdirs();
1135 cmdLine.addParameter("-Djava.io.tmpdir=" + FileUtil.toSystemIndependentName(workDirectory.getPath()) + "/" + TEMP_DIR_NAME);
1137 for (BuildProcessParametersProvider provider : project.getExtensions(BuildProcessParametersProvider.EP_NAME)) {
1138 final List<String> args = provider.getVMArguments();
1139 cmdLine.addParameters(args);
1142 @SuppressWarnings("UnnecessaryFullyQualifiedName")
1143 final Class<?> launcherClass = org.jetbrains.jps.cmdline.Launcher.class;
1145 final List<String> launcherCp = new ArrayList<String>();
1146 launcherCp.add(ClasspathBootstrap.getResourcePath(launcherClass));
1147 launcherCp.add(compilerPath);
1148 ClasspathBootstrap.appendJavaCompilerClasspath(launcherCp);
1149 launcherCp.addAll(BuildProcessClasspathManager.getLauncherClasspath(project));
1150 cmdLine.addParameter("-classpath");
1151 cmdLine.addParameter(classpathToString(launcherCp));
1153 cmdLine.addParameter(launcherClass.getName());
1155 final List<String> cp = ClasspathBootstrap.getBuildProcessApplicationClasspath(true);
1156 cp.addAll(myClasspathManager.getBuildProcessPluginsClasspath(project));
1157 if (isProfilingMode) {
1158 cp.add(new File(workDirectory, "yjp-controller-api-redist.jar").getPath());
1160 cmdLine.addParameter(classpathToString(cp));
1162 cmdLine.addParameter(BuildMain.class.getName());
1163 cmdLine.addParameter(Boolean.valueOf(System.getProperty("java.net.preferIPv6Addresses", "false"))? "::1" : "127.0.0.1");
1164 cmdLine.addParameter(Integer.toString(port));
1165 cmdLine.addParameter(sessionId.toString());
1167 cmdLine.addParameter(FileUtil.toSystemIndependentName(workDirectory.getPath()));
1169 cmdLine.setWorkDirectory(workDirectory);
1172 ApplicationManager.getApplication().getMessageBus().syncPublisher(BuildManagerListener.TOPIC).beforeBuildProcessStarted(project, sessionId);
1174 catch (Throwable e) {
1178 final OSProcessHandler processHandler = new OSProcessHandler(cmdLine) {
1180 protected boolean shouldDestroyProcessRecursively() {
1186 protected BaseOutputReader.Options readerOptions() {
1187 return BaseOutputReader.Options.BLOCKING;
1190 processHandler.addProcessListener(new ProcessAdapter() {
1192 public void onTextAvailable(ProcessEvent event, Key outputType) {
1193 // re-translate builder's output to idea.log
1194 final String text = event.getText();
1195 if (!StringUtil.isEmptyOrSpaces(text)) {
1196 LOG.info("BUILDER_PROCESS [" + outputType.toString() + "]: " + text.trim());
1200 if (debugPort > 0) {
1201 processHandler.putUserData(COMPILER_PROCESS_DEBUG_PORT, debugPort);
1204 return processHandler;
1207 public File getBuildSystemDirectory() {
1208 return new File(mySystemDirectory, SYSTEM_ROOT);
1211 public File getBuildLogDirectory() {
1212 return new File(PathManager.getLogPath(), "build-log");
1216 public File getProjectSystemDirectory(Project project) {
1217 final String projectPath = getProjectPath(project);
1218 return projectPath != null? Utils.getDataStorageRoot(getBuildSystemDirectory(), projectPath) : null;
1221 private static File getUsageFile(@NotNull File projectSystemDir) {
1222 return new File(projectSystemDir, "ustamp");
1225 private static void updateUsageFile(@Nullable Project project, @NotNull File projectSystemDir) {
1226 final File usageFile = getUsageFile(projectSystemDir);
1227 StringBuilder content = new StringBuilder();
1229 synchronized (USAGE_STAMP_DATE_FORMAT) {
1230 content.append(USAGE_STAMP_DATE_FORMAT.format(System.currentTimeMillis()));
1232 if (project != null && !project.isDisposed()) {
1233 final String projectFilePath = project.getProjectFilePath();
1234 if (!StringUtil.isEmptyOrSpaces(projectFilePath)) {
1235 content.append("\n").append(FileUtil.toCanonicalPath(projectFilePath));
1238 FileUtil.writeToFile(usageFile, content.toString());
1240 catch (Throwable e) {
1246 private static Pair<Date, File> readUsageFile(File usageFile) {
1248 final List<String> lines = FileUtil.loadLines(usageFile, CharsetToolkit.UTF8_CHARSET.name());
1249 if (!lines.isEmpty()) {
1250 final String dateString = lines.get(0);
1252 synchronized (USAGE_STAMP_DATE_FORMAT) {
1253 date = USAGE_STAMP_DATE_FORMAT.parse(dateString);
1255 final File projectFile = lines.size() > 1? new File(lines.get(1)) : null;
1256 return Pair.create(date, projectFile);
1259 catch (Throwable e) {
1265 private static int getMinorVersion(String vs) {
1266 final int dashIndex = vs.lastIndexOf('_');
1267 if (dashIndex >= 0) {
1268 StringBuilder builder = new StringBuilder();
1269 for (int idx = dashIndex + 1; idx < vs.length(); idx++) {
1270 final char ch = vs.charAt(idx);
1271 if (Character.isDigit(ch)) {
1278 if (builder.length() > 0) {
1280 return Integer.parseInt(builder.toString());
1282 catch (NumberFormatException ignored) {
1290 private Future<?> stopListening() {
1292 return myChannelRegistrar.close(true);
1295 private int startListening() throws Exception {
1296 final ServerBootstrap bootstrap = NettyKt.serverBootstrap(new NioEventLoopGroup(1, ConcurrencyUtil.newNamedThreadFactory("External compiler")));
1297 bootstrap.childHandler(new ChannelInitializer() {
1299 protected void initChannel(@NotNull Channel channel) throws Exception {
1300 channel.pipeline().addLast(myChannelRegistrar,
1301 new ProtobufVarint32FrameDecoder(),
1302 new ProtobufDecoder(CmdlineRemoteProto.Message.getDefaultInstance()),
1303 new ProtobufVarint32LengthFieldPrepender(),
1304 new ProtobufEncoder(),
1305 myMessageDispatcher);
1308 Channel serverChannel = bootstrap.bind(InetAddress.getLoopbackAddress(), 0).syncUninterruptibly().channel();
1309 myChannelRegistrar.add(serverChannel);
1310 return ((InetSocketAddress)serverChannel.localAddress()).getPort();
1313 private static String classpathToString(List<String> cp) {
1314 StringBuilder builder = new StringBuilder();
1315 for (String file : cp) {
1316 if (builder.length() > 0) {
1317 builder.append(File.pathSeparator);
1319 builder.append(FileUtil.toCanonicalPath(file));
1321 return builder.toString();
1324 public boolean isBuildProcessDebuggingEnabled() {
1325 return myBuildProcessDebuggingEnabled;
1328 public void setBuildProcessDebuggingEnabled(boolean buildProcessDebuggingEnabled) {
1329 myBuildProcessDebuggingEnabled = buildProcessDebuggingEnabled;
1330 if (myBuildProcessDebuggingEnabled) {
1331 cancelAllPreloadedBuilds();
1335 private abstract static class BuildManagerPeriodicTask implements Runnable {
1336 private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
1337 private final AtomicBoolean myInProgress = new AtomicBoolean(false);
1338 private final Runnable myTaskRunnable = () -> {
1343 myInProgress.set(false);
1347 public final void schedule() {
1348 myAlarm.cancelAllRequests();
1349 final int delay = Math.max(100, getDelay());
1350 myAlarm.addRequest(this, delay);
1353 protected abstract int getDelay();
1355 protected abstract void runTask();
1358 public final void run() {
1359 if (!HeavyProcessLatch.INSTANCE.isRunning() && !myInProgress.getAndSet(true)) {
1361 ApplicationManager.getApplication().executeOnPooledThread(myTaskRunnable);
1363 catch (RejectedExecutionException ignored) {
1364 // we were shut down
1365 myInProgress.set(false);
1367 catch (Throwable e) {
1368 myInProgress.set(false);
1369 throw new RuntimeException(e);
1378 private static class NotifyingMessageHandler extends DelegatingMessageHandler {
1379 private final Project myProject;
1380 private final BuilderMessageHandler myDelegateHandler;
1381 private final boolean myIsAutomake;
1383 public NotifyingMessageHandler(@NotNull Project project, @NotNull BuilderMessageHandler delegateHandler, final boolean isAutomake) {
1384 myProject = project;
1385 myDelegateHandler = delegateHandler;
1386 myIsAutomake = isAutomake;
1390 protected BuilderMessageHandler getDelegateHandler() {
1391 return myDelegateHandler;
1395 public void buildStarted(UUID sessionId) {
1396 super.buildStarted(sessionId);
1399 .getApplication().getMessageBus().syncPublisher(BuildManagerListener.TOPIC).buildStarted(myProject, sessionId, myIsAutomake);
1401 catch (Throwable e) {
1407 public void sessionTerminated(UUID sessionId) {
1409 super.sessionTerminated(sessionId);
1413 ApplicationManager.getApplication().getMessageBus().syncPublisher(BuildManagerListener.TOPIC).buildFinished(myProject, sessionId, myIsAutomake);
1415 catch (Throwable e) {
1422 private static final class StdOutputCollector extends ProcessAdapter {
1423 private final Appendable myOutput;
1424 private int myStoredLength;
1425 StdOutputCollector(@NotNull Appendable outputSink) {
1426 myOutput = outputSink;
1430 public void onTextAvailable(ProcessEvent event, Key outputType) {
1433 synchronized (this) {
1434 if (myStoredLength > 16384) {
1437 text = event.getText();
1438 if (StringUtil.isEmptyOrSpaces(text)) {
1441 myStoredLength += text.length();
1445 myOutput.append(text);
1447 catch (IOException ignored) {
1452 private class ProjectWatcher extends ProjectManagerAdapter {
1453 private final Map<Project, MessageBusConnection> myConnections = new HashMap<Project, MessageBusConnection>();
1456 public void projectOpened(final Project project) {
1457 if (ApplicationManager.getApplication().isUnitTestMode()) return;
1458 final MessageBusConnection conn = project.getMessageBus().connect();
1459 myConnections.put(project, conn);
1460 conn.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() {
1462 public void rootsChanged(final ModuleRootEvent event) {
1463 final Object source = event.getSource();
1464 if (source instanceof Project) {
1465 clearState((Project)source);
1469 conn.subscribe(ExecutionManager.EXECUTION_TOPIC, new ExecutionAdapter() {
1471 public void processTerminated(@NotNull RunProfile runProfile, @NotNull ProcessHandler handler) {
1475 conn.subscribe(CompilerTopics.COMPILATION_STATUS, new CompilationStatusListener() {
1476 private final Set<String> myRootsToRefresh = new THashSet<String>(FileUtil.PATH_HASHING_STRATEGY);
1479 public void automakeCompilationFinished(int errors, int warnings, CompileContext compileContext) {
1480 if (!compileContext.getProgressIndicator().isCanceled()) {
1481 refreshSources(compileContext);
1486 public void compilationFinished(boolean aborted, int errors, int warnings, CompileContext compileContext) {
1487 refreshSources(compileContext);
1490 private void refreshSources(CompileContext compileContext) {
1491 if (project.isDisposed()) {
1494 final Set<String> candidates = new THashSet<String>(FileUtil.PATH_HASHING_STRATEGY);
1495 synchronized (myRootsToRefresh) {
1496 candidates.addAll(myRootsToRefresh);
1497 myRootsToRefresh.clear();
1499 if (compileContext.isAnnotationProcessorsEnabled()) {
1500 // annotation processors may have re-generated code
1501 final CompilerConfiguration config = CompilerConfiguration.getInstance(project);
1502 for (Module module : compileContext.getCompileScope().getAffectedModules()) {
1503 if (config.getAnnotationProcessingConfiguration(module).isEnabled()) {
1504 final String path = CompilerPaths.getAnnotationProcessorsGenerationPath(module);
1506 candidates.add(path);
1512 if (!candidates.isEmpty()) {
1513 ApplicationManager.getApplication().executeOnPooledThread(() -> {
1514 if (project.isDisposed()) {
1517 final List<File> rootFiles = new ArrayList<File>(candidates.size());
1518 for (String root : candidates) {
1519 rootFiles.add(new File(root));
1521 // this will ensure that we'll be able to obtain VirtualFile for existing roots
1522 CompilerUtil.refreshOutputDirectories(rootFiles, false);
1524 final LocalFileSystem lfs = LocalFileSystem.getInstance();
1525 final Set<VirtualFile> filesToRefresh = new HashSet<VirtualFile>();
1526 ApplicationManager.getApplication().runReadAction(() -> {
1527 if (project.isDisposed()) {
1530 final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
1531 for (File root : rootFiles) {
1532 final VirtualFile rootFile = lfs.findFileByIoFile(root);
1533 if (rootFile != null && fileIndex.isInSourceContent(rootFile)) {
1534 filesToRefresh.add(rootFile);
1537 if (!filesToRefresh.isEmpty()) {
1538 lfs.refreshFiles(filesToRefresh, true, true, null);
1546 public void fileGenerated(String outputRoot, String relativePath) {
1547 synchronized (myRootsToRefresh) {
1548 myRootsToRefresh.add(outputRoot);
1552 final String projectPath = getProjectPath(project);
1553 Disposer.register(project, new Disposable() {
1555 public void dispose() {
1556 cancelPreloadedBuilds(projectPath);
1557 myProjectDataMap.remove(projectPath);
1560 StartupManager.getInstance(project).registerPostStartupActivity(() -> {
1562 final File projectSystemDir = getProjectSystemDirectory(project);
1563 if (projectSystemDir != null) {
1564 updateUsageFile(project, projectSystemDir);
1567 scheduleAutoMake(); // run automake after project opened
1572 public boolean canCloseProject(Project project) {
1573 cancelAutoMakeTasks(project);
1574 return super.canCloseProject(project);
1578 public void projectClosing(Project project) {
1579 cancelPreloadedBuilds(getProjectPath(project));
1580 for (TaskFuture future : cancelAutoMakeTasks(project)) {
1581 future.waitFor(500, TimeUnit.MILLISECONDS);
1586 public void projectClosed(Project project) {
1587 myProjectDataMap.remove(getProjectPath(project));
1588 final MessageBusConnection conn = myConnections.remove(project);
1595 private static class ProjectData {
1597 final ExecutorService taskQueue;
1598 private final Set<InternedPath> myChanged = new THashSet<InternedPath>();
1599 private final Set<InternedPath> myDeleted = new THashSet<InternedPath>();
1600 private long myNextEventOrdinal;
1601 private boolean myNeedRescan = true;
1603 private ProjectData(@NotNull ExecutorService taskQueue) {
1604 this.taskQueue = taskQueue;
1607 public void addChanged(Collection<String> paths) {
1608 if (!myNeedRescan) {
1609 for (String path : paths) {
1610 final InternedPath _path = InternedPath.create(path);
1611 myDeleted.remove(_path);
1612 myChanged.add(_path);
1617 public void addDeleted(Collection<String> paths) {
1618 if (!myNeedRescan) {
1619 for (String path : paths) {
1620 final InternedPath _path = InternedPath.create(path);
1621 myChanged.remove(_path);
1622 myDeleted.add(_path);
1627 public CmdlineRemoteProto.Message.ControllerMessage.FSEvent createNextEvent() {
1628 final CmdlineRemoteProto.Message.ControllerMessage.FSEvent.Builder builder =
1629 CmdlineRemoteProto.Message.ControllerMessage.FSEvent.newBuilder();
1630 builder.setOrdinal(++myNextEventOrdinal);
1632 for (InternedPath path : myChanged) {
1633 builder.addChangedPaths(path.getValue());
1637 for (InternedPath path : myDeleted) {
1638 builder.addDeletedPaths(path.getValue());
1642 return builder.build();
1645 public boolean getAndResetRescanFlag() {
1646 final boolean rescan = myNeedRescan;
1647 myNeedRescan = false;
1651 public void dropChanges() {
1652 myNeedRescan = true;
1653 myNextEventOrdinal = 0L;
1659 private abstract static class InternedPath {
1660 protected final int[] myPath;
1663 * @param path assuming system-independent path with forward slashes
1665 protected InternedPath(String path) {
1666 final IntArrayList list = new IntArrayList();
1667 final StringTokenizer tokenizer = new StringTokenizer(path, "/", false);
1668 while(tokenizer.hasMoreTokens()) {
1669 final String element = tokenizer.nextToken();
1670 list.add(FileNameCache.storeName(element));
1672 myPath = list.toArray();
1675 public abstract String getValue();
1678 public boolean equals(Object o) {
1679 if (this == o) return true;
1680 if (o == null || getClass() != o.getClass()) return false;
1682 InternedPath path = (InternedPath)o;
1684 if (!Arrays.equals(myPath, path.myPath)) return false;
1690 public int hashCode() {
1691 return Arrays.hashCode(myPath);
1694 public static InternedPath create(String path) {
1695 return path.startsWith("/")? new XInternedPath(path) : new WinInternedPath(path);
1699 private static class WinInternedPath extends InternedPath {
1700 private WinInternedPath(String path) {
1705 public String getValue() {
1706 if (myPath.length == 1) {
1707 final String name = FileNameCache.getVFileName(myPath[0]).toString();
1708 // handle case of windows drive letter
1709 return name.length() == 2 && name.endsWith(":")? name + "/" : name;
1712 final StringBuilder buf = new StringBuilder();
1713 for (int element : myPath) {
1714 if (buf.length() > 0) {
1717 buf.append(FileNameCache.getVFileName(element));
1719 return buf.toString();
1723 private static class XInternedPath extends InternedPath {
1724 private XInternedPath(String path) {
1729 public String getValue() {
1730 if (myPath.length > 0) {
1731 final StringBuilder buf = new StringBuilder();
1732 for (int element : myPath) {
1733 buf.append("/").append(FileNameCache.getVFileName(element));
1735 return buf.toString();
1741 private static final class DelegateFuture implements TaskFuture {
1743 private TaskFuture[] myDelegates;
1744 private Boolean myRequestedCancelState;
1747 private synchronized TaskFuture[] getDelegates() {
1748 TaskFuture[] delegates = myDelegates;
1749 while (delegates == null) {
1753 catch (InterruptedException ignored) {
1755 delegates = myDelegates;
1760 private synchronized boolean setDelegates(@NotNull TaskFuture... delegates) {
1761 if (myDelegates == null) {
1763 myDelegates = delegates;
1764 if (myRequestedCancelState != null) {
1765 for (TaskFuture delegate : delegates) {
1766 delegate.cancel(myRequestedCancelState);
1779 public synchronized boolean cancel(boolean mayInterruptIfRunning) {
1780 Future[] delegates = myDelegates;
1781 if (delegates == null) {
1782 myRequestedCancelState = mayInterruptIfRunning;
1785 Stream.of(delegates).forEach(delegate -> delegate.cancel(mayInterruptIfRunning));
1790 public void waitFor() {
1791 Stream.of(getDelegates()).forEach(TaskFuture::waitFor);
1795 public boolean waitFor(long timeout, TimeUnit unit) {
1796 Stream.of(getDelegates()).forEach(delegate -> delegate.waitFor(timeout, unit));
1801 public boolean isCancelled() {
1802 final Future[] delegates;
1803 synchronized (this) {
1804 delegates = myDelegates;
1805 if (delegates == null) {
1806 return myRequestedCancelState != null;
1809 return Stream.of(delegates).anyMatch(Future::isCancelled);
1813 public boolean isDone() {
1814 final Future[] delegates;
1815 synchronized (this) {
1816 delegates = myDelegates;
1817 if (delegates == null) {
1821 return Stream.of(delegates).allMatch(Future::isDone);
1825 public Object get() throws InterruptedException, java.util.concurrent.ExecutionException {
1826 for (Future delegate : getDelegates()) {
1833 public Object get(long timeout, @NotNull TimeUnit unit) throws InterruptedException, java.util.concurrent.ExecutionException, TimeoutException {
1834 for (Future delegate : getDelegates()) {
1835 delegate.get(timeout, unit);
1841 private class CancelBuildSessionAction<T extends BuilderMessageHandler> implements RequestFuture.CancelAction<T> {
1843 public void cancel(RequestFuture<T> future) throws Exception {
1844 myMessageDispatcher.cancelSession(future.getRequestID());