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