#include "clang/Frontend/LangStandard.h"
#include "clang/Tooling/CompilationDatabase.h"
#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/Optional.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/Option/ArgList.h"
Optional<types::ID> Type;
// Standard specified by -std.
LangStandard::Kind Std = LangStandard::lang_unspecified;
+ // Whether the command line is for the cl-compatible driver.
+ bool ClangCLMode;
TransferableCommand(CompileCommand C)
- : Cmd(std::move(C)), Type(guessType(Cmd.Filename)) {
- std::vector<std::string> NewArgs = {Cmd.CommandLine.front()};
+ : Cmd(std::move(C)), Type(guessType(Cmd.Filename)),
+ ClangCLMode(checkIsCLMode(Cmd.CommandLine)) {
+ std::vector<std::string> OldArgs = std::move(Cmd.CommandLine);
+ Cmd.CommandLine.clear();
+
+ // Wrap the old arguments in an InputArgList.
+ llvm::opt::InputArgList ArgList;
+ {
+ SmallVector<const char *, 16> TmpArgv;
+ for (const std::string &S : OldArgs)
+ TmpArgv.push_back(S.c_str());
+ ArgList = {TmpArgv.begin(), TmpArgv.end()};
+ }
+
// Parse the old args in order to strip out and record unwanted flags.
+ // We parse each argument individually so that we can retain the exact
+ // spelling of each argument; re-rendering is lossy for aliased flags.
+ // E.g. in CL mode, /W4 maps to -Wall.
auto OptTable = clang::driver::createDriverOptTable();
- std::vector<const char *> Argv;
- for (unsigned I = 1; I < Cmd.CommandLine.size(); ++I)
- Argv.push_back(Cmd.CommandLine[I].c_str());
- unsigned MissingI, MissingC;
- auto ArgList = OptTable->ParseArgs(Argv, MissingI, MissingC);
- for (const auto *Arg : ArgList) {
- const auto &option = Arg->getOption();
+ Cmd.CommandLine.emplace_back(OldArgs.front());
+ for (unsigned Pos = 1; Pos < OldArgs.size();) {
+ using namespace driver::options;
+
+ const unsigned OldPos = Pos;
+ std::unique_ptr<llvm::opt::Arg> Arg(OptTable->ParseOneArg(
+ ArgList, Pos,
+ /* Include */ClangCLMode ? CoreOption | CLOption : 0,
+ /* Exclude */ClangCLMode ? 0 : CLOption));
+
+ if (!Arg)
+ continue;
+
+ const llvm::opt::Option &Opt = Arg->getOption();
+
// Strip input and output files.
- if (option.matches(clang::driver::options::OPT_INPUT) ||
- option.matches(clang::driver::options::OPT_o)) {
+ if (Opt.matches(OPT_INPUT) || Opt.matches(OPT_o) ||
+ (ClangCLMode && (Opt.matches(OPT__SLASH_Fa) ||
+ Opt.matches(OPT__SLASH_Fe) ||
+ Opt.matches(OPT__SLASH_Fi) ||
+ Opt.matches(OPT__SLASH_Fo))))
continue;
- }
+
// Strip -x, but record the overridden language.
- if (option.matches(clang::driver::options::OPT_x)) {
- for (const char *Value : Arg->getValues())
- Type = types::lookupTypeForTypeSpecifier(Value);
+ if (const auto GivenType = tryParseTypeArg(*Arg)) {
+ Type = *GivenType;
continue;
}
- // Strip --std, but record the value.
- if (option.matches(clang::driver::options::OPT_std_EQ)) {
- for (const char *Value : Arg->getValues()) {
- Std = llvm::StringSwitch<LangStandard::Kind>(Value)
-#define LANGSTANDARD(id, name, lang, desc, features) \
- .Case(name, LangStandard::lang_##id)
-#define LANGSTANDARD_ALIAS(id, alias) .Case(alias, LangStandard::lang_##id)
-#include "clang/Frontend/LangStandards.def"
- .Default(Std);
- }
+
+ // Strip -std, but record the value.
+ if (const auto GivenStd = tryParseStdArg(*Arg)) {
+ if (*GivenStd != LangStandard::lang_unspecified)
+ Std = *GivenStd;
continue;
}
- llvm::opt::ArgStringList ArgStrs;
- Arg->render(ArgList, ArgStrs);
- NewArgs.insert(NewArgs.end(), ArgStrs.begin(), ArgStrs.end());
+
+ Cmd.CommandLine.insert(Cmd.CommandLine.end(),
+ &OldArgs[OldPos], &OldArgs[Pos]);
}
- Cmd.CommandLine = std::move(NewArgs);
if (Std != LangStandard::lang_unspecified) // -std take precedence over -x
Type = toType(LangStandard::getLangStandardForKind(Std).getLanguage());
TargetType = types::onlyPrecompileType(TargetType) // header?
? types::lookupHeaderTypeForSourceType(*Type)
: *Type;
- Result.CommandLine.push_back("-x");
- Result.CommandLine.push_back(types::getTypeName(TargetType));
+ if (ClangCLMode) {
+ const StringRef Flag = toCLFlag(TargetType);
+ if (!Flag.empty())
+ Result.CommandLine.push_back(Flag);
+ } else {
+ Result.CommandLine.push_back("-x");
+ Result.CommandLine.push_back(types::getTypeName(TargetType));
+ }
}
// --std flag may only be transferred if the language is the same.
// We may consider "translating" these, e.g. c++11 -> c11.
if (Std != LangStandard::lang_unspecified && foldType(TargetType) == Type) {
- Result.CommandLine.push_back(
- "-std=" +
- std::string(LangStandard::getLangStandardForKind(Std).getName()));
+ Result.CommandLine.emplace_back((
+ llvm::Twine(ClangCLMode ? "/std:" : "-std=") +
+ LangStandard::getLangStandardForKind(Std).getName()).str());
}
Result.CommandLine.push_back(Filename);
return Result;
}
private:
+ // Determine whether the given command line is intended for the CL driver.
+ static bool checkIsCLMode(ArrayRef<std::string> CmdLine) {
+ // First look for --driver-mode.
+ for (StringRef S : llvm::reverse(CmdLine)) {
+ if (S.consume_front("--driver-mode="))
+ return S == "cl";
+ }
+
+ // Otherwise just check the clang executable file name.
+ return llvm::sys::path::stem(CmdLine.front()).endswith_lower("cl");
+ }
+
// Map the language from the --std flag to that of the -x flag.
static types::ID toType(InputKind::Language Lang) {
switch (Lang) {
return types::TY_INVALID;
}
}
+
+ // Convert a file type to the matching CL-style type flag.
+ static StringRef toCLFlag(types::ID Type) {
+ switch (Type) {
+ case types::TY_C:
+ case types::TY_CHeader:
+ return "/TC";
+ case types::TY_CXX:
+ case types::TY_CXXHeader:
+ return "/TP";
+ default:
+ return StringRef();
+ }
+ }
+
+ // Try to interpret the argument as a type specifier, e.g. '-x'.
+ Optional<types::ID> tryParseTypeArg(const llvm::opt::Arg &Arg) {
+ const llvm::opt::Option &Opt = Arg.getOption();
+ using namespace driver::options;
+ if (ClangCLMode) {
+ if (Opt.matches(OPT__SLASH_TC) || Opt.matches(OPT__SLASH_Tc))
+ return types::TY_C;
+ if (Opt.matches(OPT__SLASH_TP) || Opt.matches(OPT__SLASH_Tp))
+ return types::TY_CXX;
+ } else {
+ if (Opt.matches(driver::options::OPT_x))
+ return types::lookupTypeForTypeSpecifier(Arg.getValue());
+ }
+ return None;
+ }
+
+ // Try to interpret the argument as '-std='.
+ Optional<LangStandard::Kind> tryParseStdArg(const llvm::opt::Arg &Arg) {
+ using namespace driver::options;
+ if (Arg.getOption().matches(ClangCLMode ? OPT__SLASH_std : OPT_std_EQ)) {
+ return llvm::StringSwitch<LangStandard::Kind>(Arg.getValue())
+#define LANGSTANDARD(id, name, lang, ...) .Case(name, LangStandard::lang_##id)
+#define LANGSTANDARD_ALIAS(id, alias) .Case(alias, LangStandard::lang_##id)
+#include "clang/Frontend/LangStandards.def"
+#undef LANGSTANDARD_ALIAS
+#undef LANGSTANDARD
+ .Default(LangStandard::lang_unspecified);
+ }
+ return None;
+ }
};
// Given a filename, FileIndex picks the best matching file from the underlying
protected:
// Adds an entry to the underlying compilation database.
// A flag is injected: -D <File>, so the command used can be identified.
- void add(llvm::StringRef File, llvm::StringRef Flags = "") {
- llvm::SmallVector<StringRef, 8> Argv = {"clang", File, "-D", File};
+ void add(StringRef File, StringRef Clang, StringRef Flags) {
+ SmallVector<StringRef, 8> Argv = {Clang, File, "-D", File};
llvm::SplitString(Flags, Argv);
- llvm::SmallString<32> Dir;
+
+ SmallString<32> Dir;
llvm::sys::path::system_temp_directory(false, Dir);
+
Entries[path(File)].push_back(
{Dir, path(File), {Argv.begin(), Argv.end()}, "foo.o"});
}
+ void add(StringRef File, StringRef Flags = "") { add(File, "clang", Flags); }
// Turn a unix path fragment (foo/bar.h) into a native path (C:\tmp\foo\bar.h)
std::string path(llvm::SmallString<32> File) {
EXPECT_EQ(getCommand("foo/bar/baz/shout.C"), "clang -D FOO/BAR/BAZ/SHOUT.cc");
}
+TEST_F(InterpolateTest, Aliasing) {
+ add("foo.cpp", "-faligned-new");
+
+ // The interpolated command should keep the given flag as written, even though
+ // the flag is internally represented as an alias.
+ EXPECT_EQ(getCommand("foo.hpp"), "clang -D foo.cpp -faligned-new");
+}
+
+TEST_F(InterpolateTest, ClangCL) {
+ add("foo.cpp", "clang-cl", "/W4");
+
+ // Language flags should be added with CL syntax.
+ EXPECT_EQ(getCommand("foo.h"), "clang-cl -D foo.cpp /W4 /TP");
+}
+
+TEST_F(InterpolateTest, DriverModes) {
+ add("foo.cpp", "clang-cl", "--driver-mode=gcc");
+ add("bar.cpp", "clang", "--driver-mode=cl");
+
+ // --driver-mode overrides should be respected.
+ EXPECT_EQ(getCommand("foo.h"), "clang-cl -D foo.cpp --driver-mode=gcc -x c++-header");
+ EXPECT_EQ(getCommand("bar.h"), "clang -D bar.cpp --driver-mode=cl /TP");
+}
+
TEST(CompileCommandTest, EqualityOperator) {
CompileCommand CCRef("/foo/bar", "hello.c", {"a", "b"}, "hello.o");
CompileCommand CCTest = CCRef;