Package vektah.rust.ide.builder

Source Code of vektah.rust.ide.builder.RustCompilerDriver

package vektah.rust.ide.builder;

import com.intellij.compiler.CompilerConfiguration;
import com.intellij.compiler.ProblemsView;
import com.intellij.compiler.impl.*;
import com.intellij.compiler.progress.CompilerTask;
import com.intellij.compiler.server.CustomBuilderMessageHandler;
import com.intellij.compiler.server.DefaultMessageHandler;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.compiler.*;
import com.intellij.openapi.compiler.ex.CompilerPathsEx;
import com.intellij.openapi.deployment.DeploymentUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.openapi.wm.StatusBar;
import com.intellij.openapi.wm.ToolWindowId;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.packaging.artifacts.Artifact;
import com.intellij.packaging.impl.compiler.ArtifactCompilerUtil;
import com.intellij.packaging.impl.compiler.ArtifactsCompiler;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.util.ThrowableRunnable;
import com.intellij.util.containers.*;
import com.intellij.util.messages.MessageBus;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.api.CmdlineProtoUtil;
import org.jetbrains.jps.api.CmdlineRemoteProto;
import org.jetbrains.jps.api.RequestFuture;

import javax.swing.*;
import java.io.File;
import java.util.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.concurrent.TimeUnit;

public class RustCompilerDriver {
  private static final Logger LOG = Logger.getInstance(RustCompilerDriver.class);

  private static final Key<ExitStatus> COMPILE_SERVER_BUILD_STATUS = Key.create("RUST_COMPILE_SERVER_BUILD_STATUS");
  private static final long ONE_MINUTE_MS = 60L /*sec*/ * 1000L /*millisec*/;

  private final Project myProject;

  public RustCompilerDriver(Project myProject) {
    this.myProject = myProject;
  }

  public void make(CompileScope scope, CompileStatusNotification callback) {
    startup(scope, false, false, callback, null, true);
  }

  private void startup(final CompileScope scope,
                       final boolean isRebuild,
                       final boolean forceCompile,
                       final CompileStatusNotification callback,
                       final CompilerMessage message,
                       final boolean checkCachesVersion) {
    ApplicationManager.getApplication().assertIsDispatchThread();

    final String contentName =
        forceCompile ? CompilerBundle.message("compiler.content.name.compile") : CompilerBundle.message("compiler.content.name.make");
    final boolean isUnitTestMode = ApplicationManager.getApplication().isUnitTestMode();
    final CompilerTask compileTask = new CompilerTask(myProject, contentName, isUnitTestMode, true, true, isCompilationStartedAutomatically(scope));

    StatusBar.Info.set("", myProject, "Compiler");

    saveAndCommit(isUnitTestMode);

    final CompileContextImpl compileContext = new CompileContextImpl(myProject, compileTask, scope, null, !isRebuild && !forceCompile, isRebuild);

    executeCompileTask(scope, isRebuild, forceCompile, callback, message, checkCachesVersion, compileTask, compileContext);
  }

