Package org.jetbrains.jps.clojure.build

Source Code of org.jetbrains.jps.clojure.build.ClojureBuilder

package org.jetbrains.jps.clojure.build;

import com.intellij.execution.process.BaseOSProcessHandler;
import com.intellij.execution.process.ProcessAdapter;
import com.intellij.execution.process.ProcessEvent;
import com.intellij.execution.process.ProcessOutputTypes;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.ModuleChunk;
import org.jetbrains.jps.builders.DirtyFilesHolder;
import org.jetbrains.jps.builders.FileProcessor;
import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor;
import org.jetbrains.jps.builders.storage.SourceToOutputMapping;
import org.jetbrains.jps.clojure.model.JpsClojureCompilerSettingsExtension;
import org.jetbrains.jps.clojure.model.JpsClojureExtensionService;
import org.jetbrains.jps.incremental.*;
import org.jetbrains.jps.incremental.messages.BuildMessage;
import org.jetbrains.jps.incremental.messages.CompilerMessage;
import org.jetbrains.jps.incremental.messages.ProgressMessage;
import org.jetbrains.jps.model.JpsDummyElement;
import org.jetbrains.jps.model.JpsProject;
import org.jetbrains.jps.model.java.JpsJavaExtensionService;
import org.jetbrains.jps.model.java.JpsJavaModuleType;
import org.jetbrains.jps.model.java.JpsJavaSdkType;
import org.jetbrains.jps.model.library.sdk.JpsSdk;
import org.jetbrains.jps.model.module.JpsModule;
import org.jetbrains.jps.model.module.JpsModuleSourceRoot;
import org.jetbrains.jps.service.SharedThreadPool;

import java.io.*;
import java.util.*;
import java.util.concurrent.Future;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* @author nik, Alefas
* @since 02.11.12
*/
public class ClojureBuilder extends ModuleLevelBuilder {
  public static final String COMPILER_NAME = "Clojure Compiler";
  public static final String CLOJURE_MAIN = "clojure.main";

  public static final String COMPILING_PREFIX = "[compiling]:";
  public static final String COMPILED_PREFIX = "[compiled]:";
  public static final String ERROR_PREFIX = "[error]:";
  public static final String WRITING_PREFIX = "[writing]:";
  private final boolean myBeforeJava;

  public ClojureBuilder(boolean isBeforeJava) {
    super(isBeforeJava ? BuilderCategory.SOURCE_PROCESSOR : BuilderCategory.OVERWRITING_TRANSLATOR);
    myBeforeJava = isBeforeJava;
  }

