From 0d96e1932937b866343ae8b52c20e0a8c058f3b2 Mon Sep 17 00:00:00 2001 From: Tobias Hunger Date: Fri, 9 Sep 2016 10:01:46 +0200 Subject: [PATCH] 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" --- Source/CMakeLists.txt | 1 + Source/cmFileMonitor.cxx | 389 ++++++++++++++++++++++++++++++++++ Source/cmFileMonitor.h | 28 +++ Source/cmServer.cxx | 5 + Source/cmServer.h | 3 + Source/cmServerConnection.cxx | 9 +- Source/cmServerConnection.h | 4 + Source/cmServerProtocol.cxx | 8 + Source/cmServerProtocol.h | 2 + 9 files changed, 448 insertions(+), 1 deletion(-) create mode 100644 Source/cmFileMonitor.cxx create mode 100644 Source/cmFileMonitor.h diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index b8f02e38d..bd237e49b 100644 --- a/Source/CMakeLists.txt +++ b/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 diff --git a/Source/cmFileMonitor.cxx b/Source/cmFileMonitor.cxx new file mode 100644 index 000000000..b97590b86 --- /dev/null +++ b/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 + +#include +#include +#include +#include + +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 WatchedFiles() const = 0; + virtual std::vector 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 WatchedFiles() const final + { + std::vector result; + for (const auto& i : this->Children) { + for (const auto& j : i.second->WatchedFiles()) { + result.push_back(j); + } + } + return result; + } + + std::vector WatchedDirectories() const override + { + std::vector 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 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(this->Handle), &on_handle_close); + this->Handle = nullptr; + } + cmVirtualDirectoryWatcher::StopWatching(); + } + + uv_loop_t* Loop() const final { return this->Parent->Loop(); } + + std::vector WatchedDirectories() const override + { + std::vector 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 WatchedDirectories() const final { return {}; } + + std::vector 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(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 CbList; +}; + +namespace { + +void on_directory_change(uv_fs_event_t* handle, const char* filename, + int events, int status) +{ + const cmIBaseWatcher* const watcher = + static_cast(handle->data); + const std::string pathSegment(filename); + watcher->Trigger(pathSegment, events, status); +} + +void on_handle_close(uv_handle_t* handle) +{ + delete (reinterpret_cast(handle)); +} + +} // namespace + +cmFileMonitor::cmFileMonitor(uv_loop_t* l) + : Root(new cmRootWatcher(l)) +{ +} + +cmFileMonitor::~cmFileMonitor() +{ + delete this->Root; +} + +void cmFileMonitor::MonitorPaths(const std::vector& paths, + Callback cb) +{ + for (const auto& p : paths) { + std::vector 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(currentWatcher), + currentSegment, cb); + assert(currentWatcher->Find(currentSegment) == nextWatcher); + } else { // Any normal directory in between + nextWatcher = new cmDirectoryWatcher( + dynamic_cast(currentWatcher), + currentSegment); + assert(currentWatcher->Find(currentSegment) == nextWatcher); + } + } else { + if (fileSegment) { + auto filePtr = dynamic_cast(nextWatcher); + assert(filePtr); + filePtr->AppendCallback(cb); + continue; + } + } + currentWatcher = dynamic_cast(nextWatcher); + } + } + this->Root->StartWatching(); +} + +void cmFileMonitor::StopMonitoring() +{ + this->Root->StopWatching(); + this->Root->Reset(); +} + +std::vector cmFileMonitor::WatchedFiles() const +{ + std::vector result; + if (this->Root) { + result = this->Root->WatchedFiles(); + } + return result; +} + +std::vector cmFileMonitor::WatchedDirectories() const +{ + std::vector result; + if (this->Root) { + result = this->Root->WatchedDirectories(); + } + return result; +} diff --git a/Source/cmFileMonitor.h b/Source/cmFileMonitor.h new file mode 100644 index 000000000..e05f48dc4 --- /dev/null +++ b/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 +#include +#include + +#include "cm_uv.h" + +class cmRootWatcher; + +class cmFileMonitor +{ +public: + cmFileMonitor(uv_loop_t* l); + ~cmFileMonitor(); + + using Callback = std::function; + void MonitorPaths(const std::vector& paths, Callback cb); + void StopMonitoring(); + + std::vector WatchedFiles() const; + std::vector WatchedDirectories() const; + +private: + cmRootWatcher* Root; +}; diff --git a/Source/cmServer.cxx b/Source/cmServer.cxx index ba1bd9d7a..51a363f03 100644 --- a/Source/cmServer.cxx +++ b/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 { diff --git a/Source/cmServer.h b/Source/cmServer.h index 796db8e66..7f29e3221 100644 --- a/Source/cmServer.h +++ b/Source/cmServer.h @@ -13,6 +13,7 @@ #include #include +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); diff --git a/Source/cmServerConnection.cxx b/Source/cmServerConnection.cxx index 89ee6d8e5..c62ca3c3e 100644 --- a/Source/cmServerConnection.cxx +++ b/Source/cmServerConnection.cxx @@ -4,7 +4,8 @@ #include "cmServerDictionary.h" -#include +#include "cmFileMonitor.h" +#include "cmServer.h" #include @@ -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; } diff --git a/Source/cmServerConnection.h b/Source/cmServerConnection.h index 16b1d5c01..78842e7cf 100644 --- a/Source/cmServerConnection.h +++ b/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; diff --git a/Source/cmServerProtocol.cxx b/Source/cmServerProtocol.cxx index f083e49fe..66cd80105 100644 --- a/Source/cmServerProtocol.cxx +++ b/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 cacheArgs; bool cacheArgumentsError = false; diff --git a/Source/cmServerProtocol.h b/Source/cmServerProtocol.h index d672a60b6..586efffe2 100644 --- a/Source/cmServerProtocol.h +++ b/Source/cmServerProtocol.h @@ -13,6 +13,7 @@ #include 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: