From beff40047a392fa0535a28e423bbee34d3a60f27 Mon Sep 17 00:00:00 2001 From: Chris Bradley Date: Fri, 20 Sep 2024 16:54:21 +0100 Subject: [PATCH] Add support for colon seperated list of library homes --- src/eckit/system/Library.cc | 74 +++++++++++++---------------- src/eckit/system/Library.h | 5 +- tests/system/test_system_library.cc | 32 +++++++++++++ 3 files changed, 69 insertions(+), 42 deletions(-) diff --git a/src/eckit/system/Library.cc b/src/eckit/system/Library.cc index 92ac5e8d6..88c4d64bc 100644 --- a/src/eckit/system/Library.cc +++ b/src/eckit/system/Library.cc @@ -74,29 +74,42 @@ std::string Library::prefixDirectory() const { return prefixDirectory_; } -std::string Library::home() const { +const std::vector& Library::homes() const { AutoLock lock(mutex_); + if (!homes_.empty()) { + return homes_; + } + std::string libhome = prefix_ + "_HOME"; - char* home = ::getenv(libhome.c_str()); - if (home) { - return home; + char* home_env = ::getenv(libhome.c_str()); + + // Home is a colon separated list of directories + if (home_env) { + std::stringstream ss(home_env); + std::string item; + while (std::getline(ss, item, ':')) { + homes_.push_back(item); + } + } + else { + // Default homes + LocalPathName prefix = prefixDirectory(); + homes_ = {prefix, prefix.dirName(), prefix.dirName().dirName(), "/"}; } - return home_; // may return empty string (meaning not set) + ASSERT(!homes_.empty()); + return homes_; } std::string Library::libraryHome() const { - std::string h = home(); - if (!h.empty()) { - return h; - } - return prefixDirectory(); + const std::vector& h = homes(); + return h[0]; } void Library::libraryHome(const std::string& home) { AutoLock lock(mutex_); - home_ = home; + homes_ = {home}; } std::string Library::libraryPath() const { @@ -155,39 +168,20 @@ std::string Library::expandPath(const std::string& p) const { ASSERT(p.substr(0, s.size()) == s); ASSERT(p.size() == s.size() || p[s.size()] == '/'); - // 1. if HOME is set for this library, either via env variable LIBNAME_HOME exists - // or set in code expand ~lib/ to its content - - const std::string h = home(); - if (!h.empty()) { - std::string result = h + "/" + p.substr(s.size()); - return result; - } - - // 2. try to walk up the path and check for paths that exist + // If HOME is set for this library, either via env variable LIBNAME_HOME + // or set in code, expand ~lib/ to its content + std::string tail = "/" + p.substr(s.size()); + const std::vector& h = homes(); - const std::string extra = "/" + p.substr(s.size()); - - eckit::LocalPathName path = prefixDirectory(); - eckit::LocalPathName root("/"); - - while (true) { - LocalPathName tmp = path + extra; - - if (tmp.exists()) { - return tmp; + for (const auto& home : h) { + LocalPathName result = LocalPathName(home + tail); + if (result.exists()) { + return result; } - - if (path == root) { - break; - } - - path = path.dirName(); } - // 3. as a last resort expand with prefix directory although we know the path doesn't exist - - return prefixDirectory() + extra; + // As a last resort expand with the first home entry, although we know the path doesn't exist + return h[0] + tail; } void Library::print(std::ostream& out) const { diff --git a/src/eckit/system/Library.h b/src/eckit/system/Library.h index 2d5e176e0..20c479d80 100644 --- a/src/eckit/system/Library.h +++ b/src/eckit/system/Library.h @@ -77,7 +77,8 @@ class Library : private eckit::NonCopyable { void unlock() { mutex_.unlock(); } protected: // methods - virtual std::string home() const; + + virtual const std::vector& homes() const; virtual const void* addr() const; @@ -94,7 +95,7 @@ class Library : private eckit::NonCopyable { private: // members std::string name_; std::string prefix_; - std::string home_; // if not set explicitly, will be empty + mutable std::vector homes_; bool debug_; diff --git a/tests/system/test_system_library.cc b/tests/system/test_system_library.cc index 93aa42ec1..d0c71d925 100644 --- a/tests/system/test_system_library.cc +++ b/tests/system/test_system_library.cc @@ -22,6 +22,7 @@ #include "eckit/system/ResourceUsage.h" #include "eckit/system/SystemInfo.h" #include "eckit/testing/Test.h" +#include "eckit/filesystem/TmpDir.h" using namespace std; using namespace eckit; @@ -85,6 +86,37 @@ CASE("test_eckit_system_info") { Log::info() << "execPath is " << execPath << std::endl; } +CASE("Test colon-seperated ECKIT_HOME expansion") { + + eckit::TmpDir td; + LocalPathName tmpdir = td.localPath(); + + // create files $tmpdir/dir{i}/file{i} + for (int i = 0; i < 3; i++) { + LocalPathName dir = tmpdir + "/dir" + std::to_string(i); + LocalPathName file = dir + "/file" + std::to_string(i); + file.touch(); + } + + std::stringstream ss; + ss << tmpdir << "/dir0:" << tmpdir << "/dir1:" << tmpdir << "/dir2"; + + setenv("ECKIT_HOME", ss.str().c_str(), 1); + + EXPECT(LocalPathName("~eckit/file0").exists()); + EXPECT(LocalPathName("~eckit/file1").exists()); + EXPECT(LocalPathName("~eckit/file2").exists()); + + EXPECT(LocalPathName("~eckit/file0") == tmpdir + "/dir0/file0"); + EXPECT(LocalPathName("~eckit/file1") == tmpdir + "/dir1/file1"); + EXPECT(LocalPathName("~eckit/file2") == tmpdir + "/dir2/file2"); + + // add file3 to dir1 and dir2 and test that the first one is returned + LocalPathName(tmpdir + "/dir1/file3").touch(); + LocalPathName(tmpdir + "/dir2/file3").touch(); + EXPECT(LocalPathName("~eckit/file3") == tmpdir + "/dir1/file3"); +} + CASE("test_eckit_system_library") { std::vector libs = LibraryManager::list();