Skip to content

Commit cee80c0

Browse files
committed
[clangd] Pull installed gRPC and introduce clangd-remote-(server|client)
Summary: This patch allows using installed gRPC to build two simple tools which currently provide the functionality of looking up the symbol by name. remote-index-client is a simplified version of dexp which connects to remote-index-server passes lookup requests. I also significantly reduced the scope of this patch to prevent large changelist and more bugs. The next steps would be: * Extending Protocol for deep copies of Symbol and inherit RemoteIndex from Index to unify the interfaces * Make remote-index-server more generic and merge the remote index client with dexp * Modify Clangd to allow using remote index instead of the local one for all global index requests Reviewers: sammccall Reviewed By: sammccall Subscribers: mgorny, ilya-biryukov, MaskRay, jkorous, arphaman, kadircet, usaxena95, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D77794
1 parent 1a3e89a commit cee80c0

File tree

9 files changed

+376
-0
lines changed

9 files changed

+376
-0
lines changed

clang-tools-extra/clangd/CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,3 +153,12 @@ if(CLANG_INCLUDE_TESTS)
153153
add_subdirectory(test)
154154
add_subdirectory(unittests)
155155
endif()
156+
157+
# FIXME(kirillbobyrev): Document this in the LLVM docs once remote index is stable.
158+
option(CLANGD_ENABLE_REMOTE "Use gRPC library to enable remote index support for Clangd" OFF)
159+
set(GRPC_INSTALL_PATH "" CACHE PATH "Path to gRPC library manual installation.")
160+
161+
if (CLANGD_ENABLE_REMOTE)
162+
include(FindGRPC)
163+
add_subdirectory(index/remote)
164+
endif()
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
generate_grpc_protos(RemoteIndexProtos "Index.proto")
2+
3+
include_directories("${CMAKE_CURRENT_BINARY_DIR}")
4+
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../)
5+
6+
add_subdirectory(client)
7+
add_subdirectory(server)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//===--- Index.proto - Remote index Protocol Buffers definition -----------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
syntax = "proto3";
10+
11+
package clang.clangd.remote;
12+
13+
service Index {
14+
rpc Lookup(LookupRequest) returns (stream LookupReply) {}
15+
}
16+
17+
message LookupRequest { string id = 1; }
18+
19+
message LookupReply { string symbol_yaml = 1; }
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Clangd remote index
2+
3+
Clangd uses a global index for project-wide code completion, navigation and
4+
other features. For large projects, building this can take many hours and
5+
keeping it loaded uses a lot of memory.
6+
7+
To relieve that burden, we're building remote index — a global index
8+
served on a different machine and shared between developers. This directory
9+
contains code that is used as Proof of Concept for the upcoming remote index
10+
feature.
11+
12+
## Building
13+
14+
This feature uses gRPC and Protobuf libraries, so you will need to install them.
15+
There are two ways of doing that.
16+
17+
However you install dependencies, to enable this feature and build remote index
18+
tools you will need to set this CMake flag — `-DCLANGD_ENABLE_REMOTE=On`.
19+
20+
### System-installed libraries
21+
22+
On Debian-like systems gRPC and Protobuf can be installed from apt:
23+
24+
```bash
25+
apt install libgrpc++-dev libprotobuf-dev protobuf-compiler protobuf-compiler-grpc
26+
```
27+
28+
### Building from sources
29+
30+
Another way of installing gRPC and Protobuf is building from sources using
31+
CMake. The easiest way of doing that would be to choose a directory where you
32+
want to install so that the installation files are not copied to system root and
33+
you can uninstall gRPC or use different versions of the library.
34+
35+
```bash
36+
# Get source code.
37+
$ git clone -b v1.28.1 https://github.com/grpc/grpc
38+
$ cd grpc
39+
$ git submodule update --init
40+
# Choose directory where you want gRPC installation to live.
41+
$ export GRPC_INSTALL_PATH=/where/you/want/grpc/to/be/installed
42+
# Build and install gRPC to ${GRPC_INSTALL_PATH}
43+
$ mkdir build; cd build
44+
$ cmake -DgRPC_INSTALL=ON -DCMAKE_INSTALL_PREFIX=${GRPC_INSTALL_PATH} -DCMAKE_BUILD_TYPE=Release ..
45+
$ make install
46+
```
47+
48+
This [guide](https://github.com/grpc/grpc/blob/master/BUILDING.md) goes into
49+
more detail on how to build gRPC from sources.
50+
51+
By default, CMake will look for system-installed libraries when building remote
52+
index tools so you will have to adjust LLVM's CMake invocation. The following
53+
flag will inform build system that you chose this option —
54+
`-DGRPC_INSTALL_PATH=${GRPC_INSTALL_PATH}`.
55+
56+
## Running
57+
58+
The remote index isn't usable with Clangd yet, but you can try the
59+
proof-of-concept tools in `client/` and `server/` subdirectories.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
set(LLVM_LINK_COMPONENTS
2+
LineEditor
3+
Support
4+
)
5+
add_clang_executable(clangd-index-client
6+
Client.cpp
7+
)
8+
target_compile_definitions(clangd-index-client PRIVATE -DGOOGLE_PROTOBUF_NO_RTTI=1)
9+
clang_target_link_libraries(clangd-index-client
10+
PRIVATE
11+
clangDaemon
12+
)
13+
target_link_libraries(clangd-index-client
14+
PRIVATE
15+
RemoteIndexProtos
16+
17+
protobuf
18+
grpc++
19+
)
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
//===--- Client.cpp - Remote Index Client -----------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This file implements a simple interactive tool which can be used to manually
10+
// evaluate symbol search quality of Clangd index.
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
#include "SourceCode.h"
15+
#include "index/Serialization.h"
16+
#include "index/dex/Dex.h"
17+
#include "llvm/ADT/ScopeExit.h"
18+
#include "llvm/ADT/SmallVector.h"
19+
#include "llvm/ADT/StringRef.h"
20+
#include "llvm/ADT/StringSwitch.h"
21+
#include "llvm/LineEditor/LineEditor.h"
22+
#include "llvm/Support/CommandLine.h"
23+
#include "llvm/Support/Signals.h"
24+
25+
#include "grpcpp/grpcpp.h"
26+
27+
#include "Index.grpc.pb.h"
28+
29+
namespace clang {
30+
namespace clangd {
31+
namespace {
32+
33+
llvm::cl::opt<std::string>
34+
ServerAddress("server-address",
35+
llvm::cl::desc("Address of remote index server to use."),
36+
llvm::cl::init("0.0.0.0:50051"));
37+
38+
static const std::string Overview = R"(
39+
This is an **experimental** interactive tool to process user-provided search
40+
queries over given symbol collection obtained via clangd-indexer with the help
41+
of remote index server. The client will connect to remote index server and pass
42+
it lookup queries.
43+
)";
44+
45+
class RemoteIndexClient {
46+
public:
47+
RemoteIndexClient(std::shared_ptr<grpc::Channel> Channel)
48+
: Stub(remote::Index::NewStub(Channel)) {}
49+
50+
void lookup(llvm::StringRef ID) {
51+
llvm::outs() << "Lookup of symbol with ID " << ID << '\n';
52+
remote::LookupRequest Proto;
53+
Proto.set_id(ID.str());
54+
55+
grpc::ClientContext Context;
56+
remote::LookupReply Reply;
57+
std::unique_ptr<grpc::ClientReader<remote::LookupReply>> Reader(
58+
Stub->Lookup(&Context, Proto));
59+
while (Reader->Read(&Reply)) {
60+
llvm::outs() << Reply.symbol_yaml();
61+
}
62+
grpc::Status Status = Reader->Finish();
63+
if (Status.ok()) {
64+
llvm::outs() << "lookupRequest rpc succeeded.\n";
65+
} else {
66+
llvm::outs() << "lookupRequest rpc failed.\n";
67+
}
68+
}
69+
70+
private:
71+
std::unique_ptr<remote::Index::Stub> Stub;
72+
};
73+
74+
} // namespace
75+
} // namespace clangd
76+
} // namespace clang
77+
78+
int main(int argc, const char *argv[]) {
79+
using namespace clang::clangd;
80+
81+
llvm::cl::ParseCommandLineOptions(argc, argv, Overview);
82+
llvm::cl::ResetCommandLineParser(); // We reuse it for REPL commands.
83+
llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
84+
85+
RemoteIndexClient IndexClient(
86+
grpc::CreateChannel(ServerAddress, grpc::InsecureChannelCredentials()));
87+
88+
llvm::LineEditor LE("remote-index-client");
89+
while (llvm::Optional<std::string> Request = LE.readLine())
90+
IndexClient.lookup(std::move(*Request));
91+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
set(LLVM_LINK_COMPONENTS
2+
LineEditor
3+
Support
4+
)
5+
add_clang_executable(clangd-index-server
6+
Server.cpp
7+
)
8+
target_compile_definitions(clangd-index-server PRIVATE -DGOOGLE_PROTOBUF_NO_RTTI=1)
9+
clang_target_link_libraries(clangd-index-server
10+
PRIVATE
11+
clangDaemon
12+
)
13+
target_link_libraries(clangd-index-server
14+
PRIVATE
15+
RemoteIndexProtos
16+
17+
protobuf
18+
grpc++
19+
clangDaemon
20+
)
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//===--- Server.cpp - gRPC-based Remote Index Server ---------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "index/Index.h"
10+
#include "index/Serialization.h"
11+
#include "llvm/ADT/Optional.h"
12+
#include "llvm/ADT/StringRef.h"
13+
#include "llvm/LineEditor/LineEditor.h"
14+
#include "llvm/Support/CommandLine.h"
15+
#include "llvm/Support/FileSystem.h"
16+
#include "llvm/Support/Path.h"
17+
#include "llvm/Support/Signals.h"
18+
19+
#include "grpcpp/grpcpp.h"
20+
#include "grpcpp/health_check_service_interface.h"
21+
22+
#include "Index.grpc.pb.h"
23+
24+
namespace clang {
25+
namespace clangd {
26+
namespace {
27+
28+
static const std::string Overview = R"(
29+
This is an experimental remote index implementation. The server opens Dex and
30+
awaits gRPC lookup requests from the client.
31+
)";
32+
33+
llvm::cl::opt<std::string> IndexPath(llvm::cl::desc("<INDEX FILE>"),
34+
llvm::cl::Positional, llvm::cl::Required);
35+
36+
llvm::cl::opt<std::string> ServerAddress("server-address",
37+
llvm::cl::init("0.0.0.0:50051"));
38+
39+
std::unique_ptr<SymbolIndex> openIndex(llvm::StringRef Index) {
40+
return loadIndex(Index, /*UseIndex=*/true);
41+
}
42+
43+
class RemoteIndexServer final : public remote::Index::Service {
44+
public:
45+
RemoteIndexServer(std::unique_ptr<SymbolIndex> Index)
46+
: Index(std::move(Index)) {}
47+
48+
private:
49+
grpc::Status Lookup(grpc::ServerContext *Context,
50+
const remote::LookupRequest *Request,
51+
grpc::ServerWriter<remote::LookupReply> *Reply) override {
52+
llvm::outs() << "Lookup of symbol with ID " << Request->id() << '\n';
53+
LookupRequest Req;
54+
auto SID = SymbolID::fromStr(Request->id());
55+
if (!SID) {
56+
llvm::outs() << llvm::toString(SID.takeError()) << "\n";
57+
return grpc::Status::CANCELLED;
58+
}
59+
Req.IDs.insert(*SID);
60+
Index->lookup(Req, [&](const Symbol &Sym) {
61+
remote::LookupReply NextSymbol;
62+
NextSymbol.set_symbol_yaml(toYAML(Sym));
63+
Reply->Write(NextSymbol);
64+
});
65+
return grpc::Status::OK;
66+
}
67+
68+
std::unique_ptr<SymbolIndex> Index;
69+
};
70+
71+
void runServer(std::unique_ptr<SymbolIndex> Index,
72+
const std::string &ServerAddress) {
73+
RemoteIndexServer Service(std::move(Index));
74+
75+
grpc::EnableDefaultHealthCheckService(true);
76+
grpc::ServerBuilder Builder;
77+
Builder.AddListeningPort(ServerAddress, grpc::InsecureServerCredentials());
78+
Builder.RegisterService(&Service);
79+
std::unique_ptr<grpc::Server> Server(Builder.BuildAndStart());
80+
llvm::outs() << "Server listening on " << ServerAddress << '\n';
81+
82+
Server->Wait();
83+
}
84+
85+
} // namespace
86+
} // namespace clangd
87+
} // namespace clang
88+
89+
int main(int argc, char *argv[]) {
90+
using namespace clang::clangd;
91+
llvm::cl::ParseCommandLineOptions(argc, argv, clang::clangd::Overview);
92+
llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
93+
94+
std::unique_ptr<SymbolIndex> Index = openIndex(IndexPath);
95+
96+
if (!Index) {
97+
llvm::outs() << "Failed to open the index.\n";
98+
return -1;
99+
}
100+
101+
runServer(std::move(Index), ServerAddress);
102+
}

