Pārlūkot izejas kodu

server-mode: Add infrastructure to watch the filesystem

Enable the server to watch for filesystem changes. This patch includes
* The infrastructure for the file watching
* makes that infrastructure available to cmServerProtocols
* Resets the filesystemwatchers on "configure"
Tobias Hunger 9 gadi atpakaļ
vecāks
revīzija
0d96e19329

+ 1 - 0
Source/CMakeLists.txt

@@ -780,6 +780,7 @@ target_link_libraries(cmake CMakeLib)
 
 if(CMake_HAVE_SERVER_MODE)
   add_library(CMakeServerLib
+    cmFileMonitor.cxx cmFileMonitor.h
     cmServer.cxx cmServer.h
     cmServerConnection.cxx cmServerConnection.h
     cmServerProtocol.cxx cmServerProtocol.h

+ 389 - 0
Source/cmFileMonitor.cxx

@@ -0,0 +1,389 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmFileMonitor.h"
+
+#include <cmsys/SystemTools.hxx>
+
+#include <cassert>
+#include <iostream>
+#include <set>
+#include <unordered_map>
+
+namespace {
+void on_directory_change(uv_fs_event_t* handle, const char* filename,
+                         int events, int status);
+void on_handle_close(uv_handle_t* handle);
+} // namespace
+
+class cmIBaseWatcher
+{
+public:
+  cmIBaseWatcher() = default;
+  virtual ~cmIBaseWatcher() = default;
+
+  virtual void Trigger(const std::string& pathSegment, int events,
+                       int status) const = 0;
+  virtual std::string Path() const = 0;
+  virtual uv_loop_t* Loop() const = 0;
+
+  virtual void StartWatching() = 0;
+  virtual void StopWatching() = 0;
+
+  virtual std::vector<std::string> WatchedFiles() const = 0;
+  virtual std::vector<std::string> WatchedDirectories() const = 0;
+};
+
+class cmVirtualDirectoryWatcher : public cmIBaseWatcher
+{
+public:
+  ~cmVirtualDirectoryWatcher()
+  {
+    for (auto i : this->Children) {
+      delete i.second;
+    }
+  }
+
+  cmIBaseWatcher* Find(const std::string& ps)
+  {
+    const auto i = this->Children.find(ps);
+    return (i == this->Children.end()) ? nullptr : i->second;
+  }
+
+  void Trigger(const std::string& pathSegment, int events,
+               int status) const final
+  {
+    if (pathSegment.empty()) {
+      for (const auto& i : this->Children) {
+        i.second->Trigger(std::string(), events, status);
+      }
+    } else {
+      const auto i = this->Children.find(pathSegment);
+      if (i != this->Children.end()) {
+        i->second->Trigger(std::string(), events, status);
+      }
+    }
+  }
+
+  void StartWatching() override
+  {
+    for (const auto& i : this->Children) {
+      i.second->StartWatching();
+    }
+  }
+
+  void StopWatching() override
+  {
+    for (const auto& i : this->Children) {
+      i.second->StopWatching();
+    }
+  }
+
+  std::vector<std::string> WatchedFiles() const final
+  {
+    std::vector<std::string> result;
+    for (const auto& i : this->Children) {
+      for (const auto& j : i.second->WatchedFiles()) {
+        result.push_back(j);
+      }
+    }
+    return result;
+  }
+
+  std::vector<std::string> WatchedDirectories() const override
+  {
+    std::vector<std::string> result;
+    for (const auto& i : this->Children) {
+      for (const auto& j : i.second->WatchedDirectories()) {
+        result.push_back(j);
+      }
+    }
+    return result;
+  }
+
+  void Reset()
+  {
+    for (auto c : this->Children) {
+      delete c.second;
+    }
+    this->Children.clear();
+  }
+
+  void AddChildWatcher(const std::string& ps, cmIBaseWatcher* watcher)
+  {
+    assert(!ps.empty());
+    assert(this->Children.find(ps) == this->Children.end());
+    assert(watcher);
+
+    this->Children.emplace(std::make_pair(ps, watcher));
+  }
+
+private:
+  std::unordered_map<std::string, cmIBaseWatcher*> Children; // owned!
+};
+
+// Root of all the different (on windows!) root directories:
+class cmRootWatcher : public cmVirtualDirectoryWatcher
+{
+public:
+  cmRootWatcher(uv_loop_t* loop)
+    : mLoop(loop)
+  {
+    assert(loop);
+  }
+
+  std::string Path() const final
+  {
+    assert(false);
+    return std::string();
+  }
+  uv_loop_t* Loop() const final { return this->mLoop; }
+
+private:
+  uv_loop_t* const mLoop; // no ownership!
+};
+
+// Real directories:
+class cmRealDirectoryWatcher : public cmVirtualDirectoryWatcher
+{
+public:
+  cmRealDirectoryWatcher(cmVirtualDirectoryWatcher* p, const std::string& ps)
+    : Parent(p)
+    , PathSegment(ps)
+  {
+    assert(p);
+    assert(!ps.empty());
+
+    p->AddChildWatcher(ps, this);
+  }
+
+  ~cmRealDirectoryWatcher()
+  {
+    // Handle is freed via uv_handle_close callback!
+  }
+
+  void StartWatching() final
+  {
+    if (!this->Handle) {
+      this->Handle = new uv_fs_event_t;
+
+      uv_fs_event_init(this->Loop(), this->Handle);
+      this->Handle->data = this;
+      uv_fs_event_start(this->Handle, &on_directory_change, Path().c_str(), 0);
+    }
+    cmVirtualDirectoryWatcher::StartWatching();
+  }
+
+  void StopWatching() final
+  {
+    if (this->Handle) {
+      uv_fs_event_stop(this->Handle);
+      uv_close(reinterpret_cast<uv_handle_t*>(this->Handle), &on_handle_close);
+      this->Handle = nullptr;
+    }
+    cmVirtualDirectoryWatcher::StopWatching();
+  }
+
+  uv_loop_t* Loop() const final { return this->Parent->Loop(); }
+
+  std::vector<std::string> WatchedDirectories() const override
+  {
+    std::vector<std::string> result = { Path() };
+    for (const auto& j : cmVirtualDirectoryWatcher::WatchedDirectories()) {
+      result.push_back(j);
+    }
+    return result;
+  }
+
+protected:
+  cmVirtualDirectoryWatcher* const Parent;
+  const std::string PathSegment;
+
+private:
+  uv_fs_event_t* Handle = nullptr; // owner!
+};
+
+// Root directories:
+class cmRootDirectoryWatcher : public cmRealDirectoryWatcher
+{
+public:
+  cmRootDirectoryWatcher(cmRootWatcher* p, const std::string& ps)
+    : cmRealDirectoryWatcher(p, ps)
+  {
+  }
+
+  std::string Path() const final { return this->PathSegment; }
+};
+
+// Normal directories below root:
+class cmDirectoryWatcher : public cmRealDirectoryWatcher
+{
+public:
+  cmDirectoryWatcher(cmRealDirectoryWatcher* p, const std::string& ps)
+    : cmRealDirectoryWatcher(p, ps)
+  {
+  }
+
+  std::string Path() const final
+  {
+    return this->Parent->Path() + this->PathSegment + "/";
+  }
+};
+
+class cmFileWatcher : public cmIBaseWatcher
+{
+public:
+  cmFileWatcher(cmRealDirectoryWatcher* p, const std::string& ps,
+                cmFileMonitor::Callback cb)
+    : Parent(p)
+    , PathSegment(ps)
+    , CbList({ cb })
+  {
+    assert(p);
+    assert(!ps.empty());
+    p->AddChildWatcher(ps, this);
+  }
+
+  void StartWatching() final {}
+
+  void StopWatching() final {}
+
+  void AppendCallback(cmFileMonitor::Callback cb) { CbList.push_back(cb); }
+
+  std::string Path() const final
+  {
+    return this->Parent->Path() + this->PathSegment;
+  }
+
+  std::vector<std::string> WatchedDirectories() const final { return {}; }
+
+  std::vector<std::string> WatchedFiles() const final
+  {
+    return { this->Path() };
+  }
+
+  void Trigger(const std::string& ps, int events, int status) const final
+  {
+    assert(ps.empty());
+    assert(status == 0);
+    static_cast<void>(ps);
+
+    const std::string path = this->Path();
+    for (const auto& cb : this->CbList) {
+      cb(path, events, status);
+    }
+  }
+
+  uv_loop_t* Loop() const final { return this->Parent->Loop(); }
+
+private:
+  cmRealDirectoryWatcher* Parent;
+  const std::string PathSegment;
+  std::vector<cmFileMonitor::Callback> CbList;
+};
+
+namespace {
+
+void on_directory_change(uv_fs_event_t* handle, const char* filename,
+                         int events, int status)
+{
+  const cmIBaseWatcher* const watcher =
+    static_cast<const cmIBaseWatcher*>(handle->data);
+  const std::string pathSegment(filename);
+  watcher->Trigger(pathSegment, events, status);
+}
+
+void on_handle_close(uv_handle_t* handle)
+{
+  delete (reinterpret_cast<uv_fs_event_t*>(handle));
+}
+
+} // namespace
+
+cmFileMonitor::cmFileMonitor(uv_loop_t* l)
+  : Root(new cmRootWatcher(l))
+{
+}
+
+cmFileMonitor::~cmFileMonitor()
+{
+  delete this->Root;
+}
+
+void cmFileMonitor::MonitorPaths(const std::vector<std::string>& paths,
+                                 Callback cb)
+{
+  for (const auto& p : paths) {
+    std::vector<std::string> pathSegments;
+    cmsys::SystemTools::SplitPath(p, pathSegments, true);
+
+    const size_t segmentCount = pathSegments.size();
+    if (segmentCount < 2) { // Expect at least rootdir and filename
+      continue;
+    }
+    cmVirtualDirectoryWatcher* currentWatcher = this->Root;
+    for (size_t i = 0; i < segmentCount; ++i) {
+      assert(currentWatcher);
+
+      const bool fileSegment = (i == segmentCount - 1);
+      const bool rootSegment = (i == 0);
+      assert(
+        !(fileSegment &&
+          rootSegment)); // Can not be both filename and root part of the path!
+
+      const std::string& currentSegment = pathSegments[i];
+
+      cmIBaseWatcher* nextWatcher = currentWatcher->Find(currentSegment);
+      if (!nextWatcher) {
+        if (rootSegment) { // Root part
+          assert(currentWatcher == this->Root);
+          nextWatcher = new cmRootDirectoryWatcher(this->Root, currentSegment);
+          assert(currentWatcher->Find(currentSegment) == nextWatcher);
+        } else if (fileSegment) { // File part
+          assert(currentWatcher != this->Root);
+          nextWatcher = new cmFileWatcher(
+            dynamic_cast<cmRealDirectoryWatcher*>(currentWatcher),
+            currentSegment, cb);
+          assert(currentWatcher->Find(currentSegment) == nextWatcher);
+        } else { // Any normal directory in between
+          nextWatcher = new cmDirectoryWatcher(
+            dynamic_cast<cmRealDirectoryWatcher*>(currentWatcher),
+            currentSegment);
+          assert(currentWatcher->Find(currentSegment) == nextWatcher);
+        }
+      } else {
+        if (fileSegment) {
+          auto filePtr = dynamic_cast<cmFileWatcher*>(nextWatcher);
+          assert(filePtr);
+          filePtr->AppendCallback(cb);
+          continue;
+        }
+      }
+      currentWatcher = dynamic_cast<cmVirtualDirectoryWatcher*>(nextWatcher);
+    }
+  }
+  this->Root->StartWatching();
+}
+
+void cmFileMonitor::StopMonitoring()
+{
+  this->Root->StopWatching();
+  this->Root->Reset();
+}
+
+std::vector<std::string> cmFileMonitor::WatchedFiles() const
+{
+  std::vector<std::string> result;
+  if (this->Root) {
+    result = this->Root->WatchedFiles();
+  }
+  return result;
+}
+
+std::vector<std::string> cmFileMonitor::WatchedDirectories() const
+{
+  std::vector<std::string> result;
+  if (this->Root) {
+    result = this->Root->WatchedDirectories();
+  }
+  return result;
+}

+ 28 - 0
Source/cmFileMonitor.h

@@ -0,0 +1,28 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include <functional>
+#include <string>
+#include <vector>
+
+#include "cm_uv.h"
+
+class cmRootWatcher;
+
+class cmFileMonitor
+{
+public:
+  cmFileMonitor(uv_loop_t* l);
+  ~cmFileMonitor();
+
+  using Callback = std::function<void(const std::string&, int, int)>;
+  void MonitorPaths(const std::vector<std::string>& paths, Callback cb);
+  void StopMonitoring();
+
+  std::vector<std::string> WatchedFiles() const;
+  std::vector<std::string> WatchedDirectories() const;
+
+private:
+  cmRootWatcher* Root;
+};

+ 5 - 0
Source/cmServer.cxx

@@ -237,6 +237,11 @@ bool cmServer::Serve(std::string* errorMessage)
   return Connection->ProcessEvents(errorMessage);
 }
 
