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
debug
release
/compile_commands.json

View File

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

View File

@ -1,6 +1,7 @@
# 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)
* 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
#include "clang_index.h"
#include "clang_cursor.h"
#include "clang_translation_unit.h"
#include "lru_cache.h"
#include "lsp_completion.h"

View File

@ -2,10 +2,10 @@
#include "clang_utils.h"
#include <assert.h>
#include <string.h>
#include <algorithm>
#include <cassert>
#include <mutex>
Range ResolveCXSourceRange(const CXSourceRange& range, CXFile* cx_file) {
CXSourceLocation start = clang_getRangeStart(range);
@ -284,3 +284,22 @@ NtString ClangCursor::get_comments() const {
std::string ClangCursor::ToString() const {
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
// 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,
decl->semanticContainer, !decl->isRedeclaration, db, param);
// FIXME https://github.com/jacobdufault/ccls/issues/239
var->def.kind = GetSymbolKind(decl->entityInfo->kind);
if (var->def.kind == lsSymbolKind::Variable &&
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
// 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 <
kMaxLinesDisplayTypeAliasDeclarations) {
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
// ranges.
//
@ -2328,56 +2324,6 @@ void IndexInit() {
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() {
return ToString(clang_getClangVersion());
}

View File

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

View File

@ -2,8 +2,6 @@
#include "platform.h"
#include <doctest/doctest.h>
namespace {
lsRange GetLsRangeForFixIt(const CXSourceRange& range) {
@ -106,31 +104,6 @@ std::optional<lsDiagnostic> BuildAndDisposeDiagnostic(CXDiagnostic 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) {
CXString cx_name = clang_getFileName(file);
std::string name = ToString(cx_name);
@ -172,60 +145,9 @@ const char* ClangBuiltinTypeName(CXTypeKind kind) {
case CXType_Double: return "double";
case CXType_LongDouble: return "long double";
case CXType_Float128: return "__float128";
#if CINDEX_VERSION_MINOR >= 43
case CXType_Half: return "_Float16";
#endif
case CXType_NullPtr: return "nullptr";
default: return "";
// 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 <clang-c/Index.h>
#if USE_CLANG_CXX
#include <clang/Format/Format.h>
#endif
#include <optional>
#include <optional>
#include <vector>
std::optional<lsDiagnostic> BuildAndDisposeDiagnostic(CXDiagnostic diagnostic,
@ -21,10 +18,3 @@ std::string ToString(CXString cx_string);
std::string ToString(CXCursorKind cursor_kind);
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_utils.h"
#include "queue_manager.h"
#include "recorder.h"
#include "semantic_highlight_symbol_cache.h"
#include "serializer.h"
#include "serializers/json.h"
#include "test.h"
@ -43,19 +41,10 @@
#include <unordered_map>
#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;
namespace {
std::vector<std::string> kEmptyArgs;
// This function returns true if e2e timing should be displayed for the given
// MethodId.
bool ShouldDisplayMethodTiming(MethodType type) {
@ -70,9 +59,6 @@ void PrintHelp() {
<< R"help(ccls is a low-latency C/C++/Objective-C language server.
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-index <opt_filter_path>
Run index tests. opt_filter_path can be used to specify which
@ -85,37 +71,20 @@ Other command line options:
--init <initializationOptions>
Override client provided 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-append <path> Like --log-file, but appending
--log-all-to-stderr Write all log messages to STDERR.
--wait-for-input Wait for an '[Enter]' before exiting
--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/cquery-project/cquery/wiki
See more on https://github.com/MaskRay/ccls/wiki
)help";
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// QUERYDB MAIN ////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
@ -248,20 +217,6 @@ void RunQueryDbThread(const std::string& bin_name,
}
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// STDIN MAIN //////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
@ -333,8 +288,6 @@ void LaunchStdoutThread(std::unordered_map<MethodType, Timer>* request_times,
time.ResetAndPrint("[e2e] Running " + std::string(message.method));
}
RecordOutput(message.content);
fwrite(message.content.c_str(), message.content.size(), 1, stdout);
fflush(stdout);
}
@ -360,23 +313,6 @@ void LanguageServerMain(const std::string& bin_name,
RunQueryDbThread(bin_name, config, querydb_waiter, indexer_waiter);
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// MAIN ////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
int main(int argc, char** argv) {
TraceMe();
@ -413,14 +349,6 @@ int main(int argc, char** argv) {
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")) {
language_server = false;
doctest::Context context;
@ -467,10 +395,5 @@ int main(int argc, char** argv) {
&stdout_waiter);
}
if (HasOption(options, "--wait-for-input")) {
std::cerr << std::endl << "[Enter] to exit" << std::endl;
getchar();
}
return 0;
}

View File

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

View File

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

View File

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

View File

@ -4,7 +4,6 @@
#include "project.h"
#include "query_utils.h"
#include "queue_manager.h"
#include "semantic_highlight_symbol_cache.h"
#include <loguru.hpp>
@ -42,6 +41,77 @@ struct ScanLineEvent {
};
} // 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() {
// Dynamically allocate |message_handlers|, otherwise there will be static
// initialization order races.

View File

@ -1,12 +1,14 @@
#pragma once
#include "lru_cache.h"
#include "lsp.h"
#include "match.h"
#include "method.h"
#include "query.h"
#include <optional>
#include <memory>
#include <unordered_map>
#include <vector>
struct ClangCompleteManager;
@ -20,11 +22,43 @@ struct IncludeComplete;
struct MultiQueueWaiter;
struct Project;
struct QueryDatabase;
struct SemanticHighlightSymbolCache;
struct TimestampManager;
struct WorkingFile;
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
: public lsOutMessage<Out_CclsPublishSemanticHighlighting> {
struct Symbol {

View File

@ -6,7 +6,6 @@
#include "platform.h"
#include "project.h"
#include "queue_manager.h"
#include "semantic_highlight_symbol_cache.h"
#include "serializers/json.h"
#include "timer.h"
#include "work_thread.h"
@ -361,26 +360,13 @@ MAKE_REFLECT_STRUCT(lsTextDocumentClientCapabilities,
struct lsClientCapabilities {
// Workspace specific client capabilities.
std::optional<lsWorkspaceClientCapabilites> workspace;
lsWorkspaceClientCapabilites workspace;
// Text document specific client capabilities.
std::optional<lsTextDocumentClientCapabilities> textDocument;
/**
* Experimental client capabilities.
*/
// experimental?: any; // TODO
lsTextDocumentClientCapabilities textDocument;
};
MAKE_REFLECT_STRUCT(lsClientCapabilities, workspace, textDocument);
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
////////////////////////////// INITIALIZATION ///////////////////////////////
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
struct lsInitializeParams {
// The process Id of the parent process that started
// 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
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)
config->client.snippetSupport =
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);
if (include_complete->is_scanning)
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,
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)