@Nullable
  public BuildRootDescriptor findRootDescriptor(
      final String rootId, final BuildRootIndex rootIndex) {
    for (BuildRootDescriptor descriptor : rootIndex.getTargetRoots(this, null)) {
      if (descriptor.getRootId().equals(rootId)) {
        return descriptor;
      }
    }

    return null;
  }
  private void applyFSEvent(
      ProjectDescriptor pd, @Nullable CmdlineRemoteProto.Message.ControllerMessage.FSEvent event)
      throws IOException {
    if (event == null) {
      return;
    }

    final Timestamps timestamps = pd.timestamps.getStorage();

    for (String deleted : event.getDeletedPathsList()) {
      final File file = new File(deleted);
      Collection<BuildRootDescriptor> descriptor =
          pd.getBuildRootIndex().findAllParentDescriptors(file, null, null);
      if (!descriptor.isEmpty()) {
        if (Utils.IS_TEST_MODE) {
          LOG.info("Applying deleted path from fs event: " + file.getPath());
        }
        for (BuildRootDescriptor rootDescriptor : descriptor) {
          pd.fsState.registerDeleted(rootDescriptor.getTarget(), file, timestamps);
        }
      } else if (Utils.IS_TEST_MODE) {
        LOG.info("Skipping deleted path: " + file.getPath());
      }
    }
    for (String changed : event.getChangedPathsList()) {
      final File file = new File(changed);
      Collection<BuildRootDescriptor> descriptors =
          pd.getBuildRootIndex().findAllParentDescriptors(file, null, null);
      if (!descriptors.isEmpty()) {
        if (Utils.IS_TEST_MODE) {
          LOG.info("Applying dirty path from fs event: " + file.getPath());
        }
        for (BuildRootDescriptor descriptor : descriptors) {
          pd.fsState.markDirty(null, file, descriptor, timestamps);
        }
      } else if (Utils.IS_TEST_MODE) {
        LOG.info("Skipping dirty path: " + file.getPath());
      }
    }
  }
  static void markDirtyFiles(
      CompileContext context,
      BuildTarget<?> target,
      final CompilationRound round,
      Timestamps timestamps,
      boolean forceMarkDirty,
      @Nullable THashSet<File> currentFiles,
      @Nullable FileFilter filter)
      throws IOException {
    if (filter == null && forceMarkDirty) {
      addCompletelyMarkedDirtyTarget(context, target);
    }

    for (BuildRootDescriptor rd :
        context.getProjectDescriptor().getBuildRootIndex().getTargetRoots(target, context)) {
      if (!rd.getRootFile().exists()
          ||
          // temp roots are managed by compilers themselves
          (rd instanceof JavaSourceRootDescriptor && ((JavaSourceRootDescriptor) rd).isTemp)) {
        continue;
      }
      if (filter == null) {
        context.getProjectDescriptor().fsState.clearRecompile(rd);
      }
      final FSCache fsCache =
          rd.canUseFileCache() ? context.getProjectDescriptor().getFSCache() : FSCache.NO_CACHE;
      traverseRecursively(
          context,
          rd,
          round,
          rd.getRootFile(),
          timestamps,
          forceMarkDirty,
          currentFiles,
          filter,
          fsCache);
    }
  }
 private static void traverseRecursively(
     CompileContext context,
     final BuildRootDescriptor rd,
     final CompilationRound round,
     final File file,
     @NotNull final Timestamps tsStorage,
     final boolean forceDirty,
     @Nullable Set<File> currentFiles,
     @Nullable FileFilter filter,
     @NotNull FSCache fsCache)
     throws IOException {
   BuildRootIndex rootIndex = context.getProjectDescriptor().getBuildRootIndex();
   final File[] children = fsCache.getChildren(file);
   if (children != null) { // is directory
     if (children.length > 0 && rootIndex.isDirectoryAccepted(file, rd)) {
       for (File child : children) {
         traverseRecursively(
             context, rd, round, child, tsStorage, forceDirty, currentFiles, filter, fsCache);
       }
     }
   } else { // is file
     if (rootIndex.isFileAccepted(file, rd) && (filter == null || filter.accept(file))) {
       boolean markDirty = forceDirty;
       if (!markDirty) {
         markDirty = tsStorage.getStamp(file, rd.getTarget()) != FileSystemUtil.lastModified(file);
       }
       if (markDirty) {
         // if it is full project rebuild, all storages are already completely cleared;
         // so passing null because there is no need to access the storage to clear non-existing
         // data
         final Timestamps marker = context.isProjectRebuild() ? null : tsStorage;
         context.getProjectDescriptor().fsState.markDirty(context, round, file, rd, marker);
       }
       if (currentFiles != null) {
         currentFiles.add(file);
       }
     }
   }
 }