Add basic compilation database parsing
This commit is contained in:
parent
1053429b00
commit
d14965b24e
4 changed files with 283 additions and 29 deletions
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include <clang-c/CXCompilationDatabase.h>
|
||||
|
||||
#include "libclang_visitor.hpp"
|
||||
#include "raii_wrapper.hpp"
|
||||
#include "parse_error.hpp"
|
||||
|
|
@ -33,6 +35,22 @@ const std::vector<std::string>& 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<database, CXCompilationDatabase>::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<CXCompileCommands, cxcompile_commands_deleter>;
|
||||
}
|
||||
|
||||
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 <typename Func>
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
|
|||
70
test/libclang_parser.cpp
Normal file
70
test/libclang_parser.cpp
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
// Copyright (C) 2017 Jonathan Müller <jonathanmueller.dev@gmail.com>
|
||||
// This file is subject to the license terms in the LICENSE file
|
||||
// found in the top-level directory of this distribution.
|
||||
|
||||
#include <catch.hpp>
|
||||
|
||||
#include <cppast/libclang_parser.hpp>
|
||||
|
||||
#include <fstream>
|
||||
|
||||
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");
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue