private static boolean runPngCaching(
      @NotNull CompileContext context,
      @NotNull Module module,
      @NotNull AndroidFileSetStorage storage,
      @Nullable AndroidFileSetState state)
      throws IOException {
    final AndroidFileSetState savedState = storage.getState(module.getName());
    if (context.isMake() && savedState != null && savedState.equalsTo(state)) {
      return true;
    }

    final AndroidFacet facet = AndroidJpsUtil.getFacet(module);
    if (facet == null) {
      return true;
    }

    context.processMessage(
        new CompilerMessage(
            BUILDER_NAME,
            BuildMessage.Kind.INFO,
            AndroidJpsBundle.message("android.jps.progress.res.caching", module.getName())));

    final File resourceDir = AndroidJpsUtil.getResourceDirForCompilationPath(facet);
    if (resourceDir == null) {
      return true;
    }

    final Pair<AndroidSdk, IAndroidTarget> pair =
        AndroidJpsUtil.getAndroidPlatform(module, context, BUILDER_NAME);
    if (pair == null) {
      return false;
    }

    final File resCacheDir = AndroidJpsUtil.getResourcesCacheDir(context, module);

    if (!resCacheDir.exists()) {
      if (!resCacheDir.mkdirs()) {
        context.processMessage(
            new CompilerMessage(
                BUILDER_NAME,
                BuildMessage.Kind.ERROR,
                "Cannot create directory " + resCacheDir.getPath()));
        return false;
      }
    }

    final IAndroidTarget target = pair.second;

    final Map<AndroidCompilerMessageKind, List<String>> messages =
        AndroidApt.crunch(
            target, Collections.singletonList(resourceDir.getPath()), resCacheDir.getPath());

    AndroidJpsUtil.addMessages(context, messages, BUILDER_NAME);

    final boolean success = messages.get(AndroidCompilerMessageKind.ERROR).isEmpty();
    storage.update(module.getName(), success ? state : null);
    return success;
  }
  public ExitCode build(
      final CompileContext context,
      final ModuleChunk chunk,
      DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder,
      OutputConsumer outputConsumer)
      throws ProjectBuildException {
    if (!IS_ENABLED.get(context, Boolean.TRUE)) {
      return ExitCode.NOTHING_DONE;
    }
    try {
      final Map<File, ModuleBuildTarget> filesToCompile =
          new THashMap<File, ModuleBuildTarget>(FileUtil.FILE_HASHING_STRATEGY);

      dirtyFilesHolder.processDirtyFiles(
          new FileProcessor<JavaSourceRootDescriptor, ModuleBuildTarget>() {
            public boolean apply(
                ModuleBuildTarget target, File file, JavaSourceRootDescriptor descriptor)
                throws IOException {
              if (JAVA_SOURCES_FILTER.accept(file)) {
                filesToCompile.put(file, target);
              }
              return true;
            }
          });

      if (context.isMake()) {
        final ProjectBuilderLogger logger = context.getLoggingManager().getProjectBuilderLogger();
        if (logger.isEnabled()) {
          if (filesToCompile.size() > 0) {
            logger.logCompiledFiles(filesToCompile.keySet(), BUILDER_NAME, "Compiling files:");
          }
        }
      }

      return compile(context, chunk, dirtyFilesHolder, filesToCompile.keySet(), outputConsumer);
    } catch (ProjectBuildException e) {
      throw e;
    } catch (Exception e) {
      String message = e.getMessage();
      if (message == null) {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        final PrintStream stream = new PrintStream(out);
        try {
          e.printStackTrace(stream);
        } finally {
          stream.close();
        }
        message = "Internal error: \n" + out.toString();
      }
      context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, message));
      throw new ProjectBuildException(message, e);
    }
  }
  public static void markDirtyRecursively(CompileContext context, ModuleChunk chunk)
      throws IOException {
    Set<JpsModule> modules = chunk.getModules();
    Set<ModuleBuildTarget> targets = chunk.getTargets();
    final Set<ModuleBuildTarget> dirtyTargets = new HashSet<ModuleBuildTarget>(targets);

    // now mark all modules that depend on dirty modules
    final JpsJavaClasspathKind classpathKind = JpsJavaClasspathKind.compile(chunk.containsTests());
    boolean found = false;
    for (BuildTargetChunk targetChunk :
        context.getProjectDescriptor().getBuildTargetIndex().getSortedTargetChunks()) {
      if (!found) {
        if (targetChunk.getTargets().equals(chunk.getTargets())) {
          found = true;
        }
      } else {
        for (final BuildTarget<?> target : targetChunk.getTargets()) {
          if (target instanceof ModuleBuildTarget) {
            final Set<JpsModule> deps =
                getDependentModulesRecursively(
                    ((ModuleBuildTarget) target).getModule(), classpathKind);
            if (Utils.intersects(deps, modules)) {
              for (BuildTarget<?> buildTarget : targetChunk.getTargets()) {
                if (buildTarget instanceof ModuleBuildTarget) {
                  dirtyTargets.add((ModuleBuildTarget) buildTarget);
                }
              }
              break;
            }
          }
        }
      }
    }

    final Timestamps timestamps = context.getProjectDescriptor().timestamps.getStorage();
    for (ModuleBuildTarget target : dirtyTargets) {
      markDirtyFiles(context, target, timestamps, true, null);
    }

    if (context.isMake()) {
      // mark as non-incremental only the module that triggered non-incremental change
      for (ModuleBuildTarget target : targets) {
        context.markNonIncremental(target);
      }
    }
  }
  private static boolean doPackagingForModule(
      @NotNull CompileContext context,
      @NotNull Module module,
      @NotNull AndroidFileSetStorage apkFileSetStorage,
      @NotNull AndroidApkBuilderConfigStateStorage apkBuilderConfigStateStorage,
      boolean release)
      throws IOException {
    final AndroidFacet facet = AndroidJpsUtil.getFacet(module);
    if (facet == null || facet.isLibrary()) {
      return true;
    }

    final String[] sourceRoots =
        AndroidJpsUtil.toPaths(AndroidJpsUtil.getSourceRootsForModuleAndDependencies(module));
    final ProjectPaths paths = context.getProjectPaths();

    final File outputDir = AndroidJpsUtil.getOutputDirectoryForPackagedFiles(paths, module);
    if (outputDir == null) {
      context.processMessage(
          new CompilerMessage(
              BUILDER_NAME,
              BuildMessage.Kind.ERROR,
              AndroidJpsBundle.message(
                  "android.jps.errors.output.dir.not.specified", module.getName())));
      return false;
    }

    final Pair<AndroidSdk, IAndroidTarget> pair =
        AndroidJpsUtil.getAndroidPlatform(module, context, BUILDER_NAME);
    if (pair == null) {
      return false;
    }

    final Set<String> externalJarsSet = AndroidJpsUtil.getExternalLibraries(paths, module);
    final File resPackage = getPackagedResourcesFile(module, outputDir);

    final File classesDexFile = new File(outputDir.getPath(), AndroidCommonUtils.CLASSES_FILE_NAME);

    final String sdkPath = pair.getFirst().getSdkPath();
    final String outputPath = AndroidJpsUtil.getApkPath(facet, outputDir);
    if (outputPath == null) {
      context.processMessage(
          new CompilerMessage(
              BUILDER_NAME,
              BuildMessage.Kind.ERROR,
              "Cannot compute output path for file " + AndroidJpsUtil.getApkName(module)));
      return false;
    }
    final String customKeyStorePath =
        FileUtil.toSystemDependentName(facet.getCustomDebugKeyStorePath());
    final String[] nativeLibDirs = collectNativeLibsFolders(facet);

    final String resPackagePath =
        release ? resPackage.getPath() + RELEASE_SUFFIX : resPackage.getPath();
    final String outputApkPath = release ? outputPath + UNSIGNED_SUFFIX : outputPath;
    final String classesDexFilePath = classesDexFile.getPath();
    final String[] externalJars = ArrayUtil.toStringArray(externalJarsSet);

    final AndroidFileSetState currentFileSetState =
        buildCurrentApkBuilderState(
            context.getProject(),
            resPackagePath,
            classesDexFilePath,
            nativeLibDirs,
            sourceRoots,
            externalJars,
            release);

    final AndroidApkBuilderConfigState currentApkBuilderConfigState =
        new AndroidApkBuilderConfigState(outputApkPath, customKeyStorePath);

    final AndroidFileSetState savedApkFileSetState = apkFileSetStorage.getState(module.getName());
    final AndroidApkBuilderConfigState savedApkBuilderConfigState =
        apkBuilderConfigStateStorage.getState(module.getName());

    if (context.isMake()
        && currentFileSetState.equalsTo(savedApkFileSetState)
        && currentApkBuilderConfigState.equalsTo(savedApkBuilderConfigState)) {
      return true;
    }
    context.processMessage(
        new ProgressMessage(
            AndroidJpsBundle.message(
                "android.jps.progress.packaging", AndroidJpsUtil.getApkName(module))));

    final Map<AndroidCompilerMessageKind, List<String>> messages =
        AndroidApkBuilder.execute(
            resPackagePath,
            classesDexFilePath,
            sourceRoots,
            externalJars,
            nativeLibDirs,
            outputApkPath,
            release,
            sdkPath,
            customKeyStorePath,
            new MyExcludedSourcesFilter(context.getProject()));

    AndroidJpsUtil.addMessages(context, messages, BUILDER_NAME);
    final boolean success = messages.get(AndroidCompilerMessageKind.ERROR).isEmpty();

    apkFileSetStorage.update(module.getName(), success ? currentFileSetState : null);
    apkBuilderConfigStateStorage.update(
        module.getName(), success ? currentApkBuilderConfigState : null);
    return success;
  }
  private static boolean doResourcePackaging(
      @NotNull CompileContext context,
      @NotNull Collection<Module> modules,
      @NotNull Map<Module, AndroidFileSetState> resourcesStates,
      @NotNull Map<Module, AndroidFileSetState> assetsStates)
      throws IOException {
    boolean success = true;

    final File dataStorageRoot = context.getDataManager().getDataStorageRoot();
    final boolean releaseBuild = AndroidJpsUtil.isReleaseBuild(context);
    AndroidFileSetStorage resourcesStorage = null;
    AndroidFileSetStorage assetsStorage = null;

    try {
      final String resourcesStorageName =
          releaseBuild ? "resources_packaging_release" : "resources_packaging_dev";
      resourcesStorage = new AndroidFileSetStorage(dataStorageRoot, resourcesStorageName);

      final String assetsStorageName =
          releaseBuild ? "assets_packaging_release" : "assets_packaging_dev";
      assetsStorage = new AndroidFileSetStorage(dataStorageRoot, assetsStorageName);

      final Set<Module> modulesToUpdateState = new HashSet<Module>();

      for (Module module : modules) {
        final AndroidFacet facet = AndroidJpsUtil.getFacet(module);
        if (facet == null) {
          continue;
        }

        boolean updateState = true;

        if (!facet.isLibrary()
            && !(context.isMake()
                && checkUpToDate(module, resourcesStates, resourcesStorage)
                && checkUpToDate(module, assetsStates, assetsStorage))) {

          updateState = packageResources(facet, context);

          if (!updateState) {
            success = false;
          }
        }
        if (updateState) {
          modulesToUpdateState.add(module);
        }
      }

      for (Module module : modules) {
        final boolean updateState = modulesToUpdateState.contains(module);
        resourcesStorage.update(module.getName(), updateState ? resourcesStates.get(module) : null);
        assetsStorage.update(module.getName(), updateState ? assetsStates.get(module) : null);
      }
    } finally {
      if (resourcesStorage != null) {
        resourcesStorage.close();
      }

      if (assetsStorage != null) {
        assetsStorage.close();
      }
    }
    return success;
  }