diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e62c8f0..1e71d938 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/src/command_line.cc b/src/command_line.cc deleted file mode 100644 index 70599b01..00000000 --- a/src/command_line.cc +++ /dev/null @@ -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 -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -std::string g_init_options; - -namespace { - -std::unordered_map ParseOptions(int argc, - char** argv) { - std::unordered_map 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& 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 - 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 - Override client provided initialization options - https://github.com/MaskRay/ccls/wiki/Initialization-options - --log-file Logging file for diagnostics - --log-file-append 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> 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 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(); - auto non_global_code_complete_cache = std::make_unique(); - auto signature_cache = std::make_unique(); - 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* request_times) { - std::thread([request_times]() { - SetThreadName("stdin"); - auto* queue = QueueManager::instance(); - while (true) { - std::unique_ptr message; - std::optional 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* request_times, - MultiQueueWaiter* waiter) { - std::thread([=]() { - SetThreadName("stdout"); - auto* queue = QueueManager::instance(); - - while (true) { - std::vector 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 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 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(json_reader).GetPath().c_str(), - e.what()); - return 1; - } - } - - LanguageServerMain(argv[0], &querydb_waiter, &indexer_waiter, - &stdout_waiter); - } - - return 0; -} diff --git a/src/file_consumer.h b/src/file_consumer.h index eb006a32..a7f7b271 100644 --- a/src/file_consumer.h +++ b/src/file_consumer.h @@ -1,6 +1,7 @@ #pragma once #include "position.h" +#include "serializer.h" #include "utils.h" #include @@ -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); diff --git a/src/import_pipeline.cc b/src/import_pipeline.cc index a2aef1c1..625ba878 100644 --- a/src/import_pipeline.cc +++ b/src/import_pipeline.cc @@ -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 #include +#include 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* request_times) { + std::thread([request_times]() { + SetThreadName("stdin"); + auto* queue = QueueManager::instance(); + while (true) { + std::unique_ptr message; + std::optional 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 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* request_times, + MultiQueueWaiter* waiter) { + std::thread([=]() { + SetThreadName("stdout"); + auto* queue = QueueManager::instance(); + + while (true) { + std::vector 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 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(); + auto non_global_code_complete_cache = std::make_unique(); + auto signature_cache = std::make_unique(); + 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> 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 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); + } + } } diff --git a/src/import_pipeline.h b/src/import_pipeline.h index 0ba25eea..87076be9 100644 --- a/src/import_pipeline.h +++ b/src/import_pipeline.h @@ -1,30 +1,28 @@ #pragma once +#include "queue_manager.h" +#include "timer.h" + #include +#include struct ClangTranslationUnit; class DiagnosticsEngine; struct VFS; struct ICacheManager; -struct MultiQueueWaiter; struct Project; struct QueryDatabase; struct SemanticHighlightSymbolCache; struct WorkingFiles; -struct ImportPipelineStatus { - std::atomic num_active_threads = {0}; - std::atomic 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* request_times); +void LaunchStdoutThread(std::unordered_map* request_times, + MultiQueueWaiter* waiter); +void MainLoop(MultiQueueWaiter* querydb_waiter, + MultiQueueWaiter* indexer_waiter); diff --git a/src/indexer.h b/src/indexer.h index e27c78e7..12bed98c 100644 --- a/src/indexer.h +++ b/src/indexer.h @@ -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" diff --git a/src/main.cc b/src/main.cc new file mode 100644 index 00000000..503b1e12 --- /dev/null +++ b/src/main.cc @@ -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 +#include +using namespace llvm; +using namespace llvm::cl; + +#include +#include +#include + +#include +#include +#include +#include +#include + +std::string g_init_options; + +namespace { +opt opt_help("h", desc("Alias for -help")); +opt opt_verbose("v", desc("verbosity"), init(0)); +opt opt_test_index("test-index", desc("run index tests")); +opt opt_test_unit("test-unit", desc("run unit tests")); + +opt opt_init("init", desc("extra initialization options")); +opt opt_log_file("log-file", desc("log"), value_desc("filename")); +opt opt_log_file_append("log-file-append", desc("log"), value_desc("filename")); + +list 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 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(json_reader).GetPath().c_str(), + e.what()); + return 1; + } + } + + std::unordered_map 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; +} diff --git a/src/message_handler.h b/src/message_handler.h index 2afcbc43..d836c873 100644 --- a/src/message_handler.h +++ b/src/message_handler.h @@ -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; diff --git a/src/messages/initialize.cc b/src/messages/initialize.cc index 6a91b202..6dfb3d3e 100644 --- a/src/messages/initialize.cc +++ b/src/messages/initialize.cc @@ -519,8 +519,7 @@ struct Handler_Initialize : BaseMessageHandler { 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(); } diff --git a/src/performance.h b/src/performance.h deleted file mode 100644 index 28579f9c..00000000 --- a/src/performance.h +++ /dev/null @@ -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); diff --git a/src/platform.h b/src/platform.h index adf49d12..775486a0 100644 --- a/src/platform.h +++ b/src/platform.h @@ -6,9 +6,6 @@ #include #include -void PlatformInit(); - -std::string GetExecutablePath(); std::string NormalizePath(const std::string& path); void SetThreadName(const char* name); diff --git a/src/platform_posix.cc b/src/platform_posix.cc index ac058fe5..d1d6c9e0 100644 --- a/src/platform_posix.cc +++ b/src/platform_posix.cc @@ -8,40 +8,22 @@ #include -#include -#if defined(__FreeBSD__) -#include -#include -#elif defined(__OpenBSD__) -#include -#endif - #include -#include #include -#include -#include #include #include #include #include -#include #include +#include +#include +#include +#include #include #include // required for stat.h #include - -#include -#include - -#include -#include - -#if defined(__FreeBSD__) -#include // MAXPATHLEN -#include // sysctl -#elif defined(__linux__) +#ifdef __GLIBC__ #include #endif @@ -114,52 +96,13 @@ std::optional 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 resolved = RealPathNotExpandSymlink(path); return resolved ? *resolved : path; } void FreeUnusedMemory() { -#if defined(__GLIBC__) +#ifdef __GLIBC__ malloc_trim(0); #endif } diff --git a/src/platform_win.cc b/src/platform_win.cc index fdd56aa4..6c02c0ee 100644 --- a/src/platform_win.cc +++ b/src/platform_win.cc @@ -3,8 +3,6 @@ #include "utils.h" -#include - #include #include #include @@ -17,22 +15,6 @@ #include #include -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(""); diff --git a/src/queue_manager.h b/src/queue_manager.h index 63657fea..03f3909c 100644 --- a/src/queue_manager.h +++ b/src/queue_manager.h @@ -1,7 +1,6 @@ #pragma once #include "method.h" -#include "performance.h" #include "query.h" #include "threaded_queue.h" diff --git a/src/utils.cc b/src/utils.cc index cf63245f..f9632f3a 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -16,15 +16,6 @@ #include 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 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 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; }