  private void executeCompileTask(final CompileScope scope, final boolean isRebuild, final boolean forceCompile, final CompileStatusNotification callback, final CompilerMessage message, final boolean checkCachesVersion, CompilerTask compileTask, final CompileContextImpl compileContext) {
    final Runnable compileWork = new Runnable() {
      public void run() {
        final ProgressIndicator indicator = compileContext.getProgressIndicator();
        if (indicator.isCanceled() || myProject.isDisposed()) {
          if (callback != null) {
            callback.finished(true, 0, 0, compileContext);
          }
          return;
        }
        try {
          LOG.info("RUST COMPILATION STARTED (BUILD PROCESS)");
          if (message != null) {
            compileContext.addMessage(message);
          }
          if (isRebuild) {
            CompilerUtil.runInContext(compileContext, "Clearing build system data...", new ThrowableRunnable<Throwable>() {
              @Override
              public void run() throws Throwable {
                CompilerCacheManager.getInstance(myProject).clearCaches(compileContext);
              }
            });
          }
          final boolean beforeTasksOk = executeCompileTasks(compileContext, true);

          final int errorCount = compileContext.getMessageCount(CompilerMessageCategory.ERROR);
          if (!beforeTasksOk || errorCount > 0) {
            COMPILE_SERVER_BUILD_STATUS.set(compileContext, errorCount > 0? ExitStatus.ERRORS : ExitStatus.CANCELLED);
            return;
          }

          final RequestFuture future = compileInExternalProcess(compileContext, false);
          if (future != null) {
            while (!future.waitFor(200L , TimeUnit.MILLISECONDS)) {
              if (indicator.isCanceled()) {
                future.cancel(false);
              }
            }
            if (!executeCompileTasks(compileContext, false)) {
              COMPILE_SERVER_BUILD_STATUS.set(compileContext, ExitStatus.CANCELLED);
            }
            if (compileContext.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
              COMPILE_SERVER_BUILD_STATUS.set(compileContext, ExitStatus.ERRORS);
            }
          }
        }
        catch (Throwable e) {
          LOG.error(e); // todo
        }
        finally {
          CompilerCacheManager.getInstance(myProject).flushCaches();

          final long duration = notifyCompilationCompleted(compileContext, callback, COMPILE_SERVER_BUILD_STATUS.get(compileContext), true);
          CompilerUtil.logDuration(
              "\tCOMPILATION FINISHED (BUILD PROCESS); Errors: " +
                  compileContext.getMessageCount(CompilerMessageCategory.ERROR) +
                  "; warnings: " +
                  compileContext.getMessageCount(CompilerMessageCategory.WARNING),
              duration
          );
        }
      }
    };

    compileTask.start(compileWork, new Runnable() {
      public void run() {
        if (isRebuild) {
          final int rv = Messages.showOkCancelDialog(
              myProject, "You are about to rebuild the whole project.\nRun 'Make Project' instead?", "Confirm Project Rebuild",
              "Make", "Rebuild", Messages.getQuestionIcon()
          );
          if (rv == Messages.OK /*yes, please, do run make*/) {
            startup(scope, false, false, callback, null, checkCachesVersion);
            return;
          }
        }
        startup(scope, isRebuild, forceCompile, callback, message, checkCachesVersion);
      }
    });
  }

  private boolean executeCompileTasks(final CompileContext context, final boolean beforeTasks) {
    if (myProject.isDisposed()) {
      return false;
    }
    final CompilerManager manager = CompilerManager.getInstance(myProject);
    final ProgressIndicator progressIndicator = context.getProgressIndicator();
    progressIndicator.pushState();
    try {
      CompileTask[] tasks = beforeTasks ? manager.getBeforeTasks() : manager.getAfterTasks();
      if (tasks.length > 0) {
        progressIndicator.setText(beforeTasks
            ? CompilerBundle.message("progress.executing.precompile.tasks")
            : CompilerBundle.message("progress.executing.postcompile.tasks"));
        for (CompileTask task : tasks) {
          if (!task.execute(context)) {
            return false;
          }
        }
      }
    }
    finally {
      progressIndicator.popState();
      WindowManager.getInstance().getStatusBar(myProject).setInfo("");
      if (progressIndicator instanceof CompilerTask) {
        ApplicationManager.getApplication().invokeLater(new Runnable() {
          public void run() {
            ((CompilerTask)progressIndicator).showCompilerContent();
          }
        });
      }
    }
    return true;
  }


  @Nullable
  private RequestFuture compileInExternalProcess(final @NotNull CompileContextImpl compileContext, final boolean onlyCheckUpToDate)
      throws Exception {
    final CompileScope scope = compileContext.getCompileScope();
    final Collection<String> paths = CompileScopeUtil.fetchFiles(compileContext);
    // FIXME: For now, `scopes` aren't used
    List<CmdlineRemoteProto.Message.ControllerMessage.ParametersMessage.TargetTypeBuildScope> scopes = new ArrayList<CmdlineRemoteProto.Message.ControllerMessage.ParametersMessage.TargetTypeBuildScope>();
    final boolean forceBuild = !compileContext.isMake();
    List<CmdlineRemoteProto.Message.ControllerMessage.ParametersMessage.TargetTypeBuildScope> explicitScopes = CompileScopeUtil.getBaseScopeForExternalBuild(scope);
    if (explicitScopes != null) {
      scopes.addAll(explicitScopes);
    }
    else if (!compileContext.isRebuild() && !CompileScopeUtil.allProjectModulesAffected(compileContext)) {
      CompileScopeUtil.addScopesForModules(Arrays.asList(scope.getAffectedModules()), scopes, forceBuild);
    }
    else {
      scopes.addAll(CmdlineProtoUtil.createAllModulesScopes(forceBuild));
    }
    if (paths.isEmpty()) {
      for (BuildTargetScopeProvider provider : BuildTargetScopeProvider.EP_NAME.getExtensions()) {
        scopes = CompileScopeUtil.mergeScopes(scopes, provider.getBuildTargetScopes(scope, CompilerFilter.ALL, myProject, forceBuild));
      }
    }

    // need to pass scope's user data to server
    final Map<String, String> builderParams;
    if (onlyCheckUpToDate) {
      builderParams = Collections.emptyMap();
    }
    else {
      final Map<Key, Object> exported = scope.exportUserData();
      if (!exported.isEmpty()) {
        builderParams = new com.intellij.util.containers.HashMap<String, String>();
        for (Map.Entry<Key, Object> entry : exported.entrySet()) {
          final String _key = entry.getKey().toString();
          final String _value = entry.getValue().toString();
          builderParams.put(_key, _value);
        }
      }
      else {
        builderParams = Collections.emptyMap();
      }
    }

    final MessageBus messageBus = myProject.getMessageBus();
    final MultiMap<String, Artifact> outputToArtifact = ArtifactCompilerUtil.containsArtifacts(scopes) ? ArtifactCompilerUtil.createOutputToArtifactMap(myProject) : null;
    final RustBuildManager buildManager = RustBuildManager.getInstance();
    buildManager.cancelAutoMakeTasks(myProject);
    return buildManager.scheduleBuild(myProject, compileContext.isRebuild(), compileContext.isMake(), onlyCheckUpToDate, scope, paths, builderParams, new DefaultMessageHandler(myProject) {
      @Override
      public void buildStarted(UUID sessionId) {
      }

      @Override
      public void sessionTerminated(final UUID sessionId) {
        if (compileContext.shouldUpdateProblemsView()) {
          final ProblemsView view = ProblemsViewImpl.SERVICE.getInstance(myProject);
          view.clearProgress();
          view.clearOldMessages(compileContext.getCompileScope(), compileContext.getSessionId());
        }
      }

      @Override
      public void handleFailure(UUID sessionId, CmdlineRemoteProto.Message.Failure failure) {
        if (failure.hasStacktrace()) {
          compileContext.addMessage(CompilerMessageCategory.ERROR, failure.getDescription(), null, -1, -1);
        } else {
          parseErrors(failure.getDescription());
        }
        compileContext.putUserData(COMPILE_SERVER_BUILD_STATUS, ExitStatus.ERRORS);
      }

      private void parseErrors(String description) {
        Map<String, VirtualFile> files = new HashMap<String, VirtualFile>();
        String[] lines = description.split("\n");
        for (String line : lines) {
          if (Character.isWhitespace(line.charAt(0))) {
            continue;
          }
          int idx = line.indexOf(':');
          if (idx <= 0) {
            continue;
          }
          String fileName = line.substring(0, idx);
          VirtualFile file = files.get(fileName);
          if (file == null) {
            file = myProject.getBaseDir().findFileByRelativePath(fileName);
            if (file != null) {
              files.put(fileName, file);
            }
          }
          if (file == null) {
            continue;
          }
          try {
            int idx2 = line.indexOf(':', idx + 1);
            int lineFromRow = Integer.valueOf(line.substring(idx + 1, idx2));
            idx = idx2;
            idx2 = line.indexOf(':', idx + 1);
            int lineFromCol = Integer.valueOf(line.substring(idx + 1, idx2));

            idx = idx2;
            idx2 = line.indexOf(':', idx + 1);
            if (!Character.isWhitespace(line.charAt(idx + 1))) {
              continue;
            }
            int lineToRow = Integer.valueOf(line.substring(idx + 2, idx2));
            idx = idx2;
            idx2 = line.indexOf(' ', idx + 1);
            int lineToCol = Integer.valueOf(line.substring(idx + 1, idx2));
            String message = line.substring(idx2 + 1);

            final CompilerMessageCategory errorType;
            if (message.startsWith("error: ")) {
              errorType = CompilerMessageCategory.ERROR;
              message = message.substring("error: ".length());
            } else if (message.startsWith("warning: ")) {
              errorType = CompilerMessageCategory.WARNING;
              message = message.substring("error: ".length());
            } else {
              errorType = CompilerMessageCategory.INFORMATION;
            }
            compileContext.addMessage(errorType, message, file.getUrl(), lineFromRow, lineFromCol);
          } catch (Exception ex) {
          }
        }
      }

      @Override
      protected void handleCompileMessage(UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.CompileMessage message) {
        final CmdlineRemoteProto.Message.BuilderMessage.CompileMessage.Kind kind = message.getKind();
        //System.out.println(compilerMessage.getText());
        final String messageText = message.getText();
        if (kind == CmdlineRemoteProto.Message.BuilderMessage.CompileMessage.Kind.PROGRESS) {
          final ProgressIndicator indicator = compileContext.getProgressIndicator();
          indicator.setText(messageText);
          if (message.hasDone()) {
            indicator.setFraction(message.getDone());
          }
        }
        else {
          final CompilerMessageCategory category = kind == CmdlineRemoteProto.Message.BuilderMessage.CompileMessage.Kind.ERROR ? CompilerMessageCategory.ERROR
              : kind == CmdlineRemoteProto.Message.BuilderMessage.CompileMessage.Kind.WARNING ? CompilerMessageCategory.WARNING : CompilerMessageCategory.INFORMATION;

          String sourceFilePath = message.hasSourceFilePath() ? message.getSourceFilePath() : null;
          if (sourceFilePath != null) {
            sourceFilePath = FileUtil.toSystemIndependentName(sourceFilePath);
          }
          final long line = message.hasLine() ? message.getLine() : -1;
          final long column = message.hasColumn() ? message.getColumn() : -1;
          final String srcUrl = sourceFilePath != null ? VirtualFileManager.constructUrl(LocalFileSystem.PROTOCOL, sourceFilePath) : null;
          compileContext.addMessage(category, messageText, srcUrl, (int)line, (int)column);
        }
      }

      @Override
      protected void handleBuildEvent(UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.BuildEvent event) {
        final CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.Type eventType = event.getEventType();
        switch (eventType) {
          case FILES_GENERATED:
            final List<CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.GeneratedFile> generated = event.getGeneratedFilesList();
            final CompilationStatusListener publisher = messageBus.syncPublisher(CompilerTopics.COMPILATION_STATUS);
            Set<String> writtenArtifactOutputPaths = outputToArtifact != null ? new THashSet<String>(FileUtil.PATH_HASHING_STRATEGY) : null;
            for (CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.GeneratedFile generatedFile : generated) {
              final String root = FileUtil.toSystemIndependentName(generatedFile.getOutputRoot());
              final String relativePath = FileUtil.toSystemIndependentName(generatedFile.getRelativePath());
              publisher.fileGenerated(root, relativePath);
              if (outputToArtifact != null) {
                Collection<Artifact> artifacts = outputToArtifact.get(root);
                if (!artifacts.isEmpty()) {
                  for (Artifact artifact : artifacts) {
                    ArtifactsCompiler.addChangedArtifact(compileContext, artifact);
                  }
                  writtenArtifactOutputPaths.add(FileUtil.toSystemDependentName(DeploymentUtil.appendToPath(root, relativePath)));
                }
              }
            }
            if (writtenArtifactOutputPaths != null && !writtenArtifactOutputPaths.isEmpty()) {
              ArtifactsCompiler.addWrittenPaths(compileContext, writtenArtifactOutputPaths);
            }
            break;
          case BUILD_COMPLETED:
            ExitStatus status = ExitStatus.SUCCESS;
            if (event.hasCompletionStatus()) {
              final CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.Status completionStatus = event.getCompletionStatus();
              switch (completionStatus) {
                case CANCELED:
                  status = ExitStatus.CANCELLED;
                  break;
                case ERRORS:
                  status = ExitStatus.ERRORS;
                  break;
                case SUCCESS:
                  status = ExitStatus.SUCCESS;
                  break;
                case UP_TO_DATE:
                  status = ExitStatus.UP_TO_DATE;
                  break;
              }
            }
            compileContext.putUserDataIfAbsent(COMPILE_SERVER_BUILD_STATUS, status);
            break;
          case CUSTOM_BUILDER_MESSAGE:
            if (event.hasCustomBuilderMessage()) {
              CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.CustomBuilderMessage message = event.getCustomBuilderMessage();
              messageBus.syncPublisher(CustomBuilderMessageHandler.TOPIC).messageReceived(message.getBuilderId(), message.getMessageType(),
                  message.getMessageText());
            }
            break;
        }
      }
    });
  }

