jkorous created this revision.
jkorous added reviewers: arphaman, sammccall.
Herald added subscribers: cfe-commits, kadircet, dexonsmith, MaskRay, ioeric,
ilya-biryukov, mgorny.
Repository:
rCTE Clang Tools Extra
https://reviews.llvm.org/D54428
Files:
CMakeLists.txt
Features.inc.in
clangd/CMakeLists.txt
clangd/xpc/CMakeLists.txt
clangd/xpc/ConversionTests.cpp
clangd/xpc/initialize.test
lit.cfg
lit.site.cfg.in
tool/CMakeLists.txt
tool/ClangdMain.cpp
xpc/CMakeLists.txt
xpc/Conversion.cpp
xpc/Conversion.h
xpc/README.txt
xpc/XPCTransport.cpp
xpc/XPCTransport.h
xpc/cmake/Info.plist.in
xpc/cmake/XPCServiceInfo.plist.in
xpc/cmake/modules/CreateClangdXPCFramework.cmake
xpc/framework/CMakeLists.txt
xpc/framework/ClangdXPC.cpp
xpc/test-client/CMakeLists.txt
xpc/test-client/ClangdXPCTestClient.cpp
Index: clangd/xpc/ConversionTests.cpp
===================================================================
--- /dev/null
+++ clangd/xpc/ConversionTests.cpp
@@ -0,0 +1,36 @@
+//===-- ConversionTests.cpp --------------------------*- C++ -*-----------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "xpc/Conversion.h"
+#include "gtest/gtest.h"
+
+#include <limits>
+
+namespace clang {
+namespace clangd {
+namespace {
+
+using namespace llvm;
+
+TEST(JsonXpcConversionTest, JsonToXpcToJson) {
+
+ for (auto &testcase :
+ {json::Value(false), json::Value(3.14), json::Value(42),
+ json::Value(-100), json::Value("foo"), json::Value(""),
+ json::Value("123"), json::Value(" "),
+ json::Value{true, "foo", nullptr, 42},
+ json::Value(json::Object{
+ {"a", true}, {"b", "foo"}, {"c", nullptr}, {"d", 42}})}) {
+ EXPECT_TRUE(testcase == xpcToJson(jsonToXpc(testcase))) << testcase;
+ }
+}
+
+} // namespace
+} // namespace clangd
+} // namespace clang
Index: clangd/xpc/CMakeLists.txt
===================================================================
--- /dev/null
+++ clangd/xpc/CMakeLists.txt
@@ -0,0 +1,21 @@
+set(LLVM_LINK_COMPONENTS
+ support
+ )
+
+get_filename_component(CLANGD_SOURCE_DIR
+ ${CMAKE_CURRENT_SOURCE_DIR}/../../clangd REALPATH)
+include_directories(
+ ${CLANGD_SOURCE_DIR}
+ )
+
+add_extra_unittest(ClangdXpcTests
+ ConversionTests.cpp
+ )
+
+target_link_libraries(ClangdXpcTests
+ PRIVATE
+ clangdXpcJsonConversions
+ clangDaemon
+ LLVMSupport
+ LLVMTestingSupport
+ )
Index: clangd/CMakeLists.txt
===================================================================
--- clangd/CMakeLists.txt
+++ clangd/CMakeLists.txt
@@ -60,3 +60,7 @@
LLVMSupport
LLVMTestingSupport
)
+
+if (CLANGD_BUILD_XPC)
+ add_subdirectory(xpc)
+endif ()
Index: lit.site.cfg.in
===================================================================
--- lit.site.cfg.in
+++ lit.site.cfg.in
@@ -11,6 +11,7 @@
config.python_executable = "@PYTHON_EXECUTABLE@"
config.target_triple = "@TARGET_TRIPLE@"
config.clang_staticanalyzer = @CLANG_ENABLE_STATIC_ANALYZER@
+config.clangd_xpc_support = @CLANGD_BUILD_XPC_SUPPORT@
# Support substitution of the tools and libs dirs with user parameters. This is
# used when we can't determine the tool dir at configuration time.
Index: lit.cfg
===================================================================
--- lit.cfg
+++ lit.cfg
@@ -117,6 +117,10 @@
if platform.system() not in ['Windows']:
config.available_features.add('ansi-escape-sequences')
+# XPC support for Clangd.
+if config.clangd_xpc_support:
+ config.available_features.add('clangd-xpc-support')
+
if config.clang_staticanalyzer:
config.available_features.add('static-analyzer')
Index: clangd/xpc/initialize.test
===================================================================
--- /dev/null
+++ clangd/xpc/initialize.test
@@ -0,0 +1,10 @@
+# RUN: clangd-xpc-test-client < %s | FileCheck %s
+# REQUIRES: clangd-xpc-support
+
+{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootUri":"test:///workspace","capabilities":{},"trace":"off"}}
+# CHECK: {"id":0,"jsonrpc":"2.0","result":{"capabilities"
+
+{"jsonrpc":"2.0","id":3,"method":"shutdown"}
+# CHECK: {"id":3,"jsonrpc":"2.0","result":null}
+
+{"jsonrpc":"2.0","method":"exit"}
Index: xpc/test-client/ClangdXPCTestClient.cpp
===================================================================
--- /dev/null
+++ xpc/test-client/ClangdXPCTestClient.cpp
@@ -0,0 +1,96 @@
+#include "xpc/Conversion.h"
+#include "clang/Basic/LLVM.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/Support/LineIterator.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/raw_ostream.h"
+#include <dlfcn.h>
+#include <stdio.h>
+#include <string>
+#include <xpc/xpc.h>
+
+typedef const char *(*clangd_xpc_get_bundle_identifier_t)(void);
+
+using namespace llvm;
+using namespace clang;
+
+std::string getLibraryPath() {
+ Dl_info info;
+ if (dladdr((void *)(uintptr_t)getLibraryPath, &info) == 0)
+ llvm_unreachable("Call to dladdr() failed");
+ llvm::SmallString<128> LibClangPath;
+ LibClangPath = llvm::sys::path::parent_path(
+ llvm::sys::path::parent_path(info.dli_fname));
+ llvm::sys::path::append(LibClangPath, "lib", "ClangdXPC.framework",
+ "ClangdXPC");
+ return LibClangPath.str();
+}
+
+static void dumpXPCObject(xpc_object_t Object, llvm::raw_ostream &OS) {
+ xpc_type_t Type = xpc_get_type(Object);
+ if (Type == XPC_TYPE_DICTIONARY) {
+ json::Value Json = clang::clangd::xpcToJson(Object);
+ OS << Json;
+ } else {
+ OS << "<UNKNOWN>";
+ }
+}
+
+int main(int argc, char *argv[]) {
+ // Open the ClangdXPC dylib in the framework.
+ std::string LibPath = getLibraryPath();
+ void *dlHandle = dlopen(LibPath.c_str(), RTLD_LOCAL | RTLD_FIRST);
+ if (!dlHandle)
+ return 1;
+
+ // Lookup the XPC service bundle name, and launch it.
+ clangd_xpc_get_bundle_identifier_t clangd_xpc_get_bundle_identifier =
+ (clangd_xpc_get_bundle_identifier_t)dlsym(
+ dlHandle, "clangd_xpc_get_bundle_identifier");
+ xpc_connection_t conn = xpc_connection_create(
+ clangd_xpc_get_bundle_identifier(), dispatch_get_main_queue());
+
+ // Dump the XPC events.
+ xpc_connection_set_event_handler(conn, ^(xpc_object_t event) {
+ if (event == XPC_ERROR_CONNECTION_INVALID) {
+ llvm::errs() << "Received XPC_ERROR_CONNECTION_INVALID.";
+ exit(EXIT_SUCCESS);
+ }
+ if (event == XPC_ERROR_CONNECTION_INTERRUPTED) {
+ llvm::errs() << "Received XPC_ERROR_CONNECTION_INTERRUPTED.";
+ exit(EXIT_SUCCESS);
+ }
+
+ dumpXPCObject(event, llvm::outs());
+ llvm::outs() << "\n";
+ });
+
+ xpc_connection_resume(conn);
+
+ // Read the input to determine the things to send to clangd.
+ llvm::ErrorOr<std::unique_ptr<MemoryBuffer>> Stdin =
+ llvm::MemoryBuffer::getSTDIN();
+ if (!Stdin) {
+ llvm::errs() << "Failed to get STDIN!\n";
+ return 1;
+ }
+ for (llvm::line_iterator It(**Stdin, /*SkipBlanks=*/true,
+ /*CommentMarker=*/'#');
+ !It.is_at_eof(); ++It) {
+ StringRef Line = *It;
+ if (auto Request = json::parse(Line)) {
+ xpc_object_t Object = clangd::jsonToXpc(*Request);
+ xpc_connection_send_message(conn, Object);
+ } else {
+ llvm::errs() << llvm::Twine("JSON parse error: ")
+ << llvm::toString(Request.takeError());
+ return 1;
+ }
+ }
+
+ dispatch_main();
+
+ // dispatch_main() doesn't return
+ return EXIT_FAILURE;
+}
Index: xpc/test-client/CMakeLists.txt
===================================================================
--- /dev/null
+++ xpc/test-client/CMakeLists.txt
@@ -0,0 +1,26 @@
+include_directories(
+ ${CMAKE_CURRENT_SOURCE_DIR}/../../
+)
+
+add_clang_tool(
+ clangd-xpc-test-client
+ ClangdXPCTestClient.cpp
+
+ DEPENDS ClangdXPC
+)
+
+set(LLVM_LINK_COMPONENTS
+ support
+)
+
+target_link_libraries(clangd-xpc-test-client
+ PRIVATE
+ clangBasic
+ clangDaemon
+ clangFormat
+ clangFrontend
+ clangSema
+ clangTooling
+ clangToolingCore
+ clangdXpcJsonConversions
+)
Index: xpc/framework/ClangdXPC.cpp
===================================================================
--- /dev/null
+++ xpc/framework/ClangdXPC.cpp
@@ -0,0 +1,5 @@
+
+/// Returns the bundle identifier of the Clangd XPC service.
+extern "C" const char *clangd_xpc_get_bundle_identifier() {
+ return "org.llvm.clangd";
+}
Index: xpc/framework/CMakeLists.txt
===================================================================
--- /dev/null
+++ xpc/framework/CMakeLists.txt
@@ -0,0 +1,9 @@
+
+set(SOURCES
+ ClangdXPC.cpp)
+add_clang_library(ClangdXPCLib SHARED
+ ${SOURCES}
+ DEPENDS
+ clangd
+)
+create_clangd_xpc_framework(ClangdXPCLib "ClangdXPC")
Index: xpc/cmake/modules/CreateClangdXPCFramework.cmake
===================================================================
--- /dev/null
+++ xpc/cmake/modules/CreateClangdXPCFramework.cmake
@@ -0,0 +1,73 @@
+# Creates the ClangdXPC framework.
+macro(create_clangd_xpc_framework target name)
+ set(CLANGD_FRAMEWORK_LOCATION "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${name}.framework")
+ set(CLANGD_FRAMEWORK_OUT_LOCATION "${CLANGD_FRAMEWORK_LOCATION}/Versions/A")
+
+ # Create the framework info PLIST.
+ set(CLANGD_XPC_FRAMEWORK_NAME "${name}")
+ configure_file(
+ "${CLANGD_XPC_SOURCE_DIR}/cmake/Info.plist.in"
+ "${CLANGD_XPC_BINARY_DIR}/${name}.Info.plist")
+
+ set(CLANGD_XPC_SERVICE_NAME "clangd")
+ set(CLANGD_XPC_SERVICE_OUT_LOCATION
+ "${CLANGD_FRAMEWORK_OUT_LOCATION}/XPCServices/${CLANGD_XPC_SERVICE_NAME}.xpc/Contents")
+
+ # Create the XPC service info PLIST.
+ set(CLANGD_XPC_SERVICE_BUNDLE_NAME "org.llvm.${CLANGD_XPC_SERVICE_NAME}")
+ configure_file(
+ "${CLANGD_XPC_SOURCE_DIR}/cmake/XPCServiceInfo.plist.in"
+ "${CLANGD_XPC_BINARY_DIR}/${name}Service.Info.plist")
+
+ # Create the custom command
+ add_custom_command(OUTPUT ${CLANGD_FRAMEWORK_LOCATION}
+ # Copy the PLIST.
+ COMMAND ${CMAKE_COMMAND} -E copy
+ "${CLANGD_XPC_BINARY_DIR}/${name}.Info.plist"
+ "${CLANGD_FRAMEWORK_OUT_LOCATION}/Resources/Info.plist"
+
+ # Copy the framework binary.
+ COMMAND ${CMAKE_COMMAND} -E copy
+ "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/lib${target}.dylib"
+ "${CLANGD_FRAMEWORK_OUT_LOCATION}/${name}"
+
+ # Copy the XPC Service PLIST.
+ COMMAND ${CMAKE_COMMAND} -E copy
+ "${CLANGD_XPC_BINARY_DIR}/${name}Service.Info.plist"
+ "${CLANGD_XPC_SERVICE_OUT_LOCATION}/Info.plist"
+
+ # Copy the Clangd binary.
+ COMMAND ${CMAKE_COMMAND} -E copy
+ "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/clangd"
+ "${CLANGD_XPC_SERVICE_OUT_LOCATION}/MacOS/clangd"
+
+ COMMAND ${CMAKE_COMMAND} -E create_symlink "A"
+ "${CLANGD_FRAMEWORK_LOCATION}/Versions/Current"
+
+ COMMAND ${CMAKE_COMMAND} -E create_symlink
+ "Versions/Current/Resources"
+ "${CLANGD_FRAMEWORK_LOCATION}/Resources"
+
+ COMMAND ${CMAKE_COMMAND} -E create_symlink
+ "Versions/Current/XPCServices"
+ "${CLANGD_FRAMEWORK_LOCATION}/XPCServices"
+
+ COMMAND ${CMAKE_COMMAND} -E create_symlink
+ "Versions/Current/${name}"
+ "${CLANGD_FRAMEWORK_LOCATION}/${name}"
+
+ DEPENDS
+ "${CLANGD_XPC_BINARY_DIR}/${name}.Info.plist"
+ "${CLANGD_XPC_BINARY_DIR}/${name}Service.Info.plist"
+ clangd
+ COMMENT "Creating ClangdXPC framework"
+ VERBATIM
+ )
+
+ add_custom_target(
+ ClangdXPC
+ DEPENDS
+ ${target}
+ ${CLANGD_FRAMEWORK_LOCATION}
+ )
+endmacro(create_clangd_xpc_framework)
Index: xpc/cmake/XPCServiceInfo.plist.in
===================================================================
--- /dev/null
+++ xpc/cmake/XPCServiceInfo.plist.in
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleExecutable</key>
+ <string>${CLANGD_XPC_SERVICE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>${CLANGD_XPC_SERVICE_BUNDLE_NAME}</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>${CLANGD_XPC_SERVICE_NAME}</string>
+ <key>CFBundlePackageType</key>
+ <string>XPC!</string>
+ <key>CFBundleVersion</key>
+ <string></string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>XPCService</key>
+ <dict>
+ <key>ServiceType</key>
+ <string>Application</string>
+ <key>EnvironmentVariables</key>
+ <dict>
+ <key>CLANGD_AS_XPC_SERVICE</key>
+ <string>1</string>
+ </dict>
+ </dict>
+</dict>
+</plist>
Index: xpc/cmake/Info.plist.in
===================================================================
--- /dev/null
+++ xpc/cmake/Info.plist.in
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleExecutable</key>
+ <string>${CLANGD_XPC_FRAMEWORK_NAME}</string>
+ <key>CFBundleIconFile</key>
+ <string></string>
+ <key>CFBundleIdentifier</key>
+ <string>org.llvm.${CLANGD_XPC_FRAMEWORK_NAME}</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>${CLANGD_XPC_FRAMEWORK_NAME}</string>
+ <key>CFBundlePackageType</key>
+ <string>FMWK</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string></string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CSResourcesFileMapped</key>
+ <true/>
+</dict>
+</plist>
Index: xpc/XPCTransport.h
===================================================================
--- /dev/null
+++ xpc/XPCTransport.h
@@ -0,0 +1,24 @@
+//===--- XPCTransport.h - sending and receiving LSP messages over XPC -----===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_XPC_XPCTRANSPORT_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_XPC_XPCTRANSPORT_H
+
+#include "Transport.h"
+#include <memory>
+
+namespace clang {
+namespace clangd {
+
+std::unique_ptr<Transport> newXPCransport();
+
+} // namespace clangd
+} // namespace clang
+
+#endif
Index: xpc/XPCTransport.cpp
===================================================================
--- /dev/null
+++ xpc/XPCTransport.cpp
@@ -0,0 +1,210 @@
+//===--- XPCTransport.cpp - sending and receiving LSP messages over XPC ---===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+#include "Conversion.h"
+#include "Logger.h"
+#include "Protocol.h" // For LSPError
+#include "Transport.h"
+#include "llvm/Support/Errno.h"
+
+#include <xpc/xpc.h>
+
+using namespace llvm;
+using namespace clang;
+using namespace clangd;
+
+namespace {
+
+json::Object encodeError(Error E) {
+ std::string Message;
+ ErrorCode Code = ErrorCode::UnknownErrorCode;
+ if (Error Unhandled =
+ handleErrors(std::move(E), [&](const LSPError &L) -> Error {
+ Message = L.Message;
+ Code = L.Code;
+ return Error::success();
+ }))
+ Message = toString(std::move(Unhandled));
+
+ return json::Object{
+ {"message", std::move(Message)},
+ {"code", int64_t(Code)},
+ };
+}
+
+Error decodeError(const json::Object &O) {
+ std::string Msg = O.getString("message").getValueOr("Unspecified error");
+ if (auto Code = O.getInteger("code"))
+ return make_error<LSPError>(std::move(Msg), ErrorCode(*Code));
+ return make_error<StringError>(std::move(Msg), inconvertibleErrorCode());
+}
+
+// C "closure" for XPCTransport::loop() method
+namespace XPCClosure {
+void connection_handler(xpc_connection_t clientConnection);
+}
+
+class XPCTransport : public Transport {
+public:
+ XPCTransport() {}
+
+ void notify(StringRef Method, json::Value Params) override {
+ sendMessage(json::Object{
+ {"jsonrpc", "2.0"},
+ {"method", Method},
+ {"params", std::move(Params)},
+ });
+ }
+ void call(StringRef Method, json::Value Params, json::Value ID) override {
+ sendMessage(json::Object{
+ {"jsonrpc", "2.0"},
+ {"id", std::move(ID)},
+ {"method", Method},
+ {"params", std::move(Params)},
+ });
+ }
+ void reply(json::Value ID, Expected<json::Value> Result) override {
+ if (Result) {
+ sendMessage(json::Object{
+ {"jsonrpc", "2.0"},
+ {"id", std::move(ID)},
+ {"result", std::move(*Result)},
+ });
+ } else {
+ sendMessage(json::Object{
+ {"jsonrpc", "2.0"},
+ {"id", std::move(ID)},
+ {"error", encodeError(Result.takeError())},
+ });
+ }
+ }
+
+ Error loop(MessageHandler &Handler) override;
+
+private:
+ // Needs access to handleMessage() and resetClientConnection()
+ friend void XPCClosure::connection_handler(xpc_connection_t clientConnection);
+
+ // Dispatches incoming message to Handler onNotify/onCall/onReply.
+ bool handleMessage(json::Value Message, MessageHandler &Handler);
+ void sendMessage(json::Value Message) {
+ xpc_object_t response = jsonToXpc(Message);
+ xpc_connection_send_message(clientConnection, response);
+ xpc_release(response);
+ }
+ void resetClientConnection(xpc_connection_t newClientConnection) {
+ clientConnection = newClientConnection;
+ }
+ xpc_connection_t clientConnection;
+};
+
+bool XPCTransport::handleMessage(json::Value Message, MessageHandler &Handler) {
+ // Message must be an object with "jsonrpc":"2.0".
+ auto *Object = Message.getAsObject();
+ if (!Object || Object->getString("jsonrpc") != Optional<StringRef>("2.0")) {
+ elog("Not a JSON-RPC 2.0 message: {0:2}", Message);
+ return false;
+ }
+ // ID may be any JSON value. If absent, this is a notification.
+ Optional<json::Value> ID;
+ if (auto *I = Object->get("id"))
+ ID = std::move(*I);
+ auto Method = Object->getString("method");
+ if (!Method) { // This is a response.
+ if (!ID) {
+ elog("No method and no response ID: {0:2}", Message);
+ return false;
+ }
+ if (auto *Err = Object->getObject("error"))
+ return Handler.onReply(std::move(*ID), decodeError(*Err));
+ // Result should be given, use null if not.
+ json::Value Result = nullptr;
+ if (auto *R = Object->get("result"))
+ Result = std::move(*R);
+ return Handler.onReply(std::move(*ID), std::move(Result));
+ }
+ // Params should be given, use null if not.
+ json::Value Params = nullptr;
+ if (auto *P = Object->get("params"))
+ Params = std::move(*P);
+
+ if (ID)
+ return Handler.onCall(*Method, std::move(Params), std::move(*ID));
+ else
+ return Handler.onNotify(*Method, std::move(Params));
+}
+
+namespace XPCClosure {
+// "owner" of this "closure object" - necessary for propagating connection to
+// XPCTransport so it can send messages to the client.
+XPCTransport *TransportObject = nullptr;
+Transport::MessageHandler *HandlerPtr = nullptr;
+
+void connection_handler(xpc_connection_t clientConnection) {
+ xpc_transaction_begin();
+
+ TransportObject->resetClientConnection(clientConnection);
+
+ xpc_connection_set_event_handler(clientConnection, ^(xpc_object_t message) {
+ if (message == XPC_ERROR_CONNECTION_INVALID) {
+ // connection is being terminated
+ log("Received XPC_ERROR_CONNECTION_INVALID message - returning from the "
+ "event_handler.");
+ return;
+ }
+
+ if (xpc_get_type(message) != XPC_TYPE_DICTIONARY) {
+ log("Received XPC message of unknown type - returning from the "
+ "event_handler.");
+ return;
+ }
+
+ const json::Value Doc = xpcToJson(message);
+ if (Doc == json::Value(nullptr)) {
+ log("XPC message was converted to Null JSON message - returning from the "
+ "event_handler.");
+ return;
+ }
+
+ vlog("<<< {0}\n", Doc);
+
+ if (!TransportObject->handleMessage(std::move(Doc), *HandlerPtr)) {
+ log("Received exit notification - cancelling connection.");
+ xpc_connection_cancel(xpc_dictionary_get_remote_connection(message));
+ xpc_transaction_end();
+ }
+ });
+
+ xpc_connection_resume(clientConnection);
+}
+} // namespace XPCClosure
+
+Error XPCTransport::loop(MessageHandler &Handler) {
+ // This looks scary since lifetime of this (or any) XPCTransport object has
+ // to fully contain lifetime of any XPC connection. In practise any Transport
+ // object is destroyed only at the end of main() which is always after
+ // exit of xpc_main().
+ XPCClosure::TransportObject = this;
+ XPCClosure::HandlerPtr = &Handler;
+
+ xpc_main(XPCClosure::connection_handler);
+ // xpc_main doesn't ever return
+ return errorCodeToError(std::make_error_code(std::errc::io_error));
+}
+
+} // namespace
+
+namespace clang {
+namespace clangd {
+
+std::unique_ptr<Transport> newXPCransport() {
+ return llvm::make_unique<XPCTransport>();
+}
+
+} // namespace clangd
+} // namespace clang
Index: xpc/README.txt
===================================================================
--- /dev/null
+++ xpc/README.txt
@@ -0,0 +1,6 @@
+This directory contains:
+- the XPC transport layer (alternative transport layer to JSON-RPC)
+- XPC framework wrapper that wraps around Clangd to make it a valid XPC service
+- XPC test-client
+
+MacOS only. Feature is guarded by CLANGD_BUILD_XPC, including whole xpc/ dir.
\ No newline at end of file
Index: xpc/Conversion.h
===================================================================
--- /dev/null
+++ xpc/Conversion.h
@@ -0,0 +1,25 @@
+//===--- Conversion.h - LSP data (de-)serialization through XPC -*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_XPC_XPCJSONCONVERSIONS_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_XPC_XPCJSONCONVERSIONS_H
+
+#include "llvm/Support/JSON.h"
+#include <xpc/xpc.h>
+
+namespace clang {
+namespace clangd {
+
+xpc_object_t jsonToXpc(const llvm::json::Value &json);
+llvm::json::Value xpcToJson(const xpc_object_t &json);
+
+} // namespace clangd
+} // namespace clang
+
+#endif
Index: xpc/Conversion.cpp
===================================================================
--- /dev/null
+++ xpc/Conversion.cpp
@@ -0,0 +1,41 @@
+//===--- Conversion.cpp - LSP data (de-)serialization through XPC - C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "xpc/Conversion.h"
+#include "Logger.h"
+#include "llvm/ADT/StringRef.h"
+#include <string>
+#include <vector>
+
+using namespace clang;
+using namespace clangd;
+
+using namespace llvm;
+
+xpc_object_t clangd::jsonToXpc(const json::Value &json) {
+ const char *const key = "LSP";
+ std::string payload_string;
+ raw_string_ostream payload_stream(payload_string);
+ payload_stream << json;
+ payload_stream.str();
+ xpc_object_t payload_obj = xpc_string_create(payload_string.c_str());
+ return xpc_dictionary_create(&key, &payload_obj, 1);
+}
+
+json::Value clangd::xpcToJson(const xpc_object_t &xpcObj) {
+ if (xpc_get_type(xpcObj) == XPC_TYPE_DICTIONARY) {
+ const char *const LSP = xpc_dictionary_get_string(xpcObj, "LSP");
+ auto Json = json::parse(llvm::StringRef(LSP));
+ if (Json)
+ return *Json;
+ else
+ elog("JSON parse error: {0}", toString(Json.takeError()));
+ }
+ return json::Value(nullptr);
+}
\ No newline at end of file
Index: xpc/CMakeLists.txt
===================================================================
--- /dev/null
+++ xpc/CMakeLists.txt
@@ -0,0 +1,29 @@
+set(CLANGD_XPC_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
+set(CLANGD_XPC_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}")
+
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules")
+include(CreateClangdXPCFramework)
+
+add_subdirectory(framework)
+add_subdirectory(test-client)
+
+include_directories(
+ ${CMAKE_CURRENT_SOURCE_DIR}/../
+)
+
+set(LLVM_LINK_COMPONENTS
+ Support
+ )
+
+# Needed by LLVM's CMake checks because this file defines multiple targets.
+set(LLVM_OPTIONAL_SOURCES Conversion.cpp XPCTransport.cpp)
+
+add_clang_library(clangdXpcJsonConversions
+ Conversion.cpp
+ )
+
+add_clang_library(clangdXpcTransport
+ XPCTransport.cpp
+ DEPENDS clangdXpcJsonConversions
+ LINK_LIBS clangdXpcJsonConversions
+ )
Index: tool/ClangdMain.cpp
===================================================================
--- tool/ClangdMain.cpp
+++ tool/ClangdMain.cpp
@@ -8,6 +8,7 @@
//===----------------------------------------------------------------------===//
#include "ClangdLSPServer.h"
+#include "Features.inc"
#include "Path.h"
#include "Trace.h"
#include "Transport.h"
@@ -25,6 +26,10 @@
#include <string>
#include <thread>
+#ifdef CLANGD_BUILD_XPC
+#include "xpc/XPCTransport.h"
+#endif
+
using namespace llvm;
using namespace clang;
using namespace clang::clangd;
@@ -318,10 +323,17 @@
// Initialize and run ClangdLSPServer.
// Change stdin to binary to not lose \r\n on windows.
sys::ChangeStdinToBinary();
- auto Transport = newJSONTransport(
- stdin, outs(),
- InputMirrorStream ? InputMirrorStream.getPointer() : nullptr, PrettyPrint,
- InputStyle);
+ auto Transport = [&]() {
+#ifdef CLANGD_BUILD_XPC
+ if (getenv("CLANGD_AS_XPC_SERVICE"))
+ return newXPCransport();
+#endif
+ return newJSONTransport(stdin, outs(),
+ InputMirrorStream ? InputMirrorStream.getPointer()
+ : nullptr,
+ PrettyPrint, InputStyle);
+ }();
+
ClangdLSPServer LSPServer(
*Transport, CCOpts, CompileCommandsDirPath,
/*UseDirBasedCDB=*/CompileArgsFrom == FilesystemCompileArgs, Opts);
Index: tool/CMakeLists.txt
===================================================================
--- tool/CMakeLists.txt
+++ tool/CMakeLists.txt
@@ -1,4 +1,5 @@
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..)
+include_directories(${CMAKE_CURRENT_BINARY_DIR}/..)
add_clang_tool(clangd
ClangdMain.cpp
@@ -8,6 +9,11 @@
support
)
+set(CLANGD_XPC_LIBS "")
+if(CLANGD_BUILD_XPC)
+ list(APPEND CLANGD_XPC_LIBS "clangdXpcJsonConversions" "clangdXpcTransport")
+endif()
+
target_link_libraries(clangd
PRIVATE
clangBasic
@@ -17,4 +23,5 @@
clangSema
clangTooling
clangToolingCore
+ ${CLANGD_XPC_LIBS}
)
Index: Features.inc.in
===================================================================
--- /dev/null
+++ Features.inc.in
@@ -0,0 +1 @@
+#define CLANGD_BUILD_XPC @CLANGD_BUILD_XPC@
Index: CMakeLists.txt
===================================================================
--- CMakeLists.txt
+++ CMakeLists.txt
@@ -16,7 +16,8 @@
string(REPLACE ${CMAKE_CFG_INTDIR} ${LLVM_BUILD_MODE} CLANG_TOOLS_DIR ${LLVM_RUNTIME_OUTPUT_INTDIR})
llvm_canonicalize_cmake_booleans(
- CLANG_ENABLE_STATIC_ANALYZER)
+ CLANG_ENABLE_STATIC_ANALYZER
+ CLANGD_BUILD_XPC_SUPPORT)
configure_lit_site_cfg(
${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.in
@@ -61,7 +62,11 @@
clang-headers
clang-tidy
- )
+)
+
+if(CLANGD_BUILD_XPC_SUPPORT)
+ list(APPEND CLANG_TOOLS_TEST_DEPS clangd-xpc-test-client)
+endif()
set(CLANGD_TEST_DEPS
clangd
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits