37#include "clang/Basic/Diagnostic.h"
38#include "clang/Basic/DiagnosticIDs.h"
39#include "clang/Basic/DiagnosticOptions.h"
40#include "clang/Basic/TargetInfo.h"
41#include "clang/Basic/TargetOptions.h"
42#include "clang/Driver/Types.h"
43#include "clang/Tooling/CompilationDatabase.h"
44#include "llvm/ADT/ArrayRef.h"
45#include "llvm/ADT/DenseMap.h"
46#include "llvm/ADT/Hashing.h"
47#include "llvm/ADT/IntrusiveRefCntPtr.h"
48#include "llvm/ADT/STLExtras.h"
49#include "llvm/ADT/ScopeExit.h"
50#include "llvm/ADT/SmallString.h"
51#include "llvm/ADT/SmallVector.h"
52#include "llvm/ADT/StringExtras.h"
53#include "llvm/ADT/StringRef.h"
54#include "llvm/Support/ErrorHandling.h"
55#include "llvm/Support/FileSystem.h"
56#include "llvm/Support/MemoryBuffer.h"
57#include "llvm/Support/Path.h"
58#include "llvm/Support/Program.h"
59#include "llvm/Support/Regex.h"
60#include "llvm/Support/ScopedPrinter.h"
61#include "llvm/Support/raw_ostream.h"
95 std::tie(RHS.Driver, RHS.StandardIncludes, RHS.StandardCXXIncludes,
96 RHS.BuiltinIncludes, RHS.Lang, RHS.Sysroot,
ISysroot);
99 DriverArgs(
const tooling::CompileCommand &Cmd, llvm::StringRef
File) {
100 llvm::SmallString<128>
Driver(Cmd.CommandLine.front());
103 if (llvm::any_of(Driver,
104 [](
char C) {
return llvm::sys::path::is_separator(
C); })) {
105 llvm::sys::fs::make_absolute(Cmd.Directory, Driver);
107 this->Driver =
Driver.str().str();
108 for (
size_t I = 0,
E = Cmd.CommandLine.size(); I <
E; ++I) {
109 llvm::StringRef Arg = Cmd.CommandLine[I];
112 if (Arg.consume_front(
"-x")) {
113 if (Arg.empty() && I + 1 <
E)
114 Lang = Cmd.CommandLine[I + 1];
119 else if (Arg ==
"-nostdinc" || Arg ==
"--no-standard-includes")
121 else if (Arg ==
"-nostdinc++")
123 else if (Arg ==
"-nobuiltininc")
126 else if (Arg.consume_front(
"--sysroot")) {
127 if (Arg.consume_front(
"="))
129 else if (Arg.empty() && I + 1 <
E)
130 Sysroot = Cmd.CommandLine[I + 1];
131 }
else if (Arg.consume_front(
"-isysroot")) {
132 if (Arg.empty() && I + 1 <
E)
142 llvm::StringRef Ext = llvm::sys::path::extension(
File).trim(
'.');
143 auto Type = driver::types::lookupTypeForExtension(Ext);
144 if (
Type == driver::types::TY_INVALID) {
145 elog(
"System include extraction: invalid file type for {0}", Ext);
147 Lang = driver::types::getTypeName(
Type);
151 llvm::SmallVector<llvm::StringRef> render()
const {
153 assert(!Lang.empty());
154 llvm::SmallVector<llvm::StringRef>
Args = {
"-x", Lang};
155 if (!StandardIncludes)
156 Args.push_back(
"-nostdinc");
157 if (!StandardCXXIncludes)
158 Args.push_back(
"-nostdinc++");
159 if (!BuiltinIncludes)
160 Args.push_back(
"-nobuiltininc++");
179 auto Driver = DriverArgs::getEmpty();
180 Driver.Driver =
"EMPTY_KEY";
184 auto Driver = DriverArgs::getEmpty();
185 Driver.Driver =
"TOMBSTONE_KEY";
189 return llvm::hash_value(std::tuple{
191 Val.StandardIncludes,
192 Val.StandardCXXIncludes,
206bool isValidTarget(llvm::StringRef Triple) {
207 std::shared_ptr<TargetOptions> TargetOpts(
new TargetOptions);
208 TargetOpts->Triple = Triple.str();
209 DiagnosticsEngine Diags(
new DiagnosticIDs,
new DiagnosticOptions,
210 new IgnoringDiagConsumer);
211 llvm::IntrusiveRefCntPtr<TargetInfo> Target =
212 TargetInfo::CreateTargetInfo(Diags, TargetOpts);
216std::optional<DriverInfo> parseDriverOutput(llvm::StringRef
Output) {
218 const char SIS[] =
"#include <...> search starts here:";
219 const char SIE[] =
"End of search list.";
220 const char TS[] =
"Target: ";
221 llvm::SmallVector<llvm::StringRef>
Lines;
229 bool SeenIncludes =
false;
230 bool SeenTarget =
false;
231 for (
auto *It =
Lines.begin(); State != Done && It !=
Lines.end(); ++It) {
235 if (!SeenIncludes &&
Line.trim() == SIS) {
237 State = IncludesExtracting;
238 }
else if (!SeenTarget &&
Line.trim().startswith(TS)) {
240 llvm::StringRef TargetLine =
Line.trim();
241 TargetLine.consume_front(TS);
243 if (!isValidTarget(TargetLine)) {
244 elog(
"System include extraction: invalid target \"{0}\", ignoring",
247 Info.Target = TargetLine.str();
248 vlog(
"System include extraction: target extracted: \"{0}\"",
253 case IncludesExtracting:
254 if (
Line.trim() == SIE) {
255 State = SeenTarget ? Done : Initial;
257 Info.SystemIncludes.push_back(
Line.trim().str());
258 vlog(
"System include extraction: adding {0}", Line);
262 llvm_unreachable(
"Impossible state of the driver output parser");
267 elog(
"System include extraction: start marker not found: {0}",
Output);
270 if (State == IncludesExtracting) {
271 elog(
"System include extraction: end marker missing: {0}",
Output);
274 return std::move(
Info);
277std::optional<DriverInfo>
278extractSystemIncludesAndTarget(
const DriverArgs &InputArgs,
279 const llvm::Regex &QueryDriverRegex) {
280 trace::Span Tracer(
"Extract system includes and target");
282 std::string
Driver = InputArgs.Driver;
283 if (!llvm::sys::path::is_absolute(
Driver)) {
284 auto DriverProgram = llvm::sys::findProgramByName(
Driver);
286 vlog(
"System include extraction: driver {0} expanded to {1}",
Driver,
290 elog(
"System include extraction: driver {0} not found in PATH",
Driver);
298 if (!QueryDriverRegex.match(
Driver)) {
299 vlog(
"System include extraction: not allowed driver {0}",
Driver);
303 llvm::SmallString<128> StdErrPath;
304 if (
auto EC = llvm::sys::fs::createTemporaryFile(
"system-includes",
"clangd",
306 elog(
"System include extraction: failed to create temporary file with "
311 auto CleanUp = llvm::make_scope_exit(
312 [&StdErrPath]() { llvm::sys::fs::remove(StdErrPath); });
314 std::optional<llvm::StringRef> Redirects[] = {{
""}, {
""}, StdErrPath.str()};
316 llvm::SmallVector<llvm::StringRef>
Args = {
Driver,
"-E",
"-v"};
317 Args.append(InputArgs.render());
322 if (
int RC = llvm::sys::ExecuteAndWait(
Driver,
Args, std::nullopt,
325 elog(
"System include extraction: driver execution failed with return code: "
326 "{0} - '{1}'. Args: [{2}]",
331 auto BufOrError = llvm::MemoryBuffer::getFile(StdErrPath);
333 elog(
"System include extraction: failed to read {0} with error {1}",
334 StdErrPath, BufOrError.getError().message());
338 std::optional<DriverInfo>
Info =
339 parseDriverOutput(BufOrError->get()->getBuffer());
342 log(
"System includes extractor: successfully executed {0}\n\tgot includes: "
343 "\"{1}\"\n\tgot target: \"{2}\"",
348tooling::CompileCommand &
349addSystemIncludes(tooling::CompileCommand &Cmd,
351 std::vector<std::string> ToAppend;
354 ToAppend.push_back(
"-isystem");
355 ToAppend.push_back(Include.str());
357 if (!ToAppend.empty()) {
359 auto InsertAt = llvm::find(Cmd.CommandLine,
"--");
360 Cmd.CommandLine.insert(InsertAt, std::make_move_iterator(ToAppend.begin()),
361 std::make_move_iterator(ToAppend.end()));
366tooling::CompileCommand &setTarget(tooling::CompileCommand &Cmd,
367 const std::string &Target) {
368 if (!Target.empty()) {
370 for (llvm::StringRef Arg : Cmd.CommandLine) {
371 if (Arg ==
"-target" || Arg.startswith(
"--target="))
375 auto InsertAt = llvm::find(Cmd.CommandLine,
"--");
376 Cmd.CommandLine.insert(InsertAt,
"--target=" + Target);
382std::string convertGlobToRegex(llvm::StringRef Glob) {
384 llvm::raw_string_ostream RegStream(RegText);
386 for (
size_t I = 0,
E = Glob.size(); I <
E; ++I) {
387 if (Glob[I] ==
'*') {
388 if (I + 1 <
E && Glob[I + 1] ==
'*') {
395 RegStream <<
"[^/]*";
397 }
else if (llvm::sys::path::is_separator(Glob[I]) &&
398 llvm::sys::path::is_separator(
'/') &&
399 llvm::sys::path::is_separator(
'\\')) {
400 RegStream << R
"([/\\])";
402 RegStream << llvm::Regex::escape(Glob.substr(I, 1));
411llvm::Regex convertGlobsToRegex(llvm::ArrayRef<std::string> Globs) {
412 assert(!Globs.empty() &&
"Globs cannot be empty!");
413 std::vector<std::string> RegTexts;
414 RegTexts.reserve(Globs.size());
415 for (llvm::StringRef Glob : Globs)
416 RegTexts.push_back(convertGlobToRegex(Glob));
419 llvm::Regex Reg(llvm::join(RegTexts,
"|"));
420 assert(Reg.isValid(RegTexts.front()) &&
421 "Created an invalid regex from globs");
428class SystemIncludeExtractor {
430 SystemIncludeExtractor(llvm::ArrayRef<std::string> QueryDriverGlobs)
431 : QueryDriverRegex(convertGlobsToRegex(QueryDriverGlobs)) {}
433 void operator()(tooling::CompileCommand &Cmd, llvm::StringRef
File)
const {
434 if (Cmd.CommandLine.empty())
438 if (
Args.Lang.empty())
440 if (
auto Info = QueriedDrivers.get(
Args, [&] {
441 return extractSystemIncludesAndTarget(Args, QueryDriverRegex);
443 setTarget(addSystemIncludes(Cmd,
Info->SystemIncludes),
Info->Target);
449 Memoize<llvm::DenseMap<DriverArgs, std::optional<DriverInfo>>> QueriedDrivers;
450 llvm::Regex QueryDriverRegex;
456 if (QueryDriverGlobs.empty())
458 return SystemIncludeExtractor(QueryDriverGlobs);
#define SPAN_ATTACH(S, Name, Expr)
Attach a key-value pair to a Span event.
@ Info
An information message.
SystemIncludeExtractorFn getSystemIncludeExtractor(llvm::ArrayRef< std::string > QueryDriverGlobs)
void vlog(const char *Fmt, Ts &&... Vals)
bool operator==(const Inclusion &LHS, const Inclusion &RHS)
void log(const char *Fmt, Ts &&... Vals)
std::string printArgv(llvm::ArrayRef< llvm::StringRef > Args)
@ Type
An inlay hint that for a type annotation.
llvm::unique_function< void(tooling::CompileCommand &, llvm::StringRef) const > SystemIncludeExtractorFn
Extracts system include search path from drivers matching QueryDriverGlobs and adds them to the compi...
void elog(const char *Fmt, Ts &&... Vals)
Some operations such as code completion produce a set of candidates.
clang::clangd::DriverArgs DriverArgs
static unsigned getHashValue(const DriverArgs &Val)
static bool isEqual(const DriverArgs &LHS, const DriverArgs &RHS)
static DriverArgs getTombstoneKey()
static DriverArgs getEmptyKey()