forked from OSchip/llvm-project
[clangd] XPC transport layer
- New transport layer for macOS. - XPC Framework - Test client Framework and client were written by Alex Lorenz. Differential Revision: https://reviews.llvm.org/D54428 llvm-svn: 351280
This commit is contained in:
parent
78e7fff56f
commit
dca9c7cf24
|
@ -1,3 +1,8 @@
|
|||
option(CLANGD_BUILD_XPC "Build XPC Support For Clangd." OFF)
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
|
||||
set(CLANGD_BUILD_XPC ON CACHE BOOL "" FORCE)
|
||||
endif ()
|
||||
|
||||
add_subdirectory(clang-apply-replacements)
|
||||
add_subdirectory(clang-reorder-fields)
|
||||
add_subdirectory(modularize)
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
# Configure the Features.inc file.
|
||||
llvm_canonicalize_cmake_booleans(
|
||||
CLANGD_BUILD_XPC)
|
||||
configure_file(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Features.inc.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/Features.inc
|
||||
)
|
||||
|
||||
set(LLVM_LINK_COMPONENTS
|
||||
Support
|
||||
)
|
||||
|
@ -109,3 +117,6 @@ add_subdirectory(index/dex/dexp)
|
|||
if (LLVM_INCLUDE_BENCHMARKS)
|
||||
add_subdirectory(benchmarks)
|
||||
endif()
|
||||
if ( CLANGD_BUILD_XPC )
|
||||
add_subdirectory(xpc)
|
||||
endif ()
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
#define CLANGD_BUILD_XPC @CLANGD_BUILD_XPC@
|
|
@ -86,6 +86,12 @@ newJSONTransport(std::FILE *In, llvm::raw_ostream &Out,
|
|||
llvm::raw_ostream *InMirror, bool Pretty,
|
||||
JSONStreamStyle = JSONStreamStyle::Standard);
|
||||
|
||||
#ifdef CLANGD_BUILD_XPC
|
||||
// Returns a Transport for macOS based on XPC.
|
||||
// Clangd with this transport is meant to be run as bundled XPC service.
|
||||
std::unique_ptr<Transport> newXPCTransport();
|
||||
#endif
|
||||
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
||||
|
||||
|
|
|
@ -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 @@ set(LLVM_LINK_COMPONENTS
|
|||
support
|
||||
)
|
||||
|
||||
set(CLANGD_XPC_LIBS "")
|
||||
if(CLANGD_BUILD_XPC)
|
||||
list(APPEND CLANGD_XPC_LIBS "clangdXpcJsonConversions" "clangdXpcTransport")
|
||||
endif()
|
||||
|
||||
target_link_libraries(clangd
|
||||
PRIVATE
|
||||
clangBasic
|
||||
|
@ -17,4 +23,5 @@ target_link_libraries(clangd
|
|||
clangSema
|
||||
clangTooling
|
||||
clangToolingCore
|
||||
${CLANGD_XPC_LIBS}
|
||||
)
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "Features.inc"
|
||||
#include "ClangdLSPServer.h"
|
||||
#include "Path.h"
|
||||
#include "Trace.h"
|
||||
|
@ -255,6 +256,11 @@ const char TestScheme::TestDir[] = "/clangd-test";
|
|||
} // namespace clangd
|
||||
} // namespace clang
|
||||
|
||||
enum class ErrorResultCode : int {
|
||||
NoShutdownRequest = 1,
|
||||
CantRunAsXPCService = 2
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
using namespace clang;
|
||||
using namespace clang::clangd;
|
||||
|
@ -408,14 +414,26 @@ int main(int argc, char *argv[]) {
|
|||
// Initialize and run ClangdLSPServer.
|
||||
// Change stdin to binary to not lose \r\n on windows.
|
||||
llvm::sys::ChangeStdinToBinary();
|
||||
auto Transport = newJSONTransport(
|
||||
stdin, llvm::outs(),
|
||||
InputMirrorStream ? InputMirrorStream.getPointer() : nullptr, PrettyPrint,
|
||||
InputStyle);
|
||||
|
||||
std::unique_ptr<Transport> TransportLayer;
|
||||
if (getenv("CLANGD_AS_XPC_SERVICE")) {
|
||||
#ifdef CLANGD_BUILD_XPC
|
||||
TransportLayer = newXPCTransport();
|
||||
#else
|
||||
errs() << "This clangd binary wasn't built with XPC support.\n";
|
||||
return ErrorResultCode::CantRunAsXPCService;
|
||||
#endif
|
||||
} else {
|
||||
TransportLayer = newJSONTransport(
|
||||
stdin, llvm::outs(),
|
||||
InputMirrorStream ? InputMirrorStream.getPointer() : nullptr,
|
||||
PrettyPrint, InputStyle);
|
||||
}
|
||||
|
||||
ClangdLSPServer LSPServer(
|
||||
*Transport, CCOpts, CompileCommandsDirPath,
|
||||
*TransportLayer, CCOpts, CompileCommandsDirPath,
|
||||
/*UseDirBasedCDB=*/CompileArgsFrom == FilesystemCompileArgs, Opts);
|
||||
constexpr int NoShutdownRequestErrorCode = 1;
|
||||
llvm::set_thread_name("clangd.main");
|
||||
return LSPServer.run() ? 0 : NoShutdownRequestErrorCode;
|
||||
return LSPServer.run() ? 0
|
||||
: static_cast<int>(ErrorResultCode::NoShutdownRequest);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
set(CLANGD_XPC_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
set(CLANGD_XPC_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules")
|
||||
include(CreateClangdXPCFramework)
|
||||
|
||||
add_subdirectory(framework)
|
||||
add_subdirectory(test-client)
|
||||
|
||||
include_directories(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../
|
||||
)
|
||||
|
||||
set(LLVM_LINK_COMPONENTS
|
||||
Support
|
||||
)
|
||||
|
||||
# Needed by LLVM's CMake checks because this file defines multiple targets.
|
||||
set(LLVM_OPTIONAL_SOURCES Conversion.cpp XPCTransport.cpp)
|
||||
|
||||
add_clang_library(clangdXpcJsonConversions
|
||||
Conversion.cpp
|
||||
)
|
||||
|
||||
add_clang_library(clangdXpcTransport
|
||||
XPCTransport.cpp
|
||||
DEPENDS clangdXpcJsonConversions
|
||||
LINK_LIBS clangdXpcJsonConversions
|
||||
)
|
|
@ -0,0 +1,40 @@
|
|||
//===--- Conversion.cpp - LSP data (de-)serialization through XPC - C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "xpc/Conversion.h"
|
||||
#include "Logger.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/Support/ScopedPrinter.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace llvm;
|
||||
namespace clang {
|
||||
namespace clangd {
|
||||
|
||||
xpc_object_t jsonToXpc(const json::Value &JSON) {
|
||||
const char *const Key = "LSP";
|
||||
xpc_object_t PayloadObj = xpc_string_create(llvm::to_string(JSON).c_str());
|
||||
return xpc_dictionary_create(&Key, &PayloadObj, 1);
|
||||
}
|
||||
|
||||
json::Value xpcToJson(const xpc_object_t &XPCObject) {
|
||||
if (xpc_get_type(XPCObject) == XPC_TYPE_DICTIONARY) {
|
||||
const char *const LSP = xpc_dictionary_get_string(XPCObject, "LSP");
|
||||
auto Json = json::parse(llvm::StringRef(LSP));
|
||||
if (Json)
|
||||
return *Json;
|
||||
else
|
||||
elog("JSON parse error: {0}", toString(Json.takeError()));
|
||||
}
|
||||
return json::Value(nullptr);
|
||||
}
|
||||
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
|
@ -0,0 +1,25 @@
|
|||
//===--- Conversion.h - LSP data (de-)serialization through XPC -*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_XPC_XPCJSONCONVERSIONS_H
|
||||
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_XPC_XPCJSONCONVERSIONS_H
|
||||
|
||||
#include "llvm/Support/JSON.h"
|
||||
#include <xpc/xpc.h>
|
||||
|
||||
namespace clang {
|
||||
namespace clangd {
|
||||
|
||||
xpc_object_t jsonToXpc(const llvm::json::Value &JSON);
|
||||
llvm::json::Value xpcToJson(const xpc_object_t &XPCObject);
|
||||
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
||||
|
||||
#endif
|
|
@ -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.
|
|
@ -0,0 +1,217 @@
|
|||
//===--- XPCTransport.cpp - sending and receiving LSP messages over XPC ---===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
#include "Conversion.h"
|
||||
#include "Logger.h"
|
||||
#include "Protocol.h" // For LSPError
|
||||
#include "Transport.h"
|
||||
#include "llvm/Support/Errno.h"
|
||||
|
||||
#include <xpc/xpc.h>
|
||||
|
||||
using namespace llvm;
|
||||
using namespace clang;
|
||||
using namespace clangd;
|
||||
|
||||
namespace {
|
||||
|
||||
json::Object encodeError(Error E) {
|
||||
std::string Message;
|
||||
ErrorCode Code = ErrorCode::UnknownErrorCode;
|
||||
if (Error Unhandled =
|
||||
handleErrors(std::move(E), [&](const LSPError &L) -> Error {
|
||||
Message = L.Message;
|
||||
Code = L.Code;
|
||||
return Error::success();
|
||||
}))
|
||||
Message = toString(std::move(Unhandled));
|
||||
|
||||
return json::Object{
|
||||
{"message", std::move(Message)},
|
||||
{"code", int64_t(Code)},
|
||||
};
|
||||
}
|
||||
|
||||
Error decodeError(const json::Object &O) {
|
||||
std::string Msg = O.getString("message").getValueOr("Unspecified error");
|
||||
if (auto Code = O.getInteger("code"))
|
||||
return make_error<LSPError>(std::move(Msg), ErrorCode(*Code));
|
||||
return make_error<StringError>(std::move(Msg), inconvertibleErrorCode());
|
||||
}
|
||||
|
||||
// C "closure" for XPCTransport::loop() method
|
||||
namespace xpcClosure {
|
||||
void connection_handler(xpc_connection_t clientConnection);
|
||||
}
|
||||
|
||||
class XPCTransport : public Transport {
|
||||
public:
|
||||
XPCTransport() {}
|
||||
|
||||
void notify(StringRef Method, json::Value Params) override {
|
||||
sendMessage(json::Object{
|
||||
{"jsonrpc", "2.0"},
|
||||
{"method", Method},
|
||||
{"params", std::move(Params)},
|
||||
});
|
||||
}
|
||||
void call(StringRef Method, json::Value Params, json::Value ID) override {
|
||||
sendMessage(json::Object{
|
||||
{"jsonrpc", "2.0"},
|
||||
{"id", std::move(ID)},
|
||||
{"method", Method},
|
||||
{"params", std::move(Params)},
|
||||
});
|
||||
}
|
||||
void reply(json::Value ID, Expected<json::Value> Result) override {
|
||||
if (Result) {
|
||||
sendMessage(json::Object{
|
||||
{"jsonrpc", "2.0"},
|
||||
{"id", std::move(ID)},
|
||||
{"result", std::move(*Result)},
|
||||
});
|
||||
} else {
|
||||
sendMessage(json::Object{
|
||||
{"jsonrpc", "2.0"},
|
||||
{"id", std::move(ID)},
|
||||
{"error", encodeError(Result.takeError())},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Error loop(MessageHandler &Handler) override;
|
||||
|
||||
private:
|
||||
// Needs access to handleMessage() and resetClientConnection()
|
||||
friend void xpcClosure::connection_handler(xpc_connection_t clientConnection);
|
||||
|
||||
// Dispatches incoming message to Handler onNotify/onCall/onReply.
|
||||
bool handleMessage(json::Value Message, MessageHandler &Handler);
|
||||
void sendMessage(json::Value Message) {
|
||||
xpc_object_t response = jsonToXpc(Message);
|
||||
xpc_connection_send_message(clientConnection, response);
|
||||
xpc_release(response);
|
||||
}
|
||||
void resetClientConnection(xpc_connection_t newClientConnection) {
|
||||
clientConnection = newClientConnection;
|
||||
}
|
||||
xpc_connection_t clientConnection;
|
||||
};
|
||||
|
||||
bool XPCTransport::handleMessage(json::Value Message, MessageHandler &Handler) {
|
||||
// Message must be an object with "jsonrpc":"2.0".
|
||||
auto *Object = Message.getAsObject();
|
||||
if (!Object || Object->getString("jsonrpc") != Optional<StringRef>("2.0")) {
|
||||
elog("Not a JSON-RPC 2.0 message: {0:2}", Message);
|
||||
return false;
|
||||
}
|
||||
// ID may be any JSON value. If absent, this is a notification.
|
||||
Optional<json::Value> ID;
|
||||
if (auto *I = Object->get("id"))
|
||||
ID = std::move(*I);
|
||||
auto Method = Object->getString("method");
|
||||
if (!Method) { // This is a response.
|
||||
if (!ID) {
|
||||
elog("No method and no response ID: {0:2}", Message);
|
||||
return false;
|
||||
}
|
||||
if (auto *Err = Object->getObject("error"))
|
||||
return Handler.onReply(std::move(*ID), decodeError(*Err));
|
||||
// Result should be given, use null if not.
|
||||
json::Value Result = nullptr;
|
||||
if (auto *R = Object->get("result"))
|
||||
Result = std::move(*R);
|
||||
return Handler.onReply(std::move(*ID), std::move(Result));
|
||||
}
|
||||
// Params should be given, use null if not.
|
||||
json::Value Params = nullptr;
|
||||
if (auto *P = Object->get("params"))
|
||||
Params = std::move(*P);
|
||||
|
||||
if (ID)
|
||||
return Handler.onCall(*Method, std::move(Params), std::move(*ID));
|
||||
else
|
||||
return Handler.onNotify(*Method, std::move(Params));
|
||||
}
|
||||
|
||||
namespace xpcClosure {
|
||||
// "owner" of this "closure object" - necessary for propagating connection to
|
||||
// XPCTransport so it can send messages to the client.
|
||||
XPCTransport *TransportObject = nullptr;
|
||||
Transport::MessageHandler *HandlerPtr = nullptr;
|
||||
|
||||
void connection_handler(xpc_connection_t clientConnection) {
|
||||
xpc_connection_set_target_queue(clientConnection, dispatch_get_main_queue());
|
||||
|
||||
xpc_transaction_begin();
|
||||
|
||||
TransportObject->resetClientConnection(clientConnection);
|
||||
|
||||
xpc_connection_set_event_handler(clientConnection, ^(xpc_object_t message) {
|
||||
if (message == XPC_ERROR_CONNECTION_INVALID) {
|
||||
// connection is being terminated
|
||||
log("Received XPC_ERROR_CONNECTION_INVALID message - returning from the "
|
||||
"event_handler.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (xpc_get_type(message) != XPC_TYPE_DICTIONARY) {
|
||||
log("Received XPC message of unknown type - returning from the "
|
||||
"event_handler.");
|
||||
return;
|
||||
}
|
||||
|
||||
const json::Value Doc = xpcToJson(message);
|
||||
if (Doc == json::Value(nullptr)) {
|
||||
log("XPC message was converted to Null JSON message - returning from the "
|
||||
"event_handler.");
|
||||
return;
|
||||
}
|
||||
|
||||
vlog("<<< {0}\n", Doc);
|
||||
|
||||
if (!TransportObject->handleMessage(std::move(Doc), *HandlerPtr)) {
|
||||
log("Received exit notification - cancelling connection.");
|
||||
xpc_connection_cancel(xpc_dictionary_get_remote_connection(message));
|
||||
xpc_transaction_end();
|
||||
}
|
||||
});
|
||||
|
||||
xpc_connection_resume(clientConnection);
|
||||
}
|
||||
} // namespace xpcClosure
|
||||
|
||||
Error XPCTransport::loop(MessageHandler &Handler) {
|
||||
assert(xpcClosure::TransportObject == nullptr &&
|
||||
"TransportObject has already been set.");
|
||||
// This looks scary since lifetime of this (or any) XPCTransport object has
|
||||
// to fully contain lifetime of any XPC connection. In practise any Transport
|
||||
// object is destroyed only at the end of main() which is always after
|
||||
// exit of xpc_main().
|
||||
xpcClosure::TransportObject = this;
|
||||
|
||||
assert(xpcClosure::HandlerPtr == nullptr &&
|
||||
"HandlerPtr has already been set.");
|
||||
xpcClosure::HandlerPtr = &Handler;
|
||||
|
||||
xpc_main(xpcClosure::connection_handler);
|
||||
// xpc_main doesn't ever return
|
||||
return errorCodeToError(std::make_error_code(std::errc::io_error));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace clang {
|
||||
namespace clangd {
|
||||
|
||||
std::unique_ptr<Transport> newXPCTransport() {
|
||||
return llvm::make_unique<XPCTransport>();
|
||||
}
|
||||
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
|
@ -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>
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${CLANGD_XPC_SERVICE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${CLANGD_XPC_SERVICE_BUNDLE_NAME}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${CLANGD_XPC_SERVICE_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string></string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>XPCService</key>
|
||||
<dict>
|
||||
<key>ServiceType</key>
|
||||
<string>Application</string>
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>CLANGD_AS_XPC_SERVICE</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
|
@ -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)
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
set(SOURCES
|
||||
ClangdXPC.cpp)
|
||||
add_clang_library(ClangdXPCLib SHARED
|
||||
${SOURCES}
|
||||
DEPENDS
|
||||
clangd
|
||||
)
|
||||
create_clangd_xpc_framework(ClangdXPCLib "ClangdXPC")
|
|
@ -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";
|
||||
}
|
|
@ -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
|
||||
)
|
|
@ -0,0 +1,96 @@
|
|||
#include "xpc/Conversion.h"
|
||||
#include "clang/Basic/LLVM.h"
|
||||
#include "llvm/ADT/SmallString.h"
|
||||
#include "llvm/Support/LineIterator.h"
|
||||
#include "llvm/Support/MemoryBuffer.h"
|
||||
#include "llvm/Support/Path.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
#include <dlfcn.h>
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
#include <xpc/xpc.h>
|
||||
|
||||
typedef const char *(*clangd_xpc_get_bundle_identifier_t)(void);
|
||||
|
||||
using namespace llvm;
|
||||
using namespace clang;
|
||||
|
||||
std::string getLibraryPath() {
|
||||
Dl_info info;
|
||||
if (dladdr((void *)(uintptr_t)getLibraryPath, &info) == 0)
|
||||
llvm_unreachable("Call to dladdr() failed");
|
||||
llvm::SmallString<128> LibClangPath;
|
||||
LibClangPath = llvm::sys::path::parent_path(
|
||||
llvm::sys::path::parent_path(info.dli_fname));
|
||||
llvm::sys::path::append(LibClangPath, "lib", "ClangdXPC.framework",
|
||||
"ClangdXPC");
|
||||
return LibClangPath.str();
|
||||
}
|
||||
|
||||
static void dumpXPCObject(xpc_object_t Object, llvm::raw_ostream &OS) {
|
||||
xpc_type_t Type = xpc_get_type(Object);
|
||||
if (Type == XPC_TYPE_DICTIONARY) {
|
||||
json::Value Json = clang::clangd::xpcToJson(Object);
|
||||
OS << Json;
|
||||
} else {
|
||||
OS << "<UNKNOWN>";
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
// Open the ClangdXPC dylib in the framework.
|
||||
std::string LibPath = getLibraryPath();
|
||||
void *dlHandle = dlopen(LibPath.c_str(), RTLD_LOCAL | RTLD_FIRST);
|
||||
if (!dlHandle)
|
||||
return 1;
|
||||
|
||||
// Lookup the XPC service bundle name, and launch it.
|
||||
clangd_xpc_get_bundle_identifier_t clangd_xpc_get_bundle_identifier =
|
||||
(clangd_xpc_get_bundle_identifier_t)dlsym(
|
||||
dlHandle, "clangd_xpc_get_bundle_identifier");
|
||||
xpc_connection_t conn = xpc_connection_create(
|
||||
clangd_xpc_get_bundle_identifier(), dispatch_get_main_queue());
|
||||
|
||||
// Dump the XPC events.
|
||||
xpc_connection_set_event_handler(conn, ^(xpc_object_t event) {
|
||||
if (event == XPC_ERROR_CONNECTION_INVALID) {
|
||||
llvm::errs() << "Received XPC_ERROR_CONNECTION_INVALID.";
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
if (event == XPC_ERROR_CONNECTION_INTERRUPTED) {
|
||||
llvm::errs() << "Received XPC_ERROR_CONNECTION_INTERRUPTED.";
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
dumpXPCObject(event, llvm::outs());
|
||||
llvm::outs() << "\n";
|
||||
});
|
||||
|
||||
xpc_connection_resume(conn);
|
||||
|
||||
// Read the input to determine the things to send to clangd.
|
||||
llvm::ErrorOr<std::unique_ptr<MemoryBuffer>> Stdin =
|
||||
llvm::MemoryBuffer::getSTDIN();
|
||||
if (!Stdin) {
|
||||
llvm::errs() << "Failed to get STDIN!\n";
|
||||
return 1;
|
||||
}
|
||||
for (llvm::line_iterator It(**Stdin, /*SkipBlanks=*/true,
|
||||
/*CommentMarker=*/'#');
|
||||
!It.is_at_eof(); ++It) {
|
||||
StringRef Line = *It;
|
||||
if (auto Request = json::parse(Line)) {
|
||||
xpc_object_t Object = clangd::jsonToXpc(*Request);
|
||||
xpc_connection_send_message(conn, Object);
|
||||
} else {
|
||||
llvm::errs() << llvm::Twine("JSON parse error: ")
|
||||
<< llvm::toString(Request.takeError());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
dispatch_main();
|
||||
|
||||
// dispatch_main() doesn't return
|
||||
return EXIT_FAILURE;
|
||||
}
|
|
@ -16,7 +16,8 @@ endif ()
|
|||
string(REPLACE ${CMAKE_CFG_INTDIR} ${LLVM_BUILD_MODE} CLANG_TOOLS_DIR ${LLVM_RUNTIME_OUTPUT_INTDIR})
|
||||
|
||||
llvm_canonicalize_cmake_booleans(
|
||||
CLANG_ENABLE_STATIC_ANALYZER)
|
||||
CLANG_ENABLE_STATIC_ANALYZER
|
||||
CLANGD_BUILD_XPC_SUPPORT)
|
||||
|
||||
configure_lit_site_cfg(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.in
|
||||
|
@ -61,7 +62,11 @@ set(CLANG_TOOLS_TEST_DEPS
|
|||
clang-headers
|
||||
|
||||
clang-tidy
|
||||
)
|
||||
)
|
||||
|
||||
if(CLANGD_BUILD_XPC_SUPPORT)
|
||||
list(APPEND CLANG_TOOLS_TEST_DEPS clangd-xpc-test-client)
|
||||
endif()
|
||||
|
||||
set(CLANGD_TEST_DEPS
|
||||
clangd
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# RUN: clangd-xpc-test-client < %s | FileCheck %s
|
||||
# REQUIRES: clangd-xpc-support
|
||||
|
||||
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootUri":"test:///workspace","capabilities":{},"trace":"off"}}
|
||||
# CHECK: {"id":0,"jsonrpc":"2.0","result":{"capabilities"
|
||||
|
||||
{"jsonrpc":"2.0","id":3,"method":"shutdown"}
|
||||
# CHECK: {"id":3,"jsonrpc":"2.0","result":null}
|
||||
|
||||
{"jsonrpc":"2.0","method":"exit"}
|
|
@ -117,6 +117,10 @@ if not platform.system() in ['Windows'] or not execute_external:
|
|||
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')
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ config.clang_libs_dir = "@SHLIBDIR@"
|
|||
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.
|
||||
|
|
|
@ -65,3 +65,7 @@ target_link_libraries(ClangdTests
|
|||
LLVMSupport
|
||||
LLVMTestingSupport
|
||||
)
|
||||
|
||||
if (CLANGD_BUILD_XPC)
|
||||
add_subdirectory(xpc)
|
||||
endif ()
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
set(LLVM_LINK_COMPONENTS
|
||||
support
|
||||
)
|
||||
|
||||
get_filename_component(CLANGD_SOURCE_DIR
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../clangd REALPATH)
|
||||
include_directories(
|
||||
${CLANGD_SOURCE_DIR}
|
||||
)
|
||||
|
||||
add_extra_unittest(ClangdXpcTests
|
||||
ConversionTests.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(ClangdXpcTests
|
||||
PRIVATE
|
||||
clangdXpcJsonConversions
|
||||
clangDaemon
|
||||
LLVMSupport
|
||||
LLVMTestingSupport
|
||||
)
|
|
@ -0,0 +1,36 @@
|
|||
//===-- ConversionTests.cpp --------------------------*- C++ -*-----------===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "xpc/Conversion.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace clang {
|
||||
namespace clangd {
|
||||
namespace {
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
TEST(JsonXpcConversionTest, JsonToXpcToJson) {
|
||||
|
||||
for (auto &testcase :
|
||||
{json::Value(false), json::Value(3.14), json::Value(42),
|
||||
json::Value(-100), json::Value("foo"), json::Value(""),
|
||||
json::Value("123"), json::Value(" "),
|
||||
json::Value{true, "foo", nullptr, 42},
|
||||
json::Value(json::Object{
|
||||
{"a", true}, {"b", "foo"}, {"c", nullptr}, {"d", 42}})}) {
|
||||
EXPECT_TRUE(testcase == xpcToJson(jsonToXpc(testcase))) << testcase;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
Loading…
Reference in New Issue