This commit is contained in:
Fangrui Song 2018-03-30 22:05:21 -07:00
parent da649891ae
commit 89dd4b066b
30 changed files with 160 additions and 1238 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
build build
debug debug
release release
/compile_commands.json

View File

@ -170,8 +170,6 @@ target_sources(ccls PRIVATE
src/cache_manager.cc src/cache_manager.cc
src/clang_complete.cc src/clang_complete.cc
src/clang_cursor.cc src/clang_cursor.cc
src/clang_format.cc
src/clang_index.cc
src/clang_indexer.cc src/clang_indexer.cc
src/clang_translation_unit.cc src/clang_translation_unit.cc
src/clang_utils.cc src/clang_utils.cc
@ -201,11 +199,8 @@ target_sources(ccls PRIVATE
src/query_utils.cc src/query_utils.cc
src/query.cc src/query.cc
src/queue_manager.cc src/queue_manager.cc
src/recorder.cc
src/semantic_highlight_symbol_cache.cc
src/serializer.cc src/serializer.cc
src/standard_includes.cc src/standard_includes.cc
src/task.cc
src/test.cc src/test.cc
src/third_party_impl.cc src/third_party_impl.cc
src/timer.cc src/timer.cc
@ -242,9 +237,7 @@ target_sources(ccls PRIVATE
src/messages/text_document_document_highlight.cc src/messages/text_document_document_highlight.cc
src/messages/text_document_document_link.cc src/messages/text_document_document_link.cc
src/messages/text_document_document_symbol.cc src/messages/text_document_document_symbol.cc
src/messages/text_document_formatting.cc
src/messages/text_document_hover.cc src/messages/text_document_hover.cc
src/messages/text_document_range_formatting.cc
src/messages/text_document_references.cc src/messages/text_document_references.cc
src/messages/text_document_rename.cc src/messages/text_document_rename.cc
src/messages/text_document_signature_help.cc src/messages/text_document_signature_help.cc

View File

@ -1,6 +1,7 @@
# ccls # ccls
ccls is a fork of cquery, a C/C++/Objective-C language server. ccls is a fork of cquery (originally written by Jacob Dufault),
a C/C++/Objective-C language server.
* code completion (with both signature help and snippets) * code completion (with both signature help and snippets)
* finding [definition](src/messages/text_document_definition.cc)/[references](src/messages/text_document_references.cc) * finding [definition](src/messages/text_document_definition.cc)/[references](src/messages/text_document_references.cc)

View File

@ -1 +0,0 @@
build/release/compile_commands.json

View File

@ -1,6 +1,6 @@
#pragma once #pragma once
#include "clang_index.h" #include "clang_cursor.h"
#include "clang_translation_unit.h" #include "clang_translation_unit.h"
#include "lru_cache.h" #include "lru_cache.h"
#include "lsp_completion.h" #include "lsp_completion.h"

View File

@ -2,10 +2,10 @@
#include "clang_utils.h" #include "clang_utils.h"
#include <assert.h>
#include <string.h> #include <string.h>
#include <algorithm> #include <algorithm>
#include <cassert> #include <mutex>
Range ResolveCXSourceRange(const CXSourceRange& range, CXFile* cx_file) { Range ResolveCXSourceRange(const CXSourceRange& range, CXFile* cx_file) {
CXSourceLocation start = clang_getRangeStart(range); CXSourceLocation start = clang_getRangeStart(range);
@ -284,3 +284,22 @@ NtString ClangCursor::get_comments() const {
std::string ClangCursor::ToString() const { std::string ClangCursor::ToString() const {
return ::ToString(get_kind()) + " " + get_spell_name(); return ::ToString(get_kind()) + " " + get_spell_name();
} }
ClangIndex::ClangIndex() : ClangIndex(1, 0) {}
ClangIndex::ClangIndex(int exclude_declarations_from_pch,
int display_diagnostics) {
// llvm::InitializeAllTargets (and possibly others) called by
// clang_createIndex transtively modifies/reads lib/Support/TargetRegistry.cpp
// FirstTarget. There will be a race condition if two threads call
// clang_createIndex concurrently.
static std::mutex mutex_;
std::lock_guard<std::mutex> lock(mutex_);
cx_index =
clang_createIndex(exclude_declarations_from_pch, display_diagnostics);
}
ClangIndex::~ClangIndex() {
clang_disposeIndex(cx_index);
}

View File

@ -114,3 +114,14 @@ struct hash<ClangCursor> {
} }
}; };
} // namespace std } // namespace std
// Simple RAII wrapper about CXIndex.
// Note: building a ClangIndex instance acquires a global lock, since libclang
// API does not appear to be thread-safe here.
class ClangIndex {
public:
ClangIndex();
ClangIndex(int exclude_declarations_from_pch, int display_diagnostics);
~ClangIndex();
CXIndex cx_index;
};

View File

@ -1,117 +0,0 @@
#if USE_CLANG_CXX
#include "clang_format.h"
#include "working_files.h"
#include <doctest/doctest.h>
#include <loguru.hpp>
using namespace clang;
using clang::format::FormatStyle;
namespace {
// TODO Objective-C 'header/interface' files may use .h, we should get this from
// project information.
FormatStyle::LanguageKind getLanguageKindFromFilename(
llvm::StringRef filename) {
if (filename.endswith(".m") || filename.endswith(".mm")) {
return FormatStyle::LK_ObjC;
}
return FormatStyle::LK_Cpp;
}
} // namespace
std::vector<tooling::Replacement> ClangFormatDocument(
WorkingFile* working_file,
int start,
int end,
lsFormattingOptions options) {
const auto language_kind =
getLanguageKindFromFilename(working_file->filename);
FormatStyle predefined_style;
getPredefinedStyle("chromium", language_kind, &predefined_style);
llvm::Expected<FormatStyle> style =
format::getStyle("file", working_file->filename, "chromium");
if (!style) {
// If, for some reason, we cannot get a format style, use Chromium's with
// tab configuration provided by the client editor.
LOG_S(ERROR) << llvm::toString(style.takeError());
predefined_style.UseTab = options.insertSpaces
? FormatStyle::UseTabStyle::UT_Never
: FormatStyle::UseTabStyle::UT_Always;
predefined_style.IndentWidth = options.tabSize;
}
auto format_result = reformat(
style ? *style : predefined_style, working_file->buffer_content,
llvm::ArrayRef<tooling::Range>(tooling::Range(start, end - start)),
working_file->filename);
return std::vector<tooling::Replacement>(format_result.begin(),
format_result.end());
}
TEST_SUITE("ClangFormat") {
TEST_CASE("entireDocument") {
const std::string sample_document = "int main() { int *i = 0; return 0; }";
WorkingFile* file = new WorkingFile("foo.cc", sample_document);
lsFormattingOptions formatting_options;
formatting_options.insertSpaces = true;
const auto replacements = ClangFormatDocument(
file, 0, sample_document.size(), formatting_options);
// echo "int main() { int *i = 0; return 0; }" | clang-format
// -style=Chromium -output-replacements-xml
//
// <?xml version='1.0'?>
// <replacements xml:space='preserve' incomplete_format='false'>
// <replacement offset='12' length='1'>&#10; </replacement>
// <replacement offset='16' length='1'></replacement>
// <replacement offset='18' length='0'> </replacement>
// <replacement offset='24' length='1'>&#10; </replacement>
// <replacement offset='34' length='1'>&#10;</replacement>
// </replacements>
REQUIRE(replacements.size() == 5);
REQUIRE(replacements[0].getOffset() == 12);
REQUIRE(replacements[0].getLength() == 1);
REQUIRE(replacements[0].getReplacementText() == "\n ");
REQUIRE(replacements[1].getOffset() == 16);
REQUIRE(replacements[1].getLength() == 1);
REQUIRE(replacements[1].getReplacementText() == "");
REQUIRE(replacements[2].getOffset() == 18);
REQUIRE(replacements[2].getLength() == 0);
REQUIRE(replacements[2].getReplacementText() == " ");
REQUIRE(replacements[3].getOffset() == 24);
REQUIRE(replacements[3].getLength() == 1);
REQUIRE(replacements[3].getReplacementText() == "\n ");
REQUIRE(replacements[4].getOffset() == 34);
REQUIRE(replacements[4].getLength() == 1);
REQUIRE(replacements[4].getReplacementText() == "\n");
}
TEST_CASE("range") {
const std::string sampleDocument = "int main() { int *i = 0; return 0; }";
WorkingFile* file = new WorkingFile("foo.cc", sampleDocument);
lsFormattingOptions formattingOptions;
formattingOptions.insertSpaces = true;
const auto replacements =
ClangFormatDocument(file, 30, sampleDocument.size(), formattingOptions);
REQUIRE(replacements.size() == 2);
REQUIRE(replacements[0].getOffset() == 24);
REQUIRE(replacements[0].getLength() == 1);
REQUIRE(replacements[0].getReplacementText() == "\n ");
REQUIRE(replacements[1].getOffset() == 34);
REQUIRE(replacements[1].getLength() == 1);
REQUIRE(replacements[1].getReplacementText() == "\n");
}
}
#endif

View File

@ -1,18 +0,0 @@
#pragma once
#if USE_CLANG_CXX
#include "lsp.h"
#include "working_files.h"
#include <clang/Format/Format.h>
#include <vector>
std::vector<clang::tooling::Replacement> ClangFormatDocument(
WorkingFile* working_file,
int start,
int end,
lsFormattingOptions options);
#endif

View File

@ -1,22 +0,0 @@
#include "clang_index.h"
#include <mutex>
ClangIndex::ClangIndex() : ClangIndex(1, 0) {}
ClangIndex::ClangIndex(int exclude_declarations_from_pch,
int display_diagnostics) {
// llvm::InitializeAllTargets (and possibly others) called by
// clang_createIndex transtively modifies/reads lib/Support/TargetRegistry.cpp
// FirstTarget. There will be a race condition if two threads call
// clang_createIndex concurrently.
static std::mutex mutex_;
std::lock_guard<std::mutex> lock(mutex_);
cx_index =
clang_createIndex(exclude_declarations_from_pch, display_diagnostics);
}
ClangIndex::~ClangIndex() {
clang_disposeIndex(cx_index);
}

View File

@ -1,14 +0,0 @@
#pragma once
#include <clang-c/Index.h>
// Simple RAII wrapper about CXIndex.
// Note: building a ClangIndex instance acquires a global lock, since libclang
// API does not appear to be thread-safe here.
class ClangIndex {
public:
ClangIndex();
ClangIndex(int exclude_declarations_from_pch, int display_diagnostics);
~ClangIndex();
CXIndex cx_index;
};

View File

@ -1555,7 +1555,6 @@ void OnIndexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) {
SetVarDetail(var, std::string(decl->entityInfo->name), decl->cursor, SetVarDetail(var, std::string(decl->entityInfo->name), decl->cursor,
decl->semanticContainer, !decl->isRedeclaration, db, param); decl->semanticContainer, !decl->isRedeclaration, db, param);
// FIXME https://github.com/jacobdufault/ccls/issues/239
var->def.kind = GetSymbolKind(decl->entityInfo->kind); var->def.kind = GetSymbolKind(decl->entityInfo->kind);
if (var->def.kind == lsSymbolKind::Variable && if (var->def.kind == lsSymbolKind::Variable &&
decl->cursor.kind == CXCursor_ParmDecl) decl->cursor.kind == CXCursor_ParmDecl)
@ -1776,8 +1775,6 @@ void OnIndexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) {
// For Typedef/CXXTypeAlias spanning a few lines, display the declaration // For Typedef/CXXTypeAlias spanning a few lines, display the declaration
// line, with spelling name replaced with qualified name. // line, with spelling name replaced with qualified name.
// TODO Think how to display multi-line declaration like `typedef struct {
// ... } foo;` https://github.com/jacobdufault/ccls/issues/29
if (extent.end.line - extent.start.line < if (extent.end.line - extent.start.line <
kMaxLinesDisplayTypeAliasDeclarations) { kMaxLinesDisplayTypeAliasDeclarations) {
FileContents& fc = param->file_contents[db->path]; FileContents& fc = param->file_contents[db->path];
@ -1924,7 +1921,6 @@ void OnIndexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) {
} }
} }
// https://github.com/jacobdufault/ccls/issues/174
// Type-dependent member access expressions do not have accurate spelling // Type-dependent member access expressions do not have accurate spelling
// ranges. // ranges.
// //
@ -2328,56 +2324,6 @@ void IndexInit() {
clang_toggleCrashRecovery(1); clang_toggleCrashRecovery(1);
} }
void ClangSanityCheck() {
std::vector<const char*> args = {"clang", "index_tests/vars/class_member.cc"};
unsigned opts = 0;
CXIndex index = clang_createIndex(0, 1);
CXTranslationUnit tu;
clang_parseTranslationUnit2FullArgv(index, nullptr, args.data(), args.size(),
nullptr, 0, opts, &tu);
assert(tu);
IndexerCallbacks callback = {0};
callback.abortQuery = [](CXClientData client_data, void* reserved) {
return 0;
};
callback.diagnostic = [](CXClientData client_data,
CXDiagnosticSet diagnostics, void* reserved) {};
callback.enteredMainFile = [](CXClientData client_data, CXFile mainFile,
void* reserved) -> CXIdxClientFile {
return nullptr;
};
callback.ppIncludedFile =
[](CXClientData client_data,
const CXIdxIncludedFileInfo* file) -> CXIdxClientFile {
return nullptr;
};
callback.importedASTFile =
[](CXClientData client_data,
const CXIdxImportedASTFileInfo*) -> CXIdxClientASTFile {
return nullptr;
};
callback.startedTranslationUnit = [](CXClientData client_data,
void* reserved) -> CXIdxClientContainer {
return nullptr;
};
callback.indexDeclaration = [](CXClientData client_data,
const CXIdxDeclInfo* decl) {};
callback.indexEntityReference = [](CXClientData client_data,
const CXIdxEntityRefInfo* ref) {};
const unsigned kIndexOpts = 0;
CXIndexAction index_action = clang_IndexAction_create(index);
int index_param = 0;
clang_toggleCrashRecovery(0);
clang_indexTranslationUnit(index_action, &index_param, &callback,
sizeof(IndexerCallbacks), kIndexOpts, tu);
clang_IndexAction_dispose(index_action);
clang_disposeTranslationUnit(tu);
clang_disposeIndex(index);
}
std::string GetClangVersion() { std::string GetClangVersion() {
return ToString(clang_getClangVersion()); return ToString(clang_getClangVersion());
} }

