jkorous created this revision.
jkorous added reviewers: arphaman, sammccall, ilya-biryukov, simark.
Herald added subscribers: cfe-commits, dexonsmith, MaskRay, ioeric, mgorny.

Based on our internal discussions we decided to change our direction with XPC 
support for clangd. We did some extra measurements and found out that JSON 
serialized over XPC is good enough for our use-case. We also took into 
consideration that we'd have to deal with support for multiple workspaces in 
near future.

Probably the simplest solution is to keep XPC completely out of clangd binary 
and use one clangd instance per workspace. This design means it's the least 
intrusive change, it's rather simple (compared to adding support for multiple 
workspaces to clangd itself) and robust (compared to threads-based 
implementation). Long-term it's hopefully also going to be less 
maintenance-demanding since it's dependent only on LSP and not it's 
implementation.

The patch is nearly finished - I just wanted to get some feedback on the design 
early on (before finishing doxygen annotations, documentation, etc.).

Our approach to testing for the future is to reuse existing clangd lit tests 
and just send messages through our XPC code. For the time being there's just a 
single minimal testcase.


Repository:
  rCTE Clang Tools Extra

https://reviews.llvm.org/D50452

Files:
  CMakeLists.txt
  Features.inc.in
  clangd/CMakeLists.txt
  clangd/xpc/initialize.test
  lit.cfg
  lit.site.cfg.in
  xpc/CMakeLists.txt
  xpc/README.txt
  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
  xpc/tool/CMakeLists.txt
  xpc/tool/ClangdSubprocess.cpp
  xpc/tool/ClangdSubprocess.h
  xpc/tool/ClangdWorkspaceInstances.cpp
  xpc/tool/ClangdWorkspaceInstances.h
  xpc/tool/ClangdXpcAdapter.cpp
  xpc/tool/log.h

Index: clangd/CMakeLists.txt
===================================================================
--- clangd/CMakeLists.txt
+++ clangd/CMakeLists.txt
@@ -51,4 +51,4 @@
   clangToolingInclusions
   LLVMSupport
   LLVMTestingSupport
