Index: CMakeLists.txt =================================================================== --- CMakeLists.txt +++ CMakeLists.txt @@ -1,3 +1,8 @@ +option(CLANGD_BUILD_XPC "Build XPC Support For Clangd." OFF) +if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + set(CLANGD_BUILD_XPC ON CACHE BOOL "" FORCE) +endif () + add_subdirectory(clang-apply-replacements) add_subdirectory(clang-reorder-fields) add_subdirectory(modularize) Index: clangd/CMakeLists.txt =================================================================== --- clangd/CMakeLists.txt +++ clangd/CMakeLists.txt @@ -1,3 +1,11 @@ +# Configure the Features.inc file. +llvm_canonicalize_cmake_booleans( + CLANGD_BUILD_XPC) +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/Features.inc.in + ${CMAKE_CURRENT_BINARY_DIR}/Features.inc +) + set(LLVM_LINK_COMPONENTS Support ) @@ -109,3 +117,6 @@ if (LLVM_INCLUDE_BENCHMARKS) add_subdirectory(benchmarks) endif() +if ( CLANGD_BUILD_XPC ) + add_subdirectory(xpc) +endif () Index: clangd/Features.inc.in =================================================================== --- /dev/null +++ clangd/Features.inc.in @@ -0,0 +1 @@ +#define CLANGD_BUILD_XPC @CLANGD_BUILD_XPC@ Index: clangd/Transport.h =================================================================== --- clangd/Transport.h +++ clangd/Transport.h @@ -86,6 +86,12 @@ llvm::raw_ostream *InMirror, bool Pretty, JSONStreamStyle = JSONStreamStyle::Standard); +#ifdef CLANGD_BUILD_XPC +// Returns a Transport for macOS based on XPC. +// Clangd with this transport is meant to be run as bundled XPC service. +std::unique_ptr newXPCTransport(); +#endif + } // namespace clangd } // namespace clang Index: clangd/tool/CMakeLists.txt =================================================================== --- clangd/tool/CMakeLists.txt +++ clangd/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: clangd/tool/ClangdMain.cpp =================================================================== --- clangd/tool/ClangdMain.cpp +++ clangd/tool/ClangdMain.cpp @@ -7,6 +7,7 @@ // //===----------------------------------------------------------------------===// +#include "Features.inc" #include "ClangdLSPServer.h" #include "Path.h" #include "Trace.h" @@ -255,6 +256,11 @@ } // namespace clangd } // namespace clang +enum class ErrorResultCode : int { + NoShutdownRequest = 1, + CantRunAsXPCService = 2 +}; + int main(int argc, char *argv[]) { using namespace clang; using namespace clang::clangd; @@ -408,14 +414,27 @@ // Initialize and run ClangdLSPServer. // Change stdin to binary to not lose \r\n on windows. llvm::sys::ChangeStdinToBinary(); - auto Transport = newJSONTransport( - stdin, llvm::outs(), - InputMirrorStream ? InputMirrorStream.getPointer() : nullptr, PrettyPrint, - InputStyle); + + std::unique_ptr TransportLayer; + if (getenv("CLANGD_AS_XPC_SERVICE")) { +#ifdef CLANGD_BUILD_XPC + TransportLayer = newXPCTransport(); +#else + errs() << "This clangd binary wasn't built with XPC support.\n"; + return ErrorResultCode::CantRunAsXPCService; +#endif + } else { + TransportLayer = newJSONTransport( + stdin, llvm::outs(), + InputMirrorStream ? InputMirrorStream.getPointer() : nullptr, + PrettyPrint, InputStyle); + } + ClangdLSPServer LSPServer( - *Transport, CCOpts, CompileCommandsDirPath, + *TransportLayer, CCOpts, CompileCommandsDirPath, /*UseDirBasedCDB=*/CompileArgsFrom == FilesystemCompileArgs, Opts); - constexpr int NoShutdownRequestErrorCode = 1; + llvm::set_thread_name("clangd.main"); - return LSPServer.run() ? 0 : NoShutdownRequestErrorCode; + return LSPServer.run() ? 0 + : static_cast(ErrorResultCode::NoShutdownRequest); } Index: clangd/xpc/CMakeLists.txt =================================================================== --- /dev/null +++ clangd/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: clangd/xpc/Conversion.h =================================================================== --- /dev/null +++ clangd/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 + +namespace clang { +namespace clangd { + +xpc_object_t jsonToXpc(const llvm::json::Value &JSON); +llvm::json::Value xpcToJson(const xpc_object_t &XPCObject); + +} // namespace clangd +} // namespace clang + +#endif Index: clangd/xpc/Conversion.cpp =================================================================== --- /dev/null +++ clangd/xpc/Conversion.cpp @@ -0,0 +1,40 @@ +//===--- 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 "llvm/Support/ScopedPrinter.h" +#include +#include + +using namespace llvm; +namespace clang { +namespace clangd { + +xpc_object_t jsonToXpc(const json::Value &JSON) { + const char *const Key = "LSP"; + xpc_object_t PayloadObj = xpc_string_create(llvm::to_string(JSON).c_str()); + return xpc_dictionary_create(&Key, &PayloadObj, 1); +} + +json::Value xpcToJson(const xpc_object_t &XPCObject) { + if (xpc_get_type(XPCObject) == XPC_TYPE_DICTIONARY) { + const char *const LSP = xpc_dictionary_get_string(XPCObject, "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); +} + +} // namespace clangd +} // namespace clang Index: clangd/xpc/README.txt =================================================================== --- /dev/null +++ clangd/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: clangd/xpc/XPCTransport.cpp =================================================================== --- /dev/null +++ clangd/xpc/XPCTransport.cpp @@ -0,0 +1,217 @@ +//===--- 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 + +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(std::move(Msg), ErrorCode(*Code)); + return make_error(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 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("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 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_connection_set_target_queue(clientConnection, dispatch_get_main_queue()); + + 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) { + assert(xpcClosure::TransportObject == nullptr && + "TransportObject has already been set."); + // 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; + + assert(xpcClosure::HandlerPtr == nullptr && + "HandlerPtr has already been set."); + 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 newXPCTransport() { + return llvm::make_unique(); +} + +} // namespace clangd +} // namespace clang Index: clangd/xpc/cmake/Info.plist.in =================================================================== --- /dev/null +++ clangd/xpc/cmake/Info.plist.in @@ -0,0 +1,28 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${CLANGD_XPC_FRAMEWORK_NAME} + CFBundleIconFile + + CFBundleIdentifier + org.llvm.${CLANGD_XPC_FRAMEWORK_NAME} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${CLANGD_XPC_FRAMEWORK_NAME} + CFBundlePackageType + FMWK + CFBundleSignature + ???? + CFBundleVersion + + CFBundleShortVersionString + 1.0 + CSResourcesFileMapped + + + Index: clangd/xpc/cmake/XPCServiceInfo.plist.in =================================================================== --- /dev/null +++ clangd/xpc/cmake/XPCServiceInfo.plist.in @@ -0,0 +1,30 @@ + + + + + CFBundleExecutable + ${CLANGD_XPC_SERVICE_NAME} + CFBundleIdentifier + ${CLANGD_XPC_SERVICE_BUNDLE_NAME} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${CLANGD_XPC_SERVICE_NAME} + CFBundlePackageType + XPC! + CFBundleVersion + + CFBundleShortVersionString + 1.0 + XPCService + + ServiceType + Application + EnvironmentVariables + + CLANGD_AS_XPC_SERVICE + 1 + + + + Index: clangd/xpc/cmake/modules/CreateClangdXPCFramework.cmake =================================================================== --- /dev/null +++ clangd/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: clangd/xpc/framework/CMakeLists.txt =================================================================== --- /dev/null +++ clangd/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: clangd/xpc/framework/ClangdXPC.cpp =================================================================== --- /dev/null +++ clangd/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: clangd/xpc/test-client/CMakeLists.txt =================================================================== --- /dev/null +++ clangd/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: clangd/xpc/test-client/ClangdXPCTestClient.cpp =================================================================== --- /dev/null +++ clangd/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 +#include +#include +#include + +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 << ""; + } +} + +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> 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: test/CMakeLists.txt =================================================================== --- test/CMakeLists.txt +++ test/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 Index: test/clangd/xpc/initialize.test =================================================================== --- /dev/null +++ test/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: test/lit.cfg =================================================================== --- test/lit.cfg +++ test/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: test/lit.site.cfg.in =================================================================== --- test/lit.site.cfg.in +++ test/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: unittests/clangd/CMakeLists.txt =================================================================== --- unittests/clangd/CMakeLists.txt +++ unittests/clangd/CMakeLists.txt @@ -65,3 +65,7 @@ LLVMSupport LLVMTestingSupport ) + +if (CLANGD_BUILD_XPC) + add_subdirectory(xpc) +endif () Index: unittests/clangd/xpc/CMakeLists.txt =================================================================== --- /dev/null +++ unittests/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: unittests/clangd/xpc/ConversionTests.cpp =================================================================== --- /dev/null +++ unittests/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 + +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