  @Override
  public ExitCode build(final CompileContext context, final ModuleChunk chunk,
                        DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder,
                        final OutputConsumer outputConsumer) throws ProjectBuildException, IOException {
    JpsProject project = context.getProjectDescriptor().getProject();
    JpsClojureCompilerSettingsExtension extension = JpsClojureExtensionService.getExtension(project);
    if (myBeforeJava && (extension == null || !extension.isClojureBefore())) return ExitCode.NOTHING_DONE;
    if (!myBeforeJava && extension != null && extension.isClojureBefore()) return ExitCode.NOTHING_DONE;
    if (extension != null && !extension.isCompileClojure()) return ExitCode.NOTHING_DONE;

    final LinkedHashSet<JpsModule> javaModules = new LinkedHashSet<JpsModule>();
    for (JpsModule module : chunk.getModules()) {
      if (module.getModuleType().equals(JpsJavaModuleType.INSTANCE)) {
        javaModules.add(module);
      }
    }

    final List<File> toCompile = new ArrayList<File>();
    final HashMap<File, String> toCompileNamespace = new HashMap<File, String>();
    dirtyFilesHolder.processDirtyFiles(new FileProcessor<JavaSourceRootDescriptor, ModuleBuildTarget>() {
      public boolean apply(ModuleBuildTarget target, File file, JavaSourceRootDescriptor sourceRoot) throws IOException {
        if (javaModules.contains(target.getModule()) && file.getName().endsWith(".clj")) {
          if (!hasGenClass(file)) {
            return true;
          }
         
          toCompile.add(file);
         
          String filePath = file.getAbsolutePath();
          File rootFile = sourceRoot.getRootFile();
          String relPath = filePath.substring(rootFile.getAbsolutePath().length() + 1, filePath.length() - ".clj".length());
          toCompileNamespace.put(file, relPath.replace(File.separator, "."));
        }
        return true;
      }
    });

    if (toCompile.isEmpty()) return ExitCode.NOTHING_DONE;

    JpsSdk<JpsDummyElement> sdk = chunk.representativeTarget().getModule().getSdk(JpsJavaSdkType.INSTANCE);
    if (sdk == null) {
      context.processMessage(new CompilerMessage(COMPILER_NAME, BuildMessage.Kind.ERROR, "JDK is not specified"));
      return ExitCode.ABORT;
    }

    String javaExecutable = JpsJavaSdkType.getJavaExecutable(sdk);

    List<String> classpath = new ArrayList<String>();
    for (File root : JpsJavaExtensionService.getInstance().enumerateDependencies(javaModules).classes().getRoots()) {
      classpath.add(root.getAbsolutePath());
    }
    for (JpsModule module : javaModules) {
      for (JpsModuleSourceRoot sourceRoot : module.getSourceRoots()) {
        classpath.add(sourceRoot.getFile().getAbsolutePath());
      }
    }

    List<String> vmParams = new ArrayList<String>();
//    vmParams.add("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5239");
    List<String> programParams = new ArrayList<String>();
    File outputDir = chunk.representativeTarget().getOutputDir();
    outputDir.mkdirs();
   
    File fileWithCompileScript = FileUtil.createTempFile("clojurekul", ".clj");
    fillFileWithClojureCompilerParams(toCompile, toCompileNamespace, fileWithCompileScript, outputDir);
    programParams.add(fileWithCompileScript.getAbsolutePath());
    List<String> commandLine =
        ExternalProcessUtil.buildJavaCommandLine(javaExecutable, CLOJURE_MAIN, Collections.<String>emptyList(), classpath, vmParams, programParams);

    Process process = Runtime.getRuntime().exec(ArrayUtil.toStringArray(commandLine));

    BaseOSProcessHandler handler = new BaseOSProcessHandler(process, null, null) {
      @Override
      protected Future<?> executeOnPooledThread(Runnable task) {
        return SharedThreadPool.getInstance().executeOnPooledThread(task);
      }
    };

    final SourceToOutputMapping sourceToOutputMap = context.getProjectDescriptor().dataManager.getSourceToOutputMap(chunk.representativeTarget());

    final HashSet<String> outputs = new HashSet<String>();

    handler.addProcessListener(new ProcessAdapter() {
      @Override
      public void onTextAvailable(ProcessEvent event, Key outputType) {
        if (outputType != ProcessOutputTypes.STDERR) return;
        String text = event.getText().trim();
        context.processMessage(new ProgressMessage(text));
//        context.processMessage(new CompilerMessage(COMPILER_NAME, BuildMessage.Kind.WARNING, text));
        if (text.startsWith(ERROR_PREFIX)) {
          String errorDescription = text.substring(ERROR_PREFIX.length());
          Pattern pattern = Pattern.compile("(.*)@@((.*) compiling:[(](.*):([\\d]*)(:([\\d]*))?[)])");
          Matcher matcher = pattern.matcher(errorDescription);
          if (matcher.matches()) {
            String sourceName = matcher.group(1);
            String lineNumber = matcher.group(5);
            String columnNumber = matcher.group(6);
            Long locationLine = -1L;
            Long column = 0L;
            try {
              locationLine = Long.parseLong(lineNumber);
              if (columnNumber != null) {
                column = Long.parseLong(columnNumber);
              }
            } catch (Exception ignore) {}
            String errorMessage = matcher.group(2);
            context.processMessage(new CompilerMessage(COMPILER_NAME, BuildMessage.Kind.ERROR, errorMessage,
                sourceName, -1L, -1L, -1L, locationLine, column));
          } else {
            matcher = Pattern.compile("(.*)@@(.*)").matcher(errorDescription);
            if (matcher.matches()) {
              context.processMessage(new CompilerMessage(COMPILER_NAME, BuildMessage.Kind.ERROR, matcher.group(2), matcher.group(1)));
            } else {
              context.processMessage(new CompilerMessage(COMPILER_NAME, BuildMessage.Kind.ERROR, text));
            }
          }
        } else if (text.startsWith(COMPILING_PREFIX)) {
          //we don't need to do anything
        } else if (text.startsWith(WRITING_PREFIX)) {
          outputs.add(text.substring(WRITING_PREFIX.length()));
        } else if (text.startsWith(COMPILED_PREFIX)) {
          for (String output : outputs) {
            try {
              outputConsumer.registerOutputFile(chunk.representativeTarget(),
                  new File(output),
                  Collections.singleton(text.substring(COMPILED_PREFIX.length())));
            } catch (IOException e) {
              context.processMessage(new BuildMessage(e.getMessage(), BuildMessage.Kind.ERROR) {});
            }
          }
          outputs.clear();
        }
      }
    });

    handler.startNotify();
    handler.waitFor();
    if (process.exitValue() != 0) {
      context.processMessage(new CompilerMessage(COMPILER_NAME, BuildMessage.Kind.ERROR, "Clojure compiler returned code " + process.exitValue()));
    }

    return ExitCode.OK;
  }

