diff --git a/src/common/compilation_database.cc b/src/common/compilation_database.cc new file mode 100644 index 00000000..83772bae --- /dev/null +++ b/src/common/compilation_database.cc @@ -0,0 +1,104 @@ +/** + * src/common/compilation_database.cc + * + * Copyright (c) 2021-2023 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "compilation_database.h" + +namespace clanguml::common { + +std::unique_ptr +compilation_database::auto_detect_from_directory( + const clanguml::config::config &cfg) +{ + std::string error_message; + auto res = clang::tooling::CompilationDatabase::autoDetectFromDirectory( + cfg.compilation_database_dir(), error_message); + + if (!error_message.empty()) + throw compilation_database_error(error_message); + + return std::make_unique(std::move(res), cfg); +} + +compilation_database::compilation_database( + std::unique_ptr base, + const clanguml::config::config &cfg) + : base_{std::move(base)} + , config_{cfg} +{ +} + +const clanguml::config::config &compilation_database::config() const +{ + return config_; +} + +const clang::tooling::CompilationDatabase &compilation_database::base() const +{ + return *base_; +} + +std::vector compilation_database::getAllFiles() const +{ + return base().getAllFiles(); +} + +std::vector +compilation_database::getCompileCommands(clang::StringRef FilePath) const +{ + auto commands = base().getCompileCommands(FilePath); + + adjust_compilation_database(commands); + + return commands; +} + +std::vector +compilation_database::getAllCompileCommands() const +{ + auto commands = base().getAllCompileCommands(); + + adjust_compilation_database(commands); + + return commands; +} + +void compilation_database::adjust_compilation_database( + std::vector &commands) const +{ + if (config().add_compile_flags && !config().add_compile_flags().empty()) { + for (auto &compile_command : commands) { + compile_command.CommandLine.insert( + // Add flags after argv[0] + compile_command.CommandLine.begin() + 1, + config().add_compile_flags().begin(), + config().add_compile_flags().end()); + } + } + + if (config().remove_compile_flags && + !config().remove_compile_flags().empty()) { + for (auto &compile_command : commands) { + for (const auto &flag : config().remove_compile_flags()) { + util::erase_if(compile_command.CommandLine, + [&flag](const auto &arg) { return flag == arg; }); + } + } + } +} + +} // namespace clanguml::common \ No newline at end of file diff --git a/src/common/compilation_database.h b/src/common/compilation_database.h new file mode 100644 index 00000000..7a98f27c --- /dev/null +++ b/src/common/compilation_database.h @@ -0,0 +1,78 @@ +/** + * src/common/compilation_database.h + * + * Copyright (c) 2021-2023 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include "common/model/enums.h" +#include "common/model/namespace.h" +#include "common/model/template_parameter.h" +#include "config/config.h" +#include "types.h" +#include "util/util.h" + +#include +#include +#include + +#include +#include +#include + +namespace clanguml::common { + +class compilation_database_error : public std::runtime_error { + using std::runtime_error::runtime_error; +}; + +class compilation_database : public clang::tooling::CompilationDatabase { +public: + compilation_database( + std::unique_ptr base, + const clanguml::config::config &cfg); + + ~compilation_database() override = default; + + static std::unique_ptr auto_detect_from_directory( + const clanguml::config::config &cfg); + + std::vector getCompileCommands( + clang::StringRef FilePath) const override; + + std::vector getAllFiles() const override; + + std::vector + getAllCompileCommands() const override; + + const clanguml::config::config &config() const; + + const clang::tooling::CompilationDatabase &base() const; + +private: + void adjust_compilation_database( + std::vector &commands) const; + + // Actual instance of the compilation database is stored in here + // The inheritance is just to keep the interface + std::unique_ptr base_; + + // Reference to the clang-uml config + const clanguml::config::config &config_; +}; + +using compilation_database_ptr = std::unique_ptr; + +} // namespace clanguml::common \ No newline at end of file diff --git a/src/common/generators/generators.cc b/src/common/generators/generators.cc index 1a650717..45419224 100644 --- a/src/common/generators/generators.cc +++ b/src/common/generators/generators.cc @@ -79,7 +79,7 @@ void generate_diagram_select_generator(const std::string &od, template void generate_diagram_impl(const std::string &od, const std::string &name, std::shared_ptr diagram, - const clang::tooling::CompilationDatabase &db, + const common::compilation_database &db, const std::vector &translation_units, const std::vector &generators, bool verbose) @@ -107,7 +107,7 @@ void generate_diagram_impl(const std::string &od, const std::string &name, void generate_diagram(const std::string &od, const std::string &name, std::shared_ptr diagram, - const clang::tooling::CompilationDatabase &db, + const common::compilation_database &db, const std::vector &translation_units, const std::vector &generators, bool verbose) @@ -139,9 +139,9 @@ void generate_diagram(const std::string &od, const std::string &name, } void generate_diagrams(const std::vector &diagram_names, - clanguml::config::config &config, const std::string &od, - const std::unique_ptr &db, - const int verbose, const unsigned int thread_count, + config::config &config, const std::string &od, + const common::compilation_database_ptr &db, const int verbose, + const unsigned int thread_count, const std::vector &generators, const std::map> &translation_units_map) @@ -184,17 +184,4 @@ void generate_diagrams(const std::vector &diagram_names, } } -void adjust_compilation_database(const clanguml::config::config &config, - clang::tooling::CompilationDatabase &db) -{ - if (config.add_compile_flags && !config.add_compile_flags().empty()) { - for (auto &compile_command : db.getAllCompileCommands()) { - compile_command.CommandLine.insert( - compile_command.CommandLine.begin() + 1, - config.add_compile_flags().begin(), - config.add_compile_flags().end()); - } - } -} - } // namespace clanguml::common::generators \ No newline at end of file diff --git a/src/common/generators/generators.h b/src/common/generators/generators.h index e85d2f8f..557319d9 100644 --- a/src/common/generators/generators.h +++ b/src/common/generators/generators.h @@ -20,6 +20,7 @@ #include "class_diagram/generators/json/class_diagram_generator.h" #include "class_diagram/generators/plantuml/class_diagram_generator.h" #include "cli/cli_handler.h" +#include "common/compilation_database.h" #include "common/generators/generators.h" #include "common/model/diagram_filter.h" #include "config/config.h" @@ -33,7 +34,6 @@ #include "version.h" #include -#include #include #include @@ -139,9 +139,6 @@ void find_translation_units_for_diagrams( const std::vector &compilation_database_files, std::map> &translation_units_map); -void adjust_compilation_database(const clanguml::config::config &config, - clang::tooling::CompilationDatabase &db); - template class diagram_ast_consumer : public clang::ASTConsumer { @@ -229,10 +226,9 @@ private: template -std::unique_ptr generate( - const clang::tooling::CompilationDatabase &db, const std::string &name, - DiagramConfig &config, const std::vector &translation_units, - bool /*verbose*/ = false) +std::unique_ptr generate(const common::compilation_database &db, + const std::string &name, DiagramConfig &config, + const std::vector &translation_units, bool /*verbose*/ = false) { LOG_INFO("Generating diagram {}", name); @@ -262,14 +258,14 @@ std::unique_ptr generate( void generate_diagram(const std::string &od, const std::string &name, std::shared_ptr diagram, - const clang::tooling::CompilationDatabase &db, + const common::compilation_database &db, const std::vector &translation_units, const std::vector &generators, bool verbose); void generate_diagrams(const std::vector &diagram_names, clanguml::config::config &config, const std::string &od, - const std::unique_ptr &db, int verbose, + const common::compilation_database_ptr &db, int verbose, unsigned int thread_count, const std::vector &generators, const std::map> diff --git a/src/config/config.h b/src/config/config.h index dcb8427a..807d80ef 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -226,6 +226,8 @@ struct config : public inheritable_diagram_options { option compilation_database_dir{ "compilation_database_dir", "."}; option> add_compile_flags{"add_compile_flags"}; + option> remove_compile_flags{ + "remove_compile_flags"}; option output_directory{"output_directory"}; option> diagram_templates{ diff --git a/src/config/yaml_decoders.cc b/src/config/yaml_decoders.cc index 2cca89eb..5c0479ee 100644 --- a/src/config/yaml_decoders.cc +++ b/src/config/yaml_decoders.cc @@ -588,6 +588,7 @@ template <> struct convert { get_option(node, rhs.output_directory); get_option(node, rhs.compilation_database_dir); get_option(node, rhs.add_compile_flags); + get_option(node, rhs.remove_compile_flags); get_option(node, rhs.include_relations_also_as_members); get_option(node, rhs.puml); get_option(node, rhs.generate_method_arguments); diff --git a/src/config/yaml_emitters.cc b/src/config/yaml_emitters.cc index 66a0b38e..a69589d0 100644 --- a/src/config/yaml_emitters.cc +++ b/src/config/yaml_emitters.cc @@ -188,6 +188,7 @@ YAML::Emitter &operator<<(YAML::Emitter &out, const config &c) out << c.compilation_database_dir; out << c.output_directory; out << c.add_compile_flags; + out << c.remove_compile_flags; out << dynamic_cast(c); diff --git a/src/main.cc b/src/main.cc index e6acb353..825911a7 100644 --- a/src/main.cc +++ b/src/main.cc @@ -17,6 +17,7 @@ */ #include "cli/cli_handler.h" +#include "common/compilation_database.h" #include "common/generators/generators.h" #include "include_diagram/generators/plantuml/include_diagram_generator.h" #include "util/util.h" @@ -53,38 +54,33 @@ int main(int argc, const char *argv[]) if (res == cli::cli_flow_t::kError) return 1; - std::string err{}; - auto db = clang::tooling::CompilationDatabase::autoDetectFromDirectory( - cli.config.compilation_database_dir(), err); + try { + const auto db = + clanguml::common::compilation_database::auto_detect_from_directory( + cli.config); - if (!err.empty()) { - LOG_ERROR("Failed to load compilation database from {}", - cli.config.compilation_database_dir()); + const auto compilation_database_files = db->getAllFiles(); + + std::map /*translation units*/> + translation_units_map; + + // We have to generate the translation units list for each diagram + // before scheduling tasks, because std::filesystem::current_path cannot + // be trusted with multiple threads + clanguml::common::generators::find_translation_units_for_diagrams( + cli.diagram_names, cli.config, compilation_database_files, + translation_units_map); + + clanguml::common::generators::generate_diagrams(cli.diagram_names, + cli.config, cli.effective_output_directory, db, cli.verbose, + cli.thread_count, cli.generators, translation_units_map); + } + catch (clanguml::common::compilation_database_error &e) { + LOG_ERROR("Failed to load compilation database from {} due to: {}", + cli.config.compilation_database_dir(), e.what()); return 1; } - const auto compilation_database_files = db->getAllFiles(); - - std::map /*translation units*/> - translation_units_map; - - // We have to generate the translation units list for each diagram before - // scheduling tasks, because std::filesystem::current_path cannot be trusted - // with multiple threads - clanguml::common::generators::find_translation_units_for_diagrams( - cli.diagram_names, cli.config, compilation_database_files, - translation_units_map); - - // - // Inject any additional compilation flags from the config to the - // compilation database - // - clanguml::common::generators::adjust_compilation_database(cli.config, *db); - - clanguml::common::generators::generate_diagrams(cli.diagram_names, - cli.config, cli.effective_output_directory, db, cli.verbose, - cli.thread_count, cli.generators, translation_units_map); - return 0; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4c17b96e..bd41026d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,7 +1,9 @@ file(GLOB_RECURSE TEST_CASE_SOURCES t*/*.cc t*/*.c t*/src/*.c) file(GLOB_RECURSE TEST_CASE_CONFIGS t*/.clang-uml) -file(GLOB_RECURSE TEST_CONFIG_YMLS test_config_data/*.yml) +file(GLOB_RECURSE TEST_CONFIG_YMLS test_config_data/*.yml + test_compilation_database_data/*.yml + test_compilation_database_data/*.json) set(TEST_CASES_REQUIRING_CXX20 t00056 t00058 t00059) @@ -36,6 +38,7 @@ set(TEST_CASES test_util test_model test_cases + test_compilation_database test_decorator_parser test_config test_cli_handler diff --git a/tests/test_cases.cc b/tests/test_cases.cc index 980272a7..ed337623 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -19,6 +19,7 @@ #include "test_cases.h" #include "cli/cli_handler.h" +#include "common/compilation_database.h" #include "common/generators/generators.h" #include @@ -33,32 +34,28 @@ void inject_diagram_options(std::shared_ptr diagram) diagram->generate_links.set(links_config); } -std::pair> +std::pair load_config(const std::string &test_name) { - auto config = clanguml::config::load(test_name + "/.clang-uml", true); + std::pair + res; + + res.first = clanguml::config::load(test_name + "/.clang-uml", true); LOG_DBG("Loading compilation database from {}", - config.compilation_database_dir()); + res.first.compilation_database_dir()); - std::string err{}; - auto compilation_database = - clang::tooling::CompilationDatabase::autoDetectFromDirectory( - config.compilation_database_dir(), err); + res.second = + clanguml::common::compilation_database::auto_detect_from_directory( + res.first); - if (!err.empty()) { - LOG_ERROR("Failed to load compilation database from {}", - config.compilation_database_dir()); - throw std::runtime_error{err}; - } - - return std::make_pair(std::move(config), std::move(compilation_database)); + return res; } namespace detail { template -auto generate_diagram_impl(clang::tooling::CompilationDatabase &db, +auto generate_diagram_impl(clanguml::common::compilation_database &db, std::shared_ptr diagram) { using diagram_config = DiagramConfig; @@ -117,7 +114,7 @@ auto generate_diagram_json( } std::unique_ptr generate_class_diagram( - clang::tooling::CompilationDatabase &db, + clanguml::common::compilation_database &db, std::shared_ptr diagram) { return detail::generate_diagram_impl( @@ -125,7 +122,7 @@ std::unique_ptr generate_class_diagram( } std::unique_ptr -generate_sequence_diagram(clang::tooling::CompilationDatabase &db, +generate_sequence_diagram(clanguml::common::compilation_database &db, std::shared_ptr diagram) { return detail::generate_diagram_impl( @@ -133,7 +130,7 @@ generate_sequence_diagram(clang::tooling::CompilationDatabase &db, } std::unique_ptr -generate_package_diagram(clang::tooling::CompilationDatabase &db, +generate_package_diagram(clanguml::common::compilation_database &db, std::shared_ptr diagram) { return detail::generate_diagram_impl( @@ -141,7 +138,7 @@ generate_package_diagram(clang::tooling::CompilationDatabase &db, } std::unique_ptr -generate_include_diagram(clang::tooling::CompilationDatabase &db, +generate_include_diagram(clanguml::common::compilation_database &db, std::shared_ptr diagram) { return detail::generate_diagram_impl( diff --git a/tests/test_cases.h b/tests/test_cases.h index 93073628..4ebd5bdb 100644 --- a/tests/test_cases.h +++ b/tests/test_cases.h @@ -23,6 +23,7 @@ #include "class_diagram/model/diagram.h" #include "class_diagram/visitor/translation_unit_visitor.h" #include "common/clang_utils.h" +#include "common/compilation_database.h" #include "config/config.h" #include "include_diagram/generators/plantuml/include_diagram_generator.h" #include "include_diagram/visitor/translation_unit_visitor.h" @@ -54,8 +55,7 @@ using Catch::Matchers::VectorContains; using namespace clanguml::util; -std::pair> +std::pair load_config(const std::string &test_name); std::string generate_sequence_puml( diff --git a/tests/test_compilation_database.cc b/tests/test_compilation_database.cc new file mode 100644 index 00000000..b1ed87a9 --- /dev/null +++ b/tests/test_compilation_database.cc @@ -0,0 +1,86 @@ +/** + * tests/test_compilation_database.cc + * + * Copyright (c) 2021-2023 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#define CATCH_CONFIG_MAIN + +#include "common/compilation_database.h" + +#include "util/util.h" + +#include "catch.h" +#include +#include + +std::shared_ptr make_sstream_logger(std::ostream &ostr) +{ + auto oss_sink = std::make_shared(ostr); + return std::make_shared( + "clanguml-logger", std::move(oss_sink)); +} + +TEST_CASE("Test compilation_database should work", "[unit-test]") +{ + using clanguml::common::compilation_database; + using clanguml::common::compilation_database_ptr; + using clanguml::common::model::access_t; + using clanguml::common::model::relationship_t; + using clanguml::util::contains; + + auto cfg = + clanguml::config::load("./test_compilation_database_data/config.yml"); + + try { + const auto db = + clanguml::common::compilation_database::auto_detect_from_directory( + cfg); + + auto all_files = db->getAllFiles(); + + REQUIRE(all_files.size() == 3); + REQUIRE(all_files.at(0) == + "src/class_diagram/generators/json/class_diagram_generator.cc"); + REQUIRE(all_files.at(1) == + "src/class_diagram/generators/plantuml/" + "class_diagram_generator.cc"); + REQUIRE(all_files.at(2) == "src/class_diagram/model/class.cc"); + + auto ccs = db->getAllCompileCommands(); + + REQUIRE(contains(ccs.at(0).CommandLine, "-Wno-error")); + REQUIRE(contains(ccs.at(0).CommandLine, "-Wno-unknown-warning-option")); + REQUIRE( + !contains(ccs.at(0).CommandLine, "-Wno-deprecated-declarations")); + } + catch (clanguml::common::compilation_database_error &e) { + REQUIRE(false); + } +} + +TEST_CASE("Test compilation_database should throw", "[unit-test]") +{ + using clanguml::common::compilation_database; + using clanguml::common::compilation_database_error; + using clanguml::common::compilation_database_ptr; + using clanguml::util::contains; + + auto cfg = clanguml::config::load( + "./test_compilation_database_data/config_bad.yml"); + + REQUIRE_THROWS_AS( + clanguml::common::compilation_database::auto_detect_from_directory(cfg), + compilation_database_error); +} \ No newline at end of file diff --git a/tests/test_compilation_database_data/config.yml b/tests/test_compilation_database_data/config.yml new file mode 100644 index 00000000..22bef806 --- /dev/null +++ b/tests/test_compilation_database_data/config.yml @@ -0,0 +1,7 @@ +compilation_database_dir: . +output_directory: . +add_compile_flags: [-Wno-unknown-warning-option, -Wno-error] +remove_compile_flags: [-Wno-deprecated-declarations] +diagrams: + class_main: + type: class \ No newline at end of file diff --git a/tests/test_compilation_database_data/config_bad.yml b/tests/test_compilation_database_data/config_bad.yml new file mode 100644 index 00000000..d842062a --- /dev/null +++ b/tests/test_compilation_database_data/config_bad.yml @@ -0,0 +1,5 @@ +compilation_database_dir: /tmp/no_such_directory___ +output_directory: . +diagrams: + class_main: + type: class \ No newline at end of file