-  )
+  )
\ No newline at end of file
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: 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: 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: xpc/tool/log.h
===================================================================
--- /dev/null
+++ xpc/tool/log.h
@@ -0,0 +1,49 @@
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_XPC_LOG_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_XPC_LOG_H
+
+#include <sstream>
+#include <string>
+
+#include <os/log.h>
+
+class Logger {
+private:
+  enum class log_type { log, info, debug };
+  log_type type;
+  std::string buffer_data;
+  std::stringstream buffer;
+  Logger(log_type type) : type(type), buffer_data(), buffer(buffer_data) {}
+
+public:
+  template <typename T> Logger &operator<<(const T &in) {
+    buffer << in;
+    return *this;
+  }
+  friend Logger log();
+  friend Logger log_info();
+  friend Logger log_debug();
+  ~Logger() {
+    switch (type) {
+    case log_type::log:
+      os_log(OS_LOG_DEFAULT, "%s", buffer.str().c_str());
+      break;
+    case log_type::info:
+      os_log_info(OS_LOG_DEFAULT, "%s", buffer.str().c_str());
+      break;
+    case log_type::debug:
+      os_log_debug(OS_LOG_DEFAULT, "%s", buffer.str().c_str());
+      break;
+    }
+  }
+  Logger(Logger &&) = default;
+
+  Logger(const Logger &) = delete;
+  Logger &operator=(const Logger &) = delete;
+  Logger &operator=(Logger &&) = delete;
+};
+
+inline Logger log() { return Logger(Logger::log_type::log); }
+inline Logger log_info() { return Logger(Logger::log_type::info); }
+inline Logger log_debug() { return Logger(Logger::log_type::debug); }
+
+#endif
\ No newline at end of file
Index: xpc/tool/ClangdXpcAdapter.cpp
===================================================================
--- /dev/null
+++ xpc/tool/ClangdXpcAdapter.cpp
@@ -0,0 +1,172 @@
+#include <string>
+#include <vector>
+
+#include "ClangdWorkspaceInstances.h"
+#include "log.h"
+
+namespace {
+ClangdWorkspaceInstances *clangdInstancesPtr = nullptr;
+}
+
+#include <xpc/xpc.h>
+
+namespace {
+void connectionHandler(xpc_connection_t clientConnection) {
+
+  xpc_connection_set_target_queue(
+      clientConnection, dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0));
+
+  clangdInstancesPtr->setStdOutCallback([clientConnection](
+                                            const std::string &workspace,
+                                            const std::string &output) {
+    log_debug() << "clangd children stdout callback: started for workspace='"
+                << workspace << "'";
+
+    // TODO OPTIMIZE - use static const char**
+    std::vector<const char *> keys = {"payload", "workspace"};
+    std::vector<xpc_object_t> values = {xpc_string_create(output.c_str()),
+                                        xpc_string_create(workspace.c_str())};
+
+    auto response = xpc_dictionary_create(keys.data(), values.data(), 2);
+
+    xpc_connection_send_message(clientConnection, response);
+
+    xpc_release(response);
+  });
+
+  xpc_connection_set_event_handler(clientConnection, ^(xpc_object_t message) {
+    if (message == XPC_ERROR_CONNECTION_INVALID) {
+      // connection is being terminated
+      log() << "XPC event handler: received XPC_ERROR_CONNECTION_INVALID";
+      return;
+    }
+
+    if (xpc_get_type(message) != XPC_TYPE_DICTIONARY) {
+      log() << "XPC event handler: received XPC message of unknown type";
+      return;
+    }
+
+    const char *const payload = xpc_dictionary_get_string(message, "payload");
+    const char *const workspace =
+        xpc_dictionary_get_string(message, "workspace");
+
+    if (payload && workspace) {
+      if (!clangdInstancesPtr->doesInstanceExist(workspace)) {
+        // No point in locking any mutex here - the instance can always crash.
+        log_info()
+            << "XPC event handler: spawning new clangd instance for workspace '"
+            << workspace << "'";
+        if (!clangdInstancesPtr->createInstance(workspace)) {
+          log() << "XPC event handler: failed to spawn new clangd instance for "
+                   "workspace '"
+                << "'";
+          xpc_connection_cancel(clientConnection);
+          return;
+        }
+      }
+
+      std::string delimitedPayload(payload);
+      delimitedPayload += "\n---\n";
+
+      if (!clangdInstancesPtr->writeLSPMessage(workspace, delimitedPayload)) {
+        log() << "XPC event handler: failed to write_to_child";
+        xpc_connection_cancel(clientConnection);
+        return;
+      }
+    } else {
+      // Just log the invalide message and go on.
+      __block std::string keysDump;
+      xpc_dictionary_apply(message,
+                           ^bool(const char *key, xpc_object_t /* ignore */) {
+                             if (!keysDump.empty())
+                               keysDump = keysDump + ", ";
+                             keysDump = keysDump + "'" + key + "'";
+                             return true;
+                           });
+      log() << "XPC event handler: received invalid XPC message with keys {"
+            << keysDump << "}";
+    }
+  });
+
+  xpc_connection_resume(clientConnection);
+}
+} // namespace
+
+int main() {
+  log_debug() << "main(): started";
+
+  const char *const clangdPath = getenv("CLANGD_PATH");
+
+  if (!clangdPath) {
+    log() << "main(): missing CLANGD_PATH environment variable";
+    return EXIT_FAILURE;
+  }
+
+  // TODO FIXME add proper JSON RPC header and don't use delimited mode
+  ClangdWorkspaceInstances clangdInstances(clangdPath,
+                                           {"-input-style=delimited"});
+  clangdInstancesPtr = &clangdInstances;
+
+  // logging children stderr
+  clangdInstancesPtr->setStdErrCallback([](const std::string &workspace,
+                                           const pid_t pid,
+                                           const std::string &output) {
+    log_debug() << "clangd child [PID " << pid << "] stderr callback: started";
+    log() << "clangd child [PID " << pid << "] stderr callback: workspace='"
+          << workspace << "' stderr='" << output << "'";
+  });
+
+  // handling XPC transactions
+  clangdInstancesPtr->setOnSpawnCallback([](const std::string &workspace) {
+    xpc_transaction_begin();
+    log_info() << "ClangdWorkspaceInstances:: onspawn callback: ended XPC "
+                  "transaction for workspace "
+               << workspace;
+  });
+  clangdInstancesPtr->setOnRemoveCallback([](const std::string &workspace) {
+    xpc_transaction_end();
+    log_info() << "ClangdWorkspaceInstances:: onremove callback: ended XPC "
+                  "transaction for workspace "
+               << workspace;
+  });
+
+  // handle children crashing
+  {
+    // We are gonna use GCD for SIGCHLD handling so we can actually do something
+    // without being overly restricted by signal safety requirements.
+    // Using DISPATCH_QUEUE_PRIORITY_HIGH as if we detect crash we want to react
+    // ASAP so user experience impact is as low as possible.
+    dispatch_source_t source = dispatch_source_create(
+        DISPATCH_SOURCE_TYPE_SIGNAL, SIGCHLD, 0,
+        dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
+    dispatch_source_set_event_handler(source, ^{
+      log_debug() << "signal pseudo-handler: started";
+      pid_t pid;
+      int status;
+
+      while ((pid = waitpid(-1, &status, 0)) > 0) {
+        if (WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS) {
+          log() << "signal pseudo-handler: clangd PID " << pid
+                << " exited with EXIT_SUCCESS";
+          clangdInstancesPtr->removeInstance(pid);
+
+          if (clangdInstancesPtr->existingInstancesCount() == 0) {
+            log() << "signal pseudo-handler: no active children - exiting";
+            exit(EXIT_SUCCESS);
+          }
+        } else {
+          // TODO FIXME some restarting strategy - maybe some increased and/or
+          // random delay?
+          log() << "signal pseudo-handler: clangd PID " << pid
+                << " died - restarting";
+          clangdInstancesPtr->restartInstance(pid);
+        }
+      }
+    });
+    dispatch_resume(source);
+  }
+
+  xpc_main(connectionHandler);
+
+  return EXIT_SUCCESS;
+}
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 <functional>
+#include <list>
+#include <map>
+#include <mutex>
+#include <string>
+#include <vector>
+
+#include "llvm/ADT/Optional.h"
+
+#include <dispatch/dispatch.h>
+
+#include "ClangdSubprocess.h"
+
+class ClangdWorkspaceInstances {
+public:
+  ClangdWorkspaceInstances(const std::string &path,
+                           const std::vector<std::string> &args);
+
+  ~ClangdWorkspaceInstances();
+
+  // Must be called before any createInstance()
+  void setStdOutCallback(std::function<void(const std::string & /* workspace */,
+                                            const std::string & /* output */)>
+                             callback);
+
+  // Must be called before any createInstance()
+  void setStdErrCallback(
+      std::function<void(const std::string & /* workspace */, const pid_t,
+                         const std::string & /* output */)>
+          callback);
+
+  void setOnSpawnCallback(
+      std::function<void(const std::string & /* workspace */)> callback);
+  void setOnRemoveCallback(
+      std::function<void(const std::string & /* workspace */)> callback);
+
+  /// returns true if successfull
+  bool createInstance(const std::string &workspace);
+
+  bool removeInstance(const pid_t pid);
+
+  /// returns true iff successful
+  bool restartInstance(const pid_t pid);
+
+  bool writeLSPMessage(const std::string &workspace, const std::string &msg);
+
+  llvm::Optional<std::string> readLSPMessage(const std::string &workspace);
+
+  llvm::Optional<std::string> readStdErr(const std::string &workspace);
+
+  bool doesInstanceExist(const std::string &workspace);
+
+  std::size_t existingInstancesCount();
+
+private:
+  const std::string path;
+  const std::vector<std::string> args;
+
+  std::recursive_mutex mut;
+
+  // using list to have stable iterators, performance is not an issue (expected
+  // number of workspaces is very low)
+  std::list<ClangdSubprocess> processes;
+  std::map<pid_t, std::list<ClangdSubprocess>::iterator> PIDToInstance;
+  std::map<std::string, pid_t> workspaceToPID;
+  std::map<pid_t, std::string> PIDToWorkspace;
+
+  std::map<pid_t, dispatch_source_t> PIDToStdOutHandler;
+  std::map<pid_t, dispatch_source_t> PIDToStdErrHandler;
+
+  std::function<void(const std::string & /* workspace */)> onSpawnCallback;
+  std::function<void(const std::string & /* workspace */)> onRemoveCallback;
+  std::function<void(const std::string & /* workspace */,
+                     const std::string & /* output */)>
+      stdOutCallback;
+  std::function<void(const std::string & /* workspace */, const pid_t,
+                     const std::string & /* output */)>
+      stdErrCallback;
+};
+
+#endif
\ No newline at end of file
Index: xpc/tool/ClangdWorkspaceInstances.cpp
===================================================================
--- /dev/null
+++ xpc/tool/ClangdWorkspaceInstances.cpp
@@ -0,0 +1,378 @@
+#include "ClangdWorkspaceInstances.h"
+
+#include "log.h"
+
+ClangdWorkspaceInstances::ClangdWorkspaceInstances(
+    const std::string &path, const std::vector<std::string> &args)
+    : path(path), args(args) {}
+
+ClangdWorkspaceInstances::~ClangdWorkspaceInstances() {
+  log_debug()
+      << "ClangdWorkspaceInstances::~ClangdWorkspaceInstances(): started";
+  std::lock_guard<std::recursive_mutex> lock(mut);
+  log_debug() << "ClangdWorkspaceInstances::~ClangdWorkspaceInstances(): mutex "
+                 "acquired";
+
+  for (const auto it : PIDToInstance)
+    removeInstance(it.first);
+}
+
+void ClangdWorkspaceInstances::setStdOutCallback(
+    std::function<void(const std::string & /* workspace */,
+                       const std::string & /* output */)>
+        callback) {
+  log_debug() << "ClangdWorkspaceInstances::setStdOutCallback(): started";
+  std::lock_guard<std::recursive_mutex> lock(mut);
+  log_debug()
+      << "ClangdWorkspaceInstances::setStdOutCallback(): mutex acquired";
+  stdOutCallback = callback;
+}
+
+void ClangdWorkspaceInstances::setStdErrCallback(
+    std::function<void(const std::string & /* workspace */, const pid_t,
+                       const std::string & /* output */)>
+        callback) {
+  log_debug() << "ClangdWorkspaceInstances::setStdErrCallback(): started";
+  std::lock_guard<std::recursive_mutex> lock(mut);
+  log_debug()
+      << "ClangdWorkspaceInstances::setStdErrCallback(): mutex acquired";
+  stdErrCallback = callback;
+}
+
+void ClangdWorkspaceInstances::setOnSpawnCallback(
+    std::function<void(const std::string & /* workspace */)> callback) {
+  log_debug() << "ClangdWorkspaceInstances::setOnSpawnCallback(): started";
+  std::lock_guard<std::recursive_mutex> lock(mut);
+  log_debug()
+      << "ClangdWorkspaceInstances::setOnSpawnCallback(): mutex acquired";
+  onSpawnCallback = callback;
+}
+
+void ClangdWorkspaceInstances::setOnRemoveCallback(
+    std::function<void(const std::string & /* workspace */)> callback) {
+  log_debug() << "ClangdWorkspaceInstances::setOnRemoveCallback(): started";
+  std::lock_guard<std::recursive_mutex> lock(mut);
+  log_debug()
+      << "ClangdWorkspaceInstances::setOnRemoveCallback(): mutex acquired";
+  onRemoveCallback = callback;
+}
+
+bool ClangdWorkspaceInstances::createInstance(const std::string &workspace) {
+  log_debug() << "ClangdWorkspaceInstances::createInstance(): started "
+                 "workspace='"
+              << workspace << "'";
+  std::lock_guard<std::recursive_mutex> lock(mut);
+  log_debug() << "ClangdWorkspaceInstances::createInstance(): mutex "
+                 "acquired";
+
+  if (doesInstanceExist(workspace)) {
+    log_info() << "ClangdWorkspaceInstances::createInstance(): workspace "
+                  "already exists - returning early";
+    return true;
+  }
+  {
+    llvm::Optional<ClangdSubprocess> child = startClangdSubprocess(path, args);
+    if (child == llvm::None)
+      return false;
+    processes.push_back(child.getValue());
+  }
+  const pid_t PID = processes.back().PID;
+  log_info() << "ClangdWorkspaceInstances::createInstance(): spawned "
+                "subprocess; PID="
+             << PID;
+  PIDToInstance.emplace(PID, --processes.end());
+  workspaceToPID.emplace(workspace, PID);
+  PIDToWorkspace.emplace(PID, workspace);
+
+  // asynchronous handling of child's stdout
+  {
+    dispatch_source_t eventSource = dispatch_source_create(
+        DISPATCH_SOURCE_TYPE_READ, PIDToInstance[PID]->childStdOut, 0,
+        dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0));
+    dispatch_source_set_event_handler(eventSource, ^{
+      log_debug() << "stdout handler: started";
+      std::lock_guard<std::recursive_mutex> lock(mut);
+      log_debug() << "stdout handler: mutex acquired";
+
+      const auto workspaceIt = PIDToWorkspace.find(PID);
+      if (workspaceIt == PIDToWorkspace.end()) {
+        log() << "stdout handler: nonexistent workspace for PID " << PID;
+        sendKillSignal(PID);
+      }
+      const auto workspace = workspaceIt->second;
+
+      const llvm::Optional<std::string> response = readLSPMessage(workspace);
+      if (response != llvm::None) {
+        log_debug() << "stdout handler: calling stdOutCallback";
+        stdOutCallback(workspace, response.getValue());
+      } else {
+        log() << "stdout handler: failed to read from stdout of child PID "
+              << PID;
+        sendKillSignal(PID);
+      }
+    });
+
+    dispatch_resume(eventSource);
+    log_debug() << "ClangdWorkspaceInstances::createInstance(): created "
+                   "dispatch source for stdout of PID "
+                << PID;
+    PIDToStdOutHandler[PID] = eventSource;
+  }
+
+  {
+    // Using QOS_CLASS_BACKGROUND as we are just logging stderr output.
+    dispatch_source_t eventSource = dispatch_source_create(
+        DISPATCH_SOURCE_TYPE_READ, PIDToInstance[PID]->childStdErr, 0,
+        dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0));
+    dispatch_source_set_event_handler(eventSource, ^{
+      log_debug() << "stderr handler: started";
+      std::lock_guard<std::recursive_mutex> lock(mut);
+      log_debug() << "stderr handler: mutex acquired";
+
+      const auto workspaceIt = PIDToWorkspace.find(PID);
+      if (workspaceIt == PIDToWorkspace.end()) {
+        log() << "stderr handler: nonexistent workspace for PID " << PID;
+        sendKillSignal(PID);
+      }
+      const auto workspace = workspaceIt->second;
+
+      const llvm::Optional<std::string> response = readStdErr(workspace);
+      if (response != llvm::None) {
+        log_debug() << "stderr handler: calling stdErrCallback";
+        stdErrCallback(workspace, PID, response.getValue());
+      } else {
+        log() << "stdout handler: failed to read from stderr of child PID "
+              << PID;
+        sendKillSignal(PID);
+      }
+    });
+
+    dispatch_resume(eventSource);
+    log_debug() << "ClangdWorkspaceInstances::createInstance(): created "
+                   "dispatch source for stderr of PID "
+                << PID;
+    PIDToStdErrHandler[PID] = eventSource;
+  }
+  onSpawnCallback(workspace);
+
+  return true;
+}
+
+bool ClangdWorkspaceInstances::removeInstance(const pid_t pid) {
+  log_debug() << "ClangdWorkspaceInstances::removeInstance(): started";
+  std::lock_guard<std::recursive_mutex> lock(mut);
+  log_debug() << "ClangdWorkspaceInstances::removeInstance(): mutex acquired";
+
+  const auto childIt = PIDToInstance.find(pid);
+  // Try to do the clean-up no matter how messy the current state is and
+  // eventually return failure only after that.
+  bool anyFailure = false;
+
+  if (childIt != PIDToInstance.end()) {
+    // Must precede closing child's I/O pipes file descriptors.
+    {
+      const auto stdoutHandlerIt = PIDToStdOutHandler.find(pid);
+      if (stdoutHandlerIt != PIDToStdOutHandler.end()) {
+        dispatch_source_cancel(stdoutHandlerIt->second);
+      } else {
+        log_info() << "removeInstance(): didn't find stdout handler for PID "
+                   << pid;
+        anyFailure = true;
+      }
+    }
+    {
+      const auto stderrHandlerIt = PIDToStdErrHandler.find(pid);
+      if (stderrHandlerIt != PIDToStdErrHandler.end()) {
+        dispatch_source_cancel(stderrHandlerIt->second);
+      } else {
+        log_info() << "removeInstance(): didn't find stderr handler for PID "
+                   << pid;
+        anyFailure = true;
+      }
+    }
+
+    endClangdSubprocess(pid);
+    processes.erase(childIt->second);
+    if (PIDToInstance.erase(pid) != 1) {
+      log() << "ClangdWorkspaceInstances::removeInstance(): PIDToInstance "
+               "doesn't "
+               "contain PID "
+            << pid;
+      anyFailure = true;
+    }
+
+    std::string workspace;
+    {
+      const auto workspaceIt = PIDToWorkspace.find(pid);
+      if (workspaceIt == PIDToWorkspace.end()) {
+        log() << "ClangdWorkspaceInstances::removeInstance(): PIDToWorkspace "
+                 "doesn't contain PID "
+              << pid;
+        anyFailure = true;
+      }
+      workspace = workspaceIt->second;
+      onRemoveCallback(workspace);
+    }
+
+    if (workspaceToPID.erase(workspace) != 1) {
+      log() << "ClangdWorkspaceInstances::removeInstance(): workspaceToPID "
+               "doesn't "
+               "contain workspace "
+            << workspace;
+      anyFailure = true;
+    }
+
+    if (PIDToWorkspace.erase(pid) != 1) {
+      log() << "ClangdWorkspaceInstances::removeInstance(): PIDToWorkspace "
+               "doesn't "
+               "contain PID "
+            << pid;
+      anyFailure = true;
+    }
+  } else {
+    log() << "ClangdWorkspaceInstances::removeInstance(): PID " << pid
+          << " doesn't exist anymore";
+    anyFailure = true;
+  }
+
+  return !anyFailure;
+}
+
+/// returns true iff successful
+bool ClangdWorkspaceInstances::restartInstance(const pid_t PID) {
+  log_debug() << "ClangdWorkspaceInstances::restartInstance(): started";
+  std::lock_guard<std::recursive_mutex> lock(mut);
+  log_debug() << "ClangdWorkspaceInstances::restartInstance(): mutex acquired";
+
+  // Must be done before instance is removed.
+  llvm::Optional<std::string> workspace = [&]() {
+    const auto workspaceIt = PIDToWorkspace.find(PID);
+    return workspaceIt == PIDToWorkspace.end()
+               ? llvm::Optional<std::string>(llvm::None)
+               : workspaceIt->second;
+  }();
+
+  if (!removeInstance(PID)) {
+    log() << "ClangdWorkspaceInstances::restartInstance(): failed to remove "
+             "child";
+    return false;
+  }
+
+  if (workspace == llvm::None) {
+    log() << "ClangdWorkspaceInstances::restartInstance(): no instance for PID "
+          << PID;
+    return false;
+  }
+
+  if (!createInstance(workspace.getValue())) {
+    log()
+        << "ClangdWorkspaceInstances::restartInstance(): failed to start child";
+    return false;
+  }
+
+  return true;
+}
+
+bool ClangdWorkspaceInstances::writeLSPMessage(const std::string &workspace,
+                                               const std::string &msg) {
+  log_debug() << "ClangdWorkspaceInstances::writeLSPMessage(): started";
+  std::lock_guard<std::recursive_mutex> lock(mut);
+  log_debug() << "ClangdWorkspaceInstances::writeLSPMessage(): mutex acquired";
+
+  auto pidIt = workspaceToPID.find(workspace);
+  if (pidIt == workspaceToPID.end()) {
+    log() << "ClangdWorkspaceInstances::writeLSPMessage(): workspaceToPID "
+             "doesn't "
+             "contain workspace "
+          << workspace;
+    return false;
+  }
+
+  auto processIt = PIDToInstance.find(pidIt->second);
+  if (processIt == PIDToInstance.end()) {
+    log()
+        << "ClangdWorkspaceInstances::writeLSPMessage(): PIDToInstance doesn't "
+           "contain PID "
+        << pidIt->second;
+    return false;
+  }
+
+  return processIt->second->writeLSPMessage(msg);
+}
+
+llvm::Optional<std::string>
+ClangdWorkspaceInstances::readLSPMessage(const std::string &workspace) {
+  log_debug() << "ClangdWorkspaceInstances::readLSPMessage(): started";
+  std::lock_guard<std::recursive_mutex> lock(mut);
+  log_debug() << "ClangdWorkspaceInstances::readLSPMessage(): mutex "
+                 "acquired";
+
+  auto pidIt = workspaceToPID.find(workspace);
+  if (pidIt == workspaceToPID.end()) {
+    log() << "ClangdWorkspaceInstances::readLSPMessage(): no "
+             "child for "
+             "workspace '"
+          << workspace << "'";
+    return llvm::None;
+  }
+
+  auto procIt = PIDToInstance.find(pidIt->second);
+  if (procIt == PIDToInstance.end()) {
+    log() << "ClangdWorkspaceInstances::readLSPMessage(): no "
+             "sub-process for workspace '"
+          << workspace << "'";
+    return llvm::None;
+  }
+
+  log_debug() << "ClangdWorkspaceInstances::readLSPMessage(): success";
+  return procIt->second->readLSPMessage();
+}
+
+llvm::Optional<std::string>
+ClangdWorkspaceInstances::readStdErr(const std::string &workspace) {
+  log_debug() << "ClangdWorkspaceInstances::readStdErr(): started";
+  std::lock_guard<std::recursive_mutex> lock(mut);
+  log_debug() << "ClangdWorkspaceInstances::readStdErr(): mutex acquired";
+
+  auto pidIt = workspaceToPID.find(workspace);
+  if (pidIt == workspaceToPID.end()) {
+    log() << "ClangdWorkspaceInstances::readStdErr(): no child for "
+             "workspace '"
+          << workspace << "'";
+    return llvm::None;
+  }
+
+  auto procIt = PIDToInstance.find(pidIt->second);
+  if (procIt == PIDToInstance.end()) {
+    log() << "ClangdWorkspaceInstances::readStdErr(): no sub-process "
+             "for workspace '"
+          << workspace << "'";
+    return llvm::None;
+  }
+
+  llvm::Optional<std::string> result = procIt->second->readStdErr();
+  if (result != llvm::None)
+    log_debug() << "ClangdWorkspaceInstances::readStdErr(): success";
+  else
+    log_debug() << "ClangdWorkspaceInstances::readStdErr(): failed";
+
+  return result;
+}
+
+bool ClangdWorkspaceInstances::doesInstanceExist(const std::string &workspace) {
+  log_debug() << "ClangdWorkspaceInstances::doesInstanceExist(): started";
+  std::lock_guard<std::recursive_mutex> lock(mut);
+  log_debug() << "ClangdWorkspaceInstances::doesInstanceExist(): mutex "
+                 "acquired";
+
+  return workspaceToPID.find(workspace) != workspaceToPID.end();
+}
+
+std::size_t ClangdWorkspaceInstances::existingInstancesCount() {
+  log_debug() << "ClangdWorkspaceInstances::existingInstancesCount(): started";
+  std::lock_guard<std::recursive_mutex> lock(mut);
+  log_debug() << "ClangdWorkspaceInstances::existingInstancesCount(): mutex "
+                 "acquired";
+
+  return processes.size();
+}
\ 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 <string>
+#include <vector>
+
+/// 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<std::string> readLSPMessage();
+
+  llvm::Optional<std::string> readStdErr();
+};
+
+llvm::Optional<ClangdSubprocess>
+startClangdSubprocess(const std::string &path,
+                      const std::vector<std::string> &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 <signal.h>
+#include <spawn.h>
+
+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<char> 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<std::string> 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<std::size_t> 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<std::string> 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<std::string> ClangdSubprocess::readLSPMessage() {
+  log_debug() << "ClangdSubprocess::readLSPMessage() started";
+  std::size_t expectedContentLength = 0;
+  {
+    llvm::Optional<std::size_t> 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<char> 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<std::string> ClangdSubprocess::readStdErr() {
+  log_debug() << "ClangdSubprocess::readStdErr() started; childStdErr file "
+                 "descriptor = "
+              << childStdErr;
+  std::string result;
+
+  std::vector<char> 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<ClangdSubprocess>
+startClangdSubprocess(const std::string &path,
+                      const std::vector<std::string> &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<char *const>(path.c_str()); // TODO FIXME const_cast
+
+  std::size_t i = 1;
+  for (auto &a : args) {
+    args_ptrs[i] = const_cast<char *const>(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/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/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 <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,
+                          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<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;
+    std::vector<const char *> keys = {"payload", "workspace"};
+    std::vector<xpc_object_t> 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/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/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,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/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/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-adapter</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_PATH</key>
+        <string>${CLANGD_PATH_IN_CLANGDXPC_SERVICE}</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/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/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: 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)
 
 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
   )
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to