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