public static <R extends BuildRootDescriptor, T extends BuildTarget<R>>
  Map<T, Set<File>> cleanOutputsCorrespondingToChangedFiles(final CompileContext context, DirtyFilesHolder<R, T> dirtyFilesHolder) throws ProjectBuildException {
    final BuildDataManager dataManager = context.getProjectDescriptor().dataManager;
    try {
      final Map<T, Set<File>> cleanedSources = new HashMap<>();

      Set<File> dirsToDelete = FileCollectionFactory.createCanonicalFileSet();
      final Collection<String> deletedPaths = new ArrayList<>();

      dirtyFilesHolder.processDirtyFiles(new FileProcessor<R, T>() {
        private final Map<T, SourceToOutputMapping> mappingsCache = new HashMap<>(); // cache the mapping locally
        private final Object2IntMap<T> idsCache = new Object2IntOpenHashMap<>();

        @Override
        public boolean apply(@NotNull T target, @NotNull File file, @NotNull R sourceRoot) throws IOException {
          SourceToOutputMapping srcToOut = mappingsCache.get(target);
          if (srcToOut == null) {
            srcToOut = dataManager.getSourceToOutputMap(target);
            mappingsCache.put(target, srcToOut);
          }
          final int targetId;
          if (!idsCache.containsKey(target)) {
            targetId = dataManager.getTargetsState().getBuildTargetId(target);
            idsCache.put(target, targetId);
          }
          else {
            targetId = idsCache.getInt(target);
          }
          final String srcPath = file.getPath();
          final Collection<String> outputs = srcToOut.getOutputs(srcPath);
          if (outputs != null) {
            final boolean shouldPruneOutputDirs = target instanceof ModuleBasedTarget;
            final List<String> deletedForThisSource = new ArrayList<>(outputs.size());
            for (String output : outputs) {
              deleteRecursively(output, deletedForThisSource, shouldPruneOutputDirs ? dirsToDelete : null);
            }
            deletedPaths.addAll(deletedForThisSource);
            dataManager.getOutputToTargetRegistry().removeMapping(deletedForThisSource, targetId);
            Set<File> cleaned = cleanedSources.get(target);
            if (cleaned == null) {
              cleaned = FileCollectionFactory.createCanonicalFileSet();
              cleanedSources.put(target, cleaned);
            }
            cleaned.add(file);
          }
          return true;
        }

      });

      if (JavaBuilderUtil.isCompileJavaIncrementally(context)) {
        final ProjectBuilderLogger logger = context.getLoggingManager().getProjectBuilderLogger();
        if (logger.isEnabled()) {
          logger.logDeletedFiles(deletedPaths);
        }
      }

      if (!deletedPaths.isEmpty()) {
        context.processMessage(new FileDeletedEvent(deletedPaths));
      }
      // attempting to delete potentially empty directories
      FSOperations.pruneEmptyDirs(context, dirsToDelete);

      return cleanedSources;
    }
    catch (Exception e) {
      throw new ProjectBuildException(e);
    }
  }
public static <R extends BuildRootDescriptor, T extends BuildTarget<R>>
  Map<T, Set<File>> cleanOutputsCorrespondingToChangedFiles(final CompileContext context, DirtyFilesHolder<R, T> dirtyFilesHolder)