| @@ -1,5 +1,6 @@ | |||
| #include "./build.hpp" | |||
| #include <dds/catch2_embedded.hpp> | |||
| #include <dds/compdb.hpp> | |||
| #include <dds/logging.hpp> | |||
| #include <dds/usage_reqs.hpp> | |||
| @@ -111,6 +112,76 @@ load_usage_requirements(path_ref project_root, path_ref build_root, path_ref use | |||
| 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 | |||
| void dds::build(const build_params& params, const package_manifest& man) { | |||
| @@ -130,6 +201,11 @@ void dds::build(const build_params& params, const package_manifest& man) { | |||
| lib_params.build_tests = params.build_tests; | |||
| lib_params.build_apps = params.build_apps; | |||
| lib_params.enable_warnings = params.enable_warnings; | |||
| if (man.test_driver) { | |||
| prepare_test_driver(lib_params, params, man); | |||
| } | |||
| for (const library& lib : libs) { | |||
| lib_params.out_subdir = fs::relative(lib.path(), params.root); | |||
| pkg.add_library(library_plan::create(lib, lib_params, ureqs)); | |||
| @@ -23,6 +23,12 @@ class shared_compile_file_rules { | |||
| public: | |||
| 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() const noexcept { return _impl->inc_dirs; } | |||
| @@ -63,11 +63,22 @@ library_plan library_plan::create(const library& lib, | |||
| 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)) { | |||
| // Pick a subdir based on app/test | |||
| auto 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, | |||
| lib.manifest().name, | |||
| params.out_subdir / "obj"), | |||
| @@ -0,0 +1,7 @@ | |||
| #pragma once | |||
| namespace dds::detail { | |||
| extern const char* const catch2_embedded_single_header_str; | |||
| } // namespace dds::detail | |||
| @@ -45,6 +45,10 @@ struct library_build_params { | |||
| bool build_tests = false; | |||
| bool build_apps = 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); | |||
| @@ -11,16 +11,18 @@ | |||
| using namespace dds; | |||
| 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()), | |||
| kvs, | |||
| lm::read_required("Name", ret.name), | |||
| lm::read_opt("Namespace", ret.namespace_), | |||
| lm::read_required("Version", version_str), | |||
| lm::read_accumulate("Depends", depends_strs), | |||
| lm::read_opt("Test-Driver", opt_test_driver), | |||
| lm::reject_unknown()); | |||
| if (ret.name.empty()) { | |||
| @@ -31,6 +33,20 @@ package_manifest package_manifest::load_from_file(const fs::path& fpath) { | |||
| throw std::runtime_error( | |||
| 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()) { | |||
| ret.namespace_ = ret.name; | |||
| } | |||
| @@ -10,9 +10,16 @@ | |||
| namespace dds { | |||
| enum class test_lib { | |||
| catch_, | |||
| catch_main, | |||
| catch_runner, | |||
| }; | |||
| struct package_manifest { | |||
| std::string name; | |||
| std::string namespace_; | |||
| std::optional<test_lib> test_driver; | |||
| semver::version version; | |||
| std::vector<dependency> dependencies; | |||
| static package_manifest load_from_file(path_ref); | |||