From d14965b24e4a11b70f97829838b3fa5b2f096c6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Tue, 6 Jun 2017 17:23:48 +0200 Subject: [PATCH 1/8] Add basic compilation database parsing --- include/cppast/libclang_parser.hpp | 68 ++++++++++++--- src/libclang/libclang_parser.cpp | 135 +++++++++++++++++++++++++++++ test/CMakeLists.txt | 39 +++++---- test/libclang_parser.cpp | 70 +++++++++++++++ 4 files changed, 283 insertions(+), 29 deletions(-) create mode 100644 test/libclang_parser.cpp diff --git a/include/cppast/libclang_parser.hpp b/include/cppast/libclang_parser.hpp index 87f590d..a5f24ef 100644 --- a/include/cppast/libclang_parser.hpp +++ b/include/cppast/libclang_parser.hpp @@ -25,6 +25,50 @@ namespace cppast }; } // namespace detail + /// The exception thrown when a fatal parse error occurs. + class libclang_error final : public std::runtime_error + { + public: + /// \effects Creates it with a message. + libclang_error(std::string msg) : std::runtime_error(std::move(msg)) + { + } + }; + + /// A compilation database. + /// + /// This represents a `compile_commands.json` file, + /// which stores all the commands needed to compile a set of files. + /// It can be generated by CMake using the `CMAKE_EXPORT_COMPILE_COMMANDS` option. + class libclang_compilation_database + { + public: + /// \effects Creates it giving the directory where the `compile_commands.json` file is located. + /// \throws `libclang_error` if the database could not be loaded or found. + libclang_compilation_database(const std::string& build_directory); + + libclang_compilation_database(libclang_compilation_database&& other) + : database_(other.database_) + { + other.database_ = nullptr; + } + + ~libclang_compilation_database(); + + libclang_compilation_database& operator=(libclang_compilation_database&& other) + { + libclang_compilation_database tmp(std::move(other)); + std::swap(tmp.database_, database_); + return *this; + } + + private: + using database = void*; + database database_; + + friend libclang_compile_config; + }; + /// Compilation config for the [cppast::libclang_parser](). class libclang_compile_config final : public compile_config { @@ -36,6 +80,20 @@ namespace cppast /// It will also define `__cppast__` with the value `"libclang"` as well as `__cppast_major__` and `__cppast_minor__`. libclang_compile_config(); + /// Creates the configuration stored in the database. + /// + /// \effects It will use the options found in the database for the specified file. + /// This does not necessarily need to match the file that is going to be parsed, + /// but it should. + /// It will also add the default configuration options. + /// \notes Header files are not included in the compilation database, + /// you need to pass in the file name of the corresponding source file, + /// if you want to parse one. + /// \notes It will only consider options you could also set by the other functions. + /// \notes The file key will include the specified directory in the JSON, if it is not a full path. + libclang_compile_config(const libclang_compilation_database& database, + const std::string& file); + /// \effects Sets the path to the location of the `clang++` binary and the version of that binary. /// \notes It will be used for preprocessing. void set_clang_binary(std::string binary, int major, int minor, int patch) @@ -64,16 +122,6 @@ namespace cppast friend detail::libclang_compile_config_access; }; - /// The exception thrown when a fatal parse error occurs. - class libclang_error final : public std::runtime_error - { - public: - /// \effects Creates it with a message. - libclang_error(std::string msg) : std::runtime_error(std::move(msg)) - { - } - }; - /// A parser that uses libclang. class libclang_parser final : public parser { diff --git a/src/libclang/libclang_parser.cpp b/src/libclang/libclang_parser.cpp index 4718e97..44bcb85 100644 --- a/src/libclang/libclang_parser.cpp +++ b/src/libclang/libclang_parser.cpp @@ -7,6 +7,8 @@ #include #include +#include + #include "libclang_visitor.hpp" #include "raii_wrapper.hpp" #include "parse_error.hpp" @@ -33,6 +35,22 @@ const std::vector& detail::libclang_compile_config_access::flags( return config.get_flags(); } +libclang_compilation_database::libclang_compilation_database(const std::string& build_directory) +{ + static_assert(std::is_same::value, "forgot to update type"); + + auto error = CXCompilationDatabase_NoError; + database_ = clang_CompilationDatabase_fromDirectory(build_directory.c_str(), &error); + if (error != CXCompilationDatabase_NoError) + throw libclang_error("unable to load compilation database"); +} + +libclang_compilation_database::~libclang_compilation_database() +{ + if (database_) + clang_CompilationDatabase_dispose(database_); +} + namespace { int parse_number(const char*& str) @@ -65,6 +83,123 @@ libclang_compile_config::libclang_compile_config() : compile_config({}) define_macro("__cppast_version_minor__", CPPAST_VERSION_MINOR); } +namespace +{ + struct cxcompile_commands_deleter + { + void operator()(CXCompileCommands cmds) + { + clang_CompileCommands_dispose(cmds); + } + }; + + using cxcompile_commands = detail::raii_wrapper; +} + +namespace +{ + bool is_flag(const detail::cxstring& str) + { + return str.length() > 1u && str[0] == '-'; + } + + const char* find_flag_arg_sep(const std::string& last_flag) + { + if (last_flag[1] == 'D') + // no separator, equal is part of the arg + return nullptr; + return std::strchr(last_flag.c_str(), '='); + } + + template + void parse_flags(CXCompileCommand cmd, Func callback) + { + auto no_args = clang_CompileCommand_getNumArgs(cmd); + std::string last_flag; + for (auto i = 1u /* 0 is compiler executable */; i != no_args; ++i) + { + detail::cxstring str(clang_CompileCommand_getArg(cmd, i)); + if (is_flag(str)) + { + if (!last_flag.empty()) + { + // process last flag + std::string args; + if (auto ptr = find_flag_arg_sep(last_flag)) + { + auto pos = std::size_t(ptr - last_flag.c_str()); + ++ptr; + while (*ptr) + args += *ptr++; + last_flag.erase(pos); + } + else if (last_flag.size() > 2u) + { + // assume two character flag + args = last_flag.substr(2u); + last_flag.erase(2u); + } + + callback(std::move(last_flag), std::move(args)); + } + + last_flag = str.std_str(); + } + else if (!last_flag.empty()) + { + // we have flags + args + callback(std::move(last_flag), str.std_str()); + last_flag.clear(); + } + // else skip argument + } + } +} + +libclang_compile_config::libclang_compile_config(const libclang_compilation_database& database, + const std::string& file) +: libclang_compile_config() +{ + auto cxcommands = + clang_CompilationDatabase_getCompileCommands(database.database_, file.c_str()); + if (cxcommands == nullptr) + throw libclang_error(detail::format("no compile commands specified for file '", file, "'")); + cxcompile_commands commands(cxcommands); + + auto size = clang_CompileCommands_getSize(commands.get()); + for (auto i = 0u; i != size; ++i) + { + auto cmd = clang_CompileCommands_getCommand(commands.get(), i); + auto dir = detail::cxstring(clang_CompileCommand_getDirectory(cmd)); + parse_flags(cmd, [&](std::string flag, std::string args) { + if (flag == "-I") + { + if (args.front() == '/' || args.front() == '\\') + { + add_flag(std::move(flag) + std::move(args)); + } + else + { + // path relative to the directory + if (dir[dir.length() - 1] != '/' && dir[dir.length() - 1] != '\\') + add_flag(std::move(flag) + dir.std_str() + '/' + std::move(args)); + else + add_flag(std::move(flag) + dir.std_str() + std::move(args)); + } + } + else if (flag == "-D" || flag == "-U") + // preprocessor options + this->add_flag(std::move(flag) + std::move(args)); + else if (flag == "-std") + // standard + this->add_flag(std::move(flag) + "=" + std::move(args)); + else if (flag == "-f" && (args == "ms-compatibility" || args == "ms-extensions")) + // other options + this->add_flag(std::move(flag) + std::move(args)); + }); + } +} + void libclang_compile_config::do_set_flags(cpp_standard standard, compile_flags flags) { switch (standard) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a613ec6..1ac93b9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -9,25 +9,26 @@ if(NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/catch.hpp) endif() set(tests - code_generator.cpp - cpp_alias_template.cpp - cpp_class.cpp - cpp_class_template.cpp - cpp_enum.cpp - cpp_friend.cpp - cpp_function.cpp - cpp_function_template.cpp - cpp_language_linkage.cpp - cpp_member_function.cpp - cpp_member_variable.cpp - cpp_namespace.cpp - cpp_preprocessor.cpp - cpp_static_assert.cpp - cpp_template_parameter.cpp - cpp_type_alias.cpp - cpp_variable.cpp - visitor.cpp - integration.cpp) + code_generator.cpp + cpp_alias_template.cpp + cpp_class.cpp + cpp_class_template.cpp + cpp_enum.cpp + cpp_friend.cpp + cpp_function.cpp + cpp_function_template.cpp + cpp_language_linkage.cpp + cpp_member_function.cpp + cpp_member_variable.cpp + cpp_namespace.cpp + cpp_preprocessor.cpp + cpp_static_assert.cpp + cpp_template_parameter.cpp + cpp_type_alias.cpp + cpp_variable.cpp + integration.cpp + libclang_parser.cpp + visitor.cpp) add_executable(cppast_test test.cpp test_parser.hpp ${tests}) target_include_directories(cppast_test PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/test/libclang_parser.cpp b/test/libclang_parser.cpp new file mode 100644 index 0000000..718c99c --- /dev/null +++ b/test/libclang_parser.cpp @@ -0,0 +1,70 @@ +// Copyright (C) 2017 Jonathan Müller +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include + +#include + +using namespace cppast; + +libclang_compilation_database get_database(const char* json) +{ + std::ofstream file("compile_commands.json"); + file << json; + file.close(); + + return libclang_compilation_database("."); +} + +void require_flags(const libclang_compile_config& config, const char* flags) +{ + std::string result; + auto config_flags = detail::libclang_compile_config_access::flags(config); + // skip first 4, those are the default options + for (auto iter = config_flags.begin() + 4; iter != config_flags.end(); ++iter) + result += *iter + ' '; + result.pop_back(); + REQUIRE(result == flags); +} + +TEST_CASE("libclang_compile_config") +{ + // only test database parser + auto json = R"([ +{ + "directory": "/foo", + "command": "/usr/bin/clang++ -Irelative -I/absolute -DA=FOO -DB(X)=X -c -o a.o a.cpp", + "file": "a.cpp" +}, +{ + "directory": "/bar/", + "command": "/usr/bin/clang++ -Irelative -DA=FOO -c -o b.o b.cpp", + "command": "/usr/bin/clang++ -I/absolute -DB(X)=X -c -o b.o b.cpp", + "file": "/b.cpp", +}, +{ + "directory": "/bar/", + "command": "/usr/bin/clang++ -I/absolute -DB(X)=X -c -o b.o b.cpp", + "file": "/b.cpp", +}, +{ + "directory": "", + "command": "/usr/bin/clang++ -std=c++14 -fms-extensions -fms-compatibility -c -o c.o c.cpp", + "file": "/c.cpp", +} +])"; + + auto database = get_database(json); + + libclang_compile_config a(database, "/foo/a.cpp"); + require_flags(a, "-I/foo/relative -I/absolute -DA=FOO -DB(X)=X"); + + libclang_compile_config b(database, "/b.cpp"); + require_flags(b, "-I/bar/relative -DA=FOO -I/absolute -DB(X)=X"); + + libclang_compile_config c(database, "/c.cpp"); + require_flags(c, "-std=c++14 -fms-extensions -fms-compatibility"); +} From 6dd85cb7a74737062e81157acdbef936087ac9ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Fri, 23 Jun 2017 14:18:39 +0200 Subject: [PATCH 2/8] Silently ignore multiple registration of files --- include/cppast/cpp_entity_index.hpp | 10 +++++++++- include/cppast/cpp_file.hpp | 6 +++--- include/cppast/parser.hpp | 1 + src/cpp_entity_index.cpp | 8 ++++++++ src/libclang/libclang_parser.cpp | 4 ++-- 5 files changed, 23 insertions(+), 6 deletions(-) diff --git a/include/cppast/cpp_entity_index.hpp b/include/cppast/cpp_entity_index.hpp index 7e45df3..d5dd545 100644 --- a/include/cppast/cpp_entity_index.hpp +++ b/include/cppast/cpp_entity_index.hpp @@ -17,6 +17,7 @@ namespace cppast { class cpp_entity; + class cpp_file; class cpp_namespace; /// \exclude @@ -78,10 +79,17 @@ namespace cppast void register_definition(cpp_entity_id id, type_safe::object_ref entity) const; + /// \effects Registers a new [cppast::cpp_file](). + /// \returns `true` if the file was not registered before. + /// If it returns `false`, the file was registered before and nothing was changed. + /// \requires The entity must live as long as the index lives. + /// \notes This operation is thread safe. + bool register_file(cpp_entity_id id, type_safe::object_ref file) const; + /// \effects Registers a new [cppast::cpp_entity]() which is a declaration. /// Only the first declaration will be registered. /// \requires The entity must live as long as the index lives. - /// \requires The entity must not be a namespace. + /// \requires The entity must be forward declarable. /// \notes This operation is thread safe. void register_forward_declaration(cpp_entity_id id, type_safe::object_ref entity) const; diff --git a/include/cppast/cpp_file.hpp b/include/cppast/cpp_file.hpp index 0ef4a11..b45ebec 100644 --- a/include/cppast/cpp_file.hpp +++ b/include/cppast/cpp_file.hpp @@ -50,11 +50,11 @@ namespace cppast /// \effects Registers the file in the [cppast::cpp_entity_index](). /// It will use the file name as identifier. - /// \returns The finished file. + /// \returns The finished file, or `nullptr`, if that file was already registered. std::unique_ptr finish(const cpp_entity_index& idx) noexcept { - idx.register_definition(cpp_entity_id(file_->name()), type_safe::ref(*file_)); - return std::move(file_); + auto res = idx.register_file(cpp_entity_id(file_->name()), type_safe::ref(*file_)); + return res ? std::move(file_) : nullptr; } private: diff --git a/include/cppast/parser.hpp b/include/cppast/parser.hpp index 708cc99..63ac895 100644 --- a/include/cppast/parser.hpp +++ b/include/cppast/parser.hpp @@ -84,6 +84,7 @@ namespace cppast /// \effects Parses the given file. /// \returns The [cppast::cpp_file]() object describing it. + /// It can be `nullptr`, if there was an error or the specified file already registered in the index. /// \requires The dynamic type of `config` must match the required config type. std::unique_ptr parse(const cpp_entity_index& idx, std::string path, const compile_config& config) const diff --git a/src/cpp_entity_index.cpp b/src/cpp_entity_index.cpp index da46bb2..521e367 100644 --- a/src/cpp_entity_index.cpp +++ b/src/cpp_entity_index.cpp @@ -7,6 +7,7 @@ #include #include #include +#include using namespace cppast; @@ -33,6 +34,13 @@ void cpp_entity_index::register_definition(cpp_entity_id } } +bool cpp_entity_index::register_file(cpp_entity_id id, + type_safe::object_ref file) const +{ + std::lock_guard lock(mutex_); + return map_.emplace(std::move(id), value(file, true)).second; +} + void cpp_entity_index::register_forward_declaration( cpp_entity_id id, type_safe::object_ref entity) const { diff --git a/src/libclang/libclang_parser.cpp b/src/libclang/libclang_parser.cpp index 44bcb85..cafede5 100644 --- a/src/libclang/libclang_parser.cpp +++ b/src/libclang/libclang_parser.cpp @@ -399,7 +399,7 @@ std::unique_ptr libclang_parser::do_parse(const cpp_entity_index& idx, auto macro_iter = preprocessed.macros.begin(); auto include_iter = preprocessed.includes.begin(); - // convert entity hierachies + // convert entity hierarchies detail::parse_context context{tu.get(), file, type_safe::ref(logger()), type_safe::ref(idx), detail::comment_context(preprocessed.comments)}; detail::visit_tu(tu, path.c_str(), [&](const CXCursor& cur) { @@ -444,5 +444,5 @@ std::unique_ptr libclang_parser::do_parse(const cpp_entity_index& idx, catch (detail::parse_error& ex) { logger().log("libclang parser", ex.get_diagnostic()); - return cpp_file::builder(path).finish(idx); + return nullptr; } From 44708fff76659c37388c0be4cf5e88f1fe3861ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Tue, 27 Jun 2017 10:58:24 +0200 Subject: [PATCH 3/8] [breaking] Move error_logged() from logger to parser --- include/cppast/parser.hpp | 40 +++++++++++++++++++++----------- src/libclang/enum_parser.cpp | 2 ++ src/libclang/function_parser.cpp | 2 ++ src/libclang/libclang_parser.cpp | 12 ++++++++-- src/libclang/parse_functions.cpp | 2 ++ src/libclang/parse_functions.hpp | 1 + src/parser.cpp | 4 +--- test/test_parser.hpp | 2 +- tool/main.cpp | 2 +- 9 files changed, 47 insertions(+), 20 deletions(-) diff --git a/include/cppast/parser.hpp b/include/cppast/parser.hpp index 63ac895..5d4abab 100644 --- a/include/cppast/parser.hpp +++ b/include/cppast/parser.hpp @@ -21,7 +21,7 @@ namespace cppast class diagnostic_logger { public: - diagnostic_logger() noexcept : error_(false) + diagnostic_logger() noexcept : verbose_(false) { } @@ -34,15 +34,6 @@ namespace cppast /// \notes `source` points to a string literal that gives additional context to what generates the message. bool log(const char* source, const diagnostic& d) const; - /// \returns Whether or not a diagnostic of [severity::error]() was logged. - /// \notes If an error happens, the parser will still continue to build the AST, - /// unless the error is critical - /// so the result will be incomplete if this function returns `true`. - bool error_logged() const noexcept - { - return error_; - } - /// \effects Sets whether or not the logger prints debugging diagnostics. void set_verbose(bool value) noexcept { @@ -58,8 +49,7 @@ namespace cppast private: virtual bool do_log(const char* source, const diagnostic& d) const = 0; - mutable std::atomic error_; - bool verbose_ = false; + bool verbose_; }; /// A [cppast::diagnostic_logger]() that logs to `stderr`. @@ -86,15 +76,30 @@ namespace cppast /// \returns The [cppast::cpp_file]() object describing it. /// It can be `nullptr`, if there was an error or the specified file already registered in the index. /// \requires The dynamic type of `config` must match the required config type. + /// \notes This function is thread safe. std::unique_ptr parse(const cpp_entity_index& idx, std::string path, const compile_config& config) const { return do_parse(idx, std::move(path), config); } + /// \returns Whether or not an error occurred during parsing. + /// If that happens, the AST might be incomplete. + bool error() const noexcept + { + return error_; + } + + /// \effects Resets the error state. + void reset_error() noexcept + { + error_ = false; + } + protected: /// \effects Creates it giving it a reference to the logger it uses. - explicit parser(type_safe::object_ref logger) : logger_(logger) + explicit parser(type_safe::object_ref logger) + : logger_(logger), error_(false) { } @@ -104,13 +109,22 @@ namespace cppast return *logger_; } + /// \effects Sets the error state. + /// This must be called when an error or critical diagnostic is logged and the AST is incomplete. + void set_error() const noexcept + { + error_ = true; + } + private: /// \effects Parses the given file. /// \returns The [cppast::cpp_file]() object describing it. + /// \requires The function must be thread safe. virtual std::unique_ptr do_parse(const cpp_entity_index& idx, std::string path, const compile_config& config) const = 0; type_safe::object_ref logger_; + mutable std::atomic error_; }; } // namespace cppast diff --git a/src/libclang/enum_parser.cpp b/src/libclang/enum_parser.cpp index 5409c99..95eb886 100644 --- a/src/libclang/enum_parser.cpp +++ b/src/libclang/enum_parser.cpp @@ -94,10 +94,12 @@ std::unique_ptr detail::parse_cpp_enum(const detail::parse_context& } catch (parse_error& ex) { + context.error = true; context.logger->log("libclang parser", ex.get_diagnostic()); } catch (std::logic_error& ex) { + context.error = true; context.logger->log("libclang parser", diagnostic{ex.what(), make_location(child), severity::error}); } diff --git a/src/libclang/function_parser.cpp b/src/libclang/function_parser.cpp index 074823a..b0a4bd4 100644 --- a/src/libclang/function_parser.cpp +++ b/src/libclang/function_parser.cpp @@ -46,10 +46,12 @@ namespace } catch (detail::parse_error& ex) { + context.error = true; context.logger->log("libclang parser", ex.get_diagnostic()); } catch (std::logic_error& ex) { + context.error = true; context.logger->log("libclang parser", diagnostic{ex.what(), detail::make_location(child), severity::error}); diff --git a/src/libclang/libclang_parser.cpp b/src/libclang/libclang_parser.cpp index cafede5..df27ec7 100644 --- a/src/libclang/libclang_parser.cpp +++ b/src/libclang/libclang_parser.cpp @@ -400,8 +400,12 @@ std::unique_ptr libclang_parser::do_parse(const cpp_entity_index& idx, auto include_iter = preprocessed.includes.begin(); // convert entity hierarchies - detail::parse_context context{tu.get(), file, type_safe::ref(logger()), type_safe::ref(idx), - detail::comment_context(preprocessed.comments)}; + detail::parse_context context{tu.get(), + file, + type_safe::ref(logger()), + type_safe::ref(idx), + detail::comment_context(preprocessed.comments), + false}; detail::visit_tu(tu, path.c_str(), [&](const CXCursor& cur) { if (clang_getCursorKind(cur) == CXCursor_InclusionDirective) { @@ -439,10 +443,14 @@ std::unique_ptr libclang_parser::do_parse(const cpp_entity_index& idx, builder.add_unmatched_comment(std::move(c.comment)); } + if (context.error) + set_error(); + return builder.finish(idx); } catch (detail::parse_error& ex) { logger().log("libclang parser", ex.get_diagnostic()); + set_error(); return nullptr; } diff --git a/src/libclang/parse_functions.cpp b/src/libclang/parse_functions.cpp index bdbb68d..c001d51 100644 --- a/src/libclang/parse_functions.cpp +++ b/src/libclang/parse_functions.cpp @@ -230,11 +230,13 @@ std::unique_ptr detail::parse_entity(const detail::parse_context& co } catch (parse_error& ex) { + context.error = true; context.logger->log("libclang parser", ex.get_diagnostic()); return nullptr; } catch (std::logic_error& ex) { + context.error = true; context.logger->log("libclang parser", diagnostic{ex.what(), detail::make_location(cur), severity::error}); return nullptr; diff --git a/src/libclang/parse_functions.hpp b/src/libclang/parse_functions.hpp index 4572437..7ef94a1 100644 --- a/src/libclang/parse_functions.hpp +++ b/src/libclang/parse_functions.hpp @@ -62,6 +62,7 @@ namespace cppast type_safe::object_ref logger; type_safe::object_ref idx; comment_context comments; + mutable bool error; }; // parse default value of variable, function parameter... diff --git a/src/parser.cpp b/src/parser.cpp index 64f59a3..05b7789 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -13,9 +13,7 @@ using namespace cppast; bool diagnostic_logger::log(const char* source, const diagnostic& d) const { - if (d.severity == severity::error || d.severity == severity::critical) - error_ = true; - else if (!verbose_ && d.severity == severity::debug) + if (!verbose_ && d.severity == severity::debug) return false; return do_log(source, d); } diff --git a/test/test_parser.hpp b/test/test_parser.hpp index ee38f2e..09ade72 100644 --- a/test/test_parser.hpp +++ b/test/test_parser.hpp @@ -36,7 +36,7 @@ inline std::unique_ptr parse_file(const cppast::cpp_entity_ind std::unique_ptr result; REQUIRE_NOTHROW(result = p.parse(idx, name, config)); - REQUIRE(!logger.error_logged()); + REQUIRE(!p.error()); return result; } diff --git a/tool/main.cpp b/tool/main.cpp index 2f90d87..b17ef99 100644 --- a/tool/main.cpp +++ b/tool/main.cpp @@ -180,7 +180,7 @@ std::unique_ptr parse_file(const cppast::libclang_compile_conf cppast::libclang_parser parser(type_safe::ref(logger)); // parse the file auto file = parser.parse(idx, filename, config); - if (fatal_error && logger.error_logged()) + if (fatal_error && parser.error()) return nullptr; return file; } From 6b7ca183f955ea6ba85f3451da470248fa4293a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Tue, 27 Jun 2017 11:04:56 +0200 Subject: [PATCH 4/8] Add `default_logger()` and `default_verbose_logger()` --- include/cppast/parser.hpp | 12 +++++++++++- src/parser.cpp | 12 ++++++++++++ test/test_parser.hpp | 3 +-- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/include/cppast/parser.hpp b/include/cppast/parser.hpp index 5d4abab..ef35fbb 100644 --- a/include/cppast/parser.hpp +++ b/include/cppast/parser.hpp @@ -21,7 +21,8 @@ namespace cppast class diagnostic_logger { public: - diagnostic_logger() noexcept : verbose_(false) + /// \effects Creates it either as verbose or not. + explicit diagnostic_logger(bool is_verbose = false) noexcept : verbose_(is_verbose) { } @@ -52,11 +53,20 @@ namespace cppast bool verbose_; }; + /// \returns The default logger object. + type_safe::object_ref default_logger() noexcept; + + /// \returns The default verbose logger object. + type_safe::object_ref default_verbose_logger() noexcept; + /// A [cppast::diagnostic_logger]() that logs to `stderr`. /// /// It prints all diagnostics in an implementation-defined format. class stderr_diagnostic_logger final : public diagnostic_logger { + public: + using diagnostic_logger::diagnostic_logger; + private: bool do_log(const char* source, const diagnostic& d) const override; }; diff --git a/src/parser.cpp b/src/parser.cpp index 05b7789..172f628 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -18,6 +18,18 @@ bool diagnostic_logger::log(const char* source, const diagnostic& d) const return do_log(source, d); } +type_safe::object_ref cppast::default_logger() noexcept +{ + static const stderr_diagnostic_logger logger(false); + return type_safe::ref(logger); +} + +type_safe::object_ref cppast::default_verbose_logger() noexcept +{ + static const stderr_diagnostic_logger logger(true); + return type_safe::ref(logger); +} + bool stderr_diagnostic_logger::do_log(const char* source, const diagnostic& d) const { auto loc = d.location.to_string(); diff --git a/test/test_parser.hpp b/test/test_parser.hpp index 09ade72..aa4cf0d 100644 --- a/test/test_parser.hpp +++ b/test/test_parser.hpp @@ -31,8 +31,7 @@ inline std::unique_ptr parse_file(const cppast::cpp_entity_ind libclang_compile_config config; config.set_flags(cpp_standard::cpp_latest); - stderr_diagnostic_logger logger; - libclang_parser p(type_safe::ref(logger)); + libclang_parser p(default_logger()); std::unique_ptr result; REQUIRE_NOTHROW(result = p.parse(idx, name, config)); From 38831db073b9b980fc1fbe3104894a4b27d7c1ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Fri, 23 Jun 2017 08:17:26 +0200 Subject: [PATCH 5/8] Add parse_files() utility function --- include/cppast/compile_config.hpp | 3 + include/cppast/detail/intrusive_list.hpp | 44 ++++++---- include/cppast/libclang_parser.hpp | 47 +++++++++++ include/cppast/parser.hpp | 101 +++++++++++++++++++++++ src/libclang/libclang_parser.cpp | 33 ++++++++ test/CMakeLists.txt | 1 + test/parser.cpp | 71 ++++++++++++++++ 7 files changed, 286 insertions(+), 14 deletions(-) create mode 100644 test/parser.cpp diff --git a/include/cppast/compile_config.hpp b/include/cppast/compile_config.hpp index 3e17ad6..484fc35 100644 --- a/include/cppast/compile_config.hpp +++ b/include/cppast/compile_config.hpp @@ -104,6 +104,9 @@ namespace cppast { } + compile_config(const compile_config&) = default; + compile_config& operator=(const compile_config&) = default; + ~compile_config() noexcept = default; void add_flag(std::string flag) diff --git a/include/cppast/detail/intrusive_list.hpp b/include/cppast/detail/intrusive_list.hpp index cbc4c1b..ab68837 100644 --- a/include/cppast/detail/intrusive_list.hpp +++ b/include/cppast/detail/intrusive_list.hpp @@ -14,6 +14,8 @@ namespace cppast { + class cpp_file; + namespace detail { template @@ -122,22 +124,20 @@ namespace cppast intrusive_list() = default; //=== modifiers ===// - template + template < + typename Dummy = T, + typename = typename std::enable_if::value>::type> + void push_back(std::unique_ptr obj) noexcept + { + push_back_impl(std::move(obj)); + } + + template < + typename U, + typename = typename std::enable_if::value, U>::type> void push_back(const U& parent, std::unique_ptr obj) noexcept { - DEBUG_ASSERT(obj != nullptr, detail::assert_handler{}); - - if (last_) - { - auto ptr = intrusive_list_access::set_next(last_.value(), std::move(obj)); - last_ = type_safe::ref(*ptr); - } - else - { - first_ = std::move(obj); - last_ = type_safe::opt_ref(first_.get()); - } - + push_back_impl(std::move(obj)); intrusive_list_access::on_insert(last_.value(), parent); } @@ -192,6 +192,22 @@ namespace cppast } private: + void push_back_impl(std::unique_ptr obj) + { + DEBUG_ASSERT(obj != nullptr, detail::assert_handler{}); + + if (last_) + { + auto ptr = intrusive_list_access::set_next(last_.value(), std::move(obj)); + last_ = type_safe::ref(*ptr); + } + else + { + first_ = std::move(obj); + last_ = type_safe::opt_ref(first_.get()); + } + } + std::unique_ptr first_; type_safe::optional_ref last_; }; diff --git a/include/cppast/libclang_parser.hpp b/include/cppast/libclang_parser.hpp index a5f24ef..add114f 100644 --- a/include/cppast/libclang_parser.hpp +++ b/include/cppast/libclang_parser.hpp @@ -62,6 +62,16 @@ namespace cppast return *this; } + /// \returns Whether or not the database contains information about the given file. + /// \group has_config + bool has_config(const char* file_name) const; + + /// \group has_config + bool has_config(const std::string& file_name) const + { + return has_config(file_name.c_str()); + } + private: using database = void*; database database_; @@ -94,6 +104,9 @@ namespace cppast libclang_compile_config(const libclang_compilation_database& database, const std::string& file); + libclang_compile_config(const libclang_compile_config& other) = default; + libclang_compile_config& operator=(const libclang_compile_config& other) = default; + /// \effects Sets the path to the location of the `clang++` binary and the version of that binary. /// \notes It will be used for preprocessing. void set_clang_binary(std::string binary, int major, int minor, int patch) @@ -122,10 +135,21 @@ namespace cppast friend detail::libclang_compile_config_access; }; + /// Finds a configuration for a given file. + /// + /// \returns If the database contains a configuration for the given file, returns that configuration. + /// Otherwise removes the file extension of the file and tries the same procedure + /// for common C++ header and source file extensions. + /// \notes This function is intended to be used as the basis for a `get_config` function of [cppast::parse_files](standardese://cppast::parse_files_basic/). + type_safe::optional find_config_for( + const libclang_compilation_database& database, std::string file_name); + /// A parser that uses libclang. class libclang_parser final : public parser { public: + using config = libclang_compile_config; + explicit libclang_parser(type_safe::object_ref logger); ~libclang_parser() noexcept override; @@ -136,6 +160,29 @@ namespace cppast struct impl; std::unique_ptr pimpl_; }; + + /// Parses multiple files using a [cppast::libclang_parser]() and a compilation database. + /// + /// \effects Invokes [cppast::parse_files](standardese://parse_files_basic/) passing it the parser and file names, + /// and a `get_config` function using [cppast::find_config_for](). + /// + /// \throws [cppast::libclang_error]() if no configuration for a given file could be found in the database. + /// + /// \requires `FileParser` must use [cppast::libclang_parser](), + /// i.e. `FileParser::parser` must be an alias of [cppast::libclang_parser](). + template + void parse_files(FileParser& parser, Range&& file_names, + const libclang_compilation_database& database) + { + static_assert(std::is_same::value, + "must use the libclang parser"); + parse_files(parser, std::forward(file_names), [&](const std::string& file) { + auto config = find_config_for(database, file); + if (!config) + throw libclang_error("unable to find configuration for file '" + file + "'"); + return config.value(); + }); + } } // namespace cppast #endif // CPPAST_LIBCLANG_PARSER_HPP_INCLUDED diff --git a/include/cppast/parser.hpp b/include/cppast/parser.hpp index ef35fbb..5efa181 100644 --- a/include/cppast/parser.hpp +++ b/include/cppast/parser.hpp @@ -74,6 +74,9 @@ namespace cppast /// Base class for a parser. /// /// It reads a C++ source file and creates the matching [cppast::cpp_file](). + /// Derived classes can implement how the file is parsed. + /// + /// \requires A derived class must provide an alias `config` which is the corresponding derived class of the [cppast::compile_config](). class parser { public: @@ -136,6 +139,104 @@ namespace cppast type_safe::object_ref logger_; mutable std::atomic error_; }; + + /// A simple `FileParser` that parses all files synchronously. + /// + /// More advanced parsers could use a thread pool, for example. + template + class simple_file_parser + { + static_assert(std::is_base_of::value, + "Parser must be derived from cppast::parser"); + + public: + using parser = Parser; + using config = typename Parser::config; + + /// \effects Creates a file parser populating the given index + /// and using the parser created by forwarding the given arguments. + template + explicit simple_file_parser(type_safe::object_ref idx, + Args&&... args) + : parser_(std::forward(args)...), idx_(idx) + { + } + + void parse(std::string path, const config& c) + { + auto file = parser_.parse(*idx_, std::move(path), c); + if (file) + files_.push_back(std::move(file)); + } + + /// \returns The result of [cppast::parser::error](). + bool error() const noexcept + { + return parser_.error(); + } + + /// \effects Calls [cppast::parser::reset_error](). + void reset_error() noexcept + { + parser_.reset_error(); + } + + /// \returns The index that is being populated. + const cpp_entity_index& index() const noexcept + { + return *idx_; + } + + /// \returns An iteratable object iterating over all the files that have been parsed so far. + /// \exclude return + detail::iteratable_intrusive_list files() const noexcept + { + return type_safe::ref(files_); + } + + private: + Parser parser_; + detail::intrusive_list files_; + type_safe::object_ref idx_; + }; + + /// Parses multiple files using a given `FileParser`. + /// + /// \effects Will call the `parse()` function for each path specified in the `file_names`, + /// using `get_confg` to determine the configuration. + /// + /// \requires `FileParser` must be a class with the following members: + /// * `parser` - A typedef for the parser being used to do the parsing. + /// * `config` - The same as `parser::config`. + /// * `parse(path, config)` - Parses the given file with the configuration using its parser. + /// The parsing can be executed in a separated thread, but then a copy of the configuration and path must be created. + /// \requires `Range` must be some type that can be iterated in a range-based for loop. + /// \requires `Configuration` must be a function that returns a configuration of type `FileParser::config` when given a path. + /// \unique_name parse_files_basic + /// \synopsis_return void + template + auto parse_files(FileParser& parser, Range&& file_names, const Configuration& get_config) -> + typename std::enable_if(file_names).begin()))>::type, + typename FileParser::config>::value>::type + { + for (auto&& file : std::forward(file_names)) + { + auto&& config = get_config(file); + parser.parse(std::forward(file), + std::forward(config)); + } + } + + /// Parses multiple files using a given `FileParser` and configuration. + /// \effects Invokes [cppast::parse_files](standardese://parse_files_basic/) passing it the parser and file names, + /// and a function that returns the same configuration for each file. + template + void parse_files(FileParser& parser, Range&& file_names, typename FileParser::config config) + { + parse_files(parser, std::forward(file_names), + [&](const std::string&) { return config; }); + } } // namespace cppast #endif // CPPAST_PARSER_HPP_INCLUDED diff --git a/src/libclang/libclang_parser.cpp b/src/libclang/libclang_parser.cpp index df27ec7..7d3ab71 100644 --- a/src/libclang/libclang_parser.cpp +++ b/src/libclang/libclang_parser.cpp @@ -51,6 +51,15 @@ libclang_compilation_database::~libclang_compilation_database() clang_CompilationDatabase_dispose(database_); } +bool libclang_compilation_database::has_config(const char* file_name) const +{ + auto cxcommands = clang_CompilationDatabase_getCompileCommands(database_, file_name); + if (!cxcommands) + return false; + clang_CompileCommands_dispose(cxcommands); + return true; +} + namespace { int parse_number(const char*& str) @@ -260,6 +269,30 @@ void libclang_compile_config::do_remove_macro_definition(std::string name) add_flag("-U" + std::move(name)); } +type_safe::optional cppast::find_config_for( + const libclang_compilation_database& database, std::string file_name) +{ + if (database.has_config(file_name)) + return libclang_compile_config(database, std::move(file_name)); + + auto dot = file_name.rfind('.'); + if (dot != std::string::npos) + file_name.erase(dot); + + if (database.has_config(file_name)) + return libclang_compile_config(database, std::move(file_name)); + static const char* extensions[] = {".h", ".hpp", ".cpp", ".h++", ".c++", ".hxx", + ".cxx", ".hh", ".cc", ".H", ".C"}; + for (auto ext : extensions) + { + auto name = file_name + ext; + if (database.has_config(name)) + return libclang_compile_config(database, std::move(name)); + } + + return type_safe::nullopt; +} + struct libclang_parser::impl { detail::cxindex index; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1ac93b9..1d40796 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -28,6 +28,7 @@ set(tests cpp_variable.cpp integration.cpp libclang_parser.cpp + parser.cpp visitor.cpp) add_executable(cppast_test test.cpp test_parser.hpp ${tests}) diff --git a/test/parser.cpp b/test/parser.cpp new file mode 100644 index 0000000..3bc4944 --- /dev/null +++ b/test/parser.cpp @@ -0,0 +1,71 @@ +// Copyright (C) 2017 Jonathan Müller +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include + +using namespace cppast; + +TEST_CASE("parse_files") +{ + class null_compile_config : public compile_config + { + public: + null_compile_config() : compile_config({}) + { + } + + private: + void do_set_flags(cpp_standard, compile_flags) override + { + } + + void do_add_include_dir(std::string) override + { + } + + void do_add_macro_definition(std::string, std::string) override + { + } + + void do_remove_macro_definition(std::string) override + { + } + + const char* do_get_name() const noexcept override + { + return "null"; + } + } config; + + class null_parser : public parser + { + public: + using config = null_compile_config; + + null_parser() : parser(type_safe::ref(logger_)) + { + } + + private: + std::unique_ptr do_parse(const cpp_entity_index& idx, std::string path, + const compile_config&) const override + { + return cpp_file::builder(std::move(path)).finish(idx); + } + + stderr_diagnostic_logger logger_; + }; + + cpp_entity_index idx; + simple_file_parser parser(type_safe::ref(idx)); + + auto file_names = {"a.cpp", "b.cpp", "c.cpp"}; + parse_files(parser, file_names, config); + + auto iter = file_names.begin(); + for (auto& file : parser.files()) + REQUIRE(file.name() == *iter++); +} From 7092946091a869ce348fac075283001549d05850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Tue, 27 Jun 2017 16:41:32 +0200 Subject: [PATCH 6/8] Add parse_database() function --- include/cppast/libclang_parser.hpp | 34 +++++++++++++++++++- src/libclang/libclang_parser.cpp | 50 ++++++++++++++++++++---------- 2 files changed, 66 insertions(+), 18 deletions(-) diff --git a/include/cppast/libclang_parser.hpp b/include/cppast/libclang_parser.hpp index add114f..6ff4b48 100644 --- a/include/cppast/libclang_parser.hpp +++ b/include/cppast/libclang_parser.hpp @@ -12,6 +12,7 @@ namespace cppast { class libclang_compile_config; + class libclang_compilation_database; namespace detail { @@ -23,6 +24,9 @@ namespace cppast static const std::vector& flags(const libclang_compile_config& config); }; + + void for_each_file(const libclang_compilation_database& database, void* user_data, + void (*callback)(void*, std::string)); } // namespace detail /// The exception thrown when a fatal parse error occurs. @@ -77,6 +81,8 @@ namespace cppast database database_; friend libclang_compile_config; + friend void detail::for_each_file(const libclang_compilation_database& database, + void* user_data, void (*callback)(void*, std::string)); }; /// Compilation config for the [cppast::libclang_parser](). @@ -168,7 +174,7 @@ namespace cppast /// /// \throws [cppast::libclang_error]() if no configuration for a given file could be found in the database. /// - /// \requires `FileParser` must use [cppast::libclang_parser](), + /// \requires `FileParser` must use the libclang parser. /// i.e. `FileParser::parser` must be an alias of [cppast::libclang_parser](). template void parse_files(FileParser& parser, Range&& file_names, @@ -183,6 +189,32 @@ namespace cppast return config.value(); }); } + + /// Parses the files specified in a compilation database using a [cppast::libclang_parser](). + /// + /// \effects For each file specified in a compilation database, + /// uses the `FileParser` to parse the file with the configuration specified in the database. + /// + /// \requires `FileParser` must have the same requirements as for [cppast::parse_files](standardese://parse_files_basic/). + /// It must also use the libclang parser, + /// i.e. `FileParser::parser` must be an alias of [cppast::libclang_parser](). + template + void parse_database(FileParser& parser, const libclang_compilation_database& database) + { + static_assert(std::is_same::value, + "must use the libclang parser"); + struct data_t + { + FileParser& parser; + const libclang_compilation_database& database; + } data{parser, database}; + detail::for_each_file(database, &data, [](void* ptr, std::string file) { + auto& data = *static_cast(ptr); + + libclang_compile_config config(data.database, file); + data.parser.parse(std::move(file), std::move(config)); + }); + } } // namespace cppast #endif // CPPAST_LIBCLANG_PARSER_HPP_INCLUDED diff --git a/src/libclang/libclang_parser.cpp b/src/libclang/libclang_parser.cpp index 7d3ab71..93d121d 100644 --- a/src/libclang/libclang_parser.cpp +++ b/src/libclang/libclang_parser.cpp @@ -103,6 +103,35 @@ namespace }; using cxcompile_commands = detail::raii_wrapper; + + std::string get_full_path(const detail::cxstring& dir, const std::string& file) + { + if (file.front() == '/' || file.front() == '\\') + // absolute file + return file; + else if (dir[dir.length() - 1] != '/' && dir[dir.length() - 1] != '\\') + // relative needing separator + return dir.std_str() + '/' + file; + else + // relative w/o separator + return dir.std_str() + file; + } +} + +void detail::for_each_file(const libclang_compilation_database& database, void* user_data, + void (*callback)(void*, std::string)) +{ + cxcompile_commands commands( + clang_CompilationDatabase_getAllCompileCommands(database.database_)); + auto no = clang_CompileCommands_getSize(commands.get()); + for (auto i = 0u; i != no; ++i) + { + auto cmd = clang_CompileCommands_getCommand(commands.get(), i); + + auto dir = cxstring(clang_CompileCommand_getDirectory(cmd)); + callback(user_data, + get_full_path(dir, cxstring(clang_CompileCommand_getFilename(cmd)).std_str())); + } } namespace @@ -182,29 +211,16 @@ libclang_compile_config::libclang_compile_config(const libclang_compilation_data auto dir = detail::cxstring(clang_CompileCommand_getDirectory(cmd)); parse_flags(cmd, [&](std::string flag, std::string args) { if (flag == "-I") - { - if (args.front() == '/' || args.front() == '\\') - { - add_flag(std::move(flag) + std::move(args)); - } - else - { - // path relative to the directory - if (dir[dir.length() - 1] != '/' && dir[dir.length() - 1] != '\\') - add_flag(std::move(flag) + dir.std_str() + '/' + std::move(args)); - else - add_flag(std::move(flag) + dir.std_str() + std::move(args)); - } - } + add_flag(std::move(flag) + get_full_path(dir, args)); else if (flag == "-D" || flag == "-U") // preprocessor options - this->add_flag(std::move(flag) + std::move(args)); + add_flag(std::move(flag) + std::move(args)); else if (flag == "-std") // standard - this->add_flag(std::move(flag) + "=" + std::move(args)); + add_flag(std::move(flag) + "=" + std::move(args)); else if (flag == "-f" && (args == "ms-compatibility" || args == "ms-extensions")) // other options - this->add_flag(std::move(flag) + std::move(args)); + add_flag(std::move(flag) + std::move(args)); }); } } From abb2ba33d08d6f0aa2c11a471cbf16462f7874f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Tue, 27 Jun 2017 21:03:12 +0200 Subject: [PATCH 7/8] Add resolve_includes() function --- include/cppast/parser.hpp | 21 ++++++++++++++++++++- test/integration.cpp | 27 ++++++++++++--------------- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/include/cppast/parser.hpp b/include/cppast/parser.hpp index 5efa181..b1723c5 100644 --- a/include/cppast/parser.hpp +++ b/include/cppast/parser.hpp @@ -9,6 +9,7 @@ #include #include +#include namespace cppast { @@ -162,11 +163,15 @@ namespace cppast { } - void parse(std::string path, const config& c) + /// \effects Parses the given file using the given configuration. + /// \returns The parsed file or an empty optional, if a fatal error occurred. + type_safe::optional_ref parse(std::string path, const config& c) { auto file = parser_.parse(*idx_, std::move(path), c); + auto ptr = file.get(); if (file) files_.push_back(std::move(file)); + return type_safe::opt_ref(ptr); } /// \returns The result of [cppast::parser::error](). @@ -237,6 +242,20 @@ namespace cppast parse_files(parser, std::forward(file_names), [&](const std::string&) { return config; }); } + + template + void resolve_includes(FileParser& parser, const cpp_file& file, + typename FileParser::config config) + { + for (auto& entity : file) + { + if (entity.kind() == cpp_include_directive::kind()) + { + auto& include = static_cast(entity); + parser.parse(include.full_path(), config); + } + } + } } // namespace cppast #endif // CPPAST_PARSER_HPP_INCLUDED diff --git a/test/integration.cpp b/test/integration.cpp index e114931..6d25ee7 100644 --- a/test/integration.cpp +++ b/test/integration.cpp @@ -8,18 +8,6 @@ using namespace cppast; -void parse_included_files(const cpp_entity_index& idx, const cpp_file& file) -{ - for (auto& e : file) - { - if (e.kind() == cpp_entity_kind::include_directive_t) - { - auto path = static_cast(e).full_path(); - parse_file(idx, path.c_str()); - } - } -} - TEST_CASE("stdlib", "[!hide][integration]") { auto code = R"( @@ -111,8 +99,17 @@ TEST_CASE("stdlib", "[!hide][integration]") #include #include )"; + write_file("stdlib.cpp", code); - cpp_entity_index idx; - auto file = parse(idx, "stdlib.cpp", code); - parse_included_files(idx, *file); + cpp_entity_index idx; + simple_file_parser parser(type_safe::ref(idx), default_logger()); + + libclang_compile_config config; + config.set_flags(cpp_standard::cpp_latest); + auto file = parser.parse("stdlib.cpp", config); + REQUIRE(!parser.error()); + REQUIRE(file); + + resolve_includes(parser, file.value(), config); + REQUIRE(!parser.error()); } From 3677aeb564469b9db6cbb9038bd6447e7e7f1626 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Tue, 27 Jun 2017 21:33:47 +0200 Subject: [PATCH 8/8] Add support for drive letters Windows... --- src/libclang/libclang_parser.cpp | 7 ++++- test/libclang_parser.cpp | 46 +++++++++++++++++++++++++++----- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/libclang/libclang_parser.cpp b/src/libclang/libclang_parser.cpp index 93d121d..daba8b5 100644 --- a/src/libclang/libclang_parser.cpp +++ b/src/libclang/libclang_parser.cpp @@ -104,9 +104,14 @@ namespace using cxcompile_commands = detail::raii_wrapper; + bool has_drive_prefix(const std::string& file) + { + return file.size() > 2 && file[1] == ':'; + } + std::string get_full_path(const detail::cxstring& dir, const std::string& file) { - if (file.front() == '/' || file.front() == '\\') + if (has_drive_prefix(file) || file.front() == '/' || file.front() == '\\') // absolute file return file; else if (dir[dir.length() - 1] != '/' && dir[dir.length() - 1] != '\\') diff --git a/test/libclang_parser.cpp b/test/libclang_parser.cpp index 718c99c..f15cf6e 100644 --- a/test/libclang_parser.cpp +++ b/test/libclang_parser.cpp @@ -32,7 +32,34 @@ void require_flags(const libclang_compile_config& config, const char* flags) TEST_CASE("libclang_compile_config") { - // only test database parser +// only test database parser +#ifdef _WIN32 + auto json = R"([ +{ + "directory": "C:/foo", + "command": "/usr/bin/clang++ -Irelative -IC:/absolute -DA=FOO -DB(X)=X -c -o a.o a.cpp", + "file": "a.cpp" +}, +{ + "directory": "C:/bar/", + "command": "/usr/bin/clang++ -Irelative -DA=FOO -c -o b.o b.cpp", + "file": "C:/b.cpp", +}, +{ + "directory": "C:/bar/", + "command": "/usr/bin/clang++ -IC:/absolute -DB(X)=X -c -o b.o b.cpp", + "file": "C:/b.cpp", +}, +{ + "directory": "", + "command": "/usr/bin/clang++ -std=c++14 -fms-extensions -fms-compatibility -c -o c.o c.cpp", + "file": "C:/c.cpp", +} +])"; + +#define CPPAST_DETAIL_DRIVE "C:" + +#else auto json = R"([ { "directory": "/foo", @@ -42,7 +69,6 @@ TEST_CASE("libclang_compile_config") { "directory": "/bar/", "command": "/usr/bin/clang++ -Irelative -DA=FOO -c -o b.o b.cpp", - "command": "/usr/bin/clang++ -I/absolute -DB(X)=X -c -o b.o b.cpp", "file": "/b.cpp", }, { @@ -57,14 +83,20 @@ TEST_CASE("libclang_compile_config") } ])"; +#define CPPAST_DETAIL_DRIVE + +#endif + auto database = get_database(json); - libclang_compile_config a(database, "/foo/a.cpp"); - require_flags(a, "-I/foo/relative -I/absolute -DA=FOO -DB(X)=X"); + libclang_compile_config a(database, CPPAST_DETAIL_DRIVE "/foo/a.cpp"); + require_flags(a, "-I" CPPAST_DETAIL_DRIVE "/foo/relative -I" CPPAST_DETAIL_DRIVE + "/absolute -DA=FOO -DB(X)=X"); - libclang_compile_config b(database, "/b.cpp"); - require_flags(b, "-I/bar/relative -DA=FOO -I/absolute -DB(X)=X"); + libclang_compile_config b(database, CPPAST_DETAIL_DRIVE "/b.cpp"); + require_flags(b, "-I" CPPAST_DETAIL_DRIVE "/bar/relative -DA=FOO -I" CPPAST_DETAIL_DRIVE + "/absolute -DB(X)=X"); - libclang_compile_config c(database, "/c.cpp"); + libclang_compile_config c(database, CPPAST_DETAIL_DRIVE "/c.cpp"); require_flags(c, "-std=c++14 -fms-extensions -fms-compatibility"); }