1 //===--- GlobalCompilationDatabase.h -----------------------------*- C++-*-===// 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 #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_GLOBALCOMPILATIONDATABASE_H 10 #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_GLOBALCOMPILATIONDATABASE_H 11 12 #include "support/Function.h" 13 #include "support/Path.h" 14 #include "support/Threading.h" 15 #include "support/ThreadsafeFS.h" 16 #include "clang/Tooling/ArgumentsAdjusters.h" 17 #include "clang/Tooling/CompilationDatabase.h" 18 #include "llvm/ADT/Optional.h" 19 #include "llvm/ADT/StringMap.h" 20 #include <memory> 21 #include <mutex> 22 #include <vector> 23 24 namespace clang { 25 namespace clangd { 26 27 struct ProjectInfo { 28 // The directory in which the compilation database was discovered. 29 // Empty if directory-based compilation database discovery was not used. 30 std::string SourceRoot; 31 }; 32 33 /// Provides compilation arguments used for parsing C and C++ files. 34 class GlobalCompilationDatabase { 35 public: 36 virtual ~GlobalCompilationDatabase() = default; 37 38 /// If there are any known-good commands for building this file, returns one. 39 virtual llvm::Optional<tooling::CompileCommand> 40 getCompileCommand(PathRef File) const = 0; 41 42 /// Finds the closest project to \p File. getProjectInfo(PathRef File)43 virtual llvm::Optional<ProjectInfo> getProjectInfo(PathRef File) const { 44 return llvm::None; 45 } 46 47 /// Makes a guess at how to build a file. 48 /// The default implementation just runs clang on the file. 49 /// Clangd should treat the results as unreliable. 50 virtual tooling::CompileCommand getFallbackCommand(PathRef File) const; 51 52 /// If the CDB does any asynchronous work, wait for it to complete. 53 /// For use in tests. blockUntilIdle(Deadline D)54 virtual bool blockUntilIdle(Deadline D) const { return true; } 55 56 using CommandChanged = Event<std::vector<std::string>>; 57 /// The callback is notified when files may have new compile commands. 58 /// The argument is a list of full file paths. watch(CommandChanged::Listener L)59 CommandChanged::Subscription watch(CommandChanged::Listener L) const { 60 return OnCommandChanged.observe(std::move(L)); 61 } 62 63 protected: 64 mutable CommandChanged OnCommandChanged; 65 }; 66 67 // Helper class for implementing GlobalCompilationDatabases that wrap others. 68 class DelegatingCDB : public GlobalCompilationDatabase { 69 public: 70 DelegatingCDB(const GlobalCompilationDatabase *Base); 71 DelegatingCDB(std::unique_ptr<GlobalCompilationDatabase> Base); 72 73 llvm::Optional<tooling::CompileCommand> 74 getCompileCommand(PathRef File) const override; 75 76 llvm::Optional<ProjectInfo> getProjectInfo(PathRef File) const override; 77 78 tooling::CompileCommand getFallbackCommand(PathRef File) const override; 79 80 bool blockUntilIdle(Deadline D) const override; 81 82 private: 83 const GlobalCompilationDatabase *Base; 84 std::unique_ptr<GlobalCompilationDatabase> BaseOwner; 85 CommandChanged::Subscription BaseChanged; 86 }; 87 88 /// Gets compile args from tooling::CompilationDatabases built for parent 89 /// directories. 90 class DirectoryBasedGlobalCompilationDatabase 91 : public GlobalCompilationDatabase { 92 public: 93 struct Options { OptionsOptions94 Options(const ThreadsafeFS &TFS) : TFS(TFS) {} 95 96 const ThreadsafeFS &TFS; 97 // Frequency to check whether e.g. compile_commands.json has changed. 98 std::chrono::steady_clock::duration RevalidateAfter = 99 std::chrono::seconds(5); 100 // Frequency to check whether e.g. compile_commands.json has been created. 101 // (This is more expensive to check frequently, as we check many locations). 102 std::chrono::steady_clock::duration RevalidateMissingAfter = 103 std::chrono::seconds(30); 104 // Used to provide per-file configuration. 105 std::function<Context(llvm::StringRef)> ContextProvider; 106 // Only look for a compilation database in this one fixed directory. 107 // FIXME: fold this into config/context mechanism. 108 llvm::Optional<Path> CompileCommandsDir; 109 }; 110 111 DirectoryBasedGlobalCompilationDatabase(const Options &Opts); 112 ~DirectoryBasedGlobalCompilationDatabase() override; 113 114 /// Scans File's parents looking for compilation databases. 115 /// Any extra flags will be added. 116 /// Might trigger OnCommandChanged, if CDB wasn't broadcasted yet. 117 llvm::Optional<tooling::CompileCommand> 118 getCompileCommand(PathRef File) const override; 119 120 /// Returns the path to first directory containing a compilation database in 121 /// \p File's parents. 122 llvm::Optional<ProjectInfo> getProjectInfo(PathRef File) const override; 123 124 bool blockUntilIdle(Deadline Timeout) const override; 125 126 private: 127 Options Opts; 128 129 class DirectoryCache; 130 // Keyed by possibly-case-folded directory path. 131 // We can hand out pointers as they're stable and entries are never removed. 132 mutable llvm::StringMap<DirectoryCache> DirCaches; 133 mutable std::mutex DirCachesMutex; 134 135 std::vector<DirectoryCache *> 136 getDirectoryCaches(llvm::ArrayRef<llvm::StringRef> Dirs) const; 137 138 struct CDBLookupRequest { 139 PathRef FileName; 140 // Whether this lookup should trigger discovery of the CDB found. 141 bool ShouldBroadcast = false; 142 // Cached results newer than this are considered fresh and not checked 143 // against disk. 144 std::chrono::steady_clock::time_point FreshTime; 145 std::chrono::steady_clock::time_point FreshTimeMissing; 146 }; 147 struct CDBLookupResult { 148 std::shared_ptr<const tooling::CompilationDatabase> CDB; 149 ProjectInfo PI; 150 }; 151 llvm::Optional<CDBLookupResult> lookupCDB(CDBLookupRequest Request) const; 152 153 class BroadcastThread; 154 std::unique_ptr<BroadcastThread> Broadcaster; 155 156 // Performs broadcast on governed files. 157 void broadcastCDB(CDBLookupResult Res) const; 158 159 // cache test calls lookupCDB directly to ensure valid/invalid times. 160 friend class DirectoryBasedGlobalCompilationDatabaseCacheTest; 161 }; 162 163 /// Extracts system include search path from drivers matching QueryDriverGlobs 164 /// and adds them to the compile flags. Base may not be nullptr. 165 /// Returns Base when \p QueryDriverGlobs is empty. 166 std::unique_ptr<GlobalCompilationDatabase> 167 getQueryDriverDatabase(llvm::ArrayRef<std::string> QueryDriverGlobs, 168 std::unique_ptr<GlobalCompilationDatabase> Base); 169 170 /// Wraps another compilation database, and supports overriding the commands 171 /// using an in-memory mapping. 172 class OverlayCDB : public DelegatingCDB { 173 public: 174 // Base may be null, in which case no entries are inherited. 175 // FallbackFlags are added to the fallback compile command. 176 // Adjuster is applied to all commands, fallback or not. 177 OverlayCDB(const GlobalCompilationDatabase *Base, 178 std::vector<std::string> FallbackFlags = {}, 179 tooling::ArgumentsAdjuster Adjuster = nullptr); 180 181 llvm::Optional<tooling::CompileCommand> 182 getCompileCommand(PathRef File) const override; 183 tooling::CompileCommand getFallbackCommand(PathRef File) const override; 184 185 /// Sets or clears the compilation command for a particular file. 186 void 187 setCompileCommand(PathRef File, 188 llvm::Optional<tooling::CompileCommand> CompilationCommand); 189 190 private: 191 mutable std::mutex Mutex; 192 llvm::StringMap<tooling::CompileCommand> Commands; /* GUARDED_BY(Mut) */ 193 tooling::ArgumentsAdjuster ArgsAdjuster; 194 std::vector<std::string> FallbackFlags; 195 }; 196 197 } // namespace clangd 198 } // namespace clang 199 200 #endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_GLOBALCOMPILATIONDATABASE_H 201