From c26987659378fc7f1f48f3192205601c56d67583 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Sat, 12 May 2018 13:31:56 -0700 Subject: [PATCH] Use clang+llvm C++ in cmake; parse args with clang driver --- CMakeLists.txt | 31 ++---- cmake/FindClang.cmake | 60 ++++-------- src/messages/initialize.cc | 3 +- src/platform.h | 9 +- src/platform_posix.cc | 21 ++-- src/platform_win.cc | 30 ------ src/project.cc | 193 +++++++++++-------------------------- 7 files changed, 97 insertions(+), 250 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 84334a9a..2e62c8f0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -86,10 +86,12 @@ if(NOT SYSTEM_CLANG) if(${CMAKE_CXX_COMPILER_ID} STREQUAL Clang AND CLANG_USE_BUNDLED_LIBC++) message(STATUS "Using bundled libc++") target_compile_options(ccls PRIVATE -nostdinc++ -cxx-isystem ${CLANG_ROOT}/include/c++/v1) - target_link_libraries(ccls PRIVATE -stdlib=libc++ -L${CLANG_ROOT}/lib -lc++experimental) if(${CMAKE_SYSTEM_NAME} STREQUAL Linux) + # Don't use -stdlib=libc++ because while ccls is linked with libc++, bundled clang+llvm require libstdc++ + target_link_libraries(ccls PRIVATE -L${CLANG_ROOT}/lib c++ c++experimental c++abi) + else() # FreeBSD uses system libcxxrt.a and does not need libc++abi. - target_link_libraries(ccls PRIVATE c++abi) + target_link_libraries(ccls PRIVATE -stdlib=libc++ -L${CLANG_ROOT}/lib c++experimental) endif() endif() @@ -101,7 +103,11 @@ endif() # See cmake/FindClang.cmake find_package(Clang ${CLANG_VERSION} REQUIRED) +find_package(Curses REQUIRED) target_link_libraries(ccls PRIVATE Clang::Clang) +if(NOT ${CMAKE_SYSTEM_NAME} STREQUAL Windows) + target_link_libraries(ccls PRIVATE Clang::Clang ${CURSES_LIBRARIES}) +endif() # Enable threading support set(THREADS_PREFER_PTHREAD_FLAG ON) @@ -188,27 +194,6 @@ endif() file(GLOB SOURCES src/*.cc src/*.h src/serializers/*.cc src/serializers/*.h src/messages/*.h src/messages/*.cc) -if(Clang_FORMAT AND ${Clang_VERSION} STREQUAL 6.0.0) - add_custom_target(format - COMMAND ${Clang_FORMAT} -i ${SOURCES} - # .clang-format is located in the ccls root project dir - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - COMMENT "Running clang-format ...") -else() - # Set error message depending on which condition was false - if (NOT Clang_FORMAT) - set(Clang_FORMAT_ERROR "Error: clang-format executable not found") - elseif(NOT ${Clang_VERSION} STREQUAL 6.0.0) - set(Clang_FORMAT_ERROR "Error: clang-format version does not match \ -6.0.0. Due to differences in clang-format output between versions we only \ -support clang-format 6.0.0") - endif() - - add_custom_target(format - COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --red --bold - ${Clang_FORMAT_ERROR}) -endif() - ### Sources target_sources(ccls PRIVATE third_party/siphash.cc) diff --git a/cmake/FindClang.cmake b/cmake/FindClang.cmake index f21fb57b..ab81ad61 100644 --- a/cmake/FindClang.cmake +++ b/cmake/FindClang.cmake @@ -8,7 +8,6 @@ # # Clang_FOUND - True if headers and requested libraries were found # Clang_EXECUTABLE - Clang executable -# Clang_FORMAT - Clang-format executable # Clang_RESOURCE_DIR - Clang resource directory # Clang_VERSION - Clang version as reported by Clang executable # @@ -19,7 +18,6 @@ # This module reads hints about which libraries to look for and where to find # them from the following variables:: # -# CLANG_CXX - Search for and add Clang C++ libraries # CLANG_ROOT - If set, only look for Clang components in CLANG_ROOT # # Example to link against Clang target:: @@ -41,6 +39,11 @@ macro(_Clang_find_library VAR NAME) endif() endmacro() +macro(_Clang_find_add_library NAME) + _Clang_find_library(${NAME}_LIBRARY ${NAME}) + list(APPEND _Clang_LIBRARIES ${${NAME}_LIBRARY}) +endmacro() + macro(_Clang_find_path VAR INCLUDE_FILE) if (CLANG_ROOT) find_path(${VAR} ${INCLUDE_FILE} @@ -59,45 +62,22 @@ macro(_Clang_find_program VAR NAME) endif() endmacro() -# Macro to avoid duplicating logic for each Clang C++ library -macro(_Clang_find_and_add_cxx_lib NAME INCLUDE_FILE) - # Find library - _Clang_find_library(Clang_${NAME}_LIBRARY ${NAME}) - list(APPEND _Clang_REQUIRED_VARS Clang_${NAME}_LIBRARY) - list(APPEND _Clang_CXX_LIBRARIES ${Clang_${NAME}_LIBRARY}) - - # Find corresponding include directory - _Clang_find_path(Clang_${NAME}_INCLUDE_DIR ${INCLUDE_FILE}) - list(APPEND _Clang_REQUIRED_VARS Clang_${NAME}_INCLUDE_DIR) - list(APPEND _Clang_CXX_INCLUDE_DIRS ${Clang_${NAME}_INCLUDE_DIR}) -endmacro() - ### Start set(_Clang_REQUIRED_VARS Clang_LIBRARY Clang_INCLUDE_DIR Clang_EXECUTABLE - Clang_RESOURCE_DIR Clang_VERSION) + Clang_RESOURCE_DIR Clang_VERSION + LLVM_INCLUDE_DIR LLVM_BUILD_INCLUDE_DIR) _Clang_find_library(Clang_LIBRARY clang) +_Clang_find_add_library(clangDriver) +_Clang_find_add_library(LLVMOption) +_Clang_find_add_library(LLVMSupport) +_Clang_find_add_library(LLVMDemangle) _Clang_find_path(Clang_INCLUDE_DIR clang-c/Index.h) +_Clang_find_path(Clang_BUILD_INCLUDE_DIR clang/Driver/Options.inc) +_Clang_find_path(LLVM_INCLUDE_DIR llvm/PassInfo.h) +_Clang_find_path(LLVM_BUILD_INCLUDE_DIR llvm/Config/llvm-config.h) -if(CLANG_CXX) - # The order is derived by topological sorting LINK_LIBS in - # clang/lib/*/CMakeLists.txt - _Clang_find_and_add_cxx_lib(clangFormat clang/Format/Format.h) - _Clang_find_and_add_cxx_lib(clangToolingCore clang/Tooling/Core/Diagnostic.h) - _Clang_find_and_add_cxx_lib(clangRewrite clang/Rewrite/Core/Rewriter.h) - _Clang_find_and_add_cxx_lib(clangAST clang/AST/AST.h) - _Clang_find_and_add_cxx_lib(clangLex clang/Lex/Lexer.h) - _Clang_find_and_add_cxx_lib(clangBasic clang/Basic/ABI.h) - - # The order is derived from llvm-config --libs core - _Clang_find_and_add_cxx_lib(LLVMCore llvm/Pass.h) - _Clang_find_and_add_cxx_lib(LLVMBinaryFormat llvm/BinaryFormat/Dwarf.h) - _Clang_find_and_add_cxx_lib(LLVMSupport llvm/Support/Error.h) - _Clang_find_and_add_cxx_lib(LLVMDemangle llvm/Demangle/Demangle.h) -endif() - -_Clang_find_program(Clang_FORMAT clang-format) _Clang_find_program(Clang_EXECUTABLE clang) if(Clang_EXECUTABLE) # Find Clang resource directory with Clang executable @@ -127,12 +107,10 @@ find_package_handle_standard_args(Clang ) if(Clang_FOUND AND NOT TARGET Clang::Clang) - set(_Clang_LIBRARIES ${Clang_LIBRARY} ${_Clang_CXX_LIBRARIES}) - set(_Clang_INCLUDE_DIRS ${Clang_INCLUDE_DIR} ${_Clang_CXX_INCLUDE_DIRS}) + add_library(Clang::Clang UNKNOWN IMPORTED) + set_target_properties(Clang::Clang PROPERTIES + IMPORTED_LOCATION ${Clang_LIBRARY} + INTERFACE_INCLUDE_DIRECTORIES "${Clang_INCLUDE_DIR};${Clang_BUILD_INCLUDE_DIR};${LLVM_INCLUDE_DIR};${LLVM_BUILD_INCLUDE_DIR}") - add_library(Clang::Clang INTERFACE IMPORTED) - set_property(TARGET Clang::Clang PROPERTY - INTERFACE_LINK_LIBRARIES ${_Clang_LIBRARIES}) - set_property(TARGET Clang::Clang PROPERTY - INTERFACE_INCLUDE_DIRECTORIES ${_Clang_INCLUDE_DIRS}) + set_property(TARGET Clang::Clang PROPERTY INTERFACE_LINK_LIBRARIES ${_Clang_LIBRARIES}) endif() diff --git a/src/messages/initialize.cc b/src/messages/initialize.cc index 40571b8f..6a91b202 100644 --- a/src/messages/initialize.cc +++ b/src/messages/initialize.cc @@ -517,7 +517,8 @@ struct Handler_Initialize : BaseMessageHandler { for (int i = 0; i < g_config->index.threads; i++) { std::thread([=]() { g_thread_id = i + 1; - SetThreadName("indexer" + std::to_string(i)); + std::string name = "indexer" + std::to_string(i); + SetThreadName(name.c_str()); Indexer_Main(diag_engine, vfs, import_pipeline_status, project, working_files, waiter); }).detach(); diff --git a/src/platform.h b/src/platform.h index b8a36bf3..adf49d12 100644 --- a/src/platform.h +++ b/src/platform.h @@ -1,10 +1,9 @@ #pragma once -#include -#include - #include +#include #include +#include #include void PlatformInit(); @@ -12,9 +11,7 @@ void PlatformInit(); std::string GetExecutablePath(); std::string NormalizePath(const std::string& path); -void SetThreadName(const std::string& thread_name); - -std::optional GetLastModificationTime(const std::string& absolute_path); +void SetThreadName(const char* name); // Free any unused memory and return it to the system. void FreeUnusedMemory(); diff --git a/src/platform_posix.cc b/src/platform_posix.cc index 6d8f5936..ac058fe5 100644 --- a/src/platform_posix.cc +++ b/src/platform_posix.cc @@ -1,9 +1,12 @@ +#include +#include + #if defined(__unix__) || defined(__APPLE__) #include "platform.h" #include "utils.h" -#include "loguru.hpp" +#include #include #if defined(__FreeBSD__) @@ -155,17 +158,6 @@ std::string NormalizePath(const std::string& path) { return resolved ? *resolved : path; } -void SetThreadName(const std::string& thread_name) { - loguru::set_thread_name(thread_name.c_str()); -#if defined(__APPLE__) - pthread_setname_np(thread_name.c_str()); -#elif defined(__FreeBSD__) || defined(__OpenBSD__) - pthread_set_name_np(pthread_self(), thread_name.c_str()); -#elif defined(__linux__) - pthread_setname_np(pthread_self(), thread_name.c_str()); -#endif -} - void FreeUnusedMemory() { #if defined(__GLIBC__) malloc_trim(0); @@ -225,3 +217,8 @@ std::string GetExternalCommandOutput(const std::vector& command, } #endif + +void SetThreadName(const char* name) { + loguru::set_thread_name(name); + llvm::set_thread_name(name); +} diff --git a/src/platform_win.cc b/src/platform_win.cc index 471c73be..fdd56aa4 100644 --- a/src/platform_win.cc +++ b/src/platform_win.cc @@ -49,36 +49,6 @@ std::string NormalizePath(const std::string& path) { return result; } -// See https://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx -const DWORD MS_VC_EXCEPTION = 0x406D1388; -#pragma pack(push, 8) -typedef struct tagTHREADNAME_INFO { - DWORD dwType; // Must be 0x1000. - LPCSTR szName; // Pointer to name (in user addr space). - DWORD dwThreadID; // Thread ID (-1=caller thread). - DWORD dwFlags; // Reserved for future use, must be zero. -} THREADNAME_INFO; -#pragma pack(pop) -void SetThreadName(const std::string& thread_name) { - loguru::set_thread_name(thread_name.c_str()); - - THREADNAME_INFO info; - info.dwType = 0x1000; - info.szName = thread_name.c_str(); - info.dwThreadID = (DWORD)-1; - info.dwFlags = 0; - - __try { - RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), - (ULONG_PTR*)&info); -#ifdef _MSC_VER - } __except (EXCEPTION_EXECUTE_HANDLER) { -#else - } catch (...) { -#endif - } -} - void FreeUnusedMemory() {} // TODO Wait for debugger to attach diff --git a/src/project.cc b/src/project.cc index 77a8273d..67c7f0a0 100644 --- a/src/project.cc +++ b/src/project.cc @@ -12,6 +12,14 @@ #include "utils.h" #include "working_files.h" +#include +#include +#include +#include +using namespace clang; +using namespace llvm; +using namespace llvm::opt; + #include #include #include @@ -52,45 +60,13 @@ struct ProjectConfig { ProjectMode mode = ProjectMode::CompileCommandsJson; }; -// TODO: See -// https://github.com/Valloric/ycmd/blob/master/ycmd/completers/cpp/flags.py. -// Flags '-include' and '-include-pch' are blacklisted here cause libclang returns error in case when -// precompiled header was generated by a different compiler (even two different builds of same version -// of clang for the same platform are incompatible). Note that libclang always generate it's own pch -// internally. For details, see https://github.com/Valloric/ycmd/issues/892 . -std::vector kBlacklistMulti = { - "-MF", "-MT", "-MQ", "-o", "--serialize-diagnostics", "-Xclang"}; - -// Blacklisted flags which are always removed from the command line. -std::vector kBlacklist = { - "-c", "-MP", "-MD", "-MMD", "--fcolor-diagnostics", "-showIncludes" +enum OptionClass { + EqOrJoinOrSep, + EqOrSep, + JoinOrSep, + Separate, }; -// Arguments which are followed by a potentially relative path. We need to make -// all relative paths absolute, otherwise libclang will not resolve them. -std::vector kPathArgs = { - "-I", "-iquote", "-cxx-isystem", "-isystem", "--sysroot=", - "-isysroot", "-gcc-toolchain", "-include-pch", "-iframework", - "-F", "-imacros", "-include", "/I", - "-idirafter"}; - -// Arguments which always require an absolute path, ie, clang -working-directory -// does not work as expected. Argument processing assumes that this is a subset -// of kPathArgs. -std::vector kNormalizePathArgs = {"--sysroot="}; - -// Arguments whose path arguments should be injected into include dir lookup -// for #include completion. -std::vector kQuoteIncludeArgs = {"-iquote", "-I", "/I"}; -std::vector kAngleIncludeArgs = {"-cxx-isystem", "-isystem", "-I", "/I"}; - -bool ShouldAddToQuoteIncludes(const std::string& arg) { - return StartsWithAny(arg, kQuoteIncludeArgs); -} -bool ShouldAddToAngleIncludes(const std::string& arg) { - return StartsWithAny(arg, kAngleIncludeArgs); -} - Project::Entry GetCompilationEntryFromCompileCommandEntry( ProjectConfig* config, const CompileCommandsEntry& entry) { @@ -136,109 +112,51 @@ Project::Entry GetCompilationEntryFromCompileCommandEntry( // Compiler driver. result.args.push_back(args[0]); - // Add -working-directory if not provided. - if (!AnyStartsWith(args, "-working-directory")) - result.args.emplace_back("-working-directory=" + entry.directory.string()); + std::unique_ptr Opts = driver::createDriverOptTable(); + unsigned MissingArgIndex, MissingArgCount; + std::vector cargs; + for (auto& arg : args) + cargs.push_back(arg.c_str()); + InputArgList Args = + Opts->ParseArgs(makeArrayRef(cargs), MissingArgIndex, MissingArgCount, + driver::options::CC1Option); - bool next_flag_is_path = false; - bool add_next_flag_to_quote_dirs = false; - bool add_next_flag_to_angle_dirs = false; + using namespace clang::driver::options; + for (const auto* A : + Args.filtered(OPT_I, OPT_c_isystem, OPT_cxx_isystem, OPT_isystem)) + config->angle_dirs.insert(entry.ResolveIfRelative(A->getValue())); + for (const auto* A : Args.filtered(OPT_I, OPT_iquote)) + config->quote_dirs.insert(entry.ResolveIfRelative(A->getValue())); - // Note that when processing paths, some arguments support multiple forms, ie, - // {"-Ifoo"} or {"-I", "foo"}. Support both styles. - - size_t i = 1; - result.args.reserve(args.size() + config->extra_flags.size()); - for (; i < args.size(); ++i) { - std::string arg = args[i]; - - // Finish processing path for the previous argument, which was a switch. - // {"-I", "foo"} style. - if (next_flag_is_path) { - std::string normalized_arg = entry.ResolveIfRelative(arg); - if (add_next_flag_to_quote_dirs) - config->quote_dirs.insert(normalized_arg); - if (add_next_flag_to_angle_dirs) - config->angle_dirs.insert(normalized_arg); - if (clang_cl) - arg = normalized_arg; - - next_flag_is_path = false; - add_next_flag_to_quote_dirs = false; - add_next_flag_to_angle_dirs = false; - } else { - // If blacklist skip. - if (StartsWithAny(arg, kBlacklistMulti)) { - i++; - continue; - } - - // Check to see if arg is a path and needs to be updated. - for (const std::string& flag_type : kPathArgs) { - // {"-I", "foo"} style. - if (arg == flag_type) { - next_flag_is_path = true; - add_next_flag_to_quote_dirs = ShouldAddToQuoteIncludes(arg); - add_next_flag_to_angle_dirs = ShouldAddToAngleIncludes(arg); - goto done; - } - - // {"-Ifoo"} style. - if (StartsWith(arg, flag_type)) { - std::string path = arg.substr(flag_type.size()); - assert(!path.empty()); - path = entry.ResolveIfRelative(path); - if (clang_cl || StartsWithAny(arg, kNormalizePathArgs)) - arg = flag_type + path; - if (ShouldAddToQuoteIncludes(flag_type)) - config->quote_dirs.insert(path); - if (ShouldAddToAngleIncludes(flag_type)) - config->angle_dirs.insert(path); - goto done; - } - } - - if (StartsWithAny(arg, kBlacklist)) - continue; - - // This is most likely the file path we will be passing to clang. The - // path needs to be absolute, otherwise clang_codeCompleteAt is extremely - // slow. See - // https://github.com/cquery-project/cquery/commit/af63df09d57d765ce12d40007bf56302a0446678. - if (EndsWith(arg, base_name)) - arg = entry.ResolveIfRelative(arg); - // TODO Exclude .a .o to make link command in compile_commands.json work. - // Also, clang_parseTranslationUnit2FullArgv does not seem to accept - // multiple source filenames. - else if (EndsWith(arg, ".a") || EndsWith(arg, ".o")) - continue; + for (size_t i = 1; i < args.size(); i++) + // This is most likely the file path we will be passing to clang. The + // path needs to be absolute, otherwise clang_codeCompleteAt is extremely + // slow. See + // https://github.com/cquery-project/cquery/commit/af63df09d57d765ce12d40007bf56302a0446678. + if (args[i][0] != '-' && EndsWith(args[i], base_name)) { + args[i] = entry.ResolveIfRelative(args[i]); + continue; } - done: - result.args.push_back(arg); - } - // We don't do any special processing on user-given extra flags. for (const auto& flag : config->extra_flags) - result.args.push_back(flag); + args.push_back(flag); - // Add -resource-dir so clang can correctly resolve system includes like - // - if (!AnyStartsWith(result.args, "-resource-dir")) - result.args.push_back("-resource-dir=" + g_config->clang.resourceDir); + if (!Args.hasArg(OPT_resource_dir)) + args.push_back("-resource-dir=" + g_config->clang.resourceDir); + if (!Args.hasArg(OPT_working_directory)) + args.push_back("-working-directory=" + entry.directory.string()); // There could be a clang version mismatch between what the project uses and // what ccls uses. Make sure we do not emit warnings for mismatched options. - if (!AnyStartsWith(result.args, "-Wno-unknown-warning-option")) - result.args.push_back("-Wno-unknown-warning-option"); + args.push_back("-Wno-unknown-warning-option"); // Using -fparse-all-comments enables documentation in the indexer and in // code completion. - if (g_config->index.comments > 1 && - !AnyStartsWith(result.args, "-fparse-all-comments")) { - result.args.push_back("-fparse-all-comments"); - } + if (g_config->index.comments > 1) + args.push_back("-fparse-all-comments"); + result.args = std::move(args); return result; } @@ -601,15 +519,15 @@ TEST_SUITE("Project") { CheckFlags( /* raw */ {"clang", "-lstdc++", "myfile.cc"}, /* expected */ - {"clang", "-working-directory=/dir/", "-lstdc++", "/dir/myfile.cc", - "-resource-dir=/w/resource_dir/", "-Wno-unknown-warning-option", - "-fparse-all-comments"}); + {"clang", "-lstdc++", "/dir/myfile.cc", + "-resource-dir=/w/resource_dir/", "-working-directory=/dir/", + "-Wno-unknown-warning-option", "-fparse-all-comments"}); CheckFlags( /* raw */ {"clang.exe"}, /* expected */ - {"clang.exe", "-working-directory=/dir/", - "-resource-dir=/w/resource_dir/", "-Wno-unknown-warning-option", + {"clang.exe", "-resource-dir=/w/resource_dir/", + "-working-directory=/dir/", "-Wno-unknown-warning-option", "-fparse-all-comments"}); } @@ -648,12 +566,13 @@ TEST_SUITE("Project") { #endif TEST_CASE("Path in args") { - CheckFlags("/home/user", "/home/user/foo/bar.c", - /* raw */ {"cc", "-O0", "foo/bar.c"}, - /* expected */ - {"cc", "-working-directory=/home/user", "-O0", - "/home/user/foo/bar.c", "-resource-dir=/w/resource_dir/", - "-Wno-unknown-warning-option", "-fparse-all-comments"}); + CheckFlags( + "/home/user", "/home/user/foo/bar.c", + /* raw */ {"cc", "-O0", "foo/bar.c"}, + /* expected */ + {"cc", "-O0", "/home/user/foo/bar.c", "-resource-dir=/w/resource_dir/", + "-working-directory=/home/user", "-Wno-unknown-warning-option", + "-fparse-all-comments"}); } TEST_CASE("Directory extraction") {