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 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);
    }
  }