diff options
Diffstat (limited to 'Source')
-rw-r--r-- | Source/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Source/cmFileMonitor.cxx | 389 | ||||
-rw-r--r-- | Source/cmFileMonitor.h | 28 | ||||
-rw-r--r-- | Source/cmServer.cxx | 5 | ||||
-rw-r--r-- | Source/cmServer.h | 3 | ||||
-rw-r--r-- | Source/cmServerConnection.cxx | 9 | ||||
-rw-r--r-- | Source/cmServerConnection.h | 4 | ||||
-rw-r--r-- | Source/cmServerProtocol.cxx | 8 | ||||
-rw-r--r-- | Source/cmServerProtocol.h | 2 |
9 files changed, 448 insertions, 1 deletions
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index b8f02e3..bd237e4 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 0000000..b97590b --- /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 <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; +} diff --git a/Source/cmFileMonitor.h b/Source/cmFileMonitor.h new file mode 100644 index 0000000..e05f48d --- /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 <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; +}; diff --git a/Source/cmServer.cxx b/Source/cmServer.cxx index ba1bd9d..51a363f 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 796db8e..7f29e32 100644 --- a/Source/cmServer.h +++ b/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); diff --git a/Source/cmServerConnection.cxx b/Source/cmServerConnection.cxx index 89ee6d8..c62ca3c 100644 --- a/Source/cmServerConnection.cxx +++ b/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; } diff --git a/Source/cmServerConnection.h b/Source/cmServerConnection.h index 16b1d5c..78842e7 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 f083e49..66cd801 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<std::string> cacheArgs; bool cacheArgumentsError = false; diff --git a/Source/cmServerProtocol.h b/Source/cmServerProtocol.h index d672a60..586efff 100644 --- a/Source/cmServerProtocol.h +++ b/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: |