| #include "./build.hpp" | #include "./build.hpp" | ||||
| #include <dds/catch2_embedded.hpp> | |||||
| #include <dds/compdb.hpp> | #include <dds/compdb.hpp> | ||||
| #include <dds/logging.hpp> | #include <dds/logging.hpp> | ||||
| #include <dds/usage_reqs.hpp> | #include <dds/usage_reqs.hpp> | ||||
| return usage_requirement_map::from_lm_index(idx); | return usage_requirement_map::from_lm_index(idx); | ||||
| } | } | ||||
| void prepare_catch2_driver(library_build_params& lib_params, | |||||
| test_lib test_driver, | |||||
| const build_params& params, | |||||
| const package_manifest& man) { | |||||
| fs::path test_include_root = params.out_root / "_test_inc"; | |||||
| lib_params.test_include_dirs.emplace_back(test_include_root); | |||||
| auto catch_hpp = test_include_root / "catch2/catch.hpp"; | |||||
| fs::create_directories(catch_hpp.parent_path()); | |||||
| auto hpp_strm = open(catch_hpp, std::ios::out | std::ios::binary); | |||||
| hpp_strm.write(detail::catch2_embedded_single_header_str, | |||||
| std::strlen(detail::catch2_embedded_single_header_str)); | |||||
| hpp_strm.close(); | |||||
| if (test_driver == test_lib::catch_) { | |||||
| // Don't generate a test library helper | |||||
| } | |||||
| std::string fname; | |||||
| std::string definition; | |||||
| if (test_driver == test_lib::catch_main) { | |||||
| fname = "catch-main.cpp"; | |||||
| definition = "CATCH_CONFIG_MAIN"; | |||||
| } else if (test_driver == test_lib::catch_runner) { | |||||
| fname = "catch-runner.cpp"; | |||||
| definition = "CATCH_CONFIG_RUNNER"; | |||||
| } else { | |||||
| assert(false && "Impossible: Invalid `test_driver` for catch library"); | |||||
| std::terminate(); | |||||
| } | |||||
| shared_compile_file_rules comp_rules; | |||||
| comp_rules.defs().push_back(definition); | |||||
| auto catch_cpp = test_include_root / "catch2" / fname; | |||||
| auto cpp_strm = open(catch_cpp, std::ios::out | std::ios::binary); | |||||
| cpp_strm << R"(#include "./catch.hpp"\n)"; | |||||
| cpp_strm.close(); | |||||
| auto sf = source_file::from_path(catch_cpp, test_include_root); | |||||
| assert(sf.has_value()); | |||||
| compile_file_plan plan{comp_rules, std::move(*sf), "Catch2", "v1"}; | |||||
| build_env env; | |||||
| env.output_root = params.out_root / "_test-driver"; | |||||
| env.toolchain = params.toolchain; | |||||
| auto obj_file = plan.calc_object_file_path(env); | |||||
| if (!fs::exists(obj_file)) { | |||||
| spdlog::info("Compiling Catch2 test driver (This will only happen once)..."); | |||||
| plan.compile(env); | |||||
| } | |||||
| lib_params.test_link_files.push_back(obj_file); | |||||
| } | |||||
| void prepare_test_driver(library_build_params& lib_params, | |||||
| const build_params& params, | |||||
| const package_manifest& man) { | |||||
| auto& test_driver = *man.test_driver; | |||||
| if (test_driver == test_lib::catch_ || test_driver == test_lib::catch_main | |||||
| || test_driver == test_lib::catch_runner) { | |||||
| prepare_catch2_driver(lib_params, test_driver, params, man); | |||||
| } else { | |||||
| assert(false && "Unreachable"); | |||||
| std::terminate(); | |||||
| } | |||||
| } | |||||
| } // namespace | } // namespace | ||||
| void dds::build(const build_params& params, const package_manifest& man) { | void dds::build(const build_params& params, const package_manifest& man) { | ||||
| lib_params.build_tests = params.build_tests; | lib_params.build_tests = params.build_tests; | ||||
| lib_params.build_apps = params.build_apps; | lib_params.build_apps = params.build_apps; | ||||
| lib_params.enable_warnings = params.enable_warnings; | lib_params.enable_warnings = params.enable_warnings; | ||||
| if (man.test_driver) { | |||||
| prepare_test_driver(lib_params, params, man); | |||||
| } | |||||
| for (const library& lib : libs) { | for (const library& lib : libs) { | ||||
| lib_params.out_subdir = fs::relative(lib.path(), params.root); | lib_params.out_subdir = fs::relative(lib.path(), params.root); | ||||
| pkg.add_library(library_plan::create(lib, lib_params, ureqs)); | pkg.add_library(library_plan::create(lib, lib_params, ureqs)); |
| public: | public: | ||||
| shared_compile_file_rules() = default; | shared_compile_file_rules() = default; | ||||
| auto clone() const noexcept { | |||||
| auto cp = *this; | |||||
| cp._impl = std::make_shared<rules_impl>(*_impl); | |||||
| return cp; | |||||
| } | |||||
| auto& include_dirs() noexcept { return _impl->inc_dirs; } | auto& include_dirs() noexcept { return _impl->inc_dirs; } | ||||
| auto& include_dirs() const noexcept { return _impl->inc_dirs; } | auto& include_dirs() const noexcept { return _impl->inc_dirs; } | ||||
| extend(in_libs, ureqs.link_paths(link.namespace_, link.name)); | extend(in_libs, ureqs.link_paths(link.namespace_, link.name)); | ||||
| } | } | ||||
| auto test_in_libs = in_libs; | |||||
| extend(test_in_libs, params.test_link_files); | |||||
| auto test_rules = compile_rules.clone(); | |||||
| extend(test_rules.include_dirs(), params.test_include_dirs); | |||||
| for (const source_file& source : ranges::views::concat(app_sources, test_sources)) { | for (const source_file& source : ranges::views::concat(app_sources, test_sources)) { | ||||
| // Pick a subdir based on app/test | |||||
| auto subdir | auto subdir | ||||
| = source.kind == source_kind::test ? params.out_subdir / "test" : params.out_subdir; | = source.kind == source_kind::test ? params.out_subdir / "test" : params.out_subdir; | ||||
| link_executables.emplace_back(in_libs, | |||||
| compile_file_plan(compile_rules, | |||||
| // Pick compile rules based on app/test | |||||
| auto rules = source.kind == source_kind::test ? test_rules : compile_rules; | |||||
| // Pick input libs based on app/test | |||||
| auto& exe_link_libs = source.kind == source_kind::test ? test_in_libs : in_libs; | |||||
| // TODO: Apps/tests should only see the _public_ include dir, not both | |||||
| link_executables.emplace_back(exe_link_libs, | |||||
| compile_file_plan(rules, | |||||
| source, | source, | ||||
| lib.manifest().name, | lib.manifest().name, | ||||
| params.out_subdir / "obj"), | params.out_subdir / "obj"), |
| #pragma once | |||||
| namespace dds::detail { | |||||
| extern const char* const catch2_embedded_single_header_str; | |||||
| } // namespace dds::detail |
| bool build_tests = false; | bool build_tests = false; | ||||
| bool build_apps = false; | bool build_apps = false; | ||||
| bool enable_warnings = false; | bool enable_warnings = false; | ||||
| // Extras for compiling tests: | |||||
| std::vector<fs::path> test_include_dirs; | |||||
| std::vector<fs::path> test_link_files; | |||||
| }; | }; | ||||
| std::vector<library> collect_libraries(path_ref where); | std::vector<library> collect_libraries(path_ref where); |
| using namespace dds; | using namespace dds; | ||||
| package_manifest package_manifest::load_from_file(const fs::path& fpath) { | package_manifest package_manifest::load_from_file(const fs::path& fpath) { | ||||
| auto kvs = lm::parse_file(fpath); | |||||
| package_manifest ret; | |||||
| std::string version_str; | |||||
| std::vector<std::string> depends_strs; | |||||
| auto kvs = lm::parse_file(fpath); | |||||
| package_manifest ret; | |||||
| std::string version_str; | |||||
| std::vector<std::string> depends_strs; | |||||
| std::optional<std::string> opt_test_driver; | |||||
| lm::read(fmt::format("Reading package manifest '{}'", fpath.string()), | lm::read(fmt::format("Reading package manifest '{}'", fpath.string()), | ||||
| kvs, | kvs, | ||||
| lm::read_required("Name", ret.name), | lm::read_required("Name", ret.name), | ||||
| lm::read_opt("Namespace", ret.namespace_), | lm::read_opt("Namespace", ret.namespace_), | ||||
| lm::read_required("Version", version_str), | lm::read_required("Version", version_str), | ||||
| lm::read_accumulate("Depends", depends_strs), | lm::read_accumulate("Depends", depends_strs), | ||||
| lm::read_opt("Test-Driver", opt_test_driver), | |||||
| lm::reject_unknown()); | lm::reject_unknown()); | ||||
| if (ret.name.empty()) { | if (ret.name.empty()) { | ||||
| throw std::runtime_error( | throw std::runtime_error( | ||||
| fmt::format("'Version' field in [{}] may not be an empty string", fpath.string())); | fmt::format("'Version' field in [{}] may not be an empty string", fpath.string())); | ||||
| } | } | ||||
| if (opt_test_driver) { | |||||
| auto& test_driver_str = *opt_test_driver; | |||||
| if (test_driver_str == "Catch-Main") { | |||||
| ret.test_driver = test_lib::catch_main; | |||||
| } else if (test_driver_str == "Catch-Runner") { | |||||
| ret.test_driver = test_lib::catch_runner; | |||||
| } else if (test_driver_str == "Catch") { | |||||
| ret.test_driver = test_lib::catch_; | |||||
| } else { | |||||
| throw std::runtime_error( | |||||
| fmt::format("Unknown 'Test-Driver': '{}'", test_driver_str)); | |||||
| } | |||||
| } | |||||
| if (ret.namespace_.empty()) { | if (ret.namespace_.empty()) { | ||||
| ret.namespace_ = ret.name; | ret.namespace_ = ret.name; | ||||
| } | } |
| namespace dds { | namespace dds { | ||||
| enum class test_lib { | |||||
| catch_, | |||||
| catch_main, | |||||
| catch_runner, | |||||
| }; | |||||
| struct package_manifest { | struct package_manifest { | ||||
| std::string name; | std::string name; | ||||
| std::string namespace_; | std::string namespace_; | ||||
| std::optional<test_lib> test_driver; | |||||
| semver::version version; | semver::version version; | ||||
| std::vector<dependency> dependencies; | std::vector<dependency> dependencies; | ||||
| static package_manifest load_from_file(path_ref); | static package_manifest load_from_file(path_ref); |