jkorous created this revision.
jkorous added reviewers: arphaman, dexonsmith, akyrtzi, nathawes, yvvan.
Herald added subscribers: cfe-commits, jdoerfert, jfb, mgorny.
Herald added a project: clang.

This patch contains implementation of DirectoryWatcher from 
github.com/apple/swift-clang

We are starting new push to upstream the index-while-building feature in clang 
and this is one of the dependencies.

Original author is David Farler, other contributors are Argyrios Kyrtzidis, 
Thomas Roughton and Alex Lorenz.

Part of this implementation was included in the review below so I am adding 
@tschuett and @yvvan in case they are interested.
https://reviews.llvm.org/D41407


Repository:
  rC Clang

https://reviews.llvm.org/D58418

Files:
  clang/include/clang/DirectoryWatcher/DirectoryWatcher.h
  clang/lib/CMakeLists.txt
  clang/lib/DirectoryWatcher/CMakeLists.txt
  clang/lib/DirectoryWatcher/DirectoryWatcher-linux.inc.h
  clang/lib/DirectoryWatcher/DirectoryWatcher-mac.inc.h
  clang/lib/DirectoryWatcher/DirectoryWatcher.cpp
  clang/unittests/CMakeLists.txt
  clang/unittests/DirectoryWatcher/CMakeLists.txt
  clang/unittests/DirectoryWatcher/DirectoryWatcherTest.cpp

