mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-17 02:36:43 +00:00

This option, under `CompileFlags`, governs whether clangd uses its own built-in headers (`Clangd` option value) or the built-in headers of the driver in the file's compile command (`QueryDriver` option value, applicable to cases where `--query-driver` is used to instruct clangd to ask the driver for its system include paths). The default value is `Clangd`, preserving clangd's current defaut behaviour. Fixes clangd/clangd#2074
550 lines
19 KiB
C++
550 lines
19 KiB
C++
//===--- SystemIncludeExtractor.cpp ------------------------------*- C++-*-===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
// Some compiler drivers have implicit search mechanism for system headers.
|
|
// This compilation database implementation tries to extract that information by
|
|
// executing the driver in verbose mode. gcc-compatible drivers print something
|
|
// like:
|
|
// ....
|
|
// ....
|
|
// #include <...> search starts here:
|
|
// /usr/lib/gcc/x86_64-linux-gnu/7/include
|
|
// /usr/local/include
|
|
// /usr/lib/gcc/x86_64-linux-gnu/7/include-fixed
|
|
// /usr/include/x86_64-linux-gnu
|
|
// /usr/include
|
|
// End of search list.
|
|
// ....
|
|
// ....
|
|
// This component parses that output and adds each path to command line args
|
|
// provided by Base, after prepending them with -isystem. Therefore current
|
|
// implementation would not work with a driver that is not gcc-compatible.
|
|
//
|
|
// First argument of the command line received from underlying compilation
|
|
// database is used as compiler driver path. Due to this arbitrary binary
|
|
// execution, this mechanism is not used by default and only executes binaries
|
|
// in the paths that are explicitly included by the user.
|
|
|
|
#include "CompileCommands.h"
|
|
#include "Config.h"
|
|
#include "GlobalCompilationDatabase.h"
|
|
#include "support/Logger.h"
|
|
#include "support/Threading.h"
|
|
#include "support/Trace.h"
|
|
#include "clang/Basic/Diagnostic.h"
|
|
#include "clang/Basic/DiagnosticIDs.h"
|
|
#include "clang/Basic/DiagnosticOptions.h"
|
|
#include "clang/Basic/TargetInfo.h"
|
|
#include "clang/Basic/TargetOptions.h"
|
|
#include "clang/Driver/Types.h"
|
|
#include "clang/Tooling/CompilationDatabase.h"
|
|
#include "llvm/ADT/ArrayRef.h"
|
|
#include "llvm/ADT/DenseMap.h"
|
|
#include "llvm/ADT/Hashing.h"
|
|
#include "llvm/ADT/IntrusiveRefCntPtr.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "llvm/ADT/ScopeExit.h"
|
|
#include "llvm/ADT/SmallString.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/ADT/StringExtras.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
#include "llvm/Support/ErrorHandling.h"
|
|
#include "llvm/Support/FileSystem.h"
|
|
#include "llvm/Support/MemoryBuffer.h"
|
|
#include "llvm/Support/Path.h"
|
|
#include "llvm/Support/Program.h"
|
|
#include "llvm/Support/Regex.h"
|
|
#include "llvm/Support/ScopedPrinter.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
#include <cassert>
|
|
#include <cstddef>
|
|
#include <iterator>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <tuple>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace clang::clangd {
|
|
namespace {
|
|
|
|
struct DriverInfo {
|
|
std::vector<std::string> SystemIncludes;
|
|
std::string Target;
|
|
};
|
|
|
|
struct DriverArgs {
|
|
// Name of the driver program to execute or absolute path to it.
|
|
std::string Driver;
|
|
// Whether certain includes should be part of query.
|
|
bool StandardIncludes = true;
|
|
bool StandardCXXIncludes = true;
|
|
// Language to use while querying.
|
|
std::string Lang;
|
|
std::string Sysroot;
|
|
std::string ISysroot;
|
|
std::string Target;
|
|
std::string Stdlib;
|
|
llvm::SmallVector<std::string> Specs;
|
|
|
|
bool operator==(const DriverArgs &RHS) const {
|
|
return std::tie(Driver, StandardIncludes, StandardCXXIncludes, Lang,
|
|
Sysroot, ISysroot, Target, Stdlib, Specs) ==
|
|
std::tie(RHS.Driver, RHS.StandardIncludes, RHS.StandardCXXIncludes,
|
|
RHS.Lang, RHS.Sysroot, RHS.ISysroot, RHS.Target, RHS.Stdlib,
|
|
RHS.Specs);
|
|
}
|
|
|
|
DriverArgs(const tooling::CompileCommand &Cmd, llvm::StringRef File) {
|
|
llvm::SmallString<128> Driver(Cmd.CommandLine.front());
|
|
// Driver is a not a single executable name but instead a path (either
|
|
// relative or absolute).
|
|
if (llvm::any_of(Driver,
|
|
[](char C) { return llvm::sys::path::is_separator(C); })) {
|
|
llvm::sys::fs::make_absolute(Cmd.Directory, Driver);
|
|
}
|
|
this->Driver = Driver.str().str();
|
|
for (size_t I = 0, E = Cmd.CommandLine.size(); I < E; ++I) {
|
|
llvm::StringRef Arg = Cmd.CommandLine[I];
|
|
|
|
// Look for Language related flags.
|
|
if (Arg.consume_front("-x")) {
|
|
if (Arg.empty() && I + 1 < E)
|
|
Lang = Cmd.CommandLine[I + 1];
|
|
else
|
|
Lang = Arg.str();
|
|
}
|
|
// Look for standard/builtin includes.
|
|
else if (Arg == "-nostdinc" || Arg == "--no-standard-includes")
|
|
StandardIncludes = false;
|
|
else if (Arg == "-nostdinc++")
|
|
StandardCXXIncludes = false;
|
|
// Figure out sysroot
|
|
else if (Arg.consume_front("--sysroot")) {
|
|
if (Arg.consume_front("="))
|
|
Sysroot = Arg.str();
|
|
else if (Arg.empty() && I + 1 < E)
|
|
Sysroot = Cmd.CommandLine[I + 1];
|
|
} else if (Arg.consume_front("-isysroot")) {
|
|
if (Arg.empty() && I + 1 < E)
|
|
ISysroot = Cmd.CommandLine[I + 1];
|
|
else
|
|
ISysroot = Arg.str();
|
|
} else if (Arg.consume_front("--target=")) {
|
|
Target = Arg.str();
|
|
} else if (Arg.consume_front("-target")) {
|
|
if (Arg.empty() && I + 1 < E)
|
|
Target = Cmd.CommandLine[I + 1];
|
|
} else if (Arg.consume_front("--stdlib")) {
|
|
if (Arg.consume_front("="))
|
|
Stdlib = Arg.str();
|
|
else if (Arg.empty() && I + 1 < E)
|
|
Stdlib = Cmd.CommandLine[I + 1];
|
|
} else if (Arg.consume_front("-stdlib=")) {
|
|
Stdlib = Arg.str();
|
|
} else if (Arg.starts_with("-specs=")) {
|
|
// clang requires a single token like `-specs=file` or `--specs=file`,
|
|
// but gcc will accept two tokens like `--specs file`. Since the
|
|
// compilation database is presumably correct, we just forward the flags
|
|
// as-is.
|
|
Specs.push_back(Arg.str());
|
|
} else if (Arg.starts_with("--specs=")) {
|
|
Specs.push_back(Arg.str());
|
|
} else if (Arg == "--specs" && I + 1 < E) {
|
|
Specs.push_back(Arg.str());
|
|
Specs.push_back(Cmd.CommandLine[I + 1]);
|
|
}
|
|
}
|
|
|
|
// Downgrade objective-c++-header (used in clangd's fallback flags for .h
|
|
// files) to c++-header, as some drivers may fail to run the extraction
|
|
// command if it contains `-xobjective-c++-header` and objective-c++ support
|
|
// is not installed.
|
|
// In practice, we don't see different include paths for the two on
|
|
// clang+mac, which is the most common objectve-c compiler.
|
|
if (Lang == "objective-c++-header") {
|
|
Lang = "c++-header";
|
|
}
|
|
|
|
// If language is not explicit in the flags, infer from the file.
|
|
// This is important as we want to cache each language separately.
|
|
if (Lang.empty()) {
|
|
llvm::StringRef Ext = llvm::sys::path::extension(File).trim('.');
|
|
auto Type = driver::types::lookupTypeForExtension(Ext);
|
|
if (Type == driver::types::TY_INVALID) {
|
|
elog("System include extraction: invalid file type for {0}", Ext);
|
|
} else {
|
|
Lang = driver::types::getTypeName(Type);
|
|
}
|
|
}
|
|
}
|
|
llvm::SmallVector<llvm::StringRef> render() const {
|
|
// FIXME: Don't treat lang specially?
|
|
assert(!Lang.empty());
|
|
llvm::SmallVector<llvm::StringRef> Args = {"-x", Lang};
|
|
if (!StandardIncludes)
|
|
Args.push_back("-nostdinc");
|
|
if (!StandardCXXIncludes)
|
|
Args.push_back("-nostdinc++");
|
|
if (!Sysroot.empty())
|
|
Args.append({"--sysroot", Sysroot});
|
|
if (!ISysroot.empty())
|
|
Args.append({"-isysroot", ISysroot});
|
|
if (!Target.empty())
|
|
Args.append({"-target", Target});
|
|
if (!Stdlib.empty())
|
|
Args.append({"--stdlib", Stdlib});
|
|
|
|
for (llvm::StringRef Spec : Specs) {
|
|
Args.push_back(Spec);
|
|
}
|
|
|
|
return Args;
|
|
}
|
|
|
|
static DriverArgs getEmpty() { return {}; }
|
|
|
|
private:
|
|
DriverArgs() = default;
|
|
};
|
|
} // namespace
|
|
} // namespace clang::clangd
|
|
namespace llvm {
|
|
using DriverArgs = clang::clangd::DriverArgs;
|
|
template <> struct DenseMapInfo<DriverArgs> {
|
|
static DriverArgs getEmptyKey() {
|
|
auto Driver = DriverArgs::getEmpty();
|
|
Driver.Driver = "EMPTY_KEY";
|
|
return Driver;
|
|
}
|
|
static DriverArgs getTombstoneKey() {
|
|
auto Driver = DriverArgs::getEmpty();
|
|
Driver.Driver = "TOMBSTONE_KEY";
|
|
return Driver;
|
|
}
|
|
static unsigned getHashValue(const DriverArgs &Val) {
|
|
unsigned FixedFieldsHash = llvm::hash_value(std::tuple{
|
|
Val.Driver,
|
|
Val.StandardIncludes,
|
|
Val.StandardCXXIncludes,
|
|
Val.Lang,
|
|
Val.Sysroot,
|
|
Val.ISysroot,
|
|
Val.Target,
|
|
Val.Stdlib,
|
|
});
|
|
|
|
unsigned SpecsHash =
|
|
llvm::hash_combine_range(Val.Specs.begin(), Val.Specs.end());
|
|
|
|
return llvm::hash_combine(FixedFieldsHash, SpecsHash);
|
|
}
|
|
static bool isEqual(const DriverArgs &LHS, const DriverArgs &RHS) {
|
|
return LHS == RHS;
|
|
}
|
|
};
|
|
} // namespace llvm
|
|
namespace clang::clangd {
|
|
namespace {
|
|
bool isValidTarget(llvm::StringRef Triple) {
|
|
std::shared_ptr<TargetOptions> TargetOpts(new TargetOptions);
|
|
TargetOpts->Triple = Triple.str();
|
|
DiagnosticsEngine Diags(new DiagnosticIDs, new DiagnosticOptions,
|
|
new IgnoringDiagConsumer);
|
|
llvm::IntrusiveRefCntPtr<TargetInfo> Target =
|
|
TargetInfo::CreateTargetInfo(Diags, TargetOpts);
|
|
return bool(Target);
|
|
}
|
|
|
|
std::optional<DriverInfo> parseDriverOutput(llvm::StringRef Output) {
|
|
DriverInfo Info;
|
|
const char SIS[] = "#include <...> search starts here:";
|
|
const char SIE[] = "End of search list.";
|
|
const char TS[] = "Target: ";
|
|
llvm::SmallVector<llvm::StringRef> Lines;
|
|
Output.split(Lines, '\n', /*MaxSplit=*/-1, /*KeepEmpty=*/false);
|
|
|
|
enum {
|
|
Initial, // Initial state: searching for target or includes list.
|
|
IncludesExtracting, // Includes extracting.
|
|
Done // Includes and target extraction done.
|
|
} State = Initial;
|
|
bool SeenIncludes = false;
|
|
bool SeenTarget = false;
|
|
for (auto *It = Lines.begin(); State != Done && It != Lines.end(); ++It) {
|
|
auto Line = *It;
|
|
switch (State) {
|
|
case Initial:
|
|
if (!SeenIncludes && Line.trim() == SIS) {
|
|
SeenIncludes = true;
|
|
State = IncludesExtracting;
|
|
} else if (!SeenTarget && Line.trim().starts_with(TS)) {
|
|
SeenTarget = true;
|
|
llvm::StringRef TargetLine = Line.trim();
|
|
TargetLine.consume_front(TS);
|
|
// Only detect targets that clang understands
|
|
if (!isValidTarget(TargetLine)) {
|
|
elog("System include extraction: invalid target \"{0}\", ignoring",
|
|
TargetLine);
|
|
} else {
|
|
Info.Target = TargetLine.str();
|
|
vlog("System include extraction: target extracted: \"{0}\"",
|
|
TargetLine);
|
|
}
|
|
}
|
|
break;
|
|
case IncludesExtracting:
|
|
if (Line.trim() == SIE) {
|
|
State = SeenTarget ? Done : Initial;
|
|
} else {
|
|
Info.SystemIncludes.push_back(Line.trim().str());
|
|
vlog("System include extraction: adding {0}", Line);
|
|
}
|
|
break;
|
|
default:
|
|
llvm_unreachable("Impossible state of the driver output parser");
|
|
break;
|
|
}
|
|
}
|
|
if (!SeenIncludes) {
|
|
elog("System include extraction: start marker not found: {0}", Output);
|
|
return std::nullopt;
|
|
}
|
|
if (State == IncludesExtracting) {
|
|
elog("System include extraction: end marker missing: {0}", Output);
|
|
return std::nullopt;
|
|
}
|
|
return std::move(Info);
|
|
}
|
|
|
|
std::optional<std::string> run(llvm::ArrayRef<llvm::StringRef> Argv,
|
|
bool OutputIsStderr) {
|
|
llvm::SmallString<128> OutputPath;
|
|
if (auto EC = llvm::sys::fs::createTemporaryFile("system-includes", "clangd",
|
|
OutputPath)) {
|
|
elog("System include extraction: failed to create temporary file with "
|
|
"error {0}",
|
|
EC.message());
|
|
return std::nullopt;
|
|
}
|
|
auto CleanUp = llvm::make_scope_exit(
|
|
[&OutputPath]() { llvm::sys::fs::remove(OutputPath); });
|
|
|
|
std::optional<llvm::StringRef> Redirects[] = {{""}, {""}, {""}};
|
|
Redirects[OutputIsStderr ? 2 : 1] = OutputPath.str();
|
|
|
|
std::string ErrMsg;
|
|
if (int RC =
|
|
llvm::sys::ExecuteAndWait(Argv.front(), Argv, /*Env=*/std::nullopt,
|
|
Redirects, /*SecondsToWait=*/0,
|
|
/*MemoryLimit=*/0, &ErrMsg)) {
|
|
elog("System include extraction: driver execution failed with return code: "
|
|
"{0} - '{1}'. Args: [{2}]",
|
|
llvm::to_string(RC), ErrMsg, printArgv(Argv));
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto BufOrError = llvm::MemoryBuffer::getFile(OutputPath);
|
|
if (!BufOrError) {
|
|
elog("System include extraction: failed to read {0} with error {1}",
|
|
OutputPath, BufOrError.getError().message());
|
|
return std::nullopt;
|
|
}
|
|
return BufOrError.get().get()->getBuffer().str();
|
|
}
|
|
|
|
std::optional<DriverInfo>
|
|
extractSystemIncludesAndTarget(const DriverArgs &InputArgs,
|
|
const llvm::Regex &QueryDriverRegex) {
|
|
trace::Span Tracer("Extract system includes and target");
|
|
|
|
std::string Driver = InputArgs.Driver;
|
|
if (!llvm::sys::path::is_absolute(Driver)) {
|
|
auto DriverProgram = llvm::sys::findProgramByName(Driver);
|
|
if (DriverProgram) {
|
|
vlog("System include extraction: driver {0} expanded to {1}", Driver,
|
|
*DriverProgram);
|
|
Driver = *DriverProgram;
|
|
} else {
|
|
elog("System include extraction: driver {0} not found in PATH", Driver);
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
SPAN_ATTACH(Tracer, "driver", Driver);
|
|
SPAN_ATTACH(Tracer, "lang", InputArgs.Lang);
|
|
|
|
// If driver was "../foo" then having to allowlist "/path/a/../foo" rather
|
|
// than "/path/foo" is absurd.
|
|
// Allow either to match the allowlist, then proceed with "/path/a/../foo".
|
|
// This was our historical behavior, and it *could* resolve to something else.
|
|
llvm::SmallString<256> NoDots(Driver);
|
|
llvm::sys::path::remove_dots(NoDots, /*remove_dot_dot=*/true);
|
|
if (!QueryDriverRegex.match(Driver) && !QueryDriverRegex.match(NoDots)) {
|
|
vlog("System include extraction: not allowed driver {0}", Driver);
|
|
return std::nullopt;
|
|
}
|
|
|
|
llvm::SmallVector<llvm::StringRef> Args = {Driver, "-E", "-v"};
|
|
Args.append(InputArgs.render());
|
|
// Input needs to go after Lang flags.
|
|
Args.push_back("-");
|
|
auto Output = run(Args, /*OutputIsStderr=*/true);
|
|
if (!Output)
|
|
return std::nullopt;
|
|
|
|
std::optional<DriverInfo> Info = parseDriverOutput(*Output);
|
|
if (!Info)
|
|
return std::nullopt;
|
|
|
|
switch (Config::current().CompileFlags.BuiltinHeaders) {
|
|
case Config::BuiltinHeaderPolicy::Clangd: {
|
|
// The built-in headers are tightly coupled to parser builtins.
|
|
// (These are clang's "resource dir", GCC's GCC_INCLUDE_DIR.)
|
|
// We should keep using clangd's versions, so exclude the queried
|
|
// builtins. They're not specially marked in the -v output, but we can
|
|
// get the path with `$DRIVER -print-file-name=include`.
|
|
if (auto BuiltinHeaders = run({Driver, "-print-file-name=include"},
|
|
/*OutputIsStderr=*/false)) {
|
|
auto Path = llvm::StringRef(*BuiltinHeaders).trim();
|
|
if (!Path.empty() && llvm::sys::path::is_absolute(Path)) {
|
|
auto Size = Info->SystemIncludes.size();
|
|
llvm::erase(Info->SystemIncludes, Path);
|
|
vlog("System includes extractor: builtin headers {0} {1}", Path,
|
|
(Info->SystemIncludes.size() != Size)
|
|
? "excluded"
|
|
: "not found in driver's response");
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Config::BuiltinHeaderPolicy::QueryDriver:
|
|
vlog("System includes extractor: Using builtin headers from query driver.");
|
|
break;
|
|
}
|
|
|
|
log("System includes extractor: successfully executed {0}\n\tgot includes: "
|
|
"\"{1}\"\n\tgot target: \"{2}\"",
|
|
Driver, llvm::join(Info->SystemIncludes, ", "), Info->Target);
|
|
return Info;
|
|
}
|
|
|
|
tooling::CompileCommand &
|
|
addSystemIncludes(tooling::CompileCommand &Cmd,
|
|
llvm::ArrayRef<std::string> SystemIncludes) {
|
|
std::vector<std::string> ToAppend;
|
|
for (llvm::StringRef Include : SystemIncludes) {
|
|
// FIXME(kadircet): This doesn't work when we have "--driver-mode=cl"
|
|
ToAppend.push_back("-isystem");
|
|
ToAppend.push_back(Include.str());
|
|
}
|
|
if (!ToAppend.empty()) {
|
|
// Just append when `--` isn't present.
|
|
auto InsertAt = llvm::find(Cmd.CommandLine, "--");
|
|
Cmd.CommandLine.insert(InsertAt, std::make_move_iterator(ToAppend.begin()),
|
|
std::make_move_iterator(ToAppend.end()));
|
|
}
|
|
return Cmd;
|
|
}
|
|
|
|
tooling::CompileCommand &setTarget(tooling::CompileCommand &Cmd,
|
|
const std::string &Target) {
|
|
if (!Target.empty()) {
|
|
// We do not want to override existing target with extracted one.
|
|
for (llvm::StringRef Arg : Cmd.CommandLine) {
|
|
if (Arg == "-target" || Arg.starts_with("--target="))
|
|
return Cmd;
|
|
}
|
|
// Just append when `--` isn't present.
|
|
auto InsertAt = llvm::find(Cmd.CommandLine, "--");
|
|
Cmd.CommandLine.insert(InsertAt, "--target=" + Target);
|
|
}
|
|
return Cmd;
|
|
}
|
|
|
|
/// Converts a glob containing only ** or * into a regex.
|
|
std::string convertGlobToRegex(llvm::StringRef Glob) {
|
|
std::string RegText;
|
|
llvm::raw_string_ostream RegStream(RegText);
|
|
RegStream << '^';
|
|
for (size_t I = 0, E = Glob.size(); I < E; ++I) {
|
|
if (Glob[I] == '*') {
|
|
if (I + 1 < E && Glob[I + 1] == '*') {
|
|
// Double star, accept any sequence.
|
|
RegStream << ".*";
|
|
// Also skip the second star.
|
|
++I;
|
|
} else {
|
|
// Single star, accept any sequence without a slash.
|
|
RegStream << "[^/]*";
|
|
}
|
|
} else if (llvm::sys::path::is_separator(Glob[I]) &&
|
|
llvm::sys::path::is_separator('/') &&
|
|
llvm::sys::path::is_separator('\\')) {
|
|
RegStream << R"([/\\])"; // Accept either slash on windows.
|
|
} else {
|
|
RegStream << llvm::Regex::escape(Glob.substr(I, 1));
|
|
}
|
|
}
|
|
RegStream << '$';
|
|
return RegText;
|
|
}
|
|
|
|
/// Converts a glob containing only ** or * into a regex.
|
|
llvm::Regex convertGlobsToRegex(llvm::ArrayRef<std::string> Globs) {
|
|
assert(!Globs.empty() && "Globs cannot be empty!");
|
|
std::vector<std::string> RegTexts;
|
|
RegTexts.reserve(Globs.size());
|
|
for (llvm::StringRef Glob : Globs)
|
|
RegTexts.push_back(convertGlobToRegex(Glob));
|
|
|
|
// Tempting to pass IgnoreCase, but we don't know the FS sensitivity.
|
|
llvm::Regex Reg(llvm::join(RegTexts, "|"));
|
|
assert(Reg.isValid(RegTexts.front()) &&
|
|
"Created an invalid regex from globs");
|
|
return Reg;
|
|
}
|
|
|
|
/// Extracts system includes from a trusted driver by parsing the output of
|
|
/// include search path and appends them to the commands coming from underlying
|
|
/// compilation database.
|
|
class SystemIncludeExtractor {
|
|
public:
|
|
SystemIncludeExtractor(llvm::ArrayRef<std::string> QueryDriverGlobs)
|
|
: QueryDriverRegex(convertGlobsToRegex(QueryDriverGlobs)) {}
|
|
|
|
void operator()(tooling::CompileCommand &Cmd, llvm::StringRef File) const {
|
|
if (Cmd.CommandLine.empty())
|
|
return;
|
|
|
|
DriverArgs Args(Cmd, File);
|
|
if (Args.Lang.empty())
|
|
return;
|
|
if (auto Info = QueriedDrivers.get(Args, [&] {
|
|
return extractSystemIncludesAndTarget(Args, QueryDriverRegex);
|
|
})) {
|
|
setTarget(addSystemIncludes(Cmd, Info->SystemIncludes), Info->Target);
|
|
}
|
|
}
|
|
|
|
private:
|
|
// Caches includes extracted from a driver. Key is driver:lang.
|
|
Memoize<llvm::DenseMap<DriverArgs, std::optional<DriverInfo>>> QueriedDrivers;
|
|
llvm::Regex QueryDriverRegex;
|
|
};
|
|
} // namespace
|
|
|
|
SystemIncludeExtractorFn
|
|
getSystemIncludeExtractor(llvm::ArrayRef<std::string> QueryDriverGlobs) {
|
|
if (QueryDriverGlobs.empty())
|
|
return nullptr;
|
|
return SystemIncludeExtractor(QueryDriverGlobs);
|
|
}
|
|
|
|
} // namespace clang::clangd
|