mirror of
https://github.com/MaskRay/ccls.git
synced 2024-11-21 23:25:07 +00:00
Use llvm::cl and simplify main.cc import_pipeline.cc
This commit is contained in:
parent
4207c3ece1
commit
c81ca26a2e
@ -34,6 +34,9 @@ if(NOT CYGWIN)
|
||||
set_property(TARGET ccls PROPERTY CXX_EXTENSIONS OFF)
|
||||
endif()
|
||||
|
||||
# To link against LLVM libraries (usually compiled with -fno-rtti)
|
||||
target_compile_options(ccls PRIVATE -fno-rtti)
|
||||
|
||||
# CMake sets MSVC for both MSVC and Clang(Windows)
|
||||
if(MSVC)
|
||||
# Common MSVC/Clang(Windows) options
|
||||
@ -205,12 +208,12 @@ target_sources(ccls PRIVATE
|
||||
src/clang_indexer.cc
|
||||
src/clang_translation_unit.cc
|
||||
src/clang_utils.cc
|
||||
src/command_line.cc
|
||||
src/config.cc
|
||||
src/diagnostics_engine.cc
|
||||
src/file_consumer.cc
|
||||
src/filesystem.cc
|
||||
src/fuzzy_match.cc
|
||||
src/main.cc
|
||||
src/import_pipeline.cc
|
||||
src/include_complete.cc
|
||||
src/method.cc
|
||||
|
@ -1,396 +0,0 @@
|
||||
// TODO: cleanup includes
|
||||
#include "cache_manager.h"
|
||||
#include "clang_complete.h"
|
||||
#include "diagnostics_engine.h"
|
||||
#include "file_consumer.h"
|
||||
#include "import_pipeline.h"
|
||||
#include "include_complete.h"
|
||||
#include "indexer.h"
|
||||
#include "lex_utils.h"
|
||||
#include "lru_cache.h"
|
||||
#include "lsp_diagnostic.h"
|
||||
#include "match.h"
|
||||
#include "message_handler.h"
|
||||
#include "platform.h"
|
||||
#include "project.h"
|
||||
#include "query.h"
|
||||
#include "query_utils.h"
|
||||
#include "queue_manager.h"
|
||||
#include "serializer.h"
|
||||
#include "serializers/json.h"
|
||||
#include "test.h"
|
||||
#include "timer.h"
|
||||
#include "working_files.h"
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
#include <rapidjson/error/en.h>
|
||||
#include <loguru.hpp>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
std::string g_init_options;
|
||||
|
||||
namespace {
|
||||
|
||||
std::unordered_map<std::string, std::string> ParseOptions(int argc,
|
||||
char** argv) {
|
||||
std::unordered_map<std::string, std::string> output;
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string arg = argv[i];
|
||||
if (arg[0] == '-') {
|
||||
auto equal = arg.find('=');
|
||||
if (equal != std::string::npos) {
|
||||
output[arg.substr(0, equal)] = arg.substr(equal + 1);
|
||||
} else if (i + 1 < argc && argv[i + 1][0] != '-')
|
||||
output[arg] = argv[++i];
|
||||
else
|
||||
output[arg] = "";
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
bool HasOption(const std::unordered_map<std::string, std::string>& options,
|
||||
const std::string& option) {
|
||||
return options.find(option) != options.end();
|
||||
}
|
||||
|
||||
// This function returns true if e2e timing should be displayed for the given
|
||||
// MethodId.
|
||||
bool ShouldDisplayMethodTiming(MethodType type) {
|
||||
return
|
||||
type != kMethodType_TextDocumentPublishDiagnostics &&
|
||||
type != kMethodType_CclsPublishInactiveRegions &&
|
||||
type != kMethodType_Unknown;
|
||||
}
|
||||
|
||||
void PrintHelp() {
|
||||
printf("%s", R"help(ccls is a C/C++/Objective-C language server.
|
||||
|
||||
Mode:
|
||||
--test-unit Run unit tests.
|
||||
--test-index <opt_filter_path>
|
||||
Run index tests. opt_filter_path can be used to specify which
|
||||
test to run (ie, "foo" will run all tests which contain "foo"
|
||||
in the path). If not provided all tests are run.
|
||||
(default if no other mode is specified)
|
||||
Run as a language server over stdin and stdout
|
||||
|
||||
Other command line options:
|
||||
--init <initializationOptions>
|
||||
Override client provided initialization options
|
||||
https://github.com/MaskRay/ccls/wiki/Initialization-options
|
||||
--log-file <path> Logging file for diagnostics
|
||||
--log-file-append <path> Like --log-file, but appending
|
||||
--log-all-to-stderr Write all log messages to STDERR.
|
||||
--help Print this help information.
|
||||
--ci Prevents tests from prompting the user for input. Used for
|
||||
continuous integration so it can fail faster instead of timing
|
||||
out.
|
||||
|
||||
See more on https://github.com/MaskRay/ccls/wiki
|
||||
)help");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// QUERYDB MAIN ////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool QueryDbMainLoop(QueryDatabase* db,
|
||||
MultiQueueWaiter* waiter,
|
||||
Project* project,
|
||||
VFS* vfs,
|
||||
ImportPipelineStatus* status,
|
||||
SemanticHighlightSymbolCache* semantic_cache,
|
||||
WorkingFiles* working_files,
|
||||
ClangCompleteManager* clang_complete,
|
||||
IncludeComplete* include_complete,
|
||||
CodeCompleteCache* global_code_complete_cache,
|
||||
CodeCompleteCache* non_global_code_complete_cache,
|
||||
CodeCompleteCache* signature_cache) {
|
||||
auto* queue = QueueManager::instance();
|
||||
std::vector<std::unique_ptr<InMessage>> messages =
|
||||
queue->for_querydb.DequeueAll();
|
||||
bool did_work = messages.size();
|
||||
for (auto& message : messages) {
|
||||
// TODO: Consider using std::unordered_map to lookup the handler
|
||||
for (MessageHandler* handler : *MessageHandler::message_handlers) {
|
||||
if (handler->GetMethodType() == message->GetMethodType()) {
|
||||
handler->Run(std::move(message));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (message) {
|
||||
LOG_S(ERROR) << "No handler for " << message->GetMethodType();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: consider rate-limiting and checking for IPC messages so we don't
|
||||
// block requests / we can serve partial requests.
|
||||
|
||||
if (QueryDb_ImportMain(db, status, semantic_cache, working_files)) {
|
||||
did_work = true;
|
||||
}
|
||||
|
||||
return did_work;
|
||||
}
|
||||
|
||||
void RunQueryDbThread(const std::string& bin_name,
|
||||
MultiQueueWaiter* querydb_waiter,
|
||||
MultiQueueWaiter* indexer_waiter) {
|
||||
Project project;
|
||||
SemanticHighlightSymbolCache semantic_cache;
|
||||
WorkingFiles working_files;
|
||||
VFS vfs;
|
||||
DiagnosticsEngine diag_engine;
|
||||
|
||||
ClangCompleteManager clang_complete(
|
||||
&project, &working_files,
|
||||
[&](std::string path, std::vector<lsDiagnostic> diagnostics) {
|
||||
diag_engine.Publish(&working_files, path, diagnostics);
|
||||
},
|
||||
[](lsRequestId id) {
|
||||
if (id.Valid()) {
|
||||
Out_Error out;
|
||||
out.id = id;
|
||||
out.error.code = lsErrorCodes::InternalError;
|
||||
out.error.message =
|
||||
"Dropping completion request; a newer request "
|
||||
"has come in that will be serviced instead.";
|
||||
QueueManager::WriteStdout(kMethodType_Unknown, out);
|
||||
}
|
||||
});
|
||||
|
||||
IncludeComplete include_complete(&project);
|
||||
auto global_code_complete_cache = std::make_unique<CodeCompleteCache>();
|
||||
auto non_global_code_complete_cache = std::make_unique<CodeCompleteCache>();
|
||||
auto signature_cache = std::make_unique<CodeCompleteCache>();
|
||||
ImportPipelineStatus import_pipeline_status;
|
||||
QueryDatabase db;
|
||||
|
||||
// Setup shared references.
|
||||
for (MessageHandler* handler : *MessageHandler::message_handlers) {
|
||||
handler->db = &db;
|
||||
handler->waiter = indexer_waiter;
|
||||
handler->project = &project;
|
||||
handler->diag_engine = &diag_engine;
|
||||
handler->vfs = &vfs;
|
||||
handler->import_pipeline_status = &import_pipeline_status;
|
||||
handler->semantic_cache = &semantic_cache;
|
||||
handler->working_files = &working_files;
|
||||
handler->clang_complete = &clang_complete;
|
||||
handler->include_complete = &include_complete;
|
||||
handler->global_code_complete_cache = global_code_complete_cache.get();
|
||||
handler->non_global_code_complete_cache =
|
||||
non_global_code_complete_cache.get();
|
||||
handler->signature_cache = signature_cache.get();
|
||||
}
|
||||
|
||||
// Run query db main loop.
|
||||
SetThreadName("querydb");
|
||||
while (true) {
|
||||
bool did_work = QueryDbMainLoop(
|
||||
&db, querydb_waiter, &project, &vfs,
|
||||
&import_pipeline_status,
|
||||
&semantic_cache, &working_files, &clang_complete, &include_complete,
|
||||
global_code_complete_cache.get(), non_global_code_complete_cache.get(),
|
||||
signature_cache.get());
|
||||
|
||||
// Cleanup and free any unused memory.
|
||||
FreeUnusedMemory();
|
||||
|
||||
if (!did_work) {
|
||||
auto* queue = QueueManager::instance();
|
||||
querydb_waiter->Wait(&queue->on_indexed, &queue->for_querydb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// STDIN MAIN //////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Separate thread whose only job is to read from stdin and
|
||||
// dispatch read commands to the actual indexer program. This
|
||||
// cannot be done on the main thread because reading from std::cin
|
||||
// blocks.
|
||||
//
|
||||
// |ipc| is connected to a server.
|
||||
void LaunchStdinLoop(std::unordered_map<MethodType, Timer>* request_times) {
|
||||
std::thread([request_times]() {
|
||||
SetThreadName("stdin");
|
||||
auto* queue = QueueManager::instance();
|
||||
while (true) {
|
||||
std::unique_ptr<InMessage> message;
|
||||
std::optional<std::string> err =
|
||||
MessageRegistry::instance()->ReadMessageFromStdin(&message);
|
||||
|
||||
// Message parsing can fail if we don't recognize the method.
|
||||
if (err) {
|
||||
// The message may be partially deserialized.
|
||||
// Emit an error ResponseMessage if |id| is available.
|
||||
if (message) {
|
||||
lsRequestId id = message->GetRequestId();
|
||||
if (id.Valid()) {
|
||||
Out_Error out;
|
||||
out.id = id;
|
||||
out.error.code = lsErrorCodes::InvalidParams;
|
||||
out.error.message = std::move(*err);
|
||||
queue->WriteStdout(kMethodType_Unknown, out);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Cache |method_id| so we can access it after moving |message|.
|
||||
MethodType method_type = message->GetMethodType();
|
||||
(*request_times)[method_type] = Timer();
|
||||
|
||||
queue->for_querydb.PushBack(std::move(message));
|
||||
|
||||
// If the message was to exit then querydb will take care of the actual
|
||||
// exit. Stop reading from stdin since it might be detached.
|
||||
if (method_type == kMethodType_Exit)
|
||||
break;
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
|
||||
void LaunchStdoutThread(std::unordered_map<MethodType, Timer>* request_times,
|
||||
MultiQueueWaiter* waiter) {
|
||||
std::thread([=]() {
|
||||
SetThreadName("stdout");
|
||||
auto* queue = QueueManager::instance();
|
||||
|
||||
while (true) {
|
||||
std::vector<Stdout_Request> messages = queue->for_stdout.DequeueAll();
|
||||
if (messages.empty()) {
|
||||
waiter->Wait(&queue->for_stdout);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto& message : messages) {
|
||||
if (ShouldDisplayMethodTiming(message.method)) {
|
||||
Timer time = (*request_times)[message.method];
|
||||
time.ResetAndPrint("[e2e] Running " + std::string(message.method));
|
||||
}
|
||||
|
||||
fwrite(message.content.c_str(), message.content.size(), 1, stdout);
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
|
||||
void LanguageServerMain(const std::string& bin_name,
|
||||
MultiQueueWaiter* querydb_waiter,
|
||||
MultiQueueWaiter* indexer_waiter,
|
||||
MultiQueueWaiter* stdout_waiter) {
|
||||
std::unordered_map<MethodType, Timer> request_times;
|
||||
|
||||
LaunchStdinLoop(&request_times);
|
||||
|
||||
// We run a dedicated thread for writing to stdout because there can be an
|
||||
// unknown number of delays when output information.
|
||||
LaunchStdoutThread(&request_times, stdout_waiter);
|
||||
|
||||
// Start querydb which takes over this thread. The querydb will launch
|
||||
// indexer threads as needed.
|
||||
RunQueryDbThread(bin_name, querydb_waiter, indexer_waiter);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
TraceMe();
|
||||
|
||||
std::unordered_map<std::string, std::string> options =
|
||||
ParseOptions(argc, argv);
|
||||
|
||||
if (HasOption(options, "-h") || HasOption(options, "--help")) {
|
||||
PrintHelp();
|
||||
// Also emit doctest help if --test-unit is passed.
|
||||
if (!HasOption(options, "--test-unit"))
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!HasOption(options, "--log-all-to-stderr"))
|
||||
loguru::g_stderr_verbosity = loguru::Verbosity_WARNING;
|
||||
|
||||
loguru::g_preamble_date = false;
|
||||
loguru::g_preamble_time = false;
|
||||
loguru::g_preamble_verbose = false;
|
||||
loguru::g_flush_interval_ms = 0;
|
||||
loguru::init(argc, argv);
|
||||
|
||||
MultiQueueWaiter querydb_waiter, indexer_waiter, stdout_waiter;
|
||||
QueueManager::Init(&querydb_waiter, &indexer_waiter, &stdout_waiter);
|
||||
|
||||
PlatformInit();
|
||||
IndexInit();
|
||||
|
||||
bool language_server = true;
|
||||
|
||||
if (HasOption(options, "--log-file")) {
|
||||
loguru::add_file(options["--log-file"].c_str(), loguru::Truncate,
|
||||
loguru::Verbosity_MAX);
|
||||
}
|
||||
if (HasOption(options, "--log-file-append")) {
|
||||
loguru::add_file(options["--log-file-append"].c_str(), loguru::Append,
|
||||
loguru::Verbosity_MAX);
|
||||
}
|
||||
|
||||
if (HasOption(options, "--test-unit")) {
|
||||
language_server = false;
|
||||
doctest::Context context;
|
||||
context.applyCommandLine(argc, argv);
|
||||
int res = context.run();
|
||||
if (res != 0 || context.shouldExit())
|
||||
return res;
|
||||
}
|
||||
|
||||
if (HasOption(options, "--test-index")) {
|
||||
language_server = false;
|
||||
if (!RunIndexTests(options["--test-index"], !HasOption(options, "--ci")))
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (language_server) {
|
||||
if (HasOption(options, "--init")) {
|
||||
// We check syntax error here but override client-side
|
||||
// initializationOptions in messages/initialize.cc
|
||||
g_init_options = options["--init"];
|
||||
rapidjson::Document reader;
|
||||
rapidjson::ParseResult ok = reader.Parse(g_init_options.c_str());
|
||||
if (!ok) {
|
||||
fprintf(stderr, "Failed to parse --init as JSON: %s (%zd)\n",
|
||||
rapidjson::GetParseError_En(ok.Code()), ok.Offset());
|
||||
return 1;
|
||||
}
|
||||
JsonReader json_reader{&reader};
|
||||
try {
|
||||
Config config;
|
||||
Reflect(json_reader, config);
|
||||
} catch (std::invalid_argument& e) {
|
||||
fprintf(stderr, "Failed to parse --init %s, expected %s\n",
|
||||
static_cast<JsonReader&>(json_reader).GetPath().c_str(),
|
||||
e.what());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
LanguageServerMain(argv[0], &querydb_waiter, &indexer_waiter,
|
||||
&stdout_waiter);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "position.h"
|
||||
#include "serializer.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <clang-c/Index.h>
|
||||
@ -77,3 +78,30 @@ struct FileConsumer {
|
||||
std::string parse_file_;
|
||||
int thread_id_;
|
||||
};
|
||||
|
||||
// Contains timing information for the entire pipeline for importing a file
|
||||
// into the querydb.
|
||||
struct PerformanceImportFile {
|
||||
// All units are in microseconds.
|
||||
|
||||
// [indexer] clang parsing the file
|
||||
uint64_t index_parse = 0;
|
||||
// [indexer] build the IndexFile object from clang parse
|
||||
uint64_t index_build = 0;
|
||||
// [indexer] save the IndexFile to disk
|
||||
uint64_t index_save_to_disk = 0;
|
||||
// [indexer] loading previously cached index
|
||||
uint64_t index_load_cached = 0;
|
||||
// [indexer] create delta IndexUpdate object
|
||||
uint64_t index_make_delta = 0;
|
||||
// [querydb] update WorkingFile indexed file state
|
||||
// uint64_t querydb_update_working_file = 0;
|
||||
// [querydb] apply IndexUpdate
|
||||
// uint64_t querydb_apply_index_update = 0;
|
||||
};
|
||||
MAKE_REFLECT_STRUCT(PerformanceImportFile,
|
||||
index_parse,
|
||||
index_build,
|
||||
index_save_to_disk,
|
||||
index_load_cached,
|
||||
index_make_delta);
|
||||
|
@ -1,8 +1,10 @@
|
||||
#include "import_pipeline.h"
|
||||
|
||||
#include "cache_manager.h"
|
||||
#include "clang_complete.h"
|
||||
#include "config.h"
|
||||
#include "diagnostics_engine.h"
|
||||
#include "include_complete.h"
|
||||
#include "lsp.h"
|
||||
#include "message_handler.h"
|
||||
#include "platform.h"
|
||||
@ -15,6 +17,7 @@
|
||||
#include <loguru.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
namespace {
|
||||
|
||||
@ -201,11 +204,19 @@ bool Indexer_Parse(DiagnosticsEngine* diag_engine,
|
||||
return true;
|
||||
}
|
||||
|
||||
// This function returns true if e2e timing should be displayed for the given
|
||||
// MethodId.
|
||||
bool ShouldDisplayMethodTiming(MethodType type) {
|
||||
return
|
||||
type != kMethodType_TextDocumentPublishDiagnostics &&
|
||||
type != kMethodType_CclsPublishInactiveRegions &&
|
||||
type != kMethodType_Unknown;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Indexer_Main(DiagnosticsEngine* diag_engine,
|
||||
VFS* vfs,
|
||||
ImportPipelineStatus* status,
|
||||
Project* project,
|
||||
WorkingFiles* working_files,
|
||||
MultiQueueWaiter* waiter) {
|
||||
@ -218,10 +229,8 @@ void Indexer_Main(DiagnosticsEngine* diag_engine,
|
||||
waiter->Wait(&queue->index_request);
|
||||
}
|
||||
|
||||
namespace {
|
||||
void QueryDb_OnIndexed(QueueManager* queue,
|
||||
QueryDatabase* db,
|
||||
ImportPipelineStatus* status,
|
||||
SemanticHighlightSymbolCache* semantic_cache,
|
||||
WorkingFiles* working_files,
|
||||
Index_OnIndexed* response) {
|
||||
@ -264,23 +273,154 @@ void QueryDb_OnIndexed(QueueManager* queue,
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
void LaunchStdinLoop(std::unordered_map<MethodType, Timer>* request_times) {
|
||||
std::thread([request_times]() {
|
||||
SetThreadName("stdin");
|
||||
auto* queue = QueueManager::instance();
|
||||
while (true) {
|
||||
std::unique_ptr<InMessage> message;
|
||||
std::optional<std::string> err =
|
||||
MessageRegistry::instance()->ReadMessageFromStdin(&message);
|
||||
|
||||
bool QueryDb_ImportMain(QueryDatabase* db,
|
||||
ImportPipelineStatus* status,
|
||||
SemanticHighlightSymbolCache* semantic_cache,
|
||||
WorkingFiles* working_files) {
|
||||
auto* queue = QueueManager::instance();
|
||||
bool did_work = false;
|
||||
// Message parsing can fail if we don't recognize the method.
|
||||
if (err) {
|
||||
// The message may be partially deserialized.
|
||||
// Emit an error ResponseMessage if |id| is available.
|
||||
if (message) {
|
||||
lsRequestId id = message->GetRequestId();
|
||||
if (id.Valid()) {
|
||||
Out_Error out;
|
||||
out.id = id;
|
||||
out.error.code = lsErrorCodes::InvalidParams;
|
||||
out.error.message = std::move(*err);
|
||||
queue->WriteStdout(kMethodType_Unknown, out);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int i = 80; i--; ) {
|
||||
std::optional<Index_OnIndexed> response = queue->on_indexed.TryPopFront();
|
||||
if (!response)
|
||||
break;
|
||||
did_work = true;
|
||||
QueryDb_OnIndexed(queue, db, status, semantic_cache, working_files,
|
||||
&*response);
|
||||
// Cache |method_id| so we can access it after moving |message|.
|
||||
MethodType method_type = message->GetMethodType();
|
||||
(*request_times)[method_type] = Timer();
|
||||
|
||||
queue->for_querydb.PushBack(std::move(message));
|
||||
|
||||
// If the message was to exit then querydb will take care of the actual
|
||||
// exit. Stop reading from stdin since it might be detached.
|
||||
if (method_type == kMethodType_Exit)
|
||||
break;
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
|
||||
void LaunchStdoutThread(std::unordered_map<MethodType, Timer>* request_times,
|
||||
MultiQueueWaiter* waiter) {
|
||||
std::thread([=]() {
|
||||
SetThreadName("stdout");
|
||||
auto* queue = QueueManager::instance();
|
||||
|
||||
while (true) {
|
||||
std::vector<Stdout_Request> messages = queue->for_stdout.DequeueAll();
|
||||
if (messages.empty()) {
|
||||
waiter->Wait(&queue->for_stdout);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto& message : messages) {
|
||||
if (ShouldDisplayMethodTiming(message.method)) {
|
||||
Timer time = (*request_times)[message.method];
|
||||
time.ResetAndPrint("[e2e] Running " + std::string(message.method));
|
||||
}
|
||||
|
||||
fwrite(message.content.c_str(), message.content.size(), 1, stdout);
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
|
||||
void MainLoop(MultiQueueWaiter* querydb_waiter,
|
||||
MultiQueueWaiter* indexer_waiter) {
|
||||
Project project;
|
||||
SemanticHighlightSymbolCache semantic_cache;
|
||||
WorkingFiles working_files;
|
||||
VFS vfs;
|
||||
DiagnosticsEngine diag_engine;
|
||||
|
||||
ClangCompleteManager clang_complete(
|
||||
&project, &working_files,
|
||||
[&](std::string path, std::vector<lsDiagnostic> diagnostics) {
|
||||
diag_engine.Publish(&working_files, path, diagnostics);
|
||||
},
|
||||
[](lsRequestId id) {
|
||||
if (id.Valid()) {
|
||||
Out_Error out;
|
||||
out.id = id;
|
||||
out.error.code = lsErrorCodes::InternalError;
|
||||
out.error.message =
|
||||
"Dropping completion request; a newer request "
|
||||
"has come in that will be serviced instead.";
|
||||
QueueManager::WriteStdout(kMethodType_Unknown, out);
|
||||
}
|
||||
});
|
||||
|
||||
IncludeComplete include_complete(&project);
|
||||
auto global_code_complete_cache = std::make_unique<CodeCompleteCache>();
|
||||
auto non_global_code_complete_cache = std::make_unique<CodeCompleteCache>();
|
||||
auto signature_cache = std::make_unique<CodeCompleteCache>();
|
||||
QueryDatabase db;
|
||||
|
||||
// Setup shared references.
|
||||
for (MessageHandler* handler : *MessageHandler::message_handlers) {
|
||||
handler->db = &db;
|
||||
handler->waiter = indexer_waiter;
|
||||
handler->project = &project;
|
||||
handler->diag_engine = &diag_engine;
|
||||
handler->vfs = &vfs;
|
||||
handler->semantic_cache = &semantic_cache;
|
||||
handler->working_files = &working_files;
|
||||
handler->clang_complete = &clang_complete;
|
||||
handler->include_complete = &include_complete;
|
||||
handler->global_code_complete_cache = global_code_complete_cache.get();
|
||||
handler->non_global_code_complete_cache =
|
||||
non_global_code_complete_cache.get();
|
||||
handler->signature_cache = signature_cache.get();
|
||||
}
|
||||
|
||||
return did_work;
|
||||
// Run query db main loop.
|
||||
SetThreadName("querydb");
|
||||
auto* queue = QueueManager::instance();
|
||||
while (true) {
|
||||
std::vector<std::unique_ptr<InMessage>> messages =
|
||||
queue->for_querydb.DequeueAll();
|
||||
bool did_work = messages.size();
|
||||
for (auto& message : messages) {
|
||||
// TODO: Consider using std::unordered_map to lookup the handler
|
||||
for (MessageHandler* handler : *MessageHandler::message_handlers) {
|
||||
if (handler->GetMethodType() == message->GetMethodType()) {
|
||||
handler->Run(std::move(message));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (message)
|
||||
LOG_S(ERROR) << "No handler for " << message->GetMethodType();
|
||||
}
|
||||
|
||||
for (int i = 80; i--;) {
|
||||
std::optional<Index_OnIndexed> response = queue->on_indexed.TryPopFront();
|
||||
if (!response)
|
||||
break;
|
||||
did_work = true;
|
||||
QueryDb_OnIndexed(queue, &db, &semantic_cache, &working_files, &*response);
|
||||
}
|
||||
|
||||
// Cleanup and free any unused memory.
|
||||
FreeUnusedMemory();
|
||||
|
||||
if (!did_work) {
|
||||
auto* queue = QueueManager::instance();
|
||||
querydb_waiter->Wait(&queue->on_indexed, &queue->for_querydb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "queue_manager.h"
|
||||
#include "timer.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <unordered_map>
|
||||
|
||||
struct ClangTranslationUnit;
|
||||
class DiagnosticsEngine;
|
||||
struct VFS;
|
||||
struct ICacheManager;
|
||||
struct MultiQueueWaiter;
|
||||
struct Project;
|
||||
struct QueryDatabase;
|
||||
struct SemanticHighlightSymbolCache;
|
||||
struct WorkingFiles;
|
||||
|
||||
struct ImportPipelineStatus {
|
||||
std::atomic<int> num_active_threads = {0};
|
||||
std::atomic<long long> next_progress_output = {0};
|
||||
};
|
||||
|
||||
void Indexer_Main(DiagnosticsEngine* diag_engine,
|
||||
VFS* vfs,
|
||||
ImportPipelineStatus* status,
|
||||
Project* project,
|
||||
WorkingFiles* working_files,
|
||||
MultiQueueWaiter* waiter);
|
||||
|
||||
bool QueryDb_ImportMain(QueryDatabase* db,
|
||||
ImportPipelineStatus* status,
|
||||
SemanticHighlightSymbolCache* semantic_cache,
|
||||
WorkingFiles* working_files);
|
||||
void LaunchStdinLoop(std::unordered_map<MethodType, Timer>* request_times);
|
||||
void LaunchStdoutThread(std::unordered_map<MethodType, Timer>* request_times,
|
||||
MultiQueueWaiter* waiter);
|
||||
void MainLoop(MultiQueueWaiter* querydb_waiter,
|
||||
MultiQueueWaiter* indexer_waiter);
|
||||
|
@ -8,7 +8,6 @@
|
||||
#include "lsp.h"
|
||||
#include "maybe.h"
|
||||
#include "nt_string.h"
|
||||
#include "performance.h"
|
||||
#include "position.h"
|
||||
#include "serializer.h"
|
||||
#include "symbol.h"
|
||||
|
135
src/main.cc
Normal file
135
src/main.cc
Normal file
@ -0,0 +1,135 @@
|
||||
#include "import_pipeline.h"
|
||||
#include "platform.h"
|
||||
#include "serializer.h"
|
||||
#include "serializers/json.h"
|
||||
#include "test.h"
|
||||
#include "working_files.h"
|
||||
|
||||
#include <llvm/Support/CommandLine.h>
|
||||
#include <llvm/Support/Process.h>
|
||||
using namespace llvm;
|
||||
using namespace llvm::cl;
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
#include <rapidjson/error/en.h>
|
||||
#include <loguru.hpp>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
std::string g_init_options;
|
||||
|
||||
namespace {
|
||||
opt<bool> opt_help("h", desc("Alias for -help"));
|
||||
opt<int> opt_verbose("v", desc("verbosity"), init(0));
|
||||
opt<bool> opt_test_index("test-index", desc("run index tests"));
|
||||
opt<bool> opt_test_unit("test-unit", desc("run unit tests"));
|
||||
|
||||
opt<std::string> opt_init("init", desc("extra initialization options"));
|
||||
opt<std::string> opt_log_file("log-file", desc("log"), value_desc("filename"));
|
||||
opt<std::string> opt_log_file_append("log-file-append", desc("log"), value_desc("filename"));
|
||||
|
||||
list<std::string> opt_extra(Positional, ZeroOrMore, desc("extra"));
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
TraceMe();
|
||||
|
||||
ParseCommandLineOptions(argc, argv,
|
||||
"C/C++/Objective-C language server\n\n"
|
||||
"See more on https://github.com/MaskRay/ccls/wiki");
|
||||
|
||||
if (opt_help) {
|
||||
PrintHelpMessage();
|
||||
// Also emit doctest help if --test-unit is passed.
|
||||
if (!opt_test_unit)
|
||||
return 0;
|
||||
}
|
||||
|
||||
loguru::g_stderr_verbosity = opt_verbose - 1;
|
||||
loguru::g_preamble_date = false;
|
||||
loguru::g_preamble_time = false;
|
||||
loguru::g_preamble_verbose = false;
|
||||
loguru::g_flush_interval_ms = 0;
|
||||
loguru::init(argc, argv);
|
||||
|
||||
MultiQueueWaiter querydb_waiter, indexer_waiter, stdout_waiter;
|
||||
QueueManager::Init(&querydb_waiter, &indexer_waiter, &stdout_waiter);
|
||||
|
||||
#ifdef _WIN32
|
||||
// We need to write to stdout in binary mode because in Windows, writing
|
||||
// \n will implicitly write \r\n. Language server API will ignore a
|
||||
// \r\r\n split request.
|
||||
_setmode(_fileno(stdout), O_BINARY);
|
||||
_setmode(_fileno(stdin), O_BINARY);
|
||||
#endif
|
||||
|
||||
IndexInit();
|
||||
|
||||
bool language_server = true;
|
||||
|
||||
if (opt_log_file.size())
|
||||
loguru::add_file(opt_log_file.c_str(), loguru::Truncate, opt_verbose);
|
||||
if (opt_log_file_append.size())
|
||||
loguru::add_file(opt_log_file_append.c_str(), loguru::Append, opt_verbose);
|
||||
|
||||
if (opt_test_unit) {
|
||||
language_server = false;
|
||||
doctest::Context context;
|
||||
std::vector<const char*> args{argv[0]};
|
||||
if (opt_help)
|
||||
args.push_back("-h");
|
||||
for (auto& arg : opt_extra)
|
||||
args.push_back(arg.c_str());
|
||||
context.applyCommandLine(args.size(), args.data());
|
||||
int res = context.run();
|
||||
if (res != 0 || context.shouldExit())
|
||||
return res;
|
||||
}
|
||||
|
||||
if (opt_test_index) {
|
||||
language_server = false;
|
||||
if (!RunIndexTests("", sys::Process::StandardInIsUserInput()))
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (language_server) {
|
||||
if (!opt_init.empty()) {
|
||||
// We check syntax error here but override client-side
|
||||
// initializationOptions in messages/initialize.cc
|
||||
g_init_options = opt_init;
|
||||
rapidjson::Document reader;
|
||||
rapidjson::ParseResult ok = reader.Parse(g_init_options.c_str());
|
||||
if (!ok) {
|
||||
fprintf(stderr, "Failed to parse --init as JSON: %s (%zd)\n",
|
||||
rapidjson::GetParseError_En(ok.Code()), ok.Offset());
|
||||
return 1;
|
||||
}
|
||||
JsonReader json_reader{&reader};
|
||||
try {
|
||||
Config config;
|
||||
Reflect(json_reader, config);
|
||||
} catch (std::invalid_argument& e) {
|
||||
fprintf(stderr, "Failed to parse --init %s, expected %s\n",
|
||||
static_cast<JsonReader&>(json_reader).GetPath().c_str(),
|
||||
e.what());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_map<MethodType, Timer> request_times;
|
||||
|
||||
// The thread that reads from stdin and dispatchs commands to the main thread.
|
||||
LaunchStdinLoop(&request_times);
|
||||
// The thread that writes responses from the main thread to stdout.
|
||||
LaunchStdoutThread(&request_times, &stdout_waiter);
|
||||
// Main thread which also spawns indexer threads upon the "initialize" request.
|
||||
MainLoop(&querydb_waiter, &indexer_waiter);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
@ -17,7 +17,6 @@ struct Config;
|
||||
class DiagnosticsEngine;
|
||||
struct VFS;
|
||||
struct ImportManager;
|
||||
struct ImportPipelineStatus;
|
||||
struct IncludeComplete;
|
||||
struct MultiQueueWaiter;
|
||||
struct Project;
|
||||
@ -108,7 +107,6 @@ struct MessageHandler {
|
||||
DiagnosticsEngine* diag_engine = nullptr;
|
||||
VFS* vfs = nullptr;
|
||||
ImportManager* import_manager = nullptr;
|
||||
ImportPipelineStatus* import_pipeline_status = nullptr;
|
||||
SemanticHighlightSymbolCache* semantic_cache = nullptr;
|
||||
WorkingFiles* working_files = nullptr;
|
||||
ClangCompleteManager* clang_complete = nullptr;
|
||||
|
@ -519,8 +519,7 @@ struct Handler_Initialize : BaseMessageHandler<In_InitializeRequest> {
|
||||
g_thread_id = i + 1;
|
||||
std::string name = "indexer" + std::to_string(i);
|
||||
SetThreadName(name.c_str());
|
||||
Indexer_Main(diag_engine, vfs, import_pipeline_status, project,
|
||||
working_files, waiter);
|
||||
Indexer_Main(diag_engine, vfs, project, working_files, waiter);
|
||||
}).detach();
|
||||
}
|
||||
|
||||
|
@ -1,30 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "serializer.h"
|
||||
|
||||
// Contains timing information for the entire pipeline for importing a file
|
||||
// into the querydb.
|
||||
struct PerformanceImportFile {
|
||||
// All units are in microseconds.
|
||||
|
||||
// [indexer] clang parsing the file
|
||||
uint64_t index_parse = 0;
|
||||
// [indexer] build the IndexFile object from clang parse
|
||||
uint64_t index_build = 0;
|
||||
// [indexer] save the IndexFile to disk
|
||||
uint64_t index_save_to_disk = 0;
|
||||
// [indexer] loading previously cached index
|
||||
uint64_t index_load_cached = 0;
|
||||
// [indexer] create delta IndexUpdate object
|
||||
uint64_t index_make_delta = 0;
|
||||
// [querydb] update WorkingFile indexed file state
|
||||
// uint64_t querydb_update_working_file = 0;
|
||||
// [querydb] apply IndexUpdate
|
||||
// uint64_t querydb_apply_index_update = 0;
|
||||
};
|
||||
MAKE_REFLECT_STRUCT(PerformanceImportFile,
|
||||
index_parse,
|
||||
index_build,
|
||||
index_save_to_disk,
|
||||
index_load_cached,
|
||||
index_make_delta);
|
@ -6,9 +6,6 @@
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
void PlatformInit();
|
||||
|
||||
std::string GetExecutablePath();
|
||||
std::string NormalizePath(const std::string& path);
|
||||
|
||||
void SetThreadName(const char* name);
|
||||
|
@ -8,40 +8,22 @@
|
||||
|
||||
#include <loguru.hpp>
|
||||
|
||||
#include <pthread.h>
|
||||
#if defined(__FreeBSD__)
|
||||
#include <pthread_np.h>
|
||||
#include <sys/thr.h>
|
||||
#elif defined(__OpenBSD__)
|
||||
#include <pthread_np.h>
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <semaphore.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h> // required for stat.h
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <semaphore.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#if defined(__FreeBSD__)
|
||||
#include <sys/param.h> // MAXPATHLEN
|
||||
#include <sys/sysctl.h> // sysctl
|
||||
#elif defined(__linux__)
|
||||
#ifdef __GLIBC__
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
@ -114,52 +96,13 @@ std::optional<std::string> RealPathNotExpandSymlink(std::string path) {
|
||||
|
||||
} // namespace
|
||||
|
||||
void PlatformInit() {}
|
||||
|
||||
#ifdef __APPLE__
|
||||
extern "C" int _NSGetExecutablePath(char* buf, uint32_t* bufsize);
|
||||
#endif
|
||||
|
||||
// See
|
||||
// https://stackoverflow.com/questions/143174/how-do-i-get-the-directory-that-a-program-is-running-from
|
||||
std::string GetExecutablePath() {
|
||||
#ifdef __APPLE__
|
||||
uint32_t size = 0;
|
||||
_NSGetExecutablePath(nullptr, &size);
|
||||
char* buffer = new char[size];
|
||||
_NSGetExecutablePath(buffer, &size);
|
||||
char* resolved = realpath(buffer, nullptr);
|
||||
std::string result(resolved);
|
||||
delete[] buffer;
|
||||
free(resolved);
|
||||
return result;
|
||||
#elif defined(__FreeBSD__)
|
||||
static const int name[] = {
|
||||
CTL_KERN,
|
||||
KERN_PROC,
|
||||
KERN_PROC_PATHNAME,
|
||||
-1,
|
||||
};
|
||||
char path[MAXPATHLEN];
|
||||
size_t len = sizeof(path);
|
||||
path[0] = '\0';
|
||||
(void)sysctl(name, 4, path, &len, NULL, 0);
|
||||
return std::string(path);
|
||||
#else
|
||||
char buffer[PATH_MAX] = {0};
|
||||
if (-1 == readlink("/proc/self/exe", buffer, PATH_MAX))
|
||||
return "";
|
||||
return buffer;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string NormalizePath(const std::string& path) {
|
||||
std::optional<std::string> resolved = RealPathNotExpandSymlink(path);
|
||||
return resolved ? *resolved : path;
|
||||
}
|
||||
|
||||
void FreeUnusedMemory() {
|
||||
#if defined(__GLIBC__)
|
||||
#ifdef __GLIBC__
|
||||
malloc_trim(0);
|
||||
#endif
|
||||
}
|
||||
|
@ -3,8 +3,6 @@
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
#include <loguru.hpp>
|
||||
|
||||
#include <Windows.h>
|
||||
#include <direct.h>
|
||||
#include <fcntl.h>
|
||||
@ -17,22 +15,6 @@
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
|
||||
void PlatformInit() {
|
||||
// We need to write to stdout in binary mode because in Windows, writing
|
||||
// \n will implicitly write \r\n. Language server API will ignore a
|
||||
// \r\r\n split request.
|
||||
_setmode(_fileno(stdout), O_BINARY);
|
||||
_setmode(_fileno(stdin), O_BINARY);
|
||||
}
|
||||
|
||||
// See
|
||||
// https://stackoverflow.com/questions/143174/how-do-i-get-the-directory-that-a-program-is-running-from
|
||||
std::string GetExecutablePath() {
|
||||
char result[MAX_PATH] = {0};
|
||||
GetModuleFileName(NULL, result, MAX_PATH);
|
||||
return NormalizePath(result);
|
||||
}
|
||||
|
||||
std::string NormalizePath(const std::string& path) {
|
||||
DWORD retval = 0;
|
||||
TCHAR buffer[MAX_PATH] = TEXT("");
|
||||
|
@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "method.h"
|
||||
#include "performance.h"
|
||||
#include "query.h"
|
||||
#include "threaded_queue.h"
|
||||
|
||||
|
36
src/utils.cc
36
src/utils.cc
@ -16,15 +16,6 @@
|
||||
#include <unordered_map>
|
||||
using namespace std::placeholders;
|
||||
|
||||
// DEFAULT_RESOURCE_DIRECTORY is passed with quotes for non-MSVC compilers, ie,
|
||||
// foo vs "foo".
|
||||
#if defined(_MSC_VER)
|
||||
#define _STRINGIFY(x) #x
|
||||
#define ENSURE_STRING_MACRO_ARGUMENT(x) _STRINGIFY(x)
|
||||
#else
|
||||
#define ENSURE_STRING_MACRO_ARGUMENT(x) x
|
||||
#endif
|
||||
|
||||
void TrimInPlace(std::string& s) {
|
||||
auto f = [](char c) { return !isspace(c); };
|
||||
s.erase(s.begin(), std::find_if(s.begin(), s.end(), f));
|
||||
@ -137,8 +128,9 @@ std::optional<std::string> ReadContent(const std::string& filename) {
|
||||
|
||||
void WriteToFile(const std::string& filename, const std::string& content) {
|
||||
FILE* f = fopen(filename.c_str(), "wb");
|
||||
if (!f || fwrite(content.c_str(), content.size(), 1, f) != 1) {
|
||||
LOG_S(ERROR) << "Failed to write to " << filename << ' ' << strerror(errno);
|
||||
if (!f ||
|
||||
(content.size() && fwrite(content.c_str(), content.size(), 1, f) != 1)) {
|
||||
LOG_S(ERROR) << "failed to write to " << filename << ' ' << strerror(errno);
|
||||
return;
|
||||
}
|
||||
fclose(f);
|
||||
@ -154,25 +146,5 @@ std::optional<int64_t> LastWriteTime(const std::string& filename) {
|
||||
}
|
||||
|
||||
std::string GetDefaultResourceDirectory() {
|
||||
std::string result;
|
||||
|
||||
std::string resource_directory =
|
||||
std::string(ENSURE_STRING_MACRO_ARGUMENT(DEFAULT_RESOURCE_DIRECTORY));
|
||||
// Remove double quoted resource dir if it was passed with quotes
|
||||
// by the build system.
|
||||
if (resource_directory.size() >= 2 && resource_directory[0] == '"' &&
|
||||
resource_directory[resource_directory.size() - 1] == '"') {
|
||||
resource_directory =
|
||||
resource_directory.substr(1, resource_directory.size() - 2);
|
||||
}
|
||||
if (resource_directory.compare(0, 2, "..") == 0) {
|
||||
std::string executable_path = GetExecutablePath();
|
||||
size_t pos = executable_path.find_last_of('/');
|
||||
result = executable_path.substr(0, pos + 1);
|
||||
result += resource_directory;
|
||||
} else {
|
||||
result = resource_directory;
|
||||
}
|
||||
|
||||
return NormalizePath(result);
|
||||
return DEFAULT_RESOURCE_DIRECTORY;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user