Index: clang/unittests/DirectoryWatcher/DirectoryWatcherTest.cpp
===================================================================
--- /dev/null
+++ clang/unittests/DirectoryWatcher/DirectoryWatcherTest.cpp
@@ -0,0 +1,333 @@
+//===- unittests/DirectoryWatcher/DirectoryWatcherTest.cpp ----------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/DirectoryWatcher/DirectoryWatcher.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Path.h"
+#include "gtest/gtest.h"
+#include <condition_variable>
+#include <mutex>
+#include <thread>
+
+using namespace llvm;
+using namespace llvm::sys;
+using namespace llvm::sys::fs;
+using namespace clang;
+
+namespace {
+
+class EventCollection {
+  SmallVector<DirectoryWatcher::Event, 6> Events;
+
+public:
+  EventCollection() = default;
+  explicit EventCollection(ArrayRef<DirectoryWatcher::Event> events) {
+    append(events);
+  }
+
+  void append(ArrayRef<DirectoryWatcher::Event> events) {
+    Events.append(events.begin(), events.end());
+  }
+
+  bool empty() const { return Events.empty(); }
+  size_t size() const { return Events.size(); }
+  void clear() { Events.clear(); }
+
+  bool hasEvents(ArrayRef<StringRef> filenames,
+                 ArrayRef<DirectoryWatcher::EventKind> kinds,
+                 ArrayRef<file_status> stats) const {
+    assert(filenames.size() == kinds.size());
+    assert(filenames.size() == stats.size());
+    SmallVector<DirectoryWatcher::Event, 6> evts = Events;
+    bool hadError = false;
+    for (unsigned i = 0, e = filenames.size(); i < e; ++i) {
+      StringRef fname = filenames[i];
+      DirectoryWatcher::EventKind kind = kinds[i];
+      file_status stat = stats[i];
+      auto it = std::find_if(evts.begin(), evts.end(),
+                             [&](const DirectoryWatcher::Event &evt) -> bool {
+                               return path::filename(evt.Filename) == fname;
+                             });
+      if (it == evts.end()) {
+        hadError = err(Twine("expected filename '" + fname + "' not found"));
+        continue;
+      }
+      if (it->Kind != kind) {
+        hadError = err(Twine("filename '" + fname + "' has event kind " +
+                             std::to_string((int)it->Kind) + ", expected ") +
+                       std::to_string((int)kind));
+      }
+      if (it->Kind != DirectoryWatcher::EventKind::Removed &&
+          it->ModTime != stat.getLastModificationTime())
+        hadError =
+            err(Twine("filename '" + fname + "' has different mod time"));
+      evts.erase(it);
+    }
+    for (const auto &evt : evts) {
+      hadError = err(Twine("unexpected filename '" +
+                           path::filename(evt.Filename) + "' found"));
+    }
+    return !hadError;
+  }
+
+  bool hasAdded(ArrayRef<StringRef> filenames,
+                ArrayRef<file_status> stats) const {
+    std::vector<DirectoryWatcher::EventKind> kinds{
+        filenames.size(), DirectoryWatcher::EventKind::Added};
+    return hasEvents(filenames, kinds, stats);
+  }
+
+  bool hasRemoved(ArrayRef<StringRef> filenames) const {
+    std::vector<DirectoryWatcher::EventKind> kinds{
+        filenames.size(), DirectoryWatcher::EventKind::Removed};
+    std::vector<file_status> stats{filenames.size(), file_status{}};
+    return hasEvents(filenames, kinds, stats);
+  }
+
+private:
+  bool err(Twine msg) const {
+    SmallString<128> buf;
+    llvm::errs() << msg.toStringRef(buf) << '\n';
+    return true;
+  }
+};
+
+struct EventOccurrence {
+  std::vector<DirectoryWatcher::Event> Events;
+  bool IsInitial;
+};
+
+class DirectoryWatcherTest
+    : public std::enable_shared_from_this<DirectoryWatcherTest> {
+  std::string WatchedDir;
+  std::string TempDir;
+  std::unique_ptr<DirectoryWatcher> DirWatcher;
+
+  std::condition_variable Condition;
+  std::mutex Mutex;
+  std::deque<EventOccurrence> EvtOccurs;
+
+public:
+  void init() {
+    SmallString<128> pathBuf;
+    std::error_code EC = createUniqueDirectory("dirwatcher", pathBuf);
+    ASSERT_FALSE(EC);
+    TempDir = pathBuf.str();
+    path::append(pathBuf, "watch");
+    WatchedDir = pathBuf.str();
+    EC = create_directory(WatchedDir);
+    ASSERT_FALSE(EC);
+  }
+
+  ~DirectoryWatcherTest() {
+    stopWatching();
+    remove_directories(TempDir);
+  }
+
+public:
+  StringRef getWatchedDir() const { return WatchedDir; }
+
+  void addFile(StringRef filename, file_status &stat) {
+    SmallString<128> pathBuf;
+    pathBuf = TempDir;
+    path::append(pathBuf, filename);
+    Expected<file_t> ft =
+        openNativeFileForWrite(pathBuf, CD_CreateNew, OF_None);
+    ASSERT_TRUE((bool)ft);
+    closeFile(*ft);
+
+    SmallString<128> newPath;
+    newPath = WatchedDir;
+    path::append(newPath, filename);
+    std::error_code EC = rename(pathBuf, newPath);
+    ASSERT_FALSE(EC);
+
+    EC = status(newPath, stat);
+    ASSERT_FALSE(EC);
+  }
+
+  void addFiles(ArrayRef<StringRef> filenames,
+                std::vector<file_status> &stats) {
+    for (auto fname : filenames) {
+      file_status stat;
+      addFile(fname, stat);
+      stats.push_back(stat);
+    }
+  }
+
+  void addFiles(ArrayRef<StringRef> filenames) {
+    std::vector<file_status> stats;
+    addFiles(filenames, stats);
+  }
+
+  void removeFile(StringRef filename) {
+    SmallString<128> pathBuf;
+    pathBuf = WatchedDir;
+    path::append(pathBuf, filename);
+    std::error_code EC = remove(pathBuf, /*IgnoreNonExisting=*/false);
+    ASSERT_FALSE(EC);
+  }
+
+  void removeFiles(ArrayRef<StringRef> filenames) {
+    for (auto fname : filenames) {
+      removeFile(fname);
+    }
+  }
+
+  /// \returns true for error.
+  bool startWatching(bool waitInitialSync) {
+    std::weak_ptr<DirectoryWatcherTest> weakThis = shared_from_this();
+    auto receiver = [weakThis](ArrayRef<DirectoryWatcher::Event> events,
+                               bool isInitial) {
+      if (auto this_ = weakThis.lock())
+        this_->onEvents(events, isInitial);
+    };
+    std::string error;
+    DirWatcher = DirectoryWatcher::create(getWatchedDir(), receiver,
+                                          waitInitialSync, error);
+    return DirWatcher == nullptr;
+  }
+
+  void stopWatching() { DirWatcher.reset(); }
+
+  /// \returns None if the timeout is reached before getting an event.
+  Optional<EventOccurrence> getNextEvent(unsigned timeout_seconds = 5) {
+    std::unique_lock<std::mutex> lck(Mutex);
+    auto pred = [&]() -> bool { return !EvtOccurs.empty(); };
+    bool gotEvent =
+        Condition.wait_for(lck, std::chrono::seconds(timeout_seconds), pred);
+    if (!gotEvent)
+      return None;
+
+    EventOccurrence occur = EvtOccurs.front();
+    EvtOccurs.pop_front();
+    return occur;
+  }
+
+  EventOccurrence getNextEventImmediately() {
+    std::lock_guard<std::mutex> LG(Mutex);
+    assert(!EvtOccurs.empty());
+    EventOccurrence occur = EvtOccurs.front();
+    EvtOccurs.pop_front();
+    return occur;
+  }
+
+private:
+  void onEvents(ArrayRef<DirectoryWatcher::Event> events, bool isInitial) {
+    std::lock_guard<std::mutex> LG(Mutex);
+    EvtOccurs.push_back({events, isInitial});
+    Condition.notify_all();
+  }
+};
+
+} // namespace
+
+TEST(DirectoryWatcherTest, initialScan) {
+  auto t = std::make_shared<DirectoryWatcherTest>();
+  t->init();
+
+  std::vector<StringRef> fnames = {"a", "b", "c"};
+  std::vector<file_status> stats;
+  t->addFiles(fnames, stats);
+
+  bool err = t->startWatching(/*waitInitialSync=*/true);
+  ASSERT_FALSE(err);
+
+  auto evt = t->getNextEventImmediately();
+  EXPECT_TRUE(evt.IsInitial);
+  EventCollection coll1{evt.Events};
+  EXPECT_TRUE(coll1.hasAdded(fnames, stats));
+
+  StringRef additionalFname = "d";
+  file_status additionalStat;
+  t->addFile(additionalFname, additionalStat);
+  auto evtOpt = t->getNextEvent();
+  ASSERT_TRUE(evtOpt.hasValue());
+  EXPECT_FALSE(evtOpt->IsInitial);
+  EventCollection coll2{evtOpt->Events};
+  EXPECT_TRUE(coll2.hasAdded({additionalFname}, {additionalStat}));
+}
+
+TEST(DirectoryWatcherTest, fileEvents) {
+  auto t = std::make_shared<DirectoryWatcherTest>();
+  t->init();
+
+  bool err = t->startWatching(/*waitInitialSync=*/false);
+  ASSERT_FALSE(err);
+
+  auto evt = t->getNextEvent();
+  ASSERT_TRUE(evt.hasValue());
+  EXPECT_TRUE(evt->IsInitial);
+  EXPECT_TRUE(evt->Events.empty());
+  return;
+
+  {
+    std::vector<StringRef> fnames = {"a", "b"};
+    std::vector<file_status> stats;
+    t->addFiles(fnames, stats);
+
+    EventCollection coll{};
+    while (coll.size() < 2) {
+      evt = t->getNextEvent();
+      ASSERT_TRUE(evt.hasValue());
+      coll.append(evt->Events);
+    }
+    EXPECT_TRUE(coll.hasAdded(fnames, stats));
+  }
+  {
+    std::vector<StringRef> fnames = {"b", "c"};
+    std::vector<file_status> stats;
+    t->addFiles(fnames, stats);
+
+    EventCollection coll{};
+    while (coll.size() < 2) {
+      evt = t->getNextEvent();
+      ASSERT_TRUE(evt.hasValue());
+      coll.append(evt->Events);
+    }
+    EXPECT_TRUE(coll.hasAdded(fnames, stats));
+  }
+  {
+    std::vector<StringRef> fnames = {"a", "c"};
+    std::vector<file_status> stats;
+    t->addFiles(fnames, stats);
+    t->removeFile("b");
+
+    EventCollection coll{};
+    while (coll.size() < 3) {
+      evt = t->getNextEvent();
+      ASSERT_TRUE(evt.hasValue());
+      coll.append(evt->Events);
+    }
+
+    EXPECT_TRUE(coll.hasEvents(std::vector<StringRef>{"a", "b", "c"},
+                               std::vector<DirectoryWatcher::EventKind>{
+                                   DirectoryWatcher::EventKind::Added,
+                                   DirectoryWatcher::EventKind::Removed,
+                                   DirectoryWatcher::EventKind::Added,
+                               },
+                               std::vector<file_status>{
+                                   stats[0],
+                                   file_status{},
+                                   stats[1],
+                               }));
+  }
+  {
+    std::vector<StringRef> fnames = {"a", "c"};
+    t->removeFiles(fnames);
+
+    EventCollection coll{};
+    while (coll.size() < 2) {
+      evt = t->getNextEvent();
+      ASSERT_TRUE(evt.hasValue());
+      coll.append(evt->Events);
+    }
+    EXPECT_TRUE(coll.hasRemoved(fnames));
+  }
+}
Index: clang/unittests/DirectoryWatcher/CMakeLists.txt
===================================================================
--- /dev/null
+++ clang/unittests/DirectoryWatcher/CMakeLists.txt
@@ -0,0 +1,22 @@
+set(LLVM_LINK_COMPONENTS
+  Support
+  )
+
+add_clang_unittest(DirectoryWatcherTests
+  DirectoryWatcherTest.cpp
+  )
+
+target_link_libraries(DirectoryWatcherTests
+  PRIVATE
+  clangDirectoryWatcher
+  clangBasic
+  )
+
+if(APPLE)
+  check_include_files("CoreServices/CoreServices.h" HAVE_CORESERVICES_H)
+  if(HAVE_CORESERVICES_H)
+    set(DWT_LINK_FLAGS "${DWT_LINK_FLAGS} -framework CoreServices")
+    set_property(TARGET DirectoryWatcherTests APPEND_STRING PROPERTY
+                 LINK_FLAGS ${DWT_LINK_FLAGS})
+  endif()
+endif()
Index: clang/unittests/CMakeLists.txt
===================================================================
--- clang/unittests/CMakeLists.txt
+++ clang/unittests/CMakeLists.txt
@@ -30,5 +30,6 @@
 if(NOT WIN32 AND CLANG_TOOL_LIBCLANG_BUILD) 
   add_subdirectory(libclang)
 endif()
