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"
92 llvm::SmallVector<std::string>
Specs;
97 std::tie(RHS.Driver, RHS.StandardIncludes, RHS.StandardCXXIncludes,
98 RHS.Lang, RHS.Sysroot, RHS.ISysroot, RHS.Target, RHS.Stdlib,
102 DriverArgs(
const tooling::CompileCommand &Cmd, llvm::StringRef
File) {
103 llvm::SmallString<128>
Driver(Cmd.CommandLine.front());
106 if (llvm::any_of(Driver,
107 [](
char C) {
return llvm::sys::path::is_separator(
C); })) {
108 llvm::sys::fs::make_absolute(Cmd.Directory, Driver);
110 this->Driver =
Driver.str().str();
111 for (
size_t I = 0,
E = Cmd.CommandLine.size(); I <
E; ++I) {
112 llvm::StringRef Arg = Cmd.CommandLine[I];
115 if (Arg.consume_front(
"-x")) {
116 if (Arg.empty() && I + 1 <
E)
117 Lang = Cmd.CommandLine[I + 1];
122 else if (Arg ==
"-nostdinc" || Arg ==
"--no-standard-includes")
124 else if (Arg ==
"-nostdinc++")
127 else if (Arg.consume_front(
"--sysroot")) {
128 if (Arg.consume_front(
"="))
130 else if (Arg.empty() && I + 1 <
E)
131 Sysroot = Cmd.CommandLine[I + 1];
132 }
else if (Arg.consume_front(
"-isysroot")) {
133 if (Arg.empty() && I + 1 <
E)
137 }
else if (Arg.consume_front(
"--target=")) {
139 }
else if (Arg.consume_front(
"-target")) {
140 if (Arg.empty() && I + 1 <
E)
141 Target = Cmd.CommandLine[I + 1];
142 }
else if (Arg.consume_front(
"--stdlib")) {
143 if (Arg.consume_front(
"="))
145 else if (Arg.empty() && I + 1 <
E)
146 Stdlib = Cmd.CommandLine[I + 1];
147 }
else if (Arg.consume_front(
"-stdlib=")) {
149 }
else if (Arg.starts_with(
"-specs=")) {
154 Specs.push_back(Arg.str());
155 }
else if (Arg.starts_with(
"--specs=")) {
156 Specs.push_back(Arg.str());
157 }
else if (Arg ==
"--specs" && I + 1 <
E) {
158 Specs.push_back(Arg.str());
159 Specs.push_back(Cmd.CommandLine[I + 1]);
169 if (Lang ==
"objective-c++-header") {
176 llvm::StringRef Ext = llvm::sys::path::extension(
File).trim(
'.');
177 auto Type = driver::types::lookupTypeForExtension(Ext);
178 if (
Type == driver::types::TY_INVALID) {
179 elog(
"System include extraction: invalid file type for {0}", Ext);
181 Lang = driver::types::getTypeName(
Type);
185 llvm::SmallVector<llvm::StringRef> render()
const {
187 assert(!Lang.empty());
188 llvm::SmallVector<llvm::StringRef>
Args = {
"-x", Lang};
189 if (!StandardIncludes)
190 Args.push_back(
"-nostdinc");
191 if (!StandardCXXIncludes)
192 Args.push_back(
"-nostdinc++");
198 Args.append({
"-target", Target});
200 Args.append({
"--stdlib", Stdlib});
202 for (llvm::StringRef Spec : Specs) {
203 Args.push_back(Spec);
220 auto Driver = DriverArgs::getEmpty();
221 Driver.Driver =
"EMPTY_KEY";
225 auto Driver = DriverArgs::getEmpty();
226 Driver.Driver =
"TOMBSTONE_KEY";
230 unsigned FixedFieldsHash = llvm::hash_value(std::tuple{
232 Val.StandardIncludes,
233 Val.StandardCXXIncludes,
242 llvm::hash_combine_range(Val.Specs.begin(), Val.Specs.end());
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 DiagnosticsEngine Diags(
new DiagnosticIDs,
new DiagnosticOptions,
257 new IgnoringDiagConsumer);
258 llvm::IntrusiveRefCntPtr<TargetInfo> Target =
259 TargetInfo::CreateTargetInfo(Diags, TargetOpts);
263std::optional<DriverInfo> parseDriverOutput(llvm::StringRef
Output) {
265 const char SIS[] =
"#include <...> search starts here:";
266 const char SIE[] =
"End of search list.";
267 const char TS[] =
"Target: ";
268 llvm::SmallVector<llvm::StringRef>
Lines;
276 bool SeenIncludes =
false;
277 bool SeenTarget =
false;
278 for (
auto *It =
Lines.begin(); State != Done && It !=
Lines.end(); ++It) {
282 if (!SeenIncludes &&
Line.trim() == SIS) {
284 State = IncludesExtracting;
285 }
else if (!SeenTarget &&
Line.trim().starts_with(TS)) {
287 llvm::StringRef TargetLine =
Line.trim();
288 TargetLine.consume_front(TS);
290 if (!isValidTarget(TargetLine)) {
291 elog(
"System include extraction: invalid target \"{0}\", ignoring",
294 Info.Target = TargetLine.str();
295 vlog(
"System include extraction: target extracted: \"{0}\"",
300 case IncludesExtracting:
301 if (
Line.trim() == SIE) {
302 State = SeenTarget ? Done : Initial;
304 Info.SystemIncludes.push_back(
Line.trim().str());
305 vlog(
"System include extraction: adding {0}", Line);
309 llvm_unreachable(
"Impossible state of the driver output parser");
314 elog(
"System include extraction: start marker not found: {0}",
Output);
317 if (State == IncludesExtracting) {
318 elog(
"System include extraction: end marker missing: {0}",
Output);
321 return std::move(
Info);
324std::optional<std::string>
run(llvm::ArrayRef<llvm::StringRef> Argv,
325 bool OutputIsStderr) {
326 llvm::SmallString<128> OutputPath;
327 if (
auto EC = llvm::sys::fs::createTemporaryFile(
"system-includes",
"clangd",
329 elog(
"System include extraction: failed to create temporary file with "
334 auto CleanUp = llvm::make_scope_exit(
335 [&OutputPath]() { llvm::sys::fs::remove(OutputPath); });
337 std::optional<llvm::StringRef> Redirects[] = {{
""}, {
""}, {
""}};
338 Redirects[OutputIsStderr ? 2 : 1] = OutputPath.str();
342 llvm::sys::ExecuteAndWait(Argv.front(), Argv, std::nullopt,
345 elog(
"System include extraction: driver execution failed with return code: "
346 "{0} - '{1}'. Args: [{2}]",
347 llvm::to_string(RC), ErrMsg,
printArgv(Argv));
351 auto BufOrError = llvm::MemoryBuffer::getFile(OutputPath);
353 elog(
"System include extraction: failed to read {0} with error {1}",
354 OutputPath, BufOrError.getError().message());
357 return BufOrError.get().get()->getBuffer().str();
360std::optional<DriverInfo>
361extractSystemIncludesAndTarget(
const DriverArgs &InputArgs,
362 const llvm::Regex &QueryDriverRegex) {
363 trace::Span Tracer(
"Extract system includes and target");
365 std::string
Driver = InputArgs.Driver;
366 if (!llvm::sys::path::is_absolute(
Driver)) {
367 auto DriverProgram = llvm::sys::findProgramByName(
Driver);
369 vlog(
"System include extraction: driver {0} expanded to {1}",
Driver,
373 elog(
"System include extraction: driver {0} not found in PATH",
Driver);
385 llvm::SmallString<256> NoDots(
Driver);
386 llvm::sys::path::remove_dots(NoDots,
true);
387 if (!QueryDriverRegex.match(
Driver) && !QueryDriverRegex.match(NoDots)) {
388 vlog(
"System include extraction: not allowed driver {0}",
Driver);
392 llvm::SmallVector<llvm::StringRef>
Args = {
Driver,
"-E",
"-v"};
393 Args.append(InputArgs.render());
400 std::optional<DriverInfo>
Info = parseDriverOutput(*
Output);
409 if (
auto BuiltinHeaders =
410 run({
Driver,
"-print-file-name=include"},
false)) {
411 auto Path = llvm::StringRef(*BuiltinHeaders).trim();
412 if (!
Path.empty() && llvm::sys::path::is_absolute(
Path)) {
413 auto Size =
Info->SystemIncludes.size();
414 llvm::erase(
Info->SystemIncludes,
Path);
415 vlog(
"System includes extractor: builtin headers {0} {1}",
Path,
416 (
Info->SystemIncludes.size() != Size)
418 :
"not found in driver's response");
422 log(
"System includes extractor: successfully executed {0}\n\tgot includes: "
423 "\"{1}\"\n\tgot target: \"{2}\"",
428tooling::CompileCommand &
429addSystemIncludes(tooling::CompileCommand &Cmd,
431 std::vector<std::string> ToAppend;
434 ToAppend.push_back(
"-isystem");
435 ToAppend.push_back(Include.str());
437 if (!ToAppend.empty()) {
439 auto InsertAt = llvm::find(Cmd.CommandLine,
"--");
440 Cmd.CommandLine.insert(InsertAt, std::make_move_iterator(ToAppend.begin()),
441 std::make_move_iterator(ToAppend.end()));
446tooling::CompileCommand &setTarget(tooling::CompileCommand &Cmd,
447 const std::string &Target) {
448 if (!Target.empty()) {
450 for (llvm::StringRef Arg : Cmd.CommandLine) {
451 if (Arg ==
"-target" || Arg.starts_with(
"--target="))
455 auto InsertAt = llvm::find(Cmd.CommandLine,
"--");
456 Cmd.CommandLine.insert(InsertAt,
"--target=" + Target);
462std::string convertGlobToRegex(llvm::StringRef Glob) {
464 llvm::raw_string_ostream RegStream(RegText);
466 for (
size_t I = 0,
E = Glob.size(); I <
E; ++I) {
467 if (Glob[I] ==
'*') {
468 if (I + 1 <
E && Glob[I + 1] ==
'*') {
475 RegStream <<
"[^/]*";
477 }
else if (llvm::sys::path::is_separator(Glob[I]) &&
478 llvm::sys::path::is_separator(
'/') &&
479 llvm::sys::path::is_separator(
'\\')) {
480 RegStream << R
"([/\\])";
482 RegStream << llvm::Regex::escape(Glob.substr(I, 1));
491llvm::Regex convertGlobsToRegex(llvm::ArrayRef<std::string> Globs) {
492 assert(!Globs.empty() &&
"Globs cannot be empty!");
493 std::vector<std::string> RegTexts;
494 RegTexts.reserve(Globs.size());
495 for (llvm::StringRef Glob : Globs)
496 RegTexts.push_back(convertGlobToRegex(Glob));
499 llvm::Regex Reg(llvm::join(RegTexts,
"|"));
500 assert(Reg.isValid(RegTexts.front()) &&
501 "Created an invalid regex from globs");
508class SystemIncludeExtractor {
510 SystemIncludeExtractor(llvm::ArrayRef<std::string> QueryDriverGlobs)
511 : QueryDriverRegex(convertGlobsToRegex(QueryDriverGlobs)) {}
513 void operator()(tooling::CompileCommand &Cmd, llvm::StringRef
File)
const {
514 if (Cmd.CommandLine.empty())
518 if (
Args.Lang.empty())
520 if (
auto Info = QueriedDrivers.get(
Args, [&] {
521 return extractSystemIncludesAndTarget(Args, QueryDriverRegex);
523 setTarget(addSystemIncludes(Cmd,
Info->SystemIncludes),
Info->Target);
529 Memoize<llvm::DenseMap<DriverArgs, std::optional<DriverInfo>>> QueriedDrivers;
530 llvm::Regex QueryDriverRegex;
536 if (QueryDriverGlobs.empty())
538 return SystemIncludeExtractor(QueryDriverGlobs);
#define SPAN_ATTACH(S, Name, Expr)
Attach a key-value pair to a Span event.
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
@ Info
An information message.
SystemIncludeExtractorFn getSystemIncludeExtractor(llvm::ArrayRef< std::string > QueryDriverGlobs)
std::string Path
A typedef to represent a file path.
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()