View File

@ -1,7 +1,6 @@
#pragma once #pragma once
#include "clang_cursor.h" #include "clang_cursor.h"
#include "clang_index.h"
#include <clang-c/Index.h> #include <clang-c/Index.h>

View File

@ -2,8 +2,6 @@
#include "platform.h" #include "platform.h"
#include <doctest/doctest.h>
namespace { namespace {
lsRange GetLsRangeForFixIt(const CXSourceRange& range) { lsRange GetLsRangeForFixIt(const CXSourceRange& range) {
@ -106,31 +104,6 @@ std::optional<lsDiagnostic> BuildAndDisposeDiagnostic(CXDiagnostic diagnostic,
return ls_diagnostic; return ls_diagnostic;
} }
#if USE_CLANG_CXX
static lsPosition OffsetToRange(llvm::StringRef document, size_t offset) {
// TODO: Support Windows line endings, etc.
llvm::StringRef text_before = document.substr(0, offset);
int num_line = text_before.count('\n');
int num_column = text_before.size() - text_before.rfind('\n') - 1;
return {num_line, num_column};
}
std::vector<lsTextEdit> ConvertClangReplacementsIntoTextEdits(
llvm::StringRef document,
const std::vector<clang::tooling::Replacement>& clang_replacements) {
std::vector<lsTextEdit> text_edits_result;
for (const auto& replacement : clang_replacements) {
const auto startPosition = OffsetToRange(document, replacement.getOffset());
const auto endPosition = OffsetToRange(
document, replacement.getOffset() + replacement.getLength());
text_edits_result.push_back(
{{startPosition, endPosition}, replacement.getReplacementText()});
}
return text_edits_result;
}
#endif
std::string FileName(CXFile file) { std::string FileName(CXFile file) {
CXString cx_name = clang_getFileName(file); CXString cx_name = clang_getFileName(file);
std::string name = ToString(cx_name); std::string name = ToString(cx_name);
@ -172,60 +145,9 @@ const char* ClangBuiltinTypeName(CXTypeKind kind) {
case CXType_Double: return "double"; case CXType_Double: return "double";
case CXType_LongDouble: return "long double"; case CXType_LongDouble: return "long double";
case CXType_Float128: return "__float128"; case CXType_Float128: return "__float128";
#if CINDEX_VERSION_MINOR >= 43
case CXType_Half: return "_Float16"; case CXType_Half: return "_Float16";
#endif
case CXType_NullPtr: return "nullptr"; case CXType_NullPtr: return "nullptr";
default: return ""; default: return "";
// clang-format on // clang-format on
} }
} }
#if USE_CLANG_CXX
TEST_SUITE("ClangUtils") {
TEST_CASE("replacements") {
const std::string sample_document =
"int \n"
"main() { int *i = 0; return 0; \n"
"}";
const std::vector<clang::tooling::Replacement> clang_replacements = {
{"foo.cc", 3, 2, " "}, {"foo.cc", 13, 1, "\n "},
{"foo.cc", 17, 1, ""}, {"foo.cc", 19, 0, " "},
{"foo.cc", 25, 1, "\n "}, {"foo.cc", 35, 2, "\n"}};
// Expected format:
//
// int main() {
// int *i = 0;
// return 0;
// }
const auto text_edits = ConvertClangReplacementsIntoTextEdits(
sample_document, clang_replacements);
REQUIRE(text_edits.size() == 6);
REQUIRE(text_edits[0].range.start.line == 0);
REQUIRE(text_edits[0].range.start.character == 3);
REQUIRE(text_edits[0].newText == " ");
REQUIRE(text_edits[1].range.start.line == 1);
REQUIRE(text_edits[1].range.start.character == 8);
REQUIRE(text_edits[1].newText == "\n ");
REQUIRE(text_edits[2].range.start.line == 1);
REQUIRE(text_edits[2].range.start.character == 12);
REQUIRE(text_edits[2].newText == "");
REQUIRE(text_edits[3].range.start.line == 1);
REQUIRE(text_edits[3].range.start.character == 14);
REQUIRE(text_edits[3].newText == " ");
REQUIRE(text_edits[4].range.start.line == 1);
REQUIRE(text_edits[4].range.start.character == 20);
REQUIRE(text_edits[4].newText == "\n ");
REQUIRE(text_edits[5].range.start.line == 1);
REQUIRE(text_edits[5].range.start.character == 30);
REQUIRE(text_edits[5].newText == "\n");
}
}
#endif

View File

@ -3,11 +3,8 @@
#include "lsp_diagnostic.h" #include "lsp_diagnostic.h"
#include <clang-c/Index.h> #include <clang-c/Index.h>
#if USE_CLANG_CXX
#include <clang/Format/Format.h>
#endif
#include <optional>
#include <optional>
#include <vector> #include <vector>
std::optional<lsDiagnostic> BuildAndDisposeDiagnostic(CXDiagnostic diagnostic, std::optional<lsDiagnostic> BuildAndDisposeDiagnostic(CXDiagnostic diagnostic,
@ -21,10 +18,3 @@ std::string ToString(CXString cx_string);
std::string ToString(CXCursorKind cursor_kind); std::string ToString(CXCursorKind cursor_kind);
const char* ClangBuiltinTypeName(CXTypeKind); const char* ClangBuiltinTypeName(CXTypeKind);
// Converts Clang formatting replacement operations into LSP text edits.
#if USE_CLANG_CXX
std::vector<lsTextEdit> ConvertClangReplacementsIntoTextEdits(
llvm::StringRef document,
const std::vector<clang::tooling::Replacement>& clang_replacements);
#endif

View File

@ -19,8 +19,6 @@
#include "query.h" #include "query.h"
#include "query_utils.h" #include "query_utils.h"
#include "queue_manager.h" #include "queue_manager.h"
#include "recorder.h"
#include "semantic_highlight_symbol_cache.h"
#include "serializer.h" #include "serializer.h"
#include "serializers/json.h" #include "serializers/json.h"
#include "test.h" #include "test.h"
@ -43,19 +41,10 @@
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
// TODO: provide a feature like 'https://github.com/goldsborough/clang-expand',
// ie, a fully linear view of a function with inline function calls expanded.
// We can probably use vscode decorators to achieve it.
// TODO: implement ThreadPool type which monitors CPU usage / number of work
// items per second completed and scales up/down number of running threads.
std::string g_init_options; std::string g_init_options;
namespace { namespace {
std::vector<std::string> kEmptyArgs;
// This function returns true if e2e timing should be displayed for the given // This function returns true if e2e timing should be displayed for the given
// MethodId. // MethodId.
bool ShouldDisplayMethodTiming(MethodType type) { bool ShouldDisplayMethodTiming(MethodType type) {
@ -70,9 +59,6 @@ void PrintHelp() {
<< R"help(ccls is a low-latency C/C++/Objective-C language server. << R"help(ccls is a low-latency C/C++/Objective-C language server.
Mode: Mode:
--clang-sanity-check
Run a simple index test. Verifies basic clang functionality.
Needs to be executed from the ccls root checkout directory.
--test-unit Run unit tests. --test-unit Run unit tests.
--test-index <opt_filter_path> --test-index <opt_filter_path>
Run index tests. opt_filter_path can be used to specify which Run index tests. opt_filter_path can be used to specify which
@ -85,37 +71,20 @@ Other command line options:
--init <initializationOptions> --init <initializationOptions>
Override client provided initialization options Override client provided initialization options
https://github.com/cquery-project/cquery/wiki/Initialization-options https://github.com/cquery-project/cquery/wiki/Initialization-options
--record <path>
Writes stdin to <path>.in and stdout to <path>.out
--log-file <path> Logging file for diagnostics --log-file <path> Logging file for diagnostics
--log-file-append <path> Like --log-file, but appending --log-file-append <path> Like --log-file, but appending
--log-all-to-stderr Write all log messages to STDERR. --log-all-to-stderr Write all log messages to STDERR.
--wait-for-input Wait for an '[Enter]' before exiting
--help Print this help information. --help Print this help information.
--ci Prevents tests from prompting the user for input. Used for --ci Prevents tests from prompting the user for input. Used for
continuous integration so it can fail faster instead of timing continuous integration so it can fail faster instead of timing
out. out.
See more on https://github.com/cquery-project/cquery/wiki See more on https://github.com/MaskRay/ccls/wiki
)help"; )help";
} }
} // namespace } // namespace
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// QUERYDB MAIN //////////////////////////////////////////////////////////////// // QUERYDB MAIN ////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -248,20 +217,6 @@ void RunQueryDbThread(const std::string& bin_name,
} }
} }
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// STDIN MAIN ////////////////////////////////////////////////////////////////// // STDIN MAIN //////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -333,8 +288,6 @@ void LaunchStdoutThread(std::unordered_map<MethodType, Timer>* request_times,
time.ResetAndPrint("[e2e] Running " + std::string(message.method)); time.ResetAndPrint("[e2e] Running " + std::string(message.method));
} }
RecordOutput(message.content);
fwrite(message.content.c_str(), message.content.size(), 1, stdout); fwrite(message.content.c_str(), message.content.size(), 1, stdout);
fflush(stdout); fflush(stdout);
} }
@ -360,23 +313,6 @@ void LanguageServerMain(const std::string& bin_name,
RunQueryDbThread(bin_name, config, querydb_waiter, indexer_waiter); RunQueryDbThread(bin_name, config, querydb_waiter, indexer_waiter);
} }
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// MAIN ////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
int main(int argc, char** argv) { int main(int argc, char** argv) {
TraceMe(); TraceMe();
@ -413,14 +349,6 @@ int main(int argc, char** argv) {
loguru::Verbosity_MAX); loguru::Verbosity_MAX);
} }
if (HasOption(options, "--record"))
EnableRecording(options["--record"]);
if (HasOption(options, "--clang-sanity-check")) {
language_server = false;
ClangSanityCheck();
}
if (HasOption(options, "--test-unit")) { if (HasOption(options, "--test-unit")) {
language_server = false; language_server = false;
doctest::Context context; doctest::Context context;
@ -467,10 +395,5 @@ int main(int argc, char** argv) {
&stdout_waiter); &stdout_waiter);
} }
if (HasOption(options, "--wait-for-input")) {
std::cerr << std::endl << "[Enter] to exit" << std::endl;
getchar();
}
return 0; return 0;
} }