+add_subdirectory(DirectoryWatcher)
 add_subdirectory(Rename)
 add_subdirectory(Index)
Index: clang/lib/DirectoryWatcher/DirectoryWatcher.cpp
===================================================================
--- /dev/null
+++ clang/lib/DirectoryWatcher/DirectoryWatcher.cpp
@@ -0,0 +1,153 @@
+//===- DirectoryWatcher.cpp - Listens for directory file changes ----------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+/// \file
+/// \brief Utility class for listening for file system changes in a directory.
+//===----------------------------------------------------------------------===//
+
+#include "clang/DirectoryWatcher/DirectoryWatcher.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/StringMap.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace clang;
+using namespace llvm;
+
+static Optional<sys::fs::file_status> getFileStatus(StringRef path) {
+  sys::fs::file_status Status;
+  std::error_code EC = status(path, Status);
+  if (EC)
+    return None;
+  return Status;
+}
+
+namespace llvm {
+// Specialize DenseMapInfo for sys::fs::UniqueID.
+template <> struct DenseMapInfo<sys::fs::UniqueID> {
+  static sys::fs::UniqueID getEmptyKey() {
+    return sys::fs::UniqueID{DenseMapInfo<uint64_t>::getEmptyKey(),
+                             DenseMapInfo<uint64_t>::getEmptyKey()};
+  }
+
+  static sys::fs::UniqueID getTombstoneKey() {
+    return sys::fs::UniqueID{DenseMapInfo<uint64_t>::getTombstoneKey(),
+                             DenseMapInfo<uint64_t>::getEmptyKey()};
+  }
+
+  static unsigned getHashValue(const sys::fs::UniqueID &val) {
+    return DenseMapInfo<std::pair<uint64_t, uint64_t>>::getHashValue(
+        std::make_pair(val.getDevice(), val.getFile()));
+  }
+
+  static bool isEqual(const sys::fs::UniqueID &LHS,
+                      const sys::fs::UniqueID &RHS) {
+    return LHS == RHS;
+  }
+};
+} // namespace llvm
+
+namespace {
+/// Used for initial directory scan.
+///
+/// Note that the caller must ensure serial access to it. It is not thread safe
+/// to access it without additional protection.
+struct DirectoryScan {
+  DenseSet<sys::fs::UniqueID> FileIDSet;
+  std::vector<std::tuple<std::string, sys::TimePoint<>>> Files;
+
+  void scanDirectory(StringRef Path) {
+    using namespace llvm::sys;
+
+    std::error_code EC;
+    for (auto It = fs::directory_iterator(Path, EC),
+              End = fs::directory_iterator();
+         !EC && It != End; It.increment(EC)) {
+      auto status = getFileStatus(It->path());
+      if (!status.hasValue())
+        continue;
+      Files.push_back(
+          std::make_tuple(It->path(), status->getLastModificationTime()));
+      FileIDSet.insert(status->getUniqueID());
+    }
+  }
+
+  std::vector<DirectoryWatcher::Event> getAsFileEvents() const {
+    std::vector<DirectoryWatcher::Event> Events;
+    for (const auto &info : Files) {
+      DirectoryWatcher::Event Event{DirectoryWatcher::EventKind::Added,
+                                    std::get<0>(info), std::get<1>(info)};
+      Events.push_back(std::move(Event));
+    }
+    return Events;
+  }
+};
+} // namespace
+
+// Add platform-specific functionality.
+
+#if !defined(__has_include)
+#define __has_include(x) 0
+#endif
+
+#if __has_include(<CoreServices/CoreServices.h>)
+#include "DirectoryWatcher-mac.inc.h"
+#elif __has_include(<sys/inotify.h>)
+#include "DirectoryWatcher-linux.inc.h"
+#else
+
+struct DirectoryWatcher::Implementation {
+  bool initialize(StringRef Path, EventReceiver Receiver, bool waitInitialSync,
+                  std::string &Error) {
+    Error = "directory listening not supported for this platform";
+    return true;
+  }
+};
+
+#endif
+
+DirectoryWatcher::DirectoryWatcher() : Impl(*new Implementation()) {}
+
+DirectoryWatcher::~DirectoryWatcher() { delete &Impl; }
+
+std::unique_ptr<DirectoryWatcher>
+DirectoryWatcher::create(StringRef Path, EventReceiver Receiver,
+                         bool waitInitialSync, std::string &Error) {
+  using namespace llvm::sys;
+
+  if (!fs::exists(Path)) {
+    std::error_code EC = fs::create_directories(Path);
+    if (EC) {
+      Error = EC.message();
+      return nullptr;
+    }
+  }
+
+  bool IsDir;
+  std::error_code EC = fs::is_directory(Path, IsDir);
+  if (EC) {
+    Error = EC.message();
+    return nullptr;
+  }
+  if (!IsDir) {
+    Error = "path is not a directory: ";
+    Error += Path;
+    return nullptr;
+  }
+
+  std::unique_ptr<DirectoryWatcher> DirWatch;
+  DirWatch.reset(new DirectoryWatcher());
+  auto &Impl = DirWatch->Impl;
+  bool hasError =
+      Impl.initialize(Path, std::move(Receiver), waitInitialSync, Error);
+  if (hasError)
+    return nullptr;
+
+  return DirWatch;
+}
Index: clang/lib/DirectoryWatcher/DirectoryWatcher-mac.inc.h
===================================================================
--- /dev/null
+++ clang/lib/DirectoryWatcher/DirectoryWatcher-mac.inc.h
@@ -0,0 +1,214 @@
+//===- DirectoryWatcher-mac.inc.h - Mac-platform directory listening ------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include <CoreServices/CoreServices.h>
+
+struct DirectoryWatcher::Implementation {
+  bool initialize(StringRef Path, EventReceiver Receiver, bool waitInitialSync,
+                  std::string &Error);
+  ~Implementation() { stopFSEventStream(); };
+
+private:
+  FSEventStreamRef EventStream = nullptr;
+
+  bool setupFSEventStream(StringRef path, EventReceiver receiver,
+                          dispatch_queue_t queue,
+                          std::shared_ptr<DirectoryScan> initialScanPtr);
+  void stopFSEventStream();
+};
+
+namespace {
+struct EventStreamContextData {
+  std::string WatchedPath;
+  DirectoryWatcher::EventReceiver Receiver;
+  std::shared_ptr<DirectoryScan> InitialScan;
+
+  EventStreamContextData(std::string watchedPath,
+                         DirectoryWatcher::EventReceiver receiver,
+                         std::shared_ptr<DirectoryScan> initialScanPtr)
+      : WatchedPath(std::move(watchedPath)), Receiver(std::move(receiver)),
+        InitialScan(std::move(initialScanPtr)) {}
+
+  static void dispose(const void *ctx) {
+    delete static_cast<const EventStreamContextData *>(ctx);
+  }
+};
+} // namespace
+
+static void eventStreamCallback(ConstFSEventStreamRef stream,
+                                void *clientCallBackInfo, size_t numEvents,
+                                void *eventPaths,
+                                const FSEventStreamEventFlags eventFlags[],
+                                const FSEventStreamEventId eventIds[]) {
+  auto *ctx = static_cast<EventStreamContextData *>(clientCallBackInfo);
+
+  std::vector<DirectoryWatcher::Event> Events;
+  for (size_t i = 0; i < numEvents; ++i) {
+    StringRef path = ((const char **)eventPaths)[i];
+    const FSEventStreamEventFlags flags = eventFlags[i];
+    if (!(flags & kFSEventStreamEventFlagItemIsFile)) {
+      if ((flags & kFSEventStreamEventFlagItemRemoved) &&
+          path == ctx->WatchedPath) {
+        DirectoryWatcher::Event Evt{
+            DirectoryWatcher::EventKind::DirectoryDeleted, path,
+            llvm::sys::TimePoint<>{}};
+        Events.push_back(Evt);
+        break;
+      }
+      continue;
+    }
+    DirectoryWatcher::EventKind K = DirectoryWatcher::EventKind::Modified;
+    bool hasAddedFlag = flags & (kFSEventStreamEventFlagItemCreated |
+                                 kFSEventStreamEventFlagItemRenamed);
+    bool hasRemovedFlag = flags & kFSEventStreamEventFlagItemRemoved;
+    Optional<sys::fs::file_status> statusOpt;
+    // NOTE: With low latency sometimes for a file that is moved inside the
+    // directory, or for a file that is removed from the directory, the flags
+    // have both 'renamed' and 'removed'. We use getting the file status as a
+    // way to distinguish between the two.
+    if (hasAddedFlag) {
+      statusOpt = getFileStatus(path);
+      if (statusOpt.hasValue()) {
+        K = DirectoryWatcher::EventKind::Added;
+      } else {
+        K = DirectoryWatcher::EventKind::Removed;
+      }
+    } else if (hasRemovedFlag) {
+      K = DirectoryWatcher::EventKind::Removed;
+    } else {
+      statusOpt = getFileStatus(path);
+      if (!statusOpt.hasValue()) {
+        K = DirectoryWatcher::EventKind::Removed;
+      }
+    }
+
+    if (ctx->InitialScan && K == DirectoryWatcher::EventKind::Added) {
+      // For the first time we get the events, check that we haven't already
+      // sent the 'added' event at the initial scan.
+      if (ctx->InitialScan->FileIDSet.count(statusOpt->getUniqueID())) {
+        // Already reported this event at the initial directory scan.
+        continue;
+      }
+    }
+
+    llvm::sys::TimePoint<> modTime{};
+    if (statusOpt.hasValue())
+      modTime = statusOpt->getLastModificationTime();
+    DirectoryWatcher::Event Evt{K, path, modTime};
+    Events.push_back(Evt);
+  }
+
+  // We won't need to check again later on.
+  ctx->InitialScan.reset();
+
+  if (!Events.empty()) {
+    ctx->Receiver(Events, /*isInitial=*/false);
+  }
+}
+
+bool DirectoryWatcher::Implementation::setupFSEventStream(
+    StringRef path, EventReceiver receiver, dispatch_queue_t queue,
+    std::shared_ptr<DirectoryScan> initialScanPtr) {
+  if (path.empty())
+    return true;
+
+  CFMutableArrayRef pathsToWatch =
+      CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks);
+  CFStringRef cfPathStr =
+      CFStringCreateWithBytes(nullptr, (const UInt8 *)path.data(), path.size(),
+                              kCFStringEncodingUTF8, false);
+  CFArrayAppendValue(pathsToWatch, cfPathStr);
+  CFRelease(cfPathStr);
+  CFAbsoluteTime latency = 0.0; // Latency in seconds.
+
+  std::string realPath;
+  {
+    SmallString<128> Storage;
+    StringRef P = llvm::Twine(path).toNullTerminatedStringRef(Storage);
+    char Buffer[PATH_MAX];
+    // Use ::realpath to get the real path name
+    if (::realpath(P.begin(), Buffer) != nullptr)
+      realPath = Buffer;
+    else
+      realPath = path;
+  }
+
+  EventStreamContextData *ctxData = new EventStreamContextData(
+      std::move(realPath), std::move(receiver), std::move(initialScanPtr));
+  FSEventStreamContext context;
+  context.version = 0;
+  context.info = ctxData;
+  context.retain = nullptr;
+  context.release = EventStreamContextData::dispose;
+  context.copyDescription = nullptr;
+
+  EventStream = FSEventStreamCreate(
+      nullptr, eventStreamCallback, &context, pathsToWatch,
+      kFSEventStreamEventIdSinceNow, latency,
+      kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagNoDefer);
+  CFRelease(pathsToWatch);
+  if (!EventStream) {
+    return true;
+  }
+  FSEventStreamSetDispatchQueue(EventStream, queue);
+  FSEventStreamStart(EventStream);
+  return false;
+}
+
+void DirectoryWatcher::Implementation::stopFSEventStream() {
+  if (!EventStream)
+    return;
+  FSEventStreamStop(EventStream);
+  FSEventStreamInvalidate(EventStream);
+  FSEventStreamRelease(EventStream);
+  EventStream = nullptr;
+}
+
+bool DirectoryWatcher::Implementation::initialize(StringRef Path,
+                                                  EventReceiver Receiver,
+                                                  bool waitInitialSync,
+                                                  std::string &Error) {
+  auto initialScan = std::make_shared<DirectoryScan>();
+
+  dispatch_queue_t queue =
+      dispatch_queue_create("DirectoryWatcher", DISPATCH_QUEUE_SERIAL);
+  dispatch_semaphore_t initScanSema = dispatch_semaphore_create(0);
+  dispatch_semaphore_t setupFSEventsSema = dispatch_semaphore_create(0);
+
+  std::string copiedPath = Path;
+  dispatch_retain(initScanSema);
+  dispatch_retain(setupFSEventsSema);
+  dispatch_async(queue, ^{
+    // Wait for the event stream to be setup before doing the initial scan,
+    // to make sure we won't miss any events.
+    dispatch_semaphore_wait(setupFSEventsSema, DISPATCH_TIME_FOREVER);
+    initialScan->scanDirectory(copiedPath);
+    Receiver(initialScan->getAsFileEvents(), /*isInitial=*/true);
+    dispatch_semaphore_signal(initScanSema);
+    dispatch_release(setupFSEventsSema);
+    dispatch_release(initScanSema);
+  });
+  bool fsErr = setupFSEventStream(Path, Receiver, queue, initialScan);
+  dispatch_semaphore_signal(setupFSEventsSema);
+
+  if (waitInitialSync) {
+    dispatch_semaphore_wait(initScanSema, DISPATCH_TIME_FOREVER);
+  }
+  dispatch_release(setupFSEventsSema);
+  dispatch_release(initScanSema);
+  dispatch_release(queue);
+
+  if (fsErr) {
+    raw_string_ostream(Error)
+        << "failed to setup FSEvents stream for path: " << Path;
+    return true;
+  }
+
+  return false;
+}
Index: clang/lib/DirectoryWatcher/DirectoryWatcher-linux.inc.h
===================================================================
--- /dev/null
+++ clang/lib/DirectoryWatcher/DirectoryWatcher-linux.inc.h
@@ -0,0 +1,195 @@
+//===- DirectoryWatcher-linux.inc.h - Linux-platform directory listening --===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Support/Errno.h"
+#include "llvm/Support/Mutex.h"
+#include "llvm/Support/Path.h"
+#include <sys/inotify.h>
+#include <thread>
+#include <unistd.h>
+
+namespace {
+
+struct INotifyEvent {
+  DirectoryWatcher::EventKind K;
+  std::string Filename;
+  Optional<sys::fs::file_status> Status;
+};
+
+class EventQueue {
+  DirectoryWatcher::EventReceiver Receiver;
+  sys::Mutex Mtx;
+  bool gotInitialScan = false;
+  std::vector<INotifyEvent> PendingEvents;
+
+  DirectoryWatcher::Event toDirEvent(const INotifyEvent &evt) {
+    llvm::sys::TimePoint<> modTime{};
+    if (evt.Status.hasValue())
+      modTime = evt.Status->getLastModificationTime();
+    return DirectoryWatcher::Event{evt.K, evt.Filename, modTime};
+  }
+
+public:
+  explicit EventQueue(DirectoryWatcher::EventReceiver receiver)
+      : Receiver(receiver) {}
+
+  void onDirectoryEvents(ArrayRef<INotifyEvent> evts) {
+    sys::ScopedLock L(Mtx);
+
+    if (!gotInitialScan) {
+      PendingEvents.insert(PendingEvents.end(), evts.begin(), evts.end());
+      return;
+    }
+
+    SmallVector<DirectoryWatcher::Event, 8> dirEvents;
+    for (const auto &evt : evts) {
+      dirEvents.push_back(toDirEvent(evt));
+    }
+    Receiver(dirEvents, /*isInitial=*/false);
+  }
+
+  void onInitialScan(std::shared_ptr<DirectoryScan> dirScan) {
+    sys::ScopedLock L(Mtx);
+
+    std::vector<DirectoryWatcher::Event> events = dirScan->getAsFileEvents();
+    Receiver(events, /*isInitial=*/true);
+
+    events.clear();
+    for (const auto &evt : PendingEvents) {
+      if (evt.K == DirectoryWatcher::EventKind::Added &&
+          dirScan->FileIDSet.count(evt.Status->getUniqueID())) {
+        // Already reported this event at the initial directory scan.
+        continue;
+      }
+      events.push_back(toDirEvent(evt));
+    }
+    if (!events.empty()) {
+      Receiver(events, /*isInitial=*/false);
+    }
+
+    gotInitialScan = true;
+    PendingEvents.clear();
+  }
+};
+} // namespace
+
+struct DirectoryWatcher::Implementation {
+  bool initialize(StringRef Path, EventReceiver Receiver, bool waitInitialSync,
+                  std::string &Error);
+  ~Implementation() { stopListening(); };
+
+private:
+  int inotifyFD = -1;
+
+  void stopListening();
+};
+
+static void runWatcher(std::string pathToWatch, int inotifyFD,
+                       std::shared_ptr<EventQueue> evtQueue) {
+#define EVT_BUF_LEN (30 * (sizeof(struct inotify_event) + NAME_MAX + 1))
+  char buf[EVT_BUF_LEN] __attribute__((aligned(8)));
+
+  while (1) {
+    ssize_t numRead = read(inotifyFD, buf, EVT_BUF_LEN);
+    if (numRead == -1) {
+      return; // watcher is stopped.
+    }
+
+    SmallVector<INotifyEvent, 8> iEvents;
+    for (char *p = buf; p < buf + numRead;) {
+      struct inotify_event *ievt = (struct inotify_event *)p;
+      p += sizeof(struct inotify_event) + ievt->len;
+
+      if (ievt->mask & IN_DELETE_SELF) {
+        INotifyEvent iEvt{DirectoryWatcher::EventKind::DirectoryDeleted,
+                          pathToWatch, None};
+        iEvents.push_back(iEvt);
+        break;
+      }
+
+      DirectoryWatcher::EventKind K = DirectoryWatcher::EventKind::Added;
+      if (ievt->mask & IN_MODIFY) {
+        K = DirectoryWatcher::EventKind::Modified;
+      }
+      if (ievt->mask & IN_MOVED_TO) {
+        K = DirectoryWatcher::EventKind::Added;
+      }
+      if (ievt->mask & IN_DELETE) {
+        K = DirectoryWatcher::EventKind::Removed;
+      }
+
+      assert(ievt->len > 0 && "expected a filename from inotify");
+      SmallString<256> fullPath{pathToWatch};
+      sys::path::append(fullPath, ievt->name);
+
+      Optional<sys::fs::file_status> statusOpt;
+      if (K != DirectoryWatcher::EventKind::Removed) {
+        statusOpt = getFileStatus(fullPath);
+        if (!statusOpt.hasValue())
+          K = DirectoryWatcher::EventKind::Removed;
+      }
+      INotifyEvent iEvt{K, fullPath.str(), statusOpt};
+      iEvents.push_back(iEvt);
+    }
+
+    if (!iEvents.empty())
+      evtQueue->onDirectoryEvents(iEvents);
+  }
+}
+
+bool DirectoryWatcher::Implementation::initialize(StringRef Path,
+                                                  EventReceiver Receiver,
+                                                  bool waitInitialSync,
+                                                  std::string &errorMsg) {
+  auto error = [&](StringRef msg) -> bool {
+    errorMsg = msg;
+    errorMsg += ": ";
+    errorMsg += llvm::sys::StrError();
+    return true;
+  };
+
+  auto evtQueue = std::make_shared<EventQueue>(std::move(Receiver));
+
+  inotifyFD = inotify_init();
+  if (inotifyFD == -1)
+    return error("inotify_init failed");
+
+  std::string pathToWatch = Path;
+  int wd = inotify_add_watch(inotifyFD, pathToWatch.c_str(),
+                             IN_MOVED_TO | IN_DELETE | IN_MODIFY |
+                                 IN_DELETE_SELF | IN_ONLYDIR);
+  if (wd == -1)
+    return error("inotify_add_watch failed");
+
+  std::thread watchThread(
+      std::bind(runWatcher, pathToWatch, inotifyFD, evtQueue));
+  watchThread.detach();
+
+  auto initialScan = std::make_shared<DirectoryScan>();
+  auto runScan = [pathToWatch, initialScan, evtQueue]() {
+    initialScan->scanDirectory(pathToWatch);
+    evtQueue->onInitialScan(std::move(initialScan));
+  };
+
+  if (waitInitialSync) {
+    runScan();
+  } else {
+    std::thread scanThread(runScan);
+    scanThread.detach();
+  }
+
+  return false;
+}
+
+void DirectoryWatcher::Implementation::stopListening() {
+  if (inotifyFD == -1)
+    return;
+  close(inotifyFD);
+  inotifyFD = -1;
+}
Index: clang/lib/DirectoryWatcher/CMakeLists.txt
===================================================================
--- /dev/null
+++ clang/lib/DirectoryWatcher/CMakeLists.txt
@@ -0,0 +1,18 @@
+include(CheckIncludeFiles)
+
+set(LLVM_LINK_COMPONENTS support)
+
+add_clang_library(clangDirectoryWatcher
+  DirectoryWatcher.cpp
+  )
+
+if(BUILD_SHARED_LIBS)
+  if(APPLE)
+    check_include_files("CoreServices/CoreServices.h" HAVE_CORESERVICES_H)
+    if(HAVE_CORESERVICES_H)
+      set(DIRECTORY_WATCHER_FLAGS "${DIRECTORY_WATCHER_FLAGS} -framework CoreServices")
+    endif()
+    set_property(TARGET clangDirectoryWatcher APPEND_STRING PROPERTY
+                 LINK_FLAGS ${DIRECTORY_WATCHER_FLAGS})
+  endif()
+endif()
Index: clang/lib/CMakeLists.txt
===================================================================
--- clang/lib/CMakeLists.txt
+++ clang/lib/CMakeLists.txt
@@ -18,6 +18,7 @@
 add_subdirectory(Frontend)
 add_subdirectory(FrontendTool)
 add_subdirectory(Tooling)
+add_subdirectory(DirectoryWatcher)
 add_subdirectory(Index)
 if(CLANG_ENABLE_STATIC_ANALYZER)
   add_subdirectory(StaticAnalyzer)
Index: clang/include/clang/DirectoryWatcher/DirectoryWatcher.h
===================================================================
--- /dev/null
+++ clang/include/clang/DirectoryWatcher/DirectoryWatcher.h
@@ -0,0 +1,69 @@
+//===- DirectoryWatcher.h - Listens for directory file changes --*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+/// \file
+/// \brief Utility class for listening for file system changes in a directory.
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_DIRECTORYWATCHER_DIRECTORYWATCHER_H
+#define LLVM_CLANG_DIRECTORYWATCHER_DIRECTORYWATCHER_H
+
+#include "clang/Basic/LLVM.h"
+#include "llvm/Support/Chrono.h"
+#include <functional>
+#include <memory>
+#include <string>
+
+namespace clang {
+
+/// Provides notifications for file system changes in a directory.
+///
+/// Guarantees that the first time the directory is processed, the receiver will
+/// be invoked even if the directory is empty.
+class DirectoryWatcher {
+public:
+  enum class EventKind {
+    /// A file was added.
+    Added,
+    /// A file was removed.
+    Removed,
+    /// A file was modified.
+    Modified,
+    /// The watched directory got deleted. No more events will follow.
+    DirectoryDeleted,
+  };
+
+  struct Event {
+    EventKind Kind;
+    std::string Filename;
+    llvm::sys::TimePoint<> ModTime;
+  };
+
+  typedef std::function<void(ArrayRef<Event> Events, bool isInitial)>
+      EventReceiver;
+
+  ~DirectoryWatcher();
+
+  static std::unique_ptr<DirectoryWatcher> create(StringRef Path,
+                                                  EventReceiver Receiver,
+                                                  bool waitInitialSync,
+                                                  std::string &Error);
+
+private:
+  struct Implementation;
+  Implementation &Impl;
+
+  DirectoryWatcher();
+
+  DirectoryWatcher(const DirectoryWatcher &) = delete;
+  DirectoryWatcher &operator=(const DirectoryWatcher &) = delete;
+};
+
+} // namespace clang
+
+#endif
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to