diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt --- a/clang-tools-extra/clangd/CMakeLists.txt +++ b/clang-tools-extra/clangd/CMakeLists.txt @@ -41,6 +41,7 @@ ClangdServer.cpp CodeComplete.cpp CodeCompletionStrings.cpp + CompileCommands.cpp Compiler.cpp Context.cpp Diagnostics.cpp diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -485,8 +485,11 @@ llvm::makeArrayRef(ClangdServerOpts.QueryDriverGlobs), std::move(BaseCDB)); } + auto Mangler = CommandMangler::detect(); + if (ClangdServerOpts.ResourceDir) + Mangler.ResourceDir = *ClangdServerOpts.ResourceDir; CDB.emplace(BaseCDB.get(), Params.initializationOptions.fallbackFlags, - ClangdServerOpts.ResourceDir); + tooling::ArgumentsAdjuster(Mangler)); { // Switch caller's context with LSPServer's background context. Since we // rather want to propagate information from LSPServer's context into the diff --git a/clang-tools-extra/clangd/CompileCommands.h b/clang-tools-extra/clangd/CompileCommands.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/CompileCommands.h @@ -0,0 +1,42 @@ +//===--- CompileCommands.h - Manipulation of compile flags -------*- 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 +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/ArgumentsAdjusters.h" +#include "clang/Tooling/CompilationDatabase.h" +#include +#include + +namespace clang { +namespace clangd { + +// CommandMangler transforms compile commands from some external source +// for use in clangd. This means: +// - running the frontend only, stripping args regarding output files etc +// - forcing the use of clangd's builtin headers rather than clang's +// - resolving argv0 as cc1 expects +// - injecting -isysroot flags on mac as the system clang does +struct CommandMangler { + // Absolute path to clang. + llvm::Optional ClangPath; + // Directory containing builtin headers. + llvm::Optional ResourceDir; + // Root for searching for standard library (passed to -isysroot). + llvm::Optional Sysroot; + + static CommandMangler detect(); + static CommandMangler forTests(); + + void adjust(std::vector &Cmd) const; + explicit operator clang::tooling::ArgumentsAdjuster(); + +private: + CommandMangler() = default; +}; + +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/CompileCommands.cpp b/clang-tools-extra/clangd/CompileCommands.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/CompileCommands.cpp @@ -0,0 +1,185 @@ +//===--- CompileCommands.cpp ----------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "CompileCommands.h" +#include "Logger.h" +#include "clang/Frontend/CompilerInvocation.h" +#include "clang/Tooling/ArgumentsAdjusters.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/FileUtilities.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Program.h" + +namespace clang { +namespace clangd { +namespace { + +// Query apple's `xcrun` launcher, which is the source of truth for "how should" +// clang be invoked on this system. +llvm::Optional queryXcrun(llvm::ArrayRef Argv) { + auto Xcrun = llvm::sys::findProgramByName("xcrun"); + if (!Xcrun) { + log("Couldn't find xcrun. Hopefully you have a non-apple toolchain..."); + return llvm::None; + } + llvm::SmallString<64> OutFile; + llvm::sys::fs::createTemporaryFile("clangd-xcrun", "", OutFile); + llvm::FileRemover OutRemover(OutFile); + llvm::Optional Redirects[3] = { + /*stdin=*/{""}, /*stdout=*/{OutFile}, /*stderr=*/{""}}; + vlog("Invoking {0} to find clang installation", *Xcrun); + int Ret = llvm::sys::ExecuteAndWait(*Xcrun, Argv, + /*Env=*/llvm::None, Redirects, + /*SecondsToWait=*/10); + if (Ret != 0) { + log("xcrun exists but failed with code {0}. " + "If you have a non-apple toolchain, this is OK. " + "Otherwise, try xcode-select --install.", + Ret); + return llvm::None; + } + + auto Buf = llvm::MemoryBuffer::getFile(OutFile); + if (!Buf) { + log("Can't read xcrun output: {0}", Buf.getError().message()); + return llvm::None; + } + StringRef Path = Buf->get()->getBuffer().trim(); + if (Path.empty()) { + log("xcrun produced no output"); + return llvm::None; + } + return Path.str(); +} + +// Resolve symlinks if possible. +std::string resolve(std::string Path) { + llvm::SmallString<128> Resolved; + if (llvm::sys::fs::real_path(Path, Resolved)) + return Path; // On error; + return Resolved.str(); +} + +// Get a plausible full `clang` path. +// This is used in the fallback compile command, or when the CDB returns a +// generic driver with no path. +std::string detectClangPath() { + // The driver and/or cc1 sometimes depend on the binary name to compute + // useful things like the standard library location. + // We need to emulate what clang on this system is likely to see. + // cc1 in particular looks at the "real path" of the running process, and + // so if /usr/bin/clang is a symlink, it sees the resolved path. + // clangd doesn't have that luxury, so we resolve symlinks ourselves. + + // On Mac, `which clang` is /usr/bin/clang. It runs `xcrun clang`, which knows + // where the real clang is kept. We need to do the same thing, + // because cc1 (not the driver!) will find libc++ relative to argv[0]. +#ifdef __APPLE__ + if (auto MacClang = queryXcrun({"xcrun", "--find", "clang"})) + return resolve(std::move(*MacClang)); +#endif + // On other platforms, just look for compilers on the PATH. + for (const char *Name : {"clang", "gcc", "cc"}) + if (auto PathCC = llvm::sys::findProgramByName(Name)) + return resolve(std::move(*PathCC)); + // Fallback: a nonexistent 'clang' binary next to clangd. + static int Dummy; + std::string ClangdExecutable = + llvm::sys::fs::getMainExecutable("clangd", (void *)&Dummy); + SmallString<128> ClangPath; + ClangPath = llvm::sys::path::parent_path(ClangdExecutable); + llvm::sys::path::append(ClangPath, "clang"); + return ClangPath.str(); +} + +// On mac, /usr/bin/clang sets SDKROOT and then invokes the real clang. +// The effect of this is to set -isysroot correctly. We do the same. +const llvm::Optional detectSysroot() { +#ifndef __APPLE__ + return llvm::None; +#endif + + // SDKROOT overridden in environment, respect it. Driver will set isysroot. + if (::getenv("SDKROOT")) + return llvm::None; + return queryXcrun({"xcrun", "--show-sdk-path"}); + return llvm::None; +} + +std::string detectStandardResourceDir() { + static int Dummy; // Just an address in this process. + return CompilerInvocation::GetResourcesPath("clangd", (void *)&Dummy); +} + +} // namespace + +CommandMangler CommandMangler::detect() { + CommandMangler Result; + Result.ClangPath = detectClangPath(); + Result.ResourceDir = detectStandardResourceDir(); + Result.Sysroot = detectSysroot(); + return Result; +} + +CommandMangler CommandMangler::forTests() { + return CommandMangler(); +} + +void CommandMangler::adjust(std::vector &Cmd) const { + // Check whether the flag exists, either as -flag or -flag=* + auto Has = [&](llvm::StringRef Flag) { + for (llvm::StringRef Arg : Cmd) { + if (Arg.consume_front(Flag) && (Arg.empty() || Arg[0] == '=')) + return true; + } + return false; + }; + + // clangd should not write files to disk, including dependency files + // requested on the command line. + Cmd = tooling::getClangStripDependencyFileAdjuster()(Cmd, ""); + // Strip plugin related command line arguments. Clangd does + // not support plugins currently. Therefore it breaks if + // compiler tries to load plugins. + Cmd = tooling::getStripPluginsAdjuster()(Cmd, ""); + Cmd = tooling::getClangSyntaxOnlyAdjuster()(Cmd, ""); + + if (ResourceDir && !Has("-resource-dir")) + Cmd.push_back(("-resource-dir=" + *ResourceDir)); + + if (Sysroot && !Has("-isysroot")) { + Cmd.push_back("-isysroot"); + Cmd.push_back(*Sysroot); + } + + // If the driver is a generic name like "g++" with no path, add a clang path. + // This makes it easier for us to find the standard libraries on mac. + if (ClangPath && llvm::sys::path::is_absolute(*ClangPath) && !Cmd.empty()) { + std::string &Driver = Cmd.front(); + if (Driver == "clang" || Driver == "clang++" || Driver == "gcc" || + Driver == "g++" || Driver == "cc" || Driver == "c++") { + llvm::SmallString<128> QualifiedDriver = + llvm::sys::path::parent_path(*ClangPath); + llvm::sys::path::append(QualifiedDriver, Driver); + Driver = QualifiedDriver.str(); + } + } +} + +CommandMangler::operator clang::tooling::ArgumentsAdjuster() { + return [Mangler{*this}](const std::vector &Args, + llvm::StringRef File) { + auto Result = Args; + Mangler.adjust(Result); + return Result; + }; +} + +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/GlobalCompilationDatabase.h b/clang-tools-extra/clangd/GlobalCompilationDatabase.h --- a/clang-tools-extra/clangd/GlobalCompilationDatabase.h +++ b/clang-tools-extra/clangd/GlobalCompilationDatabase.h @@ -9,8 +9,10 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_GLOBALCOMPILATIONDATABASE_H #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_GLOBALCOMPILATIONDATABASE_H +#include "CompileCommands.h" #include "Function.h" #include "Path.h" +#include "clang/Tooling/ArgumentsAdjusters.h" #include "clang/Tooling/CompilationDatabase.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/StringMap.h" @@ -19,7 +21,6 @@ #include namespace clang { - namespace clangd { class Logger; @@ -118,15 +119,17 @@ getQueryDriverDatabase(llvm::ArrayRef QueryDriverGlobs, std::unique_ptr Base); + /// Wraps another compilation database, and supports overriding the commands /// using an in-memory mapping. class OverlayCDB : public GlobalCompilationDatabase { public: // Base may be null, in which case no entries are inherited. // FallbackFlags are added to the fallback compile command. + // Adjuster is applied to all commands, fallback or not. OverlayCDB(const GlobalCompilationDatabase *Base, std::vector FallbackFlags = {}, - llvm::Optional ResourceDir = llvm::None); + tooling::ArgumentsAdjuster Adjuster = nullptr); llvm::Optional getCompileCommand(PathRef File) const override; @@ -142,7 +145,7 @@ mutable std::mutex Mutex; llvm::StringMap Commands; /* GUARDED_BY(Mut) */ const GlobalCompilationDatabase *Base; - std::string ResourceDir; + tooling::ArgumentsAdjuster ArgsAdjuster; std::vector FallbackFlags; CommandChanged::Subscription BaseChanged; }; diff --git a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp --- a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp +++ b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp @@ -18,7 +18,9 @@ #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" #include "llvm/Support/FileSystem.h" +#include "llvm/Support/FileUtilities.h" #include "llvm/Support/Path.h" +#include "llvm/Support/Program.h" #include #include #include @@ -27,30 +29,6 @@ namespace clangd { namespace { -void adjustArguments(tooling::CompileCommand &Cmd, - llvm::StringRef ResourceDir) { - tooling::ArgumentsAdjuster ArgsAdjuster = tooling::combineAdjusters( - // clangd should not write files to disk, including dependency files - // requested on the command line. - tooling::getClangStripDependencyFileAdjuster(), - // Strip plugin related command line arguments. Clangd does - // not support plugins currently. Therefore it breaks if - // compiler tries to load plugins. - tooling::combineAdjusters(tooling::getStripPluginsAdjuster(), - tooling::getClangSyntaxOnlyAdjuster())); - - Cmd.CommandLine = ArgsAdjuster(Cmd.CommandLine, Cmd.Filename); - // Inject the resource dir. - // FIXME: Don't overwrite it if it's already there. - if (!ResourceDir.empty()) - Cmd.CommandLine.push_back(("-resource-dir=" + ResourceDir).str()); -} - -std::string getStandardResourceDir() { - static int Dummy; // Just an address in this process. - return CompilerInvocation::GetResourcesPath("clangd", (void *)&Dummy); -} - // Runs the given action on all parent directories of filename, starting from // deepest directory and going up to root. Stops whenever action succeeds. void actOnAllParentDirectories(PathRef FileName, @@ -63,19 +41,9 @@ } // namespace -static std::string getFallbackClangPath() { - static int Dummy; - std::string ClangdExecutable = - llvm::sys::fs::getMainExecutable("clangd", (void *)&Dummy); - SmallString<128> ClangPath; - ClangPath = llvm::sys::path::parent_path(ClangdExecutable); - llvm::sys::path::append(ClangPath, "clang"); - return ClangPath.str(); -} - tooling::CompileCommand GlobalCompilationDatabase::getFallbackCommand(PathRef File) const { - std::vector Argv = {getFallbackClangPath()}; + std::vector Argv = {"clang"}; // Clang treats .h files as C by default and files without extension as linker // input, resulting in unhelpful diagnostics. // Parsing as Objective C++ is friendly to more cases. @@ -263,9 +231,8 @@ OverlayCDB::OverlayCDB(const GlobalCompilationDatabase *Base, std::vector FallbackFlags, - llvm::Optional ResourceDir) - : Base(Base), ResourceDir(ResourceDir ? std::move(*ResourceDir) - : getStandardResourceDir()), + tooling::ArgumentsAdjuster Adjuster) + : Base(Base), ArgsAdjuster(std::move(Adjuster)), FallbackFlags(std::move(FallbackFlags)) { if (Base) BaseChanged = Base->watch([this](const std::vector Changes) { @@ -286,7 +253,8 @@ Cmd = Base->getCompileCommand(File); if (!Cmd) return llvm::None; - adjustArguments(*Cmd, ResourceDir); + if (ArgsAdjuster) + Cmd->CommandLine = ArgsAdjuster(Cmd->CommandLine, Cmd->Filename); return Cmd; } @@ -296,7 +264,8 @@ std::lock_guard Lock(Mutex); Cmd.CommandLine.insert(Cmd.CommandLine.end(), FallbackFlags.begin(), FallbackFlags.end()); - adjustArguments(Cmd, ResourceDir); + if (ArgsAdjuster) + Cmd.CommandLine = ArgsAdjuster(Cmd.CommandLine, Cmd.Filename); return Cmd; } diff --git a/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp b/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp --- a/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp +++ b/clang-tools-extra/clangd/unittests/BackgroundIndexTests.cpp @@ -532,8 +532,7 @@ llvm::StringMap Storage; size_t CacheHits = 0; MemoryShardStorage MSS(Storage, CacheHits); - OverlayCDB CDB(/*Base=*/nullptr, /*FallbackFlags=*/{}, - /*ResourceDir=*/std::string("")); + OverlayCDB CDB(/*Base=*/nullptr, /*FallbackFlags=*/{}); BackgroundIndex Idx(Context::empty(), FS, CDB, [&](llvm::StringRef) { return &MSS; }); @@ -542,7 +541,7 @@ FS.Files[testPath("A.h")] = ""; Cmd.Filename = "../A.cc"; Cmd.Directory = testPath("build"); - Cmd.CommandLine = {"clang++", "../A.cc", "-fsyntax-only"}; + Cmd.CommandLine = {"/bin/clang++", "../A.cc", "-fsyntax-only"}; CDB.setCompileCommand(testPath("build/../A.cc"), Cmd); ASSERT_TRUE(Idx.blockUntilIdleForTest()); @@ -558,7 +557,7 @@ // FIXME: Changing compile commands should be enough to invalidate the cache. FS.Files[testPath("A.cc")] = " "; - Cmd.CommandLine = {"clang++", "../A.cc", "-Dfoo", "-fsyntax-only"}; + Cmd.CommandLine = {"/bin/clang++", "../A.cc", "-Dfoo", "-fsyntax-only"}; CDB.setCompileCommand(testPath("build/../A.cc"), Cmd); ASSERT_TRUE(Idx.blockUntilIdleForTest()); diff --git a/clang-tools-extra/clangd/unittests/CMakeLists.txt b/clang-tools-extra/clangd/unittests/CMakeLists.txt --- a/clang-tools-extra/clangd/unittests/CMakeLists.txt +++ b/clang-tools-extra/clangd/unittests/CMakeLists.txt @@ -31,6 +31,7 @@ CodeCompleteTests.cpp CodeCompletionStringsTests.cpp CollectMacrosTests.cpp + CompileCommandsTests.cpp ContextTests.cpp DexTests.cpp DiagnosticsTests.cpp diff --git a/clang-tools-extra/clangd/unittests/ClangdTests.cpp b/clang-tools-extra/clangd/unittests/ClangdTests.cpp --- a/clang-tools-extra/clangd/unittests/ClangdTests.cpp +++ b/clang-tools-extra/clangd/unittests/ClangdTests.cpp @@ -959,28 +959,6 @@ } #endif -TEST_F(ClangdVFSTest, FlagsWithPlugins) { - MockFSProvider FS; - ErrorCheckingDiagConsumer DiagConsumer; - MockCompilationDatabase CDB; - CDB.ExtraClangFlags = { - "-Xclang", - "-add-plugin", - "-Xclang", - "random-plugin", - }; - OverlayCDB OCDB(&CDB); - ClangdServer Server(OCDB, FS, DiagConsumer, ClangdServer::optsForTest()); - - auto FooCpp = testPath("foo.cpp"); - const auto SourceContents = "int main() { return 0; }"; - FS.Files[FooCpp] = FooCpp; - Server.addDocument(FooCpp, SourceContents); - auto Result = dumpASTWithoutMemoryLocs(Server, FooCpp); - EXPECT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for diagnostics"; - EXPECT_NE(Result, ""); -} - TEST_F(ClangdVFSTest, FallbackWhenPreambleIsNotReady) { MockFSProvider FS; ErrorCheckingDiagConsumer DiagConsumer; diff --git a/clang-tools-extra/clangd/unittests/CompileCommandsTests.cpp b/clang-tools-extra/clangd/unittests/CompileCommandsTests.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/unittests/CompileCommandsTests.cpp @@ -0,0 +1,99 @@ +//===-- CompileCommandsTests.cpp ------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "CompileCommands.h" + +#include "llvm/ADT/StringExtras.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace clang { +namespace clangd { +namespace { + +using ::testing::Contains; +using ::testing::ElementsAre; +using ::testing::HasSubstr; +using ::testing::Not; + +// Sadly, CommandMangler::detect(), which contains much of the logic, is +// a bunch of untested integration glue. We test the string manipulation here +// assuming its results are correct. + +// Make use of all features and assert the exact command we get out. +// Other tests just verify presence/absence of certain args. +TEST(CommandMangler, Everything) { + auto Mangler = CommandMangler::forTests(); + Mangler.ClangPath = "/fake/bin/clang"; + Mangler.ResourceDir = "/fake/resources"; + Mangler.Sysroot = "/fake/sysroot"; + std::vector Cmd = {"clang++", "-Xclang", "-load", "-Xclang", + "plugin", "-MF", "dep", "foo.cc"}; + Mangler.adjust(Cmd); + EXPECT_THAT(Cmd, ElementsAre("/fake/bin/clang++", "foo.cc", "-fsyntax-only", + "-resource-dir=/fake/resources", "-isysroot", + "/fake/sysroot")); +} + +TEST(CommandMangler, ResourceDir) { + auto Mangler = CommandMangler::forTests(); + Mangler.ResourceDir = "/fake/resources"; + std::vector Cmd = {"clang++", "foo.cc"}; + Mangler.adjust(Cmd); + EXPECT_THAT(Cmd, Contains("-resource-dir=/fake/resources")); +} + +TEST(CommandMangler, Sysroot) { + auto Mangler = CommandMangler::forTests(); + Mangler.Sysroot = "/fake/sysroot"; + + std::vector Cmd = {"clang++", "foo.cc"}; + Mangler.adjust(Cmd); + EXPECT_THAT(llvm::join(Cmd, " "), HasSubstr("-isysroot /fake/sysroot")); +} + +TEST(CommandMangler, StripPlugins) { + auto Mangler = CommandMangler::forTests(); + std::vector Cmd = {"clang++", "-Xclang", "-load", + "-Xclang", "plugin", "foo.cc"}; + Mangler.adjust(Cmd); + for (const char* Stripped : {"-Xclang", "-load", "plugin"}) + EXPECT_THAT(Cmd, Not(Contains(Stripped))); +} + +TEST(CommandMangler, StripOutput) { + auto Mangler = CommandMangler::forTests(); + std::vector Cmd = {"clang++", "-MF", "dependency", "-c", + "foo.cc"}; + Mangler.adjust(Cmd); + for (const char* Stripped : {"-MF", "dependency"}) + EXPECT_THAT(Cmd, Not(Contains(Stripped))); +} + +TEST(CommandMangler, ClangPath) { + auto Mangler = CommandMangler::forTests(); + Mangler.ClangPath = "/fake/clang"; + + std::vector Cmd = {"clang++", "foo.cc"}; + Mangler.adjust(Cmd); + EXPECT_EQ("/fake/clang++", Cmd.front()); + + Cmd = {"unknown-binary", "foo.cc"}; + Mangler.adjust(Cmd); + EXPECT_EQ("unknown-binary", Cmd.front()); + + Cmd = {"/path/clang++", "foo.cc"}; + Mangler.adjust(Cmd); + EXPECT_EQ("/path/clang++", Cmd.front()); +} + +} // namespace +} // namespace clangd +} // namespace clang + diff --git a/clang-tools-extra/clangd/unittests/GlobalCompilationDatabaseTests.cpp b/clang-tools-extra/clangd/unittests/GlobalCompilationDatabaseTests.cpp --- a/clang-tools-extra/clangd/unittests/GlobalCompilationDatabaseTests.cpp +++ b/clang-tools-extra/clangd/unittests/GlobalCompilationDatabaseTests.cpp @@ -42,19 +42,16 @@ DirectoryBasedGlobalCompilationDatabase DB(None); auto Cmd = DB.getFallbackCommand(testPath("foo/bar.cc")); EXPECT_EQ(Cmd.Directory, testPath("foo")); - EXPECT_THAT(Cmd.CommandLine, - ElementsAre(EndsWith("clang"), testPath("foo/bar.cc"))); + EXPECT_THAT(Cmd.CommandLine, ElementsAre("clang", testPath("foo/bar.cc"))); EXPECT_EQ(Cmd.Output, ""); // .h files have unknown language, so they are parsed liberally as obj-c++. Cmd = DB.getFallbackCommand(testPath("foo/bar.h")); - EXPECT_THAT(Cmd.CommandLine, - ElementsAre(EndsWith("clang"), "-xobjective-c++-header", - testPath("foo/bar.h"))); + EXPECT_THAT(Cmd.CommandLine, ElementsAre("clang", "-xobjective-c++-header", + testPath("foo/bar.h"))); Cmd = DB.getFallbackCommand(testPath("foo/bar")); - EXPECT_THAT(Cmd.CommandLine, - ElementsAre(EndsWith("clang"), "-xobjective-c++-header", - testPath("foo/bar"))); + EXPECT_THAT(Cmd.CommandLine, ElementsAre("clang", "-xobjective-c++-header", + testPath("foo/bar"))); } static tooling::CompileCommand cmd(llvm::StringRef File, llvm::StringRef Arg) { @@ -87,7 +84,7 @@ }; TEST_F(OverlayCDBTest, GetCompileCommand) { - OverlayCDB CDB(Base.get(), {}, std::string("")); + OverlayCDB CDB(Base.get()); EXPECT_THAT(CDB.getCompileCommand(testPath("foo.cc"))->CommandLine, AllOf(Contains(testPath("foo.cc")), Contains("-DA=1"))); EXPECT_EQ(CDB.getCompileCommand(testPath("missing.cc")), llvm::None); @@ -105,12 +102,11 @@ TEST_F(OverlayCDBTest, GetFallbackCommand) { OverlayCDB CDB(Base.get(), {"-DA=4"}); EXPECT_THAT(CDB.getFallbackCommand(testPath("bar.cc")).CommandLine, - ElementsAre("clang", "-DA=2", testPath("bar.cc"), "-DA=4", - "-fsyntax-only", StartsWith("-resource-dir"))); + ElementsAre("clang", "-DA=2", testPath("bar.cc"), "-DA=4")); } TEST_F(OverlayCDBTest, NoBase) { - OverlayCDB CDB(nullptr, {"-DA=6"}, std::string("")); + OverlayCDB CDB(nullptr, {"-DA=6"}); EXPECT_EQ(CDB.getCompileCommand(testPath("bar.cc")), None); auto Override = cmd(testPath("bar.cc"), "-DA=5"); CDB.setCompileCommand(testPath("bar.cc"), Override); @@ -118,8 +114,7 @@ Contains("-DA=5")); EXPECT_THAT(CDB.getFallbackCommand(testPath("foo.cc")).CommandLine, - ElementsAre(EndsWith("clang"), testPath("foo.cc"), "-DA=6", - "-fsyntax-only")); + ElementsAre("clang", testPath("foo.cc"), "-DA=6")); } TEST_F(OverlayCDBTest, Watch) { @@ -140,32 +135,32 @@ } TEST_F(OverlayCDBTest, Adjustments) { - OverlayCDB CDB(Base.get(), {}, std::string("")); + OverlayCDB CDB(Base.get(), {"-DFallback"}, + [](const std::vector &Cmd, llvm::StringRef File) { + auto Ret = Cmd; + Ret.push_back( + ("-DAdjust_" + llvm::sys::path::filename(File)).str()); + return Ret; + }); + // Command from underlying gets adjusted. auto Cmd = CDB.getCompileCommand(testPath("foo.cc")).getValue(); - // Delete the file name. - Cmd.CommandLine.pop_back(); - - // Check dependency file commands are dropped. - Cmd.CommandLine.push_back("-MF"); - Cmd.CommandLine.push_back("random-dependency"); - - // Check plugin-related commands are dropped. - Cmd.CommandLine.push_back("-Xclang"); - Cmd.CommandLine.push_back("-load"); - Cmd.CommandLine.push_back("-Xclang"); - Cmd.CommandLine.push_back("random-plugin"); - - Cmd.CommandLine.push_back("-DA=5"); - Cmd.CommandLine.push_back(Cmd.Filename); - - CDB.setCompileCommand(testPath("foo.cc"), Cmd); - - EXPECT_THAT(CDB.getCompileCommand(testPath("foo.cc"))->CommandLine, - AllOf(Contains("-fsyntax-only"), Contains("-DA=5"), - Contains(testPath("foo.cc")), Not(Contains("-MF")), - Not(Contains("random-dependency")), - Not(Contains("-Xclang")), Not(Contains("-load")), - Not(Contains("random-plugin")))); + EXPECT_THAT(Cmd.CommandLine, ElementsAre("clang", "-DA=1", testPath("foo.cc"), + "-DAdjust_foo.cc")); + + // Command from overlay gets adjusted. + tooling::CompileCommand BarCommand; + BarCommand.Filename = testPath("bar.cc"); + BarCommand.CommandLine = {"clang++", "-DB=1", testPath("bar.cc")}; + CDB.setCompileCommand(testPath("bar.cc"), BarCommand); + Cmd = CDB.getCompileCommand(testPath("bar.cc")).getValue(); + EXPECT_THAT( + Cmd.CommandLine, + ElementsAre("clang++", "-DB=1", testPath("bar.cc"), "-DAdjust_bar.cc")); + + // Fallback gets adjusted. + Cmd = CDB.getFallbackCommand("baz.cc"); + EXPECT_THAT(Cmd.CommandLine, ElementsAre("clang", "-DA=2", "baz.cc", + "-DFallback", "-DAdjust_baz.cc")); } TEST(GlobalCompilationDatabaseTest, DiscoveryWithNestedCDBs) {