@@ -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); |