diff --git a/clang-tools-extra/clangd/Config.h b/clang-tools-extra/clangd/Config.h --- a/clang-tools-extra/clangd/Config.h +++ b/clang-tools-extra/clangd/Config.h @@ -26,6 +26,7 @@ #include "support/Context.h" #include "llvm/ADT/FunctionExtras.h" +#include "llvm/ADT/Optional.h" #include "llvm/ADT/StringMap.h" #include #include @@ -58,10 +59,22 @@ } CompileFlags; enum class BackgroundPolicy { Build, Skip }; + /// Describes an external index configuration. + struct ExternalIndexSpec { + enum { File, Server } Kind; + /// This is one of: + /// - Address of a clangd-index-server, in the form of "ip:port". + /// - Absolute path to an index produced by clangd-indexer. + std::string Location; + /// Absolute path to source root this index is associated with, uses + /// forward-slashes. + std::string MountPoint; + }; /// Controls background-index behavior. struct { /// Whether this TU should be indexed. BackgroundPolicy Background = BackgroundPolicy::Build; + llvm::Optional External; } Index; /// Style of the codebase. diff --git a/clang-tools-extra/clangd/ConfigCompile.cpp b/clang-tools-extra/clangd/ConfigCompile.cpp --- a/clang-tools-extra/clangd/ConfigCompile.cpp +++ b/clang-tools-extra/clangd/ConfigCompile.cpp @@ -27,11 +27,19 @@ #include "Config.h" #include "ConfigFragment.h" #include "ConfigProvider.h" +#include "Features.inc" #include "support/Logger.h" #include "support/Trace.h" +#include "llvm/ADT/None.h" +#include "llvm/ADT/Optional.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSwitch.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Format.h" +#include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Path.h" #include "llvm/Support/Regex.h" #include "llvm/Support/SMLoc.h" @@ -102,6 +110,27 @@ return Result; } + llvm::Optional makeAbsolute(Located Path, + llvm::StringLiteral Description, + llvm::sys::path::Style Style) { + if (llvm::sys::path::is_absolute(*Path)) + return *Path; + if (FragmentDirectory.empty()) { + diag(Error, + llvm::formatv( + "{0} must be an absolute path, because this fragment is not " + "associated with any directory.", + Description) + .str(), + Path.Range); + return llvm::None; + } + llvm::SmallString<256> AbsPath = llvm::StringRef(*Path); + llvm::sys::fs::make_absolute(FragmentDirectory, AbsPath); + llvm::sys::path::native(AbsPath, Style); + return AbsPath.str().str(); + } + // Helper with similar API to StringSwitch, for parsing enum values. template class EnumSwitch { FragmentCompiler &Outer; @@ -243,6 +272,59 @@ Out.Apply.push_back( [Val](const Params &, Config &C) { C.Index.Background = *Val; }); } + if (F.External) + compile(std::move(**F.External), F.External->Range); + } + + void compile(Fragment::IndexBlock::ExternalBlock &&External, + llvm::SMRange BlockRange) { +#ifndef CLANGD_ENABLE_REMOTE + if (External.Server) { + diag(Error, "Clangd isn't compiled with remote index support, ignoring " + "Server." External.Server->Range); + External.Server.reset(); + } +#endif + // Make sure exactly one of the Sources is set. + unsigned SourceCount = + External.File.hasValue() + External.Server.hasValue(); + if (SourceCount != 1) { + diag(Error, "Exactly one of File or Server must be set.", BlockRange); + return; + } + Config::ExternalIndexSpec Spec; + if (External.Server) { + Spec.Kind = Config::ExternalIndexSpec::Server; + Spec.Location = std::move(**External.Server); + } else if (External.File) { + Spec.Kind = Config::ExternalIndexSpec::File; + auto AbsPath = makeAbsolute(std::move(*External.File), "File", + llvm::sys::path::Style::native); + if (!AbsPath) + return; + Spec.Location = std::move(*AbsPath); + } + // Make sure MountPoint is an absolute path with forward slashes. + if (!External.MountPoint) + External.MountPoint.emplace(FragmentDirectory); + if ((**External.MountPoint).empty()) { + diag(Error, "A mountpoint is required.", BlockRange); + return; + } + auto AbsPath = makeAbsolute(std::move(*External.MountPoint), "MountPoint", + llvm::sys::path::Style::posix); + if (!AbsPath) + return; + Spec.MountPoint = std::move(*AbsPath); + Out.Apply.push_back([Spec(std::move(Spec))](const Params &P, Config &C) { + if (!P.Path.startswith(Spec.MountPoint)) + return; + C.Index.External = Spec; + // Disable background indexing for the files under the mountpoint. + // Note that this will overwrite statements in any previous fragments + // (including the current one). + C.Index.Background = Config::BackgroundPolicy::Skip; + }); } void compile(Fragment::StyleBlock &&F) { diff --git a/clang-tools-extra/clangd/unittests/ConfigCompileTests.cpp b/clang-tools-extra/clangd/unittests/ConfigCompileTests.cpp --- a/clang-tools-extra/clangd/unittests/ConfigCompileTests.cpp +++ b/clang-tools-extra/clangd/unittests/ConfigCompileTests.cpp @@ -9,9 +9,13 @@ #include "Config.h" #include "ConfigFragment.h" #include "ConfigTesting.h" +#include "Features.inc" #include "TestFS.h" +#include "llvm/ADT/None.h" +#include "llvm/ADT/Optional.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Path.h" +#include "llvm/Support/SourceMgr.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include @@ -20,6 +24,8 @@ namespace clangd { namespace config { namespace { +using ::testing::AllOf; +using ::testing::Contains; using ::testing::ElementsAre; using ::testing::IsEmpty; using ::testing::SizeIs; @@ -196,6 +202,104 @@ "0"); } +TEST_F(ConfigCompileTests, ExternalBlockWarnOnMultipleSource) { + Fragment::IndexBlock::ExternalBlock External; + External.File.emplace(""); + External.Server.emplace(""); + Frag.Index.External = std::move(External); + compileAndApply(); + llvm::StringLiteral ExpectedDiag = +#ifdef CLANGD_ENABLE_REMOTE + "Exactly one of File or Server must be set."; +#else + "Clangd isn't compiled with remote index support, ignoring Server."; +#endif + EXPECT_THAT(Diags.Diagnostics, + Contains(AllOf(DiagMessage(ExpectedDiag), + DiagKind(llvm::SourceMgr::DK_Error)))); +} + +TEST_F(ConfigCompileTests, ExternalBlockErrOnNoSource) { + Frag.Index.External.emplace(Fragment::IndexBlock::ExternalBlock{}); + compileAndApply(); + EXPECT_THAT( + Diags.Diagnostics, + Contains(AllOf(DiagMessage("Exactly one of File or Server must be set."), + DiagKind(llvm::SourceMgr::DK_Error)))); +} + +TEST_F(ConfigCompileTests, ExternalBlockDisablesBackgroundIndex) { + Parm.Path = "/foo/bar/baz.h"; + Frag.Index.Background.emplace("Build"); + Fragment::IndexBlock::ExternalBlock External; + External.File.emplace("/foo"); + External.MountPoint.emplace("/foo/bar"); + Frag.Index.External = std::move(External); + compileAndApply(); + EXPECT_EQ(Conf.Index.Background, Config::BackgroundPolicy::Skip); +} + +TEST_F(ConfigCompileTests, ExternalBlockMountPoint) { + auto GetFrag = [](llvm::StringRef Directory, + llvm::Optional MountPoint) { + Fragment Frag; + Frag.Source.Directory = Directory.str(); + Fragment::IndexBlock::ExternalBlock External; + External.File.emplace("/foo"); + if (MountPoint) + External.MountPoint.emplace(*MountPoint); + Frag.Index.External = std::move(External); + return Frag; + }; + + Parm.Path = "/foo/bar.h"; + // Non-absolute MountPoint without a directory raises an error. + Frag = GetFrag("", "foo"); + compileAndApply(); + ASSERT_THAT( + Diags.Diagnostics, + ElementsAre( + AllOf(DiagMessage("MountPoint must be an absolute path, because this " + "fragment is not associated with any directory."), + DiagKind(llvm::SourceMgr::DK_Error)))); + ASSERT_FALSE(Conf.Index.External); + + // Ok when relative. + Frag = GetFrag("/", "foo"); + compileAndApply(); + ASSERT_THAT(Diags.Diagnostics, IsEmpty()); + ASSERT_TRUE(Conf.Index.External); + EXPECT_THAT(Conf.Index.External->MountPoint, "/foo"); + + // None defaults to ".". + Frag = GetFrag("/", llvm::None); + compileAndApply(); + ASSERT_THAT(Diags.Diagnostics, IsEmpty()); + ASSERT_TRUE(Conf.Index.External); + EXPECT_THAT(Conf.Index.External->MountPoint, "/"); + + // Without a file, external index is empty. + Parm.Path = ""; + Frag = GetFrag("", "/foo"); + compileAndApply(); + ASSERT_THAT(Diags.Diagnostics, IsEmpty()); + ASSERT_FALSE(Conf.Index.External); + + // File outside MountPoint, no index. + Parm.Path = "/bar/baz.h"; + Frag = GetFrag("", "/foo"); + compileAndApply(); + ASSERT_THAT(Diags.Diagnostics, IsEmpty()); + ASSERT_FALSE(Conf.Index.External); + + // File under MountPoint, index should be set. + Parm.Path = "/foo/baz.h"; + Frag = GetFrag("", "/foo"); + compileAndApply(); + ASSERT_THAT(Diags.Diagnostics, IsEmpty()); + ASSERT_TRUE(Conf.Index.External); + EXPECT_THAT(Conf.Index.External->MountPoint, "/foo"); +} } // namespace } // namespace config } // namespace clangd