View File

@ -168,8 +168,6 @@ void IncludeComplete::AddFile(const std::string& absolute_path) {
if (is_scanning) if (is_scanning)
lock.lock(); lock.lock();
InsertCompletionItem(absolute_path, std::move(item)); InsertCompletionItem(absolute_path, std::move(item));
if (lock)
lock.unlock();
} }
void IncludeComplete::InsertIncludesFromDirectory(std::string directory, void IncludeComplete::InsertIncludesFromDirectory(std::string directory,

View File

@ -1,7 +1,6 @@
#pragma once #pragma once
#include "clang_cursor.h" #include "clang_cursor.h"
#include "clang_index.h"
#include "clang_translation_unit.h" #include "clang_translation_unit.h"
#include "clang_utils.h" #include "clang_utils.h"
#include "file_consumer.h" #include "file_consumer.h"
@ -16,14 +15,13 @@
#include "symbol.h" #include "symbol.h"
#include "utils.h" #include "utils.h"
#include <assert.h>
#include <ctype.h>
#include <stdint.h>
#include <algorithm>
#include <unordered_map>
#include <optional> #include <optional>
#include <string_view> #include <string_view>
#include <ctype.h>
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <unordered_map>
#include <vector> #include <vector>
struct IndexFile; struct IndexFile;

View File

@ -1,6 +1,5 @@
#include "lsp.h" #include "lsp.h"
#include "recorder.h"
#include "serializers/json.h" #include "serializers/json.h"
#include <doctest/doctest.h> #include <doctest/doctest.h>
@ -67,8 +66,6 @@ std::optional<std::string> ReadJsonRpcContentFrom(
content += *c; content += *c;
} }
RecordInput(content);
return content; return content;
} }

