private void runAutoMake() { if (ApplicationManager.getApplication().isUnitTestMode()) { return; } final Project[] openProjects = myProjectManager.getOpenProjects(); if (openProjects.length > 0) { final List<RequestFuture> futures = new ArrayList<RequestFuture>(); for (final Project project : openProjects) { if (project.isDefault() || project.isDisposed()) { continue; } final CompilerWorkspaceConfiguration config = CompilerWorkspaceConfiguration.getInstance(project); if (!config.useOutOfProcessBuild() || !config.MAKE_PROJECT_ON_SAVE) { continue; } final List<String> emptyList = Collections.emptyList(); final RequestFuture future = scheduleBuild( project, false, true, emptyList, emptyList, emptyList, Collections.<String, String>emptyMap(), new AutoMakeMessageHandler(project)); if (future != null) { futures.add(future); synchronized (myAutomakeFutures) { myAutomakeFutures.put(future, project); } } } try { for (RequestFuture future : futures) { future.waitFor(); } } finally { synchronized (myAutomakeFutures) { myAutomakeFutures.keySet().removeAll(futures); } } } }
/** @author Eugene Zhuravlev Date: 4/17/12 */ final class BuildSession implements Runnable, CanceledStatus { private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.cmdline.BuildSession"); private static final String FS_STATE_FILE = "fs_state.dat"; private final UUID mySessionId; private final Channel myChannel; private volatile boolean myCanceled = false; private String myProjectPath; @Nullable private CmdlineRemoteProto.Message.ControllerMessage.FSEvent myInitialFSDelta; // state private EventsProcessor myEventsProcessor = new EventsProcessor(); private volatile long myLastEventOrdinal; private volatile ProjectDescriptor myProjectDescriptor; private final Map<Pair<String, String>, ConstantSearchFuture> mySearchTasks = Collections.synchronizedMap(new HashMap<Pair<String, String>, ConstantSearchFuture>()); private final ConstantSearch myConstantSearch = new ConstantSearch(); private final BuildRunner myBuildRunner; private BuildType myBuildType; BuildSession( UUID sessionId, Channel channel, CmdlineRemoteProto.Message.ControllerMessage.ParametersMessage params, @Nullable CmdlineRemoteProto.Message.ControllerMessage.FSEvent delta) { mySessionId = sessionId; myChannel = channel; // globals Map<String, String> pathVars = new HashMap<String, String>(); final CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings globals = params.getGlobalSettings(); for (CmdlineRemoteProto.Message.KeyValuePair variable : globals.getPathVariableList()) { pathVars.put(variable.getKey(), variable.getValue()); } // session params myProjectPath = FileUtil.toCanonicalPath(params.getProjectId()); String globalOptionsPath = FileUtil.toCanonicalPath(globals.getGlobalOptionsPath()); myBuildType = convertCompileType(params.getBuildType()); List<CmdlineRemoteProto.Message.ControllerMessage.ParametersMessage.TargetTypeBuildScope> scopes = params.getScopeList(); List<String> filePaths = params.getFilePathList(); Map<String, String> builderParams = new HashMap<String, String>(); for (CmdlineRemoteProto.Message.KeyValuePair pair : params.getBuilderParameterList()) { builderParams.put(pair.getKey(), pair.getValue()); } myInitialFSDelta = delta; JpsModelLoaderImpl loader = new JpsModelLoaderImpl(myProjectPath, globalOptionsPath, pathVars, null); myBuildRunner = new BuildRunner(loader, scopes, filePaths, builderParams); } public void run() { Throwable error = null; final Ref<Boolean> hasErrors = new Ref<Boolean>(false); final Ref<Boolean> markedFilesUptodate = new Ref<Boolean>(false); try { runBuild( new MessageHandler() { public void processMessage(BuildMessage buildMessage) { final CmdlineRemoteProto.Message.BuilderMessage response; if (buildMessage instanceof FileGeneratedEvent) { final Collection<Pair<String, String>> paths = ((FileGeneratedEvent) buildMessage).getPaths(); response = !paths.isEmpty() ? CmdlineProtoUtil.createFileGeneratedEvent(paths) : null; } else if (buildMessage instanceof UptoDateFilesSavedEvent) { markedFilesUptodate.set(true); response = null; } else if (buildMessage instanceof CompilerMessage) { markedFilesUptodate.set(true); final CompilerMessage compilerMessage = (CompilerMessage) buildMessage; final String text = compilerMessage.getCompilerName() + ": " + compilerMessage.getMessageText(); final BuildMessage.Kind kind = compilerMessage.getKind(); if (kind == BuildMessage.Kind.ERROR) { hasErrors.set(true); } response = CmdlineProtoUtil.createCompileMessage( kind, text, compilerMessage.getSourcePath(), compilerMessage.getProblemBeginOffset(), compilerMessage.getProblemEndOffset(), compilerMessage.getProblemLocationOffset(), compilerMessage.getLine(), compilerMessage.getColumn(), -1.0f); } else { float done = -1.0f; if (buildMessage instanceof ProgressMessage) { done = ((ProgressMessage) buildMessage).getDone(); } response = CmdlineProtoUtil.createCompileProgressMessageResponse( buildMessage.getMessageText(), done); } if (response != null) { Channels.write(myChannel, CmdlineProtoUtil.toMessage(mySessionId, response)); } } }, this); } catch (Throwable e) { LOG.info(e); error = e; } finally { finishBuild(error, hasErrors.get(), markedFilesUptodate.get()); } } private void runBuild(final MessageHandler msgHandler, CanceledStatus cs) throws Throwable { final File dataStorageRoot = Utils.getDataStorageRoot(myProjectPath); if (dataStorageRoot == null) { msgHandler.processMessage( new CompilerMessage( "build", BuildMessage.Kind.ERROR, "Cannot determine build data storage root for project " + myProjectPath)); return; } if (!dataStorageRoot.exists()) { // invoked the very first time for this project. Force full rebuild myBuildType = BuildType.PROJECT_REBUILD; } final DataInputStream fsStateStream = createFSDataStream(dataStorageRoot); if (fsStateStream != null) { // optimization: check whether we can skip the build final boolean hasWorkToDoWithModules = fsStateStream.readBoolean(); if (myBuildType == BuildType.MAKE && !hasWorkToDoWithModules && scopeContainsModulesOnly(myBuildRunner.getScopes()) && !containsChanges(myInitialFSDelta)) { updateFsStateOnDisk(dataStorageRoot, fsStateStream, myInitialFSDelta.getOrdinal()); return; } } final BuildFSState fsState = new BuildFSState(false); try { final ProjectDescriptor pd = myBuildRunner.load(msgHandler, dataStorageRoot, fsState); myProjectDescriptor = pd; if (fsStateStream != null) { try { try { fsState.load(fsStateStream, pd.getModel(), pd.getBuildRootIndex()); applyFSEvent(pd, myInitialFSDelta); } finally { fsStateStream.close(); } } catch (Throwable e) { LOG.error(e); fsState.clearAll(); } } myLastEventOrdinal = myInitialFSDelta != null ? myInitialFSDelta.getOrdinal() : 0L; // free memory myInitialFSDelta = null; // ensure events from controller are processed after FSState initialization myEventsProcessor.startProcessing(); myBuildRunner.runBuild(pd, cs, myConstantSearch, msgHandler, myBuildType); } finally { saveData(fsState, dataStorageRoot); } } private static boolean scopeContainsModulesOnly(List<TargetTypeBuildScope> scopes) { for (TargetTypeBuildScope scope : scopes) { String typeId = scope.getTypeId(); if (!typeId.equals(JavaModuleBuildTargetType.PRODUCTION.getTypeId()) && !typeId.equals(JavaModuleBuildTargetType.TEST.getTypeId())) { return false; } } return true; } private void saveData(final BuildFSState fsState, File dataStorageRoot) { final boolean wasInterrupted = Thread.interrupted(); try { saveFsState(dataStorageRoot, fsState); final ProjectDescriptor pd = myProjectDescriptor; if (pd != null) { pd.release(); } } finally { if (wasInterrupted) { Thread.currentThread().interrupt(); } } } public void processFSEvent(final CmdlineRemoteProto.Message.ControllerMessage.FSEvent event) { myEventsProcessor.submit( new Runnable() { @Override public void run() { try { applyFSEvent(myProjectDescriptor, event); myLastEventOrdinal += 1; } catch (IOException e) { LOG.error(e); } } }); } public void processConstantSearchResult( CmdlineRemoteProto.Message.ControllerMessage.ConstantSearchResult result) { final ConstantSearchFuture future = mySearchTasks.remove(Pair.create(result.getOwnerClassName(), result.getFieldName())); if (future != null) { if (result.getIsSuccess()) { final List<String> paths = result.getPathList(); final List<File> files = new ArrayList<File>(paths.size()); for (String path : paths) { files.add(new File(path)); } future.setResult(files); LOG.debug("Constant search result: " + files.size() + " affected files found"); } else { future.setDone(); LOG.debug("Constant search failed"); } } } private void applyFSEvent( ProjectDescriptor pd, @Nullable CmdlineRemoteProto.Message.ControllerMessage.FSEvent event) throws IOException { if (event == null) { return; } final Timestamps timestamps = pd.timestamps.getStorage(); for (String deleted : event.getDeletedPathsList()) { final File file = new File(deleted); Collection<BuildRootDescriptor> descriptor = pd.getBuildRootIndex().findAllParentDescriptors(file, null, null); if (!descriptor.isEmpty()) { if (Utils.IS_TEST_MODE) { LOG.info("Applying deleted path from fs event: " + file.getPath()); } for (BuildRootDescriptor rootDescriptor : descriptor) { pd.fsState.registerDeleted(rootDescriptor.getTarget(), file, timestamps); } } else if (Utils.IS_TEST_MODE) { LOG.info("Skipping deleted path: " + file.getPath()); } } for (String changed : event.getChangedPathsList()) { final File file = new File(changed); Collection<BuildRootDescriptor> descriptors = pd.getBuildRootIndex().findAllParentDescriptors(file, null, null); if (!descriptors.isEmpty()) { if (Utils.IS_TEST_MODE) { LOG.info("Applying dirty path from fs event: " + file.getPath()); } for (BuildRootDescriptor descriptor : descriptors) { pd.fsState.markDirty(null, file, descriptor, timestamps); } } else if (Utils.IS_TEST_MODE) { LOG.info("Skipping dirty path: " + file.getPath()); } } } private void updateFsStateOnDisk( File dataStorageRoot, DataInputStream original, final long ordinal) { final File file = new File(dataStorageRoot, FS_STATE_FILE); try { final BufferExposingByteArrayOutputStream bytes = new BufferExposingByteArrayOutputStream(); final DataOutputStream out = new DataOutputStream(bytes); try { out.writeInt(FSState.VERSION); out.writeLong(ordinal); out.writeBoolean(false); while (true) { final int b = original.read(); if (b == -1) { break; } out.write(b); } } finally { out.close(); } saveOnDisk(bytes, file); } catch (Throwable e) { LOG.error(e); FileUtil.delete(file); } } private void saveFsState(File dataStorageRoot, BuildFSState state) { final ProjectDescriptor pd = myProjectDescriptor; final File file = new File(dataStorageRoot, FS_STATE_FILE); try { final BufferExposingByteArrayOutputStream bytes = new BufferExposingByteArrayOutputStream(); final DataOutputStream out = new DataOutputStream(bytes); try { out.writeInt(FSState.VERSION); out.writeLong(myLastEventOrdinal); boolean hasWorkToDoWithModules = false; for (JpsModule module : pd.getProject().getModules()) { for (JavaModuleBuildTargetType type : JavaModuleBuildTargetType.ALL_TYPES) { if (state.hasWorkToDo(new ModuleBuildTarget(module, type))) { hasWorkToDoWithModules = true; break; } } if (hasWorkToDoWithModules) { break; } } out.writeBoolean(hasWorkToDoWithModules); state.save(out); } finally { out.close(); } saveOnDisk(bytes, file); } catch (Throwable e) { LOG.error(e); FileUtil.delete(file); } } private static void saveOnDisk(BufferExposingByteArrayOutputStream bytes, final File file) throws IOException { FileOutputStream fos = null; try { fos = new FileOutputStream(file); } catch (FileNotFoundException e) { FileUtil.createIfDoesntExist(file); } if (fos == null) { fos = new FileOutputStream(file); } try { fos.write(bytes.getInternalBuffer(), 0, bytes.size()); } finally { fos.close(); } } @Nullable private DataInputStream createFSDataStream(File dataStorageRoot) { if (myInitialFSDelta == null) { // this will force FS rescan return null; } try { final File file = new File(dataStorageRoot, FS_STATE_FILE); final InputStream fs = new FileInputStream(file); byte[] bytes; try { bytes = FileUtil.loadBytes(fs, (int) file.length()); } finally { fs.close(); } final DataInputStream in = new DataInputStream(new ByteArrayInputStream(bytes)); final int version = in.readInt(); if (version != FSState.VERSION) { return null; } final long savedOrdinal = in.readLong(); if (savedOrdinal + 1L != myInitialFSDelta.getOrdinal()) { return null; } return in; } catch (FileNotFoundException ignored) { } catch (Throwable e) { LOG.error(e); } return null; } private static boolean containsChanges( CmdlineRemoteProto.Message.ControllerMessage.FSEvent event) { return event.getChangedPathsCount() != 0 || event.getDeletedPathsCount() != 0; } private void finishBuild(Throwable error, boolean hadBuildErrors, boolean markedUptodateFiles) { CmdlineRemoteProto.Message lastMessage = null; try { if (error != null) { Throwable cause = error.getCause(); if (cause == null) { cause = error; } final ByteArrayOutputStream out = new ByteArrayOutputStream(); final PrintStream stream = new PrintStream(out); try { cause.printStackTrace(stream); } finally { stream.close(); } final StringBuilder messageText = new StringBuilder(); messageText .append("Internal error: (") .append(cause.getClass().getName()) .append(") ") .append(cause.getMessage()); final String trace = out.toString(); if (!trace.isEmpty()) { messageText.append("\n").append(trace); } lastMessage = CmdlineProtoUtil.toMessage( mySessionId, CmdlineProtoUtil.createFailure(messageText.toString(), cause)); } else { CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.Status status = CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.Status.SUCCESS; if (myCanceled) { status = CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.Status.CANCELED; } else if (hadBuildErrors) { status = CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.Status.ERRORS; } else if (!markedUptodateFiles) { status = CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.Status.UP_TO_DATE; } lastMessage = CmdlineProtoUtil.toMessage( mySessionId, CmdlineProtoUtil.createBuildCompletedEvent("build completed", status)); } } catch (Throwable e) { lastMessage = CmdlineProtoUtil.toMessage( mySessionId, CmdlineProtoUtil.createFailure(e.getMessage(), e)); } finally { try { Channels.write(myChannel, lastMessage).await(); } catch (InterruptedException e) { LOG.info(e); } } } public void cancel() { myCanceled = true; } @Override public boolean isCanceled() { return myCanceled; } private static BuildType convertCompileType( CmdlineRemoteProto.Message.ControllerMessage.ParametersMessage.Type compileType) { switch (compileType) { case CLEAN: return BuildType.CLEAN; case MAKE: return BuildType.MAKE; case REBUILD: return BuildType.PROJECT_REBUILD; case FORCED_COMPILATION: return BuildType.FORCED_COMPILATION; } return BuildType.MAKE; // use make by default } private static class EventsProcessor extends SequentialTaskExecutor { private final AtomicBoolean myProcessingEnabled = new AtomicBoolean(false); EventsProcessor() { super(SharedThreadPool.getInstance()); } public void startProcessing() { if (!myProcessingEnabled.getAndSet(true)) { super.processQueue(); } } @Override protected void processQueue() { if (myProcessingEnabled.get()) { super.processQueue(); } } } private class ConstantSearch implements Callbacks.ConstantAffectionResolver { private ConstantSearch() {} @Nullable @Override public Future<Callbacks.ConstantAffection> request( String ownerClassName, String fieldName, int accessFlags, boolean fieldRemoved, boolean accessChanged) { final CmdlineRemoteProto.Message.BuilderMessage.ConstantSearchTask.Builder task = CmdlineRemoteProto.Message.BuilderMessage.ConstantSearchTask.newBuilder(); task.setOwnerClassName(ownerClassName); task.setFieldName(fieldName); task.setAccessFlags(accessFlags); task.setIsAccessChanged(accessChanged); task.setIsFieldRemoved(fieldRemoved); final ConstantSearchFuture future = new ConstantSearchFuture(BuildSession.this); final ConstantSearchFuture prev = mySearchTasks.put(new Pair<String, String>(ownerClassName, fieldName), future); if (prev != null) { prev.setDone(); } Channels.write( myChannel, CmdlineProtoUtil.toMessage( mySessionId, CmdlineRemoteProto.Message.BuilderMessage.newBuilder() .setType(CmdlineRemoteProto.Message.BuilderMessage.Type.CONSTANT_SEARCH_TASK) .setConstantSearchTask(task.build()) .build())); return future; } } private static class ConstantSearchFuture extends BasicFuture<Callbacks.ConstantAffection> { private volatile Callbacks.ConstantAffection myResult = Callbacks.ConstantAffection.EMPTY; private final CanceledStatus myCanceledStatus; private ConstantSearchFuture(CanceledStatus canceledStatus) { myCanceledStatus = canceledStatus; } public void setResult(final Collection<File> affectedFiles) { myResult = new Callbacks.ConstantAffection(affectedFiles); setDone(); } @Override public Callbacks.ConstantAffection get() throws InterruptedException, ExecutionException { while (true) { try { return get(300L, TimeUnit.MILLISECONDS); } catch (TimeoutException ignored) { } if (myCanceledStatus.isCanceled()) { return myResult; } } } @Override public Callbacks.ConstantAffection get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { super.get(timeout, unit); return myResult; } } }
/** @author Eugene Zhuravlev Date: 9/6/11 */ public class BuildManager implements ApplicationComponent { private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.server.BuildManager"); private static final String SYSTEM_ROOT = "compile-server"; private static final String LOGGER_CONFIG = "log.xml"; private static final String DEFAULT_LOGGER_CONFIG = "defaultLogConfig.xml"; private static final int MAKE_TRIGGER_DELAY = 5 * 1000 /*5 seconds*/; private final File mySystemDirectory; private final ProjectManager myProjectManager; private final Map<RequestFuture, Project> myAutomakeFutures = new HashMap<RequestFuture, Project>(); private final Map<String, RequestFuture> myBuildsInProgress = Collections.synchronizedMap(new HashMap<String, RequestFuture>()); private final CompileServerClasspathManager myClasspathManager = new CompileServerClasspathManager(); private final Executor myPooledThreadExecutor = new Executor() { @Override public void execute(Runnable command) { ApplicationManager.getApplication().executeOnPooledThread(command); } }; private final SequentialTaskExecutor myEventsProcessor = new SequentialTaskExecutor(myPooledThreadExecutor); private final Map<String, ProjectData> myProjectDataMap = Collections.synchronizedMap(new HashMap<String, ProjectData>()); private final ChannelGroup myAllOpenChannels = new DefaultChannelGroup("build-manager"); private final BuildMessageDispatcher myMessageDispatcher = new BuildMessageDispatcher(); private int myListenPort = -1; private volatile CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings myGlobals; public BuildManager(final ProjectManager projectManager) { myProjectManager = projectManager; final String systemPath = PathManager.getSystemPath(); File system = new File(systemPath); try { system = system.getCanonicalFile(); } catch (IOException e) { LOG.info(e); } mySystemDirectory = system; projectManager.addProjectManagerListener(new ProjectWatcher()); final MessageBusConnection conn = ApplicationManager.getApplication().getMessageBus().connect(); conn.subscribe( VirtualFileManager.VFS_CHANGES, new BulkFileListener() { private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD); private final AtomicBoolean myAutoMakeInProgress = new AtomicBoolean(false); @Override public void before(@NotNull List<? extends VFileEvent> events) {} @Override public void after(@NotNull List<? extends VFileEvent> events) { if (shouldTriggerMake(events)) { scheduleMake( new Runnable() { @Override public void run() { if (!myAutoMakeInProgress.getAndSet(true)) { try { ApplicationManager.getApplication() .executeOnPooledThread( new Runnable() { @Override public void run() { try { runAutoMake(); } finally { myAutoMakeInProgress.set(false); } } }); } catch (RejectedExecutionException ignored) { // we were shut down } } else { scheduleMake(this); } } }); } } private void scheduleMake(Runnable runnable) { myAlarm.cancelAllRequests(); myAlarm.addRequest(runnable, MAKE_TRIGGER_DELAY); } private boolean shouldTriggerMake(List<? extends VFileEvent> events) { if (!CompilerWorkspaceConfiguration.useServerlessOutOfProcessBuild()) { return false; } for (VFileEvent event : events) { if (event.isFromRefresh() || event.getRequestor() instanceof SavingRequestor) { return true; } } return false; } }); ShutDownTracker.getInstance() .registerShutdownTask( new Runnable() { @Override public void run() { stopListening(); } }); } public static BuildManager getInstance() { return ApplicationManager.getApplication().getComponent(BuildManager.class); } public void notifyFilesChanged(final Collection<String> paths) { doNotify(paths, false); } public void notifyFilesDeleted(Collection<String> paths) { doNotify(paths, true); } private void doNotify(final Collection<String> paths, final boolean notifyDeletion) { // ensure events processed in the order they arrived myEventsProcessor.submit( new Runnable() { @Override public void run() { synchronized (myProjectDataMap) { for (Map.Entry<String, ProjectData> entry : myProjectDataMap.entrySet()) { final ProjectData data = entry.getValue(); if (notifyDeletion) { data.addDeleted(paths); } else { data.addChanged(paths); } final RequestFuture future = myBuildsInProgress.get(entry.getKey()); if (future != null && !future.isCancelled() && !future.isDone()) { final UUID sessionId = future.getRequestID(); final Channel channel = myMessageDispatcher.getConnectedChannel(sessionId); if (channel != null) { final CmdlineRemoteProto.Message.ControllerMessage message = CmdlineRemoteProto.Message.ControllerMessage.newBuilder() .setType(CmdlineRemoteProto.Message.ControllerMessage.Type.FS_EVENT) .setFsEvent(data.createNextEvent()) .build(); Channels.write(channel, CmdlineProtoUtil.toMessage(sessionId, message)); } } } } } }); } public void clearState(Project project) { myGlobals = null; final String projectPath = getProjectPath(project); synchronized (myProjectDataMap) { final ProjectData data = myProjectDataMap.get(projectPath); if (data != null) { data.dropChanges(); } } } @Nullable private static String getProjectPath(final Project project) { final String path = project.getPresentableUrl(); if (path == null) { return null; } final VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(path); return vFile != null ? vFile.getPath() : null; } private void runAutoMake() { if (ApplicationManager.getApplication().isUnitTestMode()) { return; } final Project[] openProjects = myProjectManager.getOpenProjects(); if (openProjects.length > 0) { final List<RequestFuture> futures = new ArrayList<RequestFuture>(); for (final Project project : openProjects) { if (project.isDefault() || project.isDisposed()) { continue; } final CompilerWorkspaceConfiguration config = CompilerWorkspaceConfiguration.getInstance(project); if (!config.useOutOfProcessBuild() || !config.MAKE_PROJECT_ON_SAVE) { continue; } final List<String> emptyList = Collections.emptyList(); final RequestFuture future = scheduleBuild( project, false, true, emptyList, emptyList, emptyList, Collections.<String, String>emptyMap(), new AutoMakeMessageHandler(project)); if (future != null) { futures.add(future); synchronized (myAutomakeFutures) { myAutomakeFutures.put(future, project); } } } try { for (RequestFuture future : futures) { future.waitFor(); } } finally { synchronized (myAutomakeFutures) { myAutomakeFutures.keySet().removeAll(futures); } } } } public Collection<RequestFuture> cancelAutoMakeTasks(Project project) { final Collection<RequestFuture> futures = new ArrayList<RequestFuture>(); synchronized (myAutomakeFutures) { for (Map.Entry<RequestFuture, Project> entry : myAutomakeFutures.entrySet()) { if (entry.getValue().equals(project)) { final RequestFuture future = entry.getKey(); future.cancel(false); futures.add(future); } } } return futures; } @Nullable public RequestFuture scheduleBuild( final Project project, final boolean isRebuild, final boolean isMake, final Collection<String> modules, final Collection<String> artifacts, final Collection<String> paths, final Map<String, String> userData, DefaultMessageHandler handler) { final String projectPath = getProjectPath(project); final UUID sessionId = UUID.randomUUID(); final CmdlineRemoteProto.Message.ControllerMessage params; CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings globals = myGlobals; if (globals == null) { globals = buildGlobalSettings(); myGlobals = globals; } CmdlineRemoteProto.Message.ControllerMessage.FSEvent currentFSChanges = null; final SequentialTaskExecutor projectTaskQueue; synchronized (myProjectDataMap) { ProjectData data = myProjectDataMap.get(projectPath); if (data == null) { data = new ProjectData(new SequentialTaskExecutor(myPooledThreadExecutor)); myProjectDataMap.put(projectPath, data); } if (isRebuild) { data.dropChanges(); } currentFSChanges = data.getAndResetRescanFlag() ? null : data.createNextEvent(); projectTaskQueue = data.taskQueue; } if (isRebuild) { params = CmdlineProtoUtil.createRebuildRequest(projectPath, userData, globals); } else { params = isMake ? CmdlineProtoUtil.createMakeRequest( projectPath, modules, artifacts, userData, globals, currentFSChanges) : CmdlineProtoUtil.createForceCompileRequest( projectPath, modules, artifacts, paths, userData, globals, currentFSChanges); } myMessageDispatcher.registerBuildMessageHandler(sessionId, handler, params); // ensure server is listening if (myListenPort < 0) { try { myListenPort = startListening(); } catch (Exception e) { myMessageDispatcher.unregisterBuildMessageHandler(sessionId); handler.handleFailure(sessionId, CmdlineProtoUtil.createFailure(e.getMessage(), null)); handler.sessionTerminated(); return null; } } final RequestFuture<BuilderMessageHandler> future = new RequestFuture<BuilderMessageHandler>( handler, sessionId, new RequestFuture.CancelAction<BuilderMessageHandler>() { @Override public void cancel(RequestFuture<BuilderMessageHandler> future) throws Exception { myMessageDispatcher.cancelSession(future.getRequestID()); } }); projectTaskQueue.submit( new Runnable() { @Override public void run() { try { if (project.isDisposed()) { future.cancel(false); return; } myBuildsInProgress.put(projectPath, future); final Process process = launchBuildProcess(project, myListenPort, sessionId); final OSProcessHandler processHandler = new OSProcessHandler(process, null) { @Override protected boolean shouldDestroyProcessRecursively() { return true; } }; final StringBuilder stdErrOutput = new StringBuilder(); processHandler.addProcessListener( new ProcessAdapter() { @Override public void processTerminated(ProcessEvent event) { final BuilderMessageHandler handler = myMessageDispatcher.unregisterBuildMessageHandler(sessionId); if (handler != null) { handler.sessionTerminated(); } } @Override public void onTextAvailable(ProcessEvent event, Key outputType) { // re-translate builder's output to idea.log final String text = event.getText(); if (!StringUtil.isEmpty(text)) { LOG.info("BUILDER_PROCESS [" + outputType.toString() + "]: " + text.trim()); if (stdErrOutput.length() < 1024 && ProcessOutputTypes.STDERR.equals(outputType)) { stdErrOutput.append(text); } } } }); processHandler.startNotify(); final boolean terminated = processHandler.waitFor(); if (terminated) { final int exitValue = processHandler.getProcess().exitValue(); if (exitValue != 0) { final StringBuilder msg = new StringBuilder(); msg.append("Abnormal build process termination: "); if (stdErrOutput.length() > 0) { msg.append("\n").append(stdErrOutput); } else { msg.append("unknown error"); } future .getMessageHandler() .handleFailure( sessionId, CmdlineProtoUtil.createFailure(msg.toString(), null)); } } else { future .getMessageHandler() .handleFailure( sessionId, CmdlineProtoUtil.createFailure("Disconnected from build process", null)); } } catch (ExecutionException e) { myMessageDispatcher.unregisterBuildMessageHandler(sessionId); future .getMessageHandler() .handleFailure(sessionId, CmdlineProtoUtil.createFailure(e.getMessage(), e)); future.getMessageHandler().sessionTerminated(); } finally { myBuildsInProgress.remove(projectPath); future.setDone(); } } }); return future; } @Override public void initComponent() {} @Override public void disposeComponent() { stopListening(); } @NotNull @Override public String getComponentName() { return "com.intellij.compiler.server.BuildManager"; } private static CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings buildGlobalSettings() { final Map<String, String> data = new HashMap<String, String>(); for (Map.Entry<String, String> entry : PathMacrosImpl.getGlobalSystemMacros().entrySet()) { data.put(entry.getKey(), FileUtil.toSystemIndependentName(entry.getValue())); } final PathMacros pathVars = PathMacros.getInstance(); for (String name : pathVars.getAllMacroNames()) { final String path = pathVars.getValue(name); if (path != null) { data.put(name, FileUtil.toSystemIndependentName(path)); } } final List<GlobalLibrary> globals = new ArrayList<GlobalLibrary>(); fillSdks(globals); fillGlobalLibraries(globals); final CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings.Builder cmdBuilder = CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings.newBuilder(); if (!data.isEmpty()) { for (Map.Entry<String, String> entry : data.entrySet()) { final String var = entry.getKey(); final String value = entry.getValue(); if (var != null && value != null) { cmdBuilder.addPathVariable(CmdlineProtoUtil.createPair(var, value)); } } } if (!globals.isEmpty()) { for (GlobalLibrary lib : globals) { final CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings.GlobalLibrary.Builder libBuilder = CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings.GlobalLibrary .newBuilder(); libBuilder.setName(lib.getName()).addAllPath(lib.getPaths()); if (lib instanceof SdkLibrary) { final SdkLibrary sdk = (SdkLibrary) lib; libBuilder.setHomePath(sdk.getHomePath()); libBuilder.setTypeName(sdk.getTypeName()); final String additional = sdk.getAdditionalDataXml(); if (additional != null) { libBuilder.setAdditionalDataXml(additional); } final String version = sdk.getVersion(); if (version != null) { libBuilder.setVersion(version); } } cmdBuilder.addGlobalLibrary(libBuilder.build()); } } final String defaultCharset = EncodingManager.getInstance().getDefaultCharsetName(); if (defaultCharset != null) { cmdBuilder.setGlobalEncoding(defaultCharset); } final String ignoredFilesList = FileTypeManager.getInstance().getIgnoredFilesList(); cmdBuilder.setIgnoredFilesPatterns(ignoredFilesList); return cmdBuilder.build(); } private static void fillSdks(List<GlobalLibrary> globals) { for (Sdk sdk : ProjectJdkTable.getInstance().getAllJdks()) { final String name = sdk.getName(); final String homePath = sdk.getHomePath(); if (homePath == null) { continue; } final SdkAdditionalData data = sdk.getSdkAdditionalData(); final String additionalDataXml; final SdkType sdkType = (SdkType) sdk.getSdkType(); if (data == null) { additionalDataXml = null; } else { final Element element = new Element("additional"); sdkType.saveAdditionalData(data, element); additionalDataXml = JDOMUtil.writeElement(element, "\n"); } final List<String> paths = convertToLocalPaths(sdk.getRootProvider().getFiles(OrderRootType.CLASSES)); String versionString = sdk.getVersionString(); if (versionString != null && sdkType instanceof JavaSdk) { final JavaSdkVersion version = ((JavaSdk) sdkType).getVersion(versionString); if (version != null) { versionString = version.getDescription(); } } globals.add( new SdkLibrary( name, sdkType.getName(), versionString, homePath, paths, additionalDataXml)); } } private static void fillGlobalLibraries(List<GlobalLibrary> globals) { final Iterator<Library> iterator = LibraryTablesRegistrar.getInstance().getLibraryTable().getLibraryIterator(); while (iterator.hasNext()) { Library library = iterator.next(); final String name = library.getName(); if (name != null) { final List<String> paths = convertToLocalPaths(library.getFiles(OrderRootType.CLASSES)); globals.add(new GlobalLibrary(name, paths)); } } } private static List<String> convertToLocalPaths(VirtualFile[] files) { final List<String> paths = new ArrayList<String>(); for (VirtualFile file : files) { if (file.isValid()) { paths.add( StringUtil.trimEnd( FileUtil.toSystemIndependentName(file.getPath()), JarFileSystem.JAR_SEPARATOR)); } } return paths; } private Process launchBuildProcess(Project project, final int port, final UUID sessionId) throws ExecutionException { // choosing sdk with which the build process should be run final Sdk internalJdk = JavaAwareProjectJdkTableImpl.getInstanceEx().getInternalJdk(); Sdk projectJdk = internalJdk; final String versionString = projectJdk.getVersionString(); JavaSdkVersion sdkVersion = versionString != null ? ((JavaSdk) projectJdk.getSdkType()).getVersion(versionString) : null; if (sdkVersion != null) { final Set<Sdk> candidates = new HashSet<Sdk>(); for (Module module : ModuleManager.getInstance(project).getModules()) { final Sdk sdk = ModuleRootManager.getInstance(module).getSdk(); if (sdk != null && sdk.getSdkType() instanceof JavaSdk) { candidates.add(sdk); } } // now select the latest version from the sdks that are used in the project, but not older // than the internal sdk version for (Sdk candidate : candidates) { final String vs = candidate.getVersionString(); if (vs != null) { final JavaSdkVersion candidateVersion = ((JavaSdk) candidate.getSdkType()).getVersion(vs); if (candidateVersion != null) { if (candidateVersion.compareTo(sdkVersion) > 0) { sdkVersion = candidateVersion; projectJdk = candidate; } } } } } // validate tools.jar presence final File compilerPath; if (projectJdk.equals(internalJdk)) { final JavaCompiler systemCompiler = ToolProvider.getSystemJavaCompiler(); if (systemCompiler == null) { throw new ExecutionException( "No system java compiler is provided by the JRE. Make sure tools.jar is present in IntelliJ IDEA classpath."); } compilerPath = ClasspathBootstrap.getResourcePath(systemCompiler.getClass()); } else { final String path = ((JavaSdk) projectJdk.getSdkType()).getToolsPath(projectJdk); if (path == null) { throw new ExecutionException( "Cannot determine path to 'tools.jar' library for " + projectJdk.getName() + " (" + projectJdk.getHomePath() + ")"); } compilerPath = new File(path); } final GeneralCommandLine cmdLine = new GeneralCommandLine(); final String vmExecutablePath = ((JavaSdkType) projectJdk.getSdkType()).getVMExecutablePath(projectJdk); cmdLine.setExePath(vmExecutablePath); cmdLine.addParameter("-XX:MaxPermSize=150m"); cmdLine.addParameter("-XX:ReservedCodeCacheSize=64m"); final int heapSize = Registry.intValue("compiler.process.heap.size"); final int xms = heapSize / 2; if (xms > 32) { cmdLine.addParameter("-Xms" + xms + "m"); } cmdLine.addParameter("-Xmx" + heapSize + "m"); if (SystemInfo.isMac && sdkVersion != null && JavaSdkVersion.JDK_1_6.equals(sdkVersion) && Registry.is("compiler.process.32bit.vm.on.mac")) { // unfortunately -d32 is supported on jdk 1.6 only cmdLine.addParameter("-d32"); } cmdLine.addParameter("-Djava.awt.headless=true"); final String shouldGenerateIndex = System.getProperty(GlobalOptions.GENERATE_CLASSPATH_INDEX_OPTION); if (shouldGenerateIndex != null) { cmdLine.addParameter( "-D" + GlobalOptions.GENERATE_CLASSPATH_INDEX_OPTION + "=" + shouldGenerateIndex); } final String additionalOptions = Registry.stringValue("compiler.process.vm.options"); if (!StringUtil.isEmpty(additionalOptions)) { final StringTokenizer tokenizer = new StringTokenizer(additionalOptions, " ", false); while (tokenizer.hasMoreTokens()) { cmdLine.addParameter(tokenizer.nextToken()); } } // debugging final int debugPort = Registry.intValue("compiler.process.debug.port"); if (debugPort > 0) { cmdLine.addParameter("-XX:+HeapDumpOnOutOfMemoryError"); cmdLine.addParameter( "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=" + debugPort); } if (Registry.is("compiler.process.use.memory.temp.cache")) { cmdLine.addParameter("-D" + GlobalOptions.USE_MEMORY_TEMP_CACHE_OPTION); } if (Registry.is("compiler.process.use.external.javac")) { cmdLine.addParameter("-D" + GlobalOptions.USE_EXTERNAL_JAVAC_OPTION); } final String host = NetUtils.getLocalHostString(); cmdLine.addParameter("-D" + GlobalOptions.HOSTNAME_OPTION + "=" + host); // javac's VM should use the same default locale that IDEA uses in order for javac to print // messages in 'correct' language final String lang = System.getProperty("user.language"); if (lang != null) { //noinspection HardCodedStringLiteral cmdLine.addParameter("-Duser.language=" + lang); } final String country = System.getProperty("user.country"); if (country != null) { //noinspection HardCodedStringLiteral cmdLine.addParameter("-Duser.country=" + country); } //noinspection HardCodedStringLiteral final String region = System.getProperty("user.region"); if (region != null) { //noinspection HardCodedStringLiteral cmdLine.addParameter("-Duser.region=" + region); } cmdLine.addParameter("-classpath"); final List<File> cp = ClasspathBootstrap.getBuildProcessApplicationClasspath(); cp.add(compilerPath); cp.addAll(myClasspathManager.getCompileServerPluginsClasspath()); cmdLine.addParameter(classpathToString(cp)); cmdLine.addParameter(BuildMain.class.getName()); cmdLine.addParameter(host); cmdLine.addParameter(Integer.toString(port)); cmdLine.addParameter(sessionId.toString()); final File workDirectory = new File(mySystemDirectory, SYSTEM_ROOT); workDirectory.mkdirs(); ensureLogConfigExists(workDirectory); cmdLine.addParameter(FileUtil.toSystemIndependentName(workDirectory.getPath())); cmdLine.setWorkDirectory(workDirectory); return cmdLine.createProcess(); } private static void ensureLogConfigExists(File workDirectory) { final File logConfig = new File(workDirectory, LOGGER_CONFIG); if (!logConfig.exists()) { FileUtil.createIfDoesntExist(logConfig); try { final InputStream in = Server.class.getResourceAsStream("/" + DEFAULT_LOGGER_CONFIG); if (in != null) { try { final FileOutputStream out = new FileOutputStream(logConfig); try { FileUtil.copy(in, out); } finally { out.close(); } } finally { in.close(); } } } catch (IOException e) { LOG.error(e); } } } public void stopListening() { final ChannelGroupFuture closeFuture = myAllOpenChannels.close(); closeFuture.awaitUninterruptibly(); } private int startListening() throws Exception { final ChannelFactory channelFactory = new NioServerSocketChannelFactory(myPooledThreadExecutor, myPooledThreadExecutor); final SimpleChannelUpstreamHandler channelRegistrar = new SimpleChannelUpstreamHandler() { public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { myAllOpenChannels.add(e.getChannel()); super.channelOpen(ctx, e); } @Override public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { myAllOpenChannels.remove(e.getChannel()); super.channelClosed(ctx, e); } }; ChannelPipelineFactory pipelineFactory = new ChannelPipelineFactory() { public ChannelPipeline getPipeline() throws Exception { return Channels.pipeline( channelRegistrar, new ProtobufVarint32FrameDecoder(), new ProtobufDecoder(CmdlineRemoteProto.Message.getDefaultInstance()), new ProtobufVarint32LengthFieldPrepender(), new ProtobufEncoder(), myMessageDispatcher); } }; final ServerBootstrap bootstrap = new ServerBootstrap(channelFactory); bootstrap.setPipelineFactory(pipelineFactory); bootstrap.setOption("child.tcpNoDelay", true); bootstrap.setOption("child.keepAlive", true); final int listenPort = NetUtils.findAvailableSocketPort(); final Channel serverChannel = bootstrap.bind(new InetSocketAddress(listenPort)); myAllOpenChannels.add(serverChannel); return listenPort; } private static String classpathToString(List<File> cp) { StringBuilder builder = new StringBuilder(); for (File file : cp) { if (builder.length() > 0) { builder.append(File.pathSeparator); } builder.append(FileUtil.toCanonicalPath(file.getPath())); } return builder.toString(); } private class ProjectWatcher extends ProjectManagerAdapter { private final Map<Project, MessageBusConnection> myConnections = new HashMap<Project, MessageBusConnection>(); @Override public void projectOpened(final Project project) { final MessageBusConnection conn = project.getMessageBus().connect(); myConnections.put(project, conn); conn.subscribe( ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() { @Override public void rootsChanged(final ModuleRootEvent event) { final Object source = event.getSource(); if (source instanceof Project) { clearState((Project) source); } } }); } @Override public boolean canCloseProject(Project project) { cancelAutoMakeTasks(project); return super.canCloseProject(project); } @Override public void projectClosing(Project project) { for (RequestFuture future : cancelAutoMakeTasks(project)) { future.waitFor(500, TimeUnit.MILLISECONDS); } } @Override public void projectClosed(Project project) { myProjectDataMap.remove(getProjectPath(project)); final MessageBusConnection conn = myConnections.remove(project); if (conn != null) { conn.disconnect(); } } } private static class ProjectData { final SequentialTaskExecutor taskQueue; private final Set<String> myChanged = new HashSet<String>(); private final Set<String> myDeleted = new HashSet<String>(); private long myNextEventOrdinal = 0L; private boolean myNeedRescan = true; private ProjectData(SequentialTaskExecutor taskQueue) { this.taskQueue = taskQueue; } public void addChanged(Collection<String> paths) { if (!myNeedRescan) { myDeleted.removeAll(paths); myChanged.addAll(paths); } } public void addDeleted(Collection<String> paths) { if (!myNeedRescan) { myChanged.removeAll(paths); myDeleted.addAll(paths); } } public CmdlineRemoteProto.Message.ControllerMessage.FSEvent createNextEvent() { final CmdlineRemoteProto.Message.ControllerMessage.FSEvent.Builder builder = CmdlineRemoteProto.Message.ControllerMessage.FSEvent.newBuilder(); builder.setOrdinal(++myNextEventOrdinal); builder.addAllChangedPaths(myChanged); myChanged.clear(); builder.addAllDeletedPaths(myDeleted); myDeleted.clear(); return builder.build(); } public boolean getAndResetRescanFlag() { final boolean rescan = myNeedRescan; myNeedRescan = false; return rescan; } public void dropChanges() { myNeedRescan = true; myNextEventOrdinal = 0L; myChanged.clear(); myDeleted.clear(); } } }