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 @@ -72,6 +73,10 @@ ) endif() +if(CLANGD_BUILD_XPC_SUPPORT) + list(APPEND CLANG_TOOLS_TEST_DEPS clangd-xpc-test-client) +endif() + set(llvm_utils FileCheck count not ) Index: Features.inc.in =================================================================== --- /dev/null +++ Features.inc.in @@ -0,0 +1 @@ +#define CLANGD_ENABLE_XPC_SUPPORT @CLANGD_BUILD_XPC_SUPPORT@ Index: ProtocolHandlers.h =================================================================== --- ProtocolHandlers.h +++ ProtocolHandlers.h @@ -7,11 +7,11 @@ // //===----------------------------------------------------------------------===// // -// ProtocolHandlers translates incoming JSON requests from JSONRPCDispatcher -// into method calls on ClangLSPServer. +// ProtocolHandlers translates incoming LSP requests from JSONRPCDispatcher +// or XPCDispatcher into method calls on ClangLSPServer. // // Currently it parses requests into objects, but the ClangLSPServer is -// responsible for producing JSON responses. We should move that here, too. +// responsible for producing LSP responses. We should move that here, too. // //===----------------------------------------------------------------------===// Index: clangd/CMakeLists.txt =================================================================== --- clangd/CMakeLists.txt +++ clangd/CMakeLists.txt @@ -52,3 +52,7 @@ LLVMSupport LLVMTestingSupport ) + +if (CLANGD_BUILD_XPC_SUPPORT) + add_subdirectory(xpc) +endif () 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":{"codeActionProvider":true,"completionProvider":{"resolveProvider":false,"triggerCharacters":[".",">",":"]},"definitionProvider":true,"documentFormattingProvider":true,"documentHighlightProvider":true,"documentOnTypeFormattingProvider":{"firstTriggerCharacter":"}","moreTriggerCharacter":[]},"documentRangeFormattingProvider":true,"executeCommandProvider":{"commands":["clangd.applyFix"]},"hoverProvider":true,"renameProvider":true,"signatureHelpProvider":{"triggerCharacters":["(",","]},"textDocumentSync":2,"workspaceSymbolProvider":true}}} + +{"jsonrpc":"2.0","id":3,"method":"shutdown"} +# CHECK: {"id":3,"jsonrpc":"2.0","result":null} + +{"jsonrpc":"2.0","method":"exit"} 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') check_clang_tidy = os.path.join( 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: 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_SUPPORT) + list(APPEND CLANGD_XPC_LIBS "clangdXpcJsonConversions" "clangdXpcDispatcher") +endif() + target_link_libraries(clangd PRIVATE clangBasic @@ -17,4 +23,5 @@ clangSema clangTooling clangToolingCore + ${CLANGD_XPC_LIBS} ) Index: tool/ClangdMain.cpp =================================================================== --- tool/ClangdMain.cpp +++ tool/ClangdMain.cpp @@ -8,6 +8,7 @@ //===----------------------------------------------------------------------===// #include "ClangdLSPServer.h" +#include "Features.inc" #include "JSONRPCDispatcher.h" #include "Path.h" #include "Trace.h" @@ -23,6 +24,11 @@ #include #include +#ifdef CLANGD_ENABLE_XPC_SUPPORT +#include "xpc/XPCDispatcher.h" +#include +#endif + using namespace clang; using namespace clang::clangd; @@ -250,6 +256,23 @@ llvm::set_thread_name("clangd.main"); // Change stdin to binary to not lose \r\n on windows. llvm::sys::ChangeStdinToBinary(); + +#ifdef CLANGD_ENABLE_XPC_SUPPORT + if (getenv("CLANGD_AS_XPC_SERVICE")) { + XPCDispatcher Dispatcher([](const json::Expr &Params) { + replyError(ErrorCode::MethodNotFound, "method not found"); + }); + registerCallbackHandlers(Dispatcher, /*Callbacks=*/LSPServer); + + XPCLSPOutput Out( + llvm::errs(), + InputMirrorStream.hasValue() ? InputMirrorStream.getPointer(): nullptr); + + clangd::LoggingSession LoggingSession(Out); + + runXPCServerLoop(Dispatcher, Out, LSPServer.getIsDone()); + } else +#endif { JSONRPCDispatcher Dispatcher([](const json::Expr &Params) { replyError(ErrorCode::MethodNotFound, "method not found"); 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 XPCJSONConversions.cpp XPCDispatcher.cpp) + +add_clang_library(clangdXpcJsonConversions + XPCJSONConversions.cpp + ) + +add_clang_library(clangdXpcDispatcher + XPCDispatcher.cpp + DEPENDS clangdXpcJsonConversions + LINK_LIBS clangdXpcJsonConversions + ) Index: xpc/README.txt =================================================================== --- /dev/null +++ xpc/README.txt @@ -0,0 +1,4 @@ +This directory contains the XPC transport layer protocol, +XPC framework wrapper that wraps around Clangd to make it a valid XPC +service, and an XPC test-client. + Index: xpc/XPCDispatcher.h =================================================================== --- /dev/null +++ xpc/XPCDispatcher.h @@ -0,0 +1,91 @@ +//===--- XPCDispatcher.h - Main XPC service entry point ---------*- 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_XPCDISPATCHER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_XPC_XPCDISPATCHER_H + +#include "JSONExpr.h" +#include "Logger.h" +#include "LSPOutput.h" +#include "Protocol.h" +#include "Trace.h" +#include "clang/Basic/LLVM.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringMap.h" +#include +#include + +#include + +namespace clang { +namespace clangd { + +class XPCLSPOutput : public LSPOutput { +public: + XPCLSPOutput(llvm::raw_ostream &Logs, + llvm::raw_ostream *InputMirror = nullptr) + : clientConnection(nullptr), Logs(Logs), InputMirror{InputMirror} { } + + /// Set's clientConnection for output. + /// Must be either reset every time XPC event handler is called or connection + /// has to be retained through xpc_retain(). + void resetClientConnection(xpc_connection_t newClientConnection); + + /// Emit an LSP message. + void writeMessage(const json::Expr &Result) override; + + /// Write a line to the logging stream. + void log(const Twine &Message) override; + + /// Mirror \p Message into InputMirror stream. Does nothing if InputMirror is + /// null. + /// Unlike other methods, mirrorInput is not thread-safe. + void mirrorInput(const Twine &) override; + +private: + xpc_connection_t clientConnection; + llvm::raw_ostream &Logs; + llvm::raw_ostream *InputMirror; + + std::mutex LogStreamMutex; +}; + +/// Parses the LSP "header" and calls the right registered Handler. +class XPCDispatcher { +public: + // A handler responds to requests for a particular method name. + using Handler = std::function; + + /// UnknownHandler is called when an unknown method is received. + XPCDispatcher(Handler UnknownHandler) + : UnknownHandler(std::move(UnknownHandler)) {} + + /// Registers a Handler for the specified Method. + void registerHandler(StringRef Method, Handler H); + + /// Parses a JSONRPC message and calls the Handler for it. + bool call(const json::Expr &Message, LSPOutput &Out) const; + +private: + llvm::StringMap Handlers; + Handler UnknownHandler; +}; + +/// Parses input queries from LSP client (coming through XPC connections) and +/// runs call method of \p Dispatcher for each query. +/// If \p InputJSONMirror is set every received request is logged in JSON form. +/// After handling each query checks if \p IsDone is set true and exits +/// the loop if it is. +void runXPCServerLoop(XPCDispatcher &Dispatcher, XPCLSPOutput &Out, + bool &IsDone); + +} // namespace clangd +} // namespace clang + +#endif Index: xpc/XPCDispatcher.cpp =================================================================== --- /dev/null +++ xpc/XPCDispatcher.cpp @@ -0,0 +1,162 @@ +//===--- XPCDispatcher.h - Main XPC service entry point ---------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "XPCDispatcher.h" + +#include "DispatcherCommon.h" +#include "xpc/XPCJSONConversions.h" +#include "JSONExpr.h" +#include "Trace.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Chrono.h" +#include "llvm/Support/SourceMgr.h" +#include + +#include + +using namespace clang; +using namespace clangd; + +void XPCDispatcher::registerHandler(StringRef Method, Handler H) { + assert(!Handlers.count(Method) && "Handler already registered!"); + Handlers[Method] = std::move(H); +} + +bool XPCDispatcher::call(const json::Expr &Message, LSPOutput &Out) const { + // Message must be an object with "jsonrpc":"2.0". + auto *Object = Message.asObject(); + if (!Object || Object->getString("jsonrpc") != Optional("2.0")) + return false; + // ID may be any JSON value. If absent, this is a notification. + llvm::Optional ID; + if (auto *I = Object->get("id")) + ID = std::move(*I); + // Method must be given. + auto Method = Object->getString("method"); + if (!Method) + return false; + // Params should be given, use null if not. + json::Expr Params = nullptr; + if (auto *P = Object->get("params")) + Params = std::move(*P); + + auto I = Handlers.find(*Method); + auto &Handler = I != Handlers.end() ? I->second : UnknownHandler; + + // Create a Context that contains request information. + WithContextValue WithRequestOut(RequestOut, &Out); + llvm::Optional WithID; + if (ID) + WithID.emplace(RequestID, *ID); + + // Create a tracing Span covering the whole request lifetime. + trace::Span Tracer(*Method); + if (ID) + SPAN_ATTACH(Tracer, "ID", *ID); + SPAN_ATTACH(Tracer, "Params", Params); + + // Stash a reference to the span args, so later calls can add metadata. + WithContext WithRequestSpan(RequestSpan::stash(Tracer)); + Handler(std::move(Params)); + return true; +} + +void XPCLSPOutput::resetClientConnection(xpc_connection_t newClientConnection) { + clientConnection = newClientConnection; +} + +void XPCLSPOutput::writeMessage(const json::Expr &Message) { + xpc_object_t response = jsonToXpc(Message); + xpc_connection_send_message(clientConnection, response); + xpc_release(response); +} + +void XPCLSPOutput::log(const Twine &Message) { + llvm::sys::TimePoint<> Timestamp = std::chrono::system_clock::now(); + trace::log(Message); + std::lock_guard Guard(LogStreamMutex); + Logs << llvm::formatv("[{0:%H:%M:%S.%L}] {1}\n", Timestamp, Message); + Logs.flush(); +} + +void XPCLSPOutput::mirrorInput(const Twine &Message) { + if (!InputMirror) + return; + + *InputMirror << Message; + InputMirror->flush(); +} + +// C "closure" +namespace { + XPCDispatcher *DispatcherPtr = nullptr; + bool *IsDonePtr = nullptr; + XPCLSPOutput *OutPtr = nullptr; + bool HasTransactionBeginBeenCalled = false; + + void connection_handler(xpc_connection_t clientConnection) { + + xpc_connection_set_target_queue(clientConnection, dispatch_get_main_queue()); + + if(!HasTransactionBeginBeenCalled) { + // man xpc_main + // "If the service remains idle after a period of inactivity (defined by + // the system), xpc_main() will exit the process." + // ... + // "Services may extend the default behavior using xpc_transaction_begin()..." + xpc_transaction_begin(); + HasTransactionBeginBeenCalled = true; + } + + xpc_connection_set_event_handler( + clientConnection, + ^(xpc_object_t message) { + + OutPtr->resetClientConnection(xpc_dictionary_get_remote_connection(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::Expr Doc = xpcToJson(message); + + log( llvm::formatv("<-- {0:2}\n", Doc) ); + OutPtr->mirrorInput( + llvm::formatv("{1}", Doc)); + + if (DispatcherPtr->call(Doc, *OutPtr)) { + if (*IsDonePtr) { + log("IsDonePtr == true. Returning from the event_handler.\n"); + xpc_connection_cancel(xpc_dictionary_get_remote_connection(message)); + } + } else + log("JSON dispatch failed!\n"); + } + ); + + xpc_connection_resume(clientConnection); + } +} + +void clangd::runXPCServerLoop(XPCDispatcher &Dispatcher, XPCLSPOutput &Out, + bool &IsDone) { + DispatcherPtr = &Dispatcher; + IsDonePtr = &IsDone; + OutPtr = &Out; + + xpc_main(connection_handler); +} Index: xpc/cmake/Info.plist.in =================================================================== --- /dev/null +++ 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: xpc/cmake/XPCServiceInfo.plist.in =================================================================== --- /dev/null +++ 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: 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/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/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/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/test-client/ClangdXPCTestClient.cpp =================================================================== --- /dev/null +++ xpc/test-client/ClangdXPCTestClient.cpp @@ -0,0 +1,96 @@ +#include "xpc/XPCJSONConversions.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) { + clang::clangd::json::Expr 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(), NULL); + + // 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 = clangd::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; +}