+cmFileMonitor* cmServer::FileMonitor() const
+{
+  return Connection->FileMonitor();
+}
+
 void cmServer::WriteJsonObject(const Json::Value& jsonValue,
                                const DebugInfo* debug) const
 {

+ 3 - 0
Source/cmServer.h

@@ -13,6 +13,7 @@
 #include <string>
 #include <vector>
 
+class cmFileMonitor;
 class cmServerConnection;
 class cmServerProtocol;
 class cmServerRequest;
@@ -28,6 +29,8 @@ public:
 
   bool Serve(std::string* errorMessage);
 
+  cmFileMonitor* FileMonitor() const;
+
 private:
   void RegisterProtocol(cmServerProtocol* protocol);
 

+ 8 - 1
Source/cmServerConnection.cxx

@@ -4,7 +4,8 @@
 
 #include "cmServerDictionary.h"
 
-#include <cmServer.h>
+#include "cmFileMonitor.h"
+#include "cmServer.h"
 
 #include <assert.h>
 
@@ -64,10 +65,16 @@ public:
     : Connection(connection)
   {
     Connection->mLoop = uv_default_loop();
+    if (Connection->mLoop) {
+      Connection->mFileMonitor = new cmFileMonitor(Connection->mLoop);
+    }
   }
 
   ~LoopGuard()
   {
+    if (Connection->mFileMonitor) {
+      delete Connection->mFileMonitor;
+    }
     uv_loop_close(Connection->mLoop);
     Connection->mLoop = nullptr;
   }

+ 4 - 0
Source/cmServerConnection.h

@@ -10,6 +10,7 @@
 #endif
 
 class cmServer;
+class cmFileMonitor;
 class LoopGuard;
 
 class cmServerConnection
@@ -29,6 +30,8 @@ public:
 
   virtual void Connect(uv_stream_t* server) { (void)(server); }
 
+  cmFileMonitor* FileMonitor() const { return this->mFileMonitor; }
+
 protected:
   virtual bool DoSetup(std::string* errorMessage) = 0;
   virtual void TearDown() = 0;
@@ -46,6 +49,7 @@ protected:
 
 private:
   uv_loop_t* mLoop = nullptr;
+  cmFileMonitor* mFileMonitor = nullptr;
   cmServer* Server = nullptr;
 
   friend class LoopGuard;

+ 8 - 0
Source/cmServerProtocol.cxx

@@ -4,6 +4,7 @@
 
 #include "cmCacheManager.h"
 #include "cmExternalMakefileProjectGenerator.h"
+#include "cmFileMonitor.h"
 #include "cmGeneratorTarget.h"
 #include "cmGlobalGenerator.h"
 #include "cmListFileCache.h"
@@ -214,6 +215,11 @@ bool cmServerProtocol::Activate(cmServer* server,
   return result;
 }
 
+cmFileMonitor* cmServerProtocol::FileMonitor() const
+{
+  return this->m_Server ? this->m_Server->FileMonitor() : nullptr;
+}
+
 void cmServerProtocol::SendSignal(const std::string& name,
                                   const Json::Value& data) const
 {
@@ -862,6 +868,8 @@ cmServerResponse cmServerProtocol1_0::ProcessConfigure(
     return request.ReportError("This instance is inactive.");
   }
 
+  FileMonitor()->StopMonitoring();
+
   // Make sure the types of cacheArguments matches (if given):
   std::vector<std::string> cacheArgs;
   bool cacheArgumentsError = false;

+ 2 - 0
Source/cmServerProtocol.h

@@ -13,6 +13,7 @@
 #include <string>
 
 class cmake;
+class cmFileMonitor;
 class cmServer;
 
 class cmServerRequest;
@@ -81,6 +82,7 @@ public:
   bool Activate(cmServer* server, const cmServerRequest& request,
                 std::string* errorMessage);
 
+  cmFileMonitor* FileMonitor() const;
   void SendSignal(const std::string& name, const Json::Value& data) const;
 
 protected: