diff --git a/clang-tools-extra/clangd/index/BackgroundIndexStorage.cpp b/clang-tools-extra/clangd/index/BackgroundIndexStorage.cpp --- a/clang-tools-extra/clangd/index/BackgroundIndexStorage.cpp +++ b/clang-tools-extra/clangd/index/BackgroundIndexStorage.cpp @@ -36,18 +36,13 @@ return std::string(ShardRootSS.str()); } -// Uses disk as a storage for index shards. Creates a directory called -// ".clangd/index/" under the path provided during construction. +// Uses disk as a storage for index shards. class DiskBackedIndexStorage : public BackgroundIndexStorage { std::string DiskShardRoot; public: - // Sets DiskShardRoot to (Directory + ".clangd/index/") which is the base - // directory for all shard files. - DiskBackedIndexStorage(llvm::StringRef Directory) { - llvm::SmallString<128> CDBDirectory(Directory); - llvm::sys::path::append(CDBDirectory, ".clangd", "index"); - DiskShardRoot = std::string(CDBDirectory.str()); + // Creates `DiskShardRoot` and any parents during construction. + DiskBackedIndexStorage(llvm::StringRef Directory) : DiskShardRoot(Directory) { std::error_code OK; std::error_code EC = llvm::sys::fs::create_directories(DiskShardRoot); if (EC != OK) { @@ -100,26 +95,31 @@ }; // Creates and owns IndexStorages for multiple CDBs. +// When a CDB root is found, shards are stored in $ROOT/.clangd/index. +// When no root is found, the fallback path is ~/.cache/clangd/index. class DiskBackedIndexStorageManager { public: DiskBackedIndexStorageManager( std::function(PathRef)> GetProjectInfo) : IndexStorageMapMu(std::make_unique()), GetProjectInfo(std::move(GetProjectInfo)) { - llvm::SmallString<128> HomeDir; - llvm::sys::path::home_directory(HomeDir); - this->HomeDir = HomeDir.str().str(); + llvm::SmallString<128> FallbackDir; + if (llvm::sys::path::cache_directory(FallbackDir)) + llvm::sys::path::append(FallbackDir, "clangd", "index"); + this->FallbackDir = FallbackDir.str().str(); } // Creates or fetches to storage from cache for the specified project. BackgroundIndexStorage *operator()(PathRef File) { std::lock_guard Lock(*IndexStorageMapMu); - Path CDBDirectory = HomeDir; - if (auto PI = GetProjectInfo(File)) - CDBDirectory = PI->SourceRoot; - auto &IndexStorage = IndexStorageMap[CDBDirectory]; + llvm::SmallString<128> StorageDir(FallbackDir); + if (auto PI = GetProjectInfo(File)) { + StorageDir = PI->SourceRoot; + llvm::sys::path::append(StorageDir, ".clangd", "index"); + } + auto &IndexStorage = IndexStorageMap[StorageDir]; if (!IndexStorage) - IndexStorage = create(CDBDirectory); + IndexStorage = create(StorageDir); return IndexStorage.get(); } @@ -132,7 +132,7 @@ return std::make_unique(CDBDirectory); } - Path HomeDir; + Path FallbackDir; llvm::StringMap> IndexStorageMap; std::unique_ptr IndexStorageMapMu; diff --git a/llvm/include/llvm/Support/Path.h b/llvm/include/llvm/Support/Path.h --- a/llvm/include/llvm/Support/Path.h +++ b/llvm/include/llvm/Support/Path.h @@ -368,6 +368,13 @@ /// @result True if a home directory is set, false otherwise. bool home_directory(SmallVectorImpl &result); +/// Get the directory where installed packages should put their +/// machine-local cache, e.g. $XDG_CACHE_HOME. +/// +/// @param result Holds the resulting path name. +/// @result True if the appropriate path was determined, it need not exist. +bool cache_directory(SmallVectorImpl &result); + /// Has root name? /// /// root_name != "" diff --git a/llvm/lib/Support/Unix/Path.inc b/llvm/lib/Support/Unix/Path.inc --- a/llvm/lib/Support/Unix/Path.inc +++ b/llvm/lib/Support/Unix/Path.inc @@ -1138,6 +1138,19 @@ return true; } +bool cache_directory(SmallVectorImpl &result) { + if (const char *RequestedDir = getenv("XDG_CACHE_HOME")) { + result.clear(); + result.append(RequestedDir, RequestedDir + strlen(RequestedDir)); + return true; + } + if (!home_directory(result)) { + return false; + } + append(result, ".cache"); + return true; +} + static bool getDarwinConfDir(bool TempDir, SmallVectorImpl &Result) { #if defined(_CS_DARWIN_USER_TEMP_DIR) && defined(_CS_DARWIN_USER_CACHE_DIR) // On Darwin, use DARWIN_USER_TEMP_DIR or DARWIN_USER_CACHE_DIR. diff --git a/llvm/lib/Support/Windows/Path.inc b/llvm/lib/Support/Windows/Path.inc --- a/llvm/lib/Support/Windows/Path.inc +++ b/llvm/lib/Support/Windows/Path.inc @@ -1372,6 +1372,10 @@ return getKnownFolderPath(FOLDERID_Profile, result); } +bool cache_directory(SmallVectorImpl &result) { + return getKnownFolderPath(FOLDERID_LocalAppData, result); +} + static bool getTempDirEnvVar(const wchar_t *Var, SmallVectorImpl &Res) { SmallVector Buf; size_t Size = 1024; diff --git a/llvm/unittests/Support/Path.cpp b/llvm/unittests/Support/Path.cpp --- a/llvm/unittests/Support/Path.cpp +++ b/llvm/unittests/Support/Path.cpp @@ -13,6 +13,7 @@ #include "llvm/ADT/Triple.h" #include "llvm/BinaryFormat/Magic.h" #include "llvm/Config/llvm-config.h" +#include "llvm/Support/Compiler.h" #include "llvm/Support/ConvertUTF.h" #include "llvm/Support/Errc.h" #include "llvm/Support/ErrorHandling.h" @@ -305,15 +306,46 @@ } } -TEST(Support, HomeDirectory) { - std::string expected; #ifdef _WIN32 - if (wchar_t const *path = ::_wgetenv(L"USERPROFILE")) { +std::string getEnvWin(const wchar_t *Var) { + std::string expected; + if (wchar_t const *path = ::_wgetenv(Var)) { auto pathLen = ::wcslen(path); ArrayRef ref{reinterpret_cast(path), pathLen * sizeof(wchar_t)}; convertUTF16ToUTF8String(ref, expected); } + return expected; +} +#else +// RAII helper to set and restore an environment variable. +class WithEnv { + const char *Var; + llvm::Optional OriginalValue; + +public: + WithEnv(const char *Var, const char *Value) : Var(Var) { + if (const char *V = ::getenv(Var)) + OriginalValue.emplace(V); + if (Value) + ::setenv(Var, Value, 1); + else + ::unsetenv(Var); + set(Value); + } + ~WithEnv() { + if (OriginalValue) + ::setenv(Var, OriginalValue->c_str(), 1); + else + ::unsetenv(Var); + } +}; +#endif + +TEST(Support, HomeDirectory) { + std::string expected; +#ifdef _WIN32 + expected = getEnvWin(L"USERPROFILE"); #else if (char const *path = ::getenv("HOME")) expected = path; @@ -330,31 +362,48 @@ #ifdef LLVM_ON_UNIX TEST(Support, HomeDirectoryWithNoEnv) { - std::string OriginalStorage; - char const *OriginalEnv = ::getenv("HOME"); - if (OriginalEnv) { - // We're going to unset it, so make a copy and save a pointer to the copy - // so that we can reset it at the end of the test. - OriginalStorage = OriginalEnv; - OriginalEnv = OriginalStorage.c_str(); - } + WithEnv Env("HOME", nullptr); // Don't run the test if we have nothing to compare against. struct passwd *pw = getpwuid(getuid()); if (!pw || !pw->pw_dir) return; - - ::unsetenv("HOME"); - EXPECT_EQ(nullptr, ::getenv("HOME")); std::string PwDir = pw->pw_dir; SmallString<128> HomeDir; - auto status = path::home_directory(HomeDir); - EXPECT_TRUE(status); + EXPECT_TRUE(path::home_directory(HomeDir)); EXPECT_EQ(PwDir, HomeDir); +} + +TEST(Support, CacheDirectoryWithEnv) { + WithEnv Env("XDG_CACHE_HOME", "/xdg/cache"); + + SmallString<128> CacheDir; + EXPECT_TRUE(path::cache_directory(CacheDir)); + EXPECT_EQ("/xdg/cache", CacheDir); +} + +TEST(Support, CacheDirectoryNoEnv) { + WithEnv Env("XDG_CACHE_HOME", nullptr); - // Now put the environment back to its original state (meaning that if it was - // unset before, we don't reset it). - if (OriginalEnv) ::setenv("HOME", OriginalEnv, 1); + SmallString<128> Fallback; + ASSERT_TRUE(path::home_directory(Fallback)); + path::append(Fallback, ".cache"); + + SmallString<128> CacheDir; + EXPECT_TRUE(path::cache_directory(CacheDir)); + EXPECT_EQ(Fallback, CacheDir); +} +#endif + +#ifdef _WIN32 +TEST(Support, CacheDirectory) { + std::string Expected = getEnvWin(L"LOCALAPPDATA"); + // Do not try to test it if we don't know what to expect. + if (!Expected.empty()) { + SmallString<128> CacheDir; + EXPECT_TRUE(path::cache_directory(CacheDir)); + EXPECT_EQ(Expected, CacheDir); + } } #endif