llvm/cmake/modules/FindGRPC.cmake

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# This setup requires gRPC to be built from sources using CMake and installed to
2+
# ${GRPC_INSTALL_PATH} via -DCMAKE_INSTALL_PREFIX=${GRPC_INSTALL_PATH}.
3+
if (GRPC_INSTALL_PATH)
4+
set(protobuf_MODULE_COMPATIBLE TRUE)
5+
find_package(Protobuf CONFIG REQUIRED HINTS ${GRPC_INSTALL_PATH})
6+
message(STATUS "Using protobuf ${protobuf_VERSION}")
7+
find_package(gRPC CONFIG REQUIRED HINTS ${GRPC_INSTALL_PATH})
8+
message(STATUS "Using gRPC ${gRPC_VERSION}")
9+
10+
include_directories(${Protobuf_INCLUDE_DIRS})
11+
12+
# gRPC CMake CONFIG gives the libraries slightly odd names, make them match
13+
# the conventional system-installed names.
14+
set_target_properties(protobuf::libprotobuf PROPERTIES IMPORTED_GLOBAL TRUE)
15+
add_library(protobuf ALIAS protobuf::libprotobuf)
16+
set_target_properties(gRPC::grpc++ PROPERTIES IMPORTED_GLOBAL TRUE)
17+
add_library(grpc++ ALIAS gRPC::grpc++)
18+
19+
set(GRPC_CPP_PLUGIN $<TARGET_FILE:gRPC::grpc_cpp_plugin>)
20+
set(PROTOC ${Protobuf_PROTOC_EXECUTABLE})
21+
else()
22+
find_program(GRPC_CPP_PLUGIN grpc_cpp_plugin)
23+
find_program(PROTOC protoc)
24+
endif()
25+
26+
# Proto headers are generated in ${CMAKE_CURRENT_BINARY_DIR}.
27+
# Libraries that use these headers should adjust the include path.
28+
# FIXME(kirillbobyrev): Allow optional generation of gRPC code and give callers
29+
# control over it via additional parameters.
30+
function(generate_grpc_protos LibraryName ProtoFile)
31+
get_filename_component(ProtoSourceAbsolutePath "${CMAKE_CURRENT_SOURCE_DIR}/${ProtoFile}" ABSOLUTE)
32+
get_filename_component(ProtoSourcePath ${ProtoSourceAbsolutePath} PATH)
33+
34+
set(GeneratedProtoSource "${CMAKE_CURRENT_BINARY_DIR}/Index.pb.cc")
35+
set(GeneratedProtoHeader "${CMAKE_CURRENT_BINARY_DIR}/Index.pb.h")
36+
set(GeneratedGRPCSource "${CMAKE_CURRENT_BINARY_DIR}/Index.grpc.pb.cc")
37+
set(GeneratedGRPCHeader "${CMAKE_CURRENT_BINARY_DIR}/Index.grpc.pb.h")
38+
add_custom_command(
39+
OUTPUT "${GeneratedProtoSource}" "${GeneratedProtoHeader}" "${GeneratedGRPCSource}" "${GeneratedGRPCHeader}"
40+
COMMAND ${PROTOC}
41+
ARGS --grpc_out="${CMAKE_CURRENT_BINARY_DIR}"
42+
--cpp_out="${CMAKE_CURRENT_BINARY_DIR}"
43+
--proto_path="${ProtoSourcePath}"
44+
--plugin=protoc-gen-grpc="${GRPC_CPP_PLUGIN}"
45+
"${ProtoSourceAbsolutePath}"
46+
DEPENDS "${ProtoSourceAbsolutePath}")
47+
48+
add_library(${LibraryName} ${GeneratedProtoSource} ${GeneratedGRPCSource})
49+
target_link_libraries(${LibraryName} grpc++ protobuf)
50+
endfunction()

0 commit comments

Comments
 (0)