1 //== BackgroundIndexStorage.cpp - Provide caching support to BackgroundIndex ==/
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "GlobalCompilationDatabase.h"
10 #include "index/Background.h"
11 #include "support/Logger.h"
12 #include "support/Path.h"
13 #include "llvm/ADT/Optional.h"
14 #include "llvm/ADT/STLExtras.h"
15 #include "llvm/ADT/ScopeExit.h"
16 #include "llvm/ADT/SmallString.h"
17 #include "llvm/ADT/SmallVector.h"
18 #include "llvm/ADT/StringRef.h"
19 #include "llvm/Support/Error.h"
20 #include "llvm/Support/FileSystem.h"
21 #include "llvm/Support/FileUtilities.h"
22 #include "llvm/Support/MemoryBuffer.h"
23 #include "llvm/Support/Path.h"
24 #include <functional>
25 
26 namespace clang {
27 namespace clangd {
28 namespace {
29 
getShardPathFromFilePath(llvm::StringRef ShardRoot,llvm::StringRef FilePath)30 std::string getShardPathFromFilePath(llvm::StringRef ShardRoot,
31                                      llvm::StringRef FilePath) {
32   llvm::SmallString<128> ShardRootSS(ShardRoot);
33   llvm::sys::path::append(ShardRootSS, llvm::sys::path::filename(FilePath) +
34                                            "." + llvm::toHex(digest(FilePath)) +
35                                            ".idx");
36   return std::string(ShardRootSS.str());
37 }
38 
39 // Uses disk as a storage for index shards.
40 class DiskBackedIndexStorage : public BackgroundIndexStorage {
41   std::string DiskShardRoot;
42 
43 public:
44   // Creates `DiskShardRoot` and any parents during construction.
DiskBackedIndexStorage(llvm::StringRef Directory)45   DiskBackedIndexStorage(llvm::StringRef Directory) : DiskShardRoot(Directory) {
46     std::error_code OK;
47     std::error_code EC = llvm::sys::fs::create_directories(DiskShardRoot);
48     if (EC != OK) {
49       elog("Failed to create directory {0} for index storage: {1}",
50            DiskShardRoot, EC.message());
51     }
52   }
53 
54   std::unique_ptr<IndexFileIn>
loadShard(llvm::StringRef ShardIdentifier) const55   loadShard(llvm::StringRef ShardIdentifier) const override {
56     const std::string ShardPath =
57         getShardPathFromFilePath(DiskShardRoot, ShardIdentifier);
58     auto Buffer = llvm::MemoryBuffer::getFile(ShardPath);
59     if (!Buffer)
60       return nullptr;
61     if (auto I = readIndexFile(Buffer->get()->getBuffer()))
62       return std::make_unique<IndexFileIn>(std::move(*I));
63     else
64       elog("Error while reading shard {0}: {1}", ShardIdentifier,
65            I.takeError());
66     return nullptr;
67   }
68 
storeShard(llvm::StringRef ShardIdentifier,IndexFileOut Shard) const69   llvm::Error storeShard(llvm::StringRef ShardIdentifier,
70                          IndexFileOut Shard) const override {
71     auto ShardPath = getShardPathFromFilePath(DiskShardRoot, ShardIdentifier);
72     return llvm::writeFileAtomically(ShardPath + ".tmp.%%%%%%%%", ShardPath,
73                                      [&Shard](llvm::raw_ostream &OS) {
74                                        OS << Shard;
75                                        return llvm::Error::success();
76                                      });
77   }
78 };
79 
80 // Doesn't persist index shards anywhere (used when the CDB dir is unknown).
81 // We could consider indexing into ~/.clangd/ or so instead.
82 class NullStorage : public BackgroundIndexStorage {
83 public:
84   std::unique_ptr<IndexFileIn>
loadShard(llvm::StringRef ShardIdentifier) const85   loadShard(llvm::StringRef ShardIdentifier) const override {
86     return nullptr;
87   }
88 
storeShard(llvm::StringRef ShardIdentifier,IndexFileOut Shard) const89   llvm::Error storeShard(llvm::StringRef ShardIdentifier,
90                          IndexFileOut Shard) const override {
91     vlog("Couldn't find project for {0}, indexing in-memory only",
92          ShardIdentifier);
93     return llvm::Error::success();
94   }
95 };
96 
97 // Creates and owns IndexStorages for multiple CDBs.
98 // When a CDB root is found, shards are stored in $ROOT/.cache/clangd/index/.
99 // When no root is found, the fallback path is ~/.cache/clangd/index/.
100 class DiskBackedIndexStorageManager {
101 public:
DiskBackedIndexStorageManager(std::function<llvm::Optional<ProjectInfo> (PathRef)> GetProjectInfo)102   DiskBackedIndexStorageManager(
103       std::function<llvm::Optional<ProjectInfo>(PathRef)> GetProjectInfo)
104       : IndexStorageMapMu(std::make_unique<std::mutex>()),
105         GetProjectInfo(std::move(GetProjectInfo)) {
106     llvm::SmallString<128> FallbackDir;
107     if (llvm::sys::path::cache_directory(FallbackDir))
108       llvm::sys::path::append(FallbackDir, "clangd", "index");
109     this->FallbackDir = FallbackDir.str().str();
110   }
111 
112   // Creates or fetches to storage from cache for the specified project.
operator ()(PathRef File)113   BackgroundIndexStorage *operator()(PathRef File) {
114     std::lock_guard<std::mutex> Lock(*IndexStorageMapMu);
115     llvm::SmallString<128> StorageDir(FallbackDir);
116     if (auto PI = GetProjectInfo(File)) {
117       StorageDir = PI->SourceRoot;
118       llvm::sys::path::append(StorageDir, ".cache", "clangd", "index");
119     }
120     auto &IndexStorage = IndexStorageMap[StorageDir];
121     if (!IndexStorage)
122       IndexStorage = create(StorageDir);
123     return IndexStorage.get();
124   }
125 
126 private:
create(PathRef CDBDirectory)127   std::unique_ptr<BackgroundIndexStorage> create(PathRef CDBDirectory) {
128     if (CDBDirectory.empty()) {
129       elog("Tried to create storage for empty directory!");
130       return std::make_unique<NullStorage>();
131     }
132     return std::make_unique<DiskBackedIndexStorage>(CDBDirectory);
133   }
134 
135   Path FallbackDir;
136 
137   llvm::StringMap<std::unique_ptr<BackgroundIndexStorage>> IndexStorageMap;
138   std::unique_ptr<std::mutex> IndexStorageMapMu;
139 
140   std::function<llvm::Optional<ProjectInfo>(PathRef)> GetProjectInfo;
141 };
142 
143 } // namespace
144 
145 BackgroundIndexStorage::Factory
createDiskBackedStorageFactory(std::function<llvm::Optional<ProjectInfo> (PathRef)> GetProjectInfo)146 BackgroundIndexStorage::createDiskBackedStorageFactory(
147     std::function<llvm::Optional<ProjectInfo>(PathRef)> GetProjectInfo) {
148   return DiskBackedIndexStorageManager(std::move(GetProjectInfo));
149 }
150 
151 } // namespace clangd
152 } // namespace clang
153