  private void saveAndCommit(boolean isUnitTestMode) {
    // ensure the project model seen by build process is up-to-date
    myProject.save();
    if (!isUnitTestMode) {
      ApplicationManager.getApplication().saveSettings();
    }

    PsiDocumentManager.getInstance(myProject).commitAllDocuments();
    FileDocumentManager.getInstance().saveAllDocuments();
  }

  private static boolean isCompilationStartedAutomatically(CompileScope scope) {
    // FIXME
    return false;
  }

  private long notifyCompilationCompleted(final CompileContextImpl compileContext,
                                          final CompileStatusNotification callback,
                                          final ExitStatus _status,
                                          final boolean refreshOutputRoots) {
    final long duration = System.currentTimeMillis() - compileContext.getStartCompilationStamp();
    if (refreshOutputRoots && !myProject.isDisposed()) {
      // refresh on output roots is required in order for the order enumerator to see all roots via VFS
      final Set<File> outputs = new HashSet<File>();
      final Module[] affectedModules = compileContext.getCompileScope().getAffectedModules();
      for (final String path : CompilerPathsEx.getOutputPaths(affectedModules)) {
        outputs.add(new File(path));
      }
      final LocalFileSystem lfs = LocalFileSystem.getInstance();
      if (!outputs.isEmpty()) {
        final ProgressIndicator indicator = compileContext.getProgressIndicator();
        indicator.setText("Synchronizing output directories...");
        CompilerUtil.refreshOutputDirectories(outputs, _status == ExitStatus.CANCELLED);
        indicator.setText("");
      }
      if (compileContext.isAnnotationProcessorsEnabled() && !myProject.isDisposed()) {
        final Set<File> genSourceRoots = new THashSet<File>(FileUtil.FILE_HASHING_STRATEGY);
        final CompilerConfiguration config = CompilerConfiguration.getInstance(myProject);
        for (Module module : affectedModules) {
          if (config.getAnnotationProcessingConfiguration(module).isEnabled()) {
            final String path = CompilerPaths.getAnnotationProcessorsGenerationPath(module);
            if (path != null) {
              genSourceRoots.add(new File(path));
            }
          }
        }
        if (!genSourceRoots.isEmpty()) {
          // refresh generates source roots asynchronously; needed for error highlighting update
          lfs.refreshIoFiles(genSourceRoots, true, true, null);
        }
      }
    }
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        int errorCount = 0;
        int warningCount = 0;
        try {
          errorCount = compileContext.getMessageCount(CompilerMessageCategory.ERROR);
          warningCount = compileContext.getMessageCount(CompilerMessageCategory.WARNING);
          if (!myProject.isDisposed()) {
            final String statusMessage = createStatusMessage(_status, warningCount, errorCount, duration);
            final MessageType messageType = errorCount > 0 ? MessageType.ERROR : warningCount > 0 ? MessageType.WARNING : MessageType.INFO;
            if (duration > ONE_MINUTE_MS) {
              ToolWindowManager.getInstance(myProject).notifyByBalloon(ToolWindowId.MESSAGES_WINDOW, messageType, statusMessage);
            }
            CompilerManager.NOTIFICATION_GROUP.createNotification(statusMessage, messageType).notify(myProject);
            if (_status != ExitStatus.UP_TO_DATE && compileContext.getMessageCount(null) > 0) {
              compileContext.addMessage(CompilerMessageCategory.INFORMATION, statusMessage, null, -1, -1);
            }
          }
        } finally {
          if (callback != null) {
            callback.finished(_status == ExitStatus.CANCELLED, errorCount, warningCount, compileContext);
          }
        }
      }
    });
    return duration;
  }

  private static String createStatusMessage(final ExitStatus status, final int warningCount, final int errorCount, long duration) {
    String message;
    if (status == ExitStatus.CANCELLED) {
      message = CompilerBundle.message("status.compilation.aborted");
    }
    else if (status == ExitStatus.UP_TO_DATE) {
      message = CompilerBundle.message("status.all.up.to.date");
    }
    else  {
      if (status == ExitStatus.SUCCESS) {
        message = warningCount > 0
            ? CompilerBundle.message("status.compilation.completed.successfully.with.warnings", warningCount)
            : CompilerBundle.message("status.compilation.completed.successfully");
      }
      else {
        message = CompilerBundle.message("status.compilation.completed.successfully.with.warnings.and.errors", errorCount, warningCount);
      }
      message = message + " in " + StringUtil.formatDuration(duration);
    }
    return message;
  }
}
TOP

Related Classes of vektah.rust.ide.builder.RustCompilerDriver

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.