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)
configure_lit_site_cfg(
${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.in
@@ -72,6 +73,10 @@
)
endif()
+if(CLANGD_BUILD_XPC)
+ 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_BUILD_XPC @CLANGD_BUILD_XPC@
Index: clangd/CMakeLists.txt
===================================================================
--- clangd/CMakeLists.txt
+++ clangd/CMakeLists.txt
@@ -51,4 +51,4 @@
clangToolingInclusions
LLVMSupport
LLVMTestingSupport
- )
+ )
\ No newline at end of file
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-DAG: {"id":0,"jsonrpc":"2.0","result":{"capabilities":{"codeActionProvider":true,"completionProvider":{"resolveProvider":false,"triggerCharacters":[".",">",":"]},"definitionProvider":true,"documentFormattingProvider":true,"documentHighlightProvider":true,"documentOnTypeFormattingProvider":{"firstTriggerCharacter":"}","moreTriggerCharacter":[]},"documentRangeFormattingProvider":true,"documentSymbolProvider":true,"executeCommandProvider":{"commands":["clangd.applyFix"]},"hoverProvider":true,"renameProvider":true,"signatureHelpProvider":{"triggerCharacters":["(",","]},"textDocumentSync":2,"workspaceSymbolProvider":true}}}
+
+{"jsonrpc":"2.0","id":3,"method":"shutdown"}
+# CHECK-DAG: {"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 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: xpc/CMakeLists.txt
===================================================================
--- /dev/null
+++ xpc/CMakeLists.txt
@@ -0,0 +1,17 @@
+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)
+add_subdirectory(tool)
+
+include_directories(
+ ${CMAKE_CURRENT_SOURCE_DIR}/../
+)
+
+set(LLVM_LINK_COMPONENTS
+ Support
+ )
\ No newline at end of file
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/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-adapter
+ 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_PATH
+ ${CLANGD_PATH_IN_CLANGDXPC_SERVICE}
+
+
+
+
Index: xpc/cmake/modules/CreateClangdXPCFramework.cmake
===================================================================
--- /dev/null
+++ xpc/cmake/modules/CreateClangdXPCFramework.cmake
@@ -0,0 +1,81 @@
+# 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}")
+ set(CLANGD_PATH_IN_CLANGDXPC_SERVICE "${CLANGD_FRAMEWORK_OUT_LOCATION}/XPCServices/${CLANGD_XPC_SERVICE_NAME}.xpc/Contents/MacOS/clangd")
+ 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"
+
+ # Copy the clangd-xpc-adapter binary.
+ COMMAND ${CMAKE_COMMAND} -E copy
+ "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/clangd-xpc-adapter"
+ "${CLANGD_XPC_SERVICE_OUT_LOCATION}/MacOS/clangd-xpc-adapter"
+
+ 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
+ clangd-xpc-adapter
+ 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,12 @@
+
+set(SOURCES
+ ClangdXPC.cpp)
+add_clang_library(ClangdXPCLib SHARED
+ ${SOURCES}
+ DEPENDS
+ clangd
+ clangd-xpc-adapter
+)
+create_clangd_xpc_framework(ClangdXPCLib "ClangdXPC")
+
+add_dependencies(ClangdXPC clangd clangd-xpc-adapter)
\ No newline at end of file
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,25 @@
+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
+)
Index: xpc/test-client/ClangdXPCTestClient.cpp
===================================================================
--- /dev/null
+++ xpc/test-client/ClangdXPCTestClient.cpp
@@ -0,0 +1,103 @@
+#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,
+ std::size_t &count) {
+ xpc_type_t Type = xpc_get_type(Object);
+ if (Type == XPC_TYPE_DICTIONARY) {
+ const char *const payload = xpc_dictionary_get_string(Object, "payload");
+ count += strlen(payload);
+ if (payload == nullptr) {
+ OS << "[error]: dumpXPCObject(): object missing payload";
+ } else {
+ OS << payload;
+ }
+ } else {
+ OS << "[error]: dumpXPCObject(): object is not a dictionary";
+ }
+}
+
+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) {
+ static std::size_t byte_count = 0;
+ if (event == XPC_ERROR_CONNECTION_INVALID) {
+ llvm::errs() << "Received XPC_ERROR_CONNECTION_INVALID.";
+ llvm::errs() << "total B received: " << byte_count;
+ exit(EXIT_SUCCESS);
+ }
+ if (event == XPC_ERROR_CONNECTION_INTERRUPTED) {
+ llvm::errs() << "Received XPC_ERROR_CONNECTION_INTERRUPTED.";
+ llvm::errs() << "total B received: " << byte_count;
+ exit(EXIT_SUCCESS);
+ }
+
+ dumpXPCObject(event, llvm::outs(), byte_count);
+ 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;
+ std::vector keys = {"payload", "workspace"};
+ std::vector values = {xpc_string_create(Line.str().c_str()),
+ xpc_string_create("test")};
+
+ auto request = xpc_dictionary_create(keys.data(), values.data(), 2);
+
+ xpc_connection_send_message(conn, request);
+ }
+
+ dispatch_main();
+
+ // dispatch_main() doesn't return
+ return EXIT_FAILURE;
+}
Index: xpc/tool/CMakeLists.txt
===================================================================
--- /dev/null
+++ xpc/tool/CMakeLists.txt
@@ -0,0 +1,5 @@
+add_clang_tool(clangd-xpc-adapter
+ ClangdXpcAdapter.cpp
+ ClangdWorkspaceInstances.cpp
+ ClangdSubprocess.cpp
+ )
\ No newline at end of file
Index: xpc/tool/ClangdSubprocess.h
===================================================================
--- /dev/null
+++ xpc/tool/ClangdSubprocess.h
@@ -0,0 +1,35 @@
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_XPC_CLANGD_SUBPROCESS_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_XPC_CLANGD_SUBPROCESS_H
+
+#include "llvm/ADT/Optional.h"
+#include
+#include
+
+/// Holds running clangd process data.
+/// Lifetime of an instance is not automagically tied to the process.
+struct ClangdSubprocess {
+ const pid_t PID = 0;
+ const int childStdOut = -1;
+ const int childStdErr = -1;
+ const int childStdIn = -1;
+
+ ClangdSubprocess(pid_t PID, int childStdOut, int childStdErr, int childStdIn);
+
+ bool writeLSPMessage(const std::string &msg);
+
+ llvm::Optional readLSPMessage();
+
+ llvm::Optional readStdErr();
+};
+
+llvm::Optional
+startClangdSubprocess(const std::string &path,
+ const std::vector &args);
+
+// Use with care - unless someone calls waitpid() the process would become a
+// zombie.
+void sendKillSignal(pid_t pid);
+
+void endClangdSubprocess(pid_t pid);
+
+#endif
\ No newline at end of file
Index: xpc/tool/ClangdSubprocess.cpp
===================================================================
--- /dev/null
+++ xpc/tool/ClangdSubprocess.cpp
@@ -0,0 +1,338 @@
+#include "ClangdSubprocess.h"
+
+#include "log.h"
+
+#include
+#include
+
+ClangdSubprocess::ClangdSubprocess(pid_t PID, int childStdOut, int childStdErr,
+ int childStdIn)
+ : PID(PID), childStdOut(childStdOut), childStdErr(childStdErr),
+ childStdIn(childStdIn) {}
+
+bool ClangdSubprocess::writeLSPMessage(const std::string &msg) {
+ log_debug() << "ClangdSubprocess::writeLSPMessage() started";
+ std::size_t alreadyWritten = 0;
+ ssize_t lastWritten = 0;
+ while (true) {
+ if (alreadyWritten == msg.size()) {
+ break;
+ } else if (alreadyWritten > msg.size()) {
+ log() << "ClangdSubprocess::writeLSPMessage() error 'already_written > "
+ "msg.size()'";
+ return false;
+ }
+
+ lastWritten = ::write(childStdIn, msg.c_str() + alreadyWritten,
+ msg.size() - alreadyWritten);
+ if (lastWritten == -1) {
+ if (errno == EINTR)
+ continue;
+ log() << "ClangdSubprocess::writeLSPMessage() failed";
+ return false;
+ }
+
+ alreadyWritten += lastWritten;
+ };
+
+ log_debug() << "ClangdSubprocess::writeLSPMessage() successfully finished";
+ return true;
+}
+
+/// Returns true iff successfully read prefix from file discriptor fd.
+static bool ignorePrefix(const int fd, const std::string &prefix) {
+ log_debug() << "ignorePrefix(): started";
+ std::vector buffer(prefix.size());
+ std::size_t remainingPrefixLength = prefix.size();
+ std::string actualPrefix;
+
+ while (remainingPrefixLength > 0) {
+ const int bytesRead = ::read(fd, buffer.data(), remainingPrefixLength);
+ if (bytesRead < 0) {
+ if (errno == EINTR)
+ continue;
+
+ if (errno == EAGAIN)
+ log_debug() << "ignorePrefix() errno=EAGAIN";
+
+ log() << "ignorePrefix(): failed to read()";
+ return false;
+ } else if (bytesRead > 0) {
+ actualPrefix += std::string(buffer.begin(), buffer.begin() + bytesRead);
+ remainingPrefixLength -= bytesRead;
+ log_debug() << "ignorePrefix(): read() returned " << bytesRead
+ << "B; remainingPrefixLength=" << remainingPrefixLength
+ << "B";
+ } else {
+ log() << "ignorePrefix(): failed to read() more bytes";
+ return false;
+ }
+ }
+
+ if (actualPrefix != prefix) {
+ log() << "ignorePrefix(): expected '" << prefix << "'; got '"
+ << actualPrefix << "'";
+ return false;
+ }
+
+ log_debug() << "ignorePrefix(): success";
+ return true;
+}
+
+/// Doesn't return the terminator char at the end of result.
+static llvm::Optional readUntilChar(int fd, const char terminator,
+ std::size_t hardLimit) {
+ log_debug() << "readUntilChar(): started";
+ std::string result;
+ char ch;
+ while (hardLimit > 0) {
+ const int bytesRead = ::read(fd, &ch, 1);
+ if (bytesRead == 1) {
+ log_debug() << "readUntilChar(): read 1B";
+ if (ch == terminator)
+ return result;
+ result += ch;
+ --hardLimit;
+ continue;
+ } else if (bytesRead < 0) {
+ if (errno == EINTR)
+ continue;
+
+ if (errno == EAGAIN)
+ log_debug() << "readUntilChar() errno=EAGAIN";
+
+ log() << "readUntilChar(): failed to read()";
+ return llvm::None;
+ } else {
+ log() << "readUntilChar(): failed to read() more bytes";
+ return llvm::None;
+ }
+ }
+ log() << "readUntilChar(): failed to reach terminator char before hardLimit "
+ "of "
+ << hardLimit << "B";
+ return llvm::None;
+}
+
+/// returns JSON payload length
+static llvm::Optional parseLSPHeader(const int fd) {
+ log_debug() << "parseLSPHeader(): started";
+ // FIXME This is just simplistic - expects LSP header to be always just single
+ // LSP header file (Content-Length).
+ // TODO Proper LSP header support
+ std::size_t contentLength = 0;
+
+ // The first and only expected LSP header field.
+ {
+ if (!ignorePrefix(fd, "Content-Length: ")) {
+ log() << "parseLSPHeader(): expected LSP header field start - "
+ "'Content-Length: ' missing";
+ return llvm::None;
+ }
+
+ llvm::Optional tentativeContentLength =
+ readUntilChar(fd, '\r', 10);
+ if (tentativeContentLength == llvm::None) {
+ log() << "parseLSPHeader(): expected LSP header field Content-Length "
+ "value "
+ "missing";
+ return llvm::None;
+ }
+
+ for (char c : tentativeContentLength.getValue()) {
+ if (c < '0' || c > '9') {
+ break;
+ }
+ contentLength *= 10;
+ contentLength += c - '0';
+ }
+
+ if (!ignorePrefix(fd, "\n")) {
+ log() << "parseLSPHeader(): expected LSP header field delimiter - \\r\\n "
+ "missing";
+ return llvm::None;
+ }
+ }
+
+ if (!ignorePrefix(fd, "\r\n")) {
+ log() << "parseLSPHeader(): expected LSP header delimiter - \\r\\n missing";
+ return llvm::None;
+ }
+
+ return contentLength;
+}
+
+// TODO OPTIMIZE
+llvm::Optional ClangdSubprocess::readLSPMessage() {
+ log_debug() << "ClangdSubprocess::readLSPMessage() started";
+ std::size_t expectedContentLength = 0;
+ {
+ llvm::Optional tentativeContentLength =
+ parseLSPHeader(childStdOut);
+ if (!tentativeContentLength) {
+ log() << "ClangdSubprocess::readLSPMessage() failed to read LSP header";
+ return llvm::None;
+ }
+ expectedContentLength = tentativeContentLength.getValue();
+ }
+
+ log() << "ClangdSubprocess::readLSPMessage() received LSP header for "
+ << expectedContentLength << "B JSON content";
+
+ std::vector buffer(expectedContentLength);
+ std::size_t alreadyReadByteCount = 0;
+
+ log_debug()
+ << "ClangdSubprocess::readLSPMessage() childStdOut file descriptor = "
+ << childStdOut;
+ while (alreadyReadByteCount < expectedContentLength) {
+ const int bytesRead =
+ ::read(childStdOut, buffer.data() + alreadyReadByteCount,
+ expectedContentLength - alreadyReadByteCount);
+ if (bytesRead > 0) {
+ alreadyReadByteCount += bytesRead;
+ log_debug() << "ClangdSubprocess::readLSPMessage() read " << bytesRead
+ << "B; remaining "
+ << expectedContentLength - alreadyReadByteCount << " B";
+ } else if (bytesRead < 0) {
+ if (errno == EINTR)
+ continue;
+
+ if (errno == EAGAIN)
+ log_debug() << "readLSPMessage() errno=EAGAIN";
+
+ log() << "ClangdSubprocess::readLSPMessage() failed";
+ return llvm::None;
+ } else {
+ log() << "ClangdSubprocess::readLSPMessage() returned 0B";
+ return llvm::None;
+ }
+ }
+
+ log_debug() << "ClangdSubprocess::readLSPMessage() successfully finished";
+ return std::string(buffer.begin(), buffer.end());
+}
+
+llvm::Optional ClangdSubprocess::readStdErr() {
+ log_debug() << "ClangdSubprocess::readStdErr() started; childStdErr file "
+ "descriptor = "
+ << childStdErr;
+ std::string result;
+
+ std::vector buffer(4 * 1024); // 4kB
+ while (true) {
+ const int bytesRead = ::read(childStdErr, buffer.data(), buffer.size());
+ if (bytesRead > 0) {
+ result += std::string(buffer.begin(), buffer.end());
+ log_debug() << "ClangdSubprocess::readStdErr() read " << bytesRead << "B";
+ } else if (bytesRead < 0) {
+ if (errno == EINTR) {
+ log_debug() << "ClangdSubprocess::readStdErr() errno=EINTR";
+ continue;
+ }
+ if (errno == EAGAIN) {
+ log_debug() << "ClangdSubprocess::readStdErr() errno=EAGAIN";
+ break;
+ }
+
+ log() << "ClangdSubprocess::readStdErr() failed";
+ log() << "ClangdSubprocess::readStdErr() incomplete result='" << result
+ << "'";
+ return llvm::None;
+ } else {
+ log() << "ClangdSubprocess::readStdErr() returned 0B";
+ log() << "ClangdSubprocess::readStdErr() incomplete result='" << result
+ << "'";
+ return llvm::None;
+ }
+ }
+
+ log_debug() << "ClangdSubprocess::readStdErr() successfully finished";
+ return result;
+}
+
+llvm::Optional
+startClangdSubprocess(const std::string &path,
+ const std::vector &args) {
+ log_debug() << "startClangdSubprocess(): started";
+
+ pid_t pid = 0;
+ int childStdOut = -1;
+ int childStdErr = -1;
+ int childStdIn = -1;
+
+ posix_spawn_file_actions_t action;
+
+ int childInputPipe[2] = {-1, -1}; // read end, write end
+ int childOutputPipe[2] = {-1, -1}; // read end, write end
+ int childErrorPipe[2] = {-1, -1}; // read end, write end
+
+ if (pipe(childInputPipe) || pipe(childOutputPipe) || pipe(childErrorPipe))
+ return llvm::None;
+
+ childStdOut = childOutputPipe[0];
+ childStdErr = childErrorPipe[0];
+ childStdIn = childInputPipe[1];
+ const int childWrite = childOutputPipe[1];
+ const int childError = childErrorPipe[1];
+ const int childRead = childInputPipe[0];
+
+ if (posix_spawn_file_actions_init(&action) ||
+ posix_spawn_file_actions_addclose(&action, childStdIn) ||
+ posix_spawn_file_actions_addclose(&action, childStdOut) ||
+ posix_spawn_file_actions_addclose(&action, childStdErr) ||
+ posix_spawn_file_actions_adddup2(&action, childRead, STDIN_FILENO) ||
+ posix_spawn_file_actions_adddup2(&action, childWrite, STDOUT_FILENO) ||
+ posix_spawn_file_actions_adddup2(&action, childError, STDERR_FILENO) ||
+ posix_spawn_file_actions_addclose(&action, childRead) ||
+ posix_spawn_file_actions_addclose(&action, childWrite) ||
+ posix_spawn_file_actions_addclose(&action, childError)) {
+ log() << "startClangdSubprocess(): posix_spawn_file_actions_t setup failed";
+ return llvm::None;
+ }
+
+ char **args_ptrs = new char *[args.size() + 1 + 1];
+
+ args_ptrs[0] = const_cast(path.c_str()); // TODO FIXME const_cast
+
+ std::size_t i = 1;
+ for (auto &a : args) {
+ args_ptrs[i] = const_cast(a.c_str()); // TODO FIXME const_cast
+ ++i;
+ }
+ args_ptrs[args.size() + 1] = nullptr;
+
+ log_debug() << "startClangdSubprocess(): path = '" << path << "'";
+
+ if (posix_spawn(&pid, /* path */ path.c_str(), &action, /* attrp */ nullptr,
+ /* argv */ args_ptrs, /* envp */ nullptr)) {
+ log() << "startClangdSubprocess(): failed";
+ return llvm::None;
+ } else {
+ log() << "startClangdSubprocess(): spawned PID " << pid;
+ }
+
+ fcntl(childStdOut, F_SETFL, O_NONBLOCK);
+ fcntl(childStdErr, F_SETFL, O_NONBLOCK);
+
+ log_info() << "startClangdSubprocess(): switched file descriptor "
+ "of child's stdout and stderr to O_NONBLOCK";
+
+ posix_spawn_file_actions_destroy(&action);
+ delete[] args_ptrs;
+
+ return ClangdSubprocess(pid, childStdOut, childStdErr, childStdIn);
+}
+
+// Use with care - unless someone calls waitpid() the process would become a
+// zombie.
+void sendKillSignal(pid_t pid) {
+ log() << "sendKillSignal(): PID " << pid;
+ kill(pid, SIGKILL);
+}
+
+void endClangdSubprocess(pid_t pid) {
+ log() << "endClangdSubprocess(): PID " << pid;
+ kill(pid, SIGKILL);
+ waitpid(pid, 0, WNOHANG);
+}
\ No newline at end of file
Index: xpc/tool/ClangdWorkspaceInstances.h
===================================================================
--- /dev/null
+++ xpc/tool/ClangdWorkspaceInstances.h
@@ -0,0 +1,84 @@
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_XPC_CLANGD_WORKSPACE_INSTANCES_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_XPC_CLANGD_WORKSPACE_INSTANCES_H
+
+#include
+#include
+#include