38#include "clang/Basic/Diagnostic.h"
39#include "clang/Basic/DiagnosticIDs.h"
40#include "clang/Basic/DiagnosticOptions.h"
41#include "clang/Basic/TargetInfo.h"
42#include "clang/Basic/TargetOptions.h"
43#include "clang/Driver/Types.h"
44#include "clang/Tooling/CompilationDatabase.h"
45#include "llvm/ADT/ArrayRef.h"
46#include "llvm/ADT/DenseMap.h"
47#include "llvm/ADT/Hashing.h"
48#include "llvm/ADT/IntrusiveRefCntPtr.h"
49#include "llvm/ADT/STLExtras.h"
50#include "llvm/ADT/ScopeExit.h"
51#include "llvm/ADT/SmallString.h"
52#include "llvm/ADT/SmallVector.h"
53#include "llvm/ADT/StringExtras.h"
54#include "llvm/ADT/StringRef.h"
55#include "llvm/Support/ErrorHandling.h"
56#include "llvm/Support/FileSystem.h"
57#include "llvm/Support/MemoryBuffer.h"
58#include "llvm/Support/Path.h"
59#include "llvm/Support/Program.h"
60#include "llvm/Support/Regex.h"
61#include "llvm/Support/ScopedPrinter.h"
62#include "llvm/Support/raw_ostream.h"
77 std::vector<std::string> SystemIncludes;
85 bool StandardIncludes =
true;
86 bool StandardCXXIncludes =
true;
93 llvm::SmallVector<std::string> Specs;
96 return std::tie(Driver, StandardIncludes, StandardCXXIncludes, Lang,
97 Sysroot, ISysroot, Target, Stdlib, Specs) ==
98 std::tie(RHS.Driver, RHS.StandardIncludes, RHS.StandardCXXIncludes,
99 RHS.Lang, RHS.Sysroot, RHS.ISysroot, RHS.Target, RHS.Stdlib,
103 DriverArgs(
const tooling::CompileCommand &Cmd, llvm::StringRef
File) {
104 llvm::SmallString<128> Driver(Cmd.CommandLine.front());
107 if (llvm::any_of(Driver,
108 [](
char C) {
return llvm::sys::path::is_separator(C); })) {
109 llvm::sys::fs::make_absolute(Cmd.Directory, Driver);
111 this->Driver = Driver.str().str();
112 for (
size_t I = 0, E = Cmd.CommandLine.size(); I < E; ++I) {
113 llvm::StringRef Arg = Cmd.CommandLine[I];
116 if (Arg.consume_front(
"-x")) {
117 if (Arg.empty() && I + 1 < E)
118 Lang = Cmd.CommandLine[I + 1];
123 else if (Arg ==
"-nostdinc" || Arg ==
"--no-standard-includes")
124 StandardIncludes =
false;
125 else if (Arg ==
"-nostdinc++")
126 StandardCXXIncludes =
false;
128 else if (Arg.consume_front(
"--sysroot")) {
129 if (Arg.consume_front(
"="))
131 else if (Arg.empty() && I + 1 < E)
132 Sysroot = Cmd.CommandLine[I + 1];
133 }
else if (Arg.consume_front(
"-isysroot")) {
134 if (Arg.empty() && I + 1 < E)
135 ISysroot = Cmd.CommandLine[I + 1];
137 ISysroot = Arg.str();
138 }
else if (Arg.consume_front(
"--target=")) {
140 }
else if (Arg.consume_front(
"-target")) {
141 if (Arg.empty() && I + 1 < E)
142 Target = Cmd.CommandLine[I + 1];
143 }
else if (Arg.consume_front(
"--stdlib")) {
144 if (Arg.consume_front(
"="))
146 else if (Arg.empty() && I + 1 < E)
147 Stdlib = Cmd.CommandLine[I + 1];
148 }
else if (Arg.consume_front(
"-stdlib=")) {
150 }
else if (Arg.starts_with(
"-specs=")) {
155 Specs.push_back(Arg.str());
156 }
else if (Arg.starts_with(
"--specs=")) {
157 Specs.push_back(Arg.str());
158 }
else if (Arg ==
"--specs" && I + 1 < E) {
159 Specs.push_back(Arg.str());
160 Specs.push_back(Cmd.CommandLine[I + 1]);
170 if (Lang ==
"objective-c++-header") {
177 llvm::StringRef Ext = llvm::sys::path::extension(
File).trim(
'.');
178 auto Type = driver::types::lookupTypeForExtension(Ext);
179 if (
Type == driver::types::TY_INVALID) {
180 elog(
"System include extraction: invalid file type for {0}", Ext);
182 Lang = driver::types::getTypeName(
Type);
186 llvm::SmallVector<llvm::StringRef> render()
const {
188 assert(!Lang.empty());
189 llvm::SmallVector<llvm::StringRef> Args = {
"-x", Lang};
190 if (!StandardIncludes)
191 Args.push_back(
"-nostdinc");
192 if (!StandardCXXIncludes)
193 Args.push_back(
"-nostdinc++");
194 if (!Sysroot.empty())
195 Args.append({
"--sysroot", Sysroot});
196 if (!ISysroot.empty())
197 Args.append({
"-isysroot", ISysroot});
199 Args.append({
"-target", Target});
201 Args.append({
"--stdlib", Stdlib});
203 for (llvm::StringRef Spec : Specs) {
204 Args.push_back(Spec);
221 auto Driver = DriverArgs::getEmpty();
222 Driver.Driver =
"EMPTY_KEY";
226 auto Driver = DriverArgs::getEmpty();
227 Driver.Driver =
"TOMBSTONE_KEY";
231 unsigned FixedFieldsHash = llvm::hash_value(std::tuple{
233 Val.StandardIncludes,
234 Val.StandardCXXIncludes,
242 unsigned SpecsHash = llvm::hash_combine_range(Val.Specs);
244 return llvm::hash_combine(FixedFieldsHash, SpecsHash);
253bool isValidTarget(llvm::StringRef Triple) {
254 std::shared_ptr<TargetOptions> TargetOpts(
new TargetOptions);
255 TargetOpts->Triple = Triple.str();
256 DiagnosticOptions DiagOpts;
257 DiagnosticsEngine Diags(DiagnosticIDs::create(), DiagOpts,
258 new IgnoringDiagConsumer);
259 llvm::IntrusiveRefCntPtr<TargetInfo> Target =
260 TargetInfo::CreateTargetInfo(Diags, *TargetOpts);
264std::optional<DriverInfo> parseDriverOutput(llvm::StringRef Output) {
266 const char SIS[] =
"#include <...> search starts here:";
267 const char SIE[] =
"End of search list.";
268 const char TS[] =
"Target: ";
269 llvm::SmallVector<llvm::StringRef> Lines;
270 Output.split(Lines,
'\n', -1,
false);
277 bool SeenIncludes =
false;
278 bool SeenTarget =
false;
279 for (
auto *It = Lines.begin(); State != Done && It != Lines.end(); ++It) {
283 if (!SeenIncludes && Line.trim() == SIS) {
285 State = IncludesExtracting;
286 }
else if (!SeenTarget && Line.trim().starts_with(TS)) {
288 llvm::StringRef TargetLine = Line.trim();
289 TargetLine.consume_front(TS);
291 if (!isValidTarget(TargetLine)) {
292 elog(
"System include extraction: invalid target \"{0}\", ignoring",
295 Info.Target = TargetLine.str();
296 vlog(
"System include extraction: target extracted: \"{0}\"",
301 case IncludesExtracting:
302 if (Line.trim() == SIE) {
303 State = SeenTarget ? Done : Initial;
305 Info.SystemIncludes.push_back(Line.trim().str());
306 vlog(
"System include extraction: adding {0}", Line);
310 llvm_unreachable(
"Impossible state of the driver output parser");
315 elog(
"System include extraction: start marker not found: {0}", Output);
318 if (State == IncludesExtracting) {
319 elog(
"System include extraction: end marker missing: {0}", Output);
322 return std::move(
Info);
325std::optional<std::string> run(llvm::ArrayRef<llvm::StringRef> Argv,
326 bool OutputIsStderr) {
327 llvm::SmallString<128> OutputPath;
328 if (
auto EC = llvm::sys::fs::createTemporaryFile(
"system-includes",
"clangd",
330 elog(
"System include extraction: failed to create temporary file with "
335 auto CleanUp = llvm::make_scope_exit(
336 [&OutputPath]() { llvm::sys::fs::remove(OutputPath); });
338 std::optional<llvm::StringRef> Redirects[] = {{
""}, {
""}, {
""}};
339 Redirects[OutputIsStderr ? 2 : 1] = OutputPath.str();
343 llvm::sys::ExecuteAndWait(Argv.front(), Argv, std::nullopt,
346 elog(
"System include extraction: driver execution failed with return code: "
347 "{0} - '{1}'. Args: [{2}]",
348 llvm::to_string(RC), ErrMsg,
printArgv(Argv));
352 auto BufOrError = llvm::MemoryBuffer::getFile(OutputPath);
354 elog(
"System include extraction: failed to read {0} with error {1}",
355 OutputPath, BufOrError.getError().message());
358 return BufOrError.get().get()->getBuffer().str();
361std::optional<DriverInfo>
362extractSystemIncludesAndTarget(
const DriverArgs &InputArgs,
363 const llvm::Regex &QueryDriverRegex) {
364 trace::Span Tracer(
"Extract system includes and target");
366 std::string Driver = InputArgs.Driver;
367 if (!llvm::sys::path::is_absolute(Driver)) {
368 auto DriverProgram = llvm::sys::findProgramByName(Driver);
370 vlog(
"System include extraction: driver {0} expanded to {1}", Driver,
372 Driver = *DriverProgram;
374 elog(
"System include extraction: driver {0} not found in PATH", Driver);
386 llvm::SmallString<256> NoDots(Driver);
387 llvm::sys::path::remove_dots(NoDots,
true);
388 if (!QueryDriverRegex.match(Driver) && !QueryDriverRegex.match(NoDots)) {
389 vlog(
"System include extraction: not allowed driver {0}", Driver);
393 llvm::SmallVector<llvm::StringRef> Args = {Driver,
"-E",
"-v"};
394 Args.append(InputArgs.render());
397 auto Output = run(Args,
true);
401 std::optional<DriverInfo>
Info = parseDriverOutput(*Output);
412 if (
auto BuiltinHeaders = run({Driver,
"-print-file-name=include"},
414 auto Path = llvm::StringRef(*BuiltinHeaders).trim();
415 if (!
Path.empty() && llvm::sys::path::is_absolute(
Path)) {
416 auto Size =
Info->SystemIncludes.size();
417 llvm::erase(
Info->SystemIncludes,
Path);
418 vlog(
"System includes extractor: builtin headers {0} {1}",
Path,
419 (
Info->SystemIncludes.size() != Size)
421 :
"not found in driver's response");
427 vlog(
"System includes extractor: Using builtin headers from query driver.");
431 log(
"System includes extractor: successfully executed {0}\n\tgot includes: "
432 "\"{1}\"\n\tgot target: \"{2}\"",
433 Driver, llvm::join(
Info->SystemIncludes,
", "),
Info->Target);
437tooling::CompileCommand &
438addSystemIncludes(tooling::CompileCommand &Cmd,
439 llvm::ArrayRef<std::string> SystemIncludes) {
440 std::vector<std::string> ToAppend;
441 for (llvm::StringRef Include : SystemIncludes) {
443 ToAppend.push_back(
"-isystem");
444 ToAppend.push_back(Include.str());
446 if (!ToAppend.empty()) {
448 auto InsertAt = llvm::find(Cmd.CommandLine,
"--");
449 Cmd.CommandLine.insert(InsertAt, std::make_move_iterator(ToAppend.begin()),
450 std::make_move_iterator(ToAppend.end()));
455tooling::CompileCommand &setTarget(tooling::CompileCommand &Cmd,
456 const std::string &Target) {
457 if (!Target.empty()) {
459 for (llvm::StringRef Arg : Cmd.CommandLine) {
460 if (Arg ==
"-target" || Arg.starts_with(
"--target="))
464 auto InsertAt = llvm::find(Cmd.CommandLine,
"--");
465 Cmd.CommandLine.insert(InsertAt,
"--target=" + Target);
471std::string convertGlobToRegex(llvm::StringRef Glob) {
473 llvm::raw_string_ostream RegStream(RegText);
475 for (
size_t I = 0, E = Glob.size(); I < E; ++I) {
476 if (Glob[I] ==
'*') {
477 if (I + 1 < E && Glob[I + 1] ==
'*') {
484 RegStream <<
"[^/]*";
486 }
else if (llvm::sys::path::is_separator(Glob[I]) &&
487 llvm::sys::path::is_separator(
'/') &&
488 llvm::sys::path::is_separator(
'\\')) {
489 RegStream << R
"([/\\])";
491 RegStream << llvm::Regex::escape(Glob.substr(I, 1));
499llvm::Regex convertGlobsToRegex(llvm::ArrayRef<std::string> Globs) {
500 assert(!Globs.empty() &&
"Globs cannot be empty!");
501 std::vector<std::string> RegTexts;
502 RegTexts.reserve(Globs.size());
503 for (llvm::StringRef Glob : Globs)
504 RegTexts.push_back(convertGlobToRegex(Glob));
507 llvm::Regex Reg(llvm::join(RegTexts,
"|"));
508 assert(Reg.isValid(RegTexts.front()) &&
509 "Created an invalid regex from globs");
516class SystemIncludeExtractor {
518 SystemIncludeExtractor(llvm::ArrayRef<std::string> QueryDriverGlobs)
519 : QueryDriverRegex(convertGlobsToRegex(QueryDriverGlobs)) {}
521 void operator()(tooling::CompileCommand &Cmd, llvm::StringRef
File)
const {
522 if (Cmd.CommandLine.empty())
526 if (Args.Lang.empty())
528 if (
auto Info = QueriedDrivers.get(Args, [&] {
529 return extractSystemIncludesAndTarget(Args, QueryDriverRegex);
531 setTarget(addSystemIncludes(Cmd,
Info->SystemIncludes),
Info->Target);
537 Memoize<llvm::DenseMap<DriverArgs, std::optional<DriverInfo>>> QueriedDrivers;
538 llvm::Regex QueryDriverRegex;
544 if (QueryDriverGlobs.empty())
546 return SystemIncludeExtractor(QueryDriverGlobs);
#define SPAN_ATTACH(S, Name, Expr)
Attach a key-value pair to a Span event.
Records an event whose duration is the lifetime of the Span object.
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
@ 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)
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 log(const char *Fmt, Ts &&... Vals)
std::string printArgv(llvm::ArrayRef< llvm::StringRef > Args)
@ Type
An inlay hint that for a type annotation.
std::string Path
A typedef to represent a file path.
void elog(const char *Fmt, Ts &&... Vals)
Some operations such as code completion produce a set of candidates.
clang::clangd::DriverArgs DriverArgs
static const Config & current()
Returns the Config of the current Context, or an empty configuration.
static unsigned getHashValue(const DriverArgs &Val)
static bool isEqual(const DriverArgs &LHS, const DriverArgs &RHS)
static DriverArgs getTombstoneKey()
static DriverArgs getEmptyKey()