1 //===--- ClangdMain.cpp - clangd server loop ------------------------------===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 10 #include "ClangdLSPServer.h" 11 #include "JSONRPCDispatcher.h" 12 #include "Path.h" 13 #include "Trace.h" 14 #include "index/SymbolYAML.h" 15 #include "llvm/Support/CommandLine.h" 16 #include "llvm/Support/FileSystem.h" 17 #include "llvm/Support/Path.h" 18 #include "llvm/Support/Program.h" 19 #include "llvm/Support/Signals.h" 20 #include "llvm/Support/raw_ostream.h" 21 #include <cstdlib> 22 #include <iostream> 23 #include <memory> 24 #include <string> 25 #include <thread> 26 27 using namespace clang; 28 using namespace clang::clangd; 29 30 namespace { 31 enum class PCHStorageFlag { Disk, Memory }; 32 33 // Build an in-memory static index for global symbols from a YAML-format file. 34 // The size of global symbols should be relatively small, so that all symbols 35 // can be managed in memory. 36 std::unique_ptr<SymbolIndex> BuildStaticIndex(llvm::StringRef YamlSymbolFile) { 37 auto Buffer = llvm::MemoryBuffer::getFile(YamlSymbolFile); 38 if (!Buffer) { 39 llvm::errs() << "Can't open " << YamlSymbolFile << "\n"; 40 return nullptr; 41 } 42 auto Slab = SymbolsFromYAML(Buffer.get()->getBuffer()); 43 SymbolSlab::Builder SymsBuilder; 44 for (auto Sym : Slab) 45 SymsBuilder.insert(Sym); 46 47 return MemIndex::build(std::move(SymsBuilder).build()); 48 } 49 } // namespace 50 51 static llvm::cl::opt<Path> CompileCommandsDir( 52 "compile-commands-dir", 53 llvm::cl::desc("Specify a path to look for compile_commands.json. If path " 54 "is invalid, clangd will look in the current directory and " 55 "parent paths of each source file.")); 56 57 static llvm::cl::opt<unsigned> 58 WorkerThreadsCount("j", 59 llvm::cl::desc("Number of async workers used by clangd"), 60 llvm::cl::init(getDefaultAsyncThreadsCount())); 61 62 // FIXME: Flags are the wrong mechanism for user preferences. 63 // We should probably read a dotfile or similar. 64 static llvm::cl::opt<bool> IncludeIneligibleResults( 65 "include-ineligible-results", 66 llvm::cl::desc( 67 "Include ineligible completion results (e.g. private members)"), 68 llvm::cl::init(clangd::CodeCompleteOptions().IncludeIneligibleResults), 69 llvm::cl::Hidden); 70 71 static llvm::cl::opt<JSONStreamStyle> InputStyle( 72 "input-style", llvm::cl::desc("Input JSON stream encoding"), 73 llvm::cl::values( 74 clEnumValN(JSONStreamStyle::Standard, "standard", "usual LSP protocol"), 75 clEnumValN(JSONStreamStyle::Delimited, "delimited", 76 "messages delimited by --- lines, with # comment support")), 77 llvm::cl::init(JSONStreamStyle::Standard)); 78 79 static llvm::cl::opt<bool> 80 PrettyPrint("pretty", llvm::cl::desc("Pretty-print JSON output"), 81 llvm::cl::init(false)); 82 83 static llvm::cl::opt<bool> Test( 84 "lit-test", 85 llvm::cl::desc( 86 "Abbreviation for -input-style=delimited -pretty -run-synchronously. " 87 "Intended to simplify lit tests."), 88 llvm::cl::init(false), llvm::cl::Hidden); 89 90 static llvm::cl::opt<PCHStorageFlag> PCHStorage( 91 "pch-storage", 92 llvm::cl::desc("Storing PCHs in memory increases memory usages, but may " 93 "improve performance"), 94 llvm::cl::values( 95 clEnumValN(PCHStorageFlag::Disk, "disk", "store PCHs on disk"), 96 clEnumValN(PCHStorageFlag::Memory, "memory", "store PCHs in memory")), 97 llvm::cl::init(PCHStorageFlag::Disk)); 98 99 static llvm::cl::opt<int> LimitCompletionResult( 100 "completion-limit", 101 llvm::cl::desc("Limit the number of completion results returned by clangd. " 102 "0 means no limit."), 103 llvm::cl::init(100)); 104 105 static llvm::cl::opt<bool> RunSynchronously( 106 "run-synchronously", 107 llvm::cl::desc("Parse on main thread. If set, -j is ignored"), 108 llvm::cl::init(false), llvm::cl::Hidden); 109 110 static llvm::cl::opt<Path> 111 ResourceDir("resource-dir", 112 llvm::cl::desc("Directory for system clang headers"), 113 llvm::cl::init(""), llvm::cl::Hidden); 114 115 static llvm::cl::opt<Path> InputMirrorFile( 116 "input-mirror-file", 117 llvm::cl::desc( 118 "Mirror all LSP input to the specified file. Useful for debugging."), 119 llvm::cl::init(""), llvm::cl::Hidden); 120 121 static llvm::cl::opt<bool> EnableIndexBasedCompletion( 122 "enable-index-based-completion", 123 llvm::cl::desc( 124 "Enable index-based global code completion. " 125 "Clang uses an index built from symbols in opened files"), 126 llvm::cl::init(true)); 127 128 static llvm::cl::opt<Path> YamlSymbolFile( 129 "yaml-symbol-file", 130 llvm::cl::desc( 131 "YAML-format global symbol file to build the static index. Clangd will " 132 "use the static index for global code completion.\n" 133 "WARNING: This option is experimental only, and will be removed " 134 "eventually. Don't rely on it."), 135 llvm::cl::init(""), llvm::cl::Hidden); 136 137 int main(int argc, char *argv[]) { 138 llvm::sys::PrintStackTraceOnErrorSignal(argv[0]); 139 llvm::cl::ParseCommandLineOptions(argc, argv, "clangd"); 140 if (Test) { 141 RunSynchronously = true; 142 InputStyle = JSONStreamStyle::Delimited; 143 PrettyPrint = true; 144 } 145 146 if (!RunSynchronously && WorkerThreadsCount == 0) { 147 llvm::errs() << "A number of worker threads cannot be 0. Did you mean to " 148 "specify -run-synchronously?"; 149 return 1; 150 } 151 152 // Ignore -j option if -run-synchonously is used. 153 // FIXME: a warning should be shown here. 154 if (RunSynchronously) 155 WorkerThreadsCount = 0; 156 157 // Validate command line arguments. 158 llvm::Optional<llvm::raw_fd_ostream> InputMirrorStream; 159 if (!InputMirrorFile.empty()) { 160 std::error_code EC; 161 InputMirrorStream.emplace(InputMirrorFile, /*ref*/ EC, llvm::sys::fs::F_RW); 162 if (EC) { 163 InputMirrorStream.reset(); 164 llvm::errs() << "Error while opening an input mirror file: " 165 << EC.message(); 166 } 167 } 168 169 // Setup tracing facilities if CLANGD_TRACE is set. In practice enabling a 170 // trace flag in your editor's config is annoying, launching with 171 // `CLANGD_TRACE=trace.json vim` is easier. 172 llvm::Optional<llvm::raw_fd_ostream> TraceStream; 173 std::unique_ptr<trace::EventTracer> Tracer; 174 if (auto *TraceFile = getenv("CLANGD_TRACE")) { 175 std::error_code EC; 176 TraceStream.emplace(TraceFile, /*ref*/ EC, llvm::sys::fs::F_RW); 177 if (EC) { 178 TraceStream.reset(); 179 llvm::errs() << "Error while opening trace file " << TraceFile << ": " 180 << EC.message(); 181 } else { 182 Tracer = trace::createJSONTracer(*TraceStream, PrettyPrint); 183 } 184 } 185 186 llvm::Optional<trace::Session> TracingSession; 187 if (Tracer) 188 TracingSession.emplace(*Tracer); 189 190 llvm::raw_ostream &Outs = llvm::outs(); 191 llvm::raw_ostream &Logs = llvm::errs(); 192 JSONOutput Out(Outs, Logs, 193 InputMirrorStream ? InputMirrorStream.getPointer() : nullptr, 194 PrettyPrint); 195 196 clangd::LoggingSession LoggingSession(Out); 197 198 // If --compile-commands-dir arg was invoked, check value and override default 199 // path. 200 llvm::Optional<Path> CompileCommandsDirPath; 201 202 if (CompileCommandsDir.empty()) { 203 CompileCommandsDirPath = llvm::None; 204 } else if (!llvm::sys::path::is_absolute(CompileCommandsDir) || 205 !llvm::sys::fs::exists(CompileCommandsDir)) { 206 llvm::errs() << "Path specified by --compile-commands-dir either does not " 207 "exist or is not an absolute " 208 "path. The argument will be ignored.\n"; 209 CompileCommandsDirPath = llvm::None; 210 } else { 211 CompileCommandsDirPath = CompileCommandsDir; 212 } 213 214 bool StorePreamblesInMemory; 215 switch (PCHStorage) { 216 case PCHStorageFlag::Memory: 217 StorePreamblesInMemory = true; 218 break; 219 case PCHStorageFlag::Disk: 220 StorePreamblesInMemory = false; 221 break; 222 } 223 224 llvm::Optional<StringRef> ResourceDirRef = None; 225 if (!ResourceDir.empty()) 226 ResourceDirRef = ResourceDir; 227 228 // Change stdin to binary to not lose \r\n on windows. 229 llvm::sys::ChangeStdinToBinary(); 230 231 std::unique_ptr<SymbolIndex> StaticIdx; 232 if (EnableIndexBasedCompletion && !YamlSymbolFile.empty()) 233 StaticIdx = BuildStaticIndex(YamlSymbolFile); 234 clangd::CodeCompleteOptions CCOpts; 235 CCOpts.IncludeIneligibleResults = IncludeIneligibleResults; 236 CCOpts.Limit = LimitCompletionResult; 237 // Initialize and run ClangdLSPServer. 238 ClangdLSPServer LSPServer(Out, WorkerThreadsCount, StorePreamblesInMemory, 239 CCOpts, ResourceDirRef, CompileCommandsDirPath, 240 EnableIndexBasedCompletion, StaticIdx.get()); 241 constexpr int NoShutdownRequestErrorCode = 1; 242 llvm::set_thread_name("clangd.main"); 243 return LSPServer.run(std::cin, InputStyle) ? 0 : NoShutdownRequestErrorCode; 244 } 245