private static void loadCommonJavacOptions(CompileContext context) {
    final List<String> options = new ArrayList<String>();
    final List<String> vmOptions = new ArrayList<String>();

    final JpsProject project = context.getProjectDescriptor().getProject();
    final JpsJavaCompilerConfiguration compilerConfig =
        JpsJavaExtensionService.getInstance().getOrCreateCompilerConfiguration(project);
    final JpsJavaCompilerOptions compilerOptions = compilerConfig.getCurrentCompilerOptions();
    if (compilerOptions.DEBUGGING_INFO) {
      options.add("-g");
    }
    if (compilerOptions.DEPRECATION) {
      options.add("-deprecation");
    }
    if (compilerOptions.GENERATE_NO_WARNINGS) {
      options.add("-nowarn");
    }
    if (compilerOptions instanceof EclipseCompilerOptions) {
      final EclipseCompilerOptions eclipseOptions = (EclipseCompilerOptions) compilerOptions;
      if (eclipseOptions.PROCEED_ON_ERROR) {
        options.add("-proceedOnError");
      }
    }
    final String customArgs = compilerOptions.ADDITIONAL_OPTIONS_STRING;
    if (customArgs != null) {
      final StringTokenizer customOptsTokenizer = new StringTokenizer(customArgs, " \t\r\n");
      boolean skip = false;
      while (customOptsTokenizer.hasMoreTokens()) {
        final String userOption = customOptsTokenizer.nextToken();
        if (FILTERED_OPTIONS.contains(userOption)) {
          skip = true;
          continue;
        }
        if (!skip) {
          if (!FILTERED_SINGLE_OPTIONS.contains(userOption)) {
            if (userOption.startsWith("-J-")) {
              vmOptions.add(userOption.substring("-J".length()));
            } else {
              options.add(userOption);
            }
          }
        }
      }
    }

    if (useEclipseCompiler(context)) {
      for (String option : options) {
        if (option.startsWith("-proceedOnError")) {
          Utils.PROCEED_ON_ERROR_KEY.set(context, Boolean.TRUE);
          break;
        }
      }
    }

    JAVAC_OPTIONS.set(context, options);
    JAVAC_VM_OPTIONS.set(context, vmOptions);
  }
  private boolean updateDependencies(
      CompileContext context,
      ModuleChunk chunk,
      List<File> toCompile,
      String moduleOutputPath,
      List<GroovycOSProcessHandler.OutputItem> successfullyCompiled)
      throws IOException {
    final Mappings delta = context.createDelta();
    final List<File> successfullyCompiledFiles = new ArrayList<File>();
    if (!successfullyCompiled.isEmpty()) {

      final Callbacks.Backend callback = delta.getCallback();
      final FileGeneratedEvent generatedEvent = new FileGeneratedEvent();

      for (GroovycOSProcessHandler.OutputItem item : successfullyCompiled) {
        final String sourcePath = FileUtil.toSystemIndependentName(item.sourcePath);
        final String outputPath = FileUtil.toSystemIndependentName(item.outputPath);
        final RootDescriptor moduleAndRoot = context.getModuleAndRoot(new File(sourcePath));
        if (moduleAndRoot != null) {
          final String moduleName = moduleAndRoot.module.getName().toLowerCase(Locale.US);
          context
              .getDataManager()
              .getSourceToOutputMap(moduleName, moduleAndRoot.isTestRoot)
              .appendData(sourcePath, outputPath);
        }
        callback.associate(
            outputPath,
            Callbacks.getDefaultLookup(sourcePath),
            new ClassReader(FileUtil.loadFileBytes(new File(outputPath))));
        successfullyCompiledFiles.add(new File(sourcePath));

        generatedEvent.add(
            moduleOutputPath, FileUtil.getRelativePath(moduleOutputPath, outputPath, '/'));
      }

      context.processMessage(generatedEvent);
    }

    return updateMappings(context, delta, chunk, toCompile, successfullyCompiledFiles);
  }
  private static List<String> createCommandLine(
      CompileContext context,
      ModuleChunk chunk,
      List<File> srcFiles,
      String mainOutputDir,
      @Nullable ProcessorConfigProfile profile,
      GreclipseSettings settings) {
    final List<String> args = new ArrayList<String>();

    args.add("-cp");
    args.add(getClasspathString(chunk));

    JavaBuilder.addCompilationOptions(args, context, chunk, profile);

    args.add("-d");
    args.add(mainOutputDir);

    // todo AjCompilerSettings exact duplicate, JavaBuilder.loadCommonJavacOptions inexact duplicate
    List<String> params = ParametersListUtil.parse(settings.cmdLineParams);
    for (Iterator<String> iterator = params.iterator(); iterator.hasNext(); ) {
      String option = iterator.next();
      if ("-target".equals(option)) {
        iterator.next();
        continue;
      } else if (option.isEmpty() || "-g".equals(option) || "-verbose".equals(option)) {
        continue;
      }
      args.add(option);
    }

    if (settings.debugInfo) {
      args.add("-g");
    }

    for (File file : srcFiles) {
      args.add(file.getPath());
    }

    return args;
  }
  public ModuleLevelBuilder.ExitCode build(final CompileContext context, ModuleChunk chunk)
      throws ProjectBuildException {
    try {
      final List<File> toCompile = collectChangedFiles(context, chunk);
      if (toCompile.isEmpty()) {
        return ExitCode.NOTHING_DONE;
      }

      String moduleOutput = getModuleOutput(context, chunk);
      String compilerOutput = getCompilerOutput(moduleOutput);

      final Set<String> toCompilePaths = new LinkedHashSet<String>();
      for (File file : toCompile) {
        toCompilePaths.add(FileUtil.toSystemIndependentName(file.getPath()));
      }

      Map<String, String> class2Src =
          buildClassToSourceMap(chunk, context, toCompilePaths, moduleOutput);

      String ideCharset = chunk.getProject().getProjectCharset();
      String encoding =
          !Comparing.equal(CharsetToolkit.getDefaultSystemCharset().name(), ideCharset)
              ? ideCharset
              : null;
      List<String> patchers = Collections.emptyList(); // todo patchers
      final File tempFile =
          GroovycOSProcessHandler.fillFileWithGroovycParameters(
              compilerOutput,
              toCompilePaths,
              FileUtil.toSystemDependentName(moduleOutput),
              class2Src,
              encoding,
              patchers);

      // todo different outputs in a chunk
      // todo xmx
      final List<String> cmd =
          ExternalProcessUtil.buildJavaCommandLine(
              getJavaExecutable(chunk),
              "org.jetbrains.groovy.compiler.rt.GroovycRunner",
              Collections.<String>emptyList(),
              new ArrayList<String>(generateClasspath(context, chunk)),
              Arrays.asList(
                  "-Xmx384m",
                  "-Dfile.encoding=" + CharsetToolkit.getDefaultSystemCharset().name() /*,
                      "-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5239"*/),
              Arrays.<String>asList(myForStubs ? "stubs" : "groovyc", tempFile.getPath()));

      final Process process = Runtime.getRuntime().exec(ArrayUtil.toStringArray(cmd));
      GroovycOSProcessHandler handler =
          GroovycOSProcessHandler.runGroovyc(
              process,
              new Consumer<String>() {
                public void consume(String s) {
                  context.processMessage(new ProgressMessage(s));
                }
              });

      if (handler.shouldRetry()) {
        if (CHUNK_REBUILD_ORDERED.get(context) != null) {
          CHUNK_REBUILD_ORDERED.set(context, null);
        } else {
          CHUNK_REBUILD_ORDERED.set(context, Boolean.TRUE);
          return ExitCode.CHUNK_REBUILD_REQUIRED;
        }
      }

      if (myForStubs) {
        JavaBuilder.addTempSourcePathRoot(context, new File(compilerOutput));
      }

      for (CompilerMessage message : handler.getCompilerMessages()) {
        context.processMessage(message);
      }
      if (!myForStubs
          && updateDependencies(
              context, chunk, toCompile, moduleOutput, handler.getSuccessfullyCompiled())) {
        return ExitCode.ADDITIONAL_PASS_REQUIRED;
      }
      return ExitCode.OK;
    } catch (Exception e) {
      throw new ProjectBuildException(e);
    }
  }
  public ModuleLevelBuilder.ExitCode build(
      final CompileContext context,
      final ModuleChunk chunk,
      DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder,
      OutputConsumer outputConsumer)
      throws ProjectBuildException {
    if (GreclipseBuilder.useGreclipse(context)) return ExitCode.NOTHING_DONE;

    long start = 0;
    try {
      JpsGroovySettings settings =
          JpsGroovySettings.getSettings(context.getProjectDescriptor().getProject());

      Ref<Boolean> hasStubExcludes = Ref.create(false);
      final List<File> toCompile =
          collectChangedFiles(context, dirtyFilesHolder, myForStubs, false, hasStubExcludes);
      if (toCompile.isEmpty()) {
        return hasFilesToCompileForNextRound(context)
            ? ExitCode.ADDITIONAL_PASS_REQUIRED
            : ExitCode.NOTHING_DONE;
      }
      if (Utils.IS_TEST_MODE || LOG.isDebugEnabled()) {
        LOG.info("forStubs=" + myForStubs);
      }

      Map<ModuleBuildTarget, String> finalOutputs = getCanonicalModuleOutputs(context, chunk, this);
      if (finalOutputs == null) {
        return ExitCode.ABORT;
      }

      start = System.currentTimeMillis();

      Map<ModuleBuildTarget, String> generationOutputs =
          myForStubs ? getStubGenerationOutputs(chunk, context) : finalOutputs;
      String compilerOutput = generationOutputs.get(chunk.representativeTarget());

      GroovycOutputParser parser =
          runGroovycOrContinuation(
              context,
              chunk,
              settings,
              finalOutputs,
              compilerOutput,
              toCompile,
              hasStubExcludes.get());

      Map<ModuleBuildTarget, Collection<GroovycOutputParser.OutputItem>> compiled =
          processCompiledFiles(
              context, chunk, generationOutputs, compilerOutput, parser.getSuccessfullyCompiled());

      if (checkChunkRebuildNeeded(context, parser)) {
        clearContinuation(context, chunk);
        return ExitCode.CHUNK_REBUILD_REQUIRED;
      }

      if (myForStubs) {
        addStubRootsToJavacSourcePath(context, generationOutputs);
        rememberStubSources(context, compiled);
      }

      for (CompilerMessage message : parser.getCompilerMessages()) {
        context.processMessage(message);
      }

      if (!myForStubs) {
        updateDependencies(context, toCompile, compiled, outputConsumer, this);
      }
      return hasFilesToCompileForNextRound(context)
          ? ExitCode.ADDITIONAL_PASS_REQUIRED
          : ExitCode.OK;
    } catch (Exception e) {
      throw new ProjectBuildException(e);
    } finally {
      if (start > 0 && LOG.isDebugEnabled()) {
        LOG.debug(
            myBuilderName
                + " took "
                + (System.currentTimeMillis() - start)
                + " on "
                + chunk.getName());
      }
      if (!myForStubs) {
        FILES_MARKED_DIRTY_FOR_NEXT_ROUND.set(context, null);
      }
    }
  }
  @NotNull
  private GroovycOutputParser runGroovycOrContinuation(
      CompileContext context,
      ModuleChunk chunk,
      JpsGroovySettings settings,
      Map<ModuleBuildTarget, String> finalOutputs,
      String compilerOutput,
      List<File> toCompile,
      boolean hasStubExcludes)
      throws Exception {
    GroovycContinuation continuation = takeContinuation(context, chunk);
    if (continuation != null) {
      if (Utils.IS_TEST_MODE || LOG.isDebugEnabled()) {
        LOG.info("using continuation for " + chunk);
      }
      return continuation.continueCompilation();
    }

    final Set<String> toCompilePaths = getPathsToCompile(toCompile);

    JpsSdk<JpsDummyElement> jdk = getJdk(chunk);
    String version = jdk == null ? SystemInfo.JAVA_RUNTIME_VERSION : jdk.getVersionString();
    boolean inProcess = "true".equals(System.getProperty("groovyc.in.process", "true"));
    boolean mayDependOnUtilJar =
        version != null && StringUtil.compareVersionNumbers(version, "1.6") >= 0;
    boolean optimizeClassLoading =
        !inProcess
            && mayDependOnUtilJar
            && ourOptimizeThreshold != 0
            && toCompilePaths.size() >= ourOptimizeThreshold;

    Map<String, String> class2Src =
        buildClassToSourceMap(chunk, context, toCompilePaths, finalOutputs);

    final String encoding =
        context
            .getProjectDescriptor()
            .getEncodingConfiguration()
            .getPreferredModuleChunkEncoding(chunk);
    List<String> patchers = new ArrayList<String>();

    for (GroovyBuilderExtension extension :
        JpsServiceManager.getInstance().getExtensions(GroovyBuilderExtension.class)) {
      patchers.addAll(extension.getCompilationUnitPatchers(context, chunk));
    }

    Collection<String> classpath = generateClasspath(context, chunk);
    if (LOG.isDebugEnabled()) {
      LOG.debug("Optimized class loading: " + optimizeClassLoading);
      LOG.debug("Groovyc classpath: " + classpath);
    }

    final File tempFile =
        GroovycOutputParser.fillFileWithGroovycParameters(
            compilerOutput,
            toCompilePaths,
            finalOutputs.values(),
            class2Src,
            encoding,
            patchers,
            optimizeClassLoading ? StringUtil.join(classpath, File.pathSeparator) : "");
    GroovycFlavor groovyc =
        inProcess
            ? new InProcessGroovyc(finalOutputs.values(), hasStubExcludes)
            : new ForkedGroovyc(optimizeClassLoading, chunk);

    GroovycOutputParser parser = new GroovycOutputParser(chunk, context);

    continuation = groovyc.runGroovyc(classpath, myForStubs, settings, tempFile, parser);
    setContinuation(context, chunk, continuation);
    return parser;
  }
  public static void addCompilationOptions(
      List<String> options,
      CompileContext context,
      ModuleChunk chunk,
      @Nullable ProcessorConfigProfile profile) {
    if (!isEncodingSet(options)) {
      final CompilerEncodingConfiguration config =
          context.getProjectDescriptor().getEncodingConfiguration();
      final String encoding = config.getPreferredModuleChunkEncoding(chunk);
      if (config.getAllModuleChunkEncodings(chunk).size() > 1) {
        final StringBuilder msgBuilder = new StringBuilder();
        msgBuilder.append("Multiple encodings set for module chunk ").append(chunk.getName());
        if (encoding != null) {
          msgBuilder.append("\n\"").append(encoding).append("\" will be used by compiler");
        }
        context.processMessage(
            new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.INFO, msgBuilder.toString()));
      }
      if (!StringUtil.isEmpty(encoding)) {
        options.add("-encoding");
        options.add(encoding);
      }
    }

    final String langLevel = getLanguageLevel(chunk.getModules().iterator().next());
    if (!StringUtil.isEmpty(langLevel)) {
      options.add("-source");
      options.add(langLevel);
    }

    JpsJavaCompilerConfiguration compilerConfiguration =
        JpsJavaExtensionService.getInstance()
            .getOrCreateCompilerConfiguration(context.getProjectDescriptor().getProject());
    String bytecodeTarget = null;
    int chunkSdkVersion = -1;
    for (JpsModule module : chunk.getModules()) {
      final JpsSdk<JpsDummyElement> sdk = module.getSdk(JpsJavaSdkType.INSTANCE);
      if (sdk != null) {
        final int moduleSdkVersion = convertToNumber(sdk.getVersionString());
        if (moduleSdkVersion != 0 /*could determine the version*/
            && (chunkSdkVersion < 0 || chunkSdkVersion > moduleSdkVersion)) {
          chunkSdkVersion = moduleSdkVersion;
        }
      }

      final String moduleTarget = compilerConfiguration.getByteCodeTargetLevel(module.getName());
      if (moduleTarget == null) {
        continue;
      }
      if (bytecodeTarget == null) {
        bytecodeTarget = moduleTarget;
      } else {
        if (moduleTarget.compareTo(bytecodeTarget) < 0) {
          bytecodeTarget =
              moduleTarget; // use the lower possible target among modules that form the chunk
        }
      }
    }
    if (bytecodeTarget != null) {
      options.add("-target");
      options.add(bytecodeTarget);
    } else {
      if (chunkSdkVersion > 0 && getCompilerSdkVersion(context) > chunkSdkVersion) {
        // force lower bytecode target level to match the version of sdk assigned to this chunk
        options.add("-target");
        options.add("1." + chunkSdkVersion);
      }
    }

    if (profile != null && profile.isEnabled()) {
      // configuring annotation processing
      if (!profile.isObtainProcessorsFromClasspath()) {
        final String processorsPath = profile.getProcessorPath();
        options.add("-processorpath");
        options.add(
            processorsPath == null ? "" : FileUtil.toSystemDependentName(processorsPath.trim()));
      }

      final Set<String> processors = profile.getProcessors();
      if (!processors.isEmpty()) {
        options.add("-processor");
        options.add(StringUtil.join(processors, ","));
      }

      for (Map.Entry<String, String> optionEntry : profile.getProcessorOptions().entrySet()) {
        options.add("-A" + optionEntry.getKey() + "=" + optionEntry.getValue());
      }

      final File srcOutput =
          ProjectPaths.getAnnotationProcessorGeneratedSourcesOutputDir(
              chunk.getModules().iterator().next(), chunk.containsTests(), profile);
      if (srcOutput != null) {
        srcOutput.mkdirs();
        options.add("-s");
        options.add(srcOutput.getPath());
      }
    } else {
      options.add("-proc:none");
    }
  }
 public static void registerClassPostProcessor(ClassPostProcessor processor) {
   ourClassProcessors.add(processor);
 }
  @Override
  public ExitCode build(
      final CompileContext context,
      ModuleChunk chunk,
      DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder,
      OutputConsumer outputConsumer)
      throws ProjectBuildException, IOException {
    if (!useGreclipse(context)) return ModuleLevelBuilder.ExitCode.NOTHING_DONE;

    try {
      final List<File> toCompile =
          GroovyBuilder.collectChangedFiles(
              context, dirtyFilesHolder, false, true, Ref.create(false));
      if (toCompile.isEmpty()) {
        return ExitCode.NOTHING_DONE;
      }

      Map<ModuleBuildTarget, String> outputDirs =
          GroovyBuilder.getCanonicalModuleOutputs(context, chunk, this);
      if (outputDirs == null) {
        return ExitCode.ABORT;
      }

      JpsProject project = context.getProjectDescriptor().getProject();
      GreclipseSettings greclipseSettings = GreclipseJpsCompilerSettings.getSettings(project);
      if (greclipseSettings == null) {
        String message = "Compiler settings component not initialized for " + project;
        LOG.error(message);
        context.processMessage(
            new CompilerMessage(getPresentableName(), BuildMessage.Kind.ERROR, message));
        return ExitCode.ABORT;
      }

      ClassLoader loader = createGreclipseLoader(greclipseSettings.greclipsePath);
      if (loader == null) {
        context.processMessage(
            new CompilerMessage(
                getPresentableName(),
                BuildMessage.Kind.ERROR,
                "Invalid jar path in the compiler settings: '"
                    + greclipseSettings.greclipsePath
                    + "'"));
        return ExitCode.ABORT;
      }

      final JpsJavaExtensionService javaExt = JpsJavaExtensionService.getInstance();
      final JpsJavaCompilerConfiguration compilerConfig = javaExt.getCompilerConfiguration(project);
      assert compilerConfig != null;

      final Set<JpsModule> modules = chunk.getModules();
      ProcessorConfigProfile profile = null;
      if (modules.size() == 1) {
        profile = compilerConfig.getAnnotationProcessingProfile(modules.iterator().next());
      } else {
        String message = JavaBuilder.validateCycle(chunk, javaExt, compilerConfig, modules);
        if (message != null) {
          context.processMessage(
              new CompilerMessage(getPresentableName(), BuildMessage.Kind.ERROR, message));
          return ExitCode.ABORT;
        }
      }

      String mainOutputDir = outputDirs.get(chunk.representativeTarget());
      final List<String> args =
          createCommandLine(context, chunk, toCompile, mainOutputDir, profile, greclipseSettings);

      if (Utils.IS_TEST_MODE || LOG.isDebugEnabled()) {
        LOG.debug("Compiling with args: " + args);
      }

      Boolean notified = COMPILER_VERSION_INFO.get(context);
      if (notified != Boolean.TRUE) {
        context.processMessage(
            new CompilerMessage(
                "",
                BuildMessage.Kind.INFO,
                "Using Groovy-Eclipse to compile Java & Groovy sources"));
        COMPILER_VERSION_INFO.set(context, Boolean.TRUE);
      }

      context.processMessage(
          new ProgressMessage("Compiling java & groovy [" + chunk.getPresentableShortName() + "]"));

      StringWriter out = new StringWriter();
      StringWriter err = new StringWriter();
      HashMap<String, List<String>> outputMap = ContainerUtil.newHashMap();

      boolean success = performCompilation(args, out, err, outputMap, context, chunk);

      List<GroovycOutputParser.OutputItem> items = ContainerUtil.newArrayList();
      for (String src : outputMap.keySet()) {
        //noinspection ConstantConditions
        for (String classFile : outputMap.get(src)) {
          items.add(
              new GroovycOutputParser.OutputItem(
                  FileUtil.toSystemIndependentName(mainOutputDir + classFile),
                  FileUtil.toSystemIndependentName(src)));
        }
      }
      Map<ModuleBuildTarget, Collection<GroovycOutputParser.OutputItem>> successfullyCompiled =
          GroovyBuilder.processCompiledFiles(context, chunk, outputDirs, mainOutputDir, items);

      EclipseOutputParser parser = new EclipseOutputParser(getPresentableName(), chunk);
      List<CompilerMessage> messages =
          ContainerUtil.concat(
              parser.parseMessages(out.toString()), parser.parseMessages(err.toString()));
      boolean hasError = false;
      for (CompilerMessage message : messages) {
        if (message.getKind() == BuildMessage.Kind.ERROR) {
          hasError = true;
        }
        context.processMessage(message);
      }

      if (!success && !hasError) {
        context.processMessage(
            new CompilerMessage(
                getPresentableName(), BuildMessage.Kind.ERROR, "Compilation failed"));
      }

      GroovyBuilder.updateDependencies(
          context, toCompile, successfullyCompiled, outputConsumer, this);
      return ExitCode.OK;

    } catch (Exception e) {
      throw new ProjectBuildException(e);
    }
  }
  public Builder.ExitCode build(final CompileContext context, ModuleChunk chunk)
      throws ProjectBuildException {
    ExitCode exitCode = ExitCode.OK;
    final Map<File, Module> toCompile = new HashMap<File, Module>();
    try {
      context.processFilesToRecompile(
          chunk,
          new FileProcessor() {
            @Override
            public boolean apply(Module module, File file, String sourceRoot) throws Exception {
              final String path = file.getPath();
              if (isGroovyFile(path)) { // todo file type check
                toCompile.put(file, module);
              }
              return true;
            }
          });

      if (toCompile.isEmpty()) {
        return exitCode;
      }

      final Set<String> cp = new LinkedHashSet<String>();
      // groovy_rt.jar
      // IMPORTANT! must be the first in classpath
      cp.add(ClasspathBootstrap.getResourcePath(GroovyCompilerWrapper.class).getPath());

      for (File file :
          context
              .getProjectPaths()
              .getClasspathFiles(chunk, ClasspathKind.compile(context.isCompilingTests()), false)) {
        cp.add(FileUtil.toCanonicalPath(file.getPath()));
      }
      for (File file :
          context
              .getProjectPaths()
              .getClasspathFiles(chunk, ClasspathKind.runtime(context.isCompilingTests()), false)) {
        cp.add(FileUtil.toCanonicalPath(file.getPath()));
      }

      final File tempFile = FileUtil.createTempFile("ideaGroovyToCompile", ".txt", true);
      final Module representativeModule = chunk.getModules().iterator().next();
      File moduleOutputDir =
          context
              .getProjectPaths()
              .getModuleOutputDir(representativeModule, context.isCompilingTests());
      final File dir =
          myForStubs
              ? FileUtil.createTempDirectory(/*new File("/tmp/stubs/"), */ "groovyStubs", null)
              : moduleOutputDir;
      assert dir != null;

      final Set<String> toCompilePaths = new LinkedHashSet<String>();
      for (File file : toCompile.keySet()) {
        toCompilePaths.add(FileUtil.toSystemIndependentName(file.getPath()));
      }

      String moduleOutputPath = FileUtil.toCanonicalPath(moduleOutputDir.getPath());
      if (!moduleOutputPath.endsWith("/")) {
        moduleOutputPath += "/";
      }
      Map<String, String> class2Src =
          buildClassToSourceMap(chunk, context, toCompilePaths, moduleOutputPath);

      String encoding = "UTF-8"; // todo encoding
      List<String> patchers = Collections.emptyList(); // todo patchers
      GroovycOSProcessHandler.fillFileWithGroovycParameters(
          tempFile,
          FileUtil.toCanonicalPath(dir.getPath()),
          toCompilePaths,
          FileUtil.toSystemDependentName(moduleOutputPath),
          class2Src,
          encoding,
          patchers);

      if (myForStubs) {
        JavaBuilder.addTempSourcePathRoot(context, dir);
      }

      // todo CompilerUtil.addLocaleOptions()
      // todo different outputs in a chunk
      // todo module jdk path
      final List<String> cmd =
          ExternalProcessUtil.buildJavaCommandLine(
              SystemProperties.getJavaHome() + "/bin/java",
              "org.jetbrains.groovy.compiler.rt.GroovycRunner",
              Collections.<String>emptyList(),
              new ArrayList<String>(cp),
              Arrays.asList(
                  "-Xmx384m" /*, "-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5858"*/),
              Arrays.<String>asList(myForStubs ? "stubs" : "groovyc", tempFile.getPath()));

      deleteCorrespondingOutputFiles(context, toCompile);

      List<GroovycOSProcessHandler.OutputItem> successfullyCompiled = Collections.emptyList();
      try {
        final Process process = Runtime.getRuntime().exec(cmd.toArray(new String[cmd.size()]));
        GroovycOSProcessHandler handler =
            new GroovycOSProcessHandler(process, null) {
              @Override
              protected void updateStatus(@Nullable String status) {
                context.processMessage(
                    new ProgressMessage(status == null ? GROOVY_COMPILER_IN_OPERATION : status));
              }
            };
        handler.startNotify();
        handler.waitFor();

        successfullyCompiled = handler.getSuccessfullyCompiled();

        final List<CompilerMessage> messages = handler.getCompilerMessages();
        for (CompilerMessage message : messages) {
          context.processMessage(message);
        }

        boolean hasMessages = !messages.isEmpty();

        final StringBuffer unparsedBuffer = handler.getStdErr();
        if (unparsedBuffer.length() != 0) {
          context.processMessage(
              new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.INFO, unparsedBuffer.toString()));
        }

        final int exitValue = handler.getProcess().exitValue();
        if (!hasMessages && exitValue != 0) {
          context.processMessage(
              new CompilerMessage(
                  BUILDER_NAME,
                  BuildMessage.Kind.ERROR,
                  "Internal groovyc error: code " + exitValue));
        }
      } finally {
        if (!myForStubs) {
          final Mappings delta = context.createDelta();
          final List<File> successfullyCompiledFiles = new ArrayList<File>();
          if (!successfullyCompiled.isEmpty()) {
            final Callbacks.Backend callback = delta.getCallback();

            for (GroovycOSProcessHandler.OutputItem item : successfullyCompiled) {
              final String sourcePath = FileUtil.toSystemIndependentName(item.sourcePath);
              final String outputPath = FileUtil.toSystemIndependentName(item.outputPath);
              final RootDescriptor moduleAndRoot = context.getModuleAndRoot(new File(sourcePath));
              if (moduleAndRoot != null) {
                final String moduleName = moduleAndRoot.module.getName().toLowerCase(Locale.US);
                context
                    .getDataManager()
                    .getSourceToOutputMap(moduleName, moduleAndRoot.isTestRoot)
                    .appendData(sourcePath, outputPath);
              }
              callback.associate(
                  outputPath,
                  Callbacks.getDefaultLookup(sourcePath),
                  new ClassReader(FileUtil.loadFileBytes(new File(outputPath))));
              successfullyCompiledFiles.add(new File(sourcePath));
            }
          }

          final boolean needSecondPass =
              updateMappings(context, delta, chunk, toCompile.keySet(), successfullyCompiledFiles);
          if (needSecondPass) {
            exitCode = ExitCode.ADDITIONAL_PASS_REQUIRED;
          }
        }
      }

      return exitCode;
    } catch (Exception e) {
      throw new ProjectBuildException(e);
    }
  }