View File

@ -4,7 +4,6 @@
#include "project.h" #include "project.h"
#include "query_utils.h" #include "query_utils.h"
#include "queue_manager.h" #include "queue_manager.h"
#include "semantic_highlight_symbol_cache.h"
#include <loguru.hpp> #include <loguru.hpp>
@ -42,6 +41,77 @@ struct ScanLineEvent {
}; };
} // namespace } // namespace
SemanticHighlightSymbolCache::Entry::Entry(
SemanticHighlightSymbolCache* all_caches,
const std::string& path)
: all_caches_(all_caches), path(path) {}
std::optional<int> SemanticHighlightSymbolCache::Entry::TryGetStableId(
SymbolKind kind,
const std::string& detailed_name) {
TNameToId* map = GetMapForSymbol_(kind);
auto it = map->find(detailed_name);
if (it != map->end())
return it->second;
return std::nullopt;
}
int SemanticHighlightSymbolCache::Entry::GetStableId(
SymbolKind kind,
const std::string& detailed_name) {
std::optional<int> id = TryGetStableId(kind, detailed_name);
if (id)
return *id;
// Create a new id. First try to find a key in another map.
all_caches_->cache_.IterateValues([&](const std::shared_ptr<Entry>& entry) {
std::optional<int> other_id = entry->TryGetStableId(kind, detailed_name);
if (other_id) {
id = other_id;
return false;
}
return true;
});
// Create a new id.
TNameToId* map = GetMapForSymbol_(kind);
if (!id)
id = all_caches_->next_stable_id_++;
return (*map)[detailed_name] = *id;
}
SemanticHighlightSymbolCache::Entry::TNameToId*
SemanticHighlightSymbolCache::Entry::GetMapForSymbol_(SymbolKind kind) {
switch (kind) {
case SymbolKind::Type:
return &detailed_type_name_to_stable_id;
case SymbolKind::Func:
return &detailed_func_name_to_stable_id;
case SymbolKind::Var:
return &detailed_var_name_to_stable_id;
case SymbolKind::File:
case SymbolKind::Invalid:
break;
}
assert(false);
return nullptr;
}
SemanticHighlightSymbolCache::SemanticHighlightSymbolCache()
: cache_(kCacheSize) {}
void SemanticHighlightSymbolCache::Init(Config* config) {
match_ = std::make_unique<GroupMatch>(config->highlight.whitelist,
config->highlight.blacklist);
}
std::shared_ptr<SemanticHighlightSymbolCache::Entry>
SemanticHighlightSymbolCache::GetCacheForFile(const std::string& path) {
return cache_.Get(
path, [&, this]() { return std::make_shared<Entry>(this, path); });
}
MessageHandler::MessageHandler() { MessageHandler::MessageHandler() {
// Dynamically allocate |message_handlers|, otherwise there will be static // Dynamically allocate |message_handlers|, otherwise there will be static
// initialization order races. // initialization order races.

View File

@ -1,12 +1,14 @@
#pragma once #pragma once
#include "lru_cache.h"
#include "lsp.h" #include "lsp.h"
#include "match.h"
#include "method.h" #include "method.h"
#include "query.h" #include "query.h"
#include <optional> #include <optional>
#include <memory> #include <memory>
#include <unordered_map>
#include <vector> #include <vector>
struct ClangCompleteManager; struct ClangCompleteManager;
@ -20,11 +22,43 @@ struct IncludeComplete;
struct MultiQueueWaiter; struct MultiQueueWaiter;
struct Project; struct Project;
struct QueryDatabase; struct QueryDatabase;
struct SemanticHighlightSymbolCache;
struct TimestampManager; struct TimestampManager;
struct WorkingFile; struct WorkingFile;
struct WorkingFiles; struct WorkingFiles;
// Caches symbols for a single file for semantic highlighting to provide
// relatively stable ids. Only supports xxx files at a time.
struct SemanticHighlightSymbolCache {
struct Entry {
SemanticHighlightSymbolCache* all_caches_ = nullptr;
// The path this cache belongs to.
std::string path;
// Detailed symbol name to stable id.
using TNameToId = std::unordered_map<std::string, int>;
TNameToId detailed_type_name_to_stable_id;
TNameToId detailed_func_name_to_stable_id;
TNameToId detailed_var_name_to_stable_id;
Entry(SemanticHighlightSymbolCache* all_caches, const std::string& path);
std::optional<int> TryGetStableId(SymbolKind kind,
const std::string& detailed_name);
int GetStableId(SymbolKind kind, const std::string& detailed_name);
TNameToId* GetMapForSymbol_(SymbolKind kind);
};
constexpr static int kCacheSize = 10;
LruCache<std::string, Entry> cache_;
uint32_t next_stable_id_ = 0;
std::unique_ptr<GroupMatch> match_;
SemanticHighlightSymbolCache();
void Init(Config*);
std::shared_ptr<Entry> GetCacheForFile(const std::string& path);
};
struct Out_CclsPublishSemanticHighlighting struct Out_CclsPublishSemanticHighlighting
: public lsOutMessage<Out_CclsPublishSemanticHighlighting> { : public lsOutMessage<Out_CclsPublishSemanticHighlighting> {
struct Symbol { struct Symbol {

View File

@ -6,7 +6,6 @@
#include "platform.h" #include "platform.h"
#include "project.h" #include "project.h"
#include "queue_manager.h" #include "queue_manager.h"
#include "semantic_highlight_symbol_cache.h"
#include "serializers/json.h" #include "serializers/json.h"
#include "timer.h" #include "timer.h"
#include "work_thread.h" #include "work_thread.h"
@ -361,26 +360,13 @@ MAKE_REFLECT_STRUCT(lsTextDocumentClientCapabilities,
struct lsClientCapabilities { struct lsClientCapabilities {
// Workspace specific client capabilities. // Workspace specific client capabilities.
std::optional<lsWorkspaceClientCapabilites> workspace; lsWorkspaceClientCapabilites workspace;
// Text document specific client capabilities. // Text document specific client capabilities.
std::optional<lsTextDocumentClientCapabilities> textDocument; lsTextDocumentClientCapabilities textDocument;
/**
* Experimental client capabilities.
*/
// experimental?: any; // TODO
}; };
MAKE_REFLECT_STRUCT(lsClientCapabilities, workspace, textDocument); MAKE_REFLECT_STRUCT(lsClientCapabilities, workspace, textDocument);
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
////////////////////////////// INITIALIZATION ///////////////////////////////
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
struct lsInitializeParams { struct lsInitializeParams {
// The process Id of the parent process that started // The process Id of the parent process that started
// the server. Is null if the process has not been started by another process. // the server. Is null if the process has not been started by another process.
@ -524,8 +510,8 @@ struct Handler_Initialize : BaseMessageHandler<In_InitializeRequest> {
} }
// Client capabilities // Client capabilities
if (request->params.capabilities.textDocument) { {
const auto& cap = *request->params.capabilities.textDocument; const auto& cap = request->params.capabilities.textDocument;
if (cap.completion && cap.completion->completionItem) if (cap.completion && cap.completion->completionItem)
config->client.snippetSupport = config->client.snippetSupport =
cap.completion->completionItem->snippetSupport.value_or(false); cap.completion->completionItem->snippetSupport.value_or(false);

View File

@ -338,7 +338,10 @@ struct Handler_TextDocumentCompletion : MessageHandler {
include_complete->completion_items_mutex, std::defer_lock); include_complete->completion_items_mutex, std::defer_lock);
if (include_complete->is_scanning) if (include_complete->is_scanning)
lock.lock(); lock.lock();
out.result.items = include_complete->completion_items; std::string quote = result.match[5];
for (auto& item : include_complete->completion_items)
if (quote.empty() || quote == (item.use_angle_brackets_ ? "<" : "\""))
out.result.items.push_back(item);
} }
FilterAndSortCompletionResponse(&out, result.pattern, FilterAndSortCompletionResponse(&out, result.pattern,
config->completion.filterAndSort); config->completion.filterAndSort);

View File

@ -1,56 +0,0 @@
#include "recorder.h"
#include <loguru.hpp>
#include <cassert>
#include <fstream>
namespace {
std::ofstream* g_file_in = nullptr;
std::ofstream* g_file_out = nullptr;
} // namespace
void EnableRecording(std::string path) {
if (path.empty())
path = "ccls";
// We can only call |EnableRecording| once.
assert(!g_file_in && !g_file_out);
// Open the files.
g_file_in = new std::ofstream(
path + ".in", std::ios::out | std::ios::trunc | std::ios::binary);
g_file_out = new std::ofstream(
path + ".out", std::ios::out | std::ios::trunc | std::ios::binary);
// Make sure we can write to the files.
bool bad = false;
if (!g_file_in->good()) {
LOG_S(ERROR) << "record: cannot write to " << path << ".in";
bad = true;
}
if (!g_file_out->good()) {
LOG_S(ERROR) << "record: cannot write to " << path << ".out";
bad = true;
}
if (bad) {
delete g_file_in;
delete g_file_out;
g_file_in = nullptr;
g_file_out = nullptr;
}
}
void RecordInput(std::string_view content) {
if (!g_file_in)
return;
(*g_file_in) << "Content-Length: " << content.size() << "\r\n\r\n" << content;
(*g_file_in).flush();
}
void RecordOutput(std::string_view content) {
if (!g_file_out)
return;
(*g_file_out) << content;
(*g_file_out).flush();
}

View File

@ -1,9 +0,0 @@
#pragma once
#include <string_view>
#include <string>
void EnableRecording(std::string path);
void RecordInput(std::string_view content);
void RecordOutput(std::string_view content);

View File

@ -1,72 +0,0 @@
#include "semantic_highlight_symbol_cache.h"
SemanticHighlightSymbolCache::Entry::Entry(
SemanticHighlightSymbolCache* all_caches,
const std::string& path)
: all_caches_(all_caches), path(path) {}
std::optional<int> SemanticHighlightSymbolCache::Entry::TryGetStableId(
SymbolKind kind,
const std::string& detailed_name) {
TNameToId* map = GetMapForSymbol_(kind);
auto it = map->find(detailed_name);
if (it != map->end())
return it->second;
return std::nullopt;
}
int SemanticHighlightSymbolCache::Entry::GetStableId(
SymbolKind kind,
const std::string& detailed_name) {
std::optional<int> id = TryGetStableId(kind, detailed_name);
if (id)
return *id;
// Create a new id. First try to find a key in another map.
all_caches_->cache_.IterateValues([&](const std::shared_ptr<Entry>& entry) {
std::optional<int> other_id = entry->TryGetStableId(kind, detailed_name);
if (other_id) {
id = other_id;
return false;
}
return true;
});
// Create a new id.
TNameToId* map = GetMapForSymbol_(kind);
if (!id)
id = all_caches_->next_stable_id_++;
return (*map)[detailed_name] = *id;
}
SemanticHighlightSymbolCache::Entry::TNameToId*
SemanticHighlightSymbolCache::Entry::GetMapForSymbol_(SymbolKind kind) {
switch (kind) {
case SymbolKind::Type:
return &detailed_type_name_to_stable_id;
case SymbolKind::Func:
return &detailed_func_name_to_stable_id;
case SymbolKind::Var:
return &detailed_var_name_to_stable_id;
case SymbolKind::File:
case SymbolKind::Invalid:
break;
}
assert(false);
return nullptr;
}
SemanticHighlightSymbolCache::SemanticHighlightSymbolCache()
: cache_(kCacheSize) {}
void SemanticHighlightSymbolCache::Init(Config* config) {
match_ = std::make_unique<GroupMatch>(config->highlight.whitelist,
config->highlight.blacklist);
}
std::shared_ptr<SemanticHighlightSymbolCache::Entry>
SemanticHighlightSymbolCache::GetCacheForFile(const std::string& path) {
return cache_.Get(
path, [&, this]() { return std::make_shared<Entry>(this, path); });
}

View File

@ -1,43 +0,0 @@
#pragma once
#include "lru_cache.h"
#include "match.h"
#include "query.h"
#include <optional>
#include <string>
#include <unordered_map>
// Caches symbols for a single file for semantic highlighting to provide
// relatively stable ids. Only supports xxx files at a time.
struct SemanticHighlightSymbolCache {
struct Entry {
SemanticHighlightSymbolCache* all_caches_ = nullptr;
// The path this cache belongs to.
std::string path;
// Detailed symbol name to stable id.
using TNameToId = std::unordered_map<std::string, int>;
TNameToId detailed_type_name_to_stable_id;
TNameToId detailed_func_name_to_stable_id;
TNameToId detailed_var_name_to_stable_id;
Entry(SemanticHighlightSymbolCache* all_caches, const std::string& path);
std::optional<int> TryGetStableId(SymbolKind kind,
const std::string& detailed_name);
int GetStableId(SymbolKind kind, const std::string& detailed_name);
TNameToId* GetMapForSymbol_(SymbolKind kind);
};
constexpr static int kCacheSize = 10;
LruCache<std::string, Entry> cache_;
uint32_t next_stable_id_ = 0;
std::unique_ptr<GroupMatch> match_;
SemanticHighlightSymbolCache();
void Init(Config*);
std::shared_ptr<Entry> GetCacheForFile(const std::string& path);
};

View File

@ -1,152 +0,0 @@
#if false
#include "task.h"
#include "utils.h"
#include <doctest/doctest.h>
TaskManager::TaskManager() {
pending_tasks_[TaskThread::Indexer] = std::make_unique<TaskQueue>();
pending_tasks_[TaskThread::QueryDb] = std::make_unique<TaskQueue>();
}
void TaskManager::Post(TaskThread thread, const TTask& task) {
TaskQueue* queue = pending_tasks_[thread].get();
std::lock_guard<std::mutex> lock_guard(queue->tasks_mutex);
queue->tasks.push_back(task);
}
void TaskManager::SetIdle(TaskThread thread, const TIdleTask& task) {
TaskQueue* queue = pending_tasks_[thread].get();
std::lock_guard<std::mutex> lock_guard(queue->tasks_mutex);
assert(!queue->idle_task && "There is already an idle task");
queue->idle_task = task;
}
bool TaskManager::RunTasks(TaskThread thread, std::optional<std::chrono::duration<long long, std::nano>> max_time) {
auto start = std::chrono::high_resolution_clock::now();
TaskQueue* queue = pending_tasks_[thread].get();
bool ran_task = false;
while (true) {
std::optional<TTask> task;
// Get a task.
{
std::lock_guard<std::mutex> lock_guard(queue->tasks_mutex);
if (queue->tasks.empty())
break;
task = std::move(queue->tasks[queue->tasks.size() - 1]);
queue->tasks.pop_back();
}
// Execute task.
assert(task);
(*task)();
ran_task = true;
// Stop if we've run past our max time. Don't run idle_task.
auto elapsed = std::chrono::high_resolution_clock::now() - start;
if (max_time && elapsed > *max_time)
return ran_task;
}
if (queue->idle_task) {
// Even if the idle task returns false we still ran something before.
ran_task = (*queue->idle_task)() || ran_task;
}
return ran_task;
}
TEST_SUITE("Task");
TEST_CASE("tasks are run as soon as they are posted") {
TaskManager tm;
// Post three tasks.
int next = 1;
int a = 0, b = 0, c = 0;
tm.Post(TaskThread::QueryDb, [&] {
a = next++;
});
tm.Post(TaskThread::QueryDb, [&] {
b = next++;
});
tm.Post(TaskThread::QueryDb, [&] {
c = next++;
});
// Execute all tasks.
tm.RunTasks(TaskThread::QueryDb, std::nullopt);
// Tasks are executed in reverse order.
REQUIRE(a == 3);
REQUIRE(b == 2);
REQUIRE(c == 1);
}
TEST_CASE("post from inside task manager") {
TaskManager tm;
// Post three tasks.
int next = 1;
int a = 0, b = 0, c = 0;
tm.Post(TaskThread::QueryDb, [&] () {
a = next++;
tm.Post(TaskThread::QueryDb, [&] {
b = next++;
tm.Post(TaskThread::QueryDb, [&] {
c = next++;
});
});
});
// Execute all tasks.
tm.RunTasks(TaskThread::QueryDb, std::nullopt);
// Tasks are executed in normal order because the next task is not posted
// until the previous one is executed.
REQUIRE(a == 1);
REQUIRE(b == 2);
REQUIRE(c == 3);
}
TEST_CASE("idle task is run after nested tasks") {
TaskManager tm;
int count = 0;
tm.SetIdle(TaskThread::QueryDb, [&]() {
++count;
return true;
});
// No tasks posted - idle runs once.
REQUIRE(tm.RunTasks(TaskThread::QueryDb, std::nullopt));
REQUIRE(count == 1);
count = 0;
// Idle runs after other posted tasks.
bool did_run = false;
tm.Post(TaskThread::QueryDb, [&]() {
did_run = true;
});
REQUIRE(tm.RunTasks(TaskThread::QueryDb, std::nullopt));
REQUIRE(did_run);
REQUIRE(count == 1);
}
TEST_CASE("RunTasks returns false when idle task returns false and no other tasks were run") {
TaskManager tm;
REQUIRE(tm.RunTasks(TaskThread::QueryDb, std::nullopt) == false);
tm.SetIdle(TaskThread::QueryDb, []() { return false; });
REQUIRE(tm.RunTasks(TaskThread::QueryDb, std::nullopt) == false);
}
TEST_SUITE_END();
#endif

View File

@ -1,41 +0,0 @@
#if false
#pragma once
#include <optional>
#include <chrono>
#include <functional>
#include <mutex>
#include <unordered_map>
#include <vector>
enum class TaskThread {
Indexer,
QueryDb,
};
struct TaskManager {
using TTask = std::function<void()>;
using TIdleTask = std::function<bool()>;
TaskManager();
// Run |task| at some point in the future. This will run the task as soon as possible.
void Post(TaskThread thread, const TTask& task);
// Run |task| whenever there is nothing else to run.
void SetIdle(TaskThread thread, const TIdleTask& idle_task);
// Run pending tasks for |thread|. Stop running tasks after |max_time| has
// elapsed. Returns true if tasks were run.
bool RunTasks(TaskThread thread, std::optional<std::chrono::duration<long long, std::nano>> max_time);
struct TaskQueue {
std::optional<TIdleTask> idle_task;
std::vector<TTask> tasks;
std::mutex tasks_mutex;
};
std::unordered_map<TaskThread, std::unique_ptr<TaskQueue>> pending_tasks_;
};
#endif

424
wscript
View File

@ -1,424 +0,0 @@
#!/usr/bin/env python
# encoding: utf-8
try:
from urllib2 import urlopen # Python 2
except ImportError:
from urllib.request import urlopen # Python 3
import os.path
import string
import subprocess
import sys
import re
import ctypes
import shutil
VERSION = '0.0.1'
APPNAME = 'cquery'
top = '.'
out = 'build'
CLANG_TARBALL_NAME = None
CLANG_TARBALL_EXT = '.tar.xz'
if sys.platform == 'darwin':
CLANG_TARBALL_NAME = 'clang+llvm-$version-x86_64-apple-darwin'
elif sys.platform.startswith('freebsd'):
CLANG_TARBALL_NAME = 'clang+llvm-$version-amd64-unknown-freebsd-10'
# It is either 'linux2' or 'linux3' before Python 3.3
elif sys.platform.startswith('linux'):
# These executable depend on libtinfo.so.5
CLANG_TARBALL_NAME = 'clang+llvm-$version-x86_64-linux-gnu-ubuntu-14.04'
elif sys.platform == 'win32':
CLANG_TARBALL_NAME = 'LLVM-$version-win64'
CLANG_TARBALL_EXT = '.exe'
from waflib.Tools.compiler_cxx import cxx_compiler
cxx_compiler['linux'] = ['clang++', 'g++']
# Creating symbolic link on Windows requires a special priviledge SeCreateSymboliclinkPrivilege,
# which an non-elevated process lacks. Starting with Windows 10 build 14972, this got relaxed
# when Developer Mode is enabled. Triggering this new behaviour requires a new flag.
SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 0x2
# Python 2 has no symlink function and Python 3's symlink function does not allow creation of
# symlinks without admin rights (even when Developer Mode is enabled) so we define our own
# symlink function when on Windows.
if sys.platform == 'win32':
kdll = ctypes.windll.kernel32
def symlink(source, link_name, target_is_directory=False):
# SYMBOLIC_LINK_FLAG_DIRECTORY: 0x1
flags = int(target_is_directory) | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
# Use unicode version (W suffix) of Windows symbolic link function and convert strings to
# unicode if Python 2 is used (strings are unicode by default in Python 3).
if sys.version_info < (3, 0):
ret = kdll.CreateSymbolicLinkW(unicode(link_name), unicode(source), flags)
else:
ret = kdll.CreateSymbolicLinkW(link_name, source, flags)
if ret == 0:
err = ctypes.WinError()
raise err
os.symlink = symlink
elif sys.version_info.major < 3:
os_symlink_bak = os.symlink
os.symlink = lambda src, dst, target_is_directory=False: os_symlink_bak(src, dst)
# There is a null pointer dereference issue in tools/libclang/CXIndexDataConsumer.cpp handleReference.
def patch_byte_in_libclang(filename, offset, old, new):
with open(filename, 'rb+') as f:
f.seek(offset)
t = f.read(1)
if t == old:
f.seek(offset)
f.write(new)
print('Applied byte patch hack at 0x{:x}'.format(offset))
print('This is a makeshift for indexing default arguments of template template parameters, which otherwise would crash libclang')
print('See https://github.com/jacobdufault/cquery/issues/219 for details')
else:
assert t == new
def options(opt):
opt.load('compiler_cxx')
grp = opt.add_option_group('Configuration options related to use of clang from the system (not recommended)')
grp.add_option('--enable-assert', action='store_true')
grp.add_option('--use-clang-cxx', dest='use_clang_cxx', default=False, action='store_true',
help='use clang C++ API')
grp.add_option('--bundled-clang', dest='bundled_clang', default='6.0.0',
help='bundled clang version, downloaded from https://releases.llvm.org/ , e.g. 5.0.1 6.0.0')
grp.add_option('--llvm-config', dest='llvm_config',
help='path to llvm-config to use system libclang, e.g. llvm-config llvm-config-6.0')
grp.add_option('--clang-prefix', dest='clang_prefix',
help='enable fallback configuration method by specifying a clang installation prefix (e.g. /opt/llvm)')
grp.add_option('--variant', default='release',
help='variant name for saving configuration and build results. Variants other than "debug" turn on -O3')
def download_and_extract(destdir, url, ext):
dest = destdir + ext
# Extract the tarball.
if not os.path.isdir(os.path.join(destdir, 'include')):
# Download and save the compressed tarball as |compressed_file_name|.
if not os.path.isfile(dest):
print('Downloading tarball')
print(' destination: {0}'.format(dest))
print(' source: {0}'.format(url))
# TODO: verify checksum
response = urlopen(url, timeout=60)
with open(dest, 'wb') as f:
f.write(response.read())
else:
print('Found tarball at {0}'.format(dest))
print('Extracting {0}'.format(dest))
# TODO: make portable.
if ext == '.exe':
subprocess.call(['7z', 'x', '-o{0}'.format(destdir), '-xr!$PLUGINSDIR', dest])
else:
subprocess.call(['tar', '-x', '-C', out, '-f', dest])
# TODO Remove after migrating to a clang release newer than 5.0.1
# For 5.0.1 Mac OS X, the directory and the tarball have different name
if destdir == 'build/clang+llvm-5.0.1-x86_64-apple-darwin':
os.rename(destdir.replace('5.0.1', '5.0.1-final'), destdir)
# TODO Byte patch hack for other prebuilt binaries
elif destdir == 'build/clang+llvm-4.0.0-x86_64-linux-gnu-ubuntu-14.04':
patch_byte_in_libclang(os.path.join(destdir, 'lib/libclang.so.4.0'),
0x4172b5, b'\x48', b'\x4d')
elif destdir == 'build/clang+llvm-5.0.1-x86_64-linux-gnu-ubuntu-14.04':
patch_byte_in_libclang(os.path.join(destdir, 'lib/libclang.so.5.0'),
0x47aece, b'\x48', b'\x4d')
else:
print('Found extracted at {0}'.format(destdir))
def copy_or_symlink(src, dst):
print('copy_or_symlink src={0}, dst={1}'.format(src, dst))
try:
os.makedirs(os.path.dirname(dst))
except OSError:
pass
try:
os.symlink(src, dst)
except (OSError, NotImplementedError):
shutil.copy(src, dst)
def configure(ctx):
ctx.resetenv(ctx.options.variant)
ctx.load('compiler_cxx')
if ctx.env.CXX_NAME == 'msvc':
# /Zi: -g, /WX: -Werror, /W3: roughly -Wall, there is no -std=c++11 equivalent in MSVC.
# /wd4722: ignores warning C4722 (destructor never returns) in loguru
# /wd4267: ignores warning C4267 (conversion from 'size_t' to 'type'), roughly -Wno-sign-compare
# /MD: use multithread c library from DLL
cxxflags = ['/nologo', '/FS', '/EHsc', '/Zi', '/W3', '/wd4996', '/wd4722', '/wd4267', '/wd4800', '/MD']
ldflags = []
if 'release' in ctx.options.variant:
cxxflags.append('/O2') # There is no O3
else:
cxxflags += ['/Zi', '/FS']
ldflags += ['/DEBUG']
else:
# So that dladdr() called in loguru.hpp gives symbol names in main executable
ldflags = ['-rdynamic']
if ctx.env.CXXFLAGS:
cxxflags = ctx.env.CXXFLAGS
else:
cxxflags = ['-g', '-Wall', '-Wno-sign-compare']
if ctx.env.CXX_NAME == 'gcc':
cxxflags.append('-Wno-return-type')
# Otherwise (void)write(...) => -Werror=unused-result
cxxflags.append('-Wno-unused-result')
if all(not x.startswith('-std=') for x in ctx.env.CXXFLAGS):
cxxflags.append('-std=c++14')
if ctx.options.use_clang_cxx:
# include/clang/Format/Format.h error: multi-line comment
cxxflags.append('-Wno-comment')
# Without -fno-rtti, some Clang C++ functions may report `undefined references to typeinfo`
cxxflags.append('-fno-rtti')
if 'asan' in ctx.options.variant:
cxxflags.append('-fsanitize=address,undefined')
ldflags.append('-fsanitize=address,undefined')
if 'release' in ctx.options.variant:
cxxflags.append('-O' if 'asan' in ctx.options.variant else '-O3')
if ctx.options.enable_assert is None:
if 'debug' in ctx.options.variant:
ctx.options.enable_assert = True
if not ctx.options.enable_assert:
ctx.define('NDEBUG', 1)
if ctx.env.CXX_NAME == 'clang' and 'debug' in ctx.options.variant:
cxxflags.append('-fno-limit-debug-info')
ctx.env.CXXFLAGS = cxxflags
if not ctx.env.LDFLAGS:
ctx.env.LDFLAGS = ldflags
ctx.check(header_name='stdio.h', features='cxx cxxprogram', mandatory=True)
ctx.load('clang_compilation_database', tooldir='.')
ctx.env['use_clang_cxx'] = ctx.options.use_clang_cxx
ctx.env['llvm_config'] = ctx.options.llvm_config
ctx.env['bundled_clang'] = ctx.options.bundled_clang
def libname(lib):
# Newer MinGW and MSVC both wants full file name
if sys.platform == 'win32':
return 'lib' + lib
return lib
# Do not use bundled clang+llvm
if ctx.options.llvm_config is not None or ctx.options.clang_prefix is not None:
if ctx.options.llvm_config is not None:
# Ask llvm-config for cflags and ldflags
ctx.find_program(ctx.options.llvm_config, msg='checking for llvm-config', var='LLVM_CONFIG', mandatory=False)
ctx.env.rpath = [str(subprocess.check_output(
[ctx.options.llvm_config, '--libdir'],
stderr=subprocess.STDOUT).decode()).strip()]
if ctx.options.clang_prefix:
ctx.start_msg('Checking for clang prefix')
prefix = ctx.root.find_node(ctx.options.clang_prefix)
if not prefix:
raise ctx.errors.ConfigurationError('clang prefix not found: "%s"'%ctx.options.clang_prefix)
ctx.end_msg(prefix)
includes = [ n.abspath() for n in [ prefix.find_node('include') ] if n ]
libpath = [ n.abspath() for n in [ prefix.find_node(l) for l in ('lib', 'lib64')] if n ]
ctx.check_cxx(msg='Checking for library clang', lib=libname('clang'), uselib_store='clang', includes=includes, libpath=libpath)
else:
ctx.check_cfg(msg='Checking for clang flags',
path=ctx.env.LLVM_CONFIG,
package='',
uselib_store='clang',
args='--cppflags --ldflags')
# llvm-config does not provide the actual library we want so we check for it
# using the provided info so far.
ctx.check_cxx(lib=libname('clang'), uselib_store='clang', use='clang')
# Use CXX set by --check-cxx-compiler if it is "clang".
# See https://github.com/jacobdufault/cquery/issues/237
clang = ctx.env.get_flat('CXX')
if 'clang' not in clang:
# Otherwise, infer the clang executable path with llvm-config --bindir
output = str(subprocess.check_output(
[ctx.options.llvm_config, '--bindir'],
stderr=subprocess.STDOUT).decode()).strip()
clang = os.path.join(output, 'clang')
# Use the detected clang executable to infer resource directory
# Use `clang -### -xc /dev/null` instead of `clang -print-resource-dir` because the option is unavailable in 4.0.0
devnull = '/dev/null' if sys.platform != 'win32' else 'NUL'
output = str(subprocess.check_output(
[clang, '-###', '-xc', devnull],
stderr=subprocess.STDOUT).decode())
match = re.search(r'"-resource-dir" "([^"]*)"', output, re.M)
if match:
ctx.env.default_resource_directory = match.group(1)
else:
bld.fatal("Failed to found system clang resource directory.")
else:
global CLANG_TARBALL_NAME, CLANG_TARBALL_EXT
if CLANG_TARBALL_NAME is None:
sys.stderr.write('ERROR: releases.llvm.org does not provide prebuilt binary for your platform {0}\n'.format(sys.platform))
sys.exit(1)
# TODO Remove after 5.0.1 is stable
if ctx.options.bundled_clang == '5.0.0' and sys.platform.startswith('linux'):
CLANG_TARBALL_NAME = 'clang+llvm-$version-linux-x86_64-ubuntu14.04'
CLANG_TARBALL_NAME = string.Template(CLANG_TARBALL_NAME).substitute(version=ctx.options.bundled_clang)
# Directory clang has been extracted to.
CLANG_DIRECTORY = '{0}/{1}'.format(out, CLANG_TARBALL_NAME)
# URL of the tarball to download.
CLANG_TARBALL_URL = 'http://releases.llvm.org/{0}/{1}{2}'.format(ctx.options.bundled_clang, CLANG_TARBALL_NAME, CLANG_TARBALL_EXT)
print('Checking for clang')
download_and_extract(CLANG_DIRECTORY, CLANG_TARBALL_URL, CLANG_TARBALL_EXT)
bundled_clang_dir = os.path.join(out, ctx.options.variant, 'lib', CLANG_TARBALL_NAME)
try:
os.makedirs(os.path.dirname(bundled_clang_dir))
except OSError:
pass
clang_dir = os.path.normpath('../../' + CLANG_TARBALL_NAME)
try:
if not os.path.exists(bundled_clang_dir):
os.symlink(clang_dir, bundled_clang_dir, target_is_directory=True)
except (OSError, NotImplementedError):
# Copying the whole directory instead.
print ('shutil.copytree({0}, {1})'.format(os.path.join(out, CLANG_TARBALL_NAME), bundled_clang_dir))
try:
shutil.copytree(os.path.join(out, CLANG_TARBALL_NAME), bundled_clang_dir)
except Exception as e:
print('Failed to copy tree, ', e)
clang_node = ctx.path.find_dir(bundled_clang_dir)
ctx.check_cxx(uselib_store='clang',
includes=clang_node.find_dir('include').abspath(),
libpath=clang_node.find_dir('lib').abspath(),
lib=libname('clang'))
clang_tarball_name = os.path.basename(os.path.dirname(ctx.env['LIBPATH_clang'][0]))
ctx.env.clang_tarball_name = clang_tarball_name
ctx.env.default_resource_directory = '../lib/{}/lib/clang/{}'.format(clang_tarball_name, ctx.env.bundled_clang)
if sys.platform.startswith('freebsd') or sys.platform.startswith('linux'):
ctx.env.rpath = ['$ORIGIN/../lib/' + clang_tarball_name + '/lib']
elif sys.platform == 'darwin':
ctx.env.rpath = ['@loader_path/../lib/' + clang_tarball_name + '/lib']
elif sys.platform == 'win32':
# Poor Windows users' RPATH - copy libclang.lib and libclang.dll to the build directory.
ctx.env.rpath = [] # Unsupported
clang_dir = os.path.dirname(ctx.env['LIBPATH_clang'][0])
dest_dir = os.path.join(ctx.path.get_bld().abspath(), ctx.options.variant, 'bin')
# copy_or_symlink(os.path.join(clang_dir, 'lib', 'libclang.lib'), os.path.join(dest_dir, 'libclang.lib'))
copy_or_symlink(os.path.join(clang_dir, 'bin', 'libclang.dll'), os.path.join(dest_dir, 'libclang.dll'))
else:
ctx.env.rpath = ctx.env['LIBPATH_clang']
ctx.msg('Clang includes', ctx.env.INCLUDES_clang)
ctx.msg('Clang library dir', ctx.env.LIBPATH_clang)
def build(bld):
cc_files = bld.path.ant_glob(['src/*.cc', 'src/messages/*.cc', 'third_party/*.cc'])
if bld.env['use_clang_cxx']:
cc_files += bld.path.ant_glob(['src/clang_cxx/*.cc'])
lib = []
if sys.platform.startswith('linux'):
# For __atomic_* when lock free instructions are unavailable
# (either through hardware or OS support)
lib.append('pthread')
# loguru calls dladdr
lib.append('dl')
elif sys.platform.startswith('freebsd'):
# loguru::stacktrace_as_stdstring calls backtrace_symbols
lib.append('execinfo')
# sparsepp/spp_memory.h uses libkvm
lib.append('kvm')
lib.append('pthread')
lib.append('thr')
elif sys.platform == 'darwin':
lib.append('pthread')
elif sys.platform == 'msys':
lib.append('psapi') # GetProcessMemoryInfo
if bld.env['use_clang_cxx']:
# -fno-rtti is required for object files using clang/llvm C++ API
# The order is derived by topological sorting LINK_LIBS in clang/lib/*/CMakeLists.txt
lib.append('clangFormat')
lib.append('clangToolingCore')
lib.append('clangRewrite')
lib.append('clangAST')
lib.append('clangLex')
lib.append('clangBasic')
# The order is derived from llvm-config --libs core
lib.append('LLVMCore')
lib.append('LLVMBinaryFormat')
lib.append('LLVMSupport')
lib.append('LLVMDemangle')
lib.append('ncurses')
# https://waf.io/apidocs/tools/c_aliases.html#waflib.Tools.c_aliases.program
bld.program(
source=cc_files,
use=['clang'],
includes=[
'src/',
'third_party/',
'third_party/doctest/',
'third_party/loguru/',
'third_party/msgpack-c/include',
'third_party/rapidjson/include/',
'third_party/sparsepp/'] +
(['libclang'] if bld.env['use_clang_cxx'] else []),
defines=[
'LOGURU_WITH_STREAMS=1',
'LOGURU_FILENAME_WIDTH=18',
'LOGURU_THREADNAME_WIDTH=13',
'DEFAULT_RESOURCE_DIRECTORY="' + bld.env.get_flat('default_resource_directory') + '"'] +
(['USE_CLANG_CXX=1', 'LOGURU_RTTI=0']
if bld.env['use_clang_cxx']
else []),
lib=lib,
rpath=bld.env.rpath,
target='bin/cquery')
if bld.cmd == 'install' and 'clang_tarball_name' in bld.env:
clang_tarball_name = bld.env.clang_tarball_name
if sys.platform != 'win32':
bld.install_files('${PREFIX}/lib/' + clang_tarball_name + '/lib', bld.path.get_bld().ant_glob('lib/' + clang_tarball_name + '/lib/libclang.(dylib|so.[4-9])', quiet=True))
# TODO This may be cached and cannot be re-triggered. Use proper shell escape.
bld(rule='rsync -rtR {}/./lib/{}/lib/clang/*/include {}/'.format(bld.path.get_bld(), clang_tarball_name, bld.env['PREFIX']))
def init(ctx):
from waflib.Build import BuildContext, CleanContext, InstallContext, UninstallContext
for y in (BuildContext, CleanContext, InstallContext, UninstallContext):
class tmp(y):
variant = ctx.options.variant
# This is needed because waf initializes the ConfigurationContext with
# an arbitrary setenv('') which would rewrite the previous configuration
# cache for the default variant if the configure step finishes.
# Ideally ConfigurationContext should just let us override this at class
# level like the other Context subclasses do with variant
from waflib.Configure import ConfigurationContext
class cctx(ConfigurationContext):
def resetenv(self, name):
self.all_envs = {}
self.setenv(name)