  @Override
  public List<String> getCompilableFileExtensions() {
    return Arrays.asList("clj");
  }

  private static boolean hasGenClass(File file) throws IOException {
    return new String(FileUtilRt.loadFileText(file)).contains("gen-class");
  }

  private void fillFileWithClojureCompilerParams(List<File> toCompile, HashMap<File, String> toCompileNamespace,
                                                 File fileWithCompileScript, File outputDir) throws FileNotFoundException {
    PrintStream printer = new PrintStream(new FileOutputStream(fileWithCompileScript));

    printer.print("(import (java.io File))\n" +
        "(import (java.util HashSet))\n");

    //print output path
    printer.print("(binding [*compile-path* ");
    String outputDirPath = outputDir.getAbsolutePath().replace("\\", "\\\\");
    printer.print("\"" + outputDirPath + "\" *compile-files* true]\n");

    for (File file : toCompile) {
      //collecting current outputs in output directory
      printer.print(
              "(def outputDir \"" + outputDirPath + "\")\n" +
              "(def output (new HashSet))\n" +
              "(def outputFile (new File outputDir))\n" +
              "(defn scanOutput [#^HashSet out #^File file]\n" +
              "  (if (.isDirectory file)\n" +
              "    (doseq [i (.listFiles file)] (scanOutput out i))\n" +
              "    (.add out (.getAbsolutePath file))))\n" +
              "\n" +
              "(scanOutput output outputFile)\n");

      printer.print("(try ");
      String absolutePath = file.getAbsolutePath().replace("\\", "\\\\");
      printer.print("(. *err* println ");
      printer.print("\"" + COMPILING_PREFIX + absolutePath + "\"");
      printer.print(")\n");

      printer.print("(load-file \"");
      printer.print(absolutePath);
      printer.print("\")\n");

      printer.print("(catch Exception e (. *err* println (str \"" + ERROR_PREFIX + absolutePath +
          "@@" + "\" (let [msg (.getMessage e)] msg)  ) ) )");
      printer.print(")\n");

      //we need to compile namespace init class, otherwise we will get CNFE on Runtime
      String namespace = toCompileNamespace.get(file);
      if (namespace != null) {
        printer.print("(try ");
        printer.print("(compile \'");
        printer.print(namespace);
        printer.print(")\n");
        printer.print("(catch Exception e ())"); //all compile error should be found in file compilation
        printer.print(")\n");
      }

      //let's print information about all new created files in output directory
      printer.print("(defn printNewFiles [#^File file]\n" +
          "  (if (.isDirectory file)\n" +
          "    (doseq [i (.listFiles file)] (printNewFiles i))\n" +
          "    (if (not (.contains output (.getAbsolutePath file))) (. *err* println " +
          "(.concat \"" + WRITING_PREFIX + "\" (.getAbsolutePath file))))))\n" +
          "(printNewFiles outputFile)\n" +
          "(.clear output)");

      printer.print("(. *err* println ");
      printer.print("\"" + COMPILED_PREFIX + absolutePath + "\"");
      printer.print(")\n");
    }

    printer.print(")");
    printer.close();
  }

  @NotNull
  @Override
  public String getPresentableName() {
    return COMPILER_NAME;
  }
}
TOP

Related Classes of org.jetbrains.jps.clojure.build.